Skip to content

Commit 6d01168

Browse files
SkyZeroZxthePunderWoman
authored andcommitted
feat(service-worker): notify clients about version failures (#62718)
Add client notification when an app version fails, improving error visibility and debugging capabilities. When a version encounters a critical error, all clients using that version are now notified with details about the failure. - Add notifyClientsAboutVersionFailure method call in versionFailed - Ensure clients receive VERSION_FAILED events with error details - Improve service worker error transparency for better debugging PR Close #62718
1 parent 94b0ef1 commit 6d01168

File tree

6 files changed

+66
-2
lines changed

6 files changed

+66
-2
lines changed

goldens/public-api/service-worker/index.api.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ export interface VersionDetectedEvent {
109109
}
110110

111111
// @public
112-
export type VersionEvent = VersionDetectedEvent | VersionInstallationFailedEvent | VersionReadyEvent | NoNewVersionDetectedEvent;
112+
export type VersionEvent = VersionDetectedEvent | VersionInstallationFailedEvent | VersionReadyEvent | VersionFailedEvent | NoNewVersionDetectedEvent;
113113

114114
// @public
115115
export interface VersionInstallationFailedEvent {

packages/service-worker/src/low_level.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,23 @@ export interface VersionReadyEvent {
7070
latestVersion: {hash: string; appData?: object};
7171
}
7272

73+
/**
74+
* An event emitted when a specific version of the app has encountered a critical failure
75+
* that prevents it from functioning correctly.
76+
*
77+
* When a version fails, the service worker will notify all clients currently using that version
78+
* and may degrade to serving only existing clients if the failed version was the latest one.
79+
*
80+
* @see {@link /ecosystem/service-workers/communications Service Worker Communication Guide}
81+
*
82+
* @publicApi
83+
*/
84+
export interface VersionFailedEvent {
85+
type: 'VERSION_FAILED';
86+
version: {hash: string; appData?: object};
87+
error: string;
88+
}
89+
7390
/**
7491
* A union of all event types that can be emitted by
7592
* {@link SwUpdate#versionUpdates}.
@@ -80,6 +97,7 @@ export type VersionEvent =
8097
| VersionDetectedEvent
8198
| VersionInstallationFailedEvent
8299
| VersionReadyEvent
100+
| VersionFailedEvent
83101
| NoNewVersionDetectedEvent;
84102

85103
/**

packages/service-worker/src/update.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ export class SwUpdate {
6666
'VERSION_INSTALLATION_FAILED',
6767
'VERSION_READY',
6868
'NO_NEW_VERSION_DETECTED',
69+
'VERSION_FAILED',
6970
]);
7071
this.unrecoverable = this.sw.eventsOfType<UnrecoverableStateEvent>('UNRECOVERABLE_STATE');
7172
}

packages/service-worker/test/comm_spec.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
NoNewVersionDetectedEvent,
1414
VersionDetectedEvent,
1515
VersionEvent,
16+
VersionFailedEvent,
1617
VersionReadyEvent,
1718
} from '../src/low_level';
1819
import {ngswCommChannelFactory, SwRegistrationOptions} from '../src/provider';
@@ -509,6 +510,25 @@ describe('ServiceWorker library', () => {
509510
},
510511
});
511512
});
513+
it('processes version failed events with cache corruption error', (done) => {
514+
update.versionUpdates.subscribe((event) => {
515+
expect(event.type).toEqual('VERSION_FAILED');
516+
expect((event as VersionFailedEvent).version).toEqual({
517+
hash: 'B',
518+
appData: {name: 'test-app'},
519+
});
520+
expect((event as VersionFailedEvent).error).toContain('Cache corruption detected');
521+
done();
522+
});
523+
mock.sendMessage({
524+
type: 'VERSION_FAILED',
525+
version: {
526+
hash: 'B',
527+
appData: {name: 'test-app'},
528+
},
529+
error: 'Cache corruption detected during resource fetch',
530+
});
531+
});
512532
it('activates updates when requested', async () => {
513533
mock.messages.subscribe((msg: {action: string; nonce: number}) => {
514534
expect(msg.action).toEqual('ACTIVATE_UPDATE');

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

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -956,7 +956,8 @@ export class Driver implements Debuggable, UpdateSource {
956956
// Therefore, we keep clients on their current version (even if broken) and ensure that no new
957957
// clients will be assigned to it.
958958

959-
// TODO: notify affected apps.
959+
// Notify clients that are using this broken version about the failure.
960+
await this.notifyClientsAboutVersionFailure(brokenHash, err);
960961

961962
// The action taken depends on whether the broken manifest is the active (latest) or not.
962963
// - If the broken version is not the latest, no further action is necessary, since new clients
@@ -1298,6 +1299,29 @@ export class Driver implements Debuggable, UpdateSource {
12981299
);
12991300
}
13001301

1302+
async notifyClientsAboutVersionFailure(brokenHash: string, error: Error): Promise<void> {
1303+
await this.initialized;
1304+
1305+
// Find all clients using the broken version
1306+
const affectedClients = Array.from(this.clientVersionMap.entries())
1307+
.filter(([clientId, hash]) => hash === brokenHash)
1308+
.map(([clientId]) => clientId);
1309+
1310+
await Promise.all(
1311+
affectedClients.map(async (clientId) => {
1312+
const client = await this.scope.clients.get(clientId);
1313+
if (client) {
1314+
const brokenVersion = this.versions.get(brokenHash);
1315+
client.postMessage({
1316+
type: 'VERSION_FAILED',
1317+
version: this.mergeHashWithAppData(brokenVersion!.manifest, brokenHash),
1318+
error: errorToString(error),
1319+
});
1320+
}
1321+
}),
1322+
);
1323+
}
1324+
13011325
async broadcast(msg: Object): Promise<void> {
13021326
const clients = await this.scope.clients.matchAll();
13031327
clients.forEach((client) => {

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2564,6 +2564,7 @@ import {envIsSupported} from '../testing/utils';
25642564
expect(await makeRequest(scope, '/foo.hash.js', 'client-2')).toBeNull();
25652565
expect(mockClient2.messages).toEqual([
25662566
jasmine.objectContaining({type: 'UNRECOVERABLE_STATE'}),
2567+
jasmine.objectContaining({type: 'VERSION_FAILED'}),
25672568
]);
25682569

25692570
// This should also enter the `SW` into degraded mode, because the broken version was the

0 commit comments

Comments
 (0)