Iscrizione di un utente

Matt Gaunt

Il primo passaggio consiste nell'ottenere l'autorizzazione dell'utente per inviare messaggi push, dopodiché possiamo mettere le mani su un PushSubscription.

L'API JavaScript per farlo è abbastanza semplice, quindi vediamo il flusso logico.

Rilevamento di funzionalità

Per prima cosa dobbiamo verificare se il browser corrente supporta effettivamente i messaggi push. Possiamo verificare se il push è supportato con due semplici controlli.

  1. Controlla serviceWorker su navigator.
  2. Seleziona PushManager in finestra.
if (!('serviceWorker' in navigator)) {
  // Service Worker isn't supported on this browser, disable or hide UI.
  return;
}

if (!('PushManager' in window)) {
  // Push isn't supported on this browser, disable or hide UI.
  return;
}

Sebbene il supporto dei browser stia crescendo rapidamente sia per i worker di servizio sia per la messaggistica push, è sempre una buona idea rilevare le funzionalità per entrambi e migliorarle progressivamente.

Registra un service worker

Grazie al rilevamento delle funzionalità, sappiamo che sono supportati sia i service worker sia il push. Il passaggio successivo consiste nel "registrare" il nostro service worker.

Quando registriamo un worker di servizio, diciamo al browser dove si trova il file del worker di servizio. Il file è ancora solo JavaScript, ma il browser "concede l'accesso" alle API del worker di servizio, tra cui push. Per essere più precisi, il browser esegue il file in un ambiente di servizio worker.

Per registrare un worker di servizio, chiama navigator.serviceWorker.register() passando il percorso del tuo file. In questo modo:

function registerServiceWorker() {
  return navigator.serviceWorker
    .register('/service-worker.js')
    .then(function (registration) {
      console.log('Service worker successfully registered.');
      return registration;
    })
    .catch(function (err) {
      console.error('Unable to register service worker.', err);
    });
}

Questa funzione indica al browser che abbiamo un file del service worker e dove si trova. In questo caso, il file del service worker si trova in /service-worker.js. In background, il browser svolgerà i seguenti passaggi dopo aver chiamato register():

  1. Scarica il file del worker di servizio.

  2. Esegui il codice JavaScript.

  3. Se tutto funziona correttamente e non si verificano errori, la promessa restituita da register() si risolverà. In caso di errori di qualsiasi tipo, la promessa verrà rifiutata.

Se register() viene rifiutato, controlla se in JavaScript sono presenti errori o errori ortografici in Chrome DevTools.

Quando register() viene risolto, restituisce un ServiceWorkerRegistration. Utilizzeremo questa registrazione per accedere all'API PushManager.

Compatibilità del browser con l'API PushManager

Supporto dei browser

  • Chrome: 42.
  • Edge: 17.
  • Firefox: 44.
  • Safari: 16.

Origine

Richiesta di autorizzazione

Abbiamo registrato il nostro service worker e siamo pronti ad abbonare l'utente. Il passaggio successivo consiste nell'ottenere l'autorizzazione dell'utente per inviare messaggi push.

L'API per ottenere l'autorizzazione è relativamente semplice, ma il rovescio della medaglia è che di recente l'API è passata dall'accettare un callback alla restituzione di una promessa. Il problema è che non siamo in grado di capire quale versione dell'API è implementata dal browser corrente, quindi devi implementare entrambe le opzioni e gestire entrambe.

function askPermission() {
  return new Promise(function (resolve, reject) {
    const permissionResult = Notification.requestPermission(function (result) {
      resolve(result);
    });

    if (permissionResult) {
      permissionResult.then(resolve, reject);
    }
  }).then(function (permissionResult) {
    if (permissionResult !== 'granted') {
      throw new Error("We weren't granted permission.");
    }
  });
}

Nel codice riportato sopra, lo snippet di codice importante è la chiamata a Notification.requestPermission(). Questo metodo mostrerà un prompt all'utente:

Richiesta di autorizzazione su Chrome per computer e dispositivi mobili.

Una volta che l'utente ha interagito con la richiesta di autorizzazione premendo Consenti, Blocca o semplicemente chiudendola, viene visualizzato il risultato sotto forma di stringa: 'granted', 'default' o 'denied'.

Nel codice campione precedente, la promessa restituita da askPermission() si risolve se l'autorizzazione viene concessa, altrimenti viene generato un errore in cui la promessa viene rifiutata.

Un caso limite che devi gestire è quando l'utente fa clic sul pulsante "Blocca". In questo caso, la tua app web non potrà chiedere di nuovo all'utente l'autorizzazione. Dovrà "sbloccare" manualmente l'app modificando il suo stato di autorizzazione, che è nascosto in un riquadro delle impostazioni. Valuta attentamente come e quando chiedere all'utente l'autorizzazione, perché se fa clic su Blocca, non è facile annullare la decisione.

La buona notizia è che la maggior parte degli utenti è felice di concedere l'autorizzazione, a condizione che sappia perché gli viene chiesta.

Vedremo in seguito in che modo alcuni siti popolari richiedono l'autorizzazione.

Iscrivere un utente con PushManager

Una volta registrato il Service worker e ottenuto l'autorizzazione, possiamo iscrivere un utente chiamando registration.pushManager.subscribe().

function subscribeUserToPush() {
  return navigator.serviceWorker
    .register('/service-worker.js')
    .then(function (registration) {
      const subscribeOptions = {
        userVisibleOnly: true,
        applicationServerKey: urlBase64ToUint8Array(
          'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U',
        ),
      };

      return registration.pushManager.subscribe(subscribeOptions);
    })
    .then(function (pushSubscription) {
      console.log(
        'Received PushSubscription: ',
        JSON.stringify(pushSubscription),
      );
      return pushSubscription;
    });
}

Quando chiamiamo il metodo subscribe(), passiamo un oggetto options, costituito da parametri obbligatori e facoltativi.

Vediamo tutte le opzioni che possiamo passare.

Opzioni userVisibleOnly

Quando la funzionalità push è stata aggiunta per la prima volta ai browser, non era chiaro se gli sviluppatori dovessero essere in grado di inviare un messaggio push e non mostrare una notifica. Questo è comunemente noto come push silenzioso, perché l'utente non sa che è successo qualcosa in background.

Il timore era che gli sviluppatori potessero fare cose spiacevoli, come monitorare la posizione di un utente su base continuativa all'insaputa dell'utente.

Per evitare questo scenario e dare agli autori delle specifiche il tempo di valutare il modo migliore per supportare questa funzionalità, è stata aggiunta l'opzione userVisibleOnly e il passaggio di un valore true è un accordo simbolico con il browser per cui l'app web mostrerà una notifica ogni volta che viene ricevuto un push (ovvero nessun push silenzioso).

Al momento devi trasferire un valore pari a true. Se non includi la chiave userVisibleOnly o non passi false, verrà visualizzato il seguente errore:

Al momento, Chrome supporta l'API Push solo per gli abbonamenti che generano messaggi visibili all'utente. Puoi indicarlo chiamando pushManager.subscribe({userVisibleOnly: true}). Per maggiori dettagli, visita la pagina https://goo.gl/yqv4Q4.

Al momento sembra che le notifiche push silenziose generali non verranno mai implementate in Chrome. Gli autori delle specifiche stanno invece esplorando la nozione di API budget che consentirà alle app web un certo numero di messaggi push silenziosi in base all'utilizzo di un'app web.

Opzione applicationServerKey

Nella sezione precedente abbiamo accennato brevemente alle "chiavi del server delle applicazioni". Le "chiavi del server di applicazioni" vengono utilizzate da un servizio push per identificare l'applicazione che sottoscrive un utente e assicurarsi che sia la stessa applicazione a inviare messaggi all'utente.

Le chiavi del server dell'applicazione sono una coppia di chiavi pubblica e privata univoche per la tua applicazione. La chiave privata deve essere mantenuta segreta per l'applicazione, mentre la chiave pubblica può essere condivisa liberamente.

L'opzione applicationServerKey passata nella chiamata subscribe() è la chiave pubblica dell'applicazione. Il browser lo passa a un servizio push quando l'utente si iscrive, il che significa che il servizio push può associare la chiave pubblica della tua applicazione al PushSubscription dell'utente.

Il diagramma seguente illustra questi passaggi.

  1. L'app web viene caricata in un browser e tu chiami subscribe(), passando la chiave server delle applicazioni pubblica.
  2. Il browser invia quindi una richiesta di rete a un servizio push che genera un endpoint, lo associa alla chiave pubblica dell'applicazione e lo restituisce al browser.
  3. Il browser aggiungerà questo endpoint a PushSubscription, che viene restituito tramite la promessa subscribe().

L'illustrazione della chiave pubblica del server dell'applicazione viene utilizzata nel metodo subscribe.

Quando in un secondo momento vuoi inviare un messaggio push, dovrai creare un'intestazione Authorization che conterrà le informazioni firmate con la chiave privata del server dell'applicazione. Quando il servizio push riceve una richiesta di invio di un messaggio push, può convalidare questa intestazione Authorization firmata cercando la chiave pubblica collegata all'endpoint che riceve la richiesta. Se la firma è valida, il servizio push sa che deve provenire dal server dell'applicazione con la chiave privata corrispondente. Si tratta essenzialmente di una misura di sicurezza che impedisce a chiunque altro di inviare messaggi agli utenti di un'applicazione.

Come viene utilizzata la chiave privata del server di applicazioni durante l'invio di un messaggio

Tecnicamente, applicationServerKey è facoltativo. Tuttavia, l'implementazione più semplice su Chrome lo richiede e altri browser potrebbero richiederlo in futuro. È facoltativo su Firefox.

La specifica che definisce che cosa deve essere la chiave del server dell'applicazione è la specifica VAPID. Ogni volta che leggi qualcosa che fa riferimento a "chiavi del server dell'applicazione" o "chiavi VAPID", ricorda che si tratta della stessa cosa.

Come creare chiavi server delle applicazioni

Puoi creare un insieme pubblico e privato di chiavi del server di applicazioni visitando web-push-codelab.glitch.me oppure puoi utilizzare la riga di comando web-push per generare le chiavi nel seguente modo:

    $ npm install -g web-push
    $ web-push generate-vapid-keys

Devi creare queste chiavi una sola volta per la tua applicazione, ma assicurati di mantenere privata la chiave privata. (Sì, l'ho appena detto.)

Autorizzazioni e subscribe()

La chiamata a subscribe() ha un effetto collaterale. Se la tua app web non dispone delle autorizzazioni per mostrare le notifiche quando chiami subscribe(), il browser richiederà le autorizzazioni per te. Ciò è utile se la tua UI funziona con questo flusso, ma se vuoi un maggiore controllo (come penso che la maggior parte degli sviluppatori lo farà), attieniti all'API Notification.requestPermission() che abbiamo utilizzato in precedenza.

Che cos'è un PushSubscription?

Chiamiamo subscribe(), trasmettiamo alcune opzioni e in cambio otteniamo una promessa che si risolve in un PushSubscription generando codice come questo:

function subscribeUserToPush() {
  return navigator.serviceWorker
    .register('/service-worker.js')
    .then(function (registration) {
      const subscribeOptions = {
        userVisibleOnly: true,
        applicationServerKey: urlBase64ToUint8Array(
          'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U',
        ),
      };

      return registration.pushManager.subscribe(subscribeOptions);
    })
    .then(function (pushSubscription) {
      console.log(
        'Received PushSubscription: ',
        JSON.stringify(pushSubscription),
      );
      return pushSubscription;
    });
}

L'oggetto PushSubscription contiene tutte le informazioni necessarie per inviare un messaggio push all'utente. Se stampi i contenuti utilizzando JSON.stringify(), vedrai quanto segue:

    {
      "endpoint": "https://some.pushservice.com/something-unique",
      "keys": {
        "p256dh":
    "BIPUL12DLfytvTajnryr2PRdAgXS3HGKiLqndGcJGabyhHheJYlNGCeXl1dn18gSJ1WAkAPIxr4gK0_dQds4yiI=",
        "auth":"FPssNDTKnInHVndSTdbKFw=="
      }
    }

endpoint è l'URL dei servizi push. Per attivare un messaggio push, invia una richiesta POST a questo URL.

L'oggetto keys contiene i valori utilizzati per criptare i dati dei messaggi inviati con un messaggio push (di cui parleremo più avanti in questa sezione).

Rinnovo regolare dell'abbonamento per evitare la scadenza

Quando ti iscrivi alle notifiche push, spesso ricevi un PushSubscription.expirationTime di null. In teoria, questo significa che l'abbonamento non scade mai (a differenza di quando ricevi un DOMHighResTimeStamp, che ti indica il momento esatto in cui scade l'abbonamento). In pratica, però, è normale che i browser lascino scadere le iscrizioni, ad esempio se non sono state ricevute notifiche push per un periodo di tempo più lungo o se il browser rileva che l'utente non utilizza un'app che dispone dell'autorizzazione per le notifiche push. Un pattern per evitare questo problema è riabbonarlo a ogni notifica ricevuta, come mostrato nello snippet seguente. Ciò richiede che le notifiche vengano inviate con una frequenza sufficiente a far sì che il browser non scada automaticamente l'abbonamento e dovresti valutare con molta attenzione i vantaggi e gli svantaggi delle legittime richieste di notifica rispetto all'invio involontario di spam all'utente solo per evitare che l'abbonamento scada. In fin dei conti, non dovresti cercare di contrastare il browser nel suo tentativo di proteggere l'utente da iscrizioni a notifiche dimenticate da tempo.

/* In the Service Worker. */

self.addEventListener('push', function(event) {
  console.log('Received a push message', event);

  // Display notification or handle data
  // Example: show a notification
  const title = 'New Notification';
  const body = 'You have new updates!';
  const icon = '/images/icon.png';
  const tag = 'simple-push-demo-notification-tag';

  event.waitUntil(
    self.registration.showNotification(title, {
      body: body,
      icon: icon,
      tag: tag
    })
  );

  // Attempt to resubscribe after receiving a notification
  event.waitUntil(resubscribeToPush());
});

function resubscribeToPush() {
  return self.registration.pushManager.getSubscription()
    .then(function(subscription) {
      if (subscription) {
        return subscription.unsubscribe();
      }
    })
    .then(function() {
      return self.registration.pushManager.subscribe({
        userVisibleOnly: true,
        applicationServerKey: urlBase64ToUint8Array('YOUR_PUBLIC_VAPID_KEY_HERE')
      });
    })
    .then(function(subscription) {
      console.log('Resubscribed to push notifications:', subscription);
      // Optionally, send new subscription details to your server
    })
    .catch(function(error) {
      console.error('Failed to resubscribe:', error);
    });
}

Inviare un abbonamento al tuo server

Una volta sottoscritto un abbonamento push, dovrai inviarlo al tuo server. Sta a te decidere come farlo, ma un piccolo suggerimento è utilizzare JSON.stringify() per estrarre tutti i dati necessari dall'oggetto subscription. In alternativa, puoi ottenere lo stesso risultato manualmente nel seguente modo:

const subscriptionObject = {
  endpoint: pushSubscription.endpoint,
  keys: {
    p256dh: pushSubscription.getKeys('p256dh'),
    auth: pushSubscription.getKeys('auth'),
  },
};

// The above is the same output as:

const subscriptionObjectToo = JSON.stringify(pushSubscription);

L'invio dell'iscrizione viene effettuato nella pagina web in questo modo:

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 di nodi riceve questa richiesta e salva i dati in un database per utilizzarli in un secondo momento.

app.post('/api/save-subscription/', function (req, res) {
  if (!isValidSaveRequest(req, res)) {
    return;
  }

  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.',
          },
        }),
      );
    });
});

Con i dettagli di PushSubscription sul nostro server, possiamo inviare un messaggio all'utente quando vogliamo.

Ripetizione regolare dell'abbonamento per evitare la scadenza

Quando ti abboni alle notifiche push, spesso ricevi un PushSubscription.expirationTime di null. In teoria, questo significa che l'abbonamento non scade mai (a differenza di quando ricevi un DOMHighResTimeStamp, che ti indica il momento esatto in cui scade l'abbonamento). In pratica, però, è normale che i browser lascino scadere le iscrizioni, ad esempio se non vengono ricevute notifiche push per molto tempo o se il browser rileva che l'utente non utilizza l'app che ha l'autorizzazione per le notifiche push. Un metodo per evitare che ciò accada è ripetere l'iscrizione dell'utente a ogni notifica ricevuta, come mostrato nello snippet seguente. Ciò richiede di inviare notifiche con una frequenza sufficiente per evitare che il browser abbia annullato automaticamente l'abbonamento e devi valutare molto attentamente i vantaggi e gli svantaggi delle esigenze di notifica legittime rispetto all'invio di spam all'utente solo per evitare che l'abbonamento scada. In definitiva, non dovresti cercare di contrastare il browser nel suo tentativo di proteggere l'utente da iscrizioni alle notifiche dimenticate da tempo.

/* In the Service Worker. */

self.addEventListener('push', function(event) {
  console.log('Received a push message', event);

  // Display notification or handle data
  // Example: show a notification
  const title = 'New Notification';
  const body = 'You have new updates!';
  const icon = '/images/icon.png';
  const tag = 'simple-push-demo-notification-tag';

  event.waitUntil(
    self.registration.showNotification(title, {
      body: body,
      icon: icon,
      tag: tag
    })
  );

  // Attempt to resubscribe after receiving a notification
  event.waitUntil(resubscribeToPush());
});

function resubscribeToPush() {
  return self.registration.pushManager.getSubscription()
    .then(function(subscription) {
      if (subscription) {
        return subscription.unsubscribe();
      }
    })
    .then(function() {
      return self.registration.pushManager.subscribe({
        userVisibleOnly: true,
        applicationServerKey: urlBase64ToUint8Array('YOUR_PUBLIC_VAPID_KEY_HERE')
      });
    })
    .then(function(subscription) {
      console.log('Resubscribed to push notifications:', subscription);
      // Optionally, send new subscription details to your server
    })
    .catch(function(error) {
      console.error('Failed to resubscribe:', error);
    });
}

Domande frequenti

Ecco alcune domande comuni che le persone hanno posto a questo punto:

Posso cambiare il servizio push utilizzato da un browser?

No. Il servizio push viene selezionato dal browser e, come abbiamo visto con la chiamata a subscribe(), il browser effettuerà delle richieste di rete al servizio push per recuperare i dettagli che compongono la PushSubscription.

Ogni browser utilizza un servizio push diverso. Non hanno API differenti?

Tutti i servizi push prevedono la stessa API.

Questa API comune è denominata Web Push Protocol e descrive la richiesta di rete che l'applicazione dovrà effettuare per attivare un messaggio push.

Se un utente si iscrive da un computer, l'abbonamento viene applicato anche sullo smartphone?

Purtroppo no. Un utente deve registrarsi per il push su ogni browser su cui desidera ricevere i messaggi. In questo modo, l'utente dovrà concedere l'autorizzazione su ogni dispositivo.

Passaggi successivi

Codelab