服务工作者 - 在postMessage

时间:2018-04-21 10:49:44

标签: firebase-cloud-messaging service-worker service-worker-events

我正在使用服务工作者来处理后台通知。收到邮件后,我正在使用Notification创建新的self.registration.showNotification(title, { icon, body })。我正在使用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;
        });
    })
);

我面临的问题是postMessage内的openWindow来电永远不会传递。我猜这是因为postMessage上的WindowClient调用是在页面加载完成之前发生的,所以eventListener没有注册以侦听该消息吗?是吗?

如何从服务工作者和postMessage打开一个新窗口到新窗口。

3 个答案:

答案 0 :(得分:1)

我也迷惑了这个问题,因为使用超时是一种反模式,并且可能导致延迟大于可能失败的chrome限制10秒。

我所做的是检查是否需要打开新的客户端窗口。 如果在clients数组中找不到任何匹配项(这是瓶颈),则需要等待页面加载完毕,这会花费一些时间,而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,然后传递回服务人员以向我们发送消息。

...
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的“全局”不是持久性的,这是很好的,因为当推送通知到达时服务工作者被“唤醒”时,我们只需要它即可。

给出的代码是代码,要指出的是说明,这个想法对我有用。

答案 1 :(得分:-1)

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

答案 2 :(得分:-2)

我遇到了同样的问题。我的错误是我在窗口上注册了事件处理程序。但它应该像这样在服务工作者上注册:

// 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。