In alcuni casi, un'app web potrebbe dover stabilire un canale di comunicazione bidirezionale tra la pagina e il service worker.
Ad esempio, in una PWA di podcast si potrebbe creare una funzionalità che consenta all'utente di scaricare episodi per la fruizione offline e consentire al service worker di informare regolarmente la pagina sullo stato di avanzamento, in modo che il thread principale possa aggiornare la UI.
In questa guida esploreremo i diversi modi per implementare una comunicazione bidirezionale tra il contesto della finestra e del service worker, esaminando diverse API, la libreria Workbox e alcuni casi avanzati.

Utilizzo di Workbox
workbox-window
è un insieme di
moduli della libreria Workbox che sono destinati
a essere eseguiti nel contesto della finestra. La classe Workbox
fornisce un metodo messageSW()
per inviare un messaggio al service worker registrato dell'istanza e
attendere una risposta.
Il seguente codice di pagina crea una nuova istanza Workbox
e invia un messaggio al service worker
per ottenere la sua 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 all'altra estremità e risponde al service worker registrato:
const SW_VERSION = '1.0.0';
self.addEventListener('message', (event) => {
if (event.data.type === 'GET_VERSION') {
event.ports[0].postMessage(SW_VERSION);
}
});
Sotto il cofano, la libreria utilizza un'API del browser che esamineremo nella sezione successiva: Message Channel, ma astrae molti dettagli di implementazione, rendendola più facile da usare, sfruttando al contempo l'ampio supporto del browser di questa API.

Utilizzo delle API del browser
Se la libreria Workbox non è sufficiente per le tue esigenze, sono disponibili diverse API di livello inferiore per implementare la comunicazione "bidirezionale" tra pagine e service worker. Presentano alcune analogie e differenze:
Analogie:
- In tutti i casi, la comunicazione inizia da un'estremità tramite l'interfaccia
postMessage()
e viene ricevuta dall'altra estremità implementando un gestoremessage
. - In pratica, tutte le API disponibili ci consentono di implementare gli stessi casi d'uso, ma alcune di queste potrebbero semplificare lo sviluppo in alcuni scenari.
Differenze:
- Hanno modi diversi di identificare l'altra parte della comunicazione: alcuni utilizzano un riferimento esplicito all'altro contesto, mentre altri possono comunicare implicitamente tramite un oggetto proxy istanziato su ciascuna parte.
- Il supporto del browser varia a seconda del sistema operativo.

API Broadcast Channel
L'API Broadcast Channel consente la comunicazione di base tra i contesti di navigazione tramite oggetti BroadcastChannel.
Per implementarlo, innanzitutto ogni contesto deve creare un oggetto BroadcastChannel
con lo stesso ID
e inviare e ricevere messaggi da questo oggetto:
const broadcast = new BroadcastChannel('channel-123');
L'oggetto BroadcastChannel espone un'interfaccia postMessage()
per inviare un messaggio a qualsiasi contesto di ascolto:
//send message
broadcast.postMessage({ type: 'MSG_ID', });
Qualsiasi contesto del browser può ascoltare i messaggi tramite il metodo onmessage
dell'oggetto BroadcastChannel
:
//listen to messages
broadcast.onmessage = (event) => {
if (event.data && event.data.type === 'MSG_ID') {
//process message...
}
};
Come si vede, non esiste alcun riferimento esplicito a un contesto particolare, quindi non è necessario ottenere un riferimento al service worker o a un client specifico.

Lo svantaggio è che, al momento della stesura di questo articolo, l'API è supportata da Chrome, Firefox ed Edge, ma altri browser, come Safari, non la supportano ancora.
API client
L'API client consente di ottenere un riferimento a tutti gli oggetti WindowClient
che rappresentano le schede attive controllate dal service worker.
Poiché la pagina è controllata da un singolo service worker, ascolta e invia messaggi al 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 dei suoi client, il service worker ottiene un array di oggetti
WindowClient
eseguendo metodi come
Clients.matchAll()
e
Clients.get()
. Poi può
postMessage()
uno qualsiasi di questi:
//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'});
}
});

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 suoi metodi potrebbero essere disponibili, quindi assicurati di controllare il supporto del browser prima di
implementarla nel tuo sito.
Canale messaggi
Message Channel richiede la definizione e il passaggio di una porta da un contesto all'altro per stabilire un canale di comunicazione bidirezionale.
Per inizializzare il canale, la pagina crea un oggetto MessageChannel
e lo utilizza
per inviare una porta al service worker registrato. La pagina implementa anche un listener onmessage
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
};

Il service worker riceve la porta, ne salva un riferimento e lo utilizza per inviare un messaggio all'altra parte:
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.
API avanzate: sincronizzazione in background e recupero dello sfondo
In questa guida abbiamo esplorato i modi per implementare tecniche di comunicazione bidirezionale, per casi relativamente semplici, come il passaggio di un messaggio stringa che descrive l'operazione da eseguire o un elenco di URL da memorizzare nella cache da un contesto all'altro. In questa sezione esploreremo due API per gestire scenari specifici: mancanza di connettività e download lunghi.
Sincronizzazione in background
Un'app di chat potrebbe voler assicurarsi che i messaggi non vengano mai persi a causa di una connettività scadente. L'API Background Sync consente di posticipare le azioni da riprovare quando l'utente ha una connettività stabile. Ciò è utile per garantire che qualsiasi cosa l'utente voglia inviare venga effettivamente inviata.
Anziché l'interfaccia postMessage()
, la pagina registra un sync
:
navigator.serviceWorker.ready.then(function (swRegistration) {
return swRegistration.sync.register('myFirstSync');
});
Il service worker ascolta quindi l'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 dell'operazione
che sta tentando di eseguire. Se la promessa viene soddisfatta, la sincronizzazione è completata. Se non riesce, verrà pianificata un'altra sincronizzazione per
riprovare. I nuovi tentativi di sincronizzazione attendono anche la connettività e utilizzano un backoff esponenziale.
Una volta eseguita l'operazione, il service worker può comunicare di nuovo con la pagina per aggiornare la UI utilizzando una delle API di comunicazione esaminate in precedenza.
La Ricerca Google utilizza la sincronizzazione in background per rendere persistenti le query non riuscite a causa di una connettività scadente e riprovarle in un secondo momento quando l'utente è online. Una volta eseguita l'operazione, comunicano il risultato all'utente tramite una notifica push web:

Recupero dello sfondo
Per attività relativamente brevi, come l'invio di un messaggio o un elenco di URL da memorizzare nella cache, le opzioni esplorate finora sono una buona scelta. Se l'attività richiede troppo tempo, il browser interromperà il service worker, altrimenti la privacy e la batteria dell'utente sono a rischio.
L'API Background Fetch consente di scaricare un'attività lunga su un service worker, ad esempio il download di film, podcast o livelli di un gioco.
Per comunicare con il service worker dalla pagina, utilizza 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
per seguire
l'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}%`);
});

Passaggi successivi
In questa guida abbiamo esplorato il caso più generale di comunicazione tra service worker e pagine (comunicazione bidirezionale).
Molte volte, potrebbe essere necessario un solo contesto per comunicare con l'altro, senza ricevere una risposta. Consulta le seguenti guide per indicazioni su come implementare tecniche unidirezionali nelle tue pagine da e verso il service worker, insieme a casi d'uso ed esempi di produzione:
- Guida alla memorizzazione nella cache imperativa: chiama un service worker dalla pagina per memorizzare nella cache le risorse in anticipo (ad es. negli scenari di prefetching).
- Aggiornamenti di trasmissione: chiama la pagina dal service worker per comunicare aggiornamenti importanti (ad es. è disponibile una nuova versione della web app).