【发布时间】:2020-12-17 12:24:52
【问题描述】:
我们的应用程序有一个活动版本,它会立即开始轮询服务器以获取一些基本信息 - 当请求失败时,它会在一秒钟后重试,直到请求成功。
在较新的版本中,我们已删除此轮询:即旧端点不再存在。
现在的问题是,PWA 永远不会更新到新版本:即,当之前已经打开旧版本的用户打开应用程序时,他们会看到不断尝试轮询服务器的旧版本(以及这个请求当然失败了)。但是 Angular Service Worker 永远不会更新到新版本(即刷新不起作用、关闭浏览器选项卡或整个浏览器也不起作用)。
注意:问题似乎与投票有关
- 当我们有一个成功轮询服务器的应用版本(即轮询停止)时,该应用将按预期更新
- 当我们有一个不轮询服务器的应用版本时,该应用将按预期更新
- 但在我们的例子中,我们有一个应用程序轮询服务器并且轮询不断失败(并且会每秒重试一次):在这种情况下,应用程序永远不会更新!
唯一的解决方法是按 CTRL+F5 硬重新加载应用程序。
但这当然不是解决方案,因为普通用户不知道他们可以/应该这样做......
知道如何解决这个问题吗?
- 我们需要一个可以在服务器上完成的解决方案:即不可能告诉每个用户按 CTRL+F5 或通过开发工具等卸载应用程序。
- 目前一个令人讨厌的临时解决方法是我们重新实现了端点(旧版本轮询)并返回一些有效的虚拟数据
复制和细节
这是一个重现问题的测试应用程序:https://github.com/tmtron/pwa-test
自述文件包含完整的详细信息。
初始状态:
我们之前启动过应用,浏览器有不断轮询服务器并失败的 PWA 版本。现在我们关闭浏览器标签(显示我们的应用)
- 下一步:构建并提供新的应用版本
- 当我们打开浏览器时,它仍会提供以前的应用程序版本 2(如预期的那样)
- 由于这是一个导航请求,浏览器将检查更新
- 当我们查看 http-server 的终端时 - 几秒钟后它会显示浏览器请求新版本的 ngsw-worker.js
[2020-12-17T11:03:23.948Z] "GET /ngsw-worker.js" ... - 但它没有得到新的应用程序版本
- 当我们现在刷新选项卡(按 F5)时,旧应用版本仍将处于活动状态!
- 关闭浏览器窗口并再次打开(启用计时器)将无济于事
- 我们仍然看到旧版本 - 刷新不起作用
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:
- 我不知道为什么有这么多客户端 - 该应用程序仅在一个选项卡中打开,并且 ngsw/state 选项卡已打开
- 每当您在http://127.0.0.1:8080/ngsw/state URL 上按刷新时,都会出现一个新客户端并且不会消失
- 似乎任务队列永远不会改变
启动计时器的应用组件:
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)"
]
}
}
]
}
【问题讨论】:
-
为什么“轮询”部分对实际问题如此重要?我理解它导致的问题,但它似乎与 pwa 未更新的实际问题无关。
-
因为没有轮询,更新工作正常:github.com/tmtron/pwa-test#update-app-no-timer---works
-
我们创建了一个角度问题:github.com/angular/angular/issues/40207
标签: angular progressive-web-apps service-worker