在“install”

时间:2017-06-08 00:18:30

标签: javascript service-worker

背景

我是服务工作者的新手,但是正在致力于library,而这项工作旨在成为"离线优先" (实际上,差不多只有#34;离线")(FWIW,目的是让图书馆的消费者提供代表表格多线性文本的JSON配置,并获得一个允许用户浏览这些文本的应用程序。段落/诗歌范围内高度可定制的方式。)

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

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

  1. 要求用户提供硬编码路径,我们的服务工作者install脚本可以使用waitUntil使用自己的JSON请求来检索用户的必要文件
  2. 跳过服务工作者的服务工作者{J}文件的install步骤,依靠fetch事件更新缓存,如果用户完成安装,则提供后备显示并且在提取之前就离线了。
  3. 将一些状态信息从我们的主脚本发布到服务器,服务工作者一旦注册,将在完成其install事件之前进行查询。
  4. ...但所有选择似乎都不太理想,因为分别是:

    1. 我们图书馆的消费者可能更愿意为他们的JSON配置指定自己的位置。
    2. 鉴于JSON配置指定对向用户显示任何有用的关键文件,我宁愿不允许安装完成只是说用户必须返回在线获取其余文件,如果他们在install事件后无法保持在线状态,以便查看所有必需的提取。
    3. 除了希望避免更多访问服务器和额外代码之外,我还希望我们的代码能够完全脱机,以便能够完全在纯静态文件服务器上工作。
    4. 问题:

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

      我知道我可以自己测试一下,但是当关键应用程序文件必须像我们这样的库中动态获取时,我想知道最佳实践可能是什么。

      我猜测indexedDB可能是这里唯一的选择(即,将配置信息或JSON配置的路径保存到indexedDB,注册服务工作者,以及从{{}中检索indexedDB数据{1}}事件)?即使这样也不理想,因为我让用户为他们的存储定义命名空间,但是我也需要一种方法将它传递给工作者,否则,源上的多个这样的应用程序可能会发生冲突。

2 个答案:

答案 0 :(得分:28)

如果您觉得它很有用,那么您可以在注册时将查询参数传递给服务工作者,如下所示:

// 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 */)
  );
});

您也可能会发现在主页的上下文中将文件添加到缓存更容易,因为支持Cache Storage API的浏览器会通过window.caches公开它。在服务工作者的install处理程序中预先缓存文件确实具有确保在服务工作者安装之前已成功缓存所有文件的优势。

答案 1 :(得分:1)

更新3:

由于在工作者中依赖全局变量并不安全,因此我的消息传递解决方案似乎更不合理。我认为它必须是Jeff Posnick的解决方案(在某些情况下,importScripts可能有效)。

更新2:

虽然与这个与“安装”事件相关的主题的主题没有直接关系,但是根据https://github.com/w3c/ServiceWorker/issues/659#issuecomment-384919053开始的讨论,存在一些问题,特别是对{{1}使用此消息传递方法事件。也就是说,activate事件可能永远不会失败,因此永远不会再次尝试,使一个人的应用程序处于不稳定状态。 (activate的失败将至少不会将新服务工作者应用于旧页面,而install将保持提取状态直到事件完成,如果等待一个事件,它可能永远不会执行没有收到的消息,除了新工作者之外的任何内容都无法纠正,因为新页面无法加载以再次发送该消息。)

<强>更新

虽然我从Chrome中的activate脚本中获取了客户端,但由于某种原因,我无法通过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`
            });
        })
    );
 });

@JeffPosnick是我在OP中描述的简单案例的最佳答案,但我想我会发现我可以通过这样的方式尽早从服务工作者脚本中获取消息(在Chrome上测试)如下:

在服务工作者

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

在调用脚本中:

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');
});