如何解决service worker调用navigate报非客户端活跃worker错误
错误成因
WindowClient.navigate()方法受安全限制,仅允许当前控制对应页面的活跃Service Worker调用,非控制方调用会直接抛出你遇到的报错- 你在
clients.matchAll中开启了includeUncontrolled: true,会匹配到不受当前SW控制的同域页面;同时clients.claim()的Promise.resolve仅代表SW提交了控制权申请,不保证所有后台页面已经完成受控切换,2b场景下后台页面未完成受控时调用navigate()就会触发错误 - 前台场景下页面处于活跃状态,SW控制权切换速度快,所以不会触发该问题
解决方案
推荐改用postMessage通知页面自行跳转的方案,完全规避SW控制权限制,兼容性更好,适配Quasar PWA的路由逻辑:
第一步:修改Service Worker中的通知点击逻辑
把client.navigate的逻辑替换为发消息给页面:
self.addEventListener('notificationclick', function(event) { console.log('notificationclick', event); event.notification.close(); let route = event.notification.data.route ? JSON.parse(event.notification.data.route) : null; if(route && route.params && route.params.token) { const domain = route.domain; const path = '/#/' + route.name + '/' + route.params.token; const fullUrl = domain + path event.waitUntil(clients.claim().then(() => { return clients.matchAll({ type: 'window', includeUncontrolled: true }) .then(clients => clients.filter(client => client.url.indexOf(domain) !== -1)) .then(matchingClients => { if (matchingClients[0]) { return matchingClients[0].focus().then(function (client) { // 替换navigate为发消息,通知页面跳转 try { client.postMessage({ type: 'NOTIFICATION_CLICK', path: path }) } catch (e) { // 发消息失败兜底,直接打开新窗口 clients.openWindow(fullUrl); } }).catch(function (e) { console.log(e); }); } return clients.openWindow(fullUrl); }) }) ); } });
第二步:在Quasar PWA的全局逻辑中监听消息跳转
在Quasar的App.vue或者全局初始化逻辑中添加监听:
// 监听来自Service Worker的消息 if ('serviceWorker' in navigator) { navigator.serviceWorker.addEventListener('message', event => { if (event.data.type === 'NOTIFICATION_CLICK') { // 调用Quasar的路由实例跳转到指定路径 this.$router.push(event.data.path) // 如果你用的是Composition API,替换为对应的路由实例调用即可 } }) }
可选方案:坚持使用navigate的修复方式
如果一定要用navigate方法,需要先校验SW是否拥有页面控制权:
return matchingClients[0].focus().then(async function (client) { // 等待SW获得页面控制权,最多等1秒 for (let i = 0; i < 10; i++) { const updatedClient = await clients.get(client.id); if (updatedClient?.controller === self) { try { await updatedClient.navigate(fullUrl); } catch (e) { console.log(e); clients.openWindow(fullUrl); } return; } await new Promise(resolve => setTimeout(resolve, 100)); } // 超时兜底 clients.openWindow(fullUrl); })
内容的提问来源于stack exchange,提问作者nulele
相关产品推荐
相关产品推荐

