capacitor-community / fcm

Enable Firebase Cloud Messaging for Capacitor apps

Home Page:https://capacitor.ionicframework.com/docs/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

getToken returns some JWT instead of the instance ID

patryk-eco opened this issue · comments

Describe the bug
After updating to capacitor v3 and the current version of the fcm plugin, our call to getToken() stopped working. Instead of some FCM token it returns a JSON web token.

To Reproduce
Steps to reproduce the behavior:

  1. Setup an app with FCM
  2. Call requestPermissions() and register() on PushNotifications
  3. Call FCM.getToken()

Expected behavior
We expected it to return an fcm token which looks like a random string with a colon. e.g. eFznuNk5RsqUaR33qoswb7:APA91...
Instead we got a JWT token: jwt.io

Desktop (please complete the following information):

  • OS: Android
  • Browser: capacitor
  • Version: v3

Smartphone (please complete the following information):

  • Device: AVD Emulator - Pixel 3a API 30

Additional context

Ionic:

   Ionic CLI                     : 6.13.1 (/home/---/.npm-packages/lib/node_modules/@ionic/cli)
   Ionic Framework               : @ionic/angular 5.8.1
   @angular-devkit/build-angular : 12.2.3
   @angular-devkit/schematics    : 12.2.3
   @angular/cli                  : 12.2.3
   @ionic/angular-toolkit        : 4.0.0

Capacitor:

   Capacitor CLI   : 3.2.4
   @capacitor/core : 3.2.4

Cordova:

   Cordova CLI       : not installed
   Cordova Platforms : not available
   Cordova Plugins   : not available

Utility:

   cordova-res                          : not installed
   native-run (update available: 1.5.0) : 1.4.1

System:

   NodeJS : v15.14.0 (/home/---/.nvm/versions/node/v15.14.0/bin/node)
   npm    : 7.19.0
   OS     : Linux 5.14

commented

For this reason we use:

PushNotifications.addListener('registration', async ({ value }) => {
  let token = value // Push token for Android

  // Get FCM token instead the APN one returned by Capacitor
  if (Capacitor.getPlatform() === 'ios') {
    const { token: fcm_token } = await FCM.getToken()
    token = fcm_token
  }
  // Work with FCM_TOKEN
})

Alright, thanks for the tip! I read about that workaround somewhere, but dismissed it 😄

I'll try it out.

commented

Can someone explains why we have to do that ? I can't find any breaking change note in the changelog and when I browse project history, I can't find anything about that.
For example, this commit b1a89f0 is just a classic update to capacitor 3, but there is no change that explains why the spec has changed.

Please help, thanks :)

I have the same issue where I have to treat Android is a different way than iOS for getting the proper token. Weird.

For this reason we use:

PushNotifications.addListener('registration', async ({ value }) => {
  let token = value // Push token for Android

  // Get FCM token instead the APN one returned by Capacitor
  if (Capacitor.getPlatform() === 'ios') {
    const { token: fcm_token } = await FCM.getToken()
    token = fcm_token
  }
  // Work with FCM_TOKEN
})

Above not working.
Here is my WORKING Code:

getToken() {
    if (Capacitor.getPlatform() !== 'web') {
      PushNotifications.requestPermissions().then((permission) => {
        if (permission.receive == "granted") {

          PushNotifications.addListener('registration', async ({ value }) => {
            let token = value // Push token for Android
          
            // Get FCM token instead the APN one returned by Capacitor
            if (Capacitor.getPlatform() === 'ios') {
              const { token: fcm_token } = await FCM.getToken()
              token = fcm_token
            }
            // Work with FCM_TOKEN
            
            console.log(token);
          })
        } else {
          // No permission for push granted
          alert('No Permission for Notifications!')
        }
      });
    }
  }

Hi, previously when using getToken, I can get the token by using an async function returning the token. But if I changed to listener, it is not working anymore. Any solution?

 if (Capacitor.isNativePlatform()) {
      const permission = await PushNotifications.requestPermissions();
      if (permission.receive === 'granted') {
        await PushNotifications.register();
        const { token } = await FCM.getToken();
        return token;
      }
   }

I have gotten both types of tokens and neither seem to be working when it comes to receiving and displaying push notifications for Android 12 devices. Anyone else having this issue?

TL;DR: Cause of the issue is the migration of a breaking change in Firebase Android SDK 22.0 that went wrong in 18bb0a0 via #88

Long version:
It seems the problem did not arise from Capacitor3 upgrade, but from pull request #88 (precisely: commit 18bb0a0) where a breaking change in Firebase Android SDK 22.0 was tried to be migrated as documented. However, documentation here is a bit tricky, as migration differs depending on what you needed the token for: identifying the installation or getting an FCM token; seems like the instance id token did both jobs before.
So unfortunately the wrong part of the migration guide was chosen: migration to FirebaseInstallations instead of FirebaseMessaging.

My best guess is that they still might be the same but are not guaranteed to be the same? In the end, the migration needs to be done properly to FirebaseMessaging.

Capacitor 3's push-notification plugin still uses the deprecated FirebaseInstanceId method, which is probably why the suggest to only use SDK 21.0.1 in their docs. But at least this is working, that's why the workaround mentioned by @eljass works. But I would also really like to see that code updated to use the up-to-date solution based on FirebaseMessaging

Yes, and in newer version on this plugin is not even generating any token. But if i send notification from firebase console it wis working.

@sburnicki what's a good way to work around this in the mean time? I'm hesitant to release on Android because push notifications stopped working for me.

A good workaorund that works also for me is what ejlass proposen in the first comment: Only use the plugin on iOS and use the token from the capacitor push plugin on android.

It took a while before I came up with something usable but this is what I came up with:

import { PushNotifications } from '@capacitor/push-notifications'
import { FCM } from '@capacitor-community/fcm'

/**
 * Returns `true` if the user gave permission or false otherwise.
 */
async function askFcmPermission(): Promise<boolean> {
  const checked = await PushNotifications.checkPermissions()
  let status: 'prompt' | 'prompt-with-rationale' | 'granted' | 'denied' = checked.receive

  if (status === 'prompt' || status === 'prompt-with-rationale') {
    const requested = await PushNotifications.requestPermissions()
    status = requested.receive
  }

  return status === 'granted'
}

/**
 * Gets the FCM token in a non-breaking way.
 *
 * - For iOS we must use the `FCM` plugin, because `PushNotifications` returns the wrong APN token.
 * - For Android we must use `PushNotifications`, because `FCM` is broken for Android.
 * @see https://github.com/capacitor-community/fcm/issues/99
 */
export function getFcmToken(): Promise<string> {
  return new Promise((resolve, reject) => {
    if (Capacitor.getPlatform() === 'web') return resolve('')

    PushNotifications.addListener('registration', ({ value }) => {
      if (Capacitor.getPlatform() === 'android') {
        resolve(value)
        return
      }

      // Get FCM token instead the APN one returned by Capacitor
      if (Capacitor.getPlatform() === 'ios') {
        FCM.getToken()
          .then(({ token }) => resolve(token))
          .catch((error) => reject(error))
        return
      }

      // will never come here
      reject(new Error('?'))
    })

    askFcmPermission()
      .then((granted) => {
        if (granted) {
          PushNotifications.register().catch((error) => reject(error))
        } else {
          reject(new Error('denied'))
        }
      })
      .catch((error) => reject(error))
  })
}

I have gotten both types of tokens and neither seem to be working when it comes to receiving and displaying push notifications for Android 12 devices. Anyone else having this issue?

+1 - @eljass works for me with Android 10 but doesn't work for Android 12 devices.

I spent a lot of time on this problem.
How about adding it to the documentation?
https://github.com/capacitor-community/fcm#usage

A lot of people will be upgrading not starting with Capacitor 3 from scratch.

Each time the user opens my app (if it is 7 days or more since last time) I check the token to see if it has changed/expired due to a new device / install.
I was using await FCM.getToken() for this.

Should I now use PushNotifications.register() every time the user opens the app?
How can I get the current token against the one stored in my database?

Also I noticed that PushNotifications.addListener('registration') returns a result with the token right after I have registered the listener even when I have not called PushNotifications.register(). Has anyone else noticed this?
Is this perhaps because of some native code?

should be fixed by #111

This way you don't have to add additional code to Javascript/ts/tsx.
Summary:

  • Android FCM pushes work out of the box wit Capacitor docs.
  • iOS FCM pushes need to intercept APNs tokens, assign them to Messaging... and then fetch/post FCM PushID in AppDelegate.swift

*.tsx:

import { Device, DeviceId, DeviceInfo } from '@capacitor/device'
import { PushNotifications } from '@capacitor/push-notifications'
...
...
if (
      (await Device.getInfo()).platform !== 'web' &&
      Capacitor.isPluginAvailable('PushNotifications')
    ) {
      let permStatus = await PushNotifications.checkPermissions()
      if (permStatus.receive === 'prompt') {
        permStatus = await PushNotifications.requestPermissions()
      }

      if (permStatus.receive !== 'granted') {
        throw new Error('User denied permissions!')
      }

      await PushNotifications.addListener('registration', (token) => {
        // In this case Android and iOS get FCM PushID instead of APNs token: See: AppDelegate.switf
        log.info('PushRegistration token: ', token.value)
      })
      await PushNotifications.addListener('registrationError', (err) => {
        log.info('Registration error: ', err.error)
      })
      await PushNotifications.addListener('pushNotificationReceived', (notification) => {
        log.info('Push notification received: ', JSON.stringify(notification))
      })
      await PushNotifications.addListener('pushNotificationActionPerformed', (notification) => {
        log.info(
          'Push notification action performed',
          notification.actionId,
          notification.inputValue
        )
      })
      await PushNotifications.register()
    }
  }

AppDelegate.swift:

import Capacitor
import Firebase
import FirebaseMessaging
...
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        FirebaseApp.configure()
        return true
    }
...

func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        Messaging.messaging().apnsToken = deviceToken;
        Messaging.messaging().token { fcmToken, error in
            if let error = error {
                NotificationCenter.default.post(name: .capacitorDidFailToRegisterForRemoteNotifications, object: error)
                return
            }
            NotificationCenter.default.post(name: .capacitorDidRegisterForRemoteNotifications, object: fcmToken)
        }
    }

    func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
        NotificationCenter.default.post(name: .capacitorDidFailToRegisterForRemoteNotifications, object: error)
    }

    func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
        print("AppDelegate.swift: User Info: \(userInfo)")
    }

Pods:

pod 'Capacitor', :path => '../../node_modules/@capacitor/ios'
pod 'CapacitorDevice', :path => '../../node_modules/@capacitor/device'
pod 'CapacitorPushNotifications', :path => '../../node_modules/@capacitor/push-notifications'
...
... 
pod 'Firebase/Core'
pod 'Firebase/Messaging'

Other deps:
capacitor: v4.6.2
npm:
@capacitor/app@4.1.1
@capacitor/ios@4.6.2
@capacitor/push-notifications@4.1.2

Info.plist:
"Required background modes: Item 0: App downloads content in response to push notifications"

	<key>UIBackgroundModes</key>
	<array>
		<string>remote-notification</string>
	</array>