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
Adicione no seu package.json
o script de dev
:
"scripts": {
"dev": "tsx watch src/server.ts"
}
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'));
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 JavasScript 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
}),
);
});
O código acima é bem simples, basicamente adicionamos um
listener
chamadopush
, com isso, quando for enviado alguma notificação vamos ficar sabendo, e teremos acesso ao evento que foi disparado, com esse evento conseguimos obter opayload
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();
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çãohandleServiceWorker
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,
});
});
}
obs: usei o
axios
como lib para realizar a chamada HTTP, mas poderia ser ofetch
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
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"
}
Agora é só executar o script build-sw
:
$ npm run build-sw
Pronto, agora o service-worker.ts
vai ser convertido para JavaScript.