From 935882b3b6c31aabaa8f210d9758e0a6cf83f1e1 Mon Sep 17 00:00:00 2001 From: Karem Date: Sun, 28 Jun 2026 04:07:33 +0300 Subject: [PATCH] fix(notifications): de-duplicate burst-repeated native toasts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WhatsApp can raise the same alert more than once in a quick burst (a React re-render, or one message surfacing through two notification code paths). A browser collapses those by tag, but a native OS toast stacks each call as a separate visible popup, so the user sees the same notification two or three times. Add a nativeNotify(title, body) helper that suppresses an identical title+body seen within a 3500ms window (pruning stale keys each call so the map stays bounded), and route the window.Notification shim through it. Distinct messages — including different messages from the same chat — are never suppressed, and an identical alert is allowed again once the window expires. The title/body coercion is unchanged, and lock/settings suppression still happens downstream in the Rust notify command. Verified in a real WebKitGTK engine (origin spoofed to web.whatsapp.com): 5 fires with 2 identical immediate duplicates -> 3 native notifies; distinct title/body messages all delivered; the same alert re-fires after the window expires. Note: the dedup key uses a single-space separator. An earlier iteration used a NUL byte, which (being passed to the engine as a C string) truncated the whole injected script; that was caught by the engine harness and fixed before this commit. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_016o9cWBaPy4zU4BAurUVoTp --- src-tauri/resources/bridge.js | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src-tauri/resources/bridge.js b/src-tauri/resources/bridge.js index 924a019..a9fa997 100644 --- a/src-tauri/resources/bridge.js +++ b/src-tauri/resources/bridge.js @@ -10,6 +10,28 @@ return Promise.resolve(); } + // Native OS toast with short-window de-duplication. WhatsApp can raise the same alert + // more than once in a burst (a React re-render, or one message surfacing through two + // notification code paths), and unlike a browser (which collapses repeats by tag) a + // native toast stacks every call as a separate visible popup. Suppress an identical + // title+body seen within DEDUP_MS; genuinely different messages are never affected. + // Returns true if forwarded, false if suppressed as a duplicate. + var DEDUP_MS = 3500; + var recentNotif = Object.create(null); + function nativeNotify(title, body) { + title = String(title || "WhatsApp"); + body = String(body || ""); + var now = Date.now(); + for (var k in recentNotif) { + if (now - recentNotif[k] > DEDUP_MS) delete recentNotif[k]; // prune stale keys + } + var key = title + " " + body; + if (recentNotif[key] && now - recentNotif[key] <= DEDUP_MS) return false; + recentNotif[key] = now; + invoke("notify", { title: title, body: body }); + return true; + } + // 1) Client Hints shim — navigator.userAgentData is undefined in WebKitGTK, // which WhatsApp's capability check can trip over. try { @@ -48,10 +70,7 @@ this.onclose = null; this.onerror = null; this.onshow = null; - invoke("notify", { - title: String(title || "WhatsApp"), - body: String(options.body || ""), - }); + nativeNotify(title, options.body); } ShimNotification.prototype.close = function () { if (typeof this.onclose === "function") this.onclose();