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 atual esteja controlando sejam fechadas ou passem por uma navegação antes que o service worker atualizado seja ativado e assuma o controle.

Em muitos casos, pode ser aceitável permitir que isso aconteça no futuro, mas, em alguns casos, você pode querer avisar o usuário que há uma atualização pendente do service worker e, em seguida, automatizar o processo de mudança para o novo service worker. Para fazer isso, será preciso adicionar um código à sua página e seu 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 na CDN do workbox-window. Ele registra um service worker usando workbox-window e reagirá se o service worker ficar preso 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 a atualização.

<!-- 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 ele aceitar, messageSkipWaiting() vai pedir que o service worker em espera invoque self.skipWaiting(), o que significa que ele vai começar a ser ativado. Depois de ativado, o novo service worker assumirá o controle de todos os clientes existentes, 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 pré-armazenados em cache e qualquer nova lógica de roteamento encontrada no service worker atualizado.

O código a ser colocado no seu service worker

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

Se você estiver criando seu próprio service worker, talvez com uma das ferramentas de build do Workbox no modo injectManifest, será necessário adicionar o seguinte código:

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

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

Você precisa mostrar um comando?

Esse não é um padrão que todo aplicativo que implanta um service worker precisa seguir. Ele se destina a cenários específicos em que a falta de 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 a exibição de um prompt de atualização, mas aqui estão algumas situações em que isso pode fazer sentido:

  • Você usa extensivamente o pré-armazenamento em cache. No que diz respeito aos recursos estáticos, você pode ter problemas mais tarde se usar uma estratégia que prioriza a rede ou somente a rede para solicitações de navegação, mas faça o carregamento lento de recursos estáticos. Isso pode causar situações em que os recursos com controle de versão podem mudar e não podem ser armazenados em cache por um service worker. Oferecer um botão de atualização aqui pode evitar alguns comportamentos inesperados.
  • Caso você veicule HTML pré-armazenado em cache. Nesse caso, considere altamente oferecer um botão de recarga 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 no ambiente de execução. Ao armazenar recursos em cache no ambiente de execução, você não precisa informar ao usuário que precisa recarregar. À medida que os recursos com controle de versões mudarem, eles serã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 somente dela.
  • Ao usar uma estratégia desatualizado enquanto revalida, 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 atualizações de um service worker depende do seu aplicativo e dos requisitos exclusivos dele. Se você perceber que seus usuários estão enfrentando comportamentos estranhos ao enviar um novo service worker, esse provavelmente é o melhor sinal de que você deve notificá-los.