Comunicazione bidirezionale con i service worker

Andrew Guan
Andrew Guan
Demián Renzulli
Demián Renzulli

In alcuni casi, un'app web potrebbe dover stabilire un canale di comunicazione doppio tra e il service worker.

Ad esempio, nella PWA di un podcast si potrebbe creare una funzionalità che consenta all'utente di scaricare le puntate per il consumo offline e consentire al service worker di mantenere la pagina regolarmente informata dell'avanzamento, in modo che il thread può aggiornare la UI.

In questa guida esploreremo i diversi modi per implementare una comunicazione relativa tra Window e service worker, esplorando il contesto API diverse, la libreria Workbox alcuni casi avanzati.

Diagramma che mostra un service worker e la pagina che scambiano messaggi.

Utilizzo di Workbox

workbox-window è un insieme di della libreria Workbox previsti per l'esecuzione nel contesto della finestra. La Workbox fornisce un metodo messageSW() per inviare un messaggio al service worker registrato dell'istanza e e attendere una risposta.

Il seguente codice di pagina crea una nuova istanza Workbox e invia un messaggio al service worker per ottenerne la versione:

const wb = new Workbox('/sw.js');
wb.register();

const swVersion = await wb.messageSW({type: 'GET_VERSION'});
console.log('Service Worker version:', swVersion);

Il service worker implementa un listener di messaggi dall'altra parte e risponde all'input registrato service worker:

const SW_VERSION = '1.0.0';

self.addEventListener('message', (event) => {
  if (event.data.type === 'GET_VERSION') {
    event.ports[0].postMessage(SW_VERSION);
  }
});

All'inizio, la libreria utilizza un'API del browser che esamineremo nella prossima sezione: Messaggio Channel, ma astrae molti i dettagli dell'implementazione, semplificando l'utilizzo e sfruttando al contempo il browser largo supportato dell'API.

Diagramma che mostra la comunicazione bidirezionale tra la pagina e il service worker, utilizzando la finestra della casella di lavoro.

Utilizzo delle API del browser

Se la libreria Workbox non è sufficiente per le tue esigenze, sono disponibili diverse API di livello inferiore implementare la comunicazione "due vie" tra le pagine e i service worker. Hanno alcune somiglianze e le differenze:

Analogie:

  • In tutti i casi la comunicazione inizia da un lato tramite l'interfaccia di postMessage() e viene ricevuta dall'altra parte implementando un gestore message.
  • In pratica, tutte le API disponibili ci consentono di implementare gli stessi casi d'uso, ma alcuni può semplificare lo sviluppo in alcuni scenari.

Differenze:

  • Hanno diversi modi per identificare l'altro lato della comunicazione: alcuni usano riferimento esplicito all'altro contesto, mentre gli altri possono comunicare implicitamente tramite un proxy su ciascun lato.
  • Il supporto del browser varia da un browser all'altro.
Diagramma che mostra la comunicazione bidirezionale tra la pagina e il service worker e le API browser disponibili.

API Broadcast Channel

Supporto dei browser

  • Chrome: 54.
  • Edge: 79.
  • Firefox: 38.
  • Safari: 15.4.

Origine

L'API Broadcast Channel consente la comunicazione di base tra contesti di navigazione tramite BroadcastChannel oggetti.

Per implementarlo, innanzitutto ogni contesto deve creare un'istanza di un oggetto BroadcastChannel con lo stesso ID e inviare e ricevere messaggi:

const broadcast = new BroadcastChannel('channel-123');

L'oggetto BroadcastChannel espone un'interfaccia postMessage() per inviare un messaggio a qualsiasi ascolto contesto:

//send message
broadcast.postMessage({ type: 'MSG_ID', });

Qualsiasi contesto del browser può ascoltare i messaggi con il metodo onmessage dell'BroadcastChannel :

//listen to messages
broadcast.onmessage = (event) => {
  if (event.data && event.data.type === 'MSG_ID') {
    //process message...
  }
};

Come abbiamo visto, non c'è alcun riferimento esplicito a un particolare contesto, quindi non c'è bisogno di ottenere una fare riferimento prima al service worker o a qualsiasi client specifico.

Diagramma che mostra la comunicazione bidirezionale tra la pagina e il service worker, utilizzando un oggetto Broadcast Channel.

Lo svantaggio è che, al momento della stesura del presente documento, l'API supporta Chrome, Firefox ed Edge, ma altri browser, come Safari, non lo supportano .

API client

Supporto dei browser

  • Chrome: 40.
  • Edge: 17.
  • Firefox: 44.
  • Safari: 11.1.

Origine

L'API client ti consente di ottenere riferimento a tutti gli oggetti WindowClient che rappresentano le schede attive controllate dal service worker.

Poiché la pagina è controllata da un singolo service worker, questa ascolta e invia messaggi a service worker attivo direttamente tramite l'interfaccia serviceWorker:

//send message
navigator.serviceWorker.controller.postMessage({
  type: 'MSG_ID',
});

//listen to messages
navigator.serviceWorker.onmessage = (event) => {
  if (event.data && event.data.type === 'MSG_ID') {
    //process response
  }
};

Allo stesso modo, il service worker ascolta i messaggi implementando un listener onmessage:

//listen to messages
self.addEventListener('message', (event) => {
  if (event.data && event.data.type === 'MSG_ID') {
    //Process message
  }
});

Per comunicare con uno qualsiasi dei suoi client, il service worker ottiene un array di WindowClient oggetti mediante l'esecuzione come quelli Clients.matchAll() e Clients.get() Può quindi postMessage() uno qualsiasi:

//Obtain an array of Window client objects
self.clients.matchAll(options).then(function (clients) {
  if (clients && clients.length) {
    //Respond to last focused tab
    clients[0].postMessage({type: 'MSG_ID'});
  }
});
Diagramma che mostra un service worker che comunica con un array di client.

Client API è una buona opzione per comunicare facilmente con tutte le schede attive di un service worker in modo relativamente semplice. L'API è supportata da tutti i principali browser, ma non tutti i metodi potrebbero essere disponibili, quindi assicurati di controllare il supporto del browser prima a implementarla sul tuo sito.

Canale messaggi

Supporto dei browser

  • Chrome: 2.
  • Edge: 12.
  • Firefox: 41.
  • Safari: 5.

Origine

Message Channel richiede la definizione e il passaggio di una porta da un contesto all'altro per stabilire una comunicazione doppio canale.

Per inizializzare il canale, la pagina crea un'istanza di un oggetto MessageChannel e lo utilizza per inviare una porta al service worker registrato. La pagina implementa anche un listener onmessage su per ricevere messaggi dall'altro contesto:

const messageChannel = new MessageChannel();

//Init port
navigator.serviceWorker.controller.postMessage({type: 'PORT_INITIALIZATION'}, [
  messageChannel.port2,
]);

//Listen to messages
messageChannel.port1.onmessage = (event) => {
  // Process message
};
Diagramma che mostra una pagina che passa una porta a un service worker per stabilire una comunicazione bidirezionale.

Il service worker riceve la porta, la salva un riferimento e lo utilizza per inviare un messaggio all'altro lato:

let communicationPort;

//Save reference to port
self.addEventListener('message', (event) => {
  if (event.data && event.data.type === 'PORT_INITIALIZATION') {
    communicationPort = event.ports[0];
  }
});

//Send messages
communicationPort.postMessage({type: 'MSG_ID'});

MessageChannel è attualmente supportato da tutti i principali browser supportati.

API avanzate: sincronizzazione e recupero in background

In questa guida abbiamo esplorato i modi per implementare tecniche di comunicazione due vie, relativamente casi semplici, come passare un messaggio in formato stringa che descriva l'operazione da eseguire o un elenco di URL memorizzare nella cache da un contesto all'altro. In questa sezione esploreremo due API per gestire scenari: assenza di connettività e download di lunga durata.

Sincronizzazione in background

Supporto dei browser

  • Chrome: 49.
  • Edge: 79.
  • Firefox: non supportato.
  • Safari: non supportato.

Origine

Un'app di chat potrebbe assicurarsi che i messaggi non vadano mai persi a causa di problemi di connettività. La L'API Background Sync consente di rimandare le azioni da riprovare quando la connettività dell'utente è stabile. Questo è utile per garantire viene inviato effettivamente ciò che l'utente vuole inviare.

Anziché l'interfaccia postMessage(), la pagina registra un sync:

navigator.serviceWorker.ready.then(function (swRegistration) {
  return swRegistration.sync.register('myFirstSync');
});

Il service worker rimane quindi in ascolto dell'evento sync per elaborare il messaggio:

self.addEventListener('sync', function (event) {
  if (event.tag == 'myFirstSync') {
    event.waitUntil(doSomeStuff());
  }
});

La funzione doSomeStuff() deve restituire una promessa che indica l'esito positivo o negativo di qualsiasi cosa cercando di fare. Se viene evaso, la sincronizzazione è completa. Se non riesce, verrà pianificata un'altra sincronizzazione su riprova. Anche i nuovi tentativi di sincronizzazione attendono la connettività e utilizzano un backoff esponenziale.

Una volta eseguita l'operazione, il service worker può comunicare con la pagina aggiornare la UI, utilizzando una qualsiasi delle API di comunicazione illustrate in precedenza.

La Ricerca Google utilizza la sincronizzazione in background per conservare le query non riuscite a causa di una cattiva connettività e riprovare in un secondo momento, quando l'utente sarà online. Una volta eseguita l'operazione, comunicano il risultato a l'utente tramite una notifica push web:

Diagramma che mostra una pagina che passa una porta a un service worker per stabilire una comunicazione bidirezionale.

Recupero in background

Supporto dei browser

  • Chrome: 74.
  • Edge: 79.
  • Firefox: non supportato.
  • Safari: non supportato.

Origine

Per operazioni relativamente brevi come l'invio di un messaggio o un elenco di URL da memorizzare nella cache, le opzioni esplorati finora sono una buona scelta. Se l'attività richiede troppo tempo, il browser terminerà il servizio personale, altrimenti rischia la privacy e la batteria dell'utente.

L'API Background Fetch ti consente di trasferire a un service worker un'attività lunga, come il download di film, podcast o livelli di un gioco.

Per comunicare con il service worker dalla pagina, usa backgroundFetch.fetch, anziché postMessage():

navigator.serviceWorker.ready.then(async (swReg) => {
  const bgFetch = await swReg.backgroundFetch.fetch(
    'my-fetch',
    ['/ep-5.mp3', 'ep-5-artwork.jpg'],
    {
      title: 'Episode 5: Interesting things.',
      icons: [
        {
          sizes: '300x300',
          src: '/ep-5-icon.png',
          type: 'image/png',
        },
      ],
      downloadTotal: 60 * 1024 * 1024,
    },
  );
});

L'oggetto BackgroundFetchRegistration consente alla pagina di ascoltare l'evento progress da seguire avanzamento del download:

bgFetch.addEventListener('progress', () => {
  // If we didn't provide a total, we can't provide a %.
  if (!bgFetch.downloadTotal) return;

  const percent = Math.round(
    (bgFetch.downloaded / bgFetch.downloadTotal) * 100,
  );
  console.log(`Download progress: ${percent}%`);
});
Diagramma che mostra una pagina che passa una porta a un service worker per stabilire una comunicazione bidirezionale.
La UI viene aggiornata per indicare l'avanzamento di un download (a sinistra). Grazie ai service worker, l'operazione può continuare dopo aver chiuso tutte le schede (a destra).
di Gemini Advanced.
.

Passaggi successivi

In questa guida abbiamo esplorato il caso più generale di comunicazione tra page e service worker (comunicazione bidirezionale).

Molte volte, è possibile che una persona abbia bisogno di un solo contesto per comunicare con l'altro, senza ricevere risposta. Consulta le seguenti guide per indicazioni su come implementare le tecniche unidirezionali nelle le tue pagine da e al service worker, insieme a casi d'uso ed esempi di produzione: