Publicação

Resolvendo o probleminha de aplicações fora do ar e melhorando a experiência de navegação para o usuário

foto de
Andre R

 Você já ouviu falar de UX? Caso não saiba o UX nada mais é que a experiência do usuário nas aplicações sendo elas desktop, mobile ou web. Hoje irei falar sobre o UX em aplicações web e uma das questões mais chatas que pode acontecer e que cai muito no fator experiência do usuário. Sem mais delongas vamos ao post.

Começando já de cara com o foco principal do texto, você alguma vez estava navegando em um site e de repente sua internet caiu e impediu a continuação no site? Você certamente deve ter ficado muito nervoso caso não?! Ainda mais quando você tá vendo um vídeo importante ou fazendo tarefas que só podem ser realizadas em um dia como: cadastros de formulários entre outras coisas. Calma não culpe o desenvolvedor por isso (por hora srsr), pois, o que irei mostrar agora é uma novidade para muitos. Como já dito antes é ruim para o usuário ter que esperar muitas vezes que a internet volte para fazer algumas tarefas.  Você já deve está curioso se perguntando "Afinal como resolver este problema André?", pois bem então lhes apresento uma solução o Service Worker.

O que é esse tal de Service Worker?

 

Service Worker é nada mais que um arquivo JavaScript que fica entre a aplicação rodada para o usuário e os pedidos de requisições realizados em redes. Este arquivo funciona de forma separada e não é visível ao usuário, não acessa o DOM, mas faz o controle das páginas da aplicação. O Service Worker  intercepta os pedidos feitos pelo navegador. A partir desse momento é permitido fazer o que quiser! Desde o envio normal para redes ou  até mesmo pular a rede. Para rodar o Service Worker basta você fazer da seguinte maneira:

//ES5 
navigator.serviceWorker.register('/sw.js').then(function(reg){
    console.log('Ok está tudo certo!');
}).catch(function(err){
    console.log('Tem algo de estranho!');
});
//ES6
navigator.serviceWorker.register('/sw.js').then(reg => {
    console.log('Ok está tudo certo!');
}).catch(err => {
console.log('Tem algo de estranho!');
});

Calma fique tranquilo o navegador não irá registrar novamente! Ele apenas irá criar uma Promise do registro atual. O Service Worker controla páginas onde as URLS começam com o escopo. Veja o exemplo abaixo.

navigator.serviceWorker.register('/sw.js', {
  scope: '/aplicacao/'
});

 O Service Worker irá controlar desde a url citada até mesmo outras que estejam dentro da mesma como as seguintes: /aplicacao/ , /aplicacao/url-um/ e /aplicacao/url-um/url-dois/ . Ele só não irá controlar esses tipos de urls: / , /aplicaao-minha/ e /alicacao . Usamos o escopo para definir o caminho onde está localizado o script da aplicação.  O segredo está em colocar a url de forma correta ou seja onde se encontra o script. 

Caso tenha dúvida sobre a questão de compatibilidade este link: https://jakearchibald.github.io/isserviceworkerready/ , mostra todos os navegadores que são compatíveis com o Service Worker.

Rodando um site offline

 Antes de mostrar o script de como rodar sua aplicação offline é importante dizer que o Service Worker tem um ciclo de vida baseado em:  Download , Install e Activate .

Você pode usar o exemplo abaixo para instalar o Service Worker.

self.addEventListener('install', function(event) {
  event.waitUntil(
    caches.open('static-v1').then(function(cache) {
      return cache.addAll([
        '/meu-site/',
        '/meu-site/fallback.html',
        '//meusite.com.br/estilo.css',
        '//meusite.com.br/script.js'
      ]);
    })
  );
});

O Service Worker irá introduzir uma nova API de armazenamento, um local para armazenar respostas codificadas por solicitações, semelhante ao cache do navegador. Você pode ter quantos caches quiser.

O waitUntil é responsável por levar a Promise, isto definira o comprimento de processo da instalação. As páginas não serão controladas pelo service worker até que tudo seja cumprido. Caso a Promise for rejeitada, isso irá indicar uma falha na instalação e este service worker  não será responsável por outros eventos. 

O cache.addAll([requestsOrURLs...] é nada mais que operação atômica que retorna uma Promise. Se um solicitação falhar, o cache não será afetado e a Promise será recusada. (Erro de instalação)

Código para o nosso cache:

self.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.match(event.request)
  );
});

No exemplo acima estamos pegando todas as pesquisas e respondendo com o que é corresponde ao pedido recebidos nos caches. Você pode especificar um cache específico.

O caches.match(requestOrURL) retorna uma determinada Promise para um Response, apenas o que respondWith necessita. A correspondência é feita de maneira semelhante ao protocolo HTTP, corresponde ao método url+ e obedece aos cabeçalhos Vary. Ao contrário do cache do navegador, o cache do service worker ignora o frescurinhas, as coisas permanecem no cache até que você as remova ou sobrescreva.

Recuperando se das falhas 

Infelizmente, a Promise retornada pelo caches.match terá que ser resolvida com null caso nenhuma correspondência for encontrada. Isto é igual a uma falha de rede, mas calma temos uma solução! Basta fazer 

da seguinte forma:

self.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.match(event.request).then(function(response) {
      return response || event.default();
    })
  );
});

 Agora se o usuário não estiver no cache isso pode gerar a outra falha, mas não significa que não tenha solução.

Basta fazer da seguinte forma:

self.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.match(event.request).then(function(response) {
      return response || event.default();
    }).catch(function() {
      return caches.match('/my-blog/fallback.html');
    })
  );
});

Agora tudo está a mil maravilhas! Nosso fallback se encontra dentro do cache. Caso necessite a atualizar um conjunto de caches você pode fazer isso com a sincronização em segundo plano. Claro você pode alterar e controlar a lógica quando quiser. Abaixo o código exemplo.


self.addEventListener('install', function(event) {
  event.waitUntil(
    caches.open('static-v2').then(function(cache) {
      return cache.addAll([
        '/meu-site/',
        '/meu-site/fallback.html',
        '//meusite.com.br/estilo-v2.css',
        '//meusite.com.br/script-v2.js'
      ]);
    })
  );
});

 Enquanto este processo acontece, a versão anterior ainda é responsável pelas buscas. A nova versão está instalando em segundo plano. Note que estamos chamando o novo cache de 'static-v2', então o cache anterior não é afetado.  

Depois que a instalação estiver concluída, a nova versão permanecerá em espera até que todas as páginas que usam a versão atual estejam descarregadas. Se você tiver apenas uma guia aberta, uma atualização será suficiente. Se você não quer esperar tanto tempo, então poderá chamar event.replace() no evento de instalação, mas fique ciente que agora você está no controle das páginas carregadas usando uma versão anterior.

Quando não temos nenhuma página usando a versão atual, o novo trabalhador é ativado e passa se tornar responsável pelas pesquisas. Você também irá receber um evento de ativação:

self.addEventListener('activate', function(event) {
  const cacheWhitelist = ['static-v2'];
  event.waitUntil(
    caches.keys(function(cacheNames) {
      return Promise.all(
        cacheNames.map(function(cacheName) {
          if (cacheWhitelist.indexOf(cacheName) == -1) {
            return caches.delete(cacheName);
          }
        })
      )
    })
  );
});

Desempenho 

O Service worker foi desenvolvido para ser assíncrono. APIs como XHR síncrono e localStorage não são bem-vindas neste local. Um destaque bem bacana é que você pode manter escopos globais.

Questões de Latência 

Solicitações de JS podem comprometer o desempenho. Já estão sendo trabalhadas soluções tais como: addRoute(urlRe, sources...), onde os sources é uma lista ordenada para o local onde o navegador procurar por respostas.  

Conclusão 

Seguindo os exemplos acima e lendo claramente a documentação você irá conseguir acabar com o problema de seus usuários finais ficarem sem acessar sua aplicação. Agora já imaginou se a gente combina-se isso tudo que vimos hoje com um CMS que pode trabalhar offline? Isto veremos em breve ;) . Forte abraço e até o próximo post. 

(Os códigos acima foram retirados da documentação do seguinte site: https://jakearchibald.com/2014/service-worker-first-draft/)

Comentários