Service Worker - дождаться завершения client.openWindow перед postMessage

Я использую сервисный работник для обработки фоновых уведомлений. Когда я получаю сообщение, я создаю новый Notification с помощью self.registration.showNotification(title, { icon, body }), Я наблюдаю за событием клика на уведомлении, используя self.addEventListener('notificationclick', ()=>{}), При нажатии я проверяю, есть ли WindowClient открыто, если это так, я получаю один из этих оконных клиентов и звоню postMessage на нем, чтобы отправить данные из уведомления в приложение, чтобы позволить приложению обрабатывать уведомление. Incase нет открытого окна я звоню openWindow и как только это завершится, я отправляю данные в это окно, используя postMessage,

event.waitUntil(
    clients.matchAll({ type: 'window' }).then((windows) => {
        if (windows.length > 0) {
            const window = windows[0];
            window.postMessage(_data);
            window.focus();
            return;
        }
        return clients.openWindow(this.origin).then((window) => {
            window.postMessage(_data);
            return;
        });
    })
);

Проблема, с которой я сталкиваюсь, заключается в том, что postMessage позвоните внутрь openWindow никогда не доставляется. Я предполагаю, что это потому, что postMessage позвонить на WindowClient происходит до того, как страница закончила загрузку, так что eventListener еще не зарегистрирован, чтобы прослушать это сообщение? Это правильно?

Как открыть новое окно из сервисного работника и postMessage в это новое окно.

2 ответа

Я тоже сталкиваюсь с этой проблемой, использование тайм-аута является антипаттерном и также может вызвать задержку, превышающую 10-секундный предел хрома, который может выйти из строя.

я проверял, нужно ли мне открывать новое окно клиента. Если я не нашел совпадений в массиве клиентов - а это узкое место, вам нужно подождать, пока страница загрузится, а это может занять время, и postMessage просто не будет работать.

Для этого случая я создал в сервис-воркере простой глобальный объект, который заполняется в этом конкретном случае, например:

const messages = {};
....
// we need to open new window 
messages[randomId] = pushData.message; // save the message from the push notification
await clients.openWindow(urlToOpen + '#push=' + randomId);
....

На загруженной странице, в моем случае приложение React, я жду, пока мой компонент будет смонтирован, затем я запускаю функцию, которая проверяет, содержит ли URL-адрес хэш '#push=XXX', извлекая случайный идентификатор, затем отправляю сообщение сервисный работник, чтобы отправить нам сообщение.

...
if (self.location.hash.contains('#push=')) {
  if ('serviceWorker' in navigator && 'Notification' in window && Notification.permission === 'granted') {
     const randomId = self.locaiton.hash.split('=')[1];
     const swInstance = await navigator.serviceWorker.ready;
     if (swInstance) {
        swInstance.active.postMessage({type: 'getPushMessage', id: randomId});
     }
     // TODO: change URL to be without the `#push=` hash ..
}

Затем, наконец, в сервис-воркер мы добавляем прослушиватель событий сообщения:

self.addEventListener('message', function handler(event) {
    if (event.data.type === 'getPushMessage') {
        if (event.data.id && messages[event.data.id]) {
            // FINALLY post message will work since page is loaded
            event.source.postMessage({
                type: 'clipboard',
                msg: messages[event.data.id],
            });
            delete messages[event.data.id];
        }
    }
});

messages наш "глобальный" не является постоянным, и это хорошо, поскольку нам это нужно только тогда, когда сервис-воркер "просыпается", когда приходит push-уведомление.

Представленный код является псевдокодом, чтобы объяснить идею, которая сработала для меня.

clients.openWindow(event.data.url).then(function(windowClient) {
        // do something with the windowClient.
      });

Я столкнулся с той же проблемой. Моя ошибка была в том, что я зарегистрировал обработчик событий в окне. Но это должно быть зарегистрировано на работника службы, как это:

// next line doesn't work
window.addEventListener("message", event => { /* handler */ });

// this one works
navigator.serviceWorker.addEventListener('message', event => { /* handler */ });

Смотрите примеры на этих страницах:
https://developer.mozilla.org/en-US/docs/Web/API/Clients
https://developer.mozilla.org/en-US/docs/Web/API/Client/postMessage

UPD: чтобы уточнить, этот код переходит в только что открывшееся окно. Проверено в Chromium v.66.

Другие вопросы по тегам