diff --git a/packages/service-worker/worker/src/driver.ts b/packages/service-worker/worker/src/driver.ts index b4e046539f..19fc0dded6 100644 --- a/packages/service-worker/worker/src/driver.ts +++ b/packages/service-worker/worker/src/driver.ts @@ -607,16 +607,22 @@ export class Driver implements Debuggable, UpdateSource { /** * 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 { + private async fetchLatestManifest(ignoreOfflineError?: false): Promise; + private async fetchLatestManifest(ignoreOfflineError: true): Promise; + private async fetchLatestManifest(ignoreOfflineError = false): Promise { const res = await this.safeFetch(this.adapter.newRequest('ngsw.json?ngsw-cache-bust=' + Math.random())); if (!res.ok) { if (res.status === 404) { await this.deleteAllCaches(); 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; return res.json(); @@ -728,7 +734,15 @@ export class Driver implements Debuggable, UpdateSource { async checkForUpdate(): Promise { let hash: string = '(unknown)'; 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); // Check whether this is really an update. @@ -972,4 +986,4 @@ function errorToString(error: any): string { } else { return `${error}`; } -} \ No newline at end of file +} diff --git a/packages/service-worker/worker/test/happy_spec.ts b/packages/service-worker/worker/test/happy_spec.ts index 2a63385850..f560394805 100644 --- a/packages/service-worker/worker/test/happy_spec.ts +++ b/packages/service-worker/worker/test/happy_spec.ts @@ -516,6 +516,17 @@ const manifestUpdateHash = sha1(JSON.stringify(manifestUpdate)); 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', () => { async_beforeEach(async() => { expect(await makeRequest(scope, '/foo.txt')).toEqual('this is foo'); diff --git a/packages/service-worker/worker/testing/mock.ts b/packages/service-worker/worker/testing/mock.ts index 4ab6502464..208e75f9ad 100644 --- a/packages/service-worker/worker/testing/mock.ts +++ b/packages/service-worker/worker/testing/mock.ts @@ -96,6 +96,7 @@ export class MockServerState { private gate: Promise = Promise.resolve(); private resolve: Function|null = null; private resolveNextRequest: Function; + online = true; nextRequest: Promise; constructor(private resources: Map, private errors: Set) { @@ -108,6 +109,10 @@ export class MockServerState { await this.gate; + if (!this.online) { + throw new Error('Offline.'); + } + if (req.credentials === 'include') { 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.gate = Promise.resolve(); this.resolve = null; + this.online = true; } }