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 come funziona il flusso di logica.

Rilevamento di funzionalità

Innanzitutto dobbiamo verificare se il browser attuale supporta effettivamente la messaggistica push. Possiamo verificare se la funzionalità push è supportata con due semplici controlli.

  1. Controlla se è presente serviceWorker su navigator.
  2. Cerca PushManager in window.
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 i messaggi push, è sempre una buona idea rilevare le funzionalità per entrambi e migliorarle progressivamente.

Registra un service worker

Con il rilevamento delle funzionalità sappiamo che sia i service worker che i push sono supportati. 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. Dietro le quinte, 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 al restituire una promessa. Il problema è che non possiamo sapere quale versione dell'API è implementata dal browser corrente, quindi devi implementarle entrambe e gestirle 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 mostra un messaggio 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, riceveremo il risultato sotto forma di stringa: 'granted', 'default' o 'denied'.

Nel codice di esempio riportato sopra, la promessa restituita da askPermission() viene risolta se l'autorizzazione viene concessa, altrimenti viene generato un errore che causa il rifiuto della promessa.

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. Dovranno "sbloccare" manualmente la tua app modificando il relativo 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 nostro service worker e ottenuta l'autorizzazione, possiamo abbonare 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 senza che l'utente lo sapesse.

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 rappresenta 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 passare un valore di 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. Invece, gli autori delle specifiche stanno esplorando il concetto di un'API budget che consentirà alle app web un determinato numero di messaggi push silenziosi in base all'utilizzo di un'app web.

Opzione applicationServerKey

Abbiamo brevemente accennato alle "chiavi del server delle applicazioni" nella sezione precedente. 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 alla 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. La tua app web viene caricata in un browser e chiami subscribe(), passando la chiave del server dell'applicazione 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 sostanzialmente 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 dell'applicazione 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 del 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 al momento della chiamata a subscribe(), il browser le richiederà per te. Questo è utile se la tua UI funziona con questo flusso, ma se vuoi un maggiore controllo (e penso che la maggior parte degli sviluppatori lo voglia), mantieni l'API Notification.requestPermission() che abbiamo utilizzato in precedenza.

Che cos'è un PushSubscription?

Chiamiamo subscribe(), passiamo alcune opzioni e in cambio riceviamo una promessa che si risolve in un PushSubscription, generando un 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).

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 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 di inviare notifiche con una frequenza sufficiente per evitare che il browser abbia fatto scadere automaticamente l'abbonamento e devi valutare molto attentamente i vantaggi e gli svantaggi delle esigenze di notifica legittime rispetto all'invio involontario 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);
    });
}

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'abbonamento viene eseguito nella pagina web nel seguente 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 pattern per evitare questo problema è riabbonarlo 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 chiamatasubscribe(), il browser invia richieste di rete al servizio push per recuperare i dettagli che compongono PushSubscription.

Ogni browser utilizza un servizio push diverso, non hanno API diverse?

Tutti i servizi push prevedono la stessa API.

Questa API comune si chiama Web Push Protocol e descrive la richiesta di rete che la tua 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 le notifiche push su ogni browser su cui vuole ricevere messaggi. Inoltre, è importante notare che l'utente dovrà concedere l'autorizzazione su ogni dispositivo.

Passaggi successivi

Codelab