Skip to content

v1.3.4 - Notification Permission Told Honestly

Choose a tag to compare

@Thuong180702 Thuong180702 released this 09 Aug 15:25
· 4 commits to main since this release

🚀 Notihub v1.3.4

Release Title: v1.3.4 - Notification Permission Told Honestly


🎯 What this fixes

Setup said "✓ Allowed" whether or not macOS had actually allowed anything. If you ever saw the permission step confirm itself while notifications never arrived, that checkmark was the bug — not a symptom of one.


🔍 The bug

The permission step showed a test notification and then waited 2.5 seconds:

// src/main.js — the old ofa:test-notification handler
setTimeout(() => {
  if (!cfg.notificationsOk) patchConfig({ notificationsOk: true });
  reply({ ok: true, fallback: true });
}, 2500);

Silence was treated as success. When macOS refuses, it doesn't reply — no banner appears and, often, no error event fires either. Nothing came back, the timer expired, and the app wrote down "allowed".

Three different situations produced that identical checkmark:

  • Permission genuinely denied — macOS drops the banner silently.
  • The system authorization alert was still on screen. 2.5 s routinely expired before you had clicked anything, so the app declared success on a dialog you hadn't answered yet.
  • macOS explicitly said no. patchConfig sat outside the handler's replied guard, so even after a real failed event the timer still flipped notificationsOk to true 2.5 s later. The UI showed "macOS refused" for a moment, and every visit after that showed "✓ Allowed".

Once set, nothing ever unset it. The one place that learned the truth — a real notification failing — only wrote to console.error:

// src/main.js — the old notify()
n.on('failed', (_e, err) => {
  dbg(...); console.error(...);   // and that was all
});

So turning notifications off in System Settings left the app claiming permission indefinitely.


🛠️ How it works now

The rule is that only macOS gets to say yes, and a timer expiring is not macOS saying anything. The probe is a small state machine in the new src/permissions.js with four outcomes instead of one boolean:

Status Meaning Written to disk
granted a banner actually rendered true
denied macOS refused false
pending asked, still waiting
unverified the wait elapsed with no verdict nothing

unverified is the state that used to be reported as success. It now says so plainly, in amber, next to a button that opens System Settings — because "we heard nothing" is genuinely not the same as "you're all set".

Three consequences:

  • The wait is 9 s, not 2.5 s, and it no longer concludes anything. It only changes the wording, and the probe stays open behind it — answer the macOS alert at 20 seconds and you still get a correct granted.
  • Real notifications keep the verdict honest. Every delivery reports back: one that shows confirms permission, one that fails revokes it. Turn notifications off next month and the app notices on the next message instead of insisting it has permission.
  • The stored flag is never written from a guess. Only a real show or failed moves it.

The setup window never blocks

Pressing Allow is fire-and-forget. Nothing is disabled, nothing is modal, and Continue and Skip for now stay clickable in every one of the five states — including while a probe is in flight. A probe that never resolves leaves a warning behind and nothing else.


🔒 Also fixed: web permissions could answer two different ways

Only setPermissionRequestHandler was installed. Chromium checks a permission before it requests one, and those go through separate handlers — so the unset check handler fell through to Electron's permissive default. navigator.permissions.query({name: 'geolocation'}) reported granted from that default, while the request handler stood ready to deny it. Sites got a yes, called the API, and got a no.

Both handlers now share one predicate, so they cannot disagree. Camera, microphone, screen capture, location, clipboard read, MIDI, USB, serial, HID and file-system access are all denied — this is a notification relay and none of it needs that hardware.

clipboard-sanitized-write is now explicitly allowed. Chromium checks it without ever requesting it, so it had been permitted implicitly by that same default; adding a check handler without listing it would have quietly broken every "Copy link" button in Slack, Messenger and Gmail. It permits writing sanitized content only, never reading.

Smaller fix: the global shortcut registered at startup ignored its return value, so a hotkey another app already owned failed silently. It now reports the conflict.


✅ How this was verified

  • npm test: 80 reported checks, including a new 26-check permission suite. It covers the exact failure shape — start → failed → timeout must stay denied, and a timeout must not write the stored flag whether it was previously true or false. Two of the checks read src/main.js itself, because the web-permission bug was an omission: no unit test of a module can notice a handler that was never installed.
  • A real notification through the packaged app, confirming notification SHOWN reaches the new code path and that an already-correct stored value is left alone rather than rewritten on every message.
  • All five UI states driven through the real setup.html, checking the button, the status line, the hint, and — in every state — that Continue and Skip are still usable.

📦 Download Assets & Checksums

Asset File Format SHA-256 Checksum
notihub-v1.3.4-mac.dmg Native macOS DMG Installer 1fea8163383193fe8bc71b260a11c42364a38291f4b96215b2ff41210efd4af9
notihub-v1.3.4-mac.zip Portable Zip Archive db7073a231ca649060e434368014a4b017f4f6a546928a64d5fb8880d03541b0

These match what GitHub publishes as the digest field on each asset — the same value src/updater.js verifies the download against before installing.


📥 Installation Options

Method 1: Homebrew (Recommended)

brew tap thuong180702/notihub
brew install --cask notihub

Method 2: npm (CLI companion)

npm install -g @thuong180702/notihub

Method 3: DMG Installer

  1. Download notihub-v1.3.4-mac.dmg below.
  2. Double click to open the DMG, then drag notihub.app into Applications.

Method 4: Direct Zip Download

  1. Download notihub-v1.3.4-mac.zip.
  2. Unzip and move notihub.app into /Applications.

⬆️ Upgrading

Use Settings → Download & Install. No bundle identifier or data-directory change, so existing logins stay intact.

After upgrading, check the permission row in Settings. If it now shows amber or red where it used to show a checkmark, nothing broke in this release — that is the real state of the permission, finally being reported. Press Try again, or use Open notification settings to turn it on.


⚠️ 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.app

Updates Notihub installs itself are never quarantined, so this is only needed for a fresh manual install.