PWA: Registration event listeners don't get triggered

203 Views Asked by At

I have a PWA react app, which I can install like a PWA. So the PWA configuration seems to be setup correctly.
The service worker file is the same file generated when the project was created using create-react-app.

Anyway, I tried adding event listeners but none of them get added:

function registerValidSW(swUrl, config) {
  navigator.serviceWorker.register(swUrl).then((registration) => {
    // This gets logged
    console.log(" ~ file: serviceWorker.js:168 ~ Registered service worker:");
    console.log(
      " ~ file: serviceWorker.js:168 ~ navigator.serviceWorker.register ~ registration:",
      registration
    );

    registration.addEventListener("activate", function (event) {
      // This does not get logged
      console.log(" ~ file: Added activate event listener to registration");
    });

    registration.addEventListener("push", async function (event) {
      // This does not get logged
      console.log(" ~ file: Added push event listener to registration");
    });

    registration.addEventListener("fetch", (event) => {
      // This does not get logged
      console.log(" ~ file: Added fetch event listener to fetch");
    });
  });
}

activate event listener

Nothing gets triggered after the service worker is activated.

push event listener

It doesn't get triggered if I send a push notification

fetch event listener

It doesn't get triggered if I open a route

Any idea what's going on?

1

There are 1 best solutions below

10
VonC On BEST ANSWER

I see event listeners being added to the registration object.

But the events like "activate," "push," and "fetch" should be handled inside the service worker itself, not on the registration object.

Meaning this would not work:

function registerValidSW(swUrl, config) {
  navigator.serviceWorker.register(swUrl).then((registration) => {
    // That gets logged
    console.log(" ~ file: serviceWorker.js:168 ~ Registered service worker:");
    console.log(
      " ~ file: serviceWorker.js:168 ~ navigator.serviceWorker.register ~ registration:",
      registration
    );

    // Incorrect: Adding 'activate' event listener to registration object
    registration.addEventListener("activate", function (event) {
      // That does not get logged
      console.log(" ~ file: Added activate event listener to registration");
    });

    // Incorrect: Adding 'push' event listener to registration object
    registration.addEventListener("push", async function (event) {
      // That does not get logged
      console.log(" ~ file: Added push event listener to registration");
    });

    // Incorrect: Adding 'fetch' event listener to registration object
    registration.addEventListener("fetch", (event) => {
      // That does not get logged
      console.log(" ~ file: Added fetch event listener to fetch");
    });
  });
}

The service worker file is a separate JavaScript file that runs in the context of the service worker and not in the main thread of the page.

https://web-dev.imgix.net/image/RK2djpBgopg9kzCyJbUSjhEGmnw1/iKWO7c2WNobLt30VZx9C.png?auto=format&w=845

In the main thread of your page (e.g., in your serviceWorker.js file), register the service worker, like you are doing already. That part is responsible for registering the service worker file and does not handle service worker events like "activate," "push," and "fetch."

Inside the service worker file (e.g., service-worker.js), you would add the event listeners for "activate","push," and "fetch." That file should be the same one that is being registered by the navigator.serviceWorker.register(swUrl) call.

For example:

// The 'activate' event
self.addEventListener('activate', event => {
  console.log(" ~ Service Worker activated!");
  // Your activation logic here
});

// The 'push' event
self.addEventListener('push', event => {
  console.log(" ~ Push event received!");
  // Your push logic here
});

// The 'fetch' event
self.addEventListener('fetch', event => {
  console.log(" ~ Fetch event triggered!");
  // Your fetch logic here
});

That code should be placed in a separate JavaScript file that does not use ES6 imports or other page-level code, as it will run in the service worker context.

You can then check if you see those log messages when the corresponding events are triggered.


I don't have a service-worker.js file.

Even though, this is the swUrl used for the registration: const swUrl = ${process.env.PUBLIC_URL}/service-worker.js; registerValidSW(swUrl, config);

Should I add it somewhere or something?
I'm also confused how come it doesn't exist, and the PWA setup works correct.

I see the "Create React App" (CRA) feature should automatically generate a service worker file for you when you opt into using a service worker (for example, by using the cra-template-pwa template or manually configuring it).
In a standard CRA setup, you don't have to write your own service worker file, but it does indeed exist and is usually generated during the build process.

Since the generated service worker file in a Create React App (CRA) project is usually part of the build process, and it gets overwritten every time you build the project, it means that if you modify it directly and then run a build, ... your changes will be lost.

You could instead try and run npm run eject (or yarn eject), which will expose all the configuration files, including the service worker file. This allows you to modify it directly but comes with the drawback of having to manage the entire build configuration yourself.


Another approach is to create your own custom service worker file and include it in your project. You can register it just like the generated service worker. Make sure to handle all the necessary caching and other PWA features yourself in this custom service worker.

For instance, to create and register a custom service worker (custom-sw.js):

// custom-sw.js (Your custom service worker file)
self.addEventListener('activate', event => {
  console.log("Custom service worker activated!");
});

self.addEventListener('push', event => {
  console.log("Custom push event received!");
});

self.addEventListener('fetch', event => {
  console.log("Custom fetch event triggered!");
});

Then in your serviceWorker.js file, you can change the registration URL to point to your custom service worker:

const swUrl = `${process.env.PUBLIC_URL}/custom-sw.js`;
registerValidSW(swUrl, config);

Make sure that the custom service worker file is served from the correct location. You might need to update your build configuration or place the file in the public folder, depending on how your project is structured.


Or you can use Workbox: a set of libraries that can make service worker development easier, and it's often used with CRA to create custom service worker logic.
See "Making a Progressive Web App"

Starting with Create React App 4, you can add a src/service-worker.js file to your project to use the built-in support for Workbox's InjectManifest plugin, which will compile your service worker and inject into it a list of URLs to precache.


The OP mentions in the comments the page "How to add event listeners to create-react-app default sw.js file", adding:

It's much more straightforward. You just appened the listeners to the service-worker file after the build is finished

That method effectively allows you to inject custom code into the generated service worker file without having to modify it directly, preserving the advantages of CRA's build process while still giving you the flexibility to add your custom logic.

  • You write your custom service worker code in a separate file, such as sw-epilog.js. That could include event listeners or other code that you want to append to the generated service worker.

  • You use a script in your package.json file to concatenate this custom code file onto the end of the generated service worker file after the build process is completed. The >> operator in the shell command cat src/sw-epilog.js >> build/service-worker.js does this by taking the content of sw-epilog.js and appending it to the end of service-worker.js.

  • You include this concatenation step in your build process so that it happens automatically every time you build your project.