FCM default onMessage override? - javascript

I have a small web app which receives FCM messages, have a service worker, handles background messages and all are well. The Service Worker will receive the messages and will post them back to the main page so they can be displayed on page. That is for the background messages.
My problem is with the foreground messages being handled by some default onMessage() which passes them as well to the main page as messages encapsulated in a big message. So, lets say, if I have defined my own onMessage() function in a .js or in the main page itself, this message will be called and then the default one as well, which causes the message to be handled twice.
Below is the message I am receiving:
The code in the service worker is:
importScripts('https://www.gstatic.com/firebasejs/4.8.1/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/4.8.1/firebase-messaging.js');
var config = {
apiKey: "AIzaSyBoi6gB1BHKxFBt58JJ4phWiZr9BKJaphI",
authDomain: "webmessaging-7bef1.firebaseapp.com",
databaseURL: "https://webmessaging-7bef1.firebaseio.com",
projectId: "webmessaging-7bef1",
storageBucket: "webmessaging-7bef1.appspot.com",
messagingSenderId: "649072625962"
};
// Initialize the Firebase app in the service worker by passing in the
// messagingSenderId.
firebase.initializeApp({
'messagingSenderId': '649072625962'
});
const messaging = firebase.messaging();
self.addEventListener('notificationclick', function (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({
includeUncontrolled: true,
type: "all"
})
.then(function (clientList) {
console.log('Clients Count: ', clientList.length);
for (var i = 0; i < clientList.length; i++) {
var client = clientList[i];
var clientUrl = client.url;
console.log('Client URL:', client.url);
if ((clientUrl.indexOf("localhost") != -1) && 'focus' in client)
return client.focus();
}
if (clients.openWindow) {
return clients.openWindow('https://deanhume.github.io/typography');
}
})
);
});
messaging.setBackgroundMessageHandler(function (payload) {
console.log('[firebase-messaging-sw.js] Received background message ', payload);
// Customize notification here
const notificationTitle = payload.data.title;
const notificationOptions = {
body: payload.data.body,
icon: '/fcm-logo.png'
};
clients.matchAll({
includeUncontrolled: true,
type: "all"
})
.then(function (clientList) {
console.log('Clients Count: ', clientList.length);
for (var i = 0; i < clientList.length; i++) {
var client = clientList[i];
var clientUrl = client.url;
console.log('Client URL:', client.url);
if ((clientUrl.indexOf("localhost") != -1) && 'focus' in client) {
// client.postMessage(payload.data);
}
}
})
return self.registration.showNotification(notificationTitle,
notificationOptions);
});
If I remove my onMessage() handler, I will still get the message posted by the default onMessage() handler.
Please, advise.
Thanks...
[UPDATE]
After some thorough checkups, I have come to some conclusion that the issue appears to be in the fact that I have the following two functions which appear to be doing exactly the same thing by receiving messages sent to the page.
This one is is supposed to receive the messages when the page is in the foreground and having focus.
messaging.onMessage(function (payload) {
console.log('Message received: ', payload);
alert(payload.data.title);
// Get the data passed by the message and display on the page...
document.getElementById('Select1').innerHTML += "<option>" + payload.data.title + "</option>";
});
And this one is mean to be receiving the data passed by the service worker when the message is received while the page is at the background and not having focus.
// Listen to messages from service workers.
navigator.serviceWorker.addEventListener('message', function (event) {
console.log("Got reply from service worker: ", event);
// Get the data passed by the message and display on the page...
document.getElementById('Select1').innerHTML += "<option>" + event.data.title + "</option>";
});
So, basically, I am listening to the messages via two handlers at the same place and at the same time which results in the messages being handled twice at the same time...
Any advise how I can properly organize this code?

A couple of notes:
1- You should not include config details in the service worker (as it resides on the client's end) and restrict it to the required:
firebase.initializeApp({
'messagingSenderId': '649072625962'
});
2- messaging.setBackgroundMessageHandler is only triggered in the case of the background.
3- messaging.onMessage() you don't define in the service worker but rather in a .js as you mentioned to handle the cases of the foreground scenarios and will be triggered only in that case (there is no default definition of it until you define it)

Related

Firebase Messaging Web getToken returns an expired token after 1-2 days of working correctly (that cannot be deleted)

I have a firebase messaging service worker setup to custom handle push notifications (data messaging) in my web app that uses messaging.setBackgroundMessageHandler to handle background messages. The app works perfectly in localhost, but only for a few hours or 1-2 days on my dev server, after which it stops receiving any notifications.
The problem is that when I load the page and I call firebase messaging.getToken(...) to register it to my server, when a notification is sent on the server (java firebase admin sdk) to that token I just received, there is this error:
com.google.firebase.messaging.FirebaseMessagingException
Requested entity was not found.
Also, if the user logs out of the app, I call messaging.deleteToken(currentToken) and unregister from server. But the javascript delete token method does not work and I get the error (dev tools browser console):
DELETE firebase-messaging.js:1 DELETE https://fcmregistrations.googleapis.com/v1/projects/myproject/registrations/[MY_TOKEN] 404
firebase-messaging.js:1 Uncaught (in promise) FirebaseError: Messaging: A problem occured while unsubscribing the user from FCM: FirebaseError: Messaging: A problem occured while unsubscribing the user from FCM: Requested entity was not found. (messaging/token-unsubscribe-failed). (messaging/token-unsubscribe-failed).
at https://www.gstatic.com/firebasejs/7.17.1/firebase-messaging.js:1:24434
So I cannot even delete that token that is actually given by the Firebase SDK.
Eventually, if I just unregister my service worker and refresh the page, I get another token and everything works again.
What am I missing? (I have the latest javascript firebase sdk 7.17.1; I found nothing else regarding this issue on google)
Portions of code are as follows (some are written in GWT, but only the wrapping part; the integration with firebase is in pure javascript):
Frontpage (window client):
//initialization in web client is done by calling this method
public final void initialize(String publicKey, FirebaseCallback callback) {
usePublicVapidKey(publicKey);
String context = GWT.getHostPageBaseURL(); //current web context (host + server web context)
init_native(context , "fcm-sw-import.js", callback); // my service worker
}
init_native function contains:
var messaging = this
//$wnd is actually "window"
$wnd.navigator.serviceWorker.getRegistrations().then(function(registrations) {
for (i = 0; i < registrations.length; i++) {
if (registrations[i].active && registrations[i].active.scriptURL.includes('fcm-sw-import.js') == false) {
registrations[i].unregister();
console.log ("FCM SW unregistered other service workers: " + registrations[i], registrations[i]);
}
}
});
$wnd.navigator.serviceWorker.register(fcmServiceWorkerJSfile, {scope: '.'})
.then(function(registration) {
registration.update();
messaging.useServiceWorker(registration);
$wnd.navigator.serviceWorker.ready.then(function (registration) {
console.log('FCM SW ready');
//request permissions, get token, register to server, register my callbacks etc.
messaging.#...fcm.FirebaseMessaging::requestPermissionAndRetrieveToken (L...fcm/FirebaseCallback;)(callback);
});
//console.log("SW - registered");
});
request permissions and get token:
var messaging = this;
messaging.requestPermission().then(function() {
messaging.getToken().then(function(currentToken) {
messaging.currentFcmToken = currentToken;
if (currentToken) {
$wnd.navigator.serviceWorker.addEventListener('message', function handler(event) {
....
});
...
}
else {
// Show permission request.
...
// Show permission UI.
...
}
})['catch'](function(err) { //use [catch] so gwt wont think is a keyword
console.log(err);
... on error callback
});
// Callback fired if Instance ID token is updated.
messaging.onTokenRefresh(function() {
messaging.getToken().then(function(refreshedToken) {
var oldToken = messaging.currentFcmToken;
messaging.currentFcmToken = refreshedToken;
...on token refresh callback
})['catch'](function(err) {
});
});
messaging.onMessage(function(payload) {
...
});
})['catch'](function(err) {
...
});
Service worker is fcm-sw-import.js:
importScripts('app/fcm-sw-reg.js?v26'); //Production
setupAndInit({
apiKey: ...,
authDomain: ...,
databaseURL: ...,
messagingSenderId: ...,
projectId: ...,
storageBucket: "",
messagingSenderId: ...,
appId: ...
}, '.', "e-cache-v1");
setupAndInit method is in "fcm-sw-reg.js":
importScripts('https://www.gstatic.com/firebasejs/7.17.1/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/7.17.1/firebase-messaging.js');
... other imports, vars
function setupAndInit(config, urlContext, cacheName) {
//initialize(config, urlContext);
SENDER_ID = config.messagingSenderId;
urlContextPath = urlContext;
if (!firebase.apps.length) {
firebase.initializeApp(config);
}
const messaging = firebase.messaging();
CACHE_NAME = cacheName;
self.addEventListener('install', function(event) {
// Perform install steps
event.waitUntil(
caches.open(CACHE_NAME)
.then(function(cache) {
logToConsole('SW - Opened cache');
return cache.addAll(urlsToCache);
})
);
self.skipWaiting(); // Activate worker immediately
});
self.addEventListener('activate', function(event) {
self.clients.claim();
});
self.addEventListener('fetch', function(e) {
e.respondWith(
caches.match(e.request).then(function(response) {
return response || fetch(e.request);
})
);
});
messaging.setBackgroundMessageHandler(function(payload) {
var wnd = self;
logToConsole('SW - FCM Received fcm background message ', payload);
if (payload.from != SENDER_ID) { //cancel if the sender does not match
return;
}
var data = payload.data;
var notifExporter = new evc.NotificationDetailsExporter(data);
var notifDetails = notifExporter.getNotificationDetails();
var notificationTitle;
var notificationOptions;
if (notifDetails != null) {
var notificationTitle = notifDetails.title;
var iconPath = data.frImLink ? data.frImLink : '';
var text = data.string13 ? data.string13 : "";
var value = data.value ? " - " + data.value : "";
notificationOptions = {
body: notifDetails.message,
icon: iconPath,
data: data
};
}
logToConsole('SW - sending message to wclients...');
clients.matchAll().then(clients => {
clients.forEach(client => {
send_message_to_client(client, {ebasFCM:"true", data: payload.data});
})
})
if (notificationTitle)
return self.registration.showNotification(notificationTitle,
notificationOptions);
else
return null;
});
logToConsole("SW FCM - Initialized FCM worker: " + SENDER_ID + " at: " + urlContextPath);
self.addEventListener('notificationclick', function(event) {
event.waitUntil(clients.matchAll({
includeUncontrolled: true,
type: "window"
}).then(function(clientList) {
logToConsole("SW FCM - Searching client list: ", clientList);
for (var i = 0; i < clientList.length; i++) {
var client = clientList[i];
logToConsole("SW FCM - Searching client urls: ", client.url);
}
logToConsole("SW FCM - url context path: ", urlContextPath);
logToConsole("SW FCM - notif click event: ", event);
var notifExporter = new evc.NotificationDetailsExporter(event.notification.data);
var toUrl = notifExporter.getNotificationContextUrl();
if (clientList.length) {
if (toUrl)
send_message_to_client(clientList[0], {type: "openUrl", url: toUrl});
return clientList[0].focus();
}
else if (clients.openWindow) {
if (toUrl == null)
toUrl = "";
return clients.openWindow( urlContextPath + toUrl);
}
}));
event.notification.close();
});
logToConsole('SW FCM - Setup fcm done');
}
Please advise. Thank you.

Web Push Notification FCM on Mobile

I'm trying to send push notification to users in my Web App but it is not working on mobile.
I'm using VueJs + Firebase, I use a Cloud Function to send the notification and in the service worker, I receive and handle for show to the user.
It works in the Browser on PC, but don't work in mobile 'Android', here are some code:
Cloud Function:
}).then(function (aTokens) {
const payload = {
data: {
title: 'Test-Title',
body: 'Test-Body'
}
}
// Send notifications to all tokens.
return admin.messaging().sendToDevice(aTokens, payload).then(res => {
res.results.forEach((result, index) => {
const error = result.error
if (error) {
console.error('Failure sending notification to', tokens[index], error)
}
})
response.send('OK')
})
})
Service-Worker Function:
importScripts('https://www.gstatic.com/firebasejs/3.9.0/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/3.9.0/firebase- messaging.js');
var config = {
messagingSenderId: "123456789"
}
firebase.initializeApp(config);
var messaging = firebase.messaging();
messaging.setBackgroundMessageHandler(function (payload) {
var title = payload.data.title;
var options = {
body: payload.data.body
};
return self.registration.showNotification(title, options);
});
In Browser the Notification work See The Image:
But in Android it doesn't work, I have the last version or chrome and firefox and I tested in both, I also put the Web app in the home screen and allowed to send notification too. But the notifications never show there.
What I need to include for I see push notifications on mobile too?

Create Firebase notification with page in foreground/focus

With Firebase Cloud Messaging (for web), how can I generate the notification that appears when the webpage is closed or in the background, but when I'm actually focused on the webpage?
It's my understanding that messaging.onMessage(...) is where I handle incoming messages when the page is in focus, but I can't seem to find documentation on how I could still create the notification pop-ups as though the page were in the background.
Thanks for your time!
handle incoming messges by Notification API
messaging.onMessage(function(payload) {
const notificationTitle = payload.notification.title;
const notificationOptions = {
body: payload.notification.body,
icon: payload.notification.icon,
};
if (!("Notification" in window)) {
console.log("This browser does not support system notifications");
}
// Let's check whether notification permissions have already been granted
else if (Notification.permission === "granted") {
// If it's okay let's create a notification
var notification = new Notification(notificationTitle,notificationOptions);
notification.onclick = function(event) {
event.preventDefault(); // prevent the browser from focusing the Notification's tab
window.open(payload.notification.click_action , '_blank');
notification.close();
}
}
});
Notification is deprecated.
send message to service worker
messaging.onMessage(function(payload) {
local_registration.active.postMessage(payload);
}
receive message and show push from sw.js
self.addEventListener('notificationclick', function(event) {
console.log('[firebase-messaging-sw.js] Received notificationclick event ', event);
var click_action = event.notification.data;
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 == click_action && 'focus' in client)
return client.focus();
}
if (clients.openWindow)
return clients.openWindow(click_action);
}));
});
const showMessage = function(payload){
console.log('showMessage', payload);
const notificationTitle = payload.data.title;
const notificationOptions = {
body: payload.data.body,
icon: payload.data.icon,
image: payload.data.image,
click_action: payload.data.click_action,
data:payload.data.click_action
};
return self.registration.showNotification(notificationTitle,notificationOptions);
}
messaging.setBackgroundMessageHandler(showMessage);
self.addEventListener('message', function (evt) {
console.log("self",self);
showMessage( evt.data );
})
Since Notification is deprecated in Android, you should use Firebase serviceWorker registration to show the notification.
As of Feb 2020, it looks like Firebase registers its serviceWorker with the '/firebase-cloud-messaging-push-scope' scope (as can be seen in chrome devtools -> Application -> Service Workers)
To use that, you can do:
messaging.onMessage(function(payload) {
console.log("onMessage: ", payload);
navigator.serviceWorker.getRegistration('/firebase-cloud-messaging-push-scope').then(registration => {
registration.showNotification(
payload.notification.title,
payload.notification
)
});
});
More clean approach would be:
messaging.onMessage(payload => {
const {title, ...options} = payload.notification;
navigator.serviceWorker.ready.then(registration => {
registration.showNotification(title, options);
});
});

navigator.serviceWorker.controller is null until page refresh

I work with angularjs and use service worker to receive push notification.
but navigator.serviceWorker.controller is null until page refresh,and I don't know how to do to solve this problem
some code of serviceworker :
self.addEventListener('push', pwServiceWorker.pushReceived);
self.addEventListener('notificationclick', pwServiceWorker.notificationClicked);
// refresh caches
self.addEventListener('activate', function (event)
{
event.waitUntil(
caches.keys().then(function (cacheNames)
{
return Promise.all(
cacheNames.map(function (cacheName)
{
return caches.delete(cacheName);
})
);
})
);
});
and send message to the client in serviceworker when push received :
self.clients.matchAll().then(function(all) {
console.log(all);
all.forEach(function(client) {
client.postMessage(data);
});
});
in mainController.js give message like this :
if (!navigator.serviceWorker || !navigator.serviceWorker.register) {
console.log("This browser doesn't support service workers");
return;
}
// Listen to messages from service workers.
navigator.serviceWorker.addEventListener('message', function(event) {
console.log("Got reply from service worker: " + event.data);
});
// Are we being controlled?
if (navigator.serviceWorker.controller) {
// Yes, send our controller a message.
console.log("Sending 'hi' to controller");
navigator.serviceWorker.controller.postMessage("hi");
} else {
// No, register a service worker to control pages like us.
// Note that it won't control this instance of this page, it only takes effect
// for pages in its scope loaded *after* it's installed.
navigator.serviceWorker.register("service-worker.js")
.then(function(registration) {
console.log("Service worker registered, scope: " + registration.scope);
console.log("Refresh the page to talk to it.");
// If we want to, we might do `location.reload();` so that we'd be controlled by it
})
.catch(function(error) {
console.log("Service worker registration failed: " + error.message);
});
}
This is expected behavior. To take control over all open pages without waiting for refresh/reopen, you have to add these commands to your Service Worker:
self.addEventListener('install', function(event) {
event.waitUntil(self.skipWaiting()); // Activate worker immediately
});
self.addEventListener('activate', function(event) {
event.waitUntil(self.clients.claim()); // Become available to all pages
});
You can read more about them in skipWaiting() docs and clients.claim() docs.
Make sure the scope of your service worker includes the url in question.

Updating Web App User Interface when application is in background FCM

Am using FCM to handle notifications, it works fine up until when I need to update my UI from the firebase-messaging-sw.js when my web app is in the background.
My first question is: is it possible to update my web app UI in the background (When user is not focused on the web app) through a service worker
Secondly, if so, how? because I tried a couple of things and its not working, obviously am doing something wrong and when it does work, my web app is in the foreground. What am I doing wrong?
My codes are below.
my-firebase-service-sw.js
// [START initialize_firebase_in_sw]
// 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/4.1.1/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/4.1.1/firebase-messaging.js');
// My Custom Service Worker Codes
var CACHE_NAME = 'assembly-v0.1.3.1';
var urlsToCache = [
'/',
'lib/vendors/bower_components/animate.css/animate.min.css',
'lib/vendors/bower_components/sweetalert/dist/sweetalert.css',
'lib/css/app_1.min.css',
'lib/css/app_2.min.css',
'lib/css/design.css'
];
var myserviceWorker;
var servicePort;
// Install Service Worker
self.addEventListener('install', function (event) {
console.log('installing...');
// Perform install steps
event.waitUntil(
caches.open(CACHE_NAME)
.then(function (cache) {
console.log('Opened cache');
return cache.addAll(urlsToCache);
})
);
console.log('installed...');
});
// Service Worker Active
self.addEventListener('activate', function (event) {
console.log('activated!');
// here you can run cache management
var cacheWhitelist = [CACHE_NAME];
event.waitUntil(
caches.keys().then(function (cacheNames) {
return Promise.all(
cacheNames.map(function (cacheName) {
if (cacheWhitelist.indexOf(cacheName) === -1) {
return caches.delete(cacheName);
}
})
);
})
);
});
self.addEventListener('fetch', function (event) {
event.respondWith(
caches.match(event.request)
.then(function (response) {
// Cache hit - return response
if (response) {
return response;
}
// IMPORTANT: Clone the request. A request is a stream and
// can only be consumed once. Since we are consuming this
// once by cache and once by the browser for fetch, we need
// to clone the response.
var fetchRequest = event.request.clone();
return fetch(fetchRequest).then(
function (response) {
// Check if we received a valid response
if (!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
// IMPORTANT: Clone the response. A response is a stream
// and because we want the browser to consume the response
// as well as the cache consuming the response, we need
// to clone it so we have two streams.
var responseToCache = response.clone();
caches.open(CACHE_NAME)
.then(function (cache) {
cache.put(event.request, responseToCache);
});
return response;
}
);
})
);
});
self.addEventListener('message', function (event) {
console.log("SW Received Message: " + event.data);
// servicePort = event;
event.ports[0].postMessage("SW Replying Test Testing 4567!");
});
myserviceWorker = self;
// Initialize the Firebase app in the service worker by passing in the
// messagingSenderId.
firebase.initializeApp({
'messagingSenderId': '393093818386'
});
// Retrieve an instance of Firebase Messaging so that it can handle background
// messages.
const messaging = firebase.messaging();
// [END initialize_firebase_in_sw]
// If you would like to customize notifications that are received in the
// background (Web app is closed or not in browser focus) then you should
// implement this optional method.
// [START background_handler]
messaging.setBackgroundMessageHandler(function (payload) {
console.log('[firebase-messaging-sw.js] Received background message ', payload);
// Customize notification here
// send to client
console.log('Sending data to notification');
try {
myserviceWorker.clients.matchAll().then(function (clients) {
clients.forEach(function (client) {
console.log('sending to client ' + client);
client.postMessage({
"msg": "401",
"dta": payload.data
});
})
});
} catch (e) {
console.log(e);
}
const notificationTitle = payload.data.title;;
const notificationOptions = {
body: payload.data.body,
icon: payload.data.icon,
click_action: "value"
};
return self.registration.showNotification(notificationTitle,
notificationOptions);
});
// [END background_handler]
In my main javascript file, which receives the payload. it receives it when the application is in the foreground. My major concern and problem is receiving payload when the application is in the background, all activities on foreground works just fine.
It is possible to update the UI even your website is opening but unfocused.
Just add enable option includeUncontrolled when you get all client list.
Example:
messaging.setBackgroundMessageHandler(function (payload) {
console.log('[firebase-messaging-sw.js] Received background message ', payload);
self.clients.matchAll({includeUncontrolled: true}).then(function (clients) {
console.log(clients);
//you can see your main window client in this list.
clients.forEach(function(client) {
client.postMessage('YOUR_MESSAGE_HERE');
})
})
});
In your main page, just add listener for message from service worker.
Ex:
navigator.serviceWorker.addEventListener('message', function (event) {
console.log('event listener', event);
});
See Clients.matchAll() for more details.

Categories

Resources