My published application makes use of the CFUUID and SSKeychain in order to identify the device (and to keep that ID unchanged even if the app is uninstalled and reinstalled)
I save those device ID in the server, and I recently noticed that some users have several of those IDs for the same real device. The only explanation I see is that the ID is not being saved or loaded from the Keychain and thus the device generates a new one. The strange thing is that it works fine on some other devices running the same iOS version.
Any ideas on what could be going on?
This is my related code in (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
NSString* identifier = #"appName";
NSString* serviceName = #"com.company.appName";
NSString *retrieveuuid = [SSKeychain passwordForService:serviceName account:identifier];
if (retrieveuuid == nil) {
CFUUIDRef theUUID = CFUUIDCreate(NULL);
CFStringRef string = CFUUIDCreateString(NULL, theUUID);
CFRelease(theUUID);
NSString *uuid = (NSString*) string;
[SSKeychain setPassword:uuid forService:serviceName account:identifier];
}
EDIT: My guess is that retrieveuuid == nil is not working as expected for some reason. Later in the app I register for push notifications and send the push token to the server together with this CFUUID that I read with the same exact line [SSKeychain passwordForService:serviceName account:identifier] but when it is sent to the server it is not nil (So I can see several CFUUIDs with the same push token).
EDIT 2 to attach more actual code.
AppDelegate.m
NSString* identifier = #"appName";
NSString* serviceName = #"com.company.appName";
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//Creating UUID
NSString *retrieveuuid = [AppDelegate getDeviceId];
if (retrieveuuid == nil) {
CFUUIDRef theUUID = CFUUIDCreate(NULL);
CFStringRef string = CFUUIDCreateString(NULL, theUUID);
CFRelease(theUUID);
NSString *uuid = (NSString*) string;
[SSKeychain setPassword:uuid forService:serviceName account:identifier];
}
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:
(UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert)];
}
+ (NSString*) getDeviceId {
return [SSKeychain passwordForService:serviceName account:identifier];
}
- (void)application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken
{
NSString *newToken = [deviceToken description];
newToken = [newToken stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:#"<>"]];
newToken = [newToken stringByReplacingOccurrencesOfString:#" " withString:#""];
_deviceToken = [[NSString alloc] initWithString:newToken];
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
NSString *user = [prefs objectForKey:#"appNameUsername"];
if(user && ![user isEqualToString:#""]){
RestClient *rest = [[RestClient alloc] init];
rest.delegate = self;
rest.tag = 2;
[rest updateToken:newToken ForUsername:user];
[rest release];
}
}
RestClient.m
-(void) updateToken:(NSString *)token ForUsername:(NSString *)userName{
NSArray* info = [NSArray arrayWithObject: [NSMutableDictionary dictionaryWithObjectsAndKeys:
userName, #"Username",
token, #"TokenNo",
[[UIDevice currentDevice].model hasPrefix:#"iPad"] ? #"iPad" : #"iPhone", #"Device",
[UIDevice currentDevice].systemVersion, #"OSVersion",
[AppDelegate getDeviceId], #"DeviceID",
#"updateToken", #"CMD",
nil]];
[self doAction:info];
}
The doAction method just sends the data to the server and then callback the delegate, that part works fine. I can see on the server the logs of receiving this command:
"JSONCMD":"[
{ "TokenNo" : "f0d3aa21758350333b7e6315c38_EDIT_257c1838f49c43049f8380ec1ff63",
"AppVersion" : "1.0.4",
"Username" : "user#server.com",
"CMD" : "updateToken",
"OSVersion" : "7.0.4",
"DeviceID" : "9B794E11-6EF7-470C-B319-5A9FCCDAFD2B",
"Device" : "iPhone"
}
]
I see 2 possible candidates causing the strange behaviour, the NSStrings in the controller body and the static getDevice method. However, I don't see how this could work in many devices but fail in others.
I've had the same problem and I've found the solution. The Problem occurs with devices where user has the passcode set to unlock the phone. On iOS7 some code can run in the background (push notifications, background fetch) and if you save something in the keychain with standard accessibility type, you cannot read it when the device is locked (and the app is running in the background). Try to set this:
[SSKeychain setAccessibilityType:kSecAttrAccessibleAlways];
before you read/write the keychain.
Hope it helps.
NSString *retrieveuuid = [SSKeychain passwordForService:serviceName
account:identifier];
The retrieveuuid depends with the service, witch is always the same thing, but it depends also with the account. I think your problem is that one user can have multiple accounts in the same device, then your code are generating a UUID for each account.
Related
This question already has answers here:
UIDevice uniqueIdentifier deprecated - What to do now?
(32 answers)
Closed 5 years ago.
I am creating an ios app, since the udid is deprecated How can we uniquely identify the app.
You can use both UDID & UUID
For UUID- let uuid = NSUUID().uuidString
For UDID- let Identifier = UIDevice.current.identifierForVendor?.uuidString
Below Code Generate Unique ID for device and IT IS UNIQUE upto you will RESET the device. Happy coding...:)
- (NSString *)createNewUUID {
NSString *UUID = [[NSUUID UUID] UUIDString];
return UUID;
}
//Check And Retrive UniqueIdentifier
-(void)checkAndGenerateUniqueIdentifier{
//Retrive UDID
NSString *retrieveuuid = [SSKeychain passwordForService:[[NSBundle mainBundle] bundleIdentifier] account:[[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString *)kCFBundleNameKey]];
if (retrieveuuid == nil) {
NSString *uuid = [self createNewUUID];
NSLog(#" Create new uuid : %#",uuid);
//Store the password in Keychain
NSError *error = nil;
[SSKeychain setPassword:uuid forService:[[NSBundle mainBundle] bundleIdentifier] account:[[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString *)kCFBundleNameKey] error:&error];
if ([error code] == SSKeychainErrorNotFound) {
NSLog(#"Password not found");
}
}
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//Check And Retrive UniqueIdentifier
[self checkAndGenerateUniqueIdentifier];
return YES;
}
Use Third party Keychain wrapper or iOS Keychain API for Store UUID.
Can worklight return the device token for Android/iPhone/BB and if so how?
More specifically, I'm not looking for the "device id" but the native device token.
Worklight can return the "device id", but this is different than the device token. For example Worklight: How to get current device ID for Push subscription states how to get the "device id" using the call
WL.Client.getUserInfo("wl_deviceNoProvisioningRealm", "userId");
Unfortunately this returns something different than the device token. When using the native iPhone call like so and comparing it to the WL deviceid it's obvious they are different.
- (void)didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
NSMutableDictionary *results = [NSMutableDictionary dictionary];
NSString *token = [[[[deviceToken description] stringByReplacingOccurrencesOfString:#"<"withString:#""]
stringByReplacingOccurrencesOfString:#">" withString:#""]
stringByReplacingOccurrencesOfString: #" " withString: #""];
[results setValue:token forKey:#"deviceToken"];
#if !TARGET_IPHONE_SIMULATOR
[results setValue:[[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleDisplayName"] forKey:#"appName"];
[results setValue:[[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleVersion"] forKey:#"appVersion"];
NSUInteger rntypes = [[UIApplication sharedApplication] enabledRemoteNotificationTypes];
// Set the defaults to disabled unless we find otherwise...
NSString *pushBadge = #"disabled";
NSString *pushAlert = #"disabled";
NSString *pushSound = #"disabled";
if(rntypes & UIRemoteNotificationTypeBadge){
pushBadge = #"enabled";
}
if(rntypes & UIRemoteNotificationTypeAlert) {
pushAlert = #"enabled";
}
if(rntypes & UIRemoteNotificationTypeSound) {
pushSound = #"enabled";
}
[results setValue:pushBadge forKey:#"pushBadge"];
[results setValue:pushAlert forKey:#"pushAlert"];
[results setValue:pushSound forKey:#"pushSound"];
// Get the users Device Model, Display Name, Token & Version Number
UIDevice *dev = [UIDevice currentDevice];
[results setValue:dev.name forKey:#"deviceName"];
[results setValue:dev.model forKey:#"deviceModel"];
[results setValue:dev.systemVersion forKey:#"deviceSystemVersion"];
[self successWithMessage:[NSString stringWithFormat:#"%#", token]];
#else
[self successWithMessage:[NSString stringWithFormat:#"%#", #"simulator generated"]];
#endif
}
Moreover, the native device token is needed for a third party notification platform which is outside of worklight and so using worklights messaging system isn't feasible.
You're correct APNs device token and Worklight deviceId is two different things. In case you require APNs device token for using some 3rd party notification platform you can override the didRegisterForRemoteNotificationsWithDeviceToken method in your application delegate thus receiving full control over the device token once it arrives from APNs
I have this peace of code to get device id :
-(void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
NSString *devicetokenRN = [NSString stringWithFormat:#"%#", deviceToken];
devicetokenRN = [devicetokenRN stringByReplacingOccurrencesOfString:#" " withString:#""];
devicetokenRN = [devicetokenRN stringByReplacingOccurrencesOfString:#"<" withString:#""];
devicetokenRN = [devicetokenRN stringByReplacingOccurrencesOfString:#">" withString:#""];
// send device token for APNS without spaces or <>
DeviceID *DeviceIdentifier = [[DeviceID alloc ] init];
// sending it to the Device.h
[DeviceIdentifier setDeviceID:devicetokenRN];
}
I need to retrieve this DeviceID as the first thing on my ViewController. As it doesn't do this function before it runs the view controller, because if I use NSlog in the ViewController it is NULL as it hasn't run this first. I need to get some variable like this first before it runs the UITableView. Any ideas would be brilliant thanks.
In AppDelegate, you can save the deviceToken value in NSUserDefaults like
put these line in didRegisterForRemoteNotificationsWithDeviceToken method in appdelegate.m like this
-(void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
[[NSUserDefaults standardUserDefaults] setObject:deviceToken forKey:#"DeviceToken"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
also put this line of code in your view Controller.m and In ViewDidLoad method
and Retrieve that value.
[[NSUserDefaults standardUserDefaults] objectForKey:#"DeviceToken"];
You can get and store your deviceId in NSUserDefaults
-(void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
NSUUID *identifierForVendor = [[UIDevice currentDevice] identifierForVendor];
NSString *deviceId = [identifierForVendor UUIDString];
NSUserDefaults *userRegistrarionDetails = [NSUserDefaults standardUserDefaults];
[userRegistrarionDetails setObject:deviceId forKey:#"deviceID"];
[userRegistrarionDetails synchronize];
}
And you can get anywhere in your whole project by
NSUserDefaults *userRegistrarionDetails = [NSUserDefaults standardUserDefaults];
NSString * deviceId = [userRegistrarionDetails objectForKey:#"deviceID"];
NSLog(#"%#", deviceId);
I have an ios app which loads a webpage to show data. I use push notifications for receiving news and I want that depending of which push is received, goes to one page or other (sections inside the same page).
In the text received in push notification, I add a word before the text, something like:
page1 - text
page2 - text2
page3 - text3
...
In android I take the first word and add to the webpage url: www.page.com/ + pageAdded
In iOs I think I haveto add this code to didFinishLaunchWithOptions function. But I don't know where I should add the new code for passing the arguments. I add my function so you can tell me where to put it.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
KeychainItemWrapper *keychainItem = [[KeychainItemWrapper alloc] initWithIdentifier:#"CityInfoPush" accessGroup:nil];
udid = [keychainItem objectForKey:(__bridge id)kSecValueData];
//if udid is empty , that means we need to generate one and save it on KeyChain
if([udid length] == 0){
NSLog(#"No CFUUID found. Creating one...");
//creating CFUUID
CFUUIDRef cfuuid = CFUUIDCreate(kCFAllocatorDefault);
NSString *cfuuidString = (NSString*)CFBridgingRelease(CFUUIDCreateString(kCFAllocatorDefault, cfuuid));
NSLog(#"Device CFUUID created is : %#" , cfuuidString);
//saving CFUUID on KeyChain
[keychainItem setObject: cfuuidString forKey:(__bridge id)kSecValueData];
//retrieving CFUUID from KeyChain and passing it to udid String.
udid = [keychainItem objectForKey:(__bridge id)kSecValueData];
}
//For reseting the keyChain (testing)
//[keychainItem resetKeychainItem];
NSLog(#"Password Saved in KeyChain is: %#" , udid);
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
self.viewController = [[ViewController alloc] initWithNibName:#"ViewController_iPhone" bundle:nil];
} else {
self.viewController = [[ViewController alloc] initWithNibName:#"ViewController_iPad" bundle:nil];
}
self.window.rootViewController = self.viewController;
[self.window makeKeyAndVisible];
// Let the device know we want to receive push notifications
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:
(UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert)];
return YES;
}
I add the message to the payload for sending it to the apple server like this:
// Create the payload body
$body['aps'] = array(
'alert' => $message,
'sound' => 'default'
);
// Encode the payload as JSON
$payload = json_encode($body);
I am trying to get the alert text (message) in ios like this, but always fails and crash the app:
NSString *text=[[lastNotif valueForKeyPath:#"aps"][0] objectForKey:#"alert"];
First, you can check if you app has been launched with a puch notification in your application:
didFinishLaunchingWithOptions: function with :
if ([launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey])
What I do is save this notification in the userDefaults :
[[NSUserDefaults standardUserDefaults] setObject:[launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey] forKey:#"notificationReceived"];
[[NSUserDefaults standardUserDefaults] synchronize];
Then in the viewDidLoad where you want to catch your push notification, check if you have one in memory :
NSDictionary *lastNotif = [[NSUserDefaults standardUserDefaults] objectForKey:#"notificationReceived"];
if (lastNotif != nil)
{
//handle what you want to do with your notification
//now don't forget to clean userDefaults
[[NSUserDefaults standardUserDefaults] removeObjectForKey:#"notificationReceived"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
I am using the APNS for receiving push notifications in my application.
The problem is that I am getting the same notifications on some devices but not all. What could the problem be here since I have been trying for about 15 days to solve this issue with no success. Th device token is updating successfully. Because had it not been then I wouldn't have been getting notification on any device. But the strange thing is I am getting it on half the devices. Please help!!
Here is the code for registering and receiving notifications. I don't have any code for server side. But as I said that the notifications are working on some devices. On android too they are working.
I have 3 devices here with me and its working on two of them.
iPad2:5.0.1
iPodTouch:4.3.3
Its not working on another iPod touch that I have on version:5.1
Also its showing successfully registered for APNS in all devices. But not sending notifications on some. What can the issue be? Is there something that I am missing?
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)devToken
{
deviceToken = [devToken retain];
NSLog(#"Registered for APNS %#", deviceToken);
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
NSMutableString *dev = [[NSMutableString alloc] init];
NSRange r;
r.length = 1;
unsigned char c;
for (int i = 0; i < [deviceToken length]; i++)
{
r.location = i;
[deviceToken getBytes:&c range:r];
if (c < 10) {
[dev appendFormat:#"0%x", c];
}
else {
[dev appendFormat:#"%x", c];
}
}
[ud setObject:dev forKey:#"DeviceToken"];
[ud synchronize];
}
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
{
NSLog(#"Failed to register %#", [error localizedDescription]);
deviceToken = nil;
}
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
for(int i=0;i<[viewControllers count];i++)
{
if([[viewControllers objectAtIndex:i] isKindOfClass:[Confirmation class]])
{
Confirmation *map = (Confirmation*)[[self.navigationController viewControllers] objectAtIndex:i];
[map setFinalInfo];
[self.navigationController popToViewController:[[self.navigationController viewControllers] objectAtIndex:i] animated:YES];
}
}
}
1 ) There is no guarantee that push notifications will actually be delivered, even if the APNS server accepted them.
2 ) As far as your server is concerned, push notifications are fire-and-forget; there is no way to find out what the status of a notification is after you’ve sent it to APNS. The delivery time may also vary, from seconds up to half an hour.
3 ) Also, the user’s iPhone may not be able to receive push notifications all the time. They could be on a WiFi network that does not allow connections to be made to APNS because the required ports are blocked. Or the phone could be turned off.
4 ) APNS will try to deliver the last notification it received for that device when it comes back online, but it will only try for a limited time. Once it times out, the push notification will be lost forever!
Are you setting the "expiry" to 0 in the APN message? If you set it to 0 push messages are try-once-and-forget, else Apple may try to deliver them till the time specified.
The other thing to check might be to be absolutely certain no device id's from your development test runs end up in the list of device id's you are actually going to send push messages in production. One faulty device id and Apple will cease the SSL connection and not process any more APN messages.
I replaced the following code:
deviceToken = [devToken retain];
NSLog(#"Registered for APNS %#", deviceToken);
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
NSMutableString *dev = [[NSMutableString alloc] init];
NSRange r;
r.length = 1;
unsigned char c;
for (int i = 0; i < [deviceToken length]; i++)
{
r.location = i;
[deviceToken getBytes:&c range:r];
if (c < 10) {
[dev appendFormat:#"0%x", c];
}
else {
[dev appendFormat:#"%x", c];
}
}
[ud setObject:dev forKey:#"DeviceToken"];
[ud synchronize];
By this:
[devToken retain];
NSLog(#"~~~~devToken=%#",devToken);
NSString *dt = [[devToken description] stringByTrimmingCharactersInSet: [NSCharacterSet characterSetWithCharactersInString:#"<>"]];
dt = [dt stringByReplacingOccurrencesOfString:#" " withString:#""];
// //DeviceToken = dt;
NSUserDefaults *def = [NSUserDefaults standardUserDefaults];
[def setObject:dt forKey:#"DeviceToken"];
[def synchronize];
and its working fine now!!
The problem was it wasnt updating the device toekn correctly for some devices which I am not sure why as it was working well for some.
Thanks guys!!