Двусторонняя связь с работниками службы

Эндрю Гуан
Andrew Guan

В некоторых случаях веб-приложению может потребоваться установить двусторонний канал связи между страницей и сервис-воркером.

Например: в 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.

Схема, показывающая двустороннюю связь между страницей и сервисным работником с использованием окна Workbox.

Использование API браузера

Если библиотеки Workbox недостаточно для ваших нужд, существует несколько API более низкого уровня для реализации «двусторонней» связи между страницами и сервис-воркерами. Они имеют некоторые сходства и различия:

Сходства:

  • Во всех случаях связь начинается на одном конце через интерфейс postMessage() и принимается на другом конце путем реализации обработчика message .
  • На практике все доступные API позволяют нам реализовать одни и те же варианты использования, но некоторые из них могут упростить разработку в некоторых сценариях.

Отличия:

  • У них есть разные способы идентификации другой стороны связи: некоторые из них используют явную ссылку на другой контекст, в то время как другие могут общаться неявно через прокси-объект, созданный на каждой стороне.
  • Поддержка браузеров варьируется между ними.
Схема, показывающая двустороннюю связь между страницей и сервисным работником, а также доступные API-интерфейсы браузера.

API широковещательного канала

Поддержка браузера

  • Хром: 54.
  • Край: 79.
  • Фаерфокс: 38.
  • Сафари: 15.4.

Источник

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.

Недостатком является то, что на момент написания этой статьи API поддерживает Chrome, Firefox и Edge, но другие браузеры, такие как Safari, его еще не поддерживают .

Клиентский API

Поддержка браузера

  • Хром: 40.
  • Край: 17.
  • Фаерфокс: 44.
  • Сафари: 11.1.

Источник

Клиентский 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 поддерживается всеми основными браузерами , но не все его методы могут быть доступны, поэтому обязательно проверьте поддержку браузера, прежде чем внедрять его на своем сайте.

Канал сообщений

Поддержка браузера

  • Хром: 2.
  • Край: 12.
  • Фаерфокс: 41.
  • Сафари: 5.

Источник

Канал сообщений требует определения и передачи порта из одного контекста в другой для установления двустороннего канала связи.

Чтобы инициализировать канал, страница создает экземпляр объекта 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 для обработки конкретных сценариев: отсутствие подключения и длительная загрузка.

Фоновая синхронизация

Поддержка браузера

  • Хром: 49.
  • Край: 79.
  • Firefox: не поддерживается.
  • Сафари: не поддерживается.

Источник

Приложение чата может захотеть гарантировать, что сообщения никогда не будут потеряны из-за плохого соединения. 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-уведомление:

Схема, показывающая страницу, передающую порт сервисному работнику для установления двусторонней связи.

Фоновая выборка

Поддержка браузера

  • Хром: 74.
  • Край: 79.
  • Firefox: не поддерживается.
  • Сафари: не поддерживается.

Источник

Для относительно коротких задач, таких как отправка сообщения или список 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}%`);
});
Схема, показывающая страницу, передающую порт сервисному работнику для установления двусторонней связи.
Пользовательский интерфейс обновлен, чтобы отображать ход загрузки (слева). Благодаря сервис-воркерам операция может продолжаться, даже когда все вкладки закрыты (справа).

Следующие шаги

В этом руководстве мы рассмотрели наиболее общий случай взаимодействия между страницами и службами (двусторонняя связь).

Во многих случаях для связи с другим может потребоваться только один контекст без получения ответа. Ознакомьтесь со следующими руководствами, чтобы узнать, как реализовать однонаправленные методы на ваших страницах от и до сервис-воркера, а также варианты использования и производственные примеры: