Implement notification deduplication with collapseKey to prevent stacking across messages/conversations.

This commit is contained in:
MartinBraquet
2026-07-30 01:00:58 +02:00
parent 38b83d3323
commit 1a524345aa
4 changed files with 12 additions and 1 deletions

View File

@@ -186,6 +186,7 @@ const notifyOtherUserInChannelIfInactive = async (
title: `${creator.name}`,
body: textContent,
url: `/messages/${channelId}`,
collapseKey: `channel-${channelId}`,
}
try {
await sendWebNotifications(pg, receiverId, JSON.stringify(payload))

View File

@@ -78,6 +78,7 @@ export const updateConnectionInterests: APIHandler<'update-connection-interest'>
},
),
url: `/${currentUser.username}`,
collapseKey: `connection-${currentUser.id}`,
}
try {
await sendWebNotifications(pg, targetUserId, JSON.stringify(payload))

View File

@@ -89,6 +89,9 @@ interface PushPayload {
url: string
data?: Record<string, string>
imageUrl?: string
// Notifications sharing a collapseKey replace each other instead of stacking, so a
// conversation only ever occupies one slot in the tray. See sendPushToToken.
collapseKey?: string
}
export async function sendPushToToken(
@@ -100,10 +103,14 @@ export async function sendPushToToken(
const message: TokenMessage = {
token,
android: {
// FCM-side: if the device is offline, only the latest message of a conversation is delivered
collapseKey: payload.collapseKey,
notification: {
title: payload.title,
body: payload.body,
imageUrl: payload.imageUrl || undefined, // 👈 publicly accessible HTTPS URL
// Tray-side: a new notification replaces the previous one carrying the same tag
tag: payload.collapseKey,
},
},
data: {

View File

@@ -42,7 +42,9 @@ export default function AndroidPush() {
if (endpoint === window.location.pathname) return
const author = notif?.title
const message = notif?.body
toast.success(`${author}: "${message}"`)
// Reuse the endpoint as the toast id so successive messages from the same
// conversation replace each other rather than piling up.
toast.success(`${author}: "${message}"`, {id: endpoint})
})
}, [user?.id, isAndroid])