I'm using the Firebase Javascript SDK to add web push notifications to a React JS app. Through some sequence of events, we've wound up with two FCM tokens that appear to be from the same web client (same machine, browser and site domain). An FCM message sent to either token is received on the client. I would have expected one of the tokens to get invalidated, but that doesn't seem to be the case. Is it possible to identify and delete the extraneous token when this situation occurs?
The code to register our service worker and send the FCM token to our backend service looks like this:
firebase.initializeApp(Config.firebase);
this.messaging = firebase.messaging();
this.setupNotifications(this.messaging, this.loadAlerts.bind(this));
setupNotifications(messaging, handler) {
let _this = this;
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('./sw/firebase-messaging-sw.js')
.then(function(registration) {
registration.update();
_this.messaging.useServiceWorker(registration);
Notification.requestPermission().then(messaging.requestPermission)
.then(function (t) {
return messaging.getToken();
})
.then(function (token) {
// This saves the token to our server
Service.register(token);
})
.catch(function (error) {
console.log('Error occured: ' + error.message)
});
}).catch(e => {
console.log('Error during firebase registration: ' + e.message);
});
navigator.serviceWorker.addEventListener('message', event => {
if (event.data.incidents) {
// If the event data contains an incidents property, then this is a message from our
// service worker notification click handlers. Load details for the incidents.
window.location = some_url_path + event.data.incidents;
}
});
_this.messaging.onMessage(function (payload) {
handler();
});
_this.messaging.onTokenRefresh(function() {
messaging.getToken()
.then(function(refreshedToken) {
// This saves the refreshed token to our server
Service.register(refreshedToken);
})
.catch(function(err) {
console.log('Unable to retrieve refreshed token ', err);
});
});
}
}
A few (possibly) relevant points:
The web client had an active FCM token for some time (weeks). The new token was generated at some point, but now messaging.getToken() is returning the previous token.
Around the time the second token was generated, we had (mistakenly) changed the path to the service worker script that we pass to navigator.serviceWorker.register(). This appears to have resulted in two registered service workers (as seen in Chrome Developer Tools), but unregistering the worker with incorrect path had no effect.
Also around the same time we updated from the Firebase library we were using from 3.9.0 to 4.6.2
Through Chrome Developer tools, I can see there are two IndexedDBs, and one of them has an fcm_token_object_Store with the extraneous token, which is different than the token returned by messaging.getToken() displayed in the console output in the image below:
However it happened that these two FCM tokens got created, what my primary concern is how to get rid of one of them. Is there some way to get all active FCM tokens for a client instance so we could call messaging.deleteToken() on the extraneous one? It was only by fluke I was able to identify the two tokens in question on a machine I could debug on, so if this happens for a real end-user, I'm not sure how we'd correct the situation.
Related
Update
As Gerardo pointed out, vapidKey != FCM Token.
I finally got things going, though im unsure if this is the best way to do it.
This was what i needed to generate a NEW token:
try{
if(currentToken) await deleteToken(messaging,currentToken)
if(Notification.permission !== 'granted') await Notification.requestPermission();
currentToken = await getToken(messaging,vapidKey);
$.ajax({
type:"GET",
url:"/fcm/register-token",
data:{token:currentToken},
success:(saved)=>{
if(saved) console.log(saved,' New token registered ',currentToken)
else console.log('Unable to save token!')
}
})
}
catch(e){console.log(e)}
Question
I am testing FCM for push notifications in javascript. I have a single "Web Push Certificate" (vapidKey) generated manually in the FCM console UI. This works for sending a test notification to a single token, but i am unclear on how any additional users would be able to register a new key with FCM.
I must be misunderstanding something fundamental here, and am not even sure what to be searching for.
// TODO: Find out how to generate new registration token as needed
const vapidKey = {vapidKey:"BLgq_6FY0suqAy9llMyYX03o9K9n4FHba1gCnPz6KmG9oh6K8v3Ws4SIG9KTILfN0_8np9PfT5bPqgTDXfCA9dc"};
async function fcm_init(){
try{
// TODO: user interaction for notification permission
await Notification.requestPermission()
const currentToken = await getToken(messaging,vapidKey);
// TODO: Send token to server to save in tbl_fcm
console.log(currentToken);
}
catch(err){
console.log(err);
}
}fcm_init()
In this example, the vapidKey is hard-coded, but how would i go about getting a different key for each user in the app without manually doing so in the FCM Console UI?
This is where i have generated the key to get a token for a user. This is what im trying to avoid doing manually for each user on the system.
The vapidKey is needed for identifying the app (not the user). This is mentioned in the second screenshot "Firebase Cloud Messaging can use Application Identity key pairs to connect with external push services".
With this information, your app can generate an FCM Token (or registration token) in the client:
const currentToken = await getToken(messaging,vapidKey);
You can use these FCM tokens to send messages to a specific device. These FCM Tokens identify the device (user) and they will be different for each one.
Check this example on how you would send a message from the back end to a specific device using it's FCM token (registration token).
I am sending notification to my web application from FCM. It is working fine.
Now I have to do following two things :
I have to refresh the token each time when user visit to the portal
How user can un-subscribe from notification
I have to do the above two things using javascript or jquery.
// Callback fired if Instance ID token is updated.
messaging.onTokenRefresh(function() {
messaging.getToken().then(function(refreshedToken) {
console.log('Token refreshed.');
// Indicate that the new Instance ID token has not yet been sent to the
// app server.
setTokenSentToServer(false);
// Send Instance ID token to app server.
sendTokenToServer(refreshedToken);
// ...
}).catch(function(err) {
console.log('Unable to retrieve refreshed token ', err);
showToken('Unable to retrieve refreshed token ', err);
});
});
This method never triggered at all.
In order to understand how to use Firebase Cloud Messaging, I am following this document:
https://firebase.google.com/docs/cloud-messaging/admin/send-messages?hl=en-us
Precisely I am looking at this section: Send to individual devices
I can see in the code that I need a registrationToken. My question is how do I concretely get one?
I first want to send a message to my own iPhone, laying on my desk and later to all iPhones of registered users.
When I work in IOS-Swift, you have to add this method in your AppDelegate.swift file:
func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String) {
print("Firebase registration token: \(fcmToken)")
let dataDict:[String: String] = ["token": fcmToken]
NotificationCenter.default.post(name: Notification.Name("FCMToken"), object: nil, userInfo: dataDict)
// TODO: If necessary send token to application server.
// Note: This callback is fired at each app startup and whenever a new token is generated.
}
If you need to access the token directly, use this:
InstanceID.instanceID().instanceID { (result, error) in
if let error = error {
print("Error fetching remote instange ID: \(error)")
} else if let result = result {
print("Remote instance ID token: \(result.token)")
self.instanceIDTokenMessage.text = "Remote InstanceID token: \(result.token)"
}
}
For more information visit:
https://firebase.google.com/docs/cloud-messaging/ios/client
When working with FCM notifications, the devices generate tokens, which are renewed every so often, so if you need to send a push to a device you must know your token, you must implement a class that inherits FirebaseMessagingService, and overwrite an onNewToken method, this method is called in the background every time the device token is updated.
/**
* Called if InstanceID token is updated. This may occur if the security of
* the previous token had been compromised. Note that this is called when the InstanceID token
* is initially generated so this is where you would retrieve the token.
*/
#Override
public void onNewToken(String token) {
Log.d(TAG, "Refreshed token: " + token);
// If you want to send messages to this application instance or
// manage this apps subscriptions on the server side, send the
// Instance ID token to your app server.
sendRegistrationToServer(token);
}
It is recommended that this token be sent to your server so that from there you can send the push to the devices with registered tokens. If you want to force a first token, you can use:
FirebaseInstanceId.getInstance().getInstanceId();
I'm building a React web app on Gatsby and I'm trying to incorporate push notifications using FCM. I have the firebase-messaging-sw.js service worker in place and I am trying to get a token by this method in my app:
messaging
.requestPermission()
.then(() => {
console.log('Permission received.');
return messaging.getToken();
})
.then(token => {
console.log(token);
// Save token with user.
return this.saveFcmToken(token, authUser);
})
.catch(error => {
console.log('Error occurred.', error);
});
I call this snippet inside a firebase.auth.onAuthStateChanged() to get the user.
The problem is I get a different token each time. I haven't seen this type of issue documented after extended search.
Any ideas?
It turns out this was down to https://github.com/gatsbyjs/gatsby/issues/9770
In develop mode, Gatsby apparently unregisters service workers. Using a production build got rid of the problem.
I would like to be able to view and update a user's metadata from a single page application but whenever I attempt to gain access to any of the scopes that would appear from the documentation (https://auth0.com/docs/libraries/auth0js/v9)
However, when I run the following code:
webAuth.checkSession(
{
audience: `https://mysite.eu.auth0.com/api/v2/`,
scope: "read:current_user"
},
(err, result) => {
err && console.log("err", err");
result && console.log("result", result);
}
);
I get the following error:
{error: "consent_required", error_description: "Consent required"}
I've tried putting the scope read:current_user in multiple places but it always appears to fail.
The error means that the server is requesting an additional consent from the user, so checkSession cannot complete the action because it happens silently and cannot prompt the user.
To fix this, go to Auth0 dashboard -> APIs -> Auth0 Management API -> Settings tab -> uncheck 'Allow Skipping User Consent'.