fcm: getToken returns some JWT instead of the instance ID

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

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 10
  • Comments: 18 (3 by maintainers)

Commits related to this issue

Most upvoted comments

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
})

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

Long version: It seems the problem did not arise from Capacitor3 upgrade, but from pull request #88 (precisely: commit 18bb0a0b33bd4ed71b9b990a0153f9d06541003b) 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

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>

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.

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))
  })
}