В некоторых случаях веб-приложению может потребоваться установить двусторонний канал связи между страницей и сервис-воркером.
Например: в подкасте PWA можно создать функцию, позволяющую пользователю загружать эпизоды для просмотра в автономном режиме и позволяющую сервисному работнику регулярно информировать страницу о ходе процесса, чтобы основной поток мог обновлять пользовательский интерфейс.
В этом руководстве мы рассмотрим различные способы реализации двусторонней связи между контекстом окна и Service Worker , изучив различные API, библиотеку Workbox , а также некоторые сложные случаи.

Использование Workbox
workbox-window
— это набор модулей библиотеки Workbox , которые предназначены для запуска в контексте окна. Класс Workbox
предоставляет метод messageSW()
для отправки сообщения зарегистрированному сервисному работнику экземпляра и ожидания ответа.
Следующий код страницы создает новый экземпляр Workbox
и отправляет сообщение сервисному работнику для получения его версии:
const wb = new Workbox('/sw.js');
wb.register();
const swVersion = await wb.messageSW({type: 'GET_VERSION'});
console.log('Service Worker version:', swVersion);
Сервисный работник реализует прослушиватель сообщений на другом конце и отвечает зарегистрированному сервисному работнику:
const SW_VERSION = '1.0.0';
self.addEventListener('message', (event) => {
if (event.data.type === 'GET_VERSION') {
event.ports[0].postMessage(SW_VERSION);
}
});
Под капотом библиотека использует API браузера, который мы рассмотрим в следующем разделе: Канал сообщений , но абстрагирует многие детали реализации, что упрощает ее использование, используя при этом широкую поддержку этого API браузерами .

Использование API браузера
Если библиотеки Workbox недостаточно для ваших нужд, есть несколько API более низкого уровня, которые позволяют реализовать «двустороннюю» связь между страницами и сервис-воркерами. У них есть некоторые сходства и различия:
Сходства:
- Во всех случаях связь начинается на одном конце через интерфейс
postMessage()
и принимается на другом конце путем реализации обработчикаmessage
. - На практике все доступные API позволяют нам реализовывать одни и те же варианты использования, но некоторые из них могут упростить разработку в некоторых сценариях.
Различия:
- У них есть разные способы идентификации другой стороны коммуникации: некоторые из них используют явную ссылку на другой контекст, в то время как другие могут общаться неявно через прокси-объект, созданный на каждой стороне.
- Поддержка браузеров различается.

API вещательного канала
API Broadcast Channel обеспечивает базовую связь между контекстами просмотра через объекты BroadcastChannel .
Чтобы реализовать это, во-первых, каждый контекст должен создать экземпляр объекта BroadcastChannel
с тем же идентификатором и отправлять и получать от него сообщения:
const broadcast = new BroadcastChannel('channel-123');
Объект BroadcastChannel предоставляет интерфейс postMessage()
для отправки сообщения в любой контекст прослушивания:
//send message
broadcast.postMessage({ type: 'MSG_ID', });
Любой контекст браузера может прослушивать сообщения через метод onmessage
объекта BroadcastChannel
:
//listen to messages
broadcast.onmessage = (event) => {
if (event.data && event.data.type === 'MSG_ID') {
//process message...
}
};
Как видно, нет явной ссылки на конкретный контекст, поэтому нет необходимости сначала получать ссылку на сервис-воркера или какого-либо конкретного клиента.

Недостатком является то, что на момент написания этой статьи API поддерживается Chrome, Firefox и Edge, но другие браузеры, такие как Safari, пока его не поддерживают .
Клиентский API
Клиентский API позволяет получить ссылку на все объекты WindowClient
, представляющие активные вкладки, которыми управляет Service Worker.
Поскольку страница контролируется одним сервисным работником, она прослушивает и отправляет сообщения активному сервисному работнику напрямую через интерфейс 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
}
};
Аналогично, сервисный работник прослушивает сообщения, реализуя прослушиватель onmessage
:
//listen to messages
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'MSG_ID') {
//Process message
}
});
Чтобы связаться с любым из своих клиентов, service worker получает массив объектов WindowClient
, выполняя такие методы, как Clients.matchAll()
и Clients.get()
. Затем он может postMessage()
любого из них:
//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
— это хороший вариант для легкого взаимодействия со всеми активными вкладками из сервисного работника относительно простым способом. API поддерживается всеми основными браузерами , но не все его методы могут быть доступны, поэтому обязательно проверьте поддержку браузера перед его реализацией на своем сайте.
Канал сообщений
Канал сообщений требует определения и передачи порта из одного контекста в другой для установления двустороннего канала связи.
Для инициализации канала страница создает экземпляр объекта MessageChannel
и использует его для отправки порта зарегистрированному сервисному работнику. Страница также реализует прослушиватель onmessage
для получения сообщений из другого контекста:
const messageChannel = new MessageChannel();
//Init port
navigator.serviceWorker.controller.postMessage({type: 'PORT_INITIALIZATION'}, [
messageChannel.port2,
]);
//Listen to messages
messageChannel.port1.onmessage = (event) => {
// Process message
};

Сервисный работник получает порт, сохраняет ссылку на него и использует ее для отправки сообщения на другую сторону:
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
поддерживается всеми основными браузерами .
Расширенные API: фоновая синхронизация и фоновая загрузка
В этом руководстве мы рассмотрели способы реализации методов двусторонней связи для относительно простых случаев, таких как передача строкового сообщения, описывающего операцию для выполнения, или списка URL-адресов для кэширования из одного контекста в другой. В этом разделе мы рассмотрим два API для обработки определенных сценариев: отсутствие подключения и длительные загрузки.
Фоновая синхронизация
Чат-приложению может потребоваться убедиться, что сообщения никогда не теряются из-за плохого соединения. API фоновой синхронизации позволяет откладывать действия для повторных попыток, когда у пользователя стабильное соединение. Это полезно для обеспечения того, чтобы все, что пользователь хочет отправить, действительно было отправлено.
Вместо интерфейса postMessage()
страница регистрирует sync
:
navigator.serviceWorker.ready.then(function (swRegistration) {
return swRegistration.sync.register('myFirstSync');
});
Затем сервисный работник прослушивает событие sync
для обработки сообщения:
self.addEventListener('sync', function (event) {
if (event.tag == 'myFirstSync') {
event.waitUntil(doSomeStuff());
}
});
Функция doSomeStuff()
должна возвращать обещание, указывающее на успешность/неудачу того, что она пытается сделать. Если оно выполняется, синхронизация завершена. Если она не выполняется, будет запланирована еще одна синхронизация для повторной попытки. Повторные синхронизации также ждут подключения и используют экспоненциальную задержку.
После выполнения операции сервисный работник может связаться со страницей для обновления пользовательского интерфейса, используя любой из рассмотренных ранее коммуникационных API.
Поиск Google использует фоновую синхронизацию для сохранения неудачных запросов из-за плохого соединения и повторения их позже, когда пользователь будет в сети. После выполнения операции они сообщают результат пользователю через push-уведомление:

Фоновая выборка
Для относительно коротких фрагментов работы, таких как отправка сообщения или списка URL-адресов для кэширования, рассмотренные до сих пор варианты являются хорошим выбором. Если задача занимает слишком много времени, браузер убьет service worker, в противном случае это риск для конфиденциальности пользователя и батареи.
API фоновой загрузки позволяет передать длительную задачу сервисному работнику, например, загрузку фильмов, подкастов или уровней игры.
Чтобы связаться с сервис-воркером со страницы, используйте backgroundFetch.fetch
вместо 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,
},
);
});
Объект BackgroundFetchRegistration
позволяет странице прослушивать событие progress
, чтобы следить за ходом загрузки:
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}%`);
});

Следующие шаги
В этом руководстве мы рассмотрели наиболее общий случай взаимодействия между исполнителями страниц и сервисов (двунаправленное взаимодействие).
Во многих случаях может потребоваться только один контекст для общения с другим, без получения ответа. Ознакомьтесь со следующими руководствами, чтобы узнать, как реализовать однонаправленные методы на ваших страницах от и к service worker, а также с вариантами использования и примерами производства:
- Руководство по обязательному кэшированию : вызов сервисного работника со страницы для предварительного кэширования ресурсов (например, в сценариях предварительной выборки).
- Трансляция обновлений : вызов страницы из Service Worker для информирования о важных обновлениях (например, о доступности новой версии веб-приложения).