Request & Response
Fire-and-forget covers notifications; most real integrations also need answers. nBridge layers a promise-based request/response protocol on top of plain messages, correlated by message id.
Asking the host
const user = await bridge.sendWithResponse("getUser", { id: "42" });What happens:
- nBridge sends
{ type: "getUser", id: "1751712345678-x3k9q2f", payload: { id: "42" } }. - The host does its work and replies with the same id:
{ type: "getUser_response", id: "1751712345678-x3k9q2f", payload: { name: "Ada" } }. - The promise resolves with the reply's
payload(validated againstresponseSchemaif one is registered).
Timeouts
Every request has a deadline — defaultTimeout (5000 ms) unless overridden per call:
try {
const user = await bridge.sendWithResponse("getUser", { id: "42" }, 15_000);
} catch (err) {
// Error: Request timed out after 15000ms
}The lower-level form
sendWithResponse(type, payload, timeout) is sugar for:
const response = await bridge.send("getUser", { id: "42" }, {
expectResponse: true,
timeout: 15_000,
});
// response: { success: true, data: {...}, id: "..." }send() with expectResponse resolves with the full BridgeResponse envelope; sendWithResponse() unwraps data for you and runs response-schema validation.
Host-reported errors
By convention a host that cannot fulfil a request replies with <type>_error and an { error: string } payload (this is exactly what nBridge itself sends when one of your onWithResponse handlers throws). Both _response and _error settle the pending request — an _error reply arrives as resolved data shaped { error: string }, so check for it when your host uses the error variant:
const result = await bridge.sendWithResponse<unknown, { error?: string }>("getUser", { id: "42" });
if (result && typeof result === "object" && "error" in result) {
// host-side failure
}Answering the host
Register a responder with onWithResponse — whatever you return is sent back automatically as <type>_response, correlated to the incoming message's id:
bridge.onWithResponse("getCartState", async (payload, message) => {
const cart = await loadCart();
return { items: cart.items.length, total: cart.total };
});If the handler throws, nBridge replies with <type>_error and { error: message } instead:
bridge.onWithResponse("getLocation", async () => {
const pos = await getCurrentPosition(); // may throw
return { lat: pos.coords.latitude, lng: pos.coords.longitude };
});
// on failure the host receives:
// { type: "getLocation_error", id: "<same id>", payload: { error: "User denied Geolocation" } }Wire contract
The exact JSON the native side must produce and consume — including this response convention — is specified in the Wire Protocol reference. The Android and iOS pages show working Kotlin/Swift responders.
Notes and gotchas
- Requests bypass batching. Only fire-and-forget messages are buffered when batching is enabled;
expectResponsesends go straight out so the timeout clock is honest. - Ids are generated for you (
generateMessageId()— timestamp plus random suffix). Never reuse ids. - Reserve the suffixes. Incoming messages whose type ends in
_response/_errorand whose id matches a pending request are consumed by the correlator and are not dispatched toon()handlers. Avoid naming your own event types with those suffixes unless they are answers. - Concurrent requests are fine. Correlation is per-id, so overlapping requests of the same type resolve independently.
- Timeouts count as metrics events. When metrics are enabled, each timeout increments
timeoutsand affectssuccessRate.