Eximia Download

Eximia docs

Bridge contract

The exact wire format that travels between JavaScript and native code in both directions, on both platforms. Anything that doesn't match this doc is a bug.

Audience: anyone implementing a side of the bridge (SLMEximiaBridge.kt, SLMEximiaBridge.swift, js/src/index.ts), or a plugin author who needs to reason about errors and progress.


Transports

DirectionAndroidiOS
JS → native__eximia_bridge__.exec(payload) (where __eximia_bridge__ is a @JavascriptInterface on the WebView)window.webkit.messageHandlers.eximia.postMessage(payload)
native → JSwebView.evaluateJavascript("__slmeximia_reply__(...)", null) on the UI threadwebView.evaluateJavaScript("__slmeximia_reply__(...)") on the main queue

Both transports carry the JSON envelopes defined below.

payload is always a string (stringified JSON) on Android — the @JavascriptInterface boundary doesn't pass structured types. On iOS it's delivered as Any? and we cast to [String: Any] immediately.


Envelope: exec request (JS → native)

{
  "v": 1,
  "id": "c5e6a4f3-...",
  "plugin": "Camera",
  "action": "take",
  "args": { "quality": 80, "type": "rear" }
}
FieldTypeRequiredMeaning
vintyesProtocol version. Always 1 for v1.0. Future breaking changes bump this.
idstringyesUUIDv4 generated by JS. Used to correlate response.
pluginstringyesPlugin name (matches plugin.jsonname field, or its globalAs).
actionstringyesMethod to invoke. Plugin defines this set.
argsobjectnoMethod arguments. Plain JSON, no Date/RegExp/Map etc.

If v is unknown, the native side responds with error.code = "eximia/unsupported-version".


Envelope: exec response (native → JS, success)

{
  "v": 1,
  "id": "c5e6a4f3-...",
  "kind": "ok",
  "value": { "imageData": "iVBORw0K...", "width": 1024, "height": 768 }
}
FieldTypeMeaning
vintSame as request.
idstringMatches the request's id.
kind"ok"Discriminator.
valueanyPlugin-defined result. Plain JSON only. Binary data is base64-encoded.

Envelope: exec response (native → JS, error)

{
  "v": 1,
  "id": "c5e6a4f3-...",
  "kind": "err",
  "error": {
    "code": "camera/permission-denied",
    "message": "User denied camera permission",
    "retryable": false,
    "debug": { "platform": "android", "api": 33 }
  }
}

error.code is a stable string identifier of the form <plugin-or-eximia>/<kebab-case-reason>. See error-model.md for the full taxonomy.

error.retryable is a hint: true means "calling again may succeed" (e.g. transient network), false means "calling again won't help" (e.g. denied permission).

error.debug is optional and only populated in debug builds. Never parsed by code; for human consumption only.


Envelope: progress (native → JS, optional, multiple per exec)

For long-running operations (file download, image processing), a plugin may emit progress events between request and final response:

{
  "v": 1,
  "id": "c5e6a4f3-...",
  "kind": "progress",
  "progress": { "loaded": 102400, "total": 524288 }
}

JS dispatches these to the optional onProgress callback registered when the caller used SLMEximia.exec(plugin, action, args, { onProgress: fn }).

A progress envelope is never the final reply — every exec must end with kind=ok or kind=err.


Envelope: lifecycle event (native → JS, broadcast)

The native side broadcasts OS events to JS without an id (no request correlates):

{
  "v": 1,
  "kind": "event",
  "event": "pause",
  "data": null
}

Supported events: resume, pause, online, offline, keyboardShow, keyboardHide, backbutton (Android only), deeplink, notification. See lifecycle.md for triggers and payloads.

JS subscribes via:

SLMEximia.on("pause", () => { ... });

Concurrency

The bridge supports unlimited concurrent in-flight exec calls, each keyed by its id. The native side MUST NOT serialise plugin calls unless the plugin itself opts in by declaring serial: true in its plugin manifest.

A plugin's execute() runs on:

  • Android: the IO dispatcher of the runtime's coroutine scope, unless the plugin overrides via runOn in its manifest.
  • iOS: a dedicated DispatchQueue per plugin, or Task { ... } detached, based on the same manifest hint.

The runtime guarantees the response envelope arrives on the JS thread (main thread of the WebView) so handlers can touch the DOM safely.


Encoding gotchas

TypeHow to carry
Binarybase64 string. Mark with a sibling field *Encoding: "base64".
DatesISO 8601 string. Plugins MUST NOT use Unix timestamps unless the field is named *Timestamp or *EpochMs.
null vs missingAlways be explicit; document the difference in each plugin.
Numbers > 2^53Send as strings; JS Number loses precision past 2^53.
Maps with non-string keysForbidden. Convert to array of {key, value}.

Versioning

v: 1 covers everything in this document. We bump v only when we make breaking changes to envelopes (rename fields, change the kind tags, restructure errors). New optional fields don't bump v.

Plugins MUST tolerate unknown fields in any envelope (forwards compat).


Validation in the runtime

The SLMEximia bridge SHOULD validate incoming envelopes against this contract in debug builds and emit a warning when something is off. In release builds, validation is skipped and ill-formed envelopes silently drop with a single bridge-level error logged.


Reference implementations