Skip to content
JDesk
Reference

IPC protocol

The exact versioned JSON envelope and processing rules used across the JDesk bridge.

The IPC protocol version is an independent integer (v), currently 1. The WebView never receives Java objects; every message is a versioned JSON envelope over the platform bridge. Unknown top-level fields, wrong types, and unknown kinds are rejected deterministically before any user code runs.

Nonce lifecycle#

Each main-frame navigation creates a fresh navigation session with a 128-bit random nonce. The platform layer injects it into the page; the client echoes it in every envelope. A stale or wrong nonce can never reach command execution:

  • hello with a stale nonce is ignored.
  • invoke with a stale nonce fails with STALE_NONCE.
  • cancel with a stale nonce is ignored.
  • Navigation invalidates the previous session, rejects new invokes bound to it, cancels outstanding requests after a grace period, and drops late results and queued events so nothing reaches the new document.

Client → runtime#

hello

{"v":1, "kind":"hello", "client":"jdesk-client", "clientVersion":"0.1.0", "nonce":"<hex>"}

Required before any invoke. The runtime replies with helloAck. Unsupported v receives an explicit helloAck with ok:false and code PROTOCOL_VERSION_UNSUPPORTED; clients never need to infer incompatibility from a timeout.

invoke

{"v":1, "kind":"invoke", "id":"01J...", "command":"greeting.greet",
 "payload": {"name":"Ada"}, "nonce":"<hex>"}
  • id: 1..128 chars, unique within the navigation session (duplicates fail with INVALID_REQUEST).
  • command: 1..128 chars, dot-separated lowerCamel segments.
  • payload: optional; stays raw until the capability check passes.

cancel

{"v":1, "kind":"cancel", "id":"01J...", "nonce":"<hex>"}

Best-effort: the runtime interrupts the worker virtual thread and sends exactly one terminal result with code CANCELLED.

console

{"v":1, "kind":"console", "level":"error", "message":"boom at app.js:3", "nonce":"<hex>"}

Emitted by the injected console-capture script (not by application code): wrapped console.*, uncaught error events, and unhandled promise rejections. level is one of log|info|warn|error|debug (max 16 chars); message is truncated page-side at 8 KiB. Fire-and-forget — no result envelope. The nonce is checked like every other envelope, so stale or spoofed documents cannot inject log lines. The Java side strips control characters and forwards to the dev.jdesk.webview.console logger only in dev mode or when -Djdesk.console.forward=true is set; otherwise the envelope is dropped.

Runtime → client#

helloAck

{"v":1, "kind":"helloAck", "ok":true, "nonce":"<hex>"}

result (success / failure)

{"v":1, "kind":"result", "id":"01J...", "ok":true, "value":{"message":"Hello Ada"}}
{"v":1, "kind":"result", "id":"01J...", "ok":false,
 "error":{"code":"CAPABILITY_DENIED", "message":"Command is not allowed for this window"}}
{"v":1, "kind":"result", "id":"01J...", "ok":false,
 "error":{"code":"INVALID_REQUEST", "message":"Upstream rejected",
          "data":{"httpStatus":429, "retryAfterSeconds":30}}}

error.data is optional structured, public-safe data a handler attaches by throwing JDeskException(code, publicMessage, details, cause) — machine-readable facts (HTTP status, retry hints) the frontend can branch on instead of parsing message text. jdesk-client exposes it as JDeskError.data.

Exactly one terminal result per request. Results correlate by id and may complete out of order. Error code is one of the public ErrorCode names; message never contains class names, stack traces, paths, SQL, secrets, or internal exception text.

event

{"v":1, "kind":"event", "event":"download.progress", "payload":{"pct":42}}

Events from one emitter to one window preserve enqueue order. Queues are bounded with a configured overflow policy: REJECT (default), DROP_OLDEST, or COALESCE (by event name).

Processing order for invoke#

  1. Envelope size check (max 1 MiB encoded) and strict parse.
  2. Nonce check (STALE_NONCE).
  3. Handshake check (INVALID_REQUEST before hello).
  4. Request-id uniqueness (INVALID_REQUEST).
  5. Registry lookup (UNKNOWN_COMMAND).
  6. Capability evaluation — before payload deserialization (CAPABILITY_DENIED).
  7. In-flight limit, default 128/window (LIMIT_EXCEEDED).
  8. Payload deserialization (SERIALIZATION_ERROR, PAYLOAD_TOO_LARGE, INVALID_REQUEST when a required payload is missing).
  9. Handler runs on a virtual thread with a timeout (default 30 s; an explicit positive per-command override may extend this up to 24 hours; TIMEOUT).

Over-limit or malformed requests fail deterministically and never execute user code.

Limits (configurable downward only)#

LimitDefault
Max encoded incoming message1 MiB
Max in-flight invocations per window128
Max command duration30 s default; validated per-command override up to 24 h
Max queued events per window256
Max command/event name length128 chars
Max JSON nesting depth64

Compatibility policy#

  • The protocol version increments only for breaking wire changes.
  • The generated JS client declares its supported protocol range; the runtime rejects unsupported versions during hello.
  • After the first stable release, fixtures for the previous protocol version become compatibility tests (spec section 25).