reactjscreate-react-appprogressive-web-appsworkbox

Communicating a successful workbox-background-sync replay to open clients


I'm using React 17, Workbox 5, and react-scripts 4. I created a react app with PWA template using:

npx create-react-app my-app --template cra-template-pwa

I use BackgroundSyncPlugin from workbox-background-sync for my offline requests, so when the app is online again, request will be sent automatically. The problem is I don't know when the request is sent in my React code, so I can update some states, and display a message to the user.

How can I communicate from the service worker to my React code that the request is sent and React should update the state?

Thanks in advance.


Solution

  • You can accomplish this by using a custom onSync callback when you configure BackgroundSyncPlugin. This code is then executed instead of Workbox's built-in replayRequests() logic whenever the criteria to retry the requests are met.

    You can include whatever logic you'd like in this callback; this.shiftRequest() and this.unshiftRequest(entry) can be used to remove queued requests in order to retry them, and then re-add them if the retry fails. Here's an adaption of the default replayRequests() that will use postMessage() to communicate to all controlled window clients when a retry succeeds.

    async function postSuccessMessage(response) {
      const clients = await self.clients.matchAll();
      for (const client of clients) {
        // Customize this message format as you see fit.
        client.postMessage({
          type: 'REPLAY_SUCCESS',
          url: response.url,
        });
      }
    }
    
    async function customReplay() {
      let entry;
      while ((entry = await this.shiftRequest())) {
        try {
          const response = await fetch(entry.request.clone());
          // Optional: check response.ok and throw if it's false if you
          // want to treat HTTP 4xx and 5xx responses as retriable errors.
    
          postSuccessMessage(response);
        } catch (error) {
          await this.unshiftRequest(entry);
    
          // Throwing an error tells the Background Sync API
          // that a retry is needed.
          throw new Error('Replaying failed.');
        }
      }
    }
    
    const bgSync = new BackgroundSyncPlugin('api-queue', {
      onSync: customReplay,
    });
    
    // Now add bgSync to a Strategy that's associated with
    // a route you want to retry:
    registerRoute(
      ({url}) => url.pathname === '/api_endpoint',
      new NetworkOnly({plugins: [bgSync]}),
      'POST'
    );
    

    Within your client page, you can use navigator.seviceWorker.addEventListener('message', ...) to listen for incoming messages from the service worker and take appropriate action.