刷新

时间:2016-10-18 06:21:29

标签: service-worker

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

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

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

  

即使您只有一个标签打开演示,刷新页面   不足以让新版本接管。这是由于如何   浏览器导航工作。导航时,当前页面没有   离开,直到收到响应标题,即便如此   如果回复有Content-Disposition,则当前页面可能会停留   头。由于这种重叠,当前的服务工作者总是如此   在刷新期间控制客户端。

     

要获取更新,请使用以下命令关闭或离开所有标签   现任服务人员。然后,当您再次导航到演示时,您   应该看到马[更新内容]。

     

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

在多个标签的情况下,此行为很有意义。我的应用程序是一个需要自动更新的包;我们无法混合和匹配旧捆绑包的一部分和新捆绑包的一部分。 (在原生应用中,原子性是自动且有保证的。)

但是在单个标签的情况下,我将其称为"最后一个打开标签"应用程序,这种行为不是我想要的。我想刷新最后一个打开的标签来更新我的服务工作者。

(很难想象任何人实际上希望老服务工作者在最后一个打开的标签刷新时继续运行.Google" s"导航重叠&# 34;对我来说,争论听起来像是一个不幸的错误的好借口。)

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

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

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

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

3 个答案:

答案 0 :(得分:26)

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

更新内容

例如:

  • 博客文章
  • 活动时间表
  • 社交媒体时间表

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

更新服务工作者本质上相当于发布新的二进制文件,您不需要这样做(例如)更新文章。

更新这些缓存完全取决于您,如果不更新它们,它们就不会更新。我在offline cookbook中介绍了许多模式,但是this is my favourite one

  1. 服务页面外壳,css&使用服务工作者从缓存中获取js。
  2. 使用缓存中的内容填充页面。
  3. 尝试从网络中提取内容。
  4. 如果内容比缓存中的内容更新,请更新缓存和页面。
  5. 就“更新页面”而言,您需要以不会对用户造成干扰的方式执行此操作。对于按时间顺序排列的列表,这很简单,因为您只需将新内容添加到底部/顶部。如果它正在更新文章它有点棘手,因为你不想从用户眼睛下面切换文本。在这种情况下,通常更容易显示某种类型的通知,例如“更新可用 - 显示更新”,点击后会刷新内容。

    Trained to thrill(可能是第一个服务工作者示例)演示了如何更新数据的时间轴。

    更新“app”

    这是想要更新服务工作者的情况。当您执行以下操作时使用此模式:

    • 更新您的网页外壳
    • 更新JS和/或CSS

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

    1. 检测更新后的服务工作人员“等待”
    2. 向用户显示通知“可用更新 - 立即更新
    3. 当用户点击此通知时,请将消息邮寄给服务工作者,要求其致电skipWaiting
    4. 检测新服务工作人员变为“活动”
    5. window.loaction.reload()
    6. 实施此模式是我的service worker Udacity coursehere's the diff of the code before/after

      的一部分

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

答案 1 :(得分:8)

我写了一篇博文,解释了如何处理这个问题。 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. 安装时
  3. skipWaiting(),并刷新controllerchange上所有打开的标签更好,但当标签随机刷新时,您的用户可能会感到惊讶。
  4. 刷新controllerchange上所有打开的标签页;在安装时,使用应用内“刷新”按钮提示用户skipWaiting()更好,但用户必须使用应用内“刷新”按钮;浏览器的刷新按钮仍然无法正常工作。这在Google's Udacity course on Service WorkersWorkbox Advanced Guide以及sample on Githubmaster分支中有详细记录。
  5. 如果可能,请刷新上一个标签。navigate模式下提取期间,计算打开标签(“客户”)。如果只有一个打开的标签,则立即skipWaiting()并通过返回带有Refresh: 0标题的空白回复来刷新唯一标签。您可以在我sample on Githubrefresh-last-tab分支中看到相关的工作示例。
  6. 全部放在一起......

    在服务工作者中:

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

    在页面中:

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

答案 2 :(得分:3)

除了丹的回答;

1。在服务工作者脚本中添加skipWaiting事件监听器。

在我的情况下,我使用cra-append-sw将基于CRA的应用程序将此片段添加到服务工作者。

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

  if (event.data === 'skipWaiting') {
    self.skipWaiting();
  }
});

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('skipWaiting');
  };
} else {

从本质上讲,我们使用了本地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();
});
相关问题