В некоторых случаях веб-приложению может потребоваться установить двусторонний канал связи между страницей и сервис-воркером.
Например: в PWA подкаста можно создать функцию, позволяющую пользователю загружать эпизоды для использования в автономном режиме , а также позволить сервисному работнику регулярно информировать страницу о ходе выполнения, чтобы основной поток мог обновлять пользовательский интерфейс.
В этом руководстве мы рассмотрим различные способы реализации двусторонней связи между контекстом Window и сервисного работника , изучая различные API, библиотеку Workbox , а также некоторые сложные случаи.
![Диаграмма, показывающая работника службы и страницу обмена сообщениями.](https://web.developers.google.cn/static/articles/two-way-communication-guide/image/diagram-showing-service-8bb189d1e9e40.png?authuser=7&hl=ru)
Использование 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.
![Схема, показывающая двустороннюю связь между страницей и сервисным работником с использованием окна Workbox.](https://web.developers.google.cn/static/articles/two-way-communication-guide/image/diagram-showing-way-comm-3573ad75f3cd1.png?authuser=7&hl=ru)
Использование API браузера
Если библиотеки Workbox недостаточно для ваших нужд, существует несколько API более низкого уровня для реализации «двусторонней» связи между страницами и сервис-воркерами. Они имеют некоторые сходства и различия:
Сходства:
- Во всех случаях связь начинается на одном конце через интерфейс
postMessage()
и принимается на другом конце путем реализации обработчикаmessage
. - На практике все доступные API позволяют нам реализовать одни и те же варианты использования, но некоторые из них могут упростить разработку в некоторых сценариях.
Отличия:
- У них есть разные способы идентификации другой стороны связи: некоторые из них используют явную ссылку на другой контекст, в то время как другие могут общаться неявно через прокси-объект, созданный на каждой стороне.
- Поддержка браузеров варьируется между ними.
![Схема, показывающая двустороннюю связь между страницей и сервисным работником, а также доступные API-интерфейсы браузера.](https://web.developers.google.cn/static/articles/two-way-communication-guide/image/diagram-showing-way-comm-7b2a46c7cc95.png?authuser=7&hl=ru)
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...
}
};
Как видно, явная ссылка на конкретный контекст отсутствует, поэтому нет необходимости сначала получать ссылку на сервис-воркера или какой-либо конкретный клиент.
![Схема, показывающая двустороннюю связь между страницей и работником службы с использованием объекта Broadcast Channel.](https://web.developers.google.cn/static/articles/two-way-communication-guide/image/diagram-showing-way-comm-d328101990fe9.png?authuser=7&hl=ru)
Недостатком является то, что на момент написания этой статьи API поддерживает Chrome, Firefox и Edge, но другие браузеры, такие как Safari, его еще не поддерживают .
Клиентский API
Клиентский API позволяет получить ссылку на все объекты WindowClient
, представляющие активные вкладки, которыми управляет сервисный работник.
Поскольку страницей управляет один сервис-воркер, она прослушивает и отправляет сообщения активному сервис-воркеру напрямую через интерфейс 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
}
});
Чтобы связаться с любым из своих клиентов, сервис-воркер получает массив объектов 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'});
}
});
![Диаграмма, показывающая работника службы, общающегося с массивом клиентов.](https://web.developers.google.cn/static/articles/two-way-communication-guide/image/diagram-showing-service-6eb1e0aa007c2.png?authuser=7&hl=ru)
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
};
![Схема, показывающая страницу, передающую порт сервисному работнику для установления двусторонней связи.](https://web.developers.google.cn/static/articles/two-way-communication-guide/image/diagram-showing-page-pas-57eb808e284e1.png?authuser=7&hl=ru)
Сервис-воркер получает порт, сохраняет ссылку на него и использует его для отправки сообщения другой стороне:
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-уведомление:
![Схема, показывающая страницу, передающую порт сервисному работнику для установления двусторонней связи.](https://web.developers.google.cn/static/articles/two-way-communication-guide/image/diagram-showing-page-pas-4ed0ed40b8bc1.png?authuser=7&hl=ru)
Фоновая выборка
Для относительно коротких задач, таких как отправка сообщения или список URL-адресов для кэширования, рассмотренные до сих пор варианты являются хорошим выбором. Если задача занимает слишком много времени, браузер убьет работника службы, в противном случае это поставит под угрозу конфиденциальность пользователя и аккумулятор.
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}%`);
});
![Схема, показывающая страницу, передающую порт сервисному работнику для установления двусторонней связи.](https://web.developers.google.cn/static/articles/two-way-communication-guide/image/diagram-showing-page-pas-e7b1a5ce84bde.png?authuser=7&hl=ru)
Следующие шаги
В этом руководстве мы рассмотрели наиболее общий случай связи между страницами и службами (двусторонняя связь).
Во многих случаях для связи с другим может потребоваться только один контекст без получения ответа. Ознакомьтесь со следующими руководствами, чтобы узнать, как реализовать однонаправленные методы на ваших страницах от и до сервис-воркера, а также варианты использования и производственные примеры:
- Руководство по обязательному кэшированию : вызов сервисного работника со страницы для предварительного кэширования ресурсов (например, в сценариях предварительной выборки).
- Широковещательные обновления : вызов страницы из сервисного работника, чтобы сообщить о важных обновлениях (например, доступна новая версия веб-приложения).