diff --git a/packages/service-worker/worker/src/driver.ts b/packages/service-worker/worker/src/driver.ts index 2a3476fd24..5377dc4ff2 100644 --- a/packages/service-worker/worker/src/driver.ts +++ b/packages/service-worker/worker/src/driver.ts @@ -490,6 +490,15 @@ export class Driver implements Debuggable, UpdateSource { table.read('latest'), ]); + // Make sure latest manifest is correctly installed. If not (e.g. corrupted data), + // it could stay locked in EXISTING_CLIENTS_ONLY or SAFE_MODE state. + if (!this.versions.has(latest.latest) && !manifests.hasOwnProperty(latest.latest)) { + this.debugger.log( + `Missing manifest for latest version hash ${latest.latest}`, + 'initialize: read from DB'); + throw new Error(`Missing manifest for latest hash ${latest.latest}`); + } + // Successfully loaded from saved state. This implies a manifest exists, so // the update check needs to happen in the background. this.idle.schedule('init post-load (update, cleanup)', async () => { diff --git a/packages/service-worker/worker/test/happy_spec.ts b/packages/service-worker/worker/test/happy_spec.ts index 7699c4c3cd..fbb3d84eeb 100644 --- a/packages/service-worker/worker/test/happy_spec.ts +++ b/packages/service-worker/worker/test/happy_spec.ts @@ -1426,6 +1426,30 @@ describe('Driver', () => { expect(driver.state).toBe(DriverReadyState.NORMAL); }); + it('should not enter degraded mode if manifest for latest hash is missing upon initialization', + async () => { + // Initialize the SW. + scope.handleMessage({action: 'INITIALIZE'}, null); + await driver.initialized; + expect(driver.state).toBe(DriverReadyState.NORMAL); + + // Ensure the data has been stored in the DB. + const db: MockCache = await scope.caches.open('ngsw:/:db:control') as any; + const getLatestHashFromDb = async () => (await (await db.match('/latest')).json()).latest; + expect(await getLatestHashFromDb()).toBe(manifestHash); + + // Change the latest hash to not correspond to any manifest. + await db.put('/latest', new MockResponse('{"latest": "wrong-hash"}')); + expect(await getLatestHashFromDb()).toBe('wrong-hash'); + + // Re-initialize the SW and ensure it does not enter a degraded mode. + driver.initialized = null; + scope.handleMessage({action: 'INITIALIZE'}, null); + await driver.initialized; + expect(driver.state).toBe(DriverReadyState.NORMAL); + expect(await getLatestHashFromDb()).toBe(manifestHash); + }); + it('ignores invalid `only-if-cached` requests ', async () => { const requestFoo = (cache: RequestCache|'only-if-cached', mode: RequestMode) => makeRequest(scope, '/foo.txt', undefined, {cache, mode});