在“安装”之前,将状态信息传递到服务工作者中



背景

我是服务工作人员的新手,但是在一个旨在成为"脱机优先"的图书馆(实际上,几乎是"仅在线"((FWIW,目的是允许图书馆的消费者提供JSON配置代表表格的多线性文本并获得返回应用程序,该应用程序允许其用户以高度可自定义的方式浏览这些文本。(

其他项目是将库作为依赖项安装,然后通过我们的JavaScript API供应信息,例如JSON配置文件的路径,指示我们的应用程序将消耗为其生成(离线(应用程序的文件。

我知道我们可以做以下任何事情:

  1. 要求用户提供一条硬编码的路径,我们的服务工作者的install脚本可以从中使用waitUntil和其自己的JSON请求来检索用户的必要文件
  2. 跳过json文件的服务工作者的install步骤,并依靠fetch事件来更新缓存,如果用户完成安装并在提取之前脱机,则提供后备显示。
  3. 将一些状态信息从我们的主要脚本发布到服务器(一旦注册(在完成其install事件之前查询的服务器。

...但是所有选择似乎都不理想,因为分别:

  1. 我们图书馆的消费者可能更喜欢为JSON配置指定自己的位置。
  2. 鉴于JSON配置指定文件至关重要,可以向用户展示任何有用的内容,我宁愿不允许安装完成,只能说用户必须返回在线以获取其余文件,如果它们不是install事件后能够保持在线,以查看所有必需的获取发生。
  3. 除了要避免更多到服务器和额外代码的旅行外,我还希望我们的代码如此脱机,以便能够完全在单纯的静态文件服务器上工作。

问题:

是否有某种方法将消息或状态信息传递到服务工作者 发生install事件发生,无论是作为服务工作者URL的查询字符串的一部分还是通过消息事件?在install事件开始之后,消息传递事件甚至可以在技术开始之后到达,只要它在install中的waitUntil完成之前就可以发生。

我知道我可以自己测试,但是我想知道当必须像我们这样的库中,当时必须动态地获得关键应用程序自己的最佳实践。

我猜想indexedDB可能是这里的唯一替代方案(即,将JSON配置的配置信息或路径保存到索引DB,注册服务工作者并从install事件中检索索引DB数据(?即使这也不是理想的选择,因为我让用户为其存储定义了一个名称空间,但是我也需要一种方法才能将其传递到工人中,或者以其他方式传递到Origin上的多个此类应用程序可能会发生冲突。

使用查询参数

如果您觉得它有用,那么是的,可以在服务工作者安装期间提供状态,当您注册时向您的服务工作者提供查询参数,例如:

// Inside your main page:
const pathToJson = '/path/to/file.json';
const swUrl = '/sw.js?pathToJson=' + encodeURIComponent(pathToJson);
navigator.serviceWorker.register(swUrl);
// Inside your sw.js:
self.addEventListener('install', event => {
  const pathToJson = new URL(location).searchParams.get('pathToJson');
  event.waitUntil(
    fetch(pathToJson)
      .then(response => response.json())
      .then(jsonData => /* Do something with jsonData */)
  );
});

有关此方法需要注意的几件事:

  • 如果您的fetch() install处理程序中的JSON文件(如"代码示例"中(,则每本版本的服务Worker脚本(sw.js(有效地发生一次。如果JSON文件的内容更改,但是其他所有内容都保持不变,那么服务工作者将不会自动检测并重新填充您的缓存。

  • 从第一点开始,如果您围绕它处理,例如,包括基于Hash的json文件URL中的基于哈希的版本,每次更改URL时,最终都会安装新的服务工作者。本身本身并不是一件坏事,但是如果您的网络应用中有逻辑来聆听服务工作者生命周期事件的逻辑。

替代方法

您还会发现,仅在主页的上下文中,将文件添加到您的缓存中,因为支持高速缓存存储API的浏览器通过window.caches将其曝光。在服务工作者安装之前,在服务工作者的install处理程序中填写文件确实已成功缓存。

另一种方法是将状态信息从window上下文中写入索引DB,然后从您服务工作者的install处理程序内部读取indexedDB。

更新3:

,由于不应该安全地依靠工人内的全球范围,所以我的消息传递解决方案似乎更少。我认为这必须是Jeff Posnick的解决方案(在某些情况下,importScripts可能起作用(。

更新2:

尽管与https://github.com/w3c/serviceworker/issues/659#issuecomment-384919053开始的讨论与此线程有关的主题无直接相关,但与"安装"事件有关,但有一些问题,尤其是在activate事件中使用此消息通话方法。也就是说,activate事件可能永远不会失败,因此再也不会尝试,将自己的应用程序处于不稳定状态。(install失败至少将不将新服务工作者应用于旧页面,而activate将保持搁置为止,直到活动完成为止,如果剩下的话,它可能永远不会等待未接收到的消息,以及除了新工人外,其他任何事情都无法纠正,因为新页面无法加载以再次发送该消息。(

更新:

尽管我在Chrome中的install脚本中获得了客户端,但由于某种原因,我无法使用navigator.serviceWorker.onmessage接收消息。

但是,我能够完全确认以下方法:

在服务工作者中:

self.addEventListener('install', e => {
    e.waitUntil(
        new Promise((resolve, reject) => {
            self.addEventListener('message', ({data: {
                myData
            }}) => {
                // Do something with `myData` here
                //    then when ready, `resolve`
            });
        })
    );
 });

在调用脚本中:

navigator.serviceWorker.register('sw.js').then((r) => {
    r.installing.postMessage({myData: 100});
});

@jeffposnick是我在OP中描述的简单情况的最佳答案,但我认为我会发现我可以早日从服务工作者脚本(在Chrome上测试(将消息从此类脚本中获取。如下:

在服务工作者中

self.addEventListener('install', e => {
    e.waitUntil(self.clients.matchAll({
        includeUncontrolled: true,
        type: 'window'
    }).then((clients) => new Promise((resolve, reject) => {
        if (clients && clients.length) {
            const client = clients.pop();
            client.postMessage('send msg to main script');
            // One should presumably be able to poll to check for a
            //   variable set in the SW message listener below
            //   and then `resolve` when set
            // Despite the unreliability of setting globals in SW's
            //   I believe this could be safe here as the `install`
            //   event is to run while the main script is still open.
        }
    })));
});
self.addEventListener('message', e => {
    console.log('SW receiving main script msg', e.data);
    e.ports[0].postMessage('sw response');
});

在调用脚本中:

navigator.serviceWorker.addEventListener('message', (e) => {
    console.log('msg recd in main script', e.data);
    e.source.postMessage('sending back to sw');
});
return navigator.serviceWorker.register(
    'sw.js'
).then((r) => {
    // navigator.serviceWorker.ready.then((r) => { // This had been necessary at some point in my testing (with r.active.postMessage), but not working for me atm...
        // Sending a subsequent message
        const messageChannel = new MessageChannel();
        messageChannel.port1.onmessage = (e) => {
            if (e.data.error) {
                console.log('err', e.data.error);
            } else {
                console.log('data', e.data);
            }
        };
        navigator.serviceWorker.controller.postMessage('sending to sw', [messageChannel.port2]);
    // });
});

最新更新