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

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

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

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

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