We have an active version of our app which starts polling the server right away to get some basic info - when the request fails, it retries after a second until the request succeeds.
In the newer version, we have removed this polling: i.e. the old endpoint does not exist anymore.
The problem now is, that the PWA never updates to the newer version: i.e. when a user, who has already opened the old version before, opens the app, they will see the old version that constantly tries to poll the server (and this request fails of course). But the angular service worker never updates to the new version (i.e. refresh does not work, closing the browser tabs, or the whole browser does also not work).
Note: The issue seems to be related to the polling
- When we have an app version that polls the server successfully (i.e. polling stops), the app will be updated as expected
- When we have an app version that does not poll the server, the app will be updated as expected
- But in our case, we have an app that polls the server and polling constantly fails (and will be retried every second): In this case, the app is never updated!
The only workaround is to press CTRL+F5 to hard-reload the app.
But this is of course not a solution because average users don't know that they can/should do this...
Any idea how to solve this issue?
- We need a solution that can be done on the server: i.e. it's not possible to tell every user to press CTRL+F5 or uninstall the app via dev-tools, etc.
- A temporary nasty workaround for now is that we reimplemented the endpoint (that the old versions poll) and return some valid dummy data
Reproduction and details
Here is a test-app to reproduce the issue: https://github.com/tmtron/pwa-test
The readme file contains full details.
Initial state:
We have started the app before and the browser has the version of the PWA that constantly polls the server and fails. Now we close the browser tabs (that show our app)
- Next: build and serve a new app version
- When we open the browser it will still serve the previous app-version 2 (as expected)
- since this is a navigation request the browser will check for updates
- when we watch the terminal of the http-server - after some seconds it will show that the browser requests a new version of the ngsw-worker.js
[2020-12-17T11:03:23.948Z] "GET /ngsw-worker.js" ... - but it does not get the new application version
- When we now refresh the tab (press F5), the OLD app version will still be active!
- Closing the browser window and opening again (with the timer enabled) will not help
- we still see the OLD version - refresh won't work
NGSW state
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:
- I am not sure why there are so many Clients - the app is only opened in one tab and the ngsw/state tab is open
- whenever you press refresh on the http://127.0.0.1:8080/ngsw/state URL a new client will show up and not go away
- It seems that the task queue never changes
app-component that starts the timer:
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-config
{
"$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)"
]
}
}
]
}