diff --git a/js/service-worker.js b/js/service-worker.js index a272c7a28..4f7e3d82d 100644 --- a/js/service-worker.js +++ b/js/service-worker.js @@ -13,6 +13,9 @@ function showBgNotification(content) { const options = { body: msg.join('\n'), tag: 'nid' + content.nid, + data: { + link: content.link, + }, } return self.registration.showNotification(title, options) } @@ -88,6 +91,25 @@ self.addEventListener('push', function(event) { } }) +self.addEventListener('notificationclick', function(event) { + event.notification.close() + const link = event.notification.data && event.notification.data.link + if (!link) { + // no target: focus an existing window of this instance if any + event.waitUntil(self.clients.matchAll({ type: 'window' }).then((clientList) => { + for (const client of clientList) { + if (client.url.startsWith(self.location.origin)) { + return client.focus() + } + } + })) + return + } + // always a new tab: existing tabs may be pinned or parked there on purpose + // and the client API cannot tell those apart + event.waitUntil(self.clients.openWindow(link)) +}) + self.addEventListener('pushsubscriptionchange', function(event) { console.log('Push Subscription Change', event) event.waitUntil(self.clients.matchAll() diff --git a/lib/Push.php b/lib/Push.php index e39f4b2c4..a62d29199 100644 --- a/lib/Push.php +++ b/lib/Push.php @@ -900,7 +900,7 @@ protected function callSafelyForToken(IToken $token, string $method): ?int { * @param INotification $notification * @param int $maxLength max length of the push notification (shorter than 240 for proxy push, 3993 for webpush) * @return array - * @psalm-return array{nid: int, app: string, subject: string, type: string, id: string} + * @psalm-return array{nid: int, app: string, subject: string, type: string, id: string, link?: string} */ protected function encodeNotif(int $id, INotification $notification, int $maxLength): array { $data = [ @@ -911,6 +911,13 @@ protected function encodeNotif(int $id, INotification $notification, int $maxLen 'id' => $notification->getObjectId(), ]; + // The link lets the service worker navigate on notificationclick; the + // subject budget below shrinks accordingly, very long links are dropped + $link = $notification->getLink(); + if ($link !== '' && strlen($link) < 128) { + $data['link'] = $link; + } + // Max length of encryption is ~240, so we need to make sure the subject is shorter. // Also, subtract two for encapsulating quotes will be added. $maxDataLength = $maxLength - strlen((string)json_encode($data)) - 2; diff --git a/src/workers/service-worker.js b/src/workers/service-worker.js index a272c7a28..4f7e3d82d 100644 --- a/src/workers/service-worker.js +++ b/src/workers/service-worker.js @@ -13,6 +13,9 @@ function showBgNotification(content) { const options = { body: msg.join('\n'), tag: 'nid' + content.nid, + data: { + link: content.link, + }, } return self.registration.showNotification(title, options) } @@ -88,6 +91,25 @@ self.addEventListener('push', function(event) { } }) +self.addEventListener('notificationclick', function(event) { + event.notification.close() + const link = event.notification.data && event.notification.data.link + if (!link) { + // no target: focus an existing window of this instance if any + event.waitUntil(self.clients.matchAll({ type: 'window' }).then((clientList) => { + for (const client of clientList) { + if (client.url.startsWith(self.location.origin)) { + return client.focus() + } + } + })) + return + } + // always a new tab: existing tabs may be pinned or parked there on purpose + // and the client API cannot tell those apart + event.waitUntil(self.clients.openWindow(link)) +}) + self.addEventListener('pushsubscriptionchange', function(event) { console.log('Push Subscription Change', event) event.waitUntil(self.clients.matchAll()