我该如何移除错误的Service Worker,或者实现“紧急停止”功能?

88

我正在使用计算机中的服务工作器API进行试玩,以便了解如何在我的真实世界应用程序中受益。

我遇到了一个奇怪的情况,我注册了一个服务工作者来拦截fetch事件,以便在发送请求到源之前检查其缓存中是否有所请求的内容。 问题在于,这段代码存在错误,阻止了函数发出请求,因此我的页面变成了空白;什么也没发生。 由于服务工作者已经注册,第二次加载页面时它会拦截第一次请求(加载HTML的请求)。因为我有这个错误,fetch事件失败,它从未请求HTML,我看到的只是一个空白页。

在这种情况下,我唯一知道删除错误的服务工作者脚本的方法是通过chrome://serviceworker-internals/控制台。 如果这个错误影响了一个实际网站,最好的解决方法是什么?

谢谢!

7个回答

130

我想在这里扩展一些其他答案,并从“在将服务工作者投入生产时如何确保可以进行任何必要更改的策略”角度来看待这个问题。这些更改可能包括在生产中发现的修复任何小错误,或者(但希望不会出现)由于无法克服的错误而使服务工作者失效-所谓的“紧急停止开关”。

为了回答这个问题,假设您称之为

navigator.serviceWorker.register('service-worker.js');

在您的页面中,意味着您的服务工作者 JavaScript 资源是 service-worker.js。(如果您不确定使用了确切的服务工作者 URL —— 也许是因为您向服务工作者脚本添加了哈希或版本信息,请参见下面的内容。

问题归结为如何解决service-worker.js代码中的初始问题。如果只是一个小的 bug 修复,那么您显然可以直接进行更改并重新部署service-worker.js到您的托管环境中。如果没有明显的 bug 修复,并且您不想让您的用户在您花费时间解决问题时继续运行有问题的服务工作者代码,保留一个简单的、无操作 (no-op) 的service-worker.js 是个好主意,就像以下这样:

// A simple, no-op service worker that takes immediate control.

self.addEventListener('install', () => {
  // Skip over the "waiting" lifecycle state, to ensure that our
  // new service worker is activated immediately, even if there's
  // another tab open controlled by our older service worker code.
  self.skipWaiting();
});

/*
self.addEventListener('activate', () => {
  // Optional: Get a list of all the current open windows/tabs under
  // our service worker's control, and force them to reload.
  // This can "unbreak" any open windows/tabs as soon as the new
  // service worker activates, rather than users having to manually reload.
  self.clients.matchAll({type: 'window'}).then(windowClients => {
    windowClients.forEach(windowClient => {
      windowClient.navigate(windowClient.url);
    });
  });
});
*/

这应该是您的空操作service-worker.js文件所需的全部内容。因为没有注册fetch处理程序,所有从受控页面发起的导航和资源请求将直接针对网络进行,实际上使您获得了与无服务工作者完全相同的行为。

其他步骤

可能还可以更进一步,强制删除使用Cache Storage API存储的所有内容,或者显式注销服务工作者。 对于大多数常见情况,这可能过度,遵循上述建议应该足以使您的当前用户获得预期的行为,并且一旦修复错误就可以重新部署更新。启动即使是无操作服务工作者也存在某种程度的开销,因此如果没有计划重新部署有意义的服务工作者代码,您可以选择注销服务工作者路线。

如果您已经处于服务HTTP缓存指令提供给它的生命周期比您的用户等待的时间长的service-worker.js的情况下,请记住,在桌面浏览器上,Shift + Reload会强制页面在服务工作者控制外重新加载。不是每个用户都知道如何做到这一点,在移动设备上也不可能。因此,不要依赖Shift + Reload作为可行的回滚计划。

如果您不知道服务工作者网址怎么办?

上述信息假设您知道服务工作者URL是什么- service-worker.jssw.js或其他某些基本不变的东西。但是,如果您在服务工作者脚本中包含了某种版本或哈希信息,例如service-worker.abcd1234.js怎么办?

首先,请尽量避免这种情况-这是最佳做法违反。但是,如果您已经部署了许多版本化的服务工作者URL,并且需要禁用所有用户,而不管他们注册了哪个URL,则有一种方法可以解决问题。

每次浏览器请求服务工作者脚本时,无论是初始注册还是更新检查,它都会设置一个名为Service-Worker:的HTTP请求头。

假设您对后端HTTP服务器具有完全控制权,您可以检查传入请求是否存在此Service-Worker:头,并始终响应您的无操作服务工作者脚本响应,而不管请求URL是什么。

配置Web服务器以执行此操作的详细信息将因服务器而异。

Clear-Site-Data响应头

最后一点:当特定的HTTP响应头作为任何响应的一部分返回时,某些浏览器将自动清除特定数据并可能取消注册服务工作者:Clear-Site-Data:

设置此标题可在尝试从错误的服务工作者部署中恢复时提供帮助,并且停止开关场景包括在该功能的规范中作为示例用例。

在仅使用其作为停止开关之前,请检查Clear-Site-Data:浏览器支持情况。截至2019年7月,它不被100%支持服务工作者的浏览器支持,因此,如果您担心在所有浏览器中从故障服务工作者中恢复,最安全的方法是与上述技术一起使用Clear-Site-Data:


参见:https://github.com/GoogleChrome/workbox/issues/2582 - Jeff Posnick

14

您可以使用JavaScript“注销”服务工作者。 下面是一个示例:

if ('serviceWorker' in navigator) {
  navigator.serviceWorker.getRegistrations().then(function (registrations) {
    //returns installed service workers
    if (registrations.length) {
      for(let registration of registrations) {
        registration.unregister();
      }
    }
  });
}

1
这个必须放在 .register 方法之前,这样它就会在注册之前注销所有内容,或者它可以在任何地方运行吗? - xkeshav

9

这是一个非常糟糕的情况,希望在生产中不会发生这种情况。

如果您不想通过各种浏览器的开发者工具进行操作,在这种情况下,对于基于blink的浏览器,可以使用chrome://serviceworker-internals/,对于Firefox,请使用about:serviceworkers(将来的版本中为about:debugging#workers),这里有两个方法:

  1. 使用serviceworker更新机制。您的用户代理将检查是否注册了worker的任何更改,将获取它并再次经过activate阶段。因此,您可以潜在地更改serviceworker脚本,修复(清除缓存等)任何奇怪的情况并继续工作。唯一的缺点是需要等待浏览器更新worker,可能需要1天时间。
  2. 向您的worker添加某种kill switch。拥有一个特殊的url,您可以指导用户访问该url以恢复缓存等状态。

我不确定清除浏览器数据是否会删除worker,所以这可能是另一种选择。


1
感谢您的回答。 选项1对于一个正在运行的网站来说是不可承受的。 我会看一下选项2,但如果您在网站顶层(“/”)注册了服务工作者,我并不太有信心它会起作用。 - PaquitoSoft

4
我没有测试过,但是ServiceWorkerRegistration对象上有一个unregister()和一个update()方法。你可以从navigator.serviceWorker中获取它。
navigator.serviceWorker.getRegistration('/').then(function(registration) {
  registration.update();
});

更新应该立即检查是否有新的Service Worker,如果有,则安装它。这将绕过24小时等待期,并在每次遇到此JavaScript时下载serviceworker.js。

3

针对实时情况,您需要以字节级别更改服务工作人员(例如在第一行放置注释),它将在接下来的24小时内更新。您可以通过在Chrome中单击更新按钮,在chrome://serviceworker-internals/中模拟这种情况。

即使服务工作者本身已被缓存,也应该可以正常工作,因为更新算法的步骤9会设置绕过服务工作者的标志。


3
我们将一个网站从godaddy.com迁移到了常规的WordPress安装。客户(不是我们)在所有浏览器中缓存了一个serviceworker文件(sw.js),这完全搞乱了事情。我们的网站是一个普通的WordPress网站,没有使用service workers。
它就像一个病毒,因为它出现在每个页面上,它不是来自我们的服务器,也没有简单的方法来摆脱它。
我们在服务器的根目录下创建了一个新的空文件名为sw.js,然后在网站的每个页面上添加了以下内容。
<script>
if (navigator && navigator.serviceWorker && navigator.serviceWorker.getRegistration) {

    navigator.serviceWorker.getRegistration('/').then(function(registration) {
        if (registration) {
              registration.update();
              registration.unregister();
        }
});

}
</script>

2
我们在另一个网站上遇到了这个问题(同一客户在godaddy上有网站)- 浏览器中存在我所谓的错误。当更新机制在服务工作者中收到404时,它将使用旧版本!因此,您需要创建一个新的服务工作者。 - Tom Andersen

0
如果有人需要帮助,我试图终止在访问曾经注册它们的生产站点的浏览器中运行的服务工作者。我通过发布一个仅包含以下内容的service-worker.js文件来解决了这个问题:

self.globalThis.registration.unregister();


这可能需要24小时或更长时间才能生效?但除此之外看起来还不错。https://dev59.com/kFkT5IYBdhLWcg3wRNYR - Tom Andersen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接