DEV Community

Cover image for Enviando Push Notification com Node.js para o Browser
Mayderson Mello
Mayderson Mello

Posted on • Edited on

3

Enviando Push Notification com Node.js para o Browser

Introdução

Neste tutorial vamos aprender a enviar Push Notification para o Browser usando Node.js, irei utilizar também o React com Vite, mas poderia ser qualquer outra coisa, pois vamos utilizar de API's nativas da WEB para isso.

Será abordado o uso da Notification API para solicitarmos a permissão para o usuário, PushManager API para receber notificações de servidores de terceiros, web-push para fazer o envio da notificação no lado servidor, e por fim o Service Worker, para lidar com a notificação em Background.


Criando Back-end

Antes de tudo, precisamos criar nosso servidor em Node.js para fazer o envio dessas notificações, vou utilizar o Express para isso, mas poderia ser com qualquer outro, como o Fastify etc.

Instalando pacotes necessários e configurando script

$ mkdir server
$ cd server
$ npm init -y
$ npm i express cors web-push
$ npm i typescript tsx @types/express @types/cors @types/node @types/web-push -D
$ npx tsc --init
Enter fullscreen mode Exit fullscreen mode

Adicione no seu package.json o script de dev:

"scripts": {
  "dev": "tsx watch src/server.ts"
}
Enter fullscreen mode Exit fullscreen mode

Criando servidor

Crie um arquivo dentro da pasta src chamado server.ts e adicione o código abaixo:

import express from 'express';
import cors from 'cors';
import WebPush from 'web-push';

const app = express();

app.use(express.json());
app.use(cors());

// WebPush

// console.log(WebPush.generateVAPIDKeys());
const publicKey = 'DASdasdjk1hdhajshdashdgadaskdhaj...';
const privateKey = 'dsaduiw18eudasd1d9sdd3wddasdasa...';

WebPush.setVapidDetails('http://localhost:3333', publicKey, privateKey);

// Routes

interface ISubscription {
  subscription: {
    endpoint: string;
    keys: {
      p256dh: string;
      auth: string;
    };
  };
}

app.get('/notification/push/public_key', (request, response) => {
  return response.json({ publicKey });
});

app.post('/notification/push/register', (request, response) => {
  console.log(request.body);

  return response.sendStatus(201);
});

app.post('/notification/push/send', async (request, response) => {
  const { subscription } = request.body as ISubscription;

  WebPush.sendNotification(
    subscription,
    JSON.stringify({
      icon: 'your-icon-link.png',
      title: "'Your title',"
      body: 'Content of your message',
      imageUrl: 'your-image-link.png'
    }),
  );

  return response.sendStatus(201);
});

app.listen(3333, () => console.log('SERVER IS RUNNING AT :3333'));
Enter fullscreen mode Exit fullscreen mode

Bom de Back-end é só isso, para você gerar sua publicKey e privateKey é necessário rodar o código console.log(WebPush.generateVAPIDKeys()); somente uma vez, com isso ele vai te retornar suas chaves, ai basta você substituir essas fictícias que coloquei pelas as suas.

Sobre as rotas do servidor:

  • /notification/push/public_key: Essa rota é responsável por enviar nossa publicKey para o Front-end.

  • /notification/push/register: Essa rota serviria para você salvar no Banco de Dados por exemplo a subscription que o Front-end iria enviar, e com essa informação você poderia enviar a notificação depois, mas para não estender muito não irei abordar essa parte de salvar em algum Banco de Dados.

  • /notification/push/send: Essa rota serve para fazer o envio da notificação, nela estou recebendo a subscription no body, pois é necessário para o envio da notificação, mas caso você tenha persistido essas subscriptions no Banco de Dados, você poderia substituir o recebimento dela nessa rota, e realizar a consulta dessas subscriptions existentes.


Preparando nosso Front-end

Criando o Service Worker

Vamos criar um arquivo chamado service-worker.ts dentro da pasta public pois esse arquivo precisa estar disponível publicamente.

Estou utilizando .ts pois TypeScript é vida ❤️, mas se você quiser pode usar .js e remover as tipagens que irei adicionar, de qualquer forma se você usar com TypeScript, vai precisar converter para JavaScript no final, mas fique tranquilo irei mostrar como posteriormente.

/// <reference no-default-lib="true"/>
/// <reference lib="esnext" />
/// <reference lib="webworker" />

interface INotificationPayload {
  icon: string;
  title: string;
  body: string;
  imageUrl?: string;
}

const sw = self as unknown as ServiceWorkerGlobalScope & typeof globalThis;

sw.addEventListener('push', (event) => {
  const notificationPayload = event.data?.json() as INotificationPayload;

  event.waitUntil(
    sw.registration.showNotification(notificationPayload.title, {
      icon: notificationPayload.icon,
      body: notificationPayload.body,
      image: notificationPayload.imageUrl
    }),
  );
});

Enter fullscreen mode Exit fullscreen mode

O código acima é bem simples, basicamente adicionamos um listener chamado push, com isso, quando for enviado alguma notificação vamos ficar sabendo, e teremos acesso ao evento que foi disparado, com esse evento conseguimos obter o payload que é os dados que foram enviados, e com esses dados conseguimos exibir de fato a notificação para o usuário.

Você pode explorar mais atributos, que podem ser passados dentro desse objeto de configuração da função showNotification, por meio da documentação oficial.


Solicitando permissão

Agora precisamos solicitar as permissões para o usuário, ele precisa de fato autorizar o recebimento dessas notificações para estar chegando para ele.

Você pode implementar o código abaixo no arquivo principal do seu projeto, para que quando o mesmo for iniciado chame a função abaixo.

function notifyMe() {
  if (!('Notification' in window)) {
    alert('This browser does not support desktop notification');
  } else if (Notification.permission === 'granted') {
    handleServiceWorker();
  } else if (Notification.permission !== 'denied') {
    Notification.requestPermission().then((permission) => {
      if (permission === 'granted') {
        handleServiceWorker();
      }
    });
  }
}

notifyMe();
Enter fullscreen mode Exit fullscreen mode

Basicamente na função acima, realizamos uma verificação para checar se temos acesso a Notification API, em seguida verificamos se já temos acesso garantido a notificação, se já tivermos só executamos a função handleServiceWorker que criaremos depois, por fim se ainda não tivermos concedido permissão para a notificação, solicitamos ao usuário para que garanta o acesso.


Criando função handleServiceWorker

Essa função vai realizar o registro do nosso Service Worker, e após ser realizado o registro com sucesso, chamamos a função getSubscription do pushManager, pois ela recupera uma assinatura push existente e retorna seus detalhes, caso não encontrar retorna null.

Quando não temos uma assinatura existente, chamamos nosso endpoint /notification/push/public_key para retornar nossa publicKey, e com o valor dessa chave realizamos o subscribe, assim da próxima vez que tentarmos acessar a aplicação já vamos ter nossa inscrição feita.

Fiz também para fim de exemplo a chamada para o endpoint de registro da subscription e de envio da notificação, mas cabe a você definir quando e onde realizar o envio dessas notificações da melhor maneira.

async function handleServiceWorker() {
  navigator.serviceWorker
    .register('service-worker.js')
    .then(async (serviceWorker) => {
      serviceWorker.update();

      let subscription = await serviceWorker.pushManager.getSubscription();

      if (!subscription) {
        const publicKeyResponse = await api.get<{ publicKey: string }>(
          '/notification/push/public_key',
        );

        await serviceWorker.pushManager.subscribe({
          userVisibleOnly: true,
          applicationServerKey: publicKeyResponse.data.publicKey,
        });

        subscription = await serviceWorker.pushManager.getSubscription();
      }

      await axios.post('http://localhost:3333/notification/push/register', {
        subscription,
      });

      await axios.post('http://localhost:3333/notification/push/send', {
        subscription,
      });
    });
}
Enter fullscreen mode Exit fullscreen mode

obs: usei o axios como lib para realizar a chamada HTTP, mas poderia ser o fetch normalmente.


Convertendo Service Worker para JavaScript

Essa etapa não é necessária se você criou o arquivo service-worker.js, mas caso tenha criado ele em TypeScript execute os passos abaixo:

Instale a dependência esbuild como dependência de desenvolvimento:

$ npm i esbuild -D
Enter fullscreen mode Exit fullscreen mode

Adicione o script abaixo no seu arquivo package.json:

"scripts": {
  "build-sw": "esbuild public/service-worker.ts --outdir=public --bundle --sourcemap --minify --format=esm --legal-comments=external"
}
Enter fullscreen mode Exit fullscreen mode

Agora é só executar o script build-sw:

$ npm run build-sw
Enter fullscreen mode Exit fullscreen mode

Pronto, agora o service-worker.ts vai ser convertido para JavaScript.

Speedy emails, satisfied customers

Postmark Image

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay