Transforme seu site em PWA com Notificações Push: Guia Completo com Firebase

Foto do autor

By luis

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.