From c218e38abccaf39951df73eed8d43cedb51924b8 Mon Sep 17 00:00:00 2001 From: Alexander Zinchuk Date: Sun, 27 Jun 2021 19:09:18 +0300 Subject: [PATCH] Notifications: Group multiple push notifications (#1209) --- src/serviceWorker.ts | 1 - src/serviceWorker/pushNotification.ts | 62 +++++++++++++++++++++++---- 2 files changed, 53 insertions(+), 10 deletions(-) diff --git a/src/serviceWorker.ts b/src/serviceWorker.ts index 1127f99f5..80c4ba99d 100644 --- a/src/serviceWorker.ts +++ b/src/serviceWorker.ts @@ -45,7 +45,6 @@ self.addEventListener('fetch', (e: FetchEvent) => { })()); }); - self.addEventListener('push', handlePush); self.addEventListener('notificationclick', handleNotificationClick); self.addEventListener('message', handleClientMessage); diff --git a/src/serviceWorker/pushNotification.ts b/src/serviceWorker/pushNotification.ts index 5780038dc..3bc984841 100644 --- a/src/serviceWorker/pushNotification.ts +++ b/src/serviceWorker/pushNotification.ts @@ -1,4 +1,5 @@ import { APP_NAME, DEBUG } from '../config'; +import { debounce } from '../util/schedulers'; declare const self: ServiceWorkerGlobalScope; @@ -32,6 +33,7 @@ type NotificationData = { const clickBuffer: Record = {}; const shownNotifications = new Set(); +let pendingNotifications: Record = {}; function getPushData(e: PushEvent | Notification): PushData | undefined { try { @@ -91,6 +93,55 @@ function showNotification({ }); } +async function showNotifications(groupLimit: number = 1) { + const count = Object.keys(pendingNotifications).reduce((result, groupId) => { + result += pendingNotifications[Number(groupId)].length; + return result; + }, 0); + // if we have more than groupLimit notification groups we send only one notification + if (Object.keys(pendingNotifications).length > groupLimit) { + await showNotification({ + title: APP_NAME, + body: `You have ${count} new Telegram notifications`, + }); + } else { + // Else we send a notification per group + await Promise.all(Object.keys(pendingNotifications) + // eslint-disable-next-line no-async-without-await/no-async-without-await + .map(async (groupId) => { + const group = pendingNotifications[Number(groupId)]; + if (group.length > groupLimit) { + return showNotification({ + title: APP_NAME, + body: `You have ${count} notifications from ${group[0].title}`, + chatId: Number(groupId), + }); + } + return Promise.all(group.map(showNotification)); + })); + } + + // Clear all pending notifications + pendingNotifications = {}; +} + +const flushNotifications = debounce(showNotifications, 1000, false); + +async function handleNotification(notification: NotificationData, groupLimit?: number) { + // Dont show already triggered notification + if (shownNotifications.has(notification.messageId)) { + shownNotifications.delete(notification.messageId); + return; + } + + const groupId = notification.chatId || 0; + if (!pendingNotifications[groupId]) { + pendingNotifications[groupId] = []; + } + pendingNotifications[groupId].push(notification); + await flushNotifications(groupLimit); +} + export function handlePush(e: PushEvent) { if (DEBUG) { // eslint-disable-next-line no-console @@ -106,14 +157,7 @@ export function handlePush(e: PushEvent) { if (!data || data.mute === Boolean.True) return; const notification = getNotificationData(data); - - // Dont show already triggered notification - if (shownNotifications.has(notification.messageId)) { - shownNotifications.delete(notification.messageId); - return; - } - - e.waitUntil(showNotification(notification)); + e.waitUntil(handleNotification(notification)); } async function focusChatMessage(client: WindowClient, data: { chatId?: number; messageId?: number }) { @@ -181,7 +225,7 @@ export function handleClientMessage(e: ExtendableMessageEvent) { if (e.data.type === 'newMessageNotification') { // store messageId for already shown notification const notification: NotificationData = e.data.payload; - e.waitUntil(showNotification(notification)); + e.waitUntil(handleNotification(notification, 3)); shownNotifications.add(notification.messageId); } }