feat(service-worker): handle 'notificationclick' events (#25860)
The previous version did not support the 'notificationclick' event. Add event handler for the event and provide an observable of clicked notifications in the SwPush service. Closes #20956, #22311 PR Close #25860
This commit is contained in:
parent
ea0a99610d
commit
cf6ea283bb
|
@ -25,6 +25,8 @@ export class SwPush {
|
|||
*/
|
||||
readonly messages: Observable<object>;
|
||||
|
||||
readonly messagesClicked: Observable<object>;
|
||||
|
||||
/**
|
||||
* Emits the currently active
|
||||
* [PushSubscription](https://developer.mozilla.org/en-US/docs/Web/API/PushSubscription)
|
||||
|
@ -45,12 +47,16 @@ export class SwPush {
|
|||
constructor(private sw: NgswCommChannel) {
|
||||
if (!sw.isEnabled) {
|
||||
this.messages = NEVER;
|
||||
this.messagesClicked = NEVER;
|
||||
this.subscription = NEVER;
|
||||
return;
|
||||
}
|
||||
|
||||
this.messages = this.sw.eventsOfType<PushEvent>('PUSH').pipe(map(message => message.data));
|
||||
|
||||
this.messagesClicked =
|
||||
this.sw.eventsOfType('NOTIFICATION_CLICK').pipe(map((message: any) => message.data));
|
||||
|
||||
this.pushManager = this.sw.registration.pipe(map(registration => registration.pushManager));
|
||||
|
||||
const workerDrivenSubscriptions = this.pushManager.pipe(switchMap(pm => pm.getSubscription()));
|
||||
|
|
|
@ -303,6 +303,26 @@ import {async_fit, async_it} from './async';
|
|||
]);
|
||||
});
|
||||
});
|
||||
describe('messagesClicked', () => {
|
||||
it('receives notification clicked messages', () => {
|
||||
const sendMessage = (type: string, message: string) =>
|
||||
mock.sendMessage({type, data: {message}});
|
||||
|
||||
const receivedMessages: string[] = [];
|
||||
push.messagesClicked.subscribe(
|
||||
(msg: {message: string}) => receivedMessages.push(msg.message));
|
||||
|
||||
sendMessage('NOTIFICATION_CLICK', 'this was a click');
|
||||
sendMessage('NOT_OTIFICATION_CLICK', 'this was not a click');
|
||||
sendMessage('NOTIFICATION_CLICK', 'this was a click too');
|
||||
sendMessage('KCILC_NOITACIFITON', 'this was a KCILC_NOITACIFITON message');
|
||||
|
||||
expect(receivedMessages).toEqual([
|
||||
'this was a click',
|
||||
'this was a click too',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('subscription', () => {
|
||||
let nextSubEmitResolve: () => void;
|
||||
|
@ -367,6 +387,7 @@ import {async_fit, async_it} from './async';
|
|||
|
||||
it('does not crash on subscription to observables', () => {
|
||||
push.messages.toPromise().catch(err => fail(err));
|
||||
push.messagesClicked.toPromise().catch(err => fail(err));
|
||||
push.subscription.toPromise().catch(err => fail(err));
|
||||
});
|
||||
|
||||
|
|
|
@ -88,6 +88,7 @@ const serverUpdate =
|
|||
scope.clients.getMock('default') !.queue.subscribe(msg => { mock.sendMessage(msg); });
|
||||
|
||||
mock.messages.subscribe(msg => { scope.handleMessage(msg, 'default'); });
|
||||
mock.messagesClicked.subscribe(msg => { scope.handleMessage(msg, 'default'); });
|
||||
|
||||
mock.setupSw();
|
||||
reg = mock.mockRegistration !;
|
||||
|
@ -128,5 +129,18 @@ const serverUpdate =
|
|||
});
|
||||
await gotPushNotice;
|
||||
});
|
||||
|
||||
async_it('receives push message click events', async() => {
|
||||
const push = new SwPush(comm);
|
||||
scope.updateServerState(serverUpdate);
|
||||
|
||||
const gotNotificationClick = (async() => {
|
||||
const event = await obsToSinglePromise(push.messagesClicked);
|
||||
expect(event).toEqual({action: 'clicked', notification: {clicked: true}});
|
||||
})();
|
||||
|
||||
await scope.handleClick({clicked: true}, 'clicked');
|
||||
await gotNotificationClick;
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
|
|
@ -28,6 +28,7 @@ export class MockServiceWorkerContainer {
|
|||
mockRegistration: MockServiceWorkerRegistration|null = null;
|
||||
controller: MockServiceWorker|null = null;
|
||||
messages = new Subject();
|
||||
messagesClicked = new Subject();
|
||||
|
||||
addEventListener(event: 'controllerchange'|'message', handler: Function) {
|
||||
if (event === 'controllerchange') {
|
||||
|
|
|
@ -159,6 +159,7 @@ export class Driver implements Debuggable, UpdateSource {
|
|||
this.scope.addEventListener('fetch', (event) => this.onFetch(event !));
|
||||
this.scope.addEventListener('message', (event) => this.onMessage(event !));
|
||||
this.scope.addEventListener('push', (event) => this.onPush(event !));
|
||||
this.scope.addEventListener('notificationclick', (event) => this.onClick(event !));
|
||||
|
||||
// The debugger generates debug pages in response to debugging requests.
|
||||
this.debugger = new DebugHandler(this, this.adapter);
|
||||
|
@ -275,6 +276,11 @@ export class Driver implements Debuggable, UpdateSource {
|
|||
msg.waitUntil(this.handlePush(msg.data.json()));
|
||||
}
|
||||
|
||||
private onClick(event: NotificationClickEvent): void {
|
||||
// Handle the click event and keep the SW alive until it's handled.
|
||||
event.waitUntil(this.handleClick(event.notification, event.action));
|
||||
}
|
||||
|
||||
private async handleMessage(msg: MsgAny&{action: string}, from: Client): Promise<void> {
|
||||
if (isMsgCheckForUpdates(msg)) {
|
||||
const action = (async() => { await this.checkForUpdate(); })();
|
||||
|
@ -299,6 +305,13 @@ export class Driver implements Debuggable, UpdateSource {
|
|||
await this.scope.registration.showNotification(desc['title'] !, options);
|
||||
}
|
||||
|
||||
private async handleClick(notification: Notification, action?: string): Promise<void> {
|
||||
await this.broadcast({
|
||||
type: 'NOTIFICATION_CLICK',
|
||||
data: {action, notification},
|
||||
});
|
||||
}
|
||||
|
||||
private async reportStatus(client: Client, promise: Promise<void>, nonce: number): Promise<void> {
|
||||
const response = {type: 'STATUS', nonce, status: true};
|
||||
try {
|
||||
|
|
|
@ -83,6 +83,8 @@ interface PushEvent extends ExtendableEvent {
|
|||
data: PushMessageData;
|
||||
}
|
||||
|
||||
interface NotificationClickEvent extends NotificationEvent, ExtendableEvent {}
|
||||
|
||||
interface PushMessageData {
|
||||
arrayBuffer(): ArrayBuffer;
|
||||
blob(): Blob;
|
||||
|
@ -114,6 +116,7 @@ interface ServiceWorkerGlobalScope {
|
|||
addEventListener(event: 'fetch', fn: (event?: FetchEvent) => any): void;
|
||||
addEventListener(event: 'install', fn: (event?: ExtendableEvent) => any): void;
|
||||
addEventListener(event: 'push', fn: (event?: PushEvent) => any): void;
|
||||
addEventListener(event: 'notificationclick', fn: (event?: NotificationClickEvent) => any): void;
|
||||
addEventListener(event: 'sync', fn: (event?: SyncEvent) => any): void;
|
||||
|
||||
fetch(request: Request|string): Promise<Response>;
|
||||
|
|
|
@ -589,6 +589,16 @@ const manifestUpdateHash = sha1(JSON.stringify(manifestUpdate));
|
|||
}]);
|
||||
});
|
||||
|
||||
async_it('broadcasts notification click events', async() => {
|
||||
expect(await makeRequest(scope, '/foo.txt')).toEqual('this is foo');
|
||||
await driver.initialized;
|
||||
await scope.handleClick({title: 'This is a test', body: 'Test body'}, 'button');
|
||||
expect(scope.clients.getMock('default') !.messages).toEqual([{
|
||||
type: 'NOTIFICATION_CLICK',
|
||||
data: {action: 'button', notification: {title: 'This is a test', body: 'Test body'}}
|
||||
}]);
|
||||
});
|
||||
|
||||
async_it('prefetches updates to lazy cache when set', async() => {
|
||||
expect(await makeRequest(scope, '/foo.txt')).toEqual('this is foo');
|
||||
await driver.initialized;
|
||||
|
|
|
@ -228,6 +228,15 @@ export class SwTestHarness implements ServiceWorkerGlobalScope, Adapter, Context
|
|||
return event.ready;
|
||||
}
|
||||
|
||||
handleClick(notification: Object, action?: string): Promise<void> {
|
||||
if (!this.eventHandlers.has('notificationclick')) {
|
||||
throw new Error('No notificationclick handler registered');
|
||||
}
|
||||
const event = new MockNotificationClickEvent(notification, action);
|
||||
this.eventHandlers.get('notificationclick') !.call(this, event);
|
||||
return event.ready;
|
||||
}
|
||||
|
||||
timeout(ms: number): Promise<void> {
|
||||
const promise = new Promise<void>(resolve => {
|
||||
this.timers.push({
|
||||
|
@ -340,6 +349,9 @@ class MockPushEvent extends MockExtendableEvent {
|
|||
json: () => this._data,
|
||||
};
|
||||
}
|
||||
class MockNotificationClickEvent extends MockExtendableEvent {
|
||||
constructor(readonly notification: Object, readonly action?: string) { super(); }
|
||||
}
|
||||
|
||||
class MockInstallEvent extends MockExtendableEvent {}
|
||||
|
||||
|
|
Loading…
Reference in New Issue