From d291016123e83840267c216451fe123f86c329e7 Mon Sep 17 00:00:00 2001 From: Alexander Zinchuk Date: Mon, 23 Aug 2021 03:38:36 +0300 Subject: [PATCH] Notifications: Close when read, replace same-chat instead of counter (#1413) --- src/modules/actions/apiUpdaters/chats.ts | 9 ++++- src/serviceWorker/pushNotification.ts | 44 ++++++++++++++++-------- src/util/notifications.ts | 10 +++++- 3 files changed, 46 insertions(+), 17 deletions(-) diff --git a/src/modules/actions/apiUpdaters/chats.ts b/src/modules/actions/apiUpdaters/chats.ts index 1820ee83..0d53f439 100644 --- a/src/modules/actions/apiUpdaters/chats.ts +++ b/src/modules/actions/apiUpdaters/chats.ts @@ -4,7 +4,7 @@ import { ApiUpdate, MAIN_THREAD_ID } from '../../../api/types'; import { ARCHIVED_FOLDER_ID, MAX_ACTIVE_PINNED_CHATS } from '../../../config'; import { pick } from '../../../util/iteratees'; -import { showNewMessageNotification } from '../../../util/notifications'; +import { closeMessageNotifications, showNewMessageNotification } from '../../../util/notifications'; import { updateAppBadge } from '../../../util/appBadge'; import { updateChat, @@ -42,6 +42,13 @@ addReducer('apiUpdate', (global, actions, update: ApiUpdate) => { setGlobal(newGlobal); runThrottledForUpdateAppBadge(() => updateAppBadge(selectCountNotMutedUnread(getGlobal()))); + + if (update.chat.id) { + closeMessageNotifications({ + chatId: update.chat.id, + lastReadInboxMessageId: update.chat.lastReadInboxMessageId, + }); + } break; } diff --git a/src/serviceWorker/pushNotification.ts b/src/serviceWorker/pushNotification.ts index d88e2a39..412313db 100644 --- a/src/serviceWorker/pushNotification.ts +++ b/src/serviceWorker/pushNotification.ts @@ -31,6 +31,11 @@ type NotificationData = { icon?: string; }; +type CloseNotificationData = { + lastReadInboxMessageId?: number; + chatId: number; +}; + let lastSyncAt = new Date().valueOf(); const shownNotifications = new Set(); const clickBuffer: Record = {}; @@ -87,14 +92,15 @@ async function playNotificationSound(id: number) { }); } -async function showNotification({ +function showNotification({ chatId, messageId, body, title, icon, }: NotificationData) { - const tag = String(chatId || 0); + const isFirstBatch = new Date().valueOf() - lastSyncAt < 1000; + const tag = String(isFirstBatch ? 0 : chatId || 0); const options: NotificationOptions = { body, data: { @@ -107,21 +113,29 @@ async function showNotification({ tag, vibrate: [200, 100, 200], }; - const notifications = await self.registration.getNotifications({ tag }); - if (notifications.length > 0) { - const current = notifications[0]; - const count = current.data.count + 1; - options.data.count = count; - options.data.messageId = current.data.messageId; - options.body = `You have ${count} new messages`; - current.close(); - } + return Promise.all([ playNotificationSound(messageId || chatId || 0), self.registration.showNotification(title, options), ]); } +async function closeNotifications({ + chatId, + lastReadInboxMessageId, +}: CloseNotificationData) { + const notifications = await self.registration.getNotifications(); + const lastMessageId = lastReadInboxMessageId || Number.MAX_VALUE; + notifications.forEach((notification) => { + if ( + notification.tag === '0' + || (notification.data.chatId === chatId && notification.data.messageId <= lastMessageId) + ) { + notification.close(); + } + }); +} + export function handlePush(e: PushEvent) { if (DEBUG) { // eslint-disable-next-line no-console @@ -223,16 +237,16 @@ export function handleClientMessage(e: ExtendableMessageEvent) { } } if (e.data.type === 'newMessageNotification') { - // Do not show notifications right after sync (when browser is opened) - // To avoid stale notifications - if (new Date().valueOf() - lastSyncAt < 3000) return; - // store messageId for already shown notification const notification: NotificationData = e.data.payload; // mark this notification as shown if it was handled locally shownNotifications.add(notification.messageId); e.waitUntil(showNotification(notification)); } + + if (e.data.type === 'closeMessageNotifications') { + e.waitUntil(closeNotifications(e.data.payload)); + } } self.onsync = () => { diff --git a/src/util/notifications.ts b/src/util/notifications.ts index fb987c42..270bf818 100644 --- a/src/util/notifications.ts +++ b/src/util/notifications.ts @@ -3,7 +3,7 @@ import { ApiChat, ApiMediaFormat, ApiMessage, ApiUser, } from '../api/types'; import { renderActionMessageText } from '../components/common/helpers/renderActionMessageText'; -import { DEBUG } from '../config'; +import { DEBUG, IS_TEST } from '../config'; import { getDispatch, getGlobal, setGlobal } from '../lib/teact/teactn'; import { getChatAvatarHash, @@ -378,6 +378,14 @@ export async function showNewMessageNotification({ } } +export function closeMessageNotifications(payload: { chatId: number; lastReadInboxMessageId?: number }) { + if (IS_TEST || !navigator.serviceWorker.controller) return; + navigator.serviceWorker.controller.postMessage({ + type: 'closeMessageNotifications', + payload, + }); +} + // Notify service worker that client is fully loaded export function notifyClientReady() { if (!navigator.serviceWorker.controller) return;