From 5c3a40577cf0097226ebfef6309005c06c3cddf1 Mon Sep 17 00:00:00 2001 From: Michael Prentice Date: Fri, 6 Sep 2019 23:57:21 -0400 Subject: [PATCH] fix(service-worker): ensure initialization in handleMessage() - community members with this issue have tested this fix - @hsta verified that users have not seen the issue with this fix through all of August - resolves "Invariant violated (initialize): latest hash null has no known manifest" Fixes #25611 --- packages/service-worker/worker/src/driver.ts | 52 +++++++++---------- .../service-worker/worker/test/happy_spec.ts | 6 +++ 2 files changed, 32 insertions(+), 26 deletions(-) diff --git a/packages/service-worker/worker/src/driver.ts b/packages/service-worker/worker/src/driver.ts index afa0eb71b0b530..d74933ac707d30 100644 --- a/packages/service-worker/worker/src/driver.ts +++ b/packages/service-worker/worker/src/driver.ts @@ -295,7 +295,32 @@ export class Driver implements Debuggable, UpdateSource { event.waitUntil(this.handleClick(event.notification, event.action)); } + private async ensureInitialized(): Promise { + // Since the SW may have just been started, it may or may not have been initialized already. + // this.initialized will be null if initialization has not yet been attempted, or will be a + // Promise which will resolve (successfully or unsuccessfully) if it has. + if (this.initialized === null) { + // Initialization has not yet been attempted, so attempt it. This should only ever happen once + // per SW instantiation. + this.initialized = this.initialize(); + } + + // If initialization fails, the SW needs to enter a safe state, where it declines to respond to + // network requests. + try { + // Wait for initialization. + await this.initialized; + } catch (error) { + // Initialization failed. Enter a safe state. + this.state = DriverReadyState.SAFE_MODE; + this.stateMessage = `Initialization failed due to error: ${errorToString(error)}`; + throw error; + } + } + private async handleMessage(msg: MsgAny&{action: string}, from: Client): Promise { + await this.ensureInitialized(); + if (isMsgCheckForUpdates(msg)) { const action = (async() => { await this.checkForUpdate(); })(); await this.reportStatus(from, action, msg.statusNonce); @@ -383,32 +408,7 @@ export class Driver implements Debuggable, UpdateSource { } private async handleFetch(event: FetchEvent): Promise { - // Since the SW may have just been started, it may or may not have been initialized already. - // this.initialized will be `null` if initialization has not yet been attempted, or will be a - // Promise which will resolve (successfully or unsuccessfully) if it has. - if (this.initialized === null) { - // Initialization has not yet been attempted, so attempt it. This should only ever happen once - // per SW instantiation. - this.initialized = this.initialize(); - } - - // If initialization fails, the SW needs to enter a safe state, where it declines to respond to - // network requests. - try { - // Wait for initialization. - await this.initialized; - } catch (e) { - // Initialization failed. Enter a safe state. - this.state = DriverReadyState.SAFE_MODE; - this.stateMessage = `Initialization failed due to error: ${errorToString(e)}`; - - // Even though the driver entered safe mode, background tasks still need to happen. - event.waitUntil(this.idle.trigger()); - - // Since the SW is already committed to responding to the currently active request, - // respond with a network fetch. - return this.safeFetch(event.request); - } + await this.ensureInitialized(); // On navigation requests, check for new updates. if (event.request.mode === 'navigate' && !this.scheduledNavUpdateCheck) { diff --git a/packages/service-worker/worker/test/happy_spec.ts b/packages/service-worker/worker/test/happy_spec.ts index 0bb0a93005a727..1011945ad8b9e9 100644 --- a/packages/service-worker/worker/test/happy_spec.ts +++ b/packages/service-worker/worker/test/happy_spec.ts @@ -366,6 +366,12 @@ import {SwTestHarness, SwTestHarnessBuilder} from '../testing/scope'; server.assertNoOtherRequests(); }); + it('initializes the service worker on fetch if it has not yet been initialized', async() => { + expect(await makeRequest(scope, '/foo.txt')).toEqual('this is foo'); + server.assertSawRequestFor('/foo.txt'); + server.assertNoOtherRequests(); + }); + it('handles non-relative URLs', async() => { expect(await makeRequest(scope, '/foo.txt')).toEqual('this is foo'); await driver.initialized;