Invio di messaggi con le librerie web push

Matt Gaunt

Uno dei problemi principali quando si lavora con le notifiche push web è che l'attivazione di un messaggio push è estremamente "complicata". Per attivare un messaggio push, un'applicazione deve inviare una richiesta POST a un servizio push seguendo il protocollo push web. Per utilizzare i push su tutti i browser, devi utilizzare VAPID (chiavi del server delle applicazioni), che in pratica richiede l'impostazione di un'intestazione con un valore che dimostri che la tua applicazione può inviare un messaggio a un utente. Per inviare i dati con un messaggio push, i dati devono essere criptati e devono essere aggiunte intestazioni specifiche affinché il browser possa decriptare il messaggio correttamente.

Il problema principale dell'attivazione push è che, in caso di problemi, è difficile diagnosticarli. La situazione sta migliorando con il tempo e con il supporto di un numero maggiore di browser, ma non è affatto facile. Per questo motivo, ti consigliamo vivamente di utilizzare una libreria per gestire la crittografia, la formattazione e l'attivazione del messaggio push.

Se vuoi saperne di più sul funzionamento delle librerie, lo vedremo nella prossima sezione. Per il momento, esamineremo la gestione delle iscrizioni e l'utilizzo di una libreria push web esistente per effettuare le richieste push.

In questa sezione utilizzeremo la libreria Node web-push. Altre lingue presenteranno differenze, ma non saranno troppo diverse. Stiamo valutando Node perché è JavaScript e dovrebbe essere la lingua più accessibile per i lettori.

Procederemo nel seguente modo:

  1. Invia un abbonamento al nostro backend e salvalo.
  2. Recupera le iscrizioni salvate e attiva un messaggio push.

Salvare gli abbonamenti

Il salvataggio e le query di PushSubscription da un database variano a seconda del linguaggio lato server e del database scelto, ma potrebbe essere utile vedere un esempio di come potrebbe essere eseguita.

Nella pagina web di esempio, PushSubscription viene inviato al nostro backend tramite una semplice richiesta POST:

function sendSubscriptionToBackEnd(subscription) {
  return fetch('/api/save-subscription/', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(subscription),
  })
    .then(function (response) {
      if (!response.ok) {
        throw new Error('Bad status code from server.');
      }

      return response.json();
    })
    .then(function (responseData) {
      if (!(responseData.data && responseData.data.success)) {
        throw new Error('Bad response from server.');
      }
    });
}

Il server Express nella nostra demo ha un ascoltatore delle richieste di corrispondenza per l'endpoint /api/save-subscription/:

app.post('/api/save-subscription/', function (req, res) {

In questo percorso convalidiamo l'abbonamento per assicurarci che la richiesta sia corretta e non sia piena di rifiuti:

const isValidSaveRequest = (req, res) => {
  // Check the request body has at least an endpoint.
  if (!req.body || !req.body.endpoint) {
    // Not a valid subscription.
    res.status(400);
    res.setHeader('Content-Type', 'application/json');
    res.send(
      JSON.stringify({
        error: {
          id: 'no-endpoint',
          message: 'Subscription must have an endpoint.',
        },
      }),
    );
    return false;
  }
  return true;
};

Se l'abbonamento è valido, dobbiamo salvarlo e restituire una risposta JSON appropriata:

return saveSubscriptionToDatabase(req.body)
  .then(function (subscriptionId) {
    res.setHeader('Content-Type', 'application/json');
    res.send(JSON.stringify({data: {success: true}}));
  })
  .catch(function (err) {
    res.status(500);
    res.setHeader('Content-Type', 'application/json');
    res.send(
      JSON.stringify({
        error: {
          id: 'unable-to-save-subscription',
          message:
            'The subscription was received but we were unable to save it to our database.',
        },
      }),
    );
  });

Questa demo utilizza nedb per archiviare le iscrizioni. Si tratta di un semplice database basato su file, ma puoi utilizzare qualsiasi database a tua scelta. Lo usiamo solo perché non richiede alcuna configurazione. Per la produzione, ti consigliamo di utilizzare qualcosa di più affidabile. (Tendo a rimanere fedele al buon vecchio MySQL.)

function saveSubscriptionToDatabase(subscription) {
  return new Promise(function (resolve, reject) {
    db.insert(subscription, function (err, newDoc) {
      if (err) {
        reject(err);
        return;
      }

      resolve(newDoc._id);
    });
  });
}

Invio di messaggi push

Quando si tratta di inviare un messaggio push, in definitiva abbiamo bisogno di un evento per attivare il processo di invio di un messaggio agli utenti. Un approccio comune consiste nel creare una pagina di amministrazione che consenta di configurare e attivare il messaggio push. Tuttavia, potresti creare un programma da eseguire localmente o qualsiasi altro approccio che consenta di accedere all'elenco di PushSubscription ed eseguire il codice per attivare il messaggio push.

La nostra demo ha una pagina "come amministratore" che ti consente di attivare un push. Poiché si tratta solo di una demo, è una pagina pubblica.

Ti illustrerò tutti i passaggi necessari per far funzionare la demo. Saranno passaggi graduali, in modo che tutti possano seguire, inclusi coloro che non hanno mai utilizzato Node.

Quando abbiamo discusso dell'iscrizione di un utente, abbiamo parlato dell'aggiunta di un applicationServerKey alle opzioni subscribe(). È nel back-end che avremo bisogno di questa chiave privata.

Nella demo, questi valori vengono aggiunti alla nostra app Node come segue (codice noioso, lo so, ma voglio chiarire che non c'è nulla di magico):

const vapidKeys = {
  publicKey:
    'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U',
  privateKey: 'UUxI4O8-FbRouAevSmBQ6o18hgE4nSG3qwvJTfKc-ls',
};

Ora dobbiamo installare il modulo web-push per il nostro server Node:

npm install web-push --save

Poi, nel nostro script Node richiediamo il modulo web-push come segue:

const webpush = require('web-push');

Ora possiamo iniziare a utilizzare il modulo web-push. Innanzitutto dobbiamo indicare al modulo web-push le chiavi del server delle applicazioni. Ricorda che sono note anche come chiavi VAPID perché è il nome della specifica.

const vapidKeys = {
  publicKey:
    'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U',
  privateKey: 'UUxI4O8-FbRouAevSmBQ6o18hgE4nSG3qwvJTfKc-ls',
};

webpush.setVapidDetails(
  'mailto:web-push-book@gauntface.com',
  vapidKeys.publicKey,
  vapidKeys.privateKey,
);

Tieni presente che abbiamo incluso anche una stringa "mailto:". Questa stringa deve essere un URL o un indirizzo email di tipo mailto. Queste informazioni verranno effettivamente inviate al servizio push web nell'ambito della richiesta di attivazione di un push. Il motivo è che, se un servizio push web ha bisogno di contattare il mittente, avrà alcune informazioni che gli consentiranno di farlo.

A questo punto, il modulo web-push è pronto per l'uso. Il passaggio successivo consiste nell'attivare un messaggio push.

La demo utilizza il pannello di amministrazione simulato per attivare i messaggi push.

Screenshot della pagina di amministrazione.

Se fai clic sul pulsante "Attiva messaggio push", verrà inviata una richiesta POST a /api/trigger-push-msg/, che è l'indicatore per il nostro backend di inviare messaggi push, quindi creiamo il percorso in express per questo endpoint:

app.post('/api/trigger-push-msg/', function (req, res) {

Quando riceviamo questa richiesta, prendiamo le sottoscrizioni dal database e per ognuna attiviamo un messaggio push.

return getSubscriptionsFromDatabase().then(function (subscriptions) {
  let promiseChain = Promise.resolve();

  for (let i = 0; i < subscriptions.length; i++) {
    const subscription = subscriptions[i];
    promiseChain = promiseChain.then(() => {
      return triggerPushMsg(subscription, dataToSend);
    });
  }

  return promiseChain;
});

La funzione triggerPushMsg() può quindi utilizzare la libreria web-push per inviare un messaggio all'iscrizione fornita.

const triggerPushMsg = function (subscription, dataToSend) {
  return webpush.sendNotification(subscription, dataToSend).catch((err) => {
    if (err.statusCode === 404 || err.statusCode === 410) {
      console.log('Subscription has expired or is no longer valid: ', err);
      return deleteSubscriptionFromDatabase(subscription._id);
    } else {
      throw err;
    }
  });
};

La chiamata a webpush.sendNotification() restituirà una promessa. Se il messaggio è stato inviato correttamente, la promessa verrà risolta e non dovremo fare nulla. Se la promessa viene rifiutata, devi esaminare l'errore, che ti indicherà se PushSubscription è ancora valido o meno.

Per determinare il tipo di errore di un servizio push, è meglio esaminare il codice di stato. I messaggi di errore variano a seconda dei servizi push e alcuni sono più utili di altri.

In questo esempio, vengono controllati i codici di stato 404 e 410, che sono i codici di stato HTTP per "Non trovato" e "Non più disponibile". Se riceviamo uno di questi messaggi, significa che l'abbonamento è scaduto o non è più valido. In questi casi, dobbiamo rimuovere le iscrizioni dal nostro database.

In caso di un altro errore, basta throw err, che farà rifiutare la promessa restituita da triggerPushMsg().

Tratteremo alcuni degli altri codici di stato nella sezione successiva, quando esamineremo più in dettaglio il protocollo push web.

Dopo aver eseguito il ciclo degli abbonamenti, dobbiamo restituire una risposta JSON.

.then(() => {
res.setHeader('Content-Type', 'application/json');
    res.send(JSON.stringify({ data: { success: true } }));
})
.catch(function(err) {
res.status(500);
res.setHeader('Content-Type', 'application/json');
res.send(JSON.stringify({
    error: {
    id: 'unable-to-send-messages',
    message: `We were unable to send messages to all subscriptions : ` +
        `'${err.message}'`
    }
}));
});

Abbiamo esaminato i passaggi di implementazione principali:

  1. Crea un'API per inviare gli abbonamenti dalla nostra pagina web al nostro backend in modo da poterli salvare in un database.
  2. Crea un'API per attivare l'invio di messaggi push (in questo caso, un'API chiamata dal pannello di amministrazione simulato).
  3. Recupera tutte le sottoscrizioni dal nostro backend e invia un messaggio a ciascuna sottoscrizione con una delle librerie web-push.

Indipendentemente dal backend (Node, PHP, Python e così via), i passaggi per implementare il push saranno gli stessi.

Ora, che cosa fanno esattamente queste librerie push web per noi?

Passaggi successivi

Codelab