刷新时激活更新的服务工作者

Dan*_*ich 26 service-worker

当服务工作者更新时,它不会正确控制页面; 它进入"等待"状态,等待激活.

令人惊讶的是,更新后的服务工作人员在刷新页面后甚至无法控制选项卡.谷歌解释说:

https://developers.google.com/web/fundamentals/instant-and-offline/service-worker/lifecycle

即使您只有一个标签打开演示,刷新页面也不足以让新版本接管.这是由于浏览器导航的工作原理.导航时,当前页面在收到响应标头之前不会消失,即使这样,如果响应有Content-Disposition 标题,当前页面也可能保留.由于这种重叠,当前服务工作者总是在刷新期间控制客户端.

要获取更新,请使用当前服务工作人员关闭或远离所有选项卡.然后,当您再次导航到演示时,您应该看到马[更新内容].

此模式与Chrome更新的方式类似.Chrome在后台下载更新,但在Chrome重新启动后才会应用.同时,您可以继续使用当前版本而不会中断.然而,这在开发过程中是一种痛苦,但DevTools有办法让它变得更容易,我将在本文后面介绍.

在多个选项卡的情况下,此行为是有意义的.我的应用程序是一个需要自动更新的包; 我们不能混合和匹配旧捆绑的一部分和新捆绑的一部分.(在本机应用程序中,原子性是自动且有保证的.)

但是在单个选项卡的情况下,我称之为应用程序的"最后打开选项卡",这种行为不是我想要的.我想刷新最后一个打开的标签来更新我的服务人员.

(很难想象当最后一个打开的标签刷新时,任何人都希望旧的服务工作者继续运行.谷歌的"导航重叠"论证听起来像是一个不幸的错误的好借口.)

我的应用通常只在一个标签中使用.在生产中,我希望我的用户能够通过刷新页面来使用最新的代码,但这不起作用:旧的服务工作者将在刷新时保持控制.

我不想告诉用户,"要接收更新,请务必关闭或远离我的应用." 我想告诉他们,"只是刷新."

当用户刷新页面时,如何激活更新的服务工作者?

编辑:有一个答案我知道这是快速,简单和错误:skipWaiting在服务工作者的安装事件中.skipWaiting将使新服务工作程序在更新下载后立即生效,同时打开旧页面选项卡.这使得更新不安全地非原子化; 这就像在应用程序运行时替换本机应用程序包.那对我来说不行.我需要等到用户刷新上一个打开的选项卡的页面.

Jaf*_*ake 37

概念上有两种更新,从你的问题我们所说的不清楚,所以我将涵盖两者.

更新内容

例如:

  • 博文的文字
  • 活动安排
  • 社交媒体时间表

这些可能会存储在缓存API或IndexedDB中.这些存储独立于服务工作者 - 更新服务工作者不应该删除它们,更新它们不应该要求更新服务工作者

更新服务工作者本质上相当于发送新的二进制文件,您不应该这样做(例如)更新文章.

更新这些缓存完全取决于您,如果不更新它们,它们就不会更新.我在离线食谱中介绍了一些模式,但这是我最喜欢的模式:

  1. 使用服务工作者从缓存中提供页面shell,css和js.
  2. 使用缓存中的内容填充页面.
  3. 尝试从网络中获取内容.
  4. 如果内容比缓存中的内容更新,请更新缓存和页面.

在"更新页面"方面,您需要以不会对用户造成干扰的方式执行此操作.对于按时间顺序排列的列表,这很简单,因为您只需将新内容添加到底部/顶部.如果它正在更新文章它有点棘手,因为你不想从用户眼睛下面切换文本.在这种情况下,通常更容易显示某种类型的通知,例如"更新可用 - 显示更新 ",当单击时,刷新内容.

训练有素(可能是第一个服务工作者示例)演示了如何更新数据的时间轴.

更新"应用"

在这种情况下,您确实需要更新服务工作者.当您执行以下操作时,将使用此模式:

  • 更新您的页面shell
  • 更新JS和/或CSS

如果您希望用户以非原子但相当安全的方式获取此更新,那么也存在这样的模式.

  1. 检测更新的服务工作者"等待"
  2. 向用户显示通知"可用更新 - 立即更新 "
  3. 当用户单击此通知时,将消息发送给服务工作者,要求其进行呼叫skipWaiting.
  4. 检测新服务工作人员变为"活动"
  5. window.loaction.reload()

实现这种模式是我的服务工作者Udacity课程的一部分,这里是代码之前/之后的差异.

你也可以这样做.例如,您可以使用某种semver-esque系统,因此您知道用户当前拥有的服务工作者的版本.如果更新很小,您可以决定通话skipWaiting()是完全安全的.

  • 我确实在开发一个“应用程序”。真的没有办法在刷新时“跳过等待”吗?在应用程序中实现我自己的刷新按钮似乎是一种技巧。 (2认同)

Dan*_*ich 18

我写了一篇博客文章,解释如何处理这个问题.https://redfin.engineering/how-to-fix-the-refresh-button-when-using-service-workers-a8e27af6df68

我在github上也有一个工作样本.https://github.com/dfabulich/service-worker-refresh-sample

有四种方法:

  1. skipWaiting()在安装.这很危险,因为您的标签可以混合使用新旧内容.
  2. skipWaiting()在安装上,并刷新controllerchange Better 上的所有打开的选项卡,但当选项卡随机刷新时,您的用户可能会感到惊讶.
  3. 刷新所有打开的标签controllerchange; 在安装时,skipWaiting()使用应用程序内的"刷新"按钮提示用户.更好,但用户必须使用应用内"刷新"按钮; 浏览器的刷新按钮仍然无法正常工作.这在Google的"服务工作者和工作箱高级指南" 的Udacity课程以及master在Github上的示例分支中有详细记录.
  4. 如果可能,刷新上一个选项卡.navigate模式提取期间,计算打开的标签("客户").如果只有一个打开的选项卡,则skipWaiting()立即通过返回带Refresh: 0标题的空白响应来刷新唯一的选项卡.您可以在Github上的示例refresh-last-tab分支中看到此示例.

把它们放在一起......

在服务工作者:

addEventListener('message', messageEvent => {
  if (messageEvent.data === 'skipWaiting') return skipWaiting();
});

addEventListener('fetch', event => {
  event.respondWith((async () => {
    if (event.request.mode === "navigate" &&
      event.request.method === "GET" &&
      registration.waiting &&
      (await clients.matchAll()).length < 2
    ) {
      registration.waiting.postMessage('skipWaiting');
      return new Response("", {headers: {"Refresh": "0"}});
    }
    return await caches.match(event.request) ||
      fetch(event.request);
  })());
});
Run Code Online (Sandbox Code Playgroud)

在页面中:

function listenForWaitingServiceWorker(reg, callback) {
  function awaitStateChange() {
    reg.installing.addEventListener('statechange', function() {
      if (this.state === 'installed') callback(reg);
    });
  }
  if (!reg) return;
  if (reg.waiting) return callback(reg);
  if (reg.installing) awaitStateChange();
  reg.addEventListener('updatefound', awaitStateChange);
}

// reload once when the new Service Worker starts activating
var refreshing;
navigator.serviceWorker.addEventListener('controllerchange',
  function() {
    if (refreshing) return;
    refreshing = true;
    window.location.reload();
  }
);
function promptUserToRefresh(reg) {
  // this is just an example
  // don't use window.confirm in real life; it's terrible
  if (window.confirm("New version available! OK to refresh?")) {
    reg.waiting.postMessage('skipWaiting');
  }
}
listenForWaitingServiceWorker(reg, promptUserToRefresh);
Run Code Online (Sandbox Code Playgroud)


Chr*_*aan 6

除了丹的回答;

1.skipWaiting在service worker脚本中添加事件监听器。

在我的例子中,一个基于 CRA 的应用程序我使用cra-append-sw将此代码段添加到 service worker。

注意:如果您使用的是最新版本的react-scripts,则会自动添加以下代码段,您无需使用cra-append-sw.

self.addEventListener('message', event => {
  if (!event.data) {
    return;
  }

  if (event.data === 'SKIP_WAITING') {
    self.skipWaiting();
  }
});
Run Code Online (Sandbox Code Playgroud)

2. 更新 register-service-worker.js

对我来说,当用户在网站内导航时重新加载页面感觉很自然。在 register-service-worker 脚本中,我添加了以下代码:

if (navigator.serviceWorker.controller) {
  // At this point, the old content will have been purged and
  // the fresh content will have been added to the cache.
  // It's the perfect time to display a "New content is
  // available; please refresh." message in your web app.
  console.log('New content is available; please refresh.');

  const pushState = window.history.pushState;

  window.history.pushState = function () {
    // make sure that the user lands on the "next" page
    pushState.apply(window.history, arguments);

    // makes the new service worker active
    installingWorker.postMessage('SKIP_WAITING');
  };
} else {
Run Code Online (Sandbox Code Playgroud)

本质上,我们挂钩到本机pushState函数,以便我们知道用户正在导航到一个新页面。但是,例如,如果您还在产品页面的 URL 中使用过滤器,您可能希望阻止重新加载页面并等待更好的机会。

接下来,我还使用 Dan 的代码段在 service worker 控制器更改时重新加载所有选项卡。这也将重新加载活动选项卡。

  .catch(error => {
    console.error('Error during service worker registration:', error);
  });

navigator.serviceWorker.addEventListener('controllerchange', () => {
  if (refreshing) {
    return;
  }

  refreshing = true;
  window.location.reload();
});
Run Code Online (Sandbox Code Playgroud)