Notifiche Push nel browser (Push API)
Tutti i moderni browser (Chrome, Firefox, Edge Chromium, ...) supportano le notifiche Push gestite in JavaScript tramite le Push API ed i Service Worker API.
Le Push API abilitano le pplicazioni a ricevere notifiche inviate da un server sia che la web app sia o meno è in esecuzione nel browser.
Quello che segue è una demo sull'impostazione di un progetto JavaScript e NodeJs che usa le Push API.
a questo si aggiunge la funzione urlBase64ToUint8Array
sul browser comparirà un popup con la richiesta di confermare la sottoscrizione
In pratica gestisce l'evento push che prende il messaggio (event.data.json()), costruisce l'oggetto che rappresenta la notifica (options) e invoca il metodo self.registration.showNotification che visualizza la notifica sul client:
che genera un output simile al seguente
questi valori vanno inseriti nel file settings.json
è possibile inviare le notifiche push a tutti i client registrati nel database.
Legge tutte le sottoscrizioni presenti nel database ed invia i messaggi tramite il metodo sendNotification(subscription, payload):
Vedi il codice completo su GitHub sgart-it / sgart-push-notification.
Le Push API abilitano le pplicazioni a ricevere notifiche inviate da un server sia che la web app sia o meno è in esecuzione nel browser.
Non tutte le combinazioni di sistema operativo e browser supportanole notifiche quando il browserè chiuso.
Attualmente sono supportate su Android ma non su Apple IOS.
Attualmente sono supportate su Android ma non su Apple IOS.
Quello che segue è una demo sull'impostazione di un progetto JavaScript e NodeJs che usa le Push API.
Progetto
Il progetto è composto da 2 parti principali:- la parte client con i file che verranno eseguiti sul browser
- la parte server, scritta in NodeJS, con le API di salvataggio
Lato Client
Lato client è necessario avere almeno 3 file nel progetto:- la pagina HTML /index.html
- un file JavaScript che registra il service worker, /js/register-service-worker.js
- un file JavaScript che il codice del service worker, /service-worker-push.js
Pagina HTML
La pagina HTML non ha particolari requisiti, dovrà solo richiamare il file /js/register-service-worker.js, mentre il contenuto HTML/JavaScript dipenderà dalla specifica applicazioneHTML: index.html
<!DOCTYPE html>
<html class="no-js" dir="ltr" loc="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=0,viewport-fit=cover"/>
<title>Notifiche Push - Sgart.it</title>
<meta name="description" content="Test notifiche push." />
<link rel="manifest" href="/manifest.json" crossorigin="use-credentials" />
<link id="favicon" rel="shortcut icon" href="favicon.png" type="image/png" />
<link rel="apple-touch-icon" sizes="192x192" href="/icon-ios-192.png"/>
<meta name="mobile-web-app-capable" content="yes"/>
<meta name="apple-mobile-web-app-title" content="Twitter"/>
<meta name="apple-mobile-web-app-status-bar-style" content="white"/>
<meta name="theme-color" content="#ffffff"/>
<link href="/css/style.css" rel="stylesheet" />
</head>
<body class="web">
<h1>Demo push notification</h1>
<p>
<button type="button" id="btn-push-unsubscribe">Unsubscribe</button>
</p>
<script src="/js/register-service-worker.js"></script>
</body>
</html>
Registrazione
Il file /js/register-service-worker.js è quello che si occupa di verificare se il browser ha il supporto per i service worker e, se si, lo istanzia immediatamente al caricamento della paginaJavaScript: /js/register-service-worker.js
// Register a Service Worker.
console.log('sgart:rsw:Registrazione service worker');
navigator.serviceWorker.register('service-worker-push.js');
let pushRegistration = null;
// quando la registrazione è andata a buon fine
navigator.serviceWorker.ready
.then(function (registration) {
console.log('sgart:rsw:ready');
// salvo la registrazione per usarla successivamente
pushRegistration = registration;
// recupero la sottoscrizione.
return registration.pushManager.getSubscription()
.then(async function (subscription) {
console.log('sgart:rsw:getSubscription');
if (subscription) {
// se la subscription esisteva, la ritorno
return subscription;
}
// recupero la chiave pubblica dal server (settings.json)
const response = await fetch('/api/push/public-key');
const responseJson = await response.json();
const convertedVapidKey = urlBase64ToUint8Array(responseJson.publicKey);
// Effettuo la sottoscrizione
// Otherwise, subscribe the user (userVisibleOnly allows to specify that we don't plan to
// send notifications that don't have a visible effect for the user).
return registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: convertedVapidKey
});
});
})
.then(async function (subscription) {
// uso le Fetch API per salvare su database i dati della subscription
// le informazioni serviranno poi per inviare le notifiche
console.log('sgart:rsw:saving push registration');
const response = await fetch('/api/push/registration', {
method: 'post',
headers: { 'Content-type': 'application/json' },
body: JSON.stringify(subscription)
});
if (response.ok) {
console.log('sgart:rsw:saved push registration');
}
})
.catch(function (error) {
console.error('sgart:rsw:saving push registration', error);
});
JavaScript: urlBase64ToUint8Array(base64String)
// This function is needed because Chrome doesn't accept a base64 encoded string
// as value for applicationServerKey in pushManager.subscribe yet
// https://bugs.chromium.org/p/chromium/issues/detail?id=802280
function urlBase64ToUint8Array(base64String) {
var padding = '='.repeat((4 - base64String.length % 4) % 4);
var base64 = (base64String + padding).replace(/\-/g, '+').replace(/_/g, '/');
var rawData = window.atob(base64);
var outputArray = new Uint8Array(rawData.length);
for (var i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
}
Service Worker
In ultimo c'è il service worker, ovvero quel servizio JavaScript che rimane sempre attivo, anche quando il browser è chiuso, che si occupa di ricevere il messaggio e formattarlo per la visualizzazione.In pratica gestisce l'evento push che prende il messaggio (event.data.json()), costruisce l'oggetto che rappresenta la notifica (options) e invoca il metodo self.registration.showNotification che visualizza la notifica sul client:
JavaScript: service-worker-push.js
self.addEventListener('push', function (event) {
console.log(`sgart:swp:push event`, event);
if (!(self.Notification && self.Notification.permission === 'granted')) {
console.warn(`sgart:swp:push event NOT supported/grated`);
return;
}
var data = {};
if (event.data) {
data = event.data.json();
}
// formatto la notifica
var title = data.title || "Sgart Test Push";
var options = {
body: `${data.message}\nsent: ${data.dateSent}\nsubscriptionId: ${data.subscriptionId}` || "message empty",
//tag: 'sgart-push-demo',
icon: data.icon || "/images/new-notification.png"
};
event.waitUntil(
// visualizzo la notifica sul dispositivo client
self.registration.showNotification(title, options)
);
});
Lato Server
La parte server, realizzata in NodeJS, espone 3 API JSON- GET /api/push/public-key: ritorna la chiave pubblica presente in settings.json
- POST /api/push/registration: aggiunge una sottoscrizione al database sqlite
- DELETE /api/push/registration: rimuove una sottoscrizione dal database
Il codice completo della parte client e server si trova su GitHub sgart-it / sgart-push-notification
Console
Nel progetto ci sono altre due applicazioni- generatekeys.js per generare le chiavi pubblica e privata necessaria per le Push API
- send.js per inviare una notifica push a tutti i client registrati sul database
Generare le chiavi
Per generare le chiavi si può usare il comandoDOS / Batch file
nom run keys
Text
------------------------------------------------------------------------------------------------------
publicKey: BEYUPeh999P6IEvJfqTVWjI01zrkAVfVwyjNXr1_CC35yNAvX9f0_nM7gvYhC6mU43_0y_...
------------------------------------------------------------------------------------------------------
privateKey: 40zfSFJvowyaXwgmtudgLoC1fqX1YAA2A...
------------------------------------------------------------------------------------------------------
JSON: settings.json
{
"database": {
"filename":"/database/sgart-push.db"
},
"push": {
"publicKey":"<publicKey>",
"privateKey":"<privateKey>"
}
}
Tutta la gestione delle Push API viene gestita tramite il pacchetto npm web-push.
Il codice per generare le chiavi è questoJavaScript: generatekeys.js
const webpush = require('web-push');
const {publicKey, privateKey} = webpush.generateVAPIDKeys()
console.log("------------------------------------------------------------------------------------------------------");
console.log(`publicKey: ${publicKey}`);
console.log("------------------------------------------------------------------------------------------------------");
console.log(`privateKey: ${privateKey}`);
console.log("------------------------------------------------------------------------------------------------------");
Inviare le notifiche Push
Tramite il comandoDOS / Batch file
npm run send "messaggio"
Legge tutte le sottoscrizioni presenti nel database ed invia i messaggi tramite il metodo sendNotification(subscription, payload):
JavaScript: sendNotification(subscription, payload)
const webpush = require('web-push');
const settings = require('../settings.json');
webpush.setVapidDetails(
'https://push-demo.sgart.it',
settings.push.publicKey,
settings.push.privateKey
);
const pushSubscription = {
endpoint: sub.endpoint,
keys: {
p256dh: sub.p256dh,
auth: sub.auth
}
};
// dati da inviare come notifica,
// gestita in service-worker-push.js
const data = {
title: 'Sgart Push demo',
message: message,
icon: "/images/notification.png",
dateSent: new Date(),
subscriptionId: sub.subscriptionId
};
const sendResult = await webpush.sendNotification(pushSubscription, JSON.stringify(data));
Esiste anche il metodo generateRequestDetails identico a sendNotification solo che non invia nessuna notifica, ritorna solo le informazioni della richesta, utile per debug.
Debug Service Worker
In Chrome è possibile vedere i service worker registrati aprendo la developer toolbar (F12) nella scheda Applicationed è possibile fare debug del codice dalla scheda ourceSe avendo aperta la developer toolbar, il codice del service worker non si aggiorna, può essere necessario deregistrarlo (unregister) dalla scheda applicazioni e fare refresh della pagina.
Vedi il codice completo su GitHub sgart-it / sgart-push-notification.