NotificationHub Register issue at IOS (React Native) - ios

I'm trying to register notificationHub from React Native. I created a NativeModule and I'm sending connectionString, hubName, tags and deviceToken - deviceToken was saved to AsyncStorage (I'm getting deviceToken before user logged and I can't register Notification Hub without user tag (ex: scholarshipID) so I'm saving deviceToken for later usage. Because I can't send user tags to didRegisterForRemoteNotificationsWithDeviceToken from RN )
After user login I'm getting user scholarshipID & deviceToken and sending to native module for NotificationHub register.
// deviceToken:(NSString *)tokenForLaterUsage
[hub
registerNativeWithDeviceToken:tokenForLaterUsage
tags:self->_tags
completion:^(NSError* error)
{
if (error != nil) {
NSLog(#"Error registering for notifications: %#", error);
}else{
NSLog(#"Success");
}
}];
My tokenForLaterUsage is string and if I try use directly app is crashing. Because NotificationHub register function is waiting NSData so I'm converting NSString to NSData like this way:
NSData* data = [tokenForLaterUsage dataUsingEncoding:NSUTF8StringEncoding];
After this I'm going Azure Portal and trying to send notification but system saying no device found.
BTW : When I try to register to NotificationHub in here: didRegisterForRemoteNotificationsWithDeviceToken it's work well but I can't set tags so this is not for me. From here I'm just getting deviceToken for later usage.
--
note: I'm thinking 2 workaround to solve this issue (But I'm not sure if works.):
Workaround: I don't know but if I can I'll not register at first and when user will be login I'll get deviceToken on IOS and I'll try to register to NotificationHub.
Workaround: I'll send my deviceToken to directly backend with restApi and backEnd will pair user tags and deviceToken.
Sorry things mixed but I need to solve this. Also sorry for my English :).

Finally I found the answer.
At first when deviceToken is generated I'm storing at NSUserDefaults
NSUserDefaults *userPreferences = [NSUserDefaults standardUserDefaults];
[userPreferences setObject:deviceToken forKey:#"deviceToken"];
[userPreferences synchronize];
And when user login I'm getting the deviceToken from NSUserDefault in NativeModules and sending to NotificationHub.
NSData *token = [[NSUserDefaults standardUserDefaults] objectForKey:#"deviceToken"];

Related

How to supply ios device token to AWS Pinpoint

I'm unable to perform Direct Messaging using the AWS Pinpoint console. Each attempt results in a "Failed to submit push notification to..." error whether I provide an EndpointID or a DeviceToken. I'm guessing that I am not providing a valid device token.
On iOS, the deviceToken is delivered as an NSData object. I can print it's value to the console as string by using [deviceToken description]. This is the string I pasted into the Direct Message console (after trimming angle brackets and internal space chars). Is this the expected way to generate a string from the native deviceToken for AWS messaging? My other guess, [[NSString alloc] initWithData:deviceToken encoding:NSUTF8StringEncoding], returns nil.
Normally I would do this:
deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
But after checking AWS documentation website basically they are saying to print the device token and remove the spaces.
- (void)application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken
{
NSLog(#"deviceToken: %#", deviceToken);
}
Please check this website:
Getting Started with Apple Push Notification Service
The Data object already contains a method to do this easily
deviceToken.toHexString()

ios storing login token in KeyChain fails to retrieve, rarely and randomly, but consistently

I'm using the ios keychain (keychainItemWrapper / SSKeychain) to store my app's login token and maintain logged in state. Currently I store a simple NSDictionary in the keychain containing my token, a token expiry and a refresh token. I serialize it to NSData and storing using kSecValueData. I also set the kSecAttrAccount and kSecAttrService, but don't use those for auth.
This works great, about 95% of the time. The problem is that that randomly, unpredictably and sporadically, the keychain does not return data when I request it to retrieve the token. It is usually after a moderate time away from the app, when reopening it. It doesn't have to be from in background, or after any specific delay though.
It fails specifically when asking for my NSData below and returns <> instead of <ABCD EFGH IJKL ....>. I think it is nil. Thus the code thinks the user isn't logged in and drops them immediately on my App's Signup/Login landing page, without logout error, token expiry error, etc. If I minimize the app, then reopen, it almost always gets the correct keychain info and the user is logged in again.
This creates a confusing experience when encountered. It also means the user can't maintain this true 100% logged in state, with occasionally being randomly logged out. I've been unable to predict it or debug it and changing keychain libraries, as shown below, hasn't fixed it for me. It happens for me, and several TestFlight users, and in our production app currently.
Any suggestions how to maintain keychain integrity and loading 100% of time? We're about ready to implement an NSUserDefaults backup storage on the token to use in these cases, something I really don't want to do to store an auth token.
Storing:
// load keychain
KeychainItemWrapper *keychainItem = [KeychainItemWrapper keyChainWrapperForKeyID:kcIdentifier];
NSString *firstLaunch = [keychainItem objectForKey: (__bridge id)(kSecAttrAccount)];
if (firstLaunch == nil){
// initialize if needed
[keychainItem setObject:email forKey: (__bridge id)(kSecAttrAccount)];
[keychainItem setObject:kcIdentifier forKey: (__bridge id)kSecAttrService];
[keychainItem setObject:(id)kSecAttrAccessibleAfterFirstUnlock forKey:(id)kSecAttrAccessible];
}
// serialize "auth" NSDictionary into NSData and store
NSString *error;
NSData *dictionaryData = [NSPropertyListSerialization dataFromPropertyList:auth format:NSPropertyListXMLFormat_v1_0 errorDescription:&error];
[keychainItem setObject:dictionaryData forKey:(id)kSecValueData];
Loading:
// after similar KeychainItemWrapper initialization as above
NSData *dictionaryData = [keychainItem objectForKey:(id)kSecValueData];
NSString *error;
NSDictionary *auth = [NSPropertyListSerialization propertyListFromData:dictionaryData mutabilityOption:NSPropertyListImmutable format:nil errorDescription:&error];
NSString *token = auth[#"access_token"];
I have also tried using the SSKeychain library CocoaPod that is widely available, and a wrapper around the keychain logic. It is a cleaner access but fails with the same issue. Here I'm just storing NSString values since there was no direct way to store NSData in the lib.
// store in keychain
[SSKeychain setAccessibilityType:kSecAttrAccessibleAfterFirstUnlock];
[SSKeychain setPassword:auth[#"access_token"] forService:SSKEYCHAIN_SERVICE account:SSKEYCHAIN_TOKEN];
[SSKeychain setPassword:auth[#"expires_at"] forService:SSKEYCHAIN_SERVICE account:SSKEYCHAIN_EXPIRES_AT];
[SSKeychain setPassword:auth[#"refresh_token"] forService:SSKEYCHAIN_SERVICE account:SSKEYCHAIN_REFRESH_TOKEN];
// load from keychain
[SSKeychain setAccessibilityType:kSecAttrAccessibleAfterFirstUnlock];
NSString *token = [SSKeychain passwordForService:SSKEYCHAIN_SERVICE account:SSKEYCHAIN_TOKEN];
NSString *expires_at = [SSKeychain passwordForService:SSKEYCHAIN_SERVICE account:SSKEYCHAIN_EXPIRES_AT];
NSString *refresh_token = [SSKeychain passwordForService:SSKEYCHAIN_SERVICE account:SSKEYCHAIN_REFRESH_TOKEN];
Keychain does have issues at the moment, and for quite a while really. It sounds like you're getting off lightly as usually when it breaks a force-quit of the app is required to bring it back to life.
One thing that helps is to access the keychain just once on the first request and then cache the result in memory, if it's already in memory then just return it from there.
If you can observe a specific error when this happens then trap it and retry or, as is the current case for some unfortunate apps, kill the app. Killing the app is actually the current guidance from Apple if you raise a tech ticket to discuss the issue with them.
The only other real solution is to encrypt the data and store it in a file, but then you have issues with encryption keys so this is little better than obfuscation against a keen attacker.

Is there a way to send user-specific push notifications for iOS devices?

I would like to know if I can create a service to send customized user-specific push notifications for iOS.
Example
#"Hey %#, how you doin", firstName"
Is this possible?
Unless I completely misunderstood what you need, no answer has what you need.
Here's a relevant example from the APNS guide :
let’s consider an example. The provider specifies the following dictionary as the value of the alert property:
{ "aps" :
{
"alert" : {
"loc-key" : "GAME_PLAY_REQUEST_FORMAT",
"loc-args" : [ "Jenna", "Frank"]
}
}
}
When the device receives the notification, it uses "GAME_PLAY_REQUEST_FORMAT" as a key to look up the associated string value in the Localizable.strings file in the .lproj directory for the current language. Assuming the current localization has an Localizable.strings entry such as this:
"GAME_PLAY_REQUEST_FORMAT" = "%# and %# have invited you to play Monopoly";
the device displays an alert with the message “Jenna and Frank have invited you to play Monopoly”.
Of course. Check out the APNS programming guide, specifically the payload, which you can customize on your server before you send it to the user's device.
https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/ApplePushService.html
Also note that if you know when the notification should appear (not at a dynamic time) then look into local notifications, which don't require a server backend.
You can use any service for Push Notification (as listed) or do it by yourself.
In your code, when you receive the Push Notification message:
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
NSLog(#"Received notification: %#", userInfo);
//here you should treat your received data
}
There are some very good solutions for iOS push notifications service that use APNS. No need to implement it on your own.
PushApps - free for 1M notifications per month, and unlimited notifications for 19.99 per month, and you can import your users via the API or by sending the a csv file - documentation
Urban Airship - free up to 1M notifications per month, afterwards you are charged per 1000 notifications
Parse - also free for the first 1M notifications
PushWoosh - free for 1M devices, premium plans are from 39 EURO
If you have to implement it on your own, have a look at easyAPNS if you want to host it yourself.
Another good site for info is Ray Wenderlich's site which hosts a 2 part tutorial:
Apple Push Notification Services Tutorial: Part 1/2
Apple Push Notification Services Tutorial: Part 2/2
Or this, Apple Push Notification module for Node.js.
Of course you can...
You need to create your own back-end php file for push notification service, you can send json data from your app to the php file and use $GET to achieve the data you want, including device token, message, badge number... please refer to this
in your viewController
NSString *finalUrl =[NSString stringWithFormat:#"%#/pushService.php?device_token=%#&passphrase=%#&msg=%#",baseURL,device_token,passphrase,msg];
NSURL *url =[NSURL URLWithString:finalUrl];
NSData *jsonData = [NSData dataWithContentsOfURL:url];
if(jsonData != nil)
{
NSError *error = nil;
NSDictionary *result = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];
NSLog(#"result: %#", result);
}else
{
NSLog(#"connection error!");
}
pushService.php file
// Put your device token here (without spaces):
$deviceToken = $GET['device_token'];
// Put your private key's passphrase here
$passphrase = $GET['passphrase'];
// Put your alert message here:
$message = $GET['msg'];
$body['aps'] = array(
'alert' => $message,
'sound' => 'default',
'badge' => 5
);

Is it possible to cache the auth token to speed up login in Firebase with iOS?

I'm authenticating to Firebase with FirebaseSimpleLogin and Email/Password authentication in iOS. It seems that making the [authClient loginWithEmail:username andPassword:password withCompletionBlock:^(NSError *error, FAUser *user) { ... }]; takes roughly 5-8 seconds to complete.
Is there a way to speed up the login, like caching the authToken from FAUser, and using starting to use that directly in the first Firebase call?
Update:
It seems that storing the authToken after a successful login to NSUserDefaults:
[[NSUserDefaults standardUserDefaults] setValue:user.authToken forKey:USERDEFAULTS_LOGIN_TOKEN];
[[NSUserDefaults standardUserDefaults] synchronize];
... and then doing an authWithCredential: call with the stored authToken on next login attempt:
NSString *authToken = [[NSUserDefaults standardUserDefaults] stringForKey:USERDEFAULTS_LOGIN_TOKEN];
if (authToken) {
NSLog(#"Firebase logging in with token...");
[[Mesh root] authWithCredential:authToken withCompletionBlock:^(NSError *error, id data) { ...
... isn't any faster. Is there another way to speed up the login?
With the release of the Firebase iOS / OS-X Client v1.2.0, Firebase caches the local client authentication state and greatly optimizes the speed of re-authentication. Previous client versions required multiple server roundtrips before the client would enter an "authenticated" state, but this is now immediate if a valid, persisted session has been located on-disk.
Also note that Firebase Simple Login has been deprecated and replaced with a reimplementation of Firebase authentication that is enabled in the core Firebase client libraries. Check out https://www.firebase.com/docs/ios/guide/user-auth.html for the guides on how to get started with it on iOS.

Dropbox Sync API AccessToken

When I used the core API I simply used the code
[dbsession updateAccessToken:#"..." accessTokenSecret:#"..." forUserId:#"..."];
to access my dropbox account from any copy of the app. But now I found out of this new Sync API that is easier and more flexible, but I didn't find any equivalent for the code displayed above. It now is:
DBAccountManager* accountMgr = [[DBAccountManager alloc] initWithAppKey:#"..." secret:#"..."];
[DBAccountManager setSharedManager:accountMgr];
??[DBAccountManager updateAccessToken:#"..." accessTokenSecret:#"..." forUserId:#"..."];??
How can I access my account? Where can I insert the AccessToken?
From your question, it seems that this method on DBAccountManager is the one for using your appKey and secret:
- (id)initWithAppKey:(NSString *)key secret:(NSString *)secret
From the documentation description, it says this method "...create[s] a new account manager with your app’s app key and secret. You can register your app or find your key at the apps page."
After you create an instance of DBAccountManager and set it to be the shared manager using [DBAccountManager setSharedManager:], you can login the specific user by calling this method:
[[DBAccountManager sharedManager] linkFromController:YOUR_ROOT_CONTROLLER];
Here's a description from the dropbox iOS tutorial:
"To start interacting with the Sync API, you'll need to create a DBAccountManager object. This object lets you link to a Dropbox user's account which is the first step to working with data on their behalf"
"...the linking process will switch to the Dropbox mobile app if it's installed. Once the user completes the authorization step, Dropbox will redirect them back to your app using the URL scheme you registered when setting up the SDK. Your app needs to handle those requests to complete the auth flow."
The final step as mentioned in the tutorial is to handle the redirect. Here's some code to do this:
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url sourceApplication:(NSString *)source annotation:(id)annotation {
DBAccount *account = [[DBAccountManager sharedManager] handleOpenURL:url];
if (account) {
NSLog(#"App linked successfully!");
return YES;
}
}
The user's account information can now be obtained through [DBAccountManager sharedManager].linkedAccount which is a DBAccount with properties like userId and accountInfo.
Here's a link to the docs for reference. Hope this helps!
Update
It seems I may have misunderstood your question. I am giving you instructions on how to use the Sync API and didn't quite clarify that there is actually no place for a user's accessToken in the API. This has been replaced with the web flow that I describe above.
You can achieve what you want by generating a callback url that dropbox uses in the sync API. First you need to set the dropbox.sync.nonce user setting to match whatever you pass in as the state parameter in the NSURL. Then set the oauth_token, oauth_token_secret, and uid params with what you used to pass into [DBAccountManager updateAccessToken:#"..." accessTokenSecret:#"..." forUserId:#"..."];. See below:
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setObject:#"9b0aa24b0bd50ce3a1a904db9d309c50"
forKey:#"dropbox.sync.nonce"];
[userDefaults synchronize];
NSURL *url =
[NSURL URLWithString:#"db-APP_KEY://1/connect?
oauth_token=updateAccessToken&
oauth_token_secret=accessTokenSecret&
uid=forUserId&
state=9b0aa24b0bd50ce3a1a904db9d309c50"];
[[UIApplication sharedApplication] openURL:url];
Notice how the state parameter is the same as the value stored in the user defaults. Keep in mind this is undocumented and may change in a later API version.

Resources