Skip to content

Commit ae9c25f

Browse files
gkalpakalxhub
authored andcommitted
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
1 parent d0f575b commit ae9c25f

File tree

3 files changed

+35
-4
lines changed

3 files changed

+35
-4
lines changed

packages/service-worker/worker/src/driver.ts

+18-4
Original file line numberDiff line numberDiff line change
@@ -607,16 +607,22 @@ export class Driver implements Debuggable, UpdateSource {
607607

608608
/**
609609
* Retrieve a copy of the latest manifest from the server.
610+
* Return `null` if `ignoreOfflineError` is true (default: false) and the server or client are
611+
* offline (detected as response status 504).
610612
*/
611-
private async fetchLatestManifest(): Promise<Manifest> {
613+
private async fetchLatestManifest(ignoreOfflineError?: false): Promise<Manifest>;
614+
private async fetchLatestManifest(ignoreOfflineError: true): Promise<Manifest | null>;
615+
private async fetchLatestManifest(ignoreOfflineError = false): Promise<Manifest | null> {
612616
const res =
613617
await this.safeFetch(this.adapter.newRequest('ngsw.json?ngsw-cache-bust=' + Math.random()));
614618
if (!res.ok) {
615619
if (res.status === 404) {
616620
await this.deleteAllCaches();
617621
await this.scope.registration.unregister();
622+
} else if (res.status === 504 && ignoreOfflineError) {
623+
return null;
618624
}
619-
throw new Error('Manifest fetch failed!');
625+
throw new Error(`Manifest fetch failed! (status: ${res.status})`);
620626
}
621627
this.lastUpdateCheck = this.adapter.time;
622628
return res.json();
@@ -728,7 +734,15 @@ export class Driver implements Debuggable, UpdateSource {
728734
async checkForUpdate(): Promise<boolean> {
729735
let hash: string = '(unknown)';
730736
try {
731-
const manifest = await this.fetchLatestManifest();
737+
const manifest = await this.fetchLatestManifest(true);
738+
739+
if (manifest === null) {
740+
// Client or server offline. Unable to check for updates at this time.
741+
// Continue to service clients (existing and new).
742+
this.debugger.log('Check for update aborted. (Client or server offline.)');
743+
return false;
744+
}
745+
732746
hash = hashManifest(manifest);
733747

734748
// Check whether this is really an update.
@@ -972,4 +986,4 @@ function errorToString(error: any): string {
972986
} else {
973987
return `${error}`;
974988
}
975-
}
989+
}

packages/service-worker/worker/test/happy_spec.ts

+11
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,17 @@ const manifestUpdateHash = sha1(JSON.stringify(manifestUpdate));
516516
expect(await scope.caches.keys()).toEqual([]);
517517
});
518518

519+
async_it('does not unregister or change state when offline (i.e. manifest 504s)', async() => {
520+
expect(await makeRequest(scope, '/foo.txt')).toEqual('this is foo');
521+
await driver.initialized;
522+
server.online = false;
523+
524+
expect(await driver.checkForUpdate()).toEqual(false);
525+
expect(driver.state).toEqual(DriverReadyState.NORMAL);
526+
expect(scope.unregistered).toBeFalsy();
527+
expect(await scope.caches.keys()).not.toEqual([]);
528+
});
529+
519530
describe('unhashed requests', () => {
520531
async_beforeEach(async() => {
521532
expect(await makeRequest(scope, '/foo.txt')).toEqual('this is foo');

packages/service-worker/worker/testing/mock.ts

+6
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ export class MockServerState {
9696
private gate: Promise<void> = Promise.resolve();
9797
private resolve: Function|null = null;
9898
private resolveNextRequest: Function;
99+
online = true;
99100
nextRequest: Promise<Request>;
100101

101102
constructor(private resources: Map<string, Response>, private errors: Set<string>) {
@@ -108,6 +109,10 @@ export class MockServerState {
108109

109110
await this.gate;
110111

112+
if (!this.online) {
113+
throw new Error('Offline.');
114+
}
115+
111116
if (req.credentials === 'include') {
112117
return new MockResponse(null, {status: 0, statusText: '', type: 'opaque'});
113118
}
@@ -171,6 +176,7 @@ export class MockServerState {
171176
this.nextRequest = new Promise(resolve => { this.resolveNextRequest = resolve; });
172177
this.gate = Promise.resolve();
173178
this.resolve = null;
179+
this.online = true;
174180
}
175181
}
176182

0 commit comments

Comments
 (0)