FCM Push notifications arrive twice if the browser is in background - javascript

I've set up a simple push notification site, the notifications arrive okay if the browser is in foreground.
The problem begins if the browser is in background: the notification arrives twice, one styled with image and other settings set and the other has only title and body message.
Content of the service worker:
importScripts('https://www.gstatic.com/firebasejs/3.5.2/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/3.5.2/firebase-messaging.js');
// Initialize the Firebase app in the service worker by passing in the
// messagingSenderId.
firebase.initializeApp({
'messagingSenderId': '...'
});
const messaging = firebase.messaging();
messaging.setBackgroundMessageHandler(function(payload) {
console.log('[firebase-messaging-sw.js] Received background message ',
return null;
});
self.addEventListener('install', function (event) {
event.waitUntil(skipWaiting());
});
self.addEventListener('activate', function (event) {
event.waitUntil(clients.claim());
});
self.addEventListener('push', function (event) {
var pushData = event.data.json();
try {
var notificationData = pushData.data;
notificationData.data = JSON.parse(notificationData.data);
console.log(notificationData);
self.registration.showNotification(pushData.notification.title, notificationData);
}
catch (err) {
console.log('Push error happened: ', err);
}
});
Client side js:
firebase.initializeApp(firebaseConfig);
const messaging = firebase.messaging();
messaging.onMessage(function (payload) {
console.log("notification recieved");
return null;
});
self.addEventListener('push', function (event) {
console.log("window push stuff");
return null;
});
Thanks!

Simplest way to 100% avoid multiple notifications is adding "tag", eg.:
var options = {
body: "text",
tag: "notification-1"
};
self.registration.showNotification("title", options)

The problem can be solved with adding this line to the messaging.setBackgroundMessageHandler event:
self.registration.hideNotification();
This way, the default notification won't show and you have to show your notification in the self.addEventListener event.

It took for me around two weeks for me to understand and solve this issue. Hope that will save time for other people:
Firebase supports two types of push notifications:
Display notifications that FCM SDK handles automatically Data
Data messages, which are handled by the client app
Firebase cloud messaging UI has many advantages like advanced targeting by countries, devices, languages, and more. But it allows sending URLs only inside the data custom field.
Message sent from Firebase Console UI looks this way:
{
notification: {
title: "New Test",
body: "This is test",
},
data: {
url: 'someurl.com',
},
};
The notification comes twice when the service worker handles it and a second time when FCM SDK automatically does it.
I didn't found a way to disable auto handling of notification and in the frontend and used Firebase Functions to send it only as data message:
{
data: {
title: "New Test",
body: "This is test",
url: 'someurl.com',
},
};
So, if you want to pass custom URL to push notification, you will need to send it from your own server or using firebase functions.

This is how I get Firebase Cloud Messaging Notifications to work in Progresse Web Apps on Android Devices with those requirements:
Push notifications should only appear once in the status bar as well
The number of notifications should be highlighted at on the PWA app icon
The notification in the status bar should contain:
A small badge with the app icon
The actual app icon
A noticiation title
A notification body
Open the PWA on tap
firebase-messaging.sw.js
Don't add any cdoe or onBackgroundMessage() handler to this file. My file is nothing more but
//Give the service worker access to Firebase Messaging.
// Note that you can only use Firebase Messaging here. Other Firebase libraries
// are not available in the service worker.
importScripts('https://www.gstatic.com/firebasejs/8.3.0/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/8.3.0/firebase-messaging.js');
// Initialize the Firebase app in the service worker by passing in
// your app's Firebase config object.
// https://firebase.google.com/docs/web/setup#config-object
firebase.initializeApp({
apiKey: 'api-key',
authDomain: 'project-id.firebaseapp.com',
databaseURL: 'https://project-id.firebaseio.com',
projectId: 'project-id',
storageBucket: 'project-id.appspot.com',
messagingSenderId: 'sender-id',
appId: 'app-id',
measurementId: 'G-measurement-id',
});
// Retrieve an instance of Firebase Messaging so that it can handle background
// messages.
if (firebase.messaging.isSupported()) {
const messaging = !firebase.apps.length
? firebase.initializeApp(firebaseConfig).messaging()
: firebase.app().messaging();
Server side job to send notifications
The content and structure of the messaging payload is key. Put your notification object into a webpush object. Do not add a data object in order to avoid dubplicate notifications with incomplete content.
Make sure your badge is 24x24px png that contains only white content on a transparent background.
var message = {
webpush: {
notification: {
title: "Let's Join App", // Your message title
body: messageBody, // Your message body
icon: "./img/icons/android-chrome-192x192.png", // Your App icon, up to 512x512px, any color
badge: "./img/icons/badge.png", // Your app badge, 24x24px, white content on transparent background
},
fcmOptions: {
link: "https://www.letsjoin.app", // Your Link
},
},
token,
};
// Send a message to the device corresponding to the provided
// registration token.
await admin
.messaging()
.send(message)
.then((response) => {
// Response is a message ID string.
console.log("Successfully sent message");
})
.catch((error) => {
console.log("Error sending message:", error.errorInfo.message);
});
};

Related

Twilio Conversations push notifications with Firebase Cloud Messaging

I'm attempting to set up push notifications using Twilio Conversations and Firebase Cloud Messaging on a Next.js 12 app. The documentation is written with the assumption of using Firebase 8 syntax, but I'm using Firebase 9 in this scenario. I've been struggling to get push notifications to work while the page is open. I have the service worker set up (per Firebase docs) but it doesn't seem to be recognizing that a new message is being received from Twilio in order to actually show the notification.
Docs I've followed:
https://www.twilio.com/docs/conversations/javascript/push-notifications-web
https://firebase.google.com/docs/cloud-messaging/js/client
What I've tried
On my backend, I pass the Push Credential SID when I construct a new ChatGrant:
const chatGrant = new ChatGrant({
pushCredentialSid: process.env.TWILIO_PUSH_CREDENTIAL_SID,
serviceSid: CONVERSATIONS_SID
});
In the frontend, I followed the Twilio documentation to set up Firebase:
init.ts
import { getMessaging, getToken, onMessage } from "firebase/messaging";
import { initializeApp } from "firebase/app";
import { Client } from "#twilio/conversations";
// Omitted
const firebaseConfig = {};
export function getPermission(client: Client) {
const app = initializeApp(firebaseConfig);
const messaging = getMessaging(app);
getToken(messaging, { vapidKey:"KEY" })
.then((data) => {
console.log({ data });
client.setPushRegistrationId("fcm", data).catch((error) => {
console.error({ error });
});
onMessage(messaging, (payload) => {
console.log({ payload });
client.handlePushNotification(payload).catch((error) => {
console.error(error);
// test
});
});
})
.catch((error) => {
console.error(error);
// test
});
}
I call getPermission from this file once when the conversation app loads.
// chatClient is stored in a ref so it doesn't recalculate/refetch/reauthorize all the time
const chatClient = useRef(null);
// [Other code]
chatClient.current = new ConversationClient(data.chatAccessToken);
chatClient.current.on("connectionStateChanged", async (state) => {
switch (state) {
case "connected": {
// Only get permission once the chat client is fully set up
getPermission(chatClient.current);
// ..........
And my service worker firebase-messaging-sw.js:
importScripts('https://www.gstatic.com/firebasejs/9.14.0/firebase-app-compat.js');
importScripts('https://www.gstatic.com/firebasejs/9.14.0/firebase-messaging-compat.js');
if (!firebase.apps.length) {
firebase.initializeApp({
// CONFIG GOES HERE
});
}
const messaging = firebase.messaging();
//background notifications will be received here
messaging.onBackgroundMessage(function(payload) {
console.log('[firebase-messaging-sw.js] Received background message ', payload);
// Customize notification here
const notificationTitle = 'Background Message Title';
const notificationOptions = {
body: 'Background Message body.',
icon: '/android-chrome-192x192.png'
};
self.registration.showNotification(notificationTitle, notificationOptions);
});
What's happening
In the service worker, messaging.onBackgroundMessage never appears to be invoked. I don't know where this issue is derived from - is Twilio not passing message info to Firebase? Or is Firebase not listening to when Twilio sends it the information? Has that changed from v8 to v9?
In init.ts, onMessage is never invoked. Same deal here, is Twilio not passing the right information to Firebase, or did I misconfigure something?
I'm not getting any console errors or warnings, and the network tab is not pointing out anything super helpful.
I got this to work by using the example code (from docs) and configuring my Next.js application to compile the TypeScript into JavaScript. This helped a lot: https://github.com/vercel/next.js/issues/33863#issuecomment-1140518693

Accessing Dexie(Indexed db) inside FCM onBackgroundMessageHandler

I am using FCM for push notifications in my web app and everything is working fine foreground and background except the fact that when my browser is closed, I am receiving FCM notifications but unable to process them. I am storing the data from FCM in an Indexed db wrapped by Dexie and my db is not updated with the FCM data values when the browser is itself closed.
However, if my app's tab is closed but the browser is open, things work fine,
I am using the below code to do my task in my firebase service worker :
messaging.onBackgroundMessage(async function (payload) {
console.log('Received background message ', payload);
const notificationTitle = payload.data.title;
const notificationOptions = {
body: payload.data.body,
tag: "notification-1",
};
if(!db){
await initDb()
}
if (payload.data.type == 'DATA') {
await addDataToDb(payload.data.item)
}
self.registration.showNotification(notificationTitle,
notificationOptions);
});
async function initDb() {
console.log("Initializing Dexie")
db = await new Dexie('AppDB').open()
Dexie.getDatabaseNames().then((names) => {
console.log('Db names ' + names)
});
}
DB updating code is all fine as it works as expected when the browser is still running.
Can someone please help me here?

I can't store data from setBackgroundMessageHandler

I'm using firebase for pushing notification. When I get the notification in the background, I get data. I want to save this data in the local storage, but I get this error
TypeError: Cannot read property 'setItem' of undefined
importScripts('https://www.gstatic.com/firebasejs/3.9.0/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/3.9.0/firebase-messaging.js');
firebase.initializeApp({
messagingSenderId: '628214501041'
});
const messaging = firebase.messaging();
messaging.setBackgroundMessageHandler(function (payload) {
console.log('[firebase-messaging-sw.js] Received background message ', payload);
var notificationTitle = 'Background Message Title';
var notificationOptions = {
body: 'Background Message body.',
};
return self.registration.showNotification(notificationTitle,
notificationOptions);
});
self.addEventListener('push', function (event) {
var pushData = event.data.json();
try {
if(self.window.localStorage){
self.localStorage.setItem('notificationData' , JSON.stringify(pushData) ) ;
// }
}
catch (err) {
console.log('Push error happened:', err);
}
});
The self variable you're using in your code is not something that is defined by default.
But from your code, you're probably looking for:
if(self.window.localStorage) {
self.window.localStorage.setItem('notificationData' , JSON.stringify(pushData) ) ;
}
So the change there is using self.window.localStorage, instead of self.localStorage in the second line.
Well, you can't access the local storage or the session storage from your service worker because it have no access to the DOM.
You should use CacheAPI
Or for persisting data and access it from your service worker and window instance use:
IndexedDB
This is in the lifecyle of your service worker and can also be accessed by the window object.

React native with Firebase FCM

I'm trying to implement push notification with React Native and Firebase through this documentation.
I set up the settings I need by the tutorial.
import React, { Component } from 'react'
import { View } from 'react-native'
import { Input, Text, Button } from '../Components'
import type { RemoteMessage } from 'react-native-firebase'
import firebase from 'react-native-firebase'
import type { Notification, NotificationOpen } from 'react-native-firebase';
export default class TestComponent extends Component {
async componentDidMount() {
await this.SetUpAuth();
await this.SetUpMessaging();
this.notificationOpenedListener = firebase.notifications().onNotificationOpened((notificationOpen: NotificationOpen) => {
// Get the action triggered by the notification being opened
const action = notificationOpen.action;
// Get information about the notification that was opened
const notification: Notification = notificationOpen.notification;
});
const notificationOpen: NotificationOpen = await firebase.notifications().getInitialNotification();
if (notificationOpen) {
console.log(notificationOpen)
// App was opened by a notification
// Get the action triggered by the notification being opened
const action = notificationOpen.action;
// Get information about the notification that was opened
const notification: Notification = notificationOpen.notification;
}
}
componentWillUnmount() {
}
async SetUpAuth() {
const credential = await firebase.auth().signInAnonymouslyAndRetrieveData();
if (credential) {
console.log('default app user ->', credential.user.toJSON());
} else {
console.error('no credential');
}
}
async SetUpMessaging() {
this.notification2 = new firebase.notifications.Notification()
.setNotificationId('notificationId')
.setTitle('My notification title')
.setBody('My notification body')
.android.setChannelId('test')
.android.setClickAction('action')
.setData({
key1: 'value1',
key2: 'value2',
});
this.notification2
.android.setChannelId('channelId')
.android.setSmallIcon('ic_launcher');
console.log('assa')
onTokenRefreshListener = firebase.messaging().onTokenRefresh(fcmToken => {
console.log('token generated ->', fcmToken);
// store.dispatch(DeviceActions.SetFCMToken(fcmToken));
});
const fcmToken = await firebase.messaging().getToken();
if (fcmToken) {
// user has a device token
console.log('has token ->', fcmToken);
console.log(firebase.auth().currentUser._user)
firebase.database().ref(`/users/${firebase.auth().currentUser._user.uid}`).set({ pushToken: fcmToken })
// store.dispatch(DeviceActions.SetFCMToken(fcmToken));
} else {
// user doesn't have a device token yet
console.error('no messaging token');
}
const messagingEnabled = await firebase.messaging().hasPermission();
if (messagingEnabled) {
// user has permissions
console.log('User has FCM permissions');
} else {
// user doesn't have permission
console.log('User does not have FCM permissions');
await this.RequestMessagePermissions();
}
messageListener = firebase.messaging().onMessage((message: RemoteMessage) => {
console.log(`Recieved message - ${JSON.stringify(message)}`);
});
notificationDisplayedListener = firebase
.notifications()
.onNotificationDisplayed(notification => {
// Process your notification as required
// ANDROID: Remote notifications do not contain the channel ID. You will have to specify this manually if you'd like to re-display the notification.
console.log(`Recieved notification 1`);
});
notificationListener = firebase
.notifications()
.onNotification(notification => {
console.log(notification)
firebase.notifications().displayNotification(this.notification2)
// Process your notification as required
console.log(`Recieved notification 2`);
});
}
async RequestMessagePermissions() {
console.log('request')
console.log('Requesting FCM permission');
await firebase
.messaging()
.requestPermission()
.catch(err => console.err(err));
}
render() {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
</View>
)
}
When I try to use it in postman I get success:
{
"success": {
"results": [
{
"messageId": "0:1525013439417985%a0cec506a0cec506"
}
],
"canonicalRegistrationTokenCount": 0,
"failureCount": 0,
"successCount": 1,
"multicastId": 6840884736220792000
}
}
But in my debugger (by console.log) I don't see any new incoming message or something else. I sent a message to my device with the token I added to this post but nothing happened.
it works only when app is in foreground, But I want to make it work also when app in background/closed the app
As mentioned in the docs you need onNotificationOpened listener for android, when the app is in background
Android Background: onNotificationOpened triggered if the notification is tapped.
onNotificationDisplayed is for IOS app in background and triggered if content_available set to true
notificationBackgroundListener = firebase
.notifications()
.onNotificationOpened(notification => {
// Process your notification as required
console.log(`Recieved notification 2`);
});
If your notification is working fine when your app is in foreground then the problem is with your service. There are two possible reasons it's not working.
Either you are sending the payload as
{
time_to_live: 86400,
collapse_key: "xxxxxx",
delay_while_idle: false,
registration_ids: registration_ids,
notification: payload
}
instead of
{
time_to_live: 86400,
collapse_key: "xxxxxx",
delay_while_idle: false,
registration_ids: registration_ids,
data: payload
}
which is because the key in notification is recieved only when app is in foreground.
Another possible reason could be the service worker is getting killed for some reason. ex: I was using a one plus which auto kills the service when I force close the app. So you can try debugging your service in native code by adding log or attaching a debugger
also make sure to add google-service.json file in your android/app folder
Edit:
{
"registration_ids" : ["reg_id"],
"time_to_live": 86400,
"collapse_key": "test_type_b",
"delay_while_idle": false,
"notification": {},
"data": {
"subText":"sub title R",
"title":"Notification Heading R",
"message":"Short big text that will be shown when notification is expanded R",
"color":"red",
"actions": ["hello", "welcome"],
"vibrate": true,
"vibration": 1000,
"ticker": "My Notification Ticker",
"imageUrl": "https://cdn-images-1.medium.com/max/712/1*c3cQvYJrVezv_Az0CoDcbA.jpeg" ,
"bigText": "blalMy big text that will be shown when notification is expanded"
}
}
Here is my headers
Authorization: key=mykey:myKey
Content-Type: application/json
Request:
https://fcm.googleapis.com/fcm/send
which is of post and params are of raw type
you can use Headless JS to run task in background and you should write some native code to handle task , unfortunately is only available in android
https://facebook.github.io/react-native/docs/headless-js-android.html
second way(i don't test it) is to use this library
https://github.com/jamesisaac/react-native-background-task#installation
ps:firebase and other push notification service like onesignal handle push notification easily and you should not be concerned unless you want unique notification for each user

How to pass data to angular app from setBackgroundMessageHandler() when using Firebase in Javascript?

I am using firebase in my angular app to send push notification in web.I implemented messaging.onMessage() for receiving notification when app is in foreground and messaging.setBackgroundMessageHandler() when app is in background.I receive notification without any problem but i need to update values based on the notification is received.I could do update when using messaging.onMessage() but how could we do that when using messaging.setBackgroundMessageHandler().
Even it will be ok if i can store the received value locally and get it in my app.
I did research on this is many places but i couldn't find any solution.
Can some one help me with this?
Thanks!
importScripts('https://www.gstatic.com/firebasejs/3.5.2/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/3.5.2/firebase-messaging.js');
var config = {
apiKey: "MY_API_KEY",
authDomain: "MY_AUTH_DOMAIN",
databaseURL: "MY_DB_URL",
storageBucket: "URL",
messagingSenderId: "VALID_ID"
};
firebase.initializeApp(config);
const messaging = firebase.messaging();
messaging.setBackgroundMessageHandler(function(payload) {
console.log('[firebase-messaging-sw.js] Received background message ', payload);
return self.registration.showNotification("Title", {body: 'New notification.',})
});
After searching for days found a Github page that contains a solution for it.
What you can do is get a list of window clients which will return a list of the tabs for your origin and then post a message to each window client. (This code would be in the setBackgroundMessageHandler()).
To get list of pages is:
const promiseChain = clients.matchAll({
type: 'window',
includeUncontrolled: true
})
.then((windowClients) => {
for (let i = 0; i < windowClients.length; i++) {
const windowClient = windowClients[i];
windowClient.postMessage(data);
}
})
.then(() => {
return registration.showNotification('my notification title');
});
return promiseChain;
Then to receive the message in the page, add a listener like so:
navigator.serviceWorker.addEventListener('message', function(event) {
console.log('Received a message from service worker: ', event.data);
});
Thanks alot guys who contributed in Github.
For more reference go to
https://web-push-book.firebaseapp.com/chapter-05/04-common-notification-patterns/#message-page-from-a-push-event
Hope it will be helpful for you guys.Ping or comment me if you get any doubts in implementing.

Categories