Passaggio all'ora solare 31 ottobre 2021 03:00 02:00 sposta indietro l'orologio di 1 ora (si dorme 1 ora in più)
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 confermaPopup 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
------------------------------------------------------------------------------------------------------
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 workerElenco service worker
ed è possibile fare debug del codice dalla scheda ource
Source debug service workerSource 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.
Potrebbe interessarti anche: