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.
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.

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 applicazione

HTML: 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 pagina

JavaScript: /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);
    });
a questo si aggiunge la funzione urlBase64ToUint8Array

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;
}
sul browser comparirà un popup con la richiesta di confermare la sottoscrizione
Popup di conferma
Popup di conferma

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 comando

DOS / Batch file

nom run keys
che genera un output simile al seguente

Text

------------------------------------------------------------------------------------------------------
publicKey: BEYUPeh999P6IEvJfqTVWjI01zrkAVfVwyjNXr1_CC35yNAvX9f0_nM7gvYhC6mU43_0y_...
------------------------------------------------------------------------------------------------------
privateKey: 40zfSFJvowyaXwgmtudgLoC1fqX1YAA2A...
------------------------------------------------------------------------------------------------------
questi valori vanno inseriti nel file settings.json

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 è questo

JavaScript: 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 comando

DOS / Batch file

npm run send "messaggio"
è 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):

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 Application
Elenco service worker
Elenco service worker
ed è possibile fare debug del codice dalla scheda ource
Source debug service worker
Source debug service worker
Se 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.
Tags:
GitHub10 HTML 554 JavaScript184 NodeJs21 Vanilla JS24
Potrebbe interessarti anche: