how to refresh FCM token with js - javascript

I am setting up FCM for my React web push notification and it is doing everything properly, except I don't know how to refresh token when it's expired
I have an FCMListener function which its code is as follow
navigator.serviceWorker
.register("/static-files/firebase-messaging-sw.js")
.then((registration) => {
firebase.initializeApp(settings.getConfig().FIREBASE_CONFIG);
const messaging = firebase.messaging();
messaging.useServiceWorker(registration);
try {
messaging
.requestPermission()
.then(() => {
return messaging.getToken();
})
.then((token) => {
let topic = `${userInfo.is_host ? "host" : "guest"}`;
if (token) {
this.subscribeToTopic(topic, token);
this.sendTokenToServer({
os: "web",
push_token: token,
});
} else {
messaging.onTokenRefresh(() => {
messaging
.getToken()
.then((refreshedToken) => {
this.subscribeToTopic(topic, token);
this.sendTokenToServer({
os: "web",
push_token: refreshedToken,
});
})
.catch((err) => {
console.log("Unable to retrieve refreshed token ", err);
});
});
}
});
} catch (error) {
if (error.code === "messaging/permission-blocked") {
console.log("Please Unblock Notification Request Manually");
} else {
console.log("Error Occurred", error);
}
}
messaging.onMessage((payload) => {
console.log("Notification Received", payload);
alert(payload.notification.body);
});
});
};
}
as I don't know how to expire a firebase token and I can't test what happens when the token is expired I don't know if the part where I am trying to get refreshedToken is right or not or even if this is the proper way to get refreshed token or not. I would really appreciate any hint and advise

For testing purposes you can delete the FCM token through the API by calling the deleteToken API. After doing that, reload the page, and your onTokenRefresh should fire.

Related

Use FCM in Chrome Manifest version 3

I was wondering if it was possible to use FCM in chrome extension Manifest version 3? And by calling the getToken function in the background.js script.
I tried to use it but I keep getting the following error:
FirebaseError: Messaging: We are unable to register the default service worker.
Even though the firebase-messaging-sw.js is in the extension root. And I see on the Chrome Extension API it has a gcm entry but not a fcm entry.
I did find a github repo where fcm worked with the V3 but in that repo they opened a new window and used the service worker registration function in there and the getToken function. That won't work for my case because I need the getToken function to be called in the background.js without user interaction.
Any suggestions?
Attached the modified background.js code:
import { initializeApp } from "./firebase-app.js";
import { getMessaging, onBackgroundMessage } from './firebase-messaging-sw.js';
import { getToken, onMessage } from "./firebase-messaging.js";
import { firebaseConfig, vapidKey } from './firebaseConfig.js';
// Initialize Firebase
const firebaseApp = initializeApp(firebaseConfig);
const messaging = getMessaging(firebaseApp);
onBackgroundMessage(messaging, (payload) => {
console.log('[background.js] Received background message ', payload);
self.registration.showNotification(payload.notification.title, {
body: payload.notification.body,
});
});
chrome.runtime.onInstalled.addListener(() => {
console.log('installed');
});
getToken(messaging, {
vapidKey,
}).then((currentToken) => {
if (currentToken) {
// Send the token to your server to send push notifications.
registrationTokenElement.textContent = currentToken;
console.log(currentToken);
} else {
// Show permission request UI
console.log('No registration token available. Request permission to generate one.');
// ...
}
}).catch((err) => {
console.log('An error occurred while retrieving token. ', err);
// ...
});
async function openPopupWindow() {
console.log('open popup');
const { popupWindowId } = await chrome.storage.local.get('popupWindowId');
if (popupWindowId) {
try {
await chrome.windows.update(popupWindowId, { focused: true });
return;
} catch (e) {
// ignore
}
}
const popup = await chrome.windows.create({
url: 'popup.html',
type: 'popup',
width: 300,
height: 500,
});
await chrome.storage.local.set({
popupWindowId: popup.id,
});
}
chrome.action.onClicked.addListener(() => {
console.log('click icon');
openPopupWindow();
});
self.onnotificationclick = function(event) {
console.log('On notification click: ', event.notification.tag);
event.notification.close();
event.waitUntil(openPopupWindow());
};
chrome.windows.onRemoved.addListener(async (windowId) => {
const { popupWindowId } = await chrome.storage.local.get('popupWindowId');
if (popupWindowId === windowId) {
console.log('close popup');
await chrome.storage.local.remove('popupWindowId');
}
});

408 timeout from Firebase Cloud Messaging after the message

I'm sending push messages using FCM through Firebase Functions. The messages are being sent properly, but I'm getting the 408 time-out error after the message is sent. I'm suspecting it might have to do with the unregistered tokens not being cleaned up because:
if I were to send another message to the same device, the same timeout occurs and
the only error message I get from the Firebase log is Function execution took 60002 ms, finished with status: 'timeout'.
exports.sendMessage = functions.https.onRequest(async (request, response) => {
const {
sender,
recipient,
content,
docID
} = request.body
functions.logger.log(
"docID:",
docID,
);
// Get the list of device notification tokens.
let deviceTokens; let ref;
try {
ref = admin.firestore().collection("deviceToken").doc(recipient);
const doc = await ref.get();
if (!doc.exists) {
console.log("No such document!");
response.status(500).send(e)
} else {
console.log("doc.data():", doc.data());
deviceTokens = doc.data().token;
}
} catch (e) {
response.status(500).send(e)
}
let senderProfile;
try {
senderProfile = await admin.auth().getUser(sender);
console.log("senderProfile", senderProfile);
} catch (e) {
console.log(e);
response.status(500).send(e)
}
// Notification details.
let payload = {
notification: {
title: senderProfile.displayName,
body: content,
sound: "default",
},
data: {
uid: senderProfile.uid,
displayName: senderProfile.displayName,
docID,
messageType: "status"
}
};
functions.logger.log(
"deviceTokens", deviceTokens,
"payload", payload,
);
// Send notifications to all tokens.
const messageResponse = await admin.messaging().sendToDevice(deviceTokens, payload);
// For each message check if there was an error.
messageResponse.results.forEach((result, index) => {
const error = result.error;
if (error) {
functions.logger.error(
"Failure sending notification to",
deviceTokens[index],
error,
);
// Cleanup the tokens who are not registered anymore.
if (error.code === "messaging/invalid-registration-token" ||
error.code === "messaging/registration-token-not-registered") {
const updatedTokens = deviceTokens.filter((token) => token !== deviceTokens[index]);
console.log("updatedTokens", updatedTokens);
ref.update({
token: updatedTokens,
})
.catch(function(e) {
console.error("Error removing tokens", e);
response.status(500).send(e)
});
}
}
});
response.status(200)
});
I'm unsure why the following isn't cleaning up the unregistered tokens:
const updatedTokens = deviceTokens.filter((token) => token !== deviceTokens[index]);
ref.update({
token: updatedTokens,
})
You always have to end HTTP functions with response.status(200).send() or response.status(200).end(). In the above function, you have response.status(200) so you have to end it either with response.status(200).send() or response.status(200).end(). Please check the documentation if it helps.

Firebase Push Notification Not Popping Up

I successfully registered firebase service worker with
let messaging = null;
export const registerFirebaseServiceWorker = () => {
if ("serviceWorker" in navigator) {
console.log("serviceWorker in navigator");
navigator.serviceWorker.register(("./firebase-messaging-sw.js"))
.then((registration) => {
console.log("Registration successful, scope is: ", registration.scope);
})
}
}
and try to receive the message with messaging.onMessage callback
if (supportsWebPush) {
messaging.onMessage(payload => {
console.log("There's a message!", payload);
const notificationTitle = "notificationTitle"
const notificationOptions = {
body: payload
};
//self.registration.showNotification(notificationTitle, notificationOptions);
navigator.serviceWorker.getRegistration("./firebase-messaging-sw.js").then(registration => {
console.log("got the registration");
registration.showNotification('Hello world!');
}).catch(error => {
console.log("No service worker registered", error);
});
});
Even though console.log was working and registration.showNotification was executed, there were no push notification on my browser ? Why is this? I've been trying multiple different ways and search for many suspected issues but can't found a proper way to display default web push pop up

React Native app logs out when jwt refresh token is not expired

I am using the JWT token to verify my API requests. Access token expires in 1 minute, and refresh token expires in 1 year. After the access token expires, an API request is sent with a refresh token to get a new set of tokens. A new set of tokens are only sent if the refresh token is valid, and exists in the database. I am using Axios interceptor to achieve this. Everytyhing seems to work fine for some time. However, it logs me out even when the refresh token is valid and does exist in DB. I am assuming I am missing something in Axios interceptor or has to do with async functions.
Error logs "code does not match" in server-side at verifyRefreshToken function, and "Error here" in client-side updateToken function.
CLIENT SIDE CODE
API.js
// Response interceptor for API calls
API.interceptors.response.use((response) => {
return response
}, async (error) => {
// reject promise if network error
if (!error.response) {
console.log("Network Error");
return Promise.reject(error);
}
const originalRequest = error.config;
console.log(store.getState().auth)
// if access token is expired
if (error.response.status === 403 && error.response.data.message == "token expired") {
// var refreshToken = await getRefreshToken() // get refresh token from local storage
var refreshToken = await store.getState().auth.refreshToken
// restore tokens using refresh token
await store.dispatch(await updateToken(refreshToken)) // get new set of tokens from server and store tokens in redux state
// var newAccessToken = await getToken() // get token from local storage
var newAccessToken = await store.getState().auth.accessToken
if(newAccessToken != null){
originalRequest.headers["Authorization"] = `Bearer ${newAccessToken}`;
return API(originalRequest)
}
return Promise.reject(error);
}
// if refresh token is expired or does not match
if (error.response.status === 403 && error.response.data.message == "false token") {
socketDisconnect() // disconnect socket connection
signOut() // remove tokens from local storage
store.dispatch(logOut()) // set tokens in redux as null
return Promise.reject(error);
}
return Promise.reject(error);
});
updateTokenFunction
export const updateToken = (rt) => {
return async (dispatch) => {
const data = await API.post('/auth/refreshToken', {
token: rt
})
.then(async res => {
var accessToken = res.data.accessToken
var refreshToken = res.data.refreshToken
await storeToken(accessToken) // store access token in local storage
await storeRefreshToken(refreshToken) // store refresh token in local storage
dispatch(restoreToken({accessToken, refreshToken})) // store token in redux state
})
.catch(err => {
console.log("err here" + err) // LOG SHOWS ERROR HERE
})
}
}
SERVER SIDE CODE
// /auth/refreshToken
// POST: /api/auth/refreshToken
router.post('/', (req, res) => {
var { token } = req.body
if(!token) res.status(403).send({"status":false, "message": "false token", "result": ""})
verifyRefreshToken(token)
.then(async data => {
var userName = data.userName
// get new tokens
var accessToken = await getAccessToken(userName)
var refreshToken = await getRefreshToken(userName)
res.json({"status":true, "message": "token verified", "accessToken": accessToken, "refreshToken": refreshToken})
})
.catch(err => {
console.log(err);
res.status(403).send({"status":false, "message": "false token", "result": ""})
})
});
To generate new refresh token
// generate refresh token
const getRefreshToken = (userName) => {
return new Promise((resolve, reject) => {
var secret = process.env.REFRESH_TOKEN_SECRET
var options = { expiresIn: '1y' }
jwt.sign({userName},secret , options, (err, token) => {
if(err) reject("error")
var data = {"userName": userName, "token": token}
// delete all expired token from database
dbQueries.deleteRefreshToken(data, result => {
})
// add refresh token to database
dbQueries.addRefreshToken(data, result => {
if(result == "success"){
console.log("added token " + token);
resolve(token)
}else{
reject("failure")
}
})
});
})
}
Verifying refresh token
// verify access token
const verifyRefreshToken = (token) => {
return new Promise((resolve, reject) => {
var secret = process.env.REFRESH_TOKEN_SECRET
if(!token) return reject("no token")
jwt.verify(token, secret, (err, user) => {
if(err){
return reject(err)
}
// check if the verified token and token from database matches
var data = {"userName": user.userName}
dbQueries.getRefreshToken(data, result => {
if(result.length == 0){
return reject("no data")
}
if(token === result[0].token){
resolve(user)
} else{
reject("code does not match") // LOGS THIS ERROR
}
})
})
})
}
UPDATE
The error was due to multiple API calls at the same time, and all had requested for a new access token, with old refresh tokens. I solved the issue using code in this link.

firebase authentication: signInWithCustomToken and createSessionCookie - error auth/invalid-id

I am trying to implement a login mechanism using Firebase custom token and session cookies, for sure I am doing something wrong but I cannot figure it out.
I will put my code and then explain how I am using it to test it.
Frontend code
const functions = firebase.functions();
const auth = firebase.auth();
auth.setPersistence(firebase.auth.Auth.Persistence.NONE);
auth.onAuthStateChanged((user) => {
if (user) {
user.getIdToken()
.then((idToken) => {
console.log(idToken);
});
}
});
function testCustomLogin(token) {
firebase.auth().signInWithCustomToken(token)
.then((signInToken) => {
console.log("Login OK");
console.log("signInToken", signInToken);
signInToken.user.getIdToken()
.then((usertoken) => {
let data = {
token: usertoken
};
fetch("/logincookiesession", {
method: "POST",
body: JSON.stringify(data)
}).then((res) => {
console.log("Request complete! response:", res);
console.log("firebase signout");
auth.signOut()
.then(()=> {
console.log("redirecting ....");
window.location.assign('/');
return;
})
.catch(() => {
console.log("error during firebase.signOut");
});
});
});
})
.catch(function(error) {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
console.log(errorCode, errorMessage);
});
}
Backend code
app.post('/logincookiesession', (req, res) => {
let token = req.body.token;
// Set session expiration to 5 days.
const expiresIn = 60 * 60 * 24 * 5 * 1000;
// Create the session cookie. This will also verify the ID token in the process.
// The session cookie will have the same claims as the ID token.
// To only allow session cookie setting on recent sign-in, auth_time in ID token
// can be checked to ensure user was recently signed in before creating a session cookie.
admin.auth().createSessionCookie(token, {expiresIn})
.then((sessionCookie) => {
// Set cookie policy for session cookie.
const options = {maxAge: expiresIn, httpOnly: true, secure: true};
res.cookie('session', sessionCookie, options);
res.end(JSON.stringify({status: 'success'}));
})
.catch((error) => {
res.status(401).send('UNAUTHORIZED REQUEST!' + JSON.stringify(error));
});
});
app.get('/logintest', (req, res) => {
let userId = 'jcm#email.com';
let additionalClaims = {
premiumAccount: true
};
admin.auth().createCustomToken(userId, additionalClaims)
.then(function(customToken) {
res.send(customToken);
})
.catch(function(error) {
console.log('Error creating custom token:', error);
});
});
so basically what I do is
execute firebase emulators:start
manually execute this on my browser http://localhost:5000/logintest , this gives me a token printed in the browser
Then in another page, where I have the login form, I open the javascript console of the browser and I execute my javascript function testCustomLogin and I pass as a parameter the token from step 2.
in the network traffic I see that the call to /logincookiesession return this:
UNAUTHORIZED REQUEST!{"code":"auth/invalid-id-token","message":"The provided ID token is not a valid Firebase ID token."}
I am totally lost.
I can see in the firebase console, in the Authentication section that user jcm#email.com is created and signed-in, but I cannot create the session-cookie.
Please,I need some advice here.
The route for creating the cookie session had an error.
It should start like this.
app.post('/logincookiesession', (req, res) => {
let params = JSON.parse(req.body);
let token = params.token;
And the code I used was from a manual, OMG. I hope this helps someone too.

Categories