feat(service-worker): close notifications and focus window on click (#25860)

- Serialize notification object before using postMessage
- Close notification on click
- Focus browser if it is not already focused on click

PR Close #25860
This commit is contained in:
Joost Zöllner 2018-09-21 11:11:06 +02:00 committed by Kara Erickson
parent f017b26e5d
commit f5d5a3df59
2 changed files with 31 additions and 7 deletions

View File

@ -33,6 +33,10 @@ const NOTIFICATION_OPTION_NAMES = [
'actions', 'badge', 'body', 'dir', 'icon', 'lang', 'renotify', 'requireInteraction', 'tag', 'actions', 'badge', 'body', 'dir', 'icon', 'lang', 'renotify', 'requireInteraction', 'tag',
'vibrate', 'data' 'vibrate', 'data'
]; ];
const NOTIFICATION_CLICK_OPTION_NAMES = [
'actions', 'badge', 'title', 'body', 'dir', 'icon', 'lang', 'renotify', 'requireInteraction',
'tag', 'vibrate', 'data'
];
interface LatestEntry { interface LatestEntry {
latest: string; latest: string;
@ -276,7 +280,7 @@ export class Driver implements Debuggable, UpdateSource {
msg.waitUntil(this.handlePush(msg.data.json())); msg.waitUntil(this.handlePush(msg.data.json()));
} }
private onClick(event: NotificationClickEvent): void { private onClick(event: NotificationEvent): void {
// Handle the click event and keep the SW alive until it's handled. // Handle the click event and keep the SW alive until it's handled.
event.waitUntil(this.handleClick(event.notification, event.action)); event.waitUntil(this.handleClick(event.notification, event.action));
} }
@ -305,10 +309,17 @@ export class Driver implements Debuggable, UpdateSource {
await this.scope.registration.showNotification(desc['title'] !, options); await this.scope.registration.showNotification(desc['title'] !, options);
} }
private async handleClick(notification: Notification, action?: string): Promise<void> { private async handleClick(notification: any, action?: string): Promise<void> {
await this.broadcast({ (notification as Notification).close();
const desc = notification as{[key: string]: string | undefined};
let options: {[key: string]: string | undefined} = {};
NOTIFICATION_CLICK_OPTION_NAMES.filter(name => desc.hasOwnProperty(name))
.forEach(name => options[name] = desc[name]);
await this.broadcastAndFocus({
type: 'NOTIFICATION_CLICK', type: 'NOTIFICATION_CLICK',
data: {action, notification}, data: {action, notification: options},
}); });
} }
@ -996,6 +1007,18 @@ export class Driver implements Debuggable, UpdateSource {
}, Promise.resolve()); }, Promise.resolve());
} }
async broadcastAndFocus(msg: Object): Promise<void> {
const clients = await this.scope.clients.matchAll();
clients.forEach((client: any) => {
if ('focus' in client) {
if (!client.focused) {
client.focus();
}
}
client.postMessage(msg);
});
}
async broadcast(msg: Object): Promise<void> { async broadcast(msg: Object): Promise<void> {
const clients = await this.scope.clients.matchAll(); const clients = await this.scope.clients.matchAll();
clients.forEach(client => { client.postMessage(msg); }); clients.forEach(client => { client.postMessage(msg); });

View File

@ -232,7 +232,7 @@ export class SwTestHarness implements ServiceWorkerGlobalScope, Adapter, Context
if (!this.eventHandlers.has('notificationclick')) { if (!this.eventHandlers.has('notificationclick')) {
throw new Error('No notificationclick handler registered'); throw new Error('No notificationclick handler registered');
} }
const event = new MockNotificationClickEvent(notification, action); const event = new MockNotificationEvent(notification, action);
this.eventHandlers.get('notificationclick') !.call(this, event); this.eventHandlers.get('notificationclick') !.call(this, event);
return event.ready; return event.ready;
} }
@ -349,8 +349,9 @@ class MockPushEvent extends MockExtendableEvent {
json: () => this._data, json: () => this._data,
}; };
} }
class MockNotificationClickEvent extends MockExtendableEvent { class MockNotificationEvent extends MockExtendableEvent {
constructor(readonly notification: Object, readonly action?: string) { super(); } constructor(private _notification: any, readonly action?: string) { super(); }
readonly notification = {...this._notification, close: () => { return; }};
} }
class MockInstallEvent extends MockExtendableEvent {} class MockInstallEvent extends MockExtendableEvent {}