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);
});
});
Related
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.
I have implemented Workbox to generate my service worker using webpack.
This works pretty well - I can confirm that revision is updated in the generated service worker when running yarn run generate-sw (package.json: "generate-sw": "workbox inject:manifest").
The problem is - I have noticed my clients are not updating the cache after a new release.
Even days after updating the service worker my clients are still caching the old code and new code will only cache after several refreshes and/or unregister the service worker.
For each release the const CACHE_DYNAMIC_NAME = 'dynamic-v1.1.0' is updated.
How can I ensure that clients updates the cache immediately after a new release?
serviceWorker-base.js
importScripts('workbox-sw.prod.v2.1.3.js')
const CACHE_DYNAMIC_NAME = 'dynamic-v1.1.0'
const workboxSW = new self.WorkboxSW()
// Cache then network for fonts
workboxSW.router.registerRoute(
/.*(?:googleapis)\.com.*$/,
workboxSW.strategies.staleWhileRevalidate({
cacheName: 'google-font',
cacheExpiration: {
maxEntries: 1,
maxAgeSeconds: 60 * 60 * 24 * 28
}
})
)
// Cache then network for css
workboxSW.router.registerRoute(
'/dist/main.css',
workboxSW.strategies.staleWhileRevalidate({
cacheName: 'css'
})
)
// Cache then network for avatars
workboxSW.router.registerRoute(
'/img/avatars/:avatar-image',
workboxSW.strategies.staleWhileRevalidate({
cacheName: 'images-avatars'
})
)
// Cache then network for images
workboxSW.router.registerRoute(
'/img/:image',
workboxSW.strategies.staleWhileRevalidate({
cacheName: 'images'
})
)
// Cache then network for icons
workboxSW.router.registerRoute(
'/img/icons/:image',
workboxSW.strategies.staleWhileRevalidate({
cacheName: 'images-icons'
})
)
// Fallback page for html files
workboxSW.router.registerRoute(
(routeData)=>{
// routeData.url
return (routeData.event.request.headers.get('accept').includes('text/html'))
},
(args) => {
return caches.match(args.event.request)
.then((response) => {
if (response) {
return response
}else{
return fetch(args.event.request)
.then((res) => {
return caches.open(CACHE_DYNAMIC_NAME)
.then((cache) => {
cache.put(args.event.request.url, res.clone())
return res
})
})
.catch((err) => {
return caches.match('/offline.html')
.then((res) => { return res })
})
}
})
}
)
workboxSW.precache([])
// Own vanilla service worker code
self.addEventListener('notificationclick', function (event){
let notification = event.notification
let action = event.action
console.log(notification)
if (action === 'confirm') {
console.log('Confirm was chosen')
notification.close()
} else {
const urlToOpen = new URL(notification.data.url, self.location.origin).href;
const promiseChain = clients.matchAll({ type: 'window', includeUncontrolled: true })
.then((windowClients) => {
let matchingClient = null;
let matchingUrl = false;
for (let i=0; i < windowClients.length; i++){
const windowClient = windowClients[i];
if (windowClient.visibilityState === 'visible'){
matchingClient = windowClient;
matchingUrl = (windowClient.url === urlToOpen);
break;
}
}
if (matchingClient){
if(!matchingUrl){ matchingClient.navigate(urlToOpen); }
matchingClient.focus();
} else {
clients.openWindow(urlToOpen);
}
notification.close();
});
event.waitUntil(promiseChain);
}
})
self.addEventListener('notificationclose', (event) => {
// Great place to send back statistical data to figure out why user did not interact
console.log('Notification was closed', event)
})
self.addEventListener('push', function (event){
console.log('Push Notification received', event)
// Default values
const defaultData = {title: 'New!', content: 'Something new happened!', openUrl: '/'}
const data = (event.data) ? JSON.parse(event.data.text()) : defaultData
var options = {
body: data.content,
icon: '/images/icons/manifest-icon-512.png',
badge: '/images/icons/badge128.png',
data: {
url: data.openUrl
}
}
console.log('options', options)
event.waitUntil(
self.registration.showNotification(data.title, options)
)
})
Should I delete the cache manually or should Workbox do that for me?
caches.keys().then(cacheNames => {
cacheNames.forEach(cacheName => {
caches.delete(cacheName);
});
});
Kind regards /K
I think your problem is related to the fact that when you make an update to the app and deploy, new service worker gets installed, but not activated. Which explains the behaviour why this is happening.
The reason for this is registerRoute function also registers fetch listeners , but those fetch listeners won't be called until new service worker kicks in as activated. Also, the answer to your question: No, you don't need to remove the cache by yourself. Workbox takes care of those.
Let me know more details. When you deploy new code, and if users close all the tabs of your website and open a new one after that, does it start working after 2 refreshes? If so , that's how it should be working. I will update my answer after you provide more details.
I'd suggest you read the following: https://redfin.engineering/how-to-fix-the-refresh-button-when-using-service-workers-a8e27af6df68 and follow the 3rd approach.
One way to get WorkBox to update when you have the files locally, not on a CDN, is the following way:
In your serviceworker.js file add an event listener so that WorkBox skips waiting when there is an update, my code looks like this:
importScripts('Scripts/workbox/workbox-sw.js');
if (workbox) {
console.log('Workbox is loaded :)');
// Add a message listener to the waiting service worker
// instructing it to skip waiting on when updates are done.
addEventListener('message', (event) => {
if (event.data && event.data.type === 'SKIP_WAITING') {
skipWaiting();
}
});
// Since I am using Local Workbox Files Instead of CDN I need to set the modulePathPrefix as follows
workbox.setConfig({ modulePathPrefix: 'Scripts/workbox/' });
// other workbox settings ...
}
In your client side page add an event listener for loads if service worker is in the navigator. As a note I am doing this in MVC so I put my code in the _Layout.cshtml so that it can update from any page on my website.
<script type="text/javascript">
if ('serviceWorker' in navigator) {
// Use the window load event to keep the page load performant
window.addEventListener('load', () => {
navigator.serviceWorker
// register WorkBox, our ServiceWorker.
.register("<PATH_TO_YOUR_SERVICE_WORKER/serviceworker.js"), { scope: '/<SOME_SCOPE>/' })
.then(function (registration) {
/**
* Whether WorkBox cached files are being updated.
* #type {boolean}
* */
let updating;
// Function handler for the ServiceWorker updates.
registration.onupdatefound = () => {
const serviceWorker = registration.installing;
if (serviceWorker == null) { // service worker is not available return.
return;
}
// Listen to the browser's service worker state changes
serviceWorker.onstatechange = () => {
// IF ServiceWorker has been installed
// AND we have a controller, meaning that the old chached files got deleted and new files cached
// AND ServiceWorkerRegistration is waiting
// THEN let ServieWorker know that it can skip waiting.
if (serviceWorker.state === 'installed' && navigator.serviceWorker.controller && registration && registration.waiting) {
updating = true;
// In my "~/serviceworker.js" file there is an event listener that got added to listen to the post message.
registration.waiting.postMessage({ type: 'SKIP_WAITING' });
}
// IF we had an update of the cache files and we are done activating the ServiceWorker service
// THEN let the user know that we updated the files and we are reloading the website.
if (updating && serviceWorker.state === 'activated') {
// I am using an alert as an example, in my code I use a custom dialog that has an overlay so that the user can't do anything besides clicking okay.
alert('The cached files have been updated, the browser will re-load.');
window.location.reload();
}
};
};
console.log('ServiceWorker registration successful with scope: ', registration.scope);
}).catch(function (err) {
//registration failed :(
console.log('ServiceWorker registration failed: ', err);
});
});
} else {
console.log('No service-worker on this browser');
}
</script>
Note: I used the browser's service worker to update my WorkBox cached files, also, I've only tested this in Chrome, I have not tried it in other browsers.
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)
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('/')
}))
}
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.