Ali Mirmohammad
Ali Mirmohammad

Reputation: 63

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.

Upvotes: 1

Views: 1436

Answers (1)

Jeff Posnick
Jeff Posnick

Reputation: 56044

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.

Upvotes: 3

Related Questions