В некоторых случаях веб-приложению может потребоваться установить двусторонний канал связи между страницей и сервис-воркером.
Например: в PWA подкаста можно создать функцию, позволяющую пользователю загружать эпизоды для использования в автономном режиме , а также позволить сервисному работнику регулярно информировать страницу о ходе выполнения, чтобы основной поток мог обновлять пользовательский интерфейс.
В этом руководстве мы рассмотрим различные способы реализации двусторонней связи между контекстом Window и 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
, представляющие активные вкладки, которыми управляет сервисный работник.
Поскольку страницей управляет один сервис-воркер, она прослушивает и отправляет сообщения активному сервис-воркеру напрямую через интерфейс 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'});
}
});
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-адресов для кэширования, рассмотренные до сих пор варианты являются хорошим выбором. Если задача занимает слишком много времени, браузер уничтожит сервис-воркера, в противном случае это поставит под угрозу конфиденциальность пользователя и аккумулятор.
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}%`);
});
Следующие шаги
В этом руководстве мы рассмотрели наиболее общий случай взаимодействия между страницами и службами (двусторонняя связь).
Во многих случаях для связи с другим может потребоваться только один контекст без получения ответа. Ознакомьтесь со следующими руководствами, чтобы узнать, как реализовать однонаправленные методы на ваших страницах от и до сервис-воркера, а также варианты использования и производственные примеры:
- Руководство по обязательному кэшированию : вызов сервисного работника со страницы для предварительного кэширования ресурсов (например, в сценариях предварительной выборки).
- Широковещательные обновления : вызов страницы со стороны сервисного работника, чтобы сообщить о важных обновлениях (например, доступна новая версия веб-приложения).