阅读(1771) (0)

Angular Service Worker通信

2022-07-01 14:17:24 更新

与 Service Worker 通讯

把 ​ServiceWorkerModule ​导入到你的 ​AppModule​ 中不仅会注册 Service Worker,还会提供一些服务,让你能和 Service Worker 通讯,并控制你的应用缓存。

SwUpdate 服务

SwUpdate ​服务让你能访问一些事件,这些事件会指出 Service Worker 何时发现并安装了可用的更新

SwUpdate ​服务支持四个独立的操作:

  • 当在服务器上检测到新版本、已安装并可本地使用或安装失败时获得通知
  • 要求 Service Worker 检查服务器上是否有更新。
  • 要求 Service Worker 为当前标签页激活应用的最新版本

版本更新

versionUpdates ​是 ​SwUpdate ​的一个 ​Observable ​属性,并且会发出四种事件类型:

事件类型

详情

VersionDetectedEvent

当 Service Worker 在服务器上检测到应用程序的新版本并即将开始下载时发出。

NoNewVersionDetectedEvent

当 Service Worker 检查了服务器上应用程序的版本并且没有找到新版本时发出。

VersionReadyEvent

当有新版本的应用程序可供客户端激活时发出。它可用于通知用户可用的更新或提示他们刷新页面。

VersionInstallationFailedEvent

在新版本安装失败时发出。它可用于日志/监控目的。

@Injectable()
export class LogUpdateService {

  constructor(updates: SwUpdate) {
    updates.versionUpdates.subscribe(evt => {
      switch (evt.type) {
        case 'VERSION_DETECTED':
          console.log(`Downloading new app version: ${evt.version.hash}`);
          break;
        case 'VERSION_READY':
          console.log(`Current app version: ${evt.currentVersion.hash}`);
          console.log(`New app version ready for use: ${evt.latestVersion.hash}`);
          break;
        case 'VERSION_INSTALLATION_FAILED':
          console.log(`Failed to install app version '${evt.version.hash}': ${evt.error}`);
          break;
      }
    });
  }
}

检查更新

可以要求 Service Worker 检查是否有任何更新已经发布到了服务器上。Service Worker 会在初始化和每次导航请求(也就是用户导航到应用中的另一个地址)时检查更新。不过,如果你的站点更新非常频繁,或者需要按计划进行更新,你可能会选择手动检查更新。

通过调用 ​checkForUpdate()​ 方法来实现:

import { ApplicationRef, Injectable } from '@angular/core';
import { SwUpdate } from '@angular/service-worker';
import { concat, interval } from 'rxjs';
import { first } from 'rxjs/operators';

@Injectable()
export class CheckForUpdateService {

  constructor(appRef: ApplicationRef, updates: SwUpdate) {
    // Allow the app to stabilize first, before starting
    // polling for updates with `interval()`.
    const appIsStable$ = appRef.isStable.pipe(first(isStable => isStable === true));
    const everySixHours$ = interval(6 * 60 * 60 * 1000);
    const everySixHoursOnceAppIsStable$ = concat(appIsStable$, everySixHours$);

    everySixHoursOnceAppIsStable$.subscribe(() => updates.checkForUpdate());
  }
}

该方法返回一个用来表示检查更新已经成功完成的 ​Promise<boolean>​。这种检查可能会失败,它会导致此 ​Promise ​被拒绝(reject)。

为了避免影响页面的首次渲染,在注册 ServiceWorker 脚本之前,​ServiceWorkerModule ​默认会在应用程序达到稳定态之前等待最多 30 秒。如果不断轮询更新(比如调用 setInterval() 或 RxJS 的  interval())就会阻止应用程序达到稳定态,则直到 30 秒结束之前都不会往浏览器中注册 ServiceWorker 脚本。

注意:
应用中所执行的各种轮询都会阻止它达到稳定态。

可以通过在开始轮询更新之前先等应用达到稳定态来消除这种延迟,如上述例子所示。另外,你还可以为 ServiceWorker 定义不一样的 注册策略

强制激活更新

如果当前标签页需要立即更新到最新的应用版本,可以通过 ​activateUpdate()​ 方法来要求立即这么做:

@Injectable()
export class PromptUpdateService {

  constructor(updates: SwUpdate) {
    updates.available.subscribe(event => {
      if (promptUser(event)) {
        updates.activateUpdate().then(() => document.location.reload());
      }
    });
  }
}

如果调用 ​activateUpdate()​ 而不刷新页面,可能会破坏正在运行的应用中的惰性加载模块,特别是如果惰性加载的模块文件名中使用了哈希时,就会改变每一个版本。所以,建议每当 ​activateUpdate()​ 返回的 Promise 被解析时,都刷新一次页面。

处理不可恢复的状态

在某些情况下,Service Worker 用来为客户端提供服务的应用版本可能处于损坏状态,如果不重新加载整个页面,则无法恢复该状态。

比如,设想以下情形:

  • 用户首次打开该应用,Service Worker 会缓存该应用的最新版本。假设应用要缓存的资源包括 ​index.html​、​main.<main-hash-1>.js​ 和 ​lazy-chunk.<lazy-hash-1>.js​。
  • 用户关闭该应用程序,并且有一段时间没有打开它。
  • 一段时间后,会将新版本的应用程序部署到服务器。新版本中包含文件 ​index.html​、​main.<main-hash-2>.js​ 和 ​lazy-chunk.<lazy-hash-2>.js​。
  • 注意:
    哈希值现在已经不同了,因为文件的内容已经改变)。服务器上不再提供旧版本。

    旧版本在服务器上不再可用。

  • 同时,用户的浏览器决定从其缓存中清退 ​lazy-chunk.<lazy-hash-1>.js​ 浏览器可能决定从缓存中清退特定(或所有)资源,以便回收磁盘空间。
  • 用户再次打开本应用。此时,Service Worker 将提供它所知的最新版本,当然,实际上对我们是旧版本(​index.html​ 和 ​main.<main-hash-1>.js​)。
  • 在稍后的某个时刻,该应用程序请求惰性捆绑包 ​lazy-chunk.<lazy-hash-1>.js​。
  • Service Worker 无法在缓存中找到该资产(请记住浏览器已经将其清退了)。它也无法从服务器上获取它(因为服务器现在只有较新版本的 ​lazy-chunk.<lazy-hash-2>.js​)。

在上述情况下,Service Worker 将无法提供通常会被缓存的资产。该特定的应用程序版本已损坏,并且无法在不重新加载页面的情况下修复客户端的状态。在这种情况下,Service Worker 会通过发送 ​UnrecoverableStateEvent ​事件来通知客户端。可以订阅 ​SwUpdate#unrecoverable​ 以得到通知并处理这些错误。

@Injectable()
export class HandleUnrecoverableStateService {
  constructor(updates: SwUpdate) {
    updates.unrecoverable.subscribe(event => {
      notifyUser(
        'An error occurred that we cannot recover from:\n' +
        event.reason +
        '\n\nPlease reload the page.'
      );
    });
  }
}