package com.sptmobile.link // [impl->REQ-DEVICE-LINK-IROH] /** * Kotlin half of the JNI binding contract documented in * `rust/link-android/src/lib.rs` (JNI-PLAN W3). Every method is a BLOCKING * JSON-string-in/JSON-string-out call parked on the .so's global tokio * runtime — never call from the main thread (wrap in the dedicated IO * dispatcher, APP-PLAN Q2). Errors surface as [RuntimeException]; the one * exception is [followNext], which reports stream conditions in-band via the * envelope [FollowEvent] decodes. * * Handles: `connect`/`connectTo` return a client handle consumed by the RPC * methods and released by [close]; [digestFollow] returns a follow handle * consumed by [followNext] and released by [followClose] (which aborts the * stream — the wire-level hang-up). */ object LinkNative { init { System.loadLibrary("spt_mobile_link_android") } private val androidInitDone = java.util.concurrent.atomic.AtomicBoolean(false) /** * One-time Android runtime handoff: iroh's relay TLS verifies against the * OS trust store, which needs the JVM + app Context BEFORE any connect — * building an iroh endpoint panics otherwise (found at the W3 emulator * smoke). Every native-link entry point (activity, service, tests) calls * this first; idempotent. */ fun init(context: android.content.Context) { if (androidInitDone.compareAndSet(false, true)) { initAndroid(context.applicationContext) } } private external fun initAndroid(context: Any) /** Dial the host in `qrJson` (`{v, node, endpoint, token}`) by node key. */ external fun connect(qrJson: String): Long /** * Dial an explicit `EndpointAddr` (the host's `device link addr:` beacon * JSON) — the LAN/loopback/emulator seam. */ external fun connectTo(endpointAddrJson: String, token: String): Long /** Graceful hang-up; the handle is dead afterwards. */ external fun close(handle: Long) /** Returns `{"endpoint":"...","host_version":"..."}`. */ external fun pair(handle: Long, device: String): String /** Throws on failure, returns normally on pong. */ external fun ping(handle: Long) /** Raw `spt endpoint list --json` passthrough. */ external fun listEndpoints(handle: Long): String /** * Returns `{"outcome":"...","success":true|false}`. `success: true` is * FINAL — a SENT/QUEUED send is never re-sent (REQ-HAZARD-QUEUED-RETRY). */ external fun send( handle: Long, target: String, from: String, jsonPayload: String, body: String, ): String /** * Raw snapshot JSON. `after < 0` means no cursor (first load); otherwise * the view's cursor for a re-sync (REQ-DIGEST-CURSOR). */ external fun digestSnapshot(handle: Long, endpoint: String, after: Long): String external fun historyFetch(handle: Long, endpoint: String, limit: Int): String /** * Union of host-side history across EVERY endpoint this host logs, as a * JSON array of `{endpoint, entries:[…]}` slices (Messages tab). One host * is one gateway. `limit: 0` = all; otherwise tails each endpoint's log. */ external fun historyFetchAll(handle: Long, limit: Int): String external fun spoolDrain(handle: Long, device: String): String /** Opens a follow stream; returns a follow handle. */ external fun digestFollow(handle: Long, endpoint: String): Long /** * Blocks up to `timeoutMs` for the next stream event; returns the * envelope decoded by [FollowEvent.decode]. A timeout cancels only the * channel receive — never a half-read frame — so looping is safe. */ external fun followNext(followHandle: Long, timeoutMs: Long): String /** Aborts the stream pump (the wire-level hang-up); handle dead after. */ external fun followClose(followHandle: Long) }