Invio di messaggi con le librerie web push

Matt Gaunt

Uno dei punti deboli dell'utilizzo delle funzionalità web push è che l'attivazione di un messaggio push è estremamente "comoda". Per attivare un messaggio push, un'applicazione deve effettuare una richiesta POST a un servizio push seguendo il protocollo push web. Per utilizzare il push in tutti i browser, devi utilizzare VAPID (ovvero chiavi server delle applicazioni), che in pratica richiede l'impostazione di un'intestazione con un valore che dimostri che l'applicazione può inviare messaggi a un utente. Per inviare dati con un messaggio push, i dati devono essere criptati ed è necessario aggiungere intestazioni specifiche in modo che il browser possa decriptare il messaggio correttamente.

Il problema principale con l'attivazione del push è che, se si verifica un problema, è difficile identificarlo. Questa procedura migliorerà con il tempo e con un supporto più ampio del browser, ma è tutt'altro che facile. Per questo motivo, ti consiglio vivamente di utilizzare una libreria per gestire la crittografia, la formattazione e l'attivazione del messaggio push.

Se vuoi sapere cosa fanno le librerie, ne parleremo nella prossima sezione. Per ora, vedremo la gestione degli abbonamenti e l'utilizzo di una libreria web push esistente per effettuare le richieste push.

In questa sezione utilizzeremo la libreria dei nodi web-push. Le altre lingue saranno diverse, ma non troppo diverse. Esaminiamo il nodo poiché è JavaScript e dovrebbe essere il più accessibile per i lettori.

Analizzeremo i seguenti passaggi:

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

Salvataggio degli abbonamenti

Il salvataggio e l'esecuzione di query su PushSubscription da un database variano a seconda del linguaggio lato server e della scelta del database, ma potrebbe essere utile vedere un esempio di come eseguire questa operazione.

Nella pagina web demo, il PushSubscription viene inviato al nostro backend creando 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 listener di richieste corrispondente per l'endpoint /api/save-subscription/:

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

In questa route convalidiamo l'abbonamento solo 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 sottoscrizioni. Si tratta di un database semplice basato su file, ma puoi utilizzare qualsiasi database a tua scelta. La usiamo solo perché non richiede alcuna configurazione. Per la produzione, dovresti usare qualcosa di più affidabile. (Tendo a utilizzare il 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

Per inviare un messaggio push, abbiamo bisogno di un evento che attivi il processo di invio di un messaggio agli utenti. Un approccio comune consiste nella creazione di una pagina di amministrazione che ti consenta di configurare e attivare il messaggio push. Tuttavia, potresti creare un programma da eseguire in locale 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 "Mi piace" per amministratore che ti consente di attivare un push. Poiché è solo una demo, è una pagina pubblica.

Analizzerò tutti i passaggi necessari per far funzionare la demo. Questi sono dei piccoli passi, che tutti potranno seguire, inclusi coloro che non hanno mai usato Node.

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

Nella demo, questi valori vengono aggiunti alla nostra app Node in questo modo (un codice noioso, ma voglio solo che tu sappia che non c'è magia):

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

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

npm install web-push --save

Quindi, nel nostro script dei nodi richiediamo il modulo web-push in questo modo:

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

Ora possiamo iniziare a utilizzare il modulo web-push. Prima di tutto dobbiamo parlare al modulo web-push delle chiavi server delle applicazioni. (Ricorda che sono anche note come chiavi VAPID perché questo è 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 mailto. Questa informazione verrà effettivamente inviata al servizio web push come parte della richiesta per attivare un push. Il motivo è che, se un servizio web push ha bisogno di contattare il mittente, deve disporre di alcune informazioni che gli permetteranno di farlo.

Ora il modulo web-push è pronto per essere utilizzato. Il passaggio successivo consiste nell'attivare un messaggio push.

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

Screenshot della pagina di amministrazione.

Se fai clic sul pulsante "Attiva messaggio push", verrà effettuata una richiesta POST a /api/trigger-push-msg/, che è l'indicatore per il nostro backend per inviare i messaggi push, in modo da creare la route in express per questo endpoint:

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

Quando riceviamo questa richiesta, recuperiamo 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() potrà quindi utilizzare la libreria web push per inviare un messaggio alla sottoscrizione 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 c'è nulla da fare. Se la promessa viene rifiutata, devi esaminare l'errore per stabilire se PushSubscription è ancora valido o meno.

Per determinare il tipo di errore da un servizio push, è preferibile esaminare il codice di stato. I messaggi di errore variano in base al servizio push e alcuni sono più utili di altri.

In questo esempio, verifica la presenza dei codici di stato 404 e 410, che sono i codici di stato HTTP per "Non trovato" e "Non disponibile". Se ne riceviamo uno, significa che l'abbonamento è scaduto o non è più valido. In questi scenari, dobbiamo rimuovere le sottoscrizioni dal nostro database.

In caso di altri errori, ci limitiamo a throw err, in modo che la promessa restituita da triggerPushMsg() venga rifiutata.

Parleremo di altri codici di stato nella prossima sezione, quando esamineremo in dettaglio il protocollo web push.

Dopo aver eseguito il loop delle sottoscrizioni, 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 principali passaggi di implementazione:

  1. Crea un'API per inviare 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 presunto).
  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.

Adesso che cosa fanno esattamente queste librerie web-push per noi?

Passaggi successivi

Codelab