Menu ▾ ▴

#13 Native new-message notifications with sender avatar + preview

closed
nobody
None
2026-06-29
2026-06-28
Anonymous
No

Originally created by: kristofferR

What & why

New Messenger messages now reach the OS notification center with the sender's name, the message preview, and their profile picture — only while Carrier is in the background — and clicking the notification opens that conversation.

Previously the page's Notification bridge forwarded just title + body to plugin:notification|notify, discarding the avatar (Facebook conveniently puts the sender's fbcdn avatar URL on the Notification's icon), firing even while you were actively reading, and doing nothing on click.

How

Investigation followed Caprine's proven model and the constraints of tauri-plugin-notification (it drops images on desktop — attachments is mobile-only, icon is ignored on macOS — and surfaces no click events).

Page (inject/messenger.js)

  • In the Notification override, render options.icon (the avatar URL) to a small PNG data URL via canvas — reusing the page's Facebook session + already-cached image, so the native side never re-fetches a possibly auth/CORS-gated URL (Caprine's trick). Best-effort: resolves to "" (→ text-only) on any failure, with a 2.5s timeout.
  • Emit a carrier:notify event {id, title, body, icon} instead of calling the plugin, gated on document.hasFocus() so it only fires when Carrier is backgrounded.
  • Keep each Notification instance in a small bounded map by id; window.__carrierNotifyClick(id) (called from Rust on click) invokes Facebook's own onclick for that notification, which opens the right conversation.

Native (src-tauri/src/lib.rs)

  • Handle carrier:notify and build the notification natively (the plugin can't attach a per-notification image or report clicks on desktop). Each notification gets a thread that blocks until the user clicks or dismisses it — it only parks (waits on a condvar / message loop), it doesn't spin. On a click it hops to the main thread, shows/focuses the window, and evals __carrierNotifyClick(id).
  • macOS — drive mac-notification-sys directly: notify-rust only reports a click when the notification carries a button, whereas wait_for_click reports a plain body click (no visible button), and content_image carries the avatar (right-side thumbnail). Its shared, id-keyed delegate makes concurrent notifications safe; the Tauri main loop pumps it.
  • Linux/Windows — notify-rust's wait_for_action; Linux adds an explicit default action (shown as no button) so a body click is reported. Avatar via the notification icon.
  • Decode the avatar data URL to a temp PNG (base64), unique-named to avoid a write/read race; the avatar temp dir is cleared at startup.

notify-rust and mac-notification-sys are already in the dependency tree via tauri-plugin-notification, so the only genuinely new crate is base64.

Platform notes / limits

  • macOS can't replace the main notification icon with the sender's photo (OS constraint without special entitlements) — the avatar shows as the thumbnail, app icon stays left.
  • Avatar is best-effort everywhere; the message always shows even if the image can't be decoded.
  • Each outstanding notification holds one parked thread until it's clicked or cleared — fine for a human-paced chat app (they self-clear on interaction).
  • Relies on Facebook firing the in-page Notification API in the WebView (it does — same as Caprine in Chromium; the bridge keeps permission = "granted"). If FB ever moved to service-worker push, a title/unread-based fallback would be the safety net (ties into [#5]).

Testing

  • cargo fmt --check, cargo clippy --all-targets -- -D warnings, cargo test --lib (29 pass, incl. tests for the avatar data-URL decoder and the NotifyMsg wire contract), node --check inject/messenger.js — all green.
  • Not yet exercised end-to-end against a live inbound message in a running build — worth a manual smoke test (incl. that clicking opens the right thread).

https://claude.ai/code/session_019Sr356EC9NsasJz5mQDDXC

Summary by CodeRabbit

  • New Features
  • Added native desktop notifications for new messages, including sender avatar images.
  • Clicking a notification now returns you to the app and opens the related conversation when available.
  • Improved support for notification actions on macOS and other desktop platforms.

  • Bug Fixes

  • Better handling of notification permissions and click behavior.
  • More reliable notification delivery using temporary image handling for avatars.

Related

Tickets: #21
Tickets: #25
Tickets: #5

Discussion

  • Anonymous

    Anonymous - 2026-06-28

    Originally posted by: coderabbitai[bot]

    Review Change Stack

    [!WARNING]

    Review limit reached

    You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

    Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

    Next review available in: 58 minutes

    Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
    You're only billed for reviews past your plan's rate limits ($0.25/file).


    How can I continue?

    After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

    To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.


    How do review limits work?

    CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

    For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

    Please refer docs for additional details.


    Review details


    ⚙️ Run configuration

    Configuration used: Path: .coderabbit.yaml

    Review profile: ASSERTIVE

    Plan: Pro

    Run ID: 69f74fbb-d101-4928-820d-bde2bdfe5b8b


    📥 Commits

    Reviewing files that changed from the base of the PR and between [1f82fa] and 9eb3a8fb8b384f6c5bcb875d3339c08bdbb65704.


    ⛔ Files ignored due to path filters (1)

    • src-tauri/Cargo.lock is excluded by !**/*.lock


    📒 Files selected for processing (3)

    • src-tauri/Cargo.toml
    • src-tauri/inject/messenger.js
    • src-tauri/src/lib.rs
    📝 Walkthrough ## Walkthrough Adds native desktop notifications for new Messenger messages. The JS injection bridge now emits a `carrier:notify` event with notification metadata and a base64 avatar data URL. The Rust backend decodes the avatar into a temp PNG file, displays a platform-specific native notification (macOS via `mac-notification-sys`, others via `notify-rust`), and routes click events back to the page via `window.__carrierNotifyClick`. ## Changes **Native Message Notification Pipeline** | Layer / File(s) | Summary | |---|---| | **Cargo dependencies**
    `src-tauri/Cargo.toml` | Adds `notify-rust`, `base64`, and (macOS-only) `mac-notification-sys` dependencies. | | **JS notification bridge rework**
    `src-tauri/inject/messenger.js` | Removes old `notify(...)` bridge, adds `openUrl(...)` bridge, reworks `CarrierNotification` to emit `carrier:notify` with avatar PNG data URL and `id`, and adds `window.__carrierNotifyClick` to route notification clicks back to Facebook's original `onclick`. | | **Rust notification implementation and wiring**
    `src-tauri/src/lib.rs` | Defines `NotifyMsg`, decodes avatar `data:` URLs to temp PNG files, clears temp cache at startup, sets macOS bundle identifier, registers `carrier:notify` event listener, and spawns per-notification threads for OS-specific display and click-to-focus handling. Includes unit tests for avatar decoding, payload parsing, and malformed input rejection. | ## Estimated code review effort 🎯 4 (Complex) | ⏱️ ~45 minutes ## Possibly related PRs - [[kristofferR/Carrier#1](https://github.com/kristofferR/Carrier/issues/1)](https://github.com/kristofferR/Carrier/pull/1): Prior work on native notification commands and behavior in `src-tauri/src/lib.rs` that this PR extends with the full avatar and click-routing pipeline. ## Poem > 🐇 Hop hop! A message appears, > With avatars decoded and clear. > A temp PNG lands on disk, > The OS notified in a whisk. > Click it! The window leaps to view — > Carrier brings your chats to you! 🎉
    🚥 Pre-merge checks | ✅ 5
    ✅ Passed checks (5 passed) | Check name | Status | Explanation | | :------------------------: | :------- | :----------------------------------------------------------------------------------------------------------------- | | Description Check | ✅ Passed | Check skipped - CodeRabbit’s high-level summary is enabled. | | Title check | ✅ Passed | The title accurately summarizes the main change: native new-message notifications with avatar and preview support. | | Docstring Coverage | ✅ Passed | Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. | | Linked Issues check | ✅ Passed | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | ✅ Passed | Check skipped because no linked issues were found for this pull request. |
    ✨ Finishing Touches
    🧪 Generate unit tests (beta) - [ ] Create PR with unit tests - [ ] Commit unit tests in branch `feat/notification-content-avatar`

    Comment @coderabbitai help to get the list of available commands.

     
  • Anonymous

    Anonymous - 2026-06-29

    Originally posted by: kristofferR

    @coderabbitai review

     
  • Anonymous

    Anonymous - 2026-06-29

    Originally posted by: coderabbitai[bot]

    ✅ Action performed Review finished. > Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.
     
  • Anonymous

    Anonymous - 2026-06-29

    Originally posted by: kristofferR

    @coderabbitai review

     
  • Anonymous

    Anonymous - 2026-06-29

    Originally posted by: coderabbitai[bot]

    ✅ Action performed Review finished. > Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.
     
  • Anonymous

    Anonymous - 2026-06-29

    Ticket changed by: kristofferR

    • status: open --> closed
     

Log in to post a comment.