Crea un server per le notifiche push

In questo codelab, creerai un server di notifiche push. Il server gestirà un elenco di iscrizioni push e invierà notifiche.

Il codice client è già completo. In questo codelab, lavorerai alla funzionalità lato server.

Le notifiche vengono bloccate automaticamente dall'app Glitch incorporata, pertanto non potrai visualizzare l'anteprima dell'app in questa pagina. Ecco cosa fare:

  1. Fai clic su Remix per modificare per rendere il progetto modificabile.
  2. Per visualizzare l'anteprima del sito, premi Visualizza app. Quindi premi A schermo intero schermo intero.

L'app in tempo reale si apre in una nuova scheda di Chrome. In Glitch incorporato, fai clic su Visualizza codice sorgente per mostrare di nuovo il codice.

Mentre svolgi questo codelab, apporta modifiche al codice nel Glitch incorporato in questa pagina. Aggiorna la nuova scheda con l'app pubblicata per visualizzare le modifiche.

Acquisisci familiarità con l'app iniziale e il relativo codice

Inizia dando un'occhiata all'interfaccia utente client dell'app.

Nella nuova scheda di Chrome:

  1. Premi "Control+Maiusc+J" (o "Comando+Opzione+J" su Mac) per aprire DevTools. Fai clic sulla scheda Console.

  2. Prova a fare clic sui pulsanti nell'interfaccia utente (controlla l'output nella console per sviluppatori di Chrome).

    • Registra service worker registra un service worker per l'ambito dell'URL del progetto Glitch. Annulla la registrazione del service worker rimuove il service worker. Se è collegata una sottoscrizione push, anche la sottoscrizione push verrà disattivata.

    • Iscriviti alle notifiche push crea una sottoscrizione push. È disponibile solo quando un service worker è stato registrato ed è presente una costante VAPID_PUBLIC_KEY nel codice client (ulteriori informazioni in seguito), quindi non puoi ancora fare clic su questa opzione.

    • Se hai una sottoscrizione push attiva, Invia notifica all'abbonamento attuale richiede che il server invii una notifica al suo endpoint.

    • Invia notifiche a tutte le sottoscrizioni indica al server di inviare una notifica a tutti gli endpoint di sottoscrizione nel suo database.

      Tieni presente che alcuni di questi endpoint potrebbero non essere attivi. È sempre possibile che un abbonamento scompaia prima che il server invii una notifica.

Vediamo cosa succede lato server. Per visualizzare i messaggi del codice del server, controlla il log di Node.js nell'interfaccia di Glitch.

  • Nell'app Glitch, fai clic su Strumenti -> Log.

    Probabilmente vedrai un messaggio come Listening on port 3000.

    Se hai provato a fare clic su Notifica l'abbonamento attuale o Notifica tutti gli abbonamenti nell'interfaccia utente dell'app in tempo reale, vedrai anche il seguente messaggio:

    TODO: Implement sendNotifications()
    Endpoints to send to:  []

Ora diamo un'occhiata al codice.

  • public/index.js contiene il codice client completato. Esegue il rilevamento delle funzionalità, registra e annulla la registrazione del service worker e controlla l'iscrizione dell'utente alle notifiche push. Inoltre, invia al server informazioni sugli abbonamenti nuovi ed eliminati.

    Poiché lavorerai solo sulla funzionalità del server, non modificherai questo file (tranne che per compilare la costante VAPID_PUBLIC_KEY).

  • public/service-worker.js è un semplice service worker che acquisisce gli eventi push e mostra le notifiche.

  • /views/index.html contiene l'interfaccia utente dell'app.

  • .env contiene le variabili di ambiente che Glitch carica nel server dell'app all'avvio. Dovrai compilare .env con i dettagli di autenticazione per l'invio delle notifiche.

  • server.js è il file in cui svolgerai la maggior parte del lavoro durante questo codelab.

    Il codice iniziale crea un semplice server web Express. Sono presenti quattro elementi TODO, contrassegnati nei commenti del codice con TODO:. Tu devi:

    In questo codelab, analizzerai questi elementi TODO uno alla volta.

Genera e carica i dettagli VAPID

Il primo elemento TODO è generare i dettagli VAPID, aggiungerli alle variabili di ambiente Node.js e aggiornare il codice client e server con i nuovi valori.

Sfondo

Quando gli utenti si iscrivono alle notifiche, devono considerare attendibile l'identità dell'app e del suo server. Gli utenti devono anche essere certi che, quando ricevono una notifica, provenga dalla stessa app che ha configurato l'abbonamento. Inoltre, devono essere certi che nessun altro possa leggere i contenuti della notifica.

Il protocollo che rende le notifiche push sicure e private si chiama Voluntary Application Server Identification for Web Push (VAPID). VAPID utilizza la crittografia con chiave pubblica per verificare l'identità di app, server ed endpoint di abbonamento e per criptare i contenuti delle notifiche.

In questa app utilizzerai il pacchetto npm web-push per generare chiavi VAPID e per criptare e inviare notifiche.

Implementazione

In questo passaggio, genera una coppia di chiavi VAPID per la tua app e aggiungile alle variabili di ambiente. Carica le variabili di ambiente sul server e aggiungi la chiave pubblica come costante nel codice client.

  1. Utilizza la funzione generateVAPIDKeys della libreria web-push per creare una coppia di chiavi VAPID.

    In server.js, rimuovi i commenti dalle seguenti righe di codice:

    server.js

    // Generate VAPID keys (only do this once).
    /*
     * const vapidKeys = webpush.generateVAPIDKeys();
     * console.log(vapidKeys);
     */

    const vapidKeys = webpush.generateVAPIDKeys();
    console
    .log(vapidKeys);
  2. Dopo che Glitch ha riavviato l'app, le chiavi generate vengono visualizzate nel log di Node.js all'interno dell'interfaccia di Glitch (non nella console di Chrome). Per visualizzare le chiavi VAPID, seleziona Strumenti -> Log nell'interfaccia di Glitch.

    Assicurati di copiare le chiavi pubblica e privata dalla stessa coppia di chiavi.

    Glitch riavvia l'app ogni volta che modifichi il codice, pertanto la prima coppia di chiavi che generi potrebbe non essere più visibile man mano che viene visualizzato altro output.

  3. In .env, copia e incolla le chiavi VAPID. Racchiudi le chiavi tra virgolette doppie ("...").

    Per VAPID_SUBJECT, puoi inserire "mailto:test@test.test".

    .env

    # process.env.SECRET
    VAPID_PUBLIC_KEY
    =
    VAPID_PRIVATE_KEY
    =
    VAPID_SUBJECT
    =
    VAPID_PUBLIC_KEY
    ="BN3tWzHp3L3rBh03lGLlLlsq..."
    VAPID_PRIVATE_KEY
    ="I_lM7JMIXRhOk6HN..."
    VAPID_SUBJECT
    ="mailto:test@test.test"
  4. In server.js, commenta di nuovo queste due righe di codice, poiché devi generare le chiavi VAPID una sola volta.

    server.js

    // Generate VAPID keys (only do this once).
    /*
    const vapidKeys = webpush.generateVAPIDKeys();
    console.log(vapidKeys);
    */

    const vapidKeys = webpush.generateVAPIDKeys();
    console
    .log(vapidKeys);
  5. In server.js, carica i dettagli VAPID dalle variabili di ambiente.

    server.js

    const vapidDetails = {
     
    // TODO: Load VAPID details from environment variables.
      publicKey
    : process.env.VAPID_PUBLIC_KEY,
      privateKey
    : process.env.VAPID_PRIVATE_KEY,
      subject
    : process.env.VAPID_SUBJECT
    }
  6. Copia e incolla la chiave public anche nel codice client.

    In public/index.js, inserisci lo stesso valore per VAPID_PUBLIC_KEY che hai copiato nel file .env:

    public/index.js

    // Copy from .env
    const VAPID_PUBLIC_KEY = '';
    const VAPID_PUBLIC_KEY = 'BN3tWzHp3L3rBh03lGLlLlsq...';
    ````

Implementare la funzionalità per inviare notifiche

Sfondo

In questa app, utilizzerai il pacchetto npm web-push per inviare notifiche.

Questo pacchetto cripta automaticamente le notifiche quando viene chiamata webpush.sendNotification(), quindi non devi preoccuparti di questo.

web-push accetta più opzioni per le notifiche, ad esempio puoi allegare intestazioni al messaggio e specificare la codifica dei contenuti.

In questo codelab utilizzerai solo due opzioni, definite con le seguenti righe di codice:

let options = {
  TTL
: 10000; // Time-to-live. Notifications expire after this.
  vapidDetails
: vapidDetails; // VAPID keys from .env
};

L'opzione TTL (time-to-live) imposta un timeout di scadenza per una notifica. In questo modo, il server evita di inviare una notifica a un utente quando non è più pertinente.

L'opzione vapidDetails contiene le chiavi VAPID caricate dalle variabili di ambiente.

Implementazione

In server.js, modifica la funzione sendNotifications come segue:

server.js

function sendNotifications(database, endpoints) {
 
// TODO: Implement functionality to send notifications.
  console
.log('TODO: Implement sendNotifications()');
  console
.log('Endpoints to send to: ', endpoints);
  let notification
= JSON.stringify(createNotification());
  let options
= {
    TTL
: 10000, // Time-to-live. Notifications expire after this.
    vapidDetails
: vapidDetails // VAPID keys from .env
 
};
  endpoints
.map(endpoint => {
    let subscription
= database[endpoint];
    webpush
.sendNotification(subscription, notification, options);
 
});
}

Poiché webpush.sendNotification() restituisce una promessa, puoi aggiungere facilmente la gestione degli errori.

In server.js, modifica di nuovo la funzione sendNotifications:

server.js

function sendNotifications(database, endpoints) {
  let notification
= JSON.stringify(createNotification());
  let options
= {
    TTL
: 10000; // Time-to-live. Notifications expire after this.
    vapidDetails
: vapidDetails; // VAPID keys from .env
 
};
  endpoints
.map(endpoint => {
    let subscription
= database[endpoint];
    webpush
.sendNotification(subscription, notification, options);
    let id
= endpoint.substr((endpoint.length - 8), endpoint.length);
    webpush
.sendNotification(subscription, notification, options)
   
.then(result => {
      console
.log(`Endpoint ID: ${id}`);
      console
.log(`Result: ${result.statusCode} `);
   
})
   
.catch(error => {
      console
.log(`Endpoint ID: ${id}`);
      console
.log(`Error: ${error.body} `);
   
});
 
});
}

Gestire i nuovi abbonamenti

Sfondo

Ecco cosa succede quando l'utente si iscrive alle notifiche push:

  1. L'utente fa clic su Iscriviti per ricevere notifiche push.

  2. Il client utilizza la costante VAPID_PUBLIC_KEY (la chiave VAPID pubblica del server) per generare un oggetto VAPID_PUBLIC_KEY univoco e specifico per il server.subscription L'oggetto subscription ha il seguente aspetto:

       {
         
    "endpoint": "https://fcm.googleapis.com/fcm/send/cpqAgzGzkzQ:APA9...",
         
    "expirationTime": null,
         
    "keys":
         
    {
           
    "p256dh": "BNYDjQL9d5PSoeBurHy2e4d4GY0sGJXBN...",
           
    "auth": "0IyyvUGNJ9RxJc83poo3bA"
         
    }
       
    }
  3. Il client invia una richiesta POST all'URL /add-subscription, inclusa la sottoscrizione sotto forma di JSON con stringa nel corpo.

  4. Il server recupera subscription con formato di stringa dal corpo della richiesta POST, lo analizza nuovamente in JSON e lo aggiunge al database delle iscrizioni.

    Il database memorizza le iscrizioni utilizzando i propri endpoint come chiave:

    {
     
"https://fcm...1234": {
        endpoint
: "https://fcm...1234",
        expirationTime
: ...,
        keys
: { ... }
     
},
     
"https://fcm...abcd": {
        endpoint
: "https://fcm...abcd",
        expirationTime
: ...,
        keys
: { ... }
     
},
     
"https://fcm...zxcv": {
        endpoint
: "https://fcm...zxcv",
        expirationTime
: ...,
        keys
: { ... }
     
},
   
}

Ora il nuovo abbonamento è disponibile per il server per l'invio di notifiche.

Implementazione

Le richieste di nuovi abbonamenti vengono inoltrate alla route /add-subscription, che è un URL POST. In server.js vedrai un gestore di route stub:

server.js

app.post('/add-subscription', (request, response) => {
 
// TODO: implement handler for /add-subscription
  console
.log('TODO: Implement handler for /add-subscription');
  console
.log('Request body: ', request.body);
  response
.sendStatus(200);
});

Nell'implementazione, questo gestore deve:

  • Recupera il nuovo abbonamento dal corpo della richiesta.
  • Accedere al database degli abbonamenti attivi.
  • Aggiungi il nuovo abbonamento all'elenco degli abbonamenti attivi.

Per gestire i nuovi abbonamenti:

  • In server.js, modifica il gestore della route per /add-subscription come segue:

    server.js

    app.post('/add-subscription', (request, response) => {
     
// TODO: implement handler for /add-subscription
      console
.log('TODO: Implement handler for /add-subscription');
      console
.log('Request body: ', request.body);
      let subscriptions
= Object.assign({}, request.session.subscriptions);
      subscriptions
[request.body.endpoint] = request.body;
      request
.session.subscriptions = subscriptions;
      response
.sendStatus(200);
   
});

Gestire gli annullamenti degli abbonamenti

Sfondo

Il server non sa sempre quando un abbonamento diventa inattivo, ad esempio un abbonamento potrebbe essere cancellato quando il browser arresta il servizio worker.

Tuttavia, il server può scoprire gli abbonamenti annullati tramite l'interfaccia utente dell'app. In questo passaggio, implementerai la funzionalità per rimuovere un abbonamento dal database.

In questo modo, il server evita di inviare una serie di notifiche a endpoint inesistenti. Ovviamente, questo non importa in realtà con una semplice app di test, ma diventa importante su scala più ampia.

Implementazione

Le richieste di annullamento degli abbonamenti vengono inviate all'URL POST /remove-subscription.

Il gestore della route stub in server.js ha il seguente aspetto:

server.js

app.post('/remove-subscription', (request, response) => {
 
// TODO: implement handler for /remove-subscription
  console
.log('TODO: Implement handler for /remove-subscription');
  console
.log('Request body: ', request.body);
  response
.sendStatus(200);
});

Nell'implementazione, questo gestore deve:

  • Recupera l'endpoint dell'abbonamento annullato dal corpo della richiesta.
  • Accedere al database degli abbonamenti attivi.
  • Rimuovi l'abbonamento annullato dall'elenco degli abbonamenti attivi.

Il corpo della richiesta POST del client contiene l'endpoint da rimuovere:

{
 
"endpoint": "https://fcm.googleapis.com/fcm/send/cpqAgzGzkzQ:APA9..."
}

Per gestire gli annullamenti degli abbonamenti:

  • In server.js, modifica il gestore della route per /remove-subscription come segue:

    server.js

  app.post('/remove-subscription', (request, response) => {
   
// TODO: implement handler for /remove-subscription
    console
.log('TODO: Implement handler for /remove-subscription');
    console
.log('Request body: ', request.body);
    let subscriptions
= Object.assign({}, request.session.subscriptions);
   
delete subscriptions[request.body.endpoint];
    request
.session.subscriptions = subscriptions;
    response
.sendStatus(200);
 
});