Menu ▾ ▴

#176 macOS: carrier:// URL scheme for automation

closed
nobody
2026-08-16
2026-07-22
Anonymous
No

Originally created by: kristofferR

CFBundleURLTypes and RunEvent::Opened are completely unused today. A carrier:// scheme enables Shortcuts/Raycast/browser automation: carrier://t/<id> (open thread), carrier://compose, carrier://settings.

  • Registration: raw Info.plist + core RunEvent::Opened, no plugin. tauri-plugin-deep-link's value is Windows/Linux runtime registration and a JS API — not needed for macOS-only scope. Tauri core already delivers application:openURLs: as RunEvent::Opened { urls } on macOS. Add CFBundleURLTypes (scheme carrier, role Viewer) to the hand-maintained src-tauri/Info.plist. Launch Services routes opens to the running instance directly — single-instance/argv is not involved.
  • Strict parsing in a new shared src-tauri/src/actions.rs: AppAction { NewConversation, Settings, OpenThread(String) } + parse_carrier_url(&Url) -> Option<AppAction>. Reject any URL with query/fragment/userinfo/port; thread ids are 1–32 ASCII digits, validated via one consolidated helper — validated_thread_path already exists in notifications.rs and the digits check is currently duplicated in menu.rs; consolidate to one pub(crate) fn rather than adding a fourth copy. Nothing from the URL is ever eval'd except the validated digits, JSON-encoded (same discipline as open_recent_thread). Invalid URLs → warn log (truncated, never echoed verbatim).
  • Dispatch: run_app_action(app, action) — the same shape as [#170]'s CLI-flags design; [#170]'s CliAction should become this shared enum (Ref [#170]). Settings runs immediately; NewConversation/OpenThread need the injected page hooks, so: add a pending_action slot + messenger_loaded flag to AppState, drained by the existing on_page_load closure in window.rs (Finished on a Messenger URL). Warm path evals immediately with a bounded page-side retry IIFE (~500 ms × 20, hooks may still be registering); cold start (Launch Services starts the app with a URL) stores into the slot, last-writer-wins. Best-effort, documented — same stance as [#170].
  • Event-loop hook in lib.rs next to the existing Reopen arm: on Opened { urls }, dispatch the first valid URL, ignore the rest (any app can hand us an arbitrary vector).
  • Testing: table tests for parse_carrier_url (trailing slash, non-numeric, >32 digits, query/fragment, empty id, wrong scheme/host); manual — open "carrier://t/<real id>" warm and cold, compose, settings, malformed URLs do nothing.

Part of [#179].

Related

Tickets: #170
Tickets: #179
Tickets: #184
Tickets: #230

Discussion

  • Anonymous

    Anonymous - 2026-08-16

    Originally posted by: kristofferR

    Shipped in [#230], released in v1.9.0.

    carrier://compose, carrier://settings, and carrier://t/<id> are handled through RunEvent::Opened with CFBundleURLTypes in the hand-maintained Info.plist — no deep-link plugin. Parsing lives in actions.rs and rejects query, fragment, userinfo, port, and non-digit or over-long ids; only validated digits reach the page, JSON-encoded. Cold starts store the action in the pending slot and the page-load callback drains it, which is also what keeps an action alive across a window recreation.

     

    Related

    Tickets: #230

  • Anonymous

    Anonymous - 2026-08-16

    Ticket changed by: kristofferR

    • status: open --> closed
     

Log in to post a comment.