Service Worker - 在postMessage前等待clients.openWindow完成

8
我正在使用service worker处理后台通知。当我收到一条消息时,我使用self.registration.showNotification(title, { icon, body })创建一个新的Notification。我使用self.addEventListener('notificationclick', ()=>{})监听通知的点击事件。点击后,我会检查是否有任何打开的WindowClient,如果有,我会获取其中一个窗口客户端并在其上调用postMessage,以将通知中的数据发送到应用程序以允许应用程序处理通知。如果没有打开的窗口,我会调用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;
        });
    })
);

我面临的问题是openWindow内部的postMessage调用从未被传递。我猜测这是因为在页面完成加载之前,WindowClient上的postMessage调用已经发生了,因此事件侦听器尚未注册以侦听该消息?是这样吗?
我如何从服务工作者打开一个新窗口并向该新窗口发送消息?

1
嗨,@procedurally-generated,你有没有得到你的问题的答案? - Evan Layman
你解决了吗? - l1b3rty
@Oliver 没错,只是添加了一个延迟。因此,在3/4秒后进行postMessage,以允许页面加载。 - Procedurally Generated
这是一个解决方法,但不能是唯一的解决方案... - l1b3rty
3个回答

2
我也遇到了这个问题,使用超时是反模式,而且可能会导致延迟超过Chrome的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”的哈希值,提取随机ID,然后向服务工作者发送消息以便我们接收信息。请注意保留HTML标签。
...
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 ..
}

最后,在service worker中,我们添加一个消息事件监听器:

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 我们的“全局”变量是非持久化的,这很好,因为我们只需要在服务工作者 "唤醒" 时,当推送通知到达时使用它。

所呈现的代码是伪代码,重点是解释这个想法,这对我有用。


这是一个很好的解决办法。到目前为止,我见过最棒的。 - Procedurally Generated

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

1
请添加一个解释。 - petezurich
@Zibri,如果您查看我的问题中的示例代码,您会发现这已经是我正在做的事情。但问题在于,该代码在窗口打开时执行,而不是在页面加载完成后执行。这意味着代码在页面注册消息侦听器之前就已经执行了,因此新标签页无法捕获此 post 消息。 - Procedurally Generated

-3
我遇到了相同的问题。我的错误在于我在window上注册了事件处理程序,但应该像这样在service worker上注册:
// 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

更新:为了澄清,此代码适用于新打开的窗口。在Chromium v.66中进行了检查。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接