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:
hellowith a stale nonce is ignored.invokewith a stale nonce fails withSTALE_NONCE.cancelwith 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 withINVALID_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#
- Envelope size check (max 1 MiB encoded) and strict parse.
- Nonce check (
STALE_NONCE). - Handshake check (
INVALID_REQUESTbeforehello). - Request-id uniqueness (
INVALID_REQUEST). - Registry lookup (
UNKNOWN_COMMAND). - Capability evaluation — before payload deserialization (
CAPABILITY_DENIED). - In-flight limit, default 128/window (
LIMIT_EXCEEDED). - Payload deserialization (
SERIALIZATION_ERROR,PAYLOAD_TOO_LARGE,INVALID_REQUESTwhen a required payload is missing). - 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)#
| Limit | Default |
|---|---|
| Max encoded incoming message | 1 MiB |
| Max in-flight invocations per window | 128 |
| Max command duration | 30 s default; validated per-command override up to 24 h |
| Max queued events per window | 256 |
| Max command/event name length | 128 chars |
| Max JSON nesting depth | 64 |
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).