How to get Firebase FCM token outside the Swift AppDelegate - ios

I have a SwiftUI app and inside it's custom AppDelagte I get the FCM token and save it to my database. Which works.
But when the user registers and gets put to the main screen of the applcation, their FCM token isn't uploaded to the database until they restart the app and the AppDelagte messaging function is run again. So basically, when the user registers, the app will not function correctly until they restart the app.
I have tried to get and then send the FCM token as soon as they successfully register via:
Messaging.messaging().fcmToken
which returns an empty string, meaning it is only made after they re run the app delegate.
How do I get around this, and send the FCM token to their database profile as soon as they register?

You could store the fcmToken on a static property of the AppDelegate as soon as you get it and then access it whenever the user registers.
Add a static property to the app delegate:
class AppDelegate: UIResponder, UIApplicationDelegate {
static var fcmToken: String?
// when you get the token set it to the static property
Self.fcmToken = fcmToken
}
Then when you need the token simply access:
AppDelegate.fcmToken

You can use this method at any time to access the token instead of storing it.
Messaging.messaging().token { token, error in
if let error = error {
print("Error fetching FCM registration token: \(error)")
} else if let token = token {
print("FCM registration token: \(token)")
self.fcmRegTokenMessage.text = "Remote FCM registration token: \(token)"
}
}
https://firebase.google.com/docs/cloud-messaging/ios/client

Related

Do i need to "didReceiveRegistrationToken"?

I want to send notifications, but only to authenticated users. This means, if my token updates (potential reasons: app is restored on a new device, user uninstalls/reinstall the app or user clears app data) my user will also not be authenticated.
Thus, whenever my user tries to login i update the token, which i receive via this method, for my backend:
Messaging.messaging().token { token, error in
// ...
}
When the user logs out i delete the token, so why would i implement this method here:
func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
}
However, if i do not implement it, i receive this:
9.5.0 - [FirebaseMessaging][I-FCM002023] The object <Name.Delegate: 0x2827c0650> does not respond to -messaging:didReceiveRegistrationToken:. Please implement -messaging:didReceiveRegistrationToken: to be provided with an FCM token.
In this function you must Save your device "Token" or "refresh token" as Local:
"didReceiveRegistrationToken"
When the user logged in you must register the push-token in the backend
When the user logged out you must clear the push-token in the backend
You must after login set a key value in "KeyChain" such as:
"userId" : "x"
"For the users that before login, cleared or uninstalled the app":
In each launch you must check your app user is login or not?
if "NO" (doesn't have the token):
You must clear the push-token with "userId -> x" that you saved on keychain. (request it to the backend)
Albeit the new push-token is different with the previouse push-token.
if "YES" (user had backend token)
you must save the new "push-token" or "refresh-push-token" from below function:
"didReceiveRegistrationToken"

iOS Google SignIn refreshed idToken has missing profile info in backend authentication

I use GoogleSignIn for iOS (GoogleSignIn-iOS), v6.1.0, in my iOS app.
All calls to my backend have the idToken in the request header.
The id token is verified in the backend. Here I also need to retrieve the users email and name.
(see also: https://developers.google.com/identity/sign-in/ios/backend-auth)
After a new SignIn with GIDSignIn.sharedInstance.signIn everything works fine.
GIDSignIn.sharedInstance.currentUser.profile contains email and name.
When sending the idToken to the backend, the Verifier gives me name and email in its payload, too.
Before I do a backend request, I get a valid (=not expired) idToken, with the following code:
private static func refreshToken(_ authentication: GIDAuthentication) async throws -> GIDAuthentication {
try await withCheckedThrowingContinuation { continuation in
authentication.do { authentication, error in
if let authentication = authentication {
continuation.resume(returning: authentication)
} else if let error = error {
Log.warn("Google SignIn refreshToken failed with -> \(error)")
continuation.resume(throwing: error)
}
}
}
}
I use the following code to get the idToken, before I create the request for my URLSession.
func idToken() async -> String {
do {
guard let user = GIDSignIn.sharedInstance.currentUser else {
Log.error("No GID user to get idToken from")
return ""
}
currentAuth = try await Self.refreshToken(user.authentication) //currentAuth is a class variable
return currentAuth?.idToken ?? ""
} catch {
print("Error during Google SignIn idToken retrieval \(error)")
return ""
}
}
And now my problem comes:
The idToken is refreshed properly. It is valid for another hour, and the verifier in my backend accepts it.
But I can't get the users name from the verified payload data in the backend, the name field is null.
Same happens when I use GIDSignIn.sharedInstance.restorePreviousSignIn (which I call on every app re-start, to do the silent sign in. (But in the app, the values are there in the updated users object profile)
It seems to me, that when the idToken gets refreshed, that it looses the profile scope.
I hope someone can help me with this, or at least explain the behaviour to me.
Thank in advance :)
Update
I checked the idTokens on https://jwt.io.
They are valid, but after the refresh, the jwt payload definitely is missing the profile data, like the users name.
I waited one day and tried again. Now the silent signin after app start gives me a complete idToken with jwt payload including name, but only once. After an hour, when the idToken gets refreshed, the idToken is again missing the profile information
Unfortunately I got no hint here, so I solved my problem as follows.
I hope this approach can save time for some others in the future.
I only require the profile data, when the user logs in to the backend the first time and a new user record is created in the backend.
In all other calls, where I need the JWT for authentication, I only rely on the basic information (ID, email) and handle all other values as optional values.
So I check the users name, if it is available. Otherwise the ID and a valid token is of course sufficent for authentication.

Some registrations don't seem to be registered in Notification Hub

I'm trying to integrate my Android and iOS native apps with Notification Hub.
For example, on iOS, as soon as I receive the deviceToken and if my user is already authenticated, I register directly with NotificationHub:
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
log.debug("Received device token \(deviceToken.hexEncodedString()). Saving device token to user defaults.", "AppDelegate.didRegisterForRemoteNotificationsWithDeviceToken")
let defaults = UserDefaults.standard
defaults.set(deviceToken, forKey: UserNotificationDeviceTokenKey)
defaults.synchronize()
if let authenticatedUser = Session.shared.authenticatedUser {
log.debug("Trying to register device token \(deviceToken.hexEncodedString()) to user \(authenticatedUser.id) on notification hub with hub name \(String(describing: hubName)) and connection string \(String(describing: connectionString)). Notification hub null? \(notificationHub == nil)")
self.notificationHub?.registerNative(withDeviceToken: deviceToken, tags: Set<String>([authenticatedUser.id]), completion: { error in
if (error != nil) {
log.error("Error registering for notifications: \(error.debugDescription)", "AppDelegate.didRegisterForRemoteNotificationsWithDeviceToken");
} else {
log.debug("Successfully registered device token \(deviceToken.hexEncodedString()) for user with ID \(authenticatedUser.id) and email \(authenticatedUser.emailAddress)", "AppDelegate.didRegisterForRemoteNotificationsWithDeviceToken")
}
})
}
}
And whatever happens, I start by saving the device token to the user defaults so that when a user does log in, he can retrieve the deviceToken from user defaults and call the same registerNative method to create a registration associating this deviceToken with a tag that is the user's identifier:
func userDidSignIn(user: User) {
if let appDelegate = UIApplication.shared.delegate as? AppDelegate,
let deviceToken = UserDefaults.standard.data(forKey: UserNotificationDeviceTokenKey){
log.debug("Trying to register device token \(deviceToken.hexEncodedString()) to user \(user.id) on notification hub with hub name \(String(describing: appDelegate.hubName)) and connection string \(String(describing: appDelegate.connectionString)). Notification hub null? \(appDelegate.notificationHub == nil)")
appDelegate.notificationHub?.registerNative(withDeviceToken: deviceToken, tags: Set<String>([user.id]), completion: { error in
if (error != nil) {
log.error("Error registering for notifications: \(error.debugDescription)");
} else {
log.debug("Successfully registered device token \(deviceToken.hexEncodedString()) for user with ID \(user.id) (\(user.emailAddress)) who just logged in", context: "HTRootViewController.userDidSignIn")
}
})
} else {
log.warning("No device token found in settings.", "HTRootViewController.userDidSignIn")
}
}
I have all my logs remotely sent to Bugfender, which lets me check the logs for all the devices using this app installed in TestFlight, so I can see that all the registerNative's are successful:
Trying to register device token
0fea9a4d99ec37dc4f3ac252c35fa4e1617004fd14740973d80a7dfdaeacc857 to
user 77bfb1c6-b05a-440b-a7a0-71ae5a91bbb2 on notification hub with hub
name Optional("[my notification hub name]") and connection string
Optional("[my notification hub connection string]").
Notification hub null? false
Successfully registered device
token 0fea9a4d99ec37dc4f3ac252c35fa4e1617004fd14740973d80a7dfdaeacc857
for user with ID 77bfb1c6-b05a-440b-a7a0-71ae5a91bbb2
([my user's email]) who just logged in
But then, when I load the list of all the registrations using this .NET C# code:
public async Task<List<RegistrationDescription>> GetRegistrations()
{
var hub = NotificationHubClient.CreateClientFromConnectionString([my notification hub connection string], [my notification hub name]);
var result = await hub.GetAllRegistrationsAsync(1000);
return result.ToList();
}
I can see that some registrations are there, but others are nowhere to be found, even though they succeeded according to my mobile logs.
Does anyone know what else could be going on? What else can I check? Why else would registrations not be saved even though the call to registerNative seems to succeed?
EDIT: As a matter of fact, and after checking more precisely what is happening, the registration first appears in the list of registrations returned by Notification Hub, but as soon as we try to send a notification, it's as if Notification Hub could not reach the corresponding device token and deleted the registration altogether. Is there a way to see the logs of Notification Hub to see what is going on and why it decides to delete this registration?
I figured it out. It turns out it was working on my device because I ran the beta in debug mode, so it was getting its device token from the Sandbox APS environment, which matched the Sandbox endpoint configured in Notification Hub, but other users were using the Production APS environment configured in the archive build, so they got device tokens for a different environment and notification hub correctly identified as incorrect device tokens. Once we switched the Notification Hub to the production APNS endpoint, all the TestFlight users were able to receive notifications because their registrations stayed in Notification Hub. Now that does mean that my debug builds trying to use that environments won't receive notifications, but that's OK, that's why we have a different environment and Notification Hub for development purposes. So if anyone sees something similar, don't forget to double check your Notification Hub configuration matches the type of build your users are using, and don't forget that TestFlight builds are now production-type builds, same as App Store builds.

React native FCM iOS push notification not delivered

first of all environment is React native, I try to setup push notification in iOS following instruction from rnfirebase package here what step i do and I do testing on Real iPhone Device
create key
then add to firebase
add Google-service.plist and add setup following from firebase doc
and acivate capabillity
then I install pod package
run app got token
use token send on cloud messaging console
notification not delivered I didn't know what wrong because message also send from FCM and what happens on APNS where I get an error just confused
thank for advance
and also try by connecting directly with pusher
and also not received again
then try to use Onesignal with same certificate with Firebase is work even on the test message
The token you are using in Pusher doesn't look right. My tokens for remote notifications look like this.
I was just testing silent pushes, but the token would look similar for regular alerts. Here is the code I use to grab the token. My guess is that you are pushing a bad token up to FCM.
fileprivate func convertHexDataToString(_ data: Data) -> String {
var string: String = ""
for i in 0..<data.count {
string += String(format: "%02.2hhx", data[i] as CVarArg)
}
return string
}
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
let tokenString = convertHexDataToString(deviceToken)
#if DEBUG
print("APN sandbox token: '\(tokenString)'")
Messaging.messaging().setAPNSToken(deviceToken, type: .sandbox)
#else
print("APN prod token: '\(tokenString)'")
Messaging.messaging().setAPNSToken(deviceToken, type: .prod)
#endif
}

Firebase auth custom token iOS

I am trying to add to my app VK authorization with Firebase SDK.
When user authorization finished, I try to pass token to Firebase.
func vkSdkAccessAuthorizationFinishedWithResult(result: VKAuthorizationResult!){
let tokenString = result.token.description
FIRAuth.auth()?.signInWithCustomToken(tokenString) { (user, error) in
// ...
}
}
Error: [1] (null) "NSLocalizedDescription" : "The custom token format is incorrect. Please check the documentation."
Can I use Firebase with custom auth without running server?
From Firebase Doc. Create custom tokens using the Firebase SDK,
I would say that you need to use createCustomToken() method for this.. you can not use your VK servers token to signIn with firebase ... you need to generate token for firebase with createCustomToken() method.
var uid = "some-uid";
var customToken = firebase.auth().createCustomToken(uid);
May this work for you

Resources