DEV Community

Discussion on: PWA: Criar notificação de "uma nova versão está disponível"

Collapse
 
lgdelai profile image
GUILHERME DELAI ⚡

Olá. Parabéns pelo artigo.

No inicio você fala sobre atualizar o pwa silenciosamente.

Pode explicar como é feito este procedimento?

Collapse
 
oieduardorabelo profile image
Eduardo Rabelo

fala Guilherme, uma atualização do Service Worker é acionada automaticamente pelo navegador assim que o arquivo de sw for alterado,

Podemos reunir alguns outros motivos também:

  • Um evento de navegação para uma página dentro do escopo de um sw
  • Eventos funcionais como push ou sync ou uma verificação de atualização do arquivo de sw (pelo navegador) nas últimas 24 horas
  • Chamando .register(), no caso da URL do sw ter sido alterada, você deve evitar mudar a url do seu sw em caso de trabalho offline (ex: index.html registra sw-v1.js como sw, o sw faz o cache do index.html para trabalhar offline, você atualiza o index.html para usar sw-v2.js, o usuário nunca receberá o sw-v2.js)

Quando uma atualização acontece, o comportamento padrão é:

  • O novo sw é executado pelo navegador ao lado do sw atual, com seu próprio evento install, rodando em um novo contexto
  • Caso o novo sw não esteja ok, tenha falha de sintaxe, algum 404 ou jogue um erro, a instalação é rejeitada e o novo sw é removido, mas o atual ainda continua controlando a página
  • Caso o novo sw tenha sucesso em sua instalação, ele irá aguardar até que o sw atual não esteja controlando nenhum cliente, ou seja, todos as tabs, navegadores, sessões usando o sw atual tem que ser fechadas (nesse período, o cliente/navegador/tab terá um overlap com os DOIS sw na página)
  • podemos usar o self.skipWaiting() para remover essa espera e assumir o controle da página assim que o sw for instalado

vou me concentrar no arquivo de sw nos exemplos a seguir,

vamos supor que instalamos o seguinte sw:

self.addEventListener('install', event => {
  console.log('instalando sw-v1');

  // - criamos um cache 'static-v1'
  // - adicionamos 'banner.jpg' ao cache
  event.waitUntil(
    caches.open('static-v1').then(cache => cache.add('/banner.jpg'))
  );
});

self.addEventListener('activate', event => {
  console.log('o sw-v1  está pronto para interceptar eventos e requests');
});

self.addEventListener('fetch', event => {
  const url = new URL(event.request.url);

  // response com 'banner.jpg' do cache se o request for
  // da mesma origin e o caminho ser '/hero.jpg'
  if (url.origin == location.origin && url.pathname == '/hero.jpg') {
    event.respondWith(caches.match('/banner.jpg'));
  }
});

agora, vamos atualizar o sw para um nova versão, lembrando, que "atualizar um sw" é basicamente mudar qualquer byte dentro do arquivo registrado como sw,

self.addEventListener('install', event => {
  console.log('instalando sw-v2');

  // - criamos um cache 'static-v2'
  // - adicionamos 'novo-banner.jpg' ao cache
  // - adicionamos 'self.skipWaiting()' para instalar a nova versão imediatamente
  event.waitUntil(
    caches.open('static-v2')
    .then(cache => cache.add('/novo-banner.jpg'))
  )
  .then(() => self.skipWaiting())
});

self.addEventListener('activate', event => {
  console.log('o sw-v2  está pronto para interceptar eventos e requests');
});

self.addEventListener('fetch', event => {
  const url = new URL(event.request.url);

  // response com 'novo-banner.jpg' do cache se o request for
  // da mesma origin e o caminho ser '/hero.jpg'
  if (url.origin == location.origin && url.pathname == '/hero.jpg') {
    event.respondWith(caches.match('/novo-banner.jpg'));
  }
});

Você pode perceber o uso do self.skipWaiting() na fase de instalação, isso irá silenciosamente dar controle da página para o novo sw, resultando na "atualização silenciosa" mencionada aqui.

Olhando o artigo novamente, você pode ver que enviamos um evento para o sw do frontend em:

// ... um pedaço do exemplo completo do artigo
// o evento "stagechange" irá ser disparado pelo navegador, a gente não controla isso
newWorker.addEventListener("statechange", () => {
  // O estado do Service Worker mudou?
  switch (newWorker.state) {
    case "installed": {
      // Existe um novo Service Worker disponível, mostra a notificação
      if (navigator.serviceWorker.controller) {
        let notification = document.getElementById("notification");
        notification.className = "show";
        break;
      }
    }
  }
});

// na notifação, enviamos um evento para o sw
document.getElementById("reload").addEventListener("click", function() {
  newWorker.postMessage({ action: "skipWaiting" });
});

// no sw, se o usuário clicar na mensagem do banner, ativamos o novo sw imediatamente
self.addEventListener("message", function(event) {
  if (event.data.action === "skipWaiting") {
    self.skipWaiting();
  }
});

Desse modo, o cliente controla se ele deve atualizar no momento que a detecção de uma nova versão do sw PELO navegador acontece.

A atualização irá acontecer de qualquer modo, se houver um novo sw, o navegador irá atualizar o cliente com base nas regras descritas no começo desse comentário.

Se tiver mais dúvidas só mandar!