8000 feat(service-worker): support push subscription changes (#61856) · angular/angular@6e1df54 · GitHub
[go: up one dir, main page]

Skip to content

Commit 6e1df54

Browse files
arturovtkirjs
authored andcommitted
feat(service-worker): support push subscription changes (#61856)
In this commit, support for `pushsubscriptionchange` events has been added to the service worker Driver. When the push subscription changes, the Driver now captures the event and broadcasts a `PUSH_SUBSCRIPTION_CHANGE` message to clients. This ensures the application is aware of push events and can react accordingly. Unfortunately, it's not possible to perform any end-to-end testing of this feature. The push subscription change event exists in both Blink and Gecko. It is also supported in the latest version of Chrome, which means we can give users the ability to react to this event in order to gather feedback on whether other components might need updates. PR Close #61856
1 parent f8c1b6e commit 6e1df54

File tree

3 files changed

+67
-1
lines changed

3 files changed

+67
-1
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ export class SwPush {
5252
title: string;
5353
};
5454
}>;
55+
readonly pushSubscriptionChanges: Observable<{
56+
oldSubscription: PushSubscription | null;
57+
newSubscription: PushSubscription | null;
58+
}>;
5559
requestSubscription(options: {
5660
serverPublicKey: string;
5761
}): Promise<PushSubscription>;

packages/service-worker/src/push.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,24 @@ export class SwPush {
136136
};
137137
}>;
138138

139+
/**
140+
* Emits updates to the push subscription, including both the previous (`oldSubscription`)
141+
* and current (`newSubscription`) values. Either subscription may be `null`, depending on
142+
* the context:
143+
*
144+
* - `oldSubscription` is `null` if no previous subscription existed.
145+
* - `newSubscription` is `null` if the subscription was invalidated and not replaced.
146+
*
147+
* This stream allows clients to react to automatic changes in push subscriptions,
148+
* such as those triggered by browser expiration or key rotation.
149+
*
150+
* [Push API]: https://w3c.github.io/push-api
151+
*/
152+
readonly pushSubscriptionChanges: Observable<{
153+
oldSubscription: PushSubscription | null;
154+
newSubscription: PushSubscription | null;
155+
}>;
156+
139157
/**
140158
* Emits the currently active
141159
* [PushSubscription](https://developer.mozilla.org/en-US/docs/Web/API/PushSubscription)
@@ -159,6 +177,7 @@ export class SwPush {
159177
this.messages = NEVER;
160178
this.notificationClicks = NEVER;
161179
this.notificationCloses = NEVER;
180+
this.pushSubscriptionChanges = NEVER;
162181
this.subscription = NEVER;
163182
return;
164183
}
@@ -173,6 +192,10 @@ export class SwPush {
173192
.eventsOfType('NOTIFICATION_CLOSE')
174193
.pipe(map((message: any) => message.data));
175194

195+
this.pushSubscriptionChanges = this.sw
196+
.eventsOfType('PUSH_SUBSCRIPTION_CHANGE')
197+
.pipe(map((message: any) => message.data));
198+
176199
this.pushManager = this.sw.registration.pipe(map((registration) => registration.pushManager));
177200

178201
const workerDrivenSubscriptions = this.pushManager.pipe(

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

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,14 @@ interface LatestEntry {
6262
latest: string;
6363
}
6464

65+
// This is a bug in TypeScript, where they removed `PushSubscriptionChangeEvent`
66+
// based on the incorrect assumption that browsers don't support it.
67+
interface PushSubscriptionChangeEvent extends ExtendableEvent {
68+
// https://w3c.github.io/push-api/#pushsubscriptionchangeeventinit-interface
69+
oldSubscription: PushSubscription | null;
70+
newSubscription: PushSubscription | null;
71+
}
72+
6573
export enum DriverReadyState {
6674
// The SW is operating in a normal mode, responding to all traffic.
6775
NORMAL,
@@ -181,12 +189,18 @@ export class Driver implements Debuggable, UpdateSource {
181189
}
182190
});
183191

184-
// Handle the fetch, message, and push, notificationclick and notificationclose events.
192+
// Handle the fetch, message, and push, notificationclick,
193+
// notificationclose and pushsubscriptionchange events.
185194
this.scope.addEventListener('fetch', (event) => this.onFetch(event!));
186195
this.scope.addEventListener('message', (event) => this.onMessage(event!));
187196
this.scope.addEventListener('push', (event) => this.onPush(event!));
188197
this.scope.addEventListener('notificationclick', (event) => this.onClick(event));
189198
this.scope.addEventListener('notificationclose', (event) => this.onClose(event));
199+
this.scope.addEventListener('pushsubscriptionchange', (event) =>
200+
// This is a bug in TypeScript, where they removed `PushSubscriptionChangeEvent`
201+
// based on the incorrect assumption that browsers don't support it.
202+
this.onPushSubscriptionChange(event as PushSubscriptionChangeEvent),
203+
);
190204

191205
// The debugger generates debug pages in response to debugging requests.
192206
this.debugger = new DebugHandler(this, this.adapter);
@@ -382,6 +396,11 @@ export class Driver implements Debuggable, UpdateSource {
382396
event.waitUntil(this.handleClose(event.notification, event.action));
383397
}
384398

399+
private onPushSubscriptionChange(event: PushSubscriptionChangeEvent): void {
400+
// Handle the pushsubscriptionchange event and keep the SW alive until it's handled.
401+
event.waitUntil(this.handlePushSubscriptionChange(event));
402+
}
403+
385404
private async ensureInitialized(event: ExtendableEvent): Promise<void> {
386405
// Since the SW may have just been started, it may or may not have been initialized already.
387406
// `this.initialized` will be `null` if initialization has not yet been attempted, or will be a
@@ -510,6 +529,26 @@ export class Driver implements Debuggable, UpdateSource {
510529
});
511530
}
512531

532+
/**
533+
* Handles changes to the push subscription by capturing the old and new
534+
* subscription details and broadcasting a `PUSH_SUBSCRIPTION_CHANGE` message.
535+
*
536+
* This method is triggered when the browser invalidates an existing push
537+
* subscription and creates a new one, which can happen without user interaction.
538+
* It ensures that clients listening for service worker events are informed
539+
* of the subscription update.
540+
*
541+
* @param event - The `PushSubscriptionChangeEvent` containing the old and new subscriptions.
542+
*/
543+
private async handlePushSubscriptionChange(event: PushSubscriptionChangeEvent): Promise<void> {
544+
const {oldSubscription, newSubscription} = event;
545+
546+
await this.broadcast({
547+
type: 'PUSH_SUBSCRIPTION_CHANGE',
548+
data: {oldSubscription, newSubscription},
549+
});
550+
}
551+
513552
private async getLastFocusedMatchingClient(
514< 40FB /code>553
scope: ServiceWorkerGlobalScope,
515554
): Promise<WindowClient | null> {

0 commit comments

Comments
 (0)
0