Transformando Seu Site em um PWA com Notificações Push Usando Firebase
Neste artigo, vamos explorar o processo de conversão de uma aplicação web ou site em um PWA (Progressive Web App), implementando notificações push através do Firebase Cloud Messaging.
Atualmente, a tendência é a migração de diversas aplicações web para PWAs devido às vantagens que oferecem, como suporte offline, notificações push e sincronização em segundo plano. Essas funcionalidades enriquecem a experiência do usuário, aproximando a aplicação web do comportamento de um aplicativo nativo.
Grandes corporações como Twitter e Amazon já adotaram essa abordagem, convertendo seus aplicativos web em PWAs para aumentar o engajamento de seus usuários.
O que Define um PWA?
Um PWA pode ser entendido como a combinação de um aplicativo web tradicional com funcionalidades adicionais típicas de aplicativos nativos.
Basicamente, um PWA é construído com as mesmas tecnologias da web (HTML, CSS e JavaScript) e funciona de forma semelhante em todos os navegadores. A diferença está na capacidade de oferecer recursos nativos quando acessado através de um navegador moderno. Isso resulta em um aplicativo web mais poderoso e escalável, com a possibilidade de armazenar ativos em cache no frontend, diminuindo a carga sobre o servidor backend.
Diferenças Cruciais entre um PWA e um Aplicativo Web
- Instalável: Um PWA pode ser instalado como um aplicativo nativo no dispositivo do usuário.
- Progressivo: Funciona como um aplicativo web, mas com a adição de funcionalidades nativas.
- Experiência Nativa: A experiência de navegação e uso se assemelha à de um aplicativo nativo.
- Acesso Simplificado: Após instalado, o PWA pode ser acessado com um único toque, eliminando a necessidade de digitar o endereço web.
- Cache Aprimorado: PWAs oferecem um sistema de cache mais robusto do que o cache HTTP tradicional, permitindo o armazenamento em cache diretamente no lado do cliente.
- Publicação em Lojas: PWAs podem ser publicados tanto na Google Play Store quanto na App Store da Apple.
A conversão de seu aplicativo em PWA é um passo em direção a um produto mais robusto e funcional.
Por Que Empresas Devem Considerar PWAs?
Muitas vezes, clientes solicitam o desenvolvimento de um aplicativo web e, posteriormente, aplicativos para Android e iOS. Este cenário implica em replicar as mesmas funcionalidades em diferentes plataformas, resultando em custos adicionais de desenvolvimento e maior tempo de lançamento no mercado.
No entanto, alguns clientes possuem orçamentos limitados ou priorizam a rapidez no lançamento. Nesses casos, as funcionalidades de um PWA podem ser suficientes para atender a maioria dos requisitos, servindo como uma alternativa viável. Sugere-se então a conversão para PWA, com a opção de transformá-lo em um aplicativo Android através do TWA (Trusted Web Activity), caso a publicação na Play Store seja desejada.
Se as necessidades do cliente demandarem recursos de aplicativos nativos não disponíveis em PWAs, o desenvolvimento de aplicativos para Android e iOS é recomendado. Mesmo assim, um PWA pode ser lançado na Play Store como uma solução temporária, enquanto o desenvolvimento dos aplicativos nativos é finalizado.
Exemplo: Titan Eyeplus
Essa empresa iniciou com o desenvolvimento de um PWA, publicando-o na Play Store via TWA. Após o desenvolvimento do aplicativo Android, lançaram o aplicativo nativo, otimizando tempo e custos.
Funcionalidades dos PWAs
PWAs oferecem funcionalidades que aproximam as aplicações web da experiência de aplicativos nativos.
As principais funcionalidades incluem:
- Instalável: Aplicações web que podem ser instaladas como aplicativos nativos.
- Cache: Possibilidade de cache de dados, garantindo suporte offline.
- Notificações Push: Envio de notificações do servidor para engajar o usuário com o site.
- Geofencing: Notificações com base na mudança de localização do dispositivo.
- Solicitação de Pagamento: Pagamentos facilitados dentro do aplicativo com experiência de usuário similar a aplicativos nativos.
E outras funcionalidades que serão implementadas no futuro.
Outras funcionalidades:
- Atalhos: URLs de acesso rápido adicionados no arquivo de manifesto.
- Web Share API: Permite que o aplicativo compartilhe dados com outros aplicativos.
- Badge API: Mostra o número de notificações em um PWA instalado.
- API de Sincronização Periódica em Segundo Plano: Salva dados do usuário até a conexão com a rede.
- Seletor de Contatos: Permite selecionar contatos do celular.
- File Picker: Permite acessar arquivos no sistema local.
Vantagens de PWAs sobre Aplicativos Nativos
Embora aplicativos nativos geralmente tenham melhor desempenho e mais funcionalidades, PWAs possuem vantagens importantes:
- Compatibilidade Multiplataforma: PWAs funcionam em Android, iOS e Desktop.
- Redução de Custos: Diminui os custos de desenvolvimento.
- Implantação Simplificada: Facilidade na implementação de novas funcionalidades.
- Facilidade de Descoberta: PWAs são otimizados para SEO, o que facilita sua descoberta.
- Segurança: Funcionamento exclusivo através de HTTPS.
Desvantagens de PWAs sobre Aplicativos Nativos
- Funcionalidades Limitadas: Menos funcionalidades disponíveis em comparação com aplicativos nativos.
- Suporte Variável: Nem todos os dispositivos suportam todas as funcionalidades de PWAs.
- Menor Visibilidade: PWAs não são facilmente encontrados em lojas de aplicativos.
No entanto, é possível publicar um PWA como aplicativo Android na Play Store através do Android Trusted Web Activity (TWA), o que auxilia na melhoria da visibilidade.
O Que É Necessário Para Converter um Aplicativo Web em PWA?
Para transformar um aplicativo web ou site em PWA, você precisará:
- Service Worker: Essencial para caching, notificações push e como um proxy para requisições.
- Arquivo de Manifesto: Contém informações sobre o aplicativo, facilitando a instalação na tela inicial.
- Logotipo do Aplicativo: Uma imagem de alta qualidade (512x512px) para o ícone do aplicativo.
- Design Responsivo: O aplicativo deve ser adaptável a diferentes tamanhos de tela.
O Que É um Service Worker?
Um service worker (script do lado do cliente) atua como um intermediário entre o aplicativo web e o exterior, habilitando notificações push e cache de dados.
Ele opera independentemente do JavaScript principal, sem acesso à DOM API, tendo acesso apenas a IndexedDB API, Fetch API e CacheStorage API. A comunicação com a thread principal é feita por meio de mensagens.
Serviços Fornecidos pelo Service Worker:
- Interceptação de requisições HTTP do domínio de origem.
- Recebimento de notificações push do servidor.
- Disponibilização offline do aplicativo.
O service worker monitora e manipula requisições, mas opera de forma independente, daí a necessidade de HTTPS para evitar ataques “man-in-the-middle”.
O Que É o Arquivo de Manifesto?
O arquivo de manifesto (manifest.json) contém detalhes sobre o PWA para informar o navegador.
- name: Nome do aplicativo.
- short_name: Nome abreviado do aplicativo.
- description: Descrição do aplicativo.
- start_url: Página inicial do aplicativo.
- icons: Imagens para a tela inicial, etc.
- background_color: Cor de fundo da tela inicial.
- display: Personalização da interface do navegador.
- theme_color: Cor do tema do aplicativo.
- scope: Escopo da URL para considerar o PWA.
- shortcuts: Links rápidos para o aplicativo.
Convertendo um Aplicativo Web para PWA
Para fins de demonstração, foi criada uma estrutura de pastas do site etechpt.com com arquivos estáticos:
- index.html – página inicial
- artigos/
- index.html – página de artigos
- autores/
- index.html – página de autores
- Ferramentas/
- index.html – página de ferramentas
- ofertas/
- index.html – página de ofertas
Se você já possui um site ou aplicativo web, siga os passos abaixo para convertê-lo em PWA.
Criando as Imagens Necessárias para o PWA
Primeiro, prepare o logotipo do seu aplicativo em formato 1:1, em cinco tamanhos diferentes. É possível utilizar ferramentas como https://tools.crawlink.com/tools/pwa-icon-generator/ para facilitar esse processo.
Criando o Arquivo de Manifesto
Em seguida, crie um arquivo manifest.json com os detalhes do aplicativo. Para o exemplo do site etechpt.com:
{ "name": "etechpt.com", "short_name": "etechpt.com", "description": "etechpt.com produz artigos de alta qualidade sobre tecnologia e finanças, e oferece ferramentas e APIs para o crescimento de negócios e pessoas.", "start_url": "/", "icons": [{ "src": "assets/icon/icon-128x128.png", "sizes": "128x128", "type": "image/png" }, { "src": "assets/icon/icon-152x152.png", "sizes": "152x152", "type": "image/png" }, { "src": "assets/icon/icon-192x192.png", "sizes": "192x192", "type": "image/png" }, { "src": "assets/icon/icon-384x384.png", "sizes": "384x384", "type": "image/png" }, { "src": "assets/icon/icon-512x512.png", "sizes": "512x512", "type": "image/png" }], "background_color": "#EDF2F4", "display": "standalone", "theme_color": "#B20422", "scope": "/", "shortcuts": [{ "name": "Articles", "short_name": "Articles", "description": "1595 artigos sobre Segurança, Sysadmin, Marketing Digital, Computação em Nuvem, Desenvolvimento e outros.", "url": "https://geekflare.com/articles", "icons": [{ "src": "/assets/icon/icon-152x152.png", "sizes": "152x152" }] }, { "name": "Authors", "short_name": "Authors", "description": "etechpt.com - Autores", "url": "/authors", "icons": [{ "src": "/assets/icon/icon-152x152.png", "sizes": "152x152" }] }, { "name": "Tools", "short_name": "Tools", "description": "etechpt.com - Ferramentas", "url": "https://etechpt.com.com/tools", "icons": [{ "src": "/assets/icon/icon-152x152.png", "sizes": "152x152" }] }, { "name": "Deals", "short_name": "Deals", "description": "etechpt.com - Ofertas", "url": "/deals", "icons": [{ "src": "/assets/icon/icon-152x152.png", "sizes": "152x152" }] } ] }
Registrando o Service Worker
Crie dois arquivos: register-service-worker.js e service-worker.js, na pasta raiz.
register-service-worker.js será executado na thread principal, com acesso à API DOM, enquanto service-worker.js será executado de forma independente, com um ciclo de vida curto, sendo ativado por eventos.
No arquivo register-service-worker.js, adicione o seguinte código:
if ('serviceWorker' in navigator) { window.addEventListener('load', function() { navigator.serviceWorker.register('/service-worker.js'); }); }
Em service-worker.js, adicione:
self.addEventListener('install', (event) => { // evento quando o service worker instala console.log( 'install', event); self.skipWaiting(); }); self.addEventListener('activate', (event) => { // evento quando o service worker é ativado console.log('activate', event); return self.clients.claim(); }); self.addEventListener('fetch', function(event) { // Interceptador de requisição HTTP event.respondWith(fetch(event.request)); // envia toda requisição http sem logica de cache /*event.respondWith( caches.match(event.request).then(function(response) { return response || fetch(event. request); }) );*/ // cacheia nova requisição. Se já estiver no cache, serve com o cache. });
Por enquanto, o foco é a conversão para PWA, e não a implementação de cache offline.
Adicione o arquivo de manifesto e o script na tag <head> da página HTML.
<link rel="manifest" href="https://etechpt.com.com/manifest.json"> <script src="/register-service-worker.js"></script>
Atualize a página e você poderá instalar o aplicativo, como exemplificado no Chrome móvel.
O aplicativo será adicionado à tela inicial.
Para usuários de WordPress, plugins de conversão para PWA podem ser utilizados. Em VueJS ou ReactJS, o mesmo método pode ser seguido ou então módulos npm de PWA para acelerar o desenvolvimento, que já incluem cache offline, entre outros.
Habilitando Notificações Push
As notificações push da web são essenciais para o engajamento do usuário com o aplicativo. Elas são habilitadas através de:
- Notification API: Para configurar como as notificações serão exibidas.
- Push API: Para receber mensagens do servidor.
Primeiramente, é necessário verificar a Notification API e obter permissão do usuário. Adicione o código abaixo em register-service-worker.js:
if ('Notification' in window && Notification.permission != 'granted') { console.log('Ask user permission') Notification.requestPermission(status => { console.log('Status:'+status) displayNotification('Notification Enabled'); }); } const displayNotification = notificationTitle => { console.log('display notification') if (Notification.permission == 'granted') { navigator.serviceWorker.getRegistration().then(reg => { console.log(reg) const options = { body: 'Obrigado por permitir notificações push!', icon: '/assets/icons/icon-512x512.png', vibrate: [100, 50, 100], data: { dateOfArrival: Date.now(), primaryKey: 0 } }; reg.showNotification(notificationTitle, options); }); } };
Se tudo estiver correto, uma notificação será exibida.
A propriedade ‘Notification’ na window indica o suporte à API de notificação, e Notification.permission revela a permissão do usuário. O valor será ‘granted’ se a permissão foi concedida e ‘denied’ caso contrário.
Ativando o Firebase Cloud Messaging e Criando uma Assinatura
Para enviar notificações do servidor, é necessário um endpoint único para cada usuário. Para isso, utilizaremos o Firebase Cloud Messaging.
Primeiro, crie uma conta no Firebase em https://firebase.google.com/.
- Crie um novo projeto com um nome. No exemplo, ‘etechpt.com’.
- O Google Analytics é ativado por padrão. É possível desativá-lo e habilitá-lo posteriormente no console do Firebase.
- Após a criação, o projeto será exibido conforme abaixo.
Acesse as configurações do projeto e clique em mensagens na nuvem para gerar as chaves.
Você terá acesso a três chaves:
- Chave do servidor do projeto.
- Chave privada de certificados push da web.
- Chave pública de certificados push da web.
Cole o seguinte código em register-service-worker.js:
const updateSubscriptionOnYourServer = subscription => { console.log('Insira aqui seu código ajax para salvar a assinatura do usuário em seu banco de dados', subscription); // escreva aqui sua própria requisição ajax para salvar a assinatura no seu servidor para uso futuro. }; const subscribeUser = async () => { const swRegistration = await navigator.serviceWorker.getRegistration(); const applicationServerPublicKey = 'BOcTIipY07N4Y63Y-9r7NMoJHofmCzn3Pu9g-LMsgIMGH4HVr42_LW9ia0lMr68TsTLKS3UcdkE3IcC52hJDYsY'; // cole aqui a chave publica do seu certificado webpush const applicationServerKey = urlB64ToUint8Array(applicationServerPublicKey); swRegistration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey }) .then((subscription) => { console.log('Usuário inscrito:', subscription); updateSubscriptionOnServer(subscription); }) .catch((err) => { if (Notification.permission === 'denied') { console.warn('A permissão para notificações foi negada') } else { console.error('Falha na inscrição do usuário: ', err) } }); }; const urlB64ToUint8Array = (base64String) => { const padding = '='.repeat((4 - base64String.length % 4) % 4) const base64 = (base64String + padding) .replace(/-/g, '+') .replace(/_/g, '/') const rawData = window.atob(base64); const outputArray = new Uint8Array(rawData.length); for (let i = 0; i < rawData.length; ++i) { outputArray[i] = rawData.charCodeAt(i); } return outputArray; }; const checkSubscription = async () => { const swRegistration = await navigator.serviceWorker.getRegistration(); swRegistration.pushManager.getSubscription() .then(subscription => { if (!!subscription) { console.log('Usuário JÁ está inscrito.'); updateSubscriptionOnYourServer(subscription); } else { console.log('Usuário NÃO está inscrito. Inscrevendo usuário.'); subscribeUser(); } }); }; checkSubscription();
Em service-worker.js, cole o código abaixo:
self.addEventListener('push', (event) => { const json = JSON.parse(event.data.text()) console.log('Push Data', event.data.text()) self.registration.showNotification(json.header, json.options) });
Agora o frontend está configurado. Com a assinatura, você pode enviar notificações push quando desejar, até que o usuário revogue a permissão.
Enviando Notificações do Backend com Node.js
Para facilitar, utilize o módulo npm web-push.
Exemplo de código para envio de notificações a partir de um servidor Node.js:
const webPush = require('web-push'); // pushSubscription é a assinatura que você enviou do frontend para salvar no banco de dados const pushSubscription = {"endpoint":"https://updates.push.services.mozilla.com/wpush/v2/gAAAAABh2…E0mTFsHtUqaye8UCoLBq8sHCgo2IC7UaafhjGmVCG_SCdhZ9Z88uGj-uwMcg","keys":{"auth":"qX6AMD5JWbu41cFWE3Lk8w","p256dh":"BLxHw0IMtBMzOHnXgPxxMgSYXxwzJPxpgR8KmAbMMe1-eOudcIcUTVw0QvrC5gWOhZs-yzDa4yKooqSnM3rnx7Y"}}; //chave publica do seu certificado web const vapidPublicKey = 'BOcTIipY07N4Y63Y-9r7NMoJHofmCzn3Pu9g-LMsgIMGH4HVr42_LW9ia0lMr68TsTLKS3UcdkE3IcC52hJDYsY'; //chave privada do seu certificado web const vapidPrivateKey = 'chave privada do certificado web'; var payload = JSON.stringify({ "options": { "body": "Teste de notificação push PWA do backend", "badge": "/assets/icon/icon-152x152.png", "icon": "/assets/icon/icon-152x152.png", "vibrate": [100, 50, 100], "data": { "id": "458", }, "actions": [{ "action": "view", "title": "Visualizar" }, { "action": "close", "title": "Fechar" }] }, "header": "Notificação de etechpt.com-PWA Demo" }); var options = { vapidDetails: { subject: 'mailto:[email protected]', publicKey: vapidPublicKey, privateKey: vapidPrivateKey }, TTL: 60 }; webPush.sendNotification( pushSubscription, payload, options ).then(data => { return res.json({status : true, message : 'Notificação enviada'}); }).catch(err => { return res.json({status : false, message : err }); });
O código acima enviará uma notificação push para a assinatura, ativando o evento push no service worker.
Enviando Notificações do Backend com PHP
Para o backend PHP, utilize o pacote composer web-push-php. Confira um exemplo de código para envio de notificações:
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); require __DIR__.'/../vendor/autoload.php'; use MinishlinkWebPushWebPush; use MinishlinkWebPushSubscription; // assinatura armazenada no banco de dados $subsrciptionJson = '{"endpoint":"https://updates.push.services.mozilla.com/wpush/v2/gAAAAABh2…E0mTFsHtUqaye8UCoLBq8sHCgo2IC7UaafhjGmVCG_SCdhZ9Z88uGj-uwMcg","keys":{"auth":"qX6AMD5JWbu41cFWE3Lk8w","p256dh":"BLxHw0IMtBMzOHnXgPxxMgSYXxwzJPxpgR8KmAbMMe1-eOudcIcUTVw0QvrC5gWOhZs-yzDa4yKooqSnM3rnx7Y"}}'; $payloadData = array ( 'options' => array ( 'body' => 'Teste de notificação push PWA do backend', 'badge' => '/assets/icon/icon-152x152.png', 'icon' => '/assets/icon/icon-152x152.png', 'vibrate' => array ( 0 => 100, 1 => 50, 2 => 100, ), 'data' => array ( 'id' => '458', ), 'actions' => array ( 0 => array ( 'action' => 'view', 'title' => 'Visualizar', ), 1 => array ( 'action' => 'close', 'title' => 'Fechar', ), ), ), 'header' => 'Notificação de etechpt.com-PWA Demo', ); // auth $auth = [ 'GCM' => 'sua chave privada do projeto', // deprecated e opcional, aqui apenas por compatibilidade 'VAPID' => [ 'subject' => 'mailto:[email protected]', // pode ser um mailto: ou seu site 'publicKey' => 'BOcTIipY07N4Y63Y-9r7NMoJHofmCzn3Pu9g-LMsgIMGH4HVr42_LW9ia0lMr68TsTLKS3UcdkE3IcC52hJDYsY', // (recomendado) chave publica P-256 não comprimida codificada em Base64-URL 'privateKey' => 'sua chave privada do certificado web', // (recomendado) multiplicador secreto da chave privada codificado em Base64-URL ], ]; $webPush = new WebPush($auth); $subsrciptionData = json_decode($subsrciptionJson,true); // webpush 6.0 $webPush->sendOneNotification( Subscription::create($subsrciptionData), json_encode($payloadData) // opcional (padrão null) );
Conclusão
Este guia detalha como transformar aplicativos web em PWAs, com a adição de notificações push. O código fonte desse artigo está disponível aqui, e a demonstração pode ser acessada aqui. A funcionalidade de notificação push foi testada através do envio de mensagens do backend com o auxílio dos exemplos de código apresentados.