react-native-push-notification icon indicating copy to clipboard operation
react-native-push-notification copied to clipboard

[iOS] how to access the APNs Token

Open th0r0nd0r opened this issue 4 years ago • 14 comments

Question

Is there any method I can call to access the APNs token?

While testing on the simulator, requesting permissions after a hard device reset, onRegister does not appear to be called, and invoking requestPermissions only returns an object containing permissions. My question is:

What method(s), if any, can I call on a simulator or device to reliably return the APNs token once it exists, and what form do those return values take? And is there any way to access the token outside of onRegister?

e.g. something like requestPermissions().then(results => console.log('token: ', results.token))

Thanks!

th0r0nd0r avatar Oct 12 '20 22:10 th0r0nd0r

Hi,

Did you test on real devices ? Simulator doesn't support notifications on iOS.

Regards

Dallas62 avatar Oct 13 '20 05:10 Dallas62

@Dallas62 Testing on a sideloaded build I'm not seeing onRegister called after calling PushNotification.requestPermissions and allowing permissions from the system prompt. Should notifications work for sideloaded builds or do I need to publish my app to TestFlight?

But I'm also wondering how to access the APNs token in general, assuming everything was working properly. Is there a method that will return it? If so, what does that return look like?

Thanks!

th0r0nd0r avatar Oct 14 '20 01:10 th0r0nd0r

Once the device registers against the push server you get a token. So in the onRegister (within the configuration) handle you can access it. In my case I dispatch an action and save the token in the store. But you can send it to your sever or do whatever you want. I didn't find any other way of accessing the token

vinagrito-getpocketful avatar Oct 14 '20 05:10 vinagrito-getpocketful

+1 How do we get the token sometime after onRegister gets triggered?

What if a user denies the system prompt and then later enables push notifications in settings? How do we get the token then? Or what if registering the token with the push notification provider fails? There must be a getToken method we can call at any point, right?

The Apple documentation recommends we get the token each time as opposed to storing it:

Important

Never cache device tokens in local storage. APNs issues a new token when the user restores a device from a backup, when the user installs your app on a new device, and when the user reinstalls the operating system. If you ask the system to provide the token each time, you are guaranteed to get an up-to-date token.

santiagogarza avatar Oct 15 '20 21:10 santiagogarza

@santiagogarza I do agree on the fact that having an easy access to it is very comfortable and very convenient. Also agree on the fact that purely relying on its value being within the store it not the way to go. I use the store as a mid-step before storing it in my server. It can actually not even be saved in the store - just dispatch an action that will send it over to your server.

Now, tokens do change on new installs (I'm not sure about app updates but it wouldn't surprise me) and restores. A token is the result of successfully subscribing to a push server which in this library is achieved by calling the configure function with the config object. That means that if you make a new install you need to register the device against the server.

Having that said one way of making sure you have a valid token and getting access to it would be calling the configure method whenever needed (depending on your specific needs) - which again takes us to the onRegister handler where the token will be accessible to you. There you can make sure that it is always being updated on your server.

vinagrito-getpocketful avatar Oct 17 '20 07:10 vinagrito-getpocketful

Thanks for the answers, everyone.

@vinagrito-getpocketful my understanding and the testing I've done so far lead me to believe that onRegister is only invoked after allowing permissions from the system prompt, or allowing them from settings for the first time if the prompt was denied. We are currently calling PushNotificaion.configure on every app open, which does allow us to define a new, arbitrary callback to pass to the onRegister method, but it doesn't force onRegister to fire and give access to the token.

So that answers @santiagogarza's first question, however we've just tested and found that if the initial onRegister method fails due to network issues, it will never fire again. Maybe there's a way to solve this with onRegistrationError or some other method but I don't know when that method will fire so I'll have to do more testing. As of now it seems there is no way to access the push token if the initial call to onRegister fails.

th0r0nd0r avatar Oct 20 '20 00:10 th0r0nd0r

Hi all,

We hit the similar problem with iOS real devices, where we ask for the system permissions later in the app (when we present a custom dialog), however it seems that the token at that point of time is not generated and stays empty. We use the following code:

Notifications.configure({ onRegister: async ({ token }) => { await AsyncStorage.setItem("token", token); }, onNotification: (notification: PushNotification) => { notification.finish(PushNotificationIOS.FetchResult.NoData); }, onRegistrationError: (err) => { // log exception }, permissions: { alert: true, badge: true, sound: true }, popInitialNotification: true, requestPermissions: false });

Could please someone advise what we should do/change to avoid this issues?

defigor avatar Jan 22 '21 23:01 defigor

For iOS, you should refer to the iOS repository.

Dallas62 avatar Jan 23 '21 09:01 Dallas62

For iOS, you should refer to the iOS repository.

Thanks, @Dallas62, will check out the iOS repository for this issue.

defigor avatar Jan 23 '21 19:01 defigor

@defigor did you download firebase? there is a method for obtaining APN token. I use firebase and iOS notifications library and the token is the same from the function and from the onRegister

axeljeremy7 avatar Feb 23 '21 19:02 axeljeremy7

@axeljeremy7 Thanks for the info. From my understanding, you can only retrieve APN token from the firebase library if you use Firebase to send iOS push notifications, but if you use APNs (Apple Push Notifications service) then you should not be using firebase. And in our case we use APNs directly.

defigor avatar Feb 23 '21 19:02 defigor

@defigor is you just use iOS you don't need firebase since the app is for both platforms I use firebase but the apn token from firebase is the same ad the retrieved from iOS notifications react library. the firebase apn token is not actually from firebase, it just a wrapper to get apn that is for apple, so maybe your .m file has wrong functions, debug there maybe

axeljeremy7 avatar Feb 24 '21 16:02 axeljeremy7

For me, I always favour using PushNotification.requestPermissions() on my own terms, as to not besiege the user on app launch. However, this led me here, as I too, found that onRegister didn't reliably execute. And if it did, it happened only once. This event was obviously too ephemeral to work with.

So, in extension to @th0r0nd0r's helpful discoveries, I found a technique to ensure that onRegister continues to fire every time, after permissions have been granted by the user:

PushNotification.checkPermissions((permissions) => {
  if (!permissions.alert) {
    // Insufficient permissions. Bail early.
    return;
  }

  // Sufficient permissions. Let's configure.
  PushNotification.configure({
    onRegister: (config) => {
      // This should now fire every time...
    },
    // Clearly, we have already got these permissions, else we wouldn't be here.
    // But, this appears to produce the expected behaviour:
    requestPermissions: true,
    permissions: {
      alert: true,
    }
  });
});

To be clear, the crutial change that worked for me, was to ensure I passed through the permissions object and set requestPermissions to true. Unexpected approach, but it works. And saves me having to debug native code (for now at least).

pjlangley avatar May 28 '21 13:05 pjlangley

I ran into this problem. Luckily my manager was a former ios dev and recommended me to use this snippet of code

// used to trigger didRegisterForRemoteNotifications:withDeviceToken.
fileprivate func triggerPushTokenRegistration(waitTimeSec: Double = 0) {
  let notificationCenter = UNUserNotificationCenter.current()
  
  notificationCenter.getNotificationSettings { settings in
    if settings.authorizationStatus == .authorized {
      // use a bit of timeout to let JS side initialize... gotta be better way to handle this
      DispatchQueue.main.asyncAfter(deadline: .now() + waitTimeSec) {
        print(AppDelegate.LOG_TAG, "triggering push token registration")
        UIApplication.shared.registerForRemoteNotifications()
      }
    }
  }
}

The key is this lets you manually use UIApplication.shared.registerForRemoteNotifications() which will trigger getting a push token from APNS server as indiciated in docs here, which in turn will trigger this bit of code...

// called when user has opted into push via prompt, or we manually trigger via `triggerPushTokenRegistration` to get push token
override func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
  RNCPushNotificationIOS.didRegisterForRemoteNotifications(withDeviceToken: deviceToken)
}

I decided to run the triggerPushTokenRegistration function whenever the app becomes active for scenarios where user turns push on/off from settings and returns to app

override func applicationDidBecomeActive(_ application: UIApplication) {
  print(AppDelegate.LOG_TAG, "app is active")
  triggerPushTokenRegistration(waitTimeSec: 5)
}

Note that this is all in our AppDelegate.swift.

Also, I had to add an arbitrary timeout to the token registration check to make sure the token registered event was initialized on the JS side of things. Firing it right away causes event to drop on cold start. Maybe someone knows a better way to handle that like listening to react context event that says "hey, react here, its safe to emit events to JS"

ChrisLFieldsII avatar Oct 13 '22 23:10 ChrisLFieldsII