Chrome Extension & FCM fetching registration token - javascript

I'm building a Chrome Extension that needs to be able to receive push notifications. An alternative would be polling the server every X seconds, but that doesn't seem like much of a good choice. For this reason I chose Firebase. The custom JWT token auth seems to be done and working. The next step is to fetch the registration token, and sending this to the server.
The problem:
The token seems to never be fetched:
var config = {
apiKey: "<KEY>",
authDomain: "<DOMAIN>",
messagingSenderId: "<SENDER_ID>"
};
var app = firebase.initializeApp(config);
var messaging = firebase.messaging(app);
function initApp() {
firebase.auth().onAuthStateChanged(function(user) {
if(user) {
// Update UI
fetchToken();
} else {
// no user is signed in
}
});
}
function fetchToken() {
messaging.requestPermission()
.then(function() {
messaging.getToken()
.then(function(currentToken) {
if (currentToken) {
sendRegistrationTokenToServer(currentToken);
} else {
console.log('No token available')
}
})
.catch(function(err) {
console.debug('An error occurred while retrieving token. ', err);
});
}).catch(function(err) {
console.log('Notification permissions denied.', err);
});
firebase.messaging().onTokenRefresh(function() {
messaging.getToken()
.then(function(refreshedToken) {
sendRegistrationTokenToServer(refreshedToken);
console.log('Token refreshed')
})
.catch(function(err) {
console.log('Unable to retrieve refreshed token', err);
});
});
}
$(document).ready(function() {
initApp();
// some other stuff
});
Does anyone know why the token isn't being fetched?
No network activity can be seen in the developer console (for fetching the token). Also no error is printed. I do, however, see network activity for getAccountInfo?key=<somekey> which carries an idToken with it, though I'm not certain this is a related request (response doesn't seem to return a token).

The problem is you cannot call messaging.requestPermission() from the background script. If the user has not allowed notifications, this call will attempt to prompt the user, which will fail silently. What you can do is request the notifications permission somewhere else in your extension that has a UI (e.g. your options page) with:
Notification.requestPermission().then((permission) => { ...
Once the user has allowed the permission you can use the Permissions API, to query if you have the permission, in place of the call to messaging.requestPermission(). This is a function I wrote to do this:
/**
* Determine if navigator Notifications permission has been granted
* Note: this will work from the background script of an extension,
* unlike the other solutions
* #see https://developers.google.com/web/updates/2015/04/permissions-api-for-the-web
* #see https://w3c.github.io/permissions/
* #see https://www.chromestatus.com/features/6443143280984064
* #returns {Promise<boolean>} true if granted
* #memberOf app.Notify
*/
hasNavigatorPermission: function() {
return navigator.permissions.query({
name: 'notifications',
}).then((status) => {
if (status.state === 'granted') {
return Promise.resolve(true);
}
return Promise.resolve(false);
});
},
And use it like this:
/**
* Get the registration token for fcm
* #returns {Promise<token>} A registration token for fcm
* #memberOf app.Fb
*/
getRegToken: function() {
return app.Notify.hasNavigatorPermission().then((granted) => {
if (!granted) {
return Promise.reject(new Error(ERROR_NOTIFICATIONS));
}
return _messaging.getToken();
}).then((token) => {
if (token) {
return Promise.resolve(token);
}
return Promise.reject(new Error(ERROR_TOKEN));
});
},
A couple other things to note:
Don't rely on Notification.permission in an extension. It does not have the correct value.
As of Chrome 59, if you dismiss the notifications dialog too many times while developing, it will stop displaying it for a period of time. You need to clear your browser data to get it to start displaying again.

Related

after deploy, fcm alert function is not working... java.lang.IllegalArgumentException: Exactly one of token, topic or condition must be specified

I made alert service using FCM, it works fine in my local server. but after I deployed my server in ec2, trying alert function on ec2 app gives me this error :
[ient-SecureIO-1] o.a.t.websocket.pojo.PojoEndpointBase : No error handling configured for [springboot.utils.WebsocketClientEndpoint] and the following error occurred
java.lang.IllegalArgumentException: Exactly one of token, topic or condition must be specified
Reading the error message, i guess that there's no token in my server.
In notification.js, I'm trying to get token and request POST to '/register'
const firebaseModule = (function () {
async function init() {
// Your web app's Firebase configuration
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/firebase-messaging-sw.js')
.then(registration => {
var firebaseConfig = {
configuration Information
};
// Initialize Firebase
console.log("firebase Initialization");
firebase.initializeApp(firebaseConfig);
// Show Notification Dialog
const messaging = firebase.messaging();
messaging.requestPermission()
.then(function() {
console.log("Permission granted to get token");
return messaging.getToken();
})
.then(async function(token) {
console.log("Token: ", token);
await fetch('/register', { method: 'post', body: token })
messaging.onMessage(payload => {
const title = payload.notification.title
const options = {
body : payload.notification.body
}
navigator.serviceWorker.ready.then(registration => {
registration.showNotification(title, options);
})
})
})
.catch(function(err) {
console.log("Error Occured : " +err );
})
})
})
}
}
And by debugging it by console.log(), I found out that code is stopped before "if ('serviceWorker' in navigator) {"
So I need to make it proceed. But to be honest, it's been a while since i made this function, and I don't know almost anything about Javascript. I don't even know what navigator means(I googled it, but couldn't find clear answer) I'm having trouble figuring out what is wrong and how can I fix it. Can someone help me??

Firebase: Getting new token after current FCM token expires

I am working on a reactJS website with firebase as a backend. One of the main service that I give to my user is notification everyday. I had implemented a code to ask for permission, if they grant access, store them to my database. But after sometime I noticed that tokens are expiring. I did a research about it and found out about onTokenRefresh() method. I implemented that also. But I believe for some reason, it is not working correctly. I am not getting the new tokens if tokens are expired. I'll paste the snippet below of what I am trying to do.
On the main page,
if (!("Notification" in window)) {
//alert("This browser does not support desktop notification");
}
// Check whether notification permissions have already been granted
else if (Notification.permission === "granted") {
// Refresh logic here
this.checkForRefreshToken();
}
// Otherwise, ask the user for permission
else if (Notification.permission !== "denied") {
//Ask user here, then call another function for storing token
this.addNewToken()
}
If user is accepting notification for the first time, I call addNewToken() method
addNewToken = () => {
this.auth.onAuthStateChanged(authUser => {
const messaging = firebase.messaging();
messaging
.requestPermission()
.then(() => {
return messaging.getToken();
})
.then(token => {
firebase.database().ref('tokens').once('value')
.then(snapshots => {
let tokenExist = false
snapshots.forEach(childSnapshot => {
// console.log("Child snapshot: ", childSnapshot.key);
if (childSnapshot.val().token === token) {
tokenExist = true
return console.log('Device already registered.');
}
})
if (!tokenExist) {
// console.log('Device subscribed successfully');
return firebase.database().ref('tokens').push(token);
}
})
})
.catch(error => {
if (error.code === "messaging/permission-blocked") {
// console.log("Please Unblock Notification Request Manually");
} else {
// console.log("Error Occurred", error);
}
});
})
}
Now if user has already subscribed, I am checking if onTokenRefresh() is called, basically if token needs a refresh.
checkForRefreshToken = () => {
this.auth.onAuthStateChanged(authUser => {
const messaging = firebase.messaging();
messaging.onTokenRefresh(() => {
messaging.getToken().then((refreshedToken) => {
const token = refreshedToken;
firebase.database().ref('tokens').once('value')
.then(snapshots => {
let tokenExist = false
snapshots.forEach(childSnapshot => {
if (childSnapshot.val().token === token) {
tokenExist = true
return console.log('Device already registered.');
}
})
if (!tokenExist) {
return firebase.database().ref('device_ids').push(token);
}
})
})
})
})
}
I don't know what is going wrong here, but I am not able to get the new tokens.
For testing purpose, I have my own device with expired token, I deployed this code and opened my website on the device, refreshed the page etc but I didn't get the new token.
Also, it would be great if anyone can help me how I can test expired tokens locally.
I found different methods for an app, but not for website (javascript).
Thanks
auth.onAuthStateChanged(...) will add an event listener to events signalling changes in the authentication state. In your code above, you expect that the code for addNewToken and checkForRefreshToken are evaluated immediately, but instead they are simply adding new event listeners. The same applies for onTokenRefresh().
So reworking addNewToken() into an on-demand function yields:
requestMessagingToken = () => {
let authUser = this.auth.currentUser;
if (!authUser) {
console.log("Not logged in!");
return Promise.resolve(false); // silently ignore & fail
}
const messaging = firebase.messaging();
return messaging
.requestPermission()
.then((permission) => {
if (permission !== 'granted') {
throw 'insufficient permissions'; // notification permission was denied/ignored
}
return messaging.getToken();
})
.then(token => saveMessagingTokenForUser(authUser.uid, token))
.catch(error => {
if (error && error.code === "messaging/permission-blocked") {
// console.log("Please Unblock Notification Request Manually");
} else {
// console.log("Error Occurred", error);
}
return false; // silently fail
});
};
This has the added benefit of being able to handle auth state changes easily using:
this.auth.onAuthStateChanged(requestMessagingToken);
Once you've been granted permission to send notifications, you can activate the refreshed token listener using:
const messaging = firebase.messaging();
messaging.onTokenRefresh(() => {
let authUser = this.auth.currentUser;
if (!authUser) {
console.log("Not logged in!");
return; // ignore
}
messaging.getToken().then((refreshedToken) => {
return saveMessagingTokenForUser(authUser.uid, refreshedToken);
}).catch((err) => {
console.log('Unable to retrieve/store messaging token ', err);
});
});
Lastly, to save the token to the database, instead of searching through your database for a matching device token as the value of a push ID, you can use the token itself as the key. Furthermore, to make outdated token management easier, it is best to split your tokens by user.
"userData": {
"userId1": {
"tokens": {
"deviceToken1": true,
"deviceToken2": true,
"deviceToken3": true,
},
...
},
"userId2": {
"tokens": {
"deviceToken4": true,
"deviceToken5": true
},
...
},
...
}
Using this structure, you can use transactions to check if the data has been stored in the database and use cloud functions to check for invalid tokens.
saveMessagingTokenForUser = (uid, token) => {
return firebase.database().ref('userData/' + uid + '/tokens/' + token)
.transaction((currentData) => {
if (currentData != null) {
console.log('Device already registered.');
return; // abort transaction, no changes needed
} else {
console.log('Saving token to database.');
return true;
}
})
.then(() => true); // no errors = success
};
On the server you could run a Cloud Function listening to new device tokens added to the database and check that user's other device tokens for expiry.
exports.checkUserForOutdatedTokens = functions.database.ref('/userData/{userId}/tokens/{tokenId}')
.onCreate((newTokenSnapshot, context) => {
return newTokenSnapshot.ref.once('value')
.then((tokensSnapshot) => {
let tokens = Object.keys(tokensSnapshot.val());
const message = {
data: {heartbeat: true},
tokens: tokens,
}
return admin.messaging().sendMulticast(message)
.then((response) => {
if (response.failureCount > 0) {
const dataToDelete = {};
response.responses.forEach((resp, idx) => {
if (!resp.success) {
dataToDelete[tokens[idx]] = null;
}
});
console.log('List of tokens that caused failures: ' + Object.keys(dataToDelete));
return tokensSnapshot.ref.update(dataToDelete); // deletes failed tokens
}
});
});
});

firebase cloud messaging web not receiving test messages

I'm trying a basic proof of concept with firebase cloud messaging and I'm having pain receiving messages, nothing happens when I send messages even if the app is in foreground, the onMessage event doesn't return anything despite I don't have any error on sending all seem to be ok from postman or fcm console.
I noticed also that chrome://gcm-internals/ displays state "CONNECTING"
here is my app.js but for me it seems more to be related to the gcm state who should display "CONNECTED"
var firebase = require("firebase/app");
require('firebase/messaging');
// Your web app's Firebase configuration
var firebaseConfig = {
apiKey: "AIzaSyCbrs6uvQQ5s6kAp6YfvDzsds_CfMt3-hA",
authDomain: "test-flarum-fcm.firebaseapp.com",
databaseURL: "https://test-flarum-fcm.firebaseio.com",
projectId: "test-flarum-fcm",
storageBucket: "",
messagingSenderId: "977636469573",
appId: "1:977636469573:web:76e2e191f02f8923df6c2c"
};
// Initialize Firebase
var app = firebase.initializeApp(firebaseConfig);
// Retrieve Firebase Messaging object.
var messaging = firebase.messaging();
//console.log(messaging);
console.log('myToken : '+getToken());
Notification.requestPermission().then((permission) => {
if (permission === 'granted') {
console.log('Notification permission granted.');
//getMyToken();
if(isTokenSentToServer()){
console.log("token already saved")
}else{
// TODO(developer): Retrieve an Instance ID token for use with FCM.
getMyToken();
}
} else {
console.log('Unable to get permission to notify.');
setTokenSentToServer(false);
}
});
// Get Instance ID token. Initially this makes a network call, once retrieved
// subsequent calls to getToken will return from cache.
function getMyToken() {
messaging.getToken().then((currentToken) => {
if (currentToken) {
console.log(currentToken);
//sendTokenToServer(currentToken);//#todo
//updateUIForPushEnabled(currentToken);
saveToken(currentToken);
setTokenSentToServer(true);
} else {
// Show permission request.
console.log('No Instance ID token available. Request permission to generate one.');
// Show permission UI.
//updateUIForPushPermissionRequired();//#todo
//setTokenSentToServer(false); //#todo
setTokenSentToServer(false);
}
}).catch((err) => {
console.log('An error occurred while retrieving token. ', err);
//showToken('Error retrieving Instance ID token. ', err);
setTokenSentToServer(false);
});
}
// Callback fired if Instance ID token is updated.
messaging.onTokenRefresh(() => {
messaging.getToken().then((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((err) => {
console.log('Unable to retrieve refreshed token ', err);
//showToken('Unable to retrieve refreshed token ', err);
});
});
function setTokenSentToServer(sent) {
window.localStorage.setItem('sentTokenToServer', sent ? 1 : 0);
}
function isTokenSentToServer(){
return window.localStorage.getItem('sentTokenToServer') == 1;
}
function saveToken(token){
myToken = token;
window.localStorage.setItem('myToken', token);
}
function getToken(){
return window.localStorage.getItem('myToken');
}
messaging.onMessage((payload) => {
console.log('Message received. ', payload);
// ...
});
Thanks for your help
[update] I solved the problem of gcm state but that's not wworking better despite the send result seems to be ok in postman
{
"multicast_id": 7270949329343339591,
"success": 1,
"failure": 0,
"canonical_ids": 0,
"results": [
{
"message_id": "0:1569712574121124%e609af1cf9fd7ecd"
}
]
}

ApiRTC token authentication

I am trying to use token authentication with no success. I am wondering if anyone succeed in doing so, as the official ApiRTC documentation is weak on that topic.
1) I have activated secret key below from - Credentials screen
2) For token validation I have setup a service from API - Token authentication screen
3) I have the below code to create the user agent
function createUserAgent(token) {
ua = new apiRTC.UserAgent({
uri: 'token:' + token
});
ua.register({
id : useragentId
}).then(uaRegistered)
.catch(function (error) {
console.log("Registration error");
});
}
function uaRegistered(session) {
console.log("Registration OK");
}
4) This initializes a request to below address. And it fails with HTTP 401
GET https://cloud.apizee.com/api/v2/checkToken?token=eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhNWQxN2M1ZTVjOWZkYmRiNDJhYTgzMTJlMWQxMmEwYiIsImF1ZCI6ImFwaVJUQyIsImp0aSI6ImE5ZjU4NmNlLTcxMDctNDgxMS04ODYwLTQ5MjY4ODY2NjhiYiIsImlhdCI6MTU1OTg5OTA5MSwiZXhwIjoxNTU5OTAyNjkxLCJncmFudHMiOnsiaWRlbnRpdHkiOiJjbGk5OTQxOTgxNTgifX0.ZfQs_HgUXOWhCAlXB6fTMKhbT-pFslb9MK_JvXu2U5A 401 (Unauthorized)
5) I have also seen that no requests are made to my token validation service.
Thanks
edit: updates according to the answer
function createUserAgent(token) {
apiRTC.setLogLevel(apiRTC.LOG_LEVEL_DEBUG);
var registerInformation = {};
registerInformation.id = useragentId;
registerInformation.token = token;
ua = new apiRTC.UserAgent({
uri: 'apzkey:a5d17c5e5c9fdbdb42aa8312e1d12a0b'
});
$("#sessionStatus").text("Waiting for register response ");
ua.register(registerInformation).then(uaRegistered)
.catch(function (error) {
debugger;
console.log("Registration error");
$("#sessionStatus").text("Failed to register UA");
});
}
function uaRegistered(session) {
debugger;
console.log("Registration OK");
connectedSession = session;
$("#useragentId").text(useragentId);
$("#sessionUsername").text(session.getUsername());
$("#sessionStatus").text("Connected");
debugger;
}
Thanks for pointing this issue in documentation, we have done a first update for using an external validation service here :
https://dev.apirtc.com/authentication/index
On client side, you need to use following code :
registerInformation.token = "myToken"
ua.register(registerInformation).then(function(session) {
// Save session
connectedSession = session;
}).catch(function(error) {
// error
console.error('User agent registration failed', error);
});
the usage of token in uri is for users authentication on Apizee offers

Using chrome push notifications mainfest.json in meteor project

I am following a guide on how to implement the chrome push notification and I am trying to implement it in a Meteor app as a package.
Because I am unable to include the manifest.json I am getting "Registration failed - no sender id provided" or "Registration failed - permission denied". So how can I include this file in my project?
The manifest.json looks like this:
{
"permissions": [ "gcm" ],
"name": "push",
"short_name": "push notification",
"display": "standalone",
"gcm_sender_id": "0000000000000"
}
I have tried including it with the package.js like:
api.addAssets('manifest.json', 'client');
And also put the required variables (gcm_sender_id) in settings.json and starting meteor with meteor --settings settings.json but nothing works.
My service worker registration starts with calling Cpn.serviceWorkerRegistration:
Cpn = {};
Cpn.serviceWorkerRegistration = function () {
console.log("serviceWorkerRegistration called");
subscribe();
console.log(navigator);
// Check that service workers are supported, if so, progressively
// enhance and add push messaging support, otherwise continue without it.
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js')
.then(initialiseState);
} else {
console.warn('Service workers aren\'t supported in this browser.');
}
}
// Once the service worker is registered set the initial state
initialiseState = function () {
console.log("initialiseState");
// Are Notifications supported in the service worker?
if (!('showNotification' in ServiceWorkerRegistration.prototype)) {
console.warn('Notifications aren\'t supported.');
return;
}
// Check the current Notification permission.
// If its denied, it's a permanent block until the
// user changes the permission
if (Notification.permission === 'denied') {
console.warn('The user has blocked notifications.');
return;
}
// Check if push messaging is supported
if (!('PushManager' in window)) {
console.warn('Push messaging isn\'t supported.');
return;
}
// We need the service worker registration to check for a subscription
navigator.serviceWorker.ready.then(function (serviceWorkerRegistration) {
// Do we already have a push message subscription?
serviceWorkerRegistration.pushManager.getSubscription()
.then(function (subscription) {
if (!subscription) {
return;
}
// Keep your server in sync with the latest subscriptionId
sendSubscriptionToServer(subscription);
})
.catch(function (err) {
console.warn('Error during getSubscription()', err);
});
});
}
function subscribe() {
navigator.serviceWorker.ready.then(function (serviceWorkerRegistration) {
serviceWorkerRegistration.pushManager.subscribe()
.then(function (subscription) {
// The subscription was successful
// TODO: Send the subscription.endpoint to your server
// and save it to send a push message at a later date
return sendSubscriptionToServer(subscription);
})
.catch(function (e) {
if (Notification.permission === 'denied') {
// The user denied the notification permission which
// means we failed to subscribe and the user will need
// to manually change the notification permission to
// subscribe to push messages
console.warn('Permission for Notifications was denied');
} else {
// A problem occurred with the subscription; common reasons
// include network errors, and lacking gcm_sender_id and/or
// gcm_user_visible_only in the manifest.
console.error('Unable to subscribe to push.', e);
}
});
});
}
And the service worker looks like this:
self.addEventListener('push', showNotification)
self.addEventListener('notificationclick', closeNotificationAndOpenWindow)
function showNotification(event) {
console.log('Received a push message', event)
var title = 'Yay a message.'
var body = 'We have received a push message.'
var icon = '/images/icon-192x192.png'
var tag = 'simple-push-demo-notification-tag'
event.waitUntil(
self.registration.showNotification(title, {
body: body,
icon: icon,
tag: tag
})
)
}
function closeNotificationAndOpenWindow(event) {
console.log('On notification click: ', event.notification.tag)
// Android doesn’t close the notification when you click on it
// See: http://crbug.com/463146
event.notification.close()
// This looks to see if the current is already open and
// focuses if it is
event.waitUntil(clients.matchAll({
type: "window"
}).then(function (clientList) {
for (var i = 0; i < clientList.length; i++) {
var client = clientList[i]
if (client.url == '/' && 'focus' in client)
return client.focus()
}
if (clients.openWindow)
return clients.openWindow('/')
}))
}

Categories

Resources