🚀 Notihub v1.3.6
Release Title: v1.3.6 - Messenger Notifications Actually Say What They're About
🎯 What this fixes
Messenger banners said "1 new message" with no sender and no text — and two messages only ever produced one banner. If you've seen the menu bar badge stop at 1 no matter how many messages actually arrived, this is why, and it's fixed.
🔍 The bug
Messenger runs its entire messaging stack inside a SharedWorker and raises its notifications from there. Measured directly in the running app:
| Realm | window.Notification |
|---|---|
| Messenger's page | our shim — intercepted |
Messenger's SharedWorker |
function Notification() { [native code] } — never seen |
Every real notification went straight past us. What arrived instead was the fallback: reading the unread count out of the tab title. That title counts unread conversations, not messages — two messages from the same person move the count 0 → 1 exactly once, so the badge froze at 1 and the banner had nothing but a generic count to show.
A SharedWorker also turned out to be the one JS realm nothing in this app could reach. preload.js only ever ran in the page. Electron has no preload for a shared or dedicated worker at all — the closest thing, a service-worker preload, runs in an isolated ShadowRealm (measured: self isn't even defined there), which rules that path out too.
🛠️ How it works now
The DevTools protocol reaches where preloads can't. webContents.debugger auto-attaches to every worker a service creates, with waitForDebuggerOnStart so the shim installs before the site's own script can grab a reference to the native Notification. New file: src/worker-notify.js.
That still leaves the SharedWorker itself unreachable — auto-attach only follows a page's dedicated workers, and a shared worker is a browser-level target. So the preload now hides window.SharedWorker from the page before any site script runs. Messenger's own fallback path takes over, the same messaging bundle loads as a dedicated worker instead, and that one the debugger can attach to. Verified end-to-end: firing a notification from inside that worker now reaches notify() with the real sender name and message body, and two messages in one thread arrive as two separate banners.
New per-service option in config.json, on by default:
"hideSharedWorker": trueSet it to false for a service if hiding SharedWorker ever breaks a site that has no fallback for its absence.
Also fixed: notifications with no icon at all. nativeImage only decodes PNG and JPEG. Messenger's auto-detected favicon is a .ico, and the app had been saving it over the working PNG already in config.json — the icon silently failed to decode and the banner shipped with nothing. Unusable sources are now remembered and skipped in favor of the next candidate (Google's favicon service always re-encodes to PNG). Checked across all eight bundled presets; every one now resolves to a usable image.
Smaller fixes in the same pass:
- A site's notification
iconandtagnow travel through the whole pipeline — a sender's avatar shows where available, and same-thread messages group in Notification Center instead of piling up as separate items (grouped, not deduplicated: a second message no longer risks silently replacing the first). document.hasFocus()is now shimmed alongsidedocument.hidden— a site that gates its own notification logic on focus rather than visibility saw the hidden panel as focused and stayed silent, for the same underlying reason the visibility override already existed.
✅ How this was verified
npm test: 95 reported checks, including a new 15-check suite (test/test-worker-notify.js) for the worker-realm shim in isolation — install guards, title/body/tag/icon relay, ascending ids per notification, click replay, and permission reporting.- Live simulation through the actual realm Messenger uses. Connected to the running app over the DevTools protocol, located
MAWMainV4WebWorkerBundle(Messenger's messaging worker), and fired notifications from inside it — confirmed two messages in one thread arrive as two distinct banners with real sender names and message text, not the generic unread-count line. - A real banner from the packaged v1.3.6 build, captured on screen: sender name, service name, message body, and the Messenger icon all present.
- Confirmed Messenger keeps working with
SharedWorkerhidden — 43 conversations listed, composer present, page otherwise unaffected. - A dead end recorded rather than shipped: a service-worker preload was built and measured to run in a
ShadowRealmwith no access to the worker's own scope, and removed once that was confirmed rather than left in as dead code.
📦 Download Assets & Checksums
| Asset File | Format | SHA-256 Checksum |
|---|---|---|
notihub-v1.3.6-mac.dmg |
Native macOS DMG Installer | a8512642cd71d996e41435786eff05c00978be563e78600337a2670b9b7cb4e9 |
notihub-v1.3.6-mac.zip |
Portable Zip Archive | 6d22f1f3308089a676e28e8da93274541f1e3d39a0cfe811a043771434a98077 |
These are the same values src/updater.js verifies each download against before installing.
📥 Installation Options
Method 1: Homebrew (Recommended)
brew tap thuong180702/notihub
brew install --cask notihubMethod 2: npm (CLI companion)
npm install -g @thuong180702/notihubMethod 3: DMG Installer
- Download
notihub-v1.3.6-mac.dmgbelow. - Double click to open the DMG, then drag
notihub.appintoApplications.
Method 4: Direct Zip Download
- Download
notihub-v1.3.6-mac.zip. - Unzip and move
notihub.appinto/Applications.
⬆️ Upgrading
Use Settings → Download & Install. No bundle identifier or data-directory change, so existing logins stay intact.
If DevTools is open on a service tab after upgrading, that tab falls back to the old title-count notifications until DevTools is closed — webContents.debugger and DevTools can't attach to the same tab at once. --dev no longer opens DevTools automatically for this reason; use the new --devtools flag when you actually want it open.
⚠️ Gatekeeper Notice
Notihub is signed ad-hoc, not notarized by Apple, so macOS will refuse a manual install with "notihub is damaged and can't be opened". This isn't corruption — it's Gatekeeper rejecting an unnotarized app. Clear the quarantine flag once:
xattr -dr com.apple.quarantine /Applications/notihub.appUpdates Notihub installs itself are never quarantined, so this is only needed for a fresh manual install.