window.jarvis.ipc injected automatically. This is your communication channel to the Rust backend, enabling clipboard access, file reading, panel management, and more.
Core API
The IPC bridge provides three core methods for communication:Checking Bridge Availability
The bridge is available as soon as your script runs. There is noDOMContentLoaded race because the initialization script runs before your page’s scripts.
Sending Messages to Rust
ipc.send(kind, payload)
Fire-and-forget message sending. Use this for operations that don’t need a response.send serializes { kind, payload } as JSON and posts it via window.ipc.postMessage. The Rust IPC handler validates the message against a strict allowlist before processing.
Receiving Messages from Rust
ipc.on(kind, callback)
Register a handler for messages sent from Rust to your webview.- You can register one handler per message kind
- Calling
onagain for the same kind replaces the previous handler - The callback receives the payload object as its argument
Request-Response Pattern
ipc.request(kind, payload) → Promise
For operations that need a result back from Rust. Uses an internal request ID and has a 10-second timeout.How It Works
Assign request ID
JavaScript calls
request(kind, payload). The bridge assigns payload._reqId = N (incrementing integer).Rust processes request
Rust processes the request and sends a response IPC message back to the webview, including
_reqId: N in the payload.Resolve promise
The bridge’s augmented
_dispatch function checks incoming messages for _reqId. If found, it resolves (or rejects, if payload.error is present) the matching pending Promise.Available IPC Messages
Messages You Can Send (JS → Rust)
Complete Message Reference
| Kind | Payload | Type | Description |
|---|---|---|---|
ping | {} | send | Health check. Rust replies with pong. |
clipboard_copy | { text: string } | send | Copy text to system clipboard. |
clipboard_paste | {} | request | Read clipboard contents. Returns { kind, text?, data_url? }. |
open_url | { url: string } | send | Navigate current pane to URL. URLs without scheme get https:// prepended. |
launch_game | { game: string } | send | Launch built-in game in current pane. |
open_settings | {} | send | Open Settings panel. |
open_panel | { kind: string } | send | Open new panel of given kind (e.g., “terminal”). |
panel_close | {} | send | Close current panel (won’t close last one). |
read_file | { path: string } | request | Read image file from disk. Returns { data_url } or { error }. Max 5 MB, images only. Supports ~/ expansion. |
pty_input | { data: string } | send | Send input to terminal PTY (if this pane has one). |
pty_resize | { cols: number, rows: number } | send | Resize terminal PTY. |
keybind | { key, ctrl, alt, shift, meta } | send | Simulate keybind press. |
window_drag | {} | send | Start dragging the window. |
debug_event | { type: string, ...data } | send | Log structured data to Rust tracing::info! output. |
Messages You Can Receive (Rust → JS)
| Kind | Payload | When |
|---|---|---|
pong | "pong" | After you send ping |
palette_show | { items, query, selectedIndex, mode, placeholder } | Command palette opens (handled automatically by injected script) |
palette_update | { items, query, selectedIndex, mode, placeholder } | Command palette state changes |
palette_hide | {} | Command palette closes |
Keyboard and Input Handling
How Keyboard Events Work
Keyboard events in plugins follow a layered interception model:Overlay mode
When the command palette or assistant is open, ALL keyboard input is captured by the overlay. Your plugin receives no key events during this time.
Command keys
Most Cmd/Ctrl+key combinations are intercepted by the IPC bridge’s capture-phase
keydown listener and forwarded to Rust as keybind messages. This is how Cmd+T (new pane), Cmd+W (close pane), etc. work even when a plugin is focused.Escape key
Always forwarded to Rust. If the pane is showing a plugin (tracked in
game_active), Escape navigates back to the previous page.Keys That Pass Through
These Cmd+key combinations are NOT intercepted and reach your plugin normally:Cmd+R/Ctrl+R— useful for refreshing the plugin during developmentCmd+L/Ctrl+LCmd+Q/Ctrl+QCmd+A/Ctrl+A— select allCmd+X/Ctrl+X— cutCmd+Z/Ctrl+Z— undo
Handling Escape in Your Plugin
If your plugin has modal dialogs or internal states that should close on Escape, handle it before the IPC bridge does:Security and Validation
IPC Allowlist
All IPC messages from JavaScript are validated against a strict allowlist before processing. Only thesekind values are accepted:
kind not in this list is silently dropped with a warning log:
Message Validation
The IPC handler validates that:- The raw message body is valid JSON
- The message contains a
kindfield - The
kindis in the allowlist - The payload is properly structured
Common Patterns
Persistent Data Storage
Clipboard Operations
Reading Image Files
Opening Panels
Inter-Plugin Communication
Plugins share the samelocalStorage namespace. You can use this as a simple message bus:
Debugging
Ping/Pong Test
Verify the IPC bridge is working:Debug Events
Send structured debug events to the Rust log:tracing::info! entries:
Built-in Diagnostic Logging
The IPC bridge automatically sends diagnostic events for certain DOM events:mousedownevents (with coordinates and target element)keydownevents (with key, code, and modifier state)focusandblurevents
debug_event IPC messages by the initialization script and appear in the Rust log during development.
Implementation Details
IPC Initialization Script
The IPC bridge is injected as an initialization script (IPC_INIT_SCRIPT) that runs before any page scripts. This ensures window.jarvis.ipc is available immediately.
The initialization script provides:
window.jarvis.ipc.send()— fire-and-forget messagingwindow.jarvis.ipc.on()— message handler registrationwindow.jarvis.ipc.request()— request-response pattern- Command palette overlay system
- Keyboard shortcut forwarder
- Clipboard API polyfill
- Diagnostic event logging
Message Format
Messages sent from JavaScript to Rust have this structure:Request-Response Internals
The request-response pattern works by:- Assigning a unique
_reqIdto the payload - Storing a
{ resolve, reject }Promise in_pendingRequests - Setting a 10-second timeout
- Matching incoming messages by
_reqIdand resolving/rejecting accordingly