【问题标题】:Passing state info into a service worker before `install`在“安装”之前将状态信息传递给服务工作者
【发布时间】:2019-07-26 21:39:43
【问题描述】:

背景

我是服务人员的新手,但正在研究旨在成为“离线优先”(实际上,几乎是“仅离线”)的library(FWIW,目的是让图书馆的消费者能够提供表示表格多线性文本的 JSON 配置,并获得一个应用程序,该应用程序允许用户按段落/诗句范围以高度可定制的方式浏览这些文本。)

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

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

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

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

  1. 我们图书馆的消费者可能更愿意为其 JSON 配置指定自己的位置。
  2. 鉴于 JSON 配置指定了对向用户展示任何有用信息至关重要的文件,我宁愿不允许安装完成,只是说用户必须重新联机才能获取其余文件(如果没有的话)能够在 install 事件之后保持在线以查看所有必需的提取发生。
  3. 除了希望避免更多地访问服务器和额外的代码之外,我更希望我们的代码如此面向离线,以便能够完全在静态文件服务器上工作。

问题:

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

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

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

【问题讨论】:

  • TL,DR ;您是否尝试将您的逻辑放在.waitUntil 块中。
  • 与问题无关,但感谢您的尝试。
  • @SaurabhTiwari :我发布了一条回复,显示 waitUntil 可以用作消息传递解决方案的一部分,如果这就是你想要的。
  • 正是我的意思..

标签: javascript service-worker


【解决方案1】:

使用查询参数

如果您觉得它有用,那么是的,您可以在 Service Worker 安装期间提供状态,方法是在注册 Service Worker 时包含一个查询参数,如下所示:

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

关于这种方法的一些注意事项:

    1234563如果 JSON 文件的内容发生了变化,但其他一切都保持不变,则 service worker 不会自动检测到并重新填充您的缓存。 1234563这本身并不是一件坏事,但是如果您的 Web 应用程序中有逻辑可以侦听 Service Worker 生命周期事件,则需要牢记这一点。

替代方法

您还可能会发现,从主页上下文中将文件添加到缓存更容易,因为支持缓存存储 API 的浏览器通过 window.caches 公开它。不过,在 Service Worker 的 install 处理程序中预缓存文件确实具有确保在 Service Worker 安装之前成功缓存所有文件的优势。

另一种方法是将状态信息从 window 上下文写入 IndexedDB,然后从 Service Worker 的 install 处理程序内部的 IndexedDB 中读取。

【讨论】:

    【解决方案2】:

    更新 3:

    而且由于在工作人员中依赖全局变量不应该是安全的,我的消息传递解决方案似乎更不健全。我认为要么必须是 Jeff Posnick 的解决方案(在某些情况下,importScripts 可能有效)。

    更新 2:

    尽管与“安装”事件相关的此线程主题没有直接关系,但根据从 https://github.com/w3c/ServiceWorker/issues/659#issuecomment-384919053 开始的讨论,存在一些问题,特别是在将这种消息传递方法用于 activate 事件时。也就是说,activate 事件可能永远不会失败,因此永远不会再次尝试,从而使应用程序处于不稳定状态。 (install 的失败至少不会将新的 service worker 应用到旧页面,而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]);
        // });
    });
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-06-10
      • 1970-01-01
      • 1970-01-01
      • 2022-11-30
      • 1970-01-01
      • 2018-07-17
      相关资源
      最近更新 更多