aibridgejs
v0.6.0
Published
Transport-agnostic bridge core for iframe, Flutter InAppWebView, and mock runtimes. JSON-safe request/response with AbortSignal cancellation and adapter-isolated host coupling.
Maintainers
Readme
aibridgejs
Transport-agnostic bridge core for iframe, Flutter InAppWebView, and in-memory mock runtimes. It moves JSON-safe request/response/event envelopes across an adapter while keeping host coupling outside the core.
Status: 0.6.0 - stable 1.0-track core. Root, mock, iframe, flutter, and detect subpaths are shipped.
Install
pnpm add aibridgejsimport { createBridge } from "aibridgejs";
import { createIframeAdapter } from "aibridgejs/iframe";Quick Start
const bridge = createBridge({
adapter: createIframeAdapter(window, {
targetOrigin: "https://host.example",
expectedSource: window.parent,
}),
timeoutMs: 5000,
});
bridge.on("theme/change", (payload) => {
console.log("theme", payload);
});
const user = await bridge.call<{ name: string }>("user/current");
await bridge.emit("analytics/event", { name: "opened" });Core API
createBridge({ adapter, timeoutMs? })creates a bridge around one adapter.bridge.ready({ signal }?)waits for adapter readiness.bridge.call<T>(method, payload?, { timeoutMs, signal }?)sends a request and resolves with the remote response payload.timeoutMs(default 10 s) is measured fromcall()entry and covers the readiness wait,adapter.post()and the response wait.bridge.emit(event, payload?, { signal, timeoutMs }?)sends a fire-and-forget event after readiness; the optionalsignal/timeoutMscancel or time-bound a single emit.bridge.on<T>(event, listener, { signal, once }?)subscribes to inbound events;listenermust be a function.bridge.platform()returns"iframe","flutter","mock", or"unknown".bridge.reset()rejects pendingcall()s and in-flightemit()s withBridgeResetError, cancels the current readiness round, and forgets cached readiness so the nextready()/call()/emit()re-awaitsadapter.ready(). Registeredon()listeners and the adapter subscription are untouched; onlydispose()removes them.bridge.dispose()is idempotent, permanent teardown: in-flightcall()s andemit()s reject withBridgeDisposedErrorand every listener is removed.
Adapters
| Subpath | Use | Notes |
| --- | --- | --- |
| aibridgejs/mock | Tests and local simulations | In-memory loopback; not for production traffic. |
| aibridgejs/iframe | postMessage bridges | Requires exact targetOrigin; "*" is rejected. Optional expectedSource adds source checking. |
| aibridgejs/flutter | Flutter InAppWebView | Uses window.flutter_inappwebview.callHandler; readiness is feature-checked. |
| aibridgejs/detect | Host selection | Chooses flutter/iframe/mock based on host capabilities. |
Custom adapters implement ready, post, subscribe and dispose. The signal passed to ready() is per readiness round; it aborts on reset() (reason BridgeResetError) and dispose() (reason BridgeDisposedError); adapters must detach their listeners on abort.
Sharp Edges
- Payloads must be JSON-safe. The bridge does not validate cloneability or schema; validate at app boundaries.
call()andemit()both support per-callsignalandtimeoutMs. Foremit()these are opt-in: omitted, it is unbounded but reclaimable (it waits for readiness and adapterpost()with no time bound; onlyreset()ordispose()settle it early).timeoutMs <= 0orInfinitydisables the timer,NaNfalls back to the default, and values above 2,147,483,647 are clamped to it. Pair a disabled timer with anAbortSignalif the remote side may hang.reset()rejects pendingcall()s and in-flightemit()s withBridgeResetError; registeredon()listeners and the adapter subscription are untouched, and onlydispose()removes them.- A rejected
adapter.ready()is cached: laterready()/call()/emit()reject with the same error untilreset()starts a new readiness round. - iframe security depends on exact origin allowlisting. Pass
expectedSourcewhenever same-origin pages share the channel. - Flutter readiness failures are adapter-level errors; keep native handler names stable across app releases.
- Misuse throws
BridgeErrorwith anaibridgejs:message prefix before any side effect: a missing options object, an adapter or host missing required methods, a non-function listener, or an iframetargetOriginthat is not an exact origin. - Event-listener throws are isolated and discarded by design — one misbehaving listener cannot abort fan-out to its siblings. Wrap your own handler body in
try/catchif you need to observe errors. - After
dispose(),ready(),platform(),on(), andreset()throwBridgeDisposedErrorsynchronously rather than rejecting — unlikecall()/emit(), which reject. Code that assumesbridge.ready().then(...).catch(...)will crash the caller if the bridge is already disposed.
AI Context
- Short index:
llms.txt - Full generated context:
llms-full.txt - Stability contract:
STABILITY.md - Current review backlog:
REVIEW.md - Release history:
CHANGELOG.md
License
MIT
