首页 > 解决方案 > 轮询服务器的 PWA 永远不会更新到较新的版本

问题描述

我们的应用程序有一个活动版本,它会立即开始轮询服务器以获取一些基本信息 - 当请求失败时,它会在一秒钟后重试,直到请求成功。

在较新的版本中,我们删除了此轮询:即旧端点不再存在。
现在的问题是,PWA 永远不会更新到新版本:即,当之前已经打开旧版本的用户打开应用程序时,他们将看到不断尝试轮询服务器的旧版本(以及这个请求当然失败了)。但是角度服务工作者永远不会更新到新版本(即刷新不起作用,关闭浏览器选项卡,或者整个浏览器也不起作用)。

注意:该问题似乎与轮询有关

唯一的解决方法是按 CTRL+F5 硬重新加载应用程序。
但这当然不是解决方案,因为普通用户不知道他们可以/应该这样做......

知道如何解决这个问题吗?

复制和细节

这是一个重现该问题的测试应用程序:https
://github.com/tmtron/pwa-test 自述文件包含完整的详细信息。

初始状态:

我们之前已经启动了该应用程序,并且浏览器具有不断轮询服务器并失败的 PWA 版本。现在我们关闭浏览器选项卡(显示我们的应用程序)

NGSW 州

http://127.0.0.1:8080/ngsw/state

NGSW Debug Info:

Driver state: NORMAL ((nominal))
Latest manifest hash: b5a9f50d4efae604dbc6706040c71ecb2029c1cf
Last update check: never

=== Version b5a9f50d4efae604dbc6706040c71ecb2029c1cf ===

Clients: 4cad0ef1-179e-4629-b20f-602c4a4be1f9, 4d82d618-8fac-4e79-938c-605266702fa1, e0813c6a-3b76-4aeb-a14a-9043f0417b24, 9bf1be36-e911-4612-8158-21819eb222dc, 09337bd0-d060-46a1-b9e9-56257b879bdd, 0ce1327b-05bb-44e1-bba7-f3769cbad9b2, c9b796dd-b20c-417b-a504-7f065fd841db

=== Idle Task Queue ===
Last update tick: 1s996u
Last update run: never
Task queue:
 * init post-load (update, cleanup)
 * initialization(b5a9f50d4efae604dbc6706040c71ecb2029c1cf)
 * check-updates-on-navigation

Debug log:

启动计时器的应用程序组件:

import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Component({
  selector: 'pwa-update-test-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
})
export class AppComponent implements OnInit {
  private timerId?: number;
  status = 'uninit';

  version = 1;

  timerActive = true;

  constructor(private readonly httpClient: HttpClient) {}

  ngOnInit() {
    this.status = 'waiting';
    const timerUrlParamIsPresent = window.location.href.indexOf('timer') >= 0;
    this.timerActive = timerUrlParamIsPresent
    if (this.timerActive) {
      this.timerId = window.setTimeout(() => this.getAppInfo());
    } else {
      this.status = 'timer deactivated via URL parameter';
    }
  }

  getAppInfo() {
    this.status = 'working';
    this.httpClient
      .get(window.location.origin+'/non-existing-end-point')
      .toPromise()
      .catch((err) => {
        this.status =
          new Date().toLocaleTimeString() + ' failed! (will retry soon)';
        console.log('getAppInfo failed', err);
        if (this.timerActive) {
          this.timerId = window.setTimeout(() => this.getAppInfo(), 2000);
        }
      });
  }

  stopTimer() {
    this.timerActive = false;
    clearTimeout(this.timerId);
  }
}

ngsw-配置

{
  "$schema": "../../node_modules/@angular/service-worker/config/schema.json",
  "index": "/index.html",
  "assetGroups": [
    {
      "name": "app",
      "installMode": "prefetch",
      "resources": {
        "files": [
          "/favicon.ico",
          "/index.html",
          "/manifest.webmanifest",
          "/*.css",
          "/*.js"
        ]
      }
    },
    {
      "name": "assets",
      "installMode": "lazy",
      "updateMode": "prefetch",
      "resources": {
        "files": [
          "/assets/**",
          "/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)"
        ]
      }
    }
  ]
}

标签: angularprogressive-web-appsservice-worker

解决方案


发布与我在 GitHub 上发布的相同答案以提高知名度。


首先,感谢非常详细的复制,@tmtron ❤️
它确实有助于识别问题。

因此,会发生以下情况:
为了避免干扰应用程序请求,SW 不会立即检查初始化和导航请求的更新。Insted 它将安排一个任务在“空闲”时检查更新。目前,当 SW 5 秒内没有收到任何请求时,则认为 SW 处于空闲状态。

因此,由于您的应用程序不断轮询(即发送请求),SW 永远不会认为自己空闲,因此永远不会执行检查更新任务。

注意:这与请求失败或成功无关。碰巧当请求失败时,您的应用会在 5s 空闲阈值之前发送一个新的。

尽管应用程序在失败时不断发出相同的请求可能不是一个好主意(例如,应该有一些退避机制),但 SW 应该能够更优雅地处理它:grin:

例如,即使 SW 不被视为“空闲” , IdleScheduler也可能有一个最大延迟(比如 30 秒),在此之后它会执行其挂起的任务。

就解决方法而言,可以在客户端上做几件事来避免这个问题:

  1. 调用SwUpdate#checkForUpdate()以触发立即检查更新。这不通过IdleScheduler,所以它不受问题的影响。

  2. 对有问题的请求使用ngsw-bypass绕过 SW(这意味着重复请求不会阻止 SW 被视为空闲)。

  3. 避免在请求失败时以恒定速率轮询并实施某种指数退避

当然,如果您无法更新客户端代码,这些都不会帮助您(因为 SW 一直服务于旧版本)

恐怕,您唯一能从服务器做的就是为轮询请求返回一个 2xx 响应(这样应用程序就会停止轮询并且软件可以自行更新。

或者(或另外)您可以将服务器配置为包含已删除端点的Clear-Site-Data标头(带有"storage"or"*"指令),以便发出该请求的旧客户端将通过浏览器取消注册其 SW。


推荐阅读