mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
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
This commit is contained in:
parent
94b0ef1453
commit
6d011687ec
6 changed files with 66 additions and 2 deletions
|
|
@ -109,7 +109,7 @@ export interface VersionDetectedEvent {
|
|||
}
|
||||
|
||||
// @public
|
||||
export type VersionEvent = VersionDetectedEvent | VersionInstallationFailedEvent | VersionReadyEvent | NoNewVersionDetectedEvent;
|
||||
export type VersionEvent = VersionDetectedEvent | VersionInstallationFailedEvent | VersionReadyEvent | VersionFailedEvent | NoNewVersionDetectedEvent;
|
||||
|
||||
// @public
|
||||
export interface VersionInstallationFailedEvent {
|
||||
|
|
|
|||
|
|
@ -70,6 +70,23 @@ export interface VersionReadyEvent {
|
|||
latestVersion: {hash: string; appData?: object};
|
||||
}
|
||||
|
||||
/**
|
||||
* An event emitted when a specific version of the app has encountered a critical failure
|
||||
* that prevents it from functioning correctly.
|
||||
*
|
||||
* When a version fails, the service worker will notify all clients currently using that version
|
||||
* and may degrade to serving only existing clients if the failed version was the latest one.
|
||||
*
|
||||
* @see {@link /ecosystem/service-workers/communications Service Worker Communication Guide}
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export interface VersionFailedEvent {
|
||||
type: 'VERSION_FAILED';
|
||||
version: {hash: string; appData?: object};
|
||||
error: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A union of all event types that can be emitted by
|
||||
* {@link SwUpdate#versionUpdates}.
|
||||
|
|
@ -80,6 +97,7 @@ export type VersionEvent =
|
|||
| VersionDetectedEvent
|
||||
| VersionInstallationFailedEvent
|
||||
| VersionReadyEvent
|
||||
| VersionFailedEvent
|
||||
| NoNewVersionDetectedEvent;
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ export class SwUpdate {
|
|||
'VERSION_INSTALLATION_FAILED',
|
||||
'VERSION_READY',
|
||||
'NO_NEW_VERSION_DETECTED',
|
||||
'VERSION_FAILED',
|
||||
]);
|
||||
this.unrecoverable = this.sw.eventsOfType<UnrecoverableStateEvent>('UNRECOVERABLE_STATE');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import {
|
|||
NoNewVersionDetectedEvent,
|
||||
VersionDetectedEvent,
|
||||
VersionEvent,
|
||||
VersionFailedEvent,
|
||||
VersionReadyEvent,
|
||||
} from '../src/low_level';
|
||||
import {ngswCommChannelFactory, SwRegistrationOptions} from '../src/provider';
|
||||
|
|
@ -509,6 +510,25 @@ describe('ServiceWorker library', () => {
|
|||
},
|
||||
});
|
||||
});
|
||||
it('processes version failed events with cache corruption error', (done) => {
|
||||
update.versionUpdates.subscribe((event) => {
|
||||
expect(event.type).toEqual('VERSION_FAILED');
|
||||
expect((event as VersionFailedEvent).version).toEqual({
|
||||
hash: 'B',
|
||||
appData: {name: 'test-app'},
|
||||
});
|
||||
expect((event as VersionFailedEvent).error).toContain('Cache corruption detected');
|
||||
done();
|
||||
});
|
||||
mock.sendMessage({
|
||||
type: 'VERSION_FAILED',
|
||||
version: {
|
||||
hash: 'B',
|
||||
appData: {name: 'test-app'},
|
||||
},
|
||||
error: 'Cache corruption detected during resource fetch',
|
||||
});
|
||||
});
|
||||
it('activates updates when requested', async () => {
|
||||
mock.messages.subscribe((msg: {action: string; nonce: number}) => {
|
||||
expect(msg.action).toEqual('ACTIVATE_UPDATE');
|
||||
|
|
|
|||
|
|
@ -956,7 +956,8 @@ export class Driver implements Debuggable, UpdateSource {
|
|||
// Therefore, we keep clients on their current version (even if broken) and ensure that no new
|
||||
// clients will be assigned to it.
|
||||
|
||||
// TODO: notify affected apps.
|
||||
// Notify clients that are using this broken version about the failure.
|
||||
await this.notifyClientsAboutVersionFailure(brokenHash, err);
|
||||
|
||||
// The action taken depends on whether the broken manifest is the active (latest) or not.
|
||||
// - 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 {
|
|||
);
|
||||
}
|
||||
|
||||
async notifyClientsAboutVersionFailure(brokenHash: string, error: Error): Promise<void> {
|
||||
await this.initialized;
|
||||
|
||||
// Find all clients using the broken version
|
||||
const affectedClients = Array.from(this.clientVersionMap.entries())
|
||||
.filter(([clientId, hash]) => hash === brokenHash)
|
||||
.map(([clientId]) => clientId);
|
||||
|
||||
await Promise.all(
|
||||
affectedClients.map(async (clientId) => {
|
||||
const client = await this.scope.clients.get(clientId);
|
||||
if (client) {
|
||||
const brokenVersion = this.versions.get(brokenHash);
|
||||
client.postMessage({
|
||||
type: 'VERSION_FAILED',
|
||||
version: this.mergeHashWithAppData(brokenVersion!.manifest, brokenHash),
|
||||
error: errorToString(error),
|
||||
});
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
async broadcast(msg: Object): Promise<void> {
|
||||
const clients = await this.scope.clients.matchAll();
|
||||
clients.forEach((client) => {
|
||||
|
|
|
|||
|
|
@ -2564,6 +2564,7 @@ import {envIsSupported} from '../testing/utils';
|
|||
expect(await makeRequest(scope, '/foo.hash.js', 'client-2')).toBeNull();
|
||||
expect(mockClient2.messages).toEqual([
|
||||
jasmine.objectContaining({type: 'UNRECOVERABLE_STATE'}),
|
||||
jasmine.objectContaining({type: 'VERSION_FAILED'}),
|
||||
]);
|
||||
|
||||
// This should also enter the `SW` into degraded mode, because the broken version was the
|
||||
|
|
|
|||
Loading…
Reference in a new issue