From 89a49afe4cbfbffbcbce2907ca3c5fb3f0fa6325 Mon Sep 17 00:00:00 2001 From: Karem Date: Sun, 28 Jun 2026 03:45:13 +0300 Subject: [PATCH] fix(notifications): forward service-worker showNotification to the native toast MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WhatsApp Web raises some notifications through ServiceWorkerRegistration .showNotification (used when the tab is treated as backgrounded), which the existing window.Notification shim does not intercept — so those notifications never reached the OS notification center. Override the page-side prototype method to forward through the same native `notify` command (inheriting its lock/settings suppression), and stub getNotifications to resolve to [] since we render native toasts rather than in-page Notification objects. The override is installed before any page script runs (Tauri initialization_script), so WhatsApp cannot hold a pre-patch reference; it is origin-guarded and wrapped in its own try/catch so it can never abort the rest of bridge.js. Page-context calls only — notifications fired from inside the service worker's own realm are out of scope for an injected page script. Verified in a real WebKitGTK engine (origin spoofed to web.whatsapp.com): both new Notification(...) and reg.showNotification(...) now produce exactly one native notify with the right title/body; getNotifications resolves to []; the existing Notification path and permission==="granted" are unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_016o9cWBaPy4zU4BAurUVoTp --- src-tauri/resources/bridge.js | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src-tauri/resources/bridge.js b/src-tauri/resources/bridge.js index 924a019..1791976 100644 --- a/src-tauri/resources/bridge.js +++ b/src-tauri/resources/bridge.js @@ -66,6 +66,34 @@ window.Notification = ShimNotification; } catch (e) {} + // 2b) Service-worker notification path. Modern WhatsApp Web also raises notifications + // via ServiceWorkerRegistration.showNotification (e.g. when the tab is backgrounded), + // which the window.Notification shim above does NOT intercept — so those toasts never + // reached the OS. Override the page-side prototype method to forward through the same + // native `notify` command, and report an empty list from getNotifications (we render a + // native toast, so there is no in-page Notification object to hand back). We can only + // reach the page's prototype here; notifications fired from inside the service worker's + // own context are out of scope for a page-injected script. + try { + var SWR = window.ServiceWorkerRegistration; + if (SWR && SWR.prototype && typeof SWR.prototype.showNotification === "function") { + SWR.prototype.showNotification = function (title, options) { + options = options || {}; + invoke("notify", { + title: String(title || "WhatsApp"), + body: String(options.body || ""), + }); + // Real API resolves Promise; match it so callers awaiting it don't break. + return Promise.resolve(); + }; + if (typeof SWR.prototype.getNotifications === "function") { + SWR.prototype.getNotifications = function () { + return Promise.resolve([]); + }; + } + } + } catch (e) {} + // 3) Unread count — forward the raw string on change; Rust parses it. var lastTitle = ""; function report() {