Eximia Download

Eximia docs

Error model

How errors propagate from a plugin → native runtime → bridge → JS, and what JS code sees.


Goals

  1. Stable codes: JS code branches on error.code, not message text.
  2. Retryability hint: every error declares whether retrying makes sense.
  3. Plugin namespacing: codes carry their origin (camera/..., eximia/...) so they don't collide.
  4. No leaking stack traces in release builds.

Wire format

The error envelope from bridge-contract.md:

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

code, message, and retryable are MANDATORY. debug is optional and populated only in debug builds (BuildConfig.DEBUG on Android, #if DEBUG on iOS).


Code taxonomy

Codes use the form <scope>/<reason> where <scope> is one of:

ScopeOwned byWhen
eximia/...The runtimeBridge/transport/lifecycle errors.
<plugin-name>/...The pluginPlugin-defined errors.

Reserved eximia/* codes

CodeMeaningretryable
eximia/unsupported-versionv in request envelope is unknownfalse
eximia/unknown-pluginPlugin lookup failedfalse
eximia/unknown-actionPlugin exists but doesn't know the actionfalse
eximia/invalid-argsargs failed plugin-side validationfalse
eximia/main-thread-blockedPlugin held the main thread too long (debug only)true
eximia/internalBridge crashed; check debug for detailstrue
eximia/timeoutPlugin call exceeded its declared timeouttrue
eximia/permission-requiredPlugin requires a permission the user hasn't grantedfalse

Plugins SHOULD prefer their own scoped codes over reusing eximia/* codes.

Plugin code conventions

Plugins SHOULD use short, kebab-case reasons:

  • camera/permission-denied
  • camera/cancelled ← user dismissed picker
  • camera/no-camera ← device has no camera
  • camera/decode-failed ← image decode failed
  • biometric/lockout
  • biometric/no-enrolled
  • geolocation/timeout
  • download/no-network
  • download/disk-full

When in doubt, mimic the codes the corresponding Cordova plugin used so JS code that already handles "FILE_NOT_FOUND" or similar continues to work after migration.


How a plugin reports an error

Kotlin

class SLMCamera : SLMEximiaPlugin() {
    override suspend fun execute(
        action: String, args: JsonObject, cb: SLMEximiaCallback
    ) {
        if (!hasCameraPermission()) {
            cb.error("camera/permission-denied", "Camera permission denied")
            return
        }
        // ...
    }
}

If the plugin throws instead of calling cb.error, the runtime catches it and maps it to:

{ "code": "<plugin>/uncaught", "message": "<exception message>", "retryable": false }

The exception's stack trace is included in debug.stack (debug builds only).

Swift

class SLMCamera: SLMEximiaPlugin {
    override func execute(
        action: String, args: [String: Any], cb: SLMEximiaCallback
    ) async {
        guard hasCameraPermission() else {
            cb.error(code: "camera/permission-denied",
                     message: "Camera permission denied")
            return
        }
        // ...
    }
}

throws from execute works equivalently; the runtime maps thrown errors to <plugin>/uncaught the same way.


How JS sees the error

try {
    const result = await SLMEximia.plugin("Camera").take({ quality: 80 });
} catch (e) {
    if (e instanceof SLMEximiaError) {
        switch (e.code) {
            case "camera/permission-denied":
                showSettingsLink();
                break;
            case "camera/cancelled":
                // user just dismissed, no-op
                break;
            default:
                if (e.retryable) retryLater();
                else surfaceUnknownError(e);
        }
    } else {
        throw e;  // not an SLMEximiaError, let it propagate
    }
}

SLMEximiaError is a class exported from @slm/eximia:

class SLMEximiaError extends Error {
    code: string;
    retryable: boolean;
    debug?: object;     // present only in debug builds
}

What about success with an error-like payload?

Some Cordova plugins call success(...) even when the operation "failed" from the user's POV (e.g. camera cancelled). SLMEximia plugins SHOULD always use error() for non-success outcomes, with codes like <plugin>/cancelled and retryable: false.

The runtime does not enforce this — it's a convention. Plugins that ignore it still work, but their JS callers have to inspect the success payload for soft failures, which is the pattern SLMEximia is designed to eliminate.


Debug payloads

In debug builds, the runtime adds context to error.debug:

{
  "platform": "android",
  "eximiaVersion": "1.0.0",
  "pluginVersion": "1.2.0",
  "androidApi": 33,
  "stack": "java.lang.SecurityException at ..."
}

In release builds, debug is absent. JS code MUST NOT depend on it.


Reporting and analytics

The runtime emits a structured log line for every error response, regardless of build flavour:

W/SLMEximia  exec failure plugin=Camera action=take id=c5e6a4f3 code=camera/permission-denied retryable=false

A future plugin can subscribe to these and ship them to a crash reporter or analytics backend; the runtime doesn't ship with one in v1.0.