Управление серверными событиями с помощью сервисного работника - PullRequest
0 голосов
/ 05 апреля 2020

Я создаю веб-приложение для отображения на своем iPad, чтобы управлять моим Raspberry Pi, выступающим в качестве диктофона. Частично необходимо поддерживать открытый источник событий, чтобы сервер мог отправлять события на стороне сервера. Определенный c экземпляр приложения может захватить контроль над процессом записи, но потеряет контроль, если сервер увидит, что sse-ссылка закрывается. Это всего лишь защита от исчезновения и оставления контроля над клиентом (контроль над процессом необходимо обновлять, по крайней мере, каждые 5 минут - но я действительно не хочу ждать так долго в обычном случае, когда кто-то просто закрывает браузер вкладка.)

Отчасти мне нужно перевести браузер sh в фоновый режим, чтобы я мог открыть камеру и записать видео.

Я собрал это приложение и получил его почти работает, см. https://github.com/akc42/pi_record.git (основная ветка).
Пока я не переместил браузер в фоновый режим и не обнаружил, что IOS закрыл страницу и сломал ссылку sse.

I попытался реструктурировать с использованием частного веб-работника для управления ссылками sse, скопив сообщения между веб-работником и основным потоком javascript - снова почти работающий (см. раздел «Работники» вышеупомянутого репозитория). Но это тоже отключилось!

Моя последняя мысль - использовать сервисного работника, но как структурировать приложение?

Очевидно, что работник службы должен выступать в качестве клиента для сервера для событий на стороне сервера. Он должен держать соединение открытым, но он также должен отслеживать несколько вкладок в браузере, которые могут или не могут пытаться захватить контроль над интерфейсом, и позволяют делать это только одной вкладке.

Я могу Подумайте о трех подходах - но трудно понять, что лучше. По крайней мере, я никогда не видел упоминаний о подходе 2 и 3 ниже, но мне кажется, что один из этих двух может быть самым простым.

Подход 1

Переместите код, который у меня сейчас есть, для отдельных веб-работников в сервисный работник. Однако нам нужно добавить к сообщению, передающему некоторую форму идентификатора между окном и сервисом. Поэтому я могу записать, какая вкладка фактически захватила контроль над интерфейсом и, следовательно, исключить другие вкладки из этого (ie имитирует неудачную попытку получить контроль).

Насколько я могу понять MessageEvent.ports [ 0] может быть уникальным объектом, который я мог бы хранить где-нибудь на карте, но я не совсем уверен, что MessageChannel не закроется, если браузер переместится в фоновый режим.

Подход 2

имеет набор фантомных URL-адресов в сервисном работнике, которые имитируют все различные типы сообщений (и параметры), которые ранее отправляли мою вкладку своему частному веб-работнику.

Событие выборки предоставляет клиентский код (который я могу использовать для различия между тем, кто на самом деле захватил контроль) и который я могу использовать для выполнения Clients.get(clientid).postMessage() (или Clients.matchAll, когда требуется широковещательный ответ)

Код будет выглядеть примерно так

self.addEventListener('fetch', (event) => {

  const requestURL = new URL(event.request.url);
  if (/^\/api\//.test(requestURL.pathname)) {
    event.respondWith(fetch(event.request));  //all api requests are a direct pass through
  } else if (/^\/service\//.test(requestURL.pathname)) {
    /*
      process these like a message passing with one extra to say the client is going away.

    */
    if (urlRecognised) {
      event.respondWith(new Response('OK', {status: 200}));
    } else {
      event.respondWith(new Response(`Unknown request ${requestURL.pathname}`, {status: 404}));
    }
  } else {
    event.respondWith(async () => {
      const cache = await caches.open('recorder');
      const cachedResponse = await cache.match(event.request);
      const networkResponsePromise = fetch(event.request);

      event.waitUntil(async () => {
        const networkResponse = await networkResponsePromise;
        await cache.put(event.request, networkResponse.clone());
      });

      // Returned the cached response if we have one, otherwise return the network response.
      return cachedResponse || networkResponsePromise;

    });
  }
});

Вершина события fetch просто передает стандартные запросы API, сделанные клиентом напрямую. Я не могу их кэшировать (хотя я мог бы быть более изощренным и, возможно, заранее отвергать те, которые не поддерживаются). Второй раздел соответствует фантомным URL-адресам /service/something

Последний раздел взят из автономной кулинарной книги Джейка Арчибальда и пытается использовать кеш, но обновляет кеш в фоновом режиме, если какой-либо из файлов stati c изменился .

Подход 3

Аналогично подходу, описанному выше, в котором мы будем использовать фантомные URL и использовать клиентку в качестве уникального маркера, но на самом деле попытаемся смоделировать сервер поток побочных событий с одним URL.

Я думаю, что код будет больше похож на

...
 } else if (/^\/service\//.test(requestURL.pathname)) {
   const stream = new TransformStream();
   const writer = stream.writeable.getWriter();
   event.respondWith(async () => {
    const streamFinishedPromise = new Promise(async (resolve,reject) => {
      event.waitUntil(async () => {
        /* eventually close the link  */
        await streamFinishedPromise;
      });
      try {
        while (true) writer.write(await nextMessageFromServerSideEventStream());
      } catch(e) {
        writer.close();
        resolve();
      }
    });
    return new Response(stream.readable,{status:200}) //probably need eventstream headers too 

 }

Я думаю, что подход 2 может быть самым простым, учитывая где я сейчас нахожусь, но меня беспокоит, что я ничего не вижу при поиске того, как использовать сервисных работников, которые обсуждают этот подход с фантомным URL.

Может ли кто-нибудь прокомментировать любой из этих подходов и дать рекомендации о том, как лучше всего запрограммировать хитрые биты (например, закрывается ли канал сообщения подхода 1, когда браузер перемещается в фоновый режим на iPad, или как вы действительно сохраняете Канал ответа открыт, и закрывается ли он, когда браузер переходит на задний план в подходе 3)

1 Ответ

0 голосов
/ 07 апреля 2020

Простая правда в том, что ни один из этих подходов не будет работать. Когда я задал вопрос, я не осознал, что сервисный работник перезапускается браузером, когда есть что-то сделать, и этот запуск длится только в течение времени обработки события. Хотя eventWaitUntil может продлить это, единственная ссылка на то, как долго я могу найти, - это то, что браузер все еще может отменить его, если окажется, что он никогда не закроется. Я не могу себе представить, чем через несколько часов это не будет отменено. Таким образом, источник события будет закрывать, фактически прервав свою связь с сервером.

Так что мой единственный способ добиться того, чего я хочу, - это продолжать работу сервера при закрытии источника события и найти какой-то другой механизм для освобождения удерживаемых ресурсов. от имени клиента

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...