LiaDevelopers

Packages

The lowest layer. Typed calls and events over Lia's frame bridge, the timeouts and limits, and mockLia() for building outside Lia.

Most apps want @lia-labs/sdk, which is built on this and re-exports all of it. Use the bridge on its own to build your own layer, or to call something the SDK doesn't wrap.

Terminal
npm install @lia-labs/bridge

#The protocol

Inside Lia, your page runs in the Lia app's web view. A call is a JSON message the page posts with window.ReactNativeWebView.postMessage:

JSON
{ "lia": 1, "id": 7, "method": "haptic", "params": { "kind": "success" } }

Lia answers each call once, through window.__lia.receive(...), with { id, ok: true, result } or { id, ok: false, error: { code, message? } }, and sends events the same way as { event, data }. The bridge defines window.__lia (frozen, once per page) and matches answers to calls by id.

#Calling

TypeScript
import { isLia, request, send } from '@lia-labs/bridge';

isLia(); // true inside the Lia app (or with mockLia)

const me = await request('init');
const { best } = await request('submitScore', { score: 1280 });
send('ready'); // without waiting for the answer; failures are dropped

request(method, params?, options?) is typed per method: the params it takes and the answer it resolves with come from the Methods type. It rejects with a LiaError:

CodeWhen
not_in_liaThere's no bridge (not inside Lia, no mock).
timeoutLia didn't answer within 20 seconds (or your timeout). Calls that wait on the person (pay, ads, dialogs, the scanner, Face ID…) have no limit unless you set one.
abortedYour signal was aborted. Lia may still act on the call.
too_largeThe message is over 16,000 characters, which Lia would ignore.
Lia's own codesWhatever Lia answered: see Error codes.
TypeScript
const controller = new AbortController();
const code = request('scanQr', { text: 'Scan a table' }, { signal: controller.signal, timeout: 60_000 });

#Listening

TypeScript
import { on, once, onAny, off } from '@lia-labs/bridge';

const stop = on('theme', ({ colorScheme, theme }) => repaint(colorScheme, theme));
once('resume', () => refresh());
onAny((event, data) => console.log(event, data));
stop();

Events are typed (Events): pause, resume, theme, viewport, fullscreenChanged, languageChanged, startParam, notificationsChanged, soundChanged, charge, ad, the four *ButtonClicked events, and sensor ({ name, values }; the SDK turns it into each sensor's reading). A listener that throws is logged and doesn't stop the others.

#Everything it exports

Export
isLia()Whether a bridge is there.
request(method, params?, options?)A typed call.
send(method, params?)A call without waiting.
on, off, once, onAnyEvents.
mockLia(options?), MOCK_INITA pretend Lia: see Local development.
emit(message)Hands a message to the page as if Lia sent it (for mocks and tests).
setDebug(on, logger?)Logs every call, answer and event.
install()Starts listening now; says whether window.__lia is this package's.
LiaError, isLiaError(error, code?)Errors.
PROTOCOL, DEFAULT_TIMEOUT, MAX_MESSAGE, PATIENTThe protocol's version and limits.
resetForTests()Forgets every call and listener.
TypesMethods, MethodName, ParamsOf, ResultOf, Events, EventName, InitResult, ThemeParams and every shape Lia sends.

#One per page

Two copies of the bridge in one bundle share one set of calls and listeners (kept on a global symbol), so nothing breaks. The script tag SDK can't share: if it's already on the page, the bridge warns and can't hear Lia's answers. Use one or the other.

Type to search every page, method and endpoint.