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à loro notifiche.

Il codice client è già completo: in questo codelab, lavorerai sulla funzionalità lato server.

Esegui il remix dell'app di esempio e visualizzala in una nuova scheda

Le notifiche vengono bloccate automaticamente dall'app Glitch incorporata, quindi non potrai visualizzare l'anteprima dell'app in questa pagina. Ecco come procedere:

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

L'app pubblicata si apre in una nuova scheda di Chrome. Nella sezione Glitch incorporato, fai clic su Visualizza sorgente per visualizzare di nuovo il codice.

Mentre lavori a questo codelab, apporta modifiche al codice nel Glitch incorporato in questa pagina. Aggiorna la nuova scheda con l'app live per vedere le modifiche.

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

Inizia dando un'occhiata all'interfaccia utente del 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 sviluppatori di Chrome).

    • Registra service worker registra un service worker per l'ambito dell'URL del progetto Glitch. Annulla registrazione del service worker rimuove il service worker. Se è associato un abbonamento push, verrà disattivato anche l'abbonamento push.

    • Abbonati per le notifiche push crea una sottoscrizione push. È disponibile solo quando è stato registrato un service worker ed è presente una costante VAPID_PUBLIC_KEY nel codice client (maggiori informazioni più avanti), quindi non puoi ancora fare clic su di esso.

    • Se hai un abbonamento push attivo, Notifica abbonamento attuale richiede che il server invii una notifica al proprio endpoint.

    • Invia notifica a tutti gli abbonamenti indica al server di inviare una notifica a tutti gli endpoint degli abbonamenti presenti nel database.

      Tieni presente che alcuni di questi endpoint potrebbero non essere attivi. È sempre possibile che un abbonamento sparisca nel momento in cui il server gli invia una notifica.

Diamo un'occhiata a cosa succede sul lato server. Per visualizzare i messaggi dal codice del server, esamina il log Node.js nell'interfaccia di Glitch.

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

    Probabilmente vedrai un messaggio del tipo Listening on port 3000.

    Se hai provato a fare clic su Invia notifica abbonamento attuale o Invia notifica a tutti gli abbonamenti nell'interfaccia utente dell'app pubblicata, 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'abbonamento dell'utente alle notifiche push. Invia inoltre al server informazioni sulle sottoscrizioni nuove ed eliminate.

    Dal momento che lavorerai solo sulla funzionalità del server, non modificherai questo file (a parte la compilazione della costante VAPID_PUBLIC_KEY).

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

  • /views/index.html contiene l'UI dell'app.

  • .env contiene le variabili di ambiente che Glitch carica sul tuo server delle app all'avvio. In .env verrà inserito 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. Ci sono quattro elementi TODO per te, contrassegnati nei commenti al codice con TODO:. Tu devi:

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

Genera e carica dettagli VAPID

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

Contesto

Quando gli utenti si iscrivono alle notifiche, devono considerare attendibile l'identità dell'app e del relativo server. Gli utenti devono inoltre avere la certezza che la ricezione di una notifica provenga dalla stessa app che ha configurato l'abbonamento. Devono anche avere la certezza 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 a chiave pubblica per verificare l'identità di app, server ed endpoint degli abbonamenti e per criptare i contenuti delle notifiche.

In questa app utilizzerai il pacchetto npm push-web per generare chiavi VAPID e 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 nel server e aggiungi la chiave pubblica come costante nel codice client.

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

    In server.js, rimuovi i commenti intorno alle 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 aver riavviato l'app, Glitch restituisce le chiavi generate nel log 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 pubbliche e private dalla stessa coppia di chiavi.

    Glitch riavvia l'app ogni volta che modifichi il codice, quindi la prima coppia di chiavi generata potrebbe scorrere fuori dalla visualizzazione man mano che seguono altri 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, aggiungi un altro commento a queste due righe di codice, dato che 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 nel codice client.

    In public/index.js, inserisci per VAPID_PUBLIC_KEY lo stesso valore 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 l'invio di notifiche

Contesto

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

Questo pacchetto cripta automaticamente le notifiche quando viene chiamato webpush.sendNotification(), quindi non preoccuparti.

La funzionalità 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 (durata) imposta un timeout di scadenza per una notifica. In questo modo il server può evitare di inviare una notifica a un utente quando non è più pertinente.

L'opzione vapidDetails contiene le chiavi VAPID che hai caricato dalle variabili di ambiente.

Implementazione

In server.js, modifica la funzione sendNotifications nel seguente modo:

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

Contesto

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

  1. L'utente fa clic su Iscriviti per eseguire il push.

  2. Il client utilizza la costante VAPID_PUBLIC_KEY (la chiave VAPID pubblica del server) per generare un oggetto subscription univoco e specifico del server. 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, che include l'abbonamento come JSON con stringa nel corpo.

  4. Il server recupera l'elemento subscription stringato dal corpo della richiesta POST, lo analizza in formato JSON e lo aggiunge al database delle sottoscrizioni.

    Il database archivia le sottoscrizioni 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 al server per l'invio delle notifiche.

Implementazione

Le richieste di nuove sottoscrizioni arrivano alla route /add-subscription, che è un URL POST. Verrà visualizzato un gestore di route stub in server.js:

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);
});

Nella tua implementazione, questo gestore deve:

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

Per gestire i nuovi abbonamenti:

  • In server.js, modifica il gestore di 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

Contesto

Il server non sempre saprà quando un abbonamento diventa inattivo: ad esempio, un abbonamento potrebbe essere cancellato quando il browser arresta il service worker.

Tuttavia, il server può ricevere informazioni sugli abbonamenti annullati tramite l'interfaccia utente dell'app. In questo passaggio implementerai la funzionalità per rimuovere una sottoscrizione dal database.

In questo modo, il server evita di inviare numerose notifiche a endpoint inesistenti. Ovviamente questo non ha importanza con una semplice app di prova, ma diventa importante su scala più ampia.

Implementazione

Le richieste di annullamento degli abbonamenti arrivano nell'URL POST di /remove-subscription.

Il gestore delle 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);
});

Nella tua implementazione, questo gestore deve:

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

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

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

Per gestire l'annullamento degli abbonamenti:

  • In server.js, modifica il gestore di 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);
  });