Como gerenciar atualizações do service worker com urgência

Por padrão, o ciclo de vida do service worker exige que quando um service worker atualizado for encontrado e instalado, todas as guias abertas que o service worker está controlando precisarão ser fechadas ou passar por uma navegação antes que o service worker atualizado seja ativado e assuma o controle.

Em muitos casos, não há problema em permitir que isso aconteça no momento certo, mas, em alguns casos, convém informar ao usuário que há uma atualização pendente do service worker e automatizar o processo de alternância para o novo service worker. Para fazer isso, você precisa adicionar um código à sua página e ao service worker.

O código a ser colocado na sua página

O código a seguir é executado em um elemento <script> inline usando módulos JavaScript importados de uma versão hospedada pela CDN de workbox-window. Ele registra um service worker usando workbox-window e reagirá se o service worker ficar travado na fase de espera. Quando um service worker em espera é encontrado, esse código informa ao usuário que uma versão atualizada do site está disponível e solicita que ele atualize.

<!-- This script tag uses JavaScript modules, so the proper `type` attribute value is required -->
<script type="module">
  // This code sample uses features introduced in Workbox v6.
  import {Workbox} from 'https://storage.googleapis.com/workbox-cdn/releases/6.4.1/workbox-window.prod.mjs';

  if ('serviceWorker' in navigator) {
    const wb = new Workbox('/sw.js');
    let registration;

    const showSkipWaitingPrompt = async (event) => {
      // Assuming the user accepted the update, set up a listener
      // that will reload the page as soon as the previously waiting
      // service worker has taken control.
      wb.addEventListener('controlling', () => {
        // At this point, reloading will ensure that the current
        // tab is loaded under the control of the new service worker.
        // Depending on your web app, you may want to auto-save or
        // persist transient state before triggering the reload.
        window.location.reload();
      });

      // When `event.wasWaitingBeforeRegister` is true, a previously
      // updated service worker is still waiting.
      // You may want to customize the UI prompt accordingly.

      // This code assumes your app has a promptForUpdate() method,
      // which returns true if the user wants to update.
      // Implementing this is app-specific; some examples are:
      // https://open-ui.org/components/alert.research or
      // https://open-ui.org/components/toast.research
      const updateAccepted = await promptForUpdate();

      if (updateAccepted) {
        wb.messageSkipWaiting();
      }
    };

    // Add an event listener to detect when the registered
    // service worker has installed but is waiting to activate.
    wb.addEventListener('waiting', (event) => {
      showSkipWaitingPrompt(event);
    });

    wb.register();
  }
</script>

Se eles aceitarem, o messageSkipWaiting() vai pedir para o service worker em espera invocar self.skipWaiting(), o que significa que ele vai começar a ser ativado. Depois de ativado, o novo service worker vai assumir o controle de todos os clientes atuais, acionando o evento controlling em workbox-window. Quando isso acontece, a página atual é recarregada com a versão mais recente de todos os recursos armazenados em cache e com qualquer nova lógica de roteamento encontrada no service worker atualizado.

O código a ser colocado no service worker

Depois de receber o código da seção anterior da sua página, você precisa adicionar algum código ao service worker para que ele saiba quando pular a fase de espera. Se você estiver usando o generateSW de workbox-build e tiver a opção skipWaiting definida como false (o padrão), está tudo pronto, já que o código abaixo será incluído automaticamente no arquivo gerado do service worker.

Se estiver criando seu próprio service worker, talvez em conjunto com uma das ferramentas de compilação do Workbox no modo injectManifest, você precisará adicionar o seguinte código:

addEventListener('message', (event) => {
  if (event.data && event.data.type === 'SKIP_WAITING') {
    self.skipWaiting();
  }
});

Ele detecta mensagens enviadas ao service worker de workbox-window com um valor type de SKIP_WAITING e, quando isso acontece, chama self.skipWaiting(). O método messageSkipWaiting() no workbox-window, mostrado no exemplo de código anterior, é responsável por enviar essa mensagem.

Você precisa mostrar uma solicitação?

Esse não é um padrão que todo aplicativo que implanta um service worker precisa seguir. Ele deve ser usado em cenários selecionados em que deixar de fornecer uma oportunidade de recarregar uma página em uma atualização de service worker pode causar comportamentos inesperados. Não há regras rígidas e rápidas para mostrar uma solicitação de atualização, mas veja algumas situações em que isso pode fazer sentido:

  • Você usa bastante o pré-armazenamento em cache. Com relação aos recursos estáticos, é possível que você tenha problemas mais tarde se usar uma estratégia que prioriza a rede ou somente a rede para solicitações de navegação, mas os recursos estáticos de carregamento lento. Isso pode causar situações em que os recursos com controle de versão podem mudar sem que um service worker os armazene em cache. Oferecer um botão de atualização aqui pode evitar alguns comportamentos inesperados.
  • Se estiver exibindo HTML pré-armazenado em cache. Nesse caso, você deve considerar altamente oferecer um botão de atualização nas atualizações do service worker, já que as atualizações desse HTML não serão reconhecidas até que o service worker atualizado assuma o controle.
  • Se você não depende principalmente do armazenamento em cache do ambiente de execução. Ao armazenar recursos em cache no tempo de execução, não é necessário informar ao usuário que ele precisa ser recarregado. À medida que os recursos com controle de versões mudam, eles são adicionados ao cache do ambiente de execução no futuro, supondo que as solicitações de navegação usem uma estratégia que prioriza a rede ou apenas a rede.
  • Ao usar uma estratégia de desatualização ao revalidar, considere usar o módulo workbox-broadcast-update para notificar os usuários sobre atualizações do service worker.

A necessidade de notificar o usuário sobre as atualizações de um service worker depende do seu aplicativo e dos requisitos exclusivos dele. Se você perceber que seus usuários estão apresentando comportamentos estranhos quando você envia um novo service worker, esse provavelmente é o melhor sinal de que você deve notificá-los.