fix(service-worker): do not enter degraded mode when offline (#22883)
Previously, when trying to fetch `ngsw.json` (e.g. during `checkForUpdate()`) while either the client or the server were offline, the ServiceWorker would enter a degrade mode, where only existing clients would be served. This essentially meant that the ServiceWorker didn't work offline. This commit fixes it by differentiating offline errors and not entering degraded mode. The ServiceWorker will remain in the current mode until connectivity to the server is restored. Fixes #21636 PR Close #22883
This commit is contained in:
parent
12665a749c
commit
9e9b8dd494
|
@ -607,16 +607,22 @@ export class Driver implements Debuggable, UpdateSource {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve a copy of the latest manifest from the server.
|
* Retrieve a copy of the latest manifest from the server.
|
||||||
|
* Return `null` if `ignoreOfflineError` is true (default: false) and the server or client are
|
||||||
|
* offline (detected as response status 504).
|
||||||
*/
|
*/
|
||||||
private async fetchLatestManifest(): Promise<Manifest> {
|
private async fetchLatestManifest(ignoreOfflineError?: false): Promise<Manifest>;
|
||||||
|
private async fetchLatestManifest(ignoreOfflineError: true): Promise<Manifest | null>;
|
||||||
|
private async fetchLatestManifest(ignoreOfflineError = false): Promise<Manifest | null> {
|
||||||
const res =
|
const res =
|
||||||
await this.safeFetch(this.adapter.newRequest('ngsw.json?ngsw-cache-bust=' + Math.random()));
|
await this.safeFetch(this.adapter.newRequest('ngsw.json?ngsw-cache-bust=' + Math.random()));
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
if (res.status === 404) {
|
if (res.status === 404) {
|
||||||
await this.deleteAllCaches();
|
await this.deleteAllCaches();
|
||||||
await this.scope.registration.unregister();
|
await this.scope.registration.unregister();
|
||||||
|
} else if (res.status === 504 && ignoreOfflineError) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
throw new Error('Manifest fetch failed!');
|
throw new Error(`Manifest fetch failed! (status: ${res.status})`);
|
||||||
}
|
}
|
||||||
this.lastUpdateCheck = this.adapter.time;
|
this.lastUpdateCheck = this.adapter.time;
|
||||||
return res.json();
|
return res.json();
|
||||||
|
@ -728,7 +734,15 @@ export class Driver implements Debuggable, UpdateSource {
|
||||||
async checkForUpdate(): Promise<boolean> {
|
async checkForUpdate(): Promise<boolean> {
|
||||||
let hash: string = '(unknown)';
|
let hash: string = '(unknown)';
|
||||||
try {
|
try {
|
||||||
const manifest = await this.fetchLatestManifest();
|
const manifest = await this.fetchLatestManifest(true);
|
||||||
|
|
||||||
|
if (manifest === null) {
|
||||||
|
// Client or server offline. Unable to check for updates at this time.
|
||||||
|
// Continue to service clients (existing and new).
|
||||||
|
this.debugger.log('Check for update aborted. (Client or server offline.)');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
hash = hashManifest(manifest);
|
hash = hashManifest(manifest);
|
||||||
|
|
||||||
// Check whether this is really an update.
|
// Check whether this is really an update.
|
||||||
|
|
|
@ -516,6 +516,17 @@ const manifestUpdateHash = sha1(JSON.stringify(manifestUpdate));
|
||||||
expect(await scope.caches.keys()).toEqual([]);
|
expect(await scope.caches.keys()).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async_it('does not unregister or change state when offline (i.e. manifest 504s)', async() => {
|
||||||
|
expect(await makeRequest(scope, '/foo.txt')).toEqual('this is foo');
|
||||||
|
await driver.initialized;
|
||||||
|
server.online = false;
|
||||||
|
|
||||||
|
expect(await driver.checkForUpdate()).toEqual(false);
|
||||||
|
expect(driver.state).toEqual(DriverReadyState.NORMAL);
|
||||||
|
expect(scope.unregistered).toBeFalsy();
|
||||||
|
expect(await scope.caches.keys()).not.toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
describe('unhashed requests', () => {
|
describe('unhashed requests', () => {
|
||||||
async_beforeEach(async() => {
|
async_beforeEach(async() => {
|
||||||
expect(await makeRequest(scope, '/foo.txt')).toEqual('this is foo');
|
expect(await makeRequest(scope, '/foo.txt')).toEqual('this is foo');
|
||||||
|
|
|
@ -96,6 +96,7 @@ export class MockServerState {
|
||||||
private gate: Promise<void> = Promise.resolve();
|
private gate: Promise<void> = Promise.resolve();
|
||||||
private resolve: Function|null = null;
|
private resolve: Function|null = null;
|
||||||
private resolveNextRequest: Function;
|
private resolveNextRequest: Function;
|
||||||
|
online = true;
|
||||||
nextRequest: Promise<Request>;
|
nextRequest: Promise<Request>;
|
||||||
|
|
||||||
constructor(private resources: Map<string, Response>, private errors: Set<string>) {
|
constructor(private resources: Map<string, Response>, private errors: Set<string>) {
|
||||||
|
@ -108,6 +109,10 @@ export class MockServerState {
|
||||||
|
|
||||||
await this.gate;
|
await this.gate;
|
||||||
|
|
||||||
|
if (!this.online) {
|
||||||
|
throw new Error('Offline.');
|
||||||
|
}
|
||||||
|
|
||||||
if (req.credentials === 'include') {
|
if (req.credentials === 'include') {
|
||||||
return new MockResponse(null, {status: 0, statusText: '', type: 'opaque'});
|
return new MockResponse(null, {status: 0, statusText: '', type: 'opaque'});
|
||||||
}
|
}
|
||||||
|
@ -171,6 +176,7 @@ export class MockServerState {
|
||||||
this.nextRequest = new Promise(resolve => { this.resolveNextRequest = resolve; });
|
this.nextRequest = new Promise(resolve => { this.resolveNextRequest = resolve; });
|
||||||
this.gate = Promise.resolve();
|
this.gate = Promise.resolve();
|
||||||
this.resolve = null;
|
this.resolve = null;
|
||||||
|
this.online = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue