I have a service-worker.js
file to make my reactjs app PWA. I now also want to add push notification using FCM, which requires me to have firebase-messaging-sw.js
in the public folder. So now for both to work both are going to require to be in the same scope.
But as far as I have read from various answers on this site, we can’t have two different service workers in the same scope, so how do we combine both service-worker.js
and firebase-messaging-sw.js
so both can function properly. One of the answers suggested that I rename the service-worker.js
to firebase-messaging-sw.js
which doesn’t work. I did find one successful implementation on GitHub which I didn’t understand much https://github.com/ERS-HCL/reactjs-pwa-firebase .
How can I have both the service-worker.js
and firebase-messaging-sw.js
work together?
firebase-messaging-sw.js
// Scripts for firebase and firebase messaging
importScripts("https://www.gstatic.com/firebasejs/8.2.0/firebase-app.js");
importScripts("https://www.gstatic.com/firebasejs/8.2.0/firebase-messaging.js");
// Initialize the Firebase app in the service worker by passing the generated config
const firebaseConfig = {
apiKey: "xxxx",
authDomain: "xxxx.firebaseapp.com",
projectId: "xxxx",
storageBucket: "xxxx",
messagingSenderId: "xxxx",
appId: "xxxx",
measurementId: "xxxx"
}
firebase.initializeApp(firebaseConfig);
// Retrieve firebase messaging
const messaging = firebase.messaging();
self.addEventListener("notificationclick", function (event) {
console.debug('SW notification click event', event)
const url = event.notification.data.link
event.waitUntil(
clients.matchAll({type: 'window'}).then( windowClients => {
// Check if there is already a window/tab open with the target URL
for (var i = 0; i < windowClients.length; i++) {
var client = windowClients[i];
// If so, just focus it.
if (client.url === url && 'focus' in client) {
return client.focus();
}
}
// If not, then open the target URL in a new window/tab.
if (clients.openWindow) {
return clients.openWindow(url);
}
})
);
})
messaging.onBackgroundMessage(async function(payload) {
console.log("Received background message ", payload)
const notificationTitle = payload.notification.title
const notificationOptions = {
body: payload.notification.body,
icon: './logo192.png',
badge: './notification-badgex24.png',
data: {
link: payload.data?.link
}
}
self.registration.showNotification(notificationTitle, notificationOptions)
})
service-worker.js
import { clientsClaim } from 'workbox-core';
import { ExpirationPlugin } from 'workbox-expiration';
import { precacheAndRoute, createHandlerBoundToURL } from 'workbox-precaching';
import { registerRoute } from 'workbox-routing';
import { StaleWhileRevalidate } from 'workbox-strategies';
clientsClaim();
const fileExtensionRegexp = new RegExp('/[^/?]+\.[^/]+$');
registerRoute(
// Return false to exempt requests from being fulfilled by index.html.
({ request, url }) => {
// If this isn't a navigation, skip.
if (request.mode !== 'navigate') {
return false;
} // If this is a URL that starts with /_, skip.
if (url.pathname.startsWith('/_')) {
return false;
} // If this looks like a URL for a resource, because it contains // a file extension, skip.
if (url.pathname.match(fileExtensionRegexp)) {
return false;
} // Return true to signal that we want to use the handler.
return true;
},
createHandlerBoundToURL(process.env.PUBLIC_URL + '/index.html')
);
registerRoute(
// Add in any other file extensions or routing criteria as needed.
({ url }) => url.origin === self.location.origin && url.pathname.endsWith('.png'), // Customize this strategy as needed, e.g., by changing to CacheFirst.
new StaleWhileRevalidate({
cacheName: 'images',
plugins: [
// Ensure that once this runtime cache reaches a maximum size the
// least-recently used images are removed.
new ExpirationPlugin({ maxEntries: 50 }),
],
})
);
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'SKIP_WAITING') {
self.skipWaiting();
}
});
2
Answers
Ok, after spending weeks, I figured it out. So for anyone else, using the service worker created using the default create react app, follow the steps below.
first create
firebase-messaging-sw.js
in public folder and put the same code as in the question, but this time also addimportScripts("/service-worker.js")
.The import will import the
service-worker.js
after the build step. The service worker you have in the src folder is only a template. You cannot useimportScript
in theservice-worker.js
file that's in thesrc
folder as that will throwimportScripts is not defined
error.Your build folder after your build step:
Now in index.html add
And that's it. Both firebase-messaging and your PWA service worker will work
or you can also
You can create a new file called
sw.js
in your public folder and useimporScript()
to import bothfirebase-messaging-sw.js
and defaultservice-worker.js
make sure to pass in service worker registration in
getToken
of the firebasenow simply register your new service worker in index file as shown
I think one way of doing this is just having only firebase-messaging-sw.js file and using workbox to inject your service-worker.js into it
https://developer.chrome.com/docs/workbox/precaching-with-workbox/
forexample :
and all firebase config must be in sw.js to be written in firebase-messaging-sw.js
and in your package.json simply run build-sw.js before react-script start
or you can use react-app-rewired and workbox box plugin instead