index ↓

webapp sdk

a webapp is one page in the car thing's kiosk. it reaches the daemon through one typed client,@bridgething/client.

d1 - start a project

react, vite, tailwind, and this client come preinstalled.

bun create bridgething my-app
  • --launcher scaffolds a home screen for the device's launcher slot. seelaunchers and overlays.
  • --overlay scaffolds a system overlay, drawn over every webapp.
  • --extension adds a deno process on the desktop. combines with either. seeextensions.
  • --no-install, --no-git

every system overlay is on by default. turn one off in the manifest when you draw it yourself.

{
  "overlays": { "volume": false }
}

source and issues on github.

d2 - settings pages and extensions

a settings page is one html file in your bundle that the companion app or the desktop app renders, with its own sdk for config,fetch, and oauth.

an extension is a deno process the desktop app runs for your webapp, with host access. your webapp reaches it over client.forward.

d3 - connect

construct the client once and reuse it.

import { BridgethingClient } from '@bridgething/client';

const client = new BridgethingClient(); // auto-connects, auto-reconnects

d4 - the three call shapes

events

the daemon pushes, you subscribe.

const off = client.player.onSnapshot(r => render(r.state));
off(); // stop listening

requests

you ask, the daemon answers with a tagged result.

const res = await client.player.stateGet();
if (res.ok) render(res.response.state);

commands

resolves once the daemon has the message. a failure comes back later as an error response on client.on, not on the promise.

await client.player.skipNext();

d5 - request results

a request resolves to a tagged result. narrow it on ok.

type TypedRequestResult<Response, Error> =
  | { ok: true; response: Response }
  | { ok: false; kind: 'domain'; error: Error }
  | { ok: false; kind: 'protocol'; error: WireError };
  • kind is 'domain' for a failure the operation defines, such as an asset that is missing, or 'protocol' when the daemon could not dispatch the request.

every request also accepts an optional second argument { timeoutMs }.

type WireError =
  | { type: 'unsupported' }
  | { type: 'unimplemented' }
  | { type: 'malformed'; data: { reason: string } }
  | { type: 'handlerFailed'; data: { reason: string } };

d6 - what is available where

some surfaces depend on the connected companion app, and companions differ.

branch on client.capabilities, which reports what the connected companion supplies.

surfaceiosandroidmacoslinuxwindowsnotes
assetyesyesyesyesyes
audioyesyesyesyesyes
bluetoothyesyesyesyesyes
capabilitiesyesyesyesyesyes
configyesyesyesyesyes
docyesyesyesyesyes
forwardnonoyesyesyesavailable.forward is true while an extension for the active webapp is running.
geoyesyesyesyesyesavailable.geo reflects the location grant on the companion.
hardwareyesyesyesyesyes
libraryyesyesyesyesyesspotify everywhere, apple music on ios. capabilities.musicProvider names the one in use.
lyricsyesyesyesyesyesresolves through the music provider, so a lookup can answer notSupported.
netyesyesyesyesyes
notificationsyesyesnoyesnoancs on ios, the notification listener on android, freedesktop on linux.
peeryesyesyesyesyes
phoneyesyesnonono
playeryesyesyesyesyestransferTo needs available.playbackTargets, which depends on the music provider.
storeyesyesyesyesyes
systemyesyesyesyesyes
timeyesyesyesyesyes
voiceyesyesyesyesyesa turn resolves with stage: noModel until the companion has the voice model installed.
webappyesyesyesyesyes

macos, linux, and windows are the bridgething desktop app. ios and android are the companion phone app.

d7 - voice

the companion app transcribes the mic and resolves an intent. the daemon dispatches it.

client.voice.onActivity(a => {
  // a.phase     'idle' | 'listening' | 'thinking' | 'done' | 'failed'
  // a.transcript the recognized text, once there is one
  // a.intent     the resolved intent name, e.g. 'PAUSE'
  // a.slots      its parsed arguments
  // a.stage      'fastPath' | 'model' | 'rejectedNoIntent' | 'rejectedClarify' | 'noModel'
  // a.target     'playback' | 'device' | 'phone' | 'display' | 'webappSwitch'
  // a.error      { code, msg } when the turn ended without dispatching
  render(a);
});

display intents

the daemon hands three intents to the active webapp to draw: search, showView, andmoreLikeThis.

client.voice.onIntent(i => {
  // the spoken words are i.transcript. i.slots.target is the entity the nlu
  // found in them, when it found one.
  if (i.intent === 'search') return showSearch(i.slots.target ?? i.transcript, i.slots.targetType);
  if (i.intent === 'showView') return navigate(i.slots.view);
  if (i.intent === 'moreLikeThis') return showSimilar(i.slots);
});

declare rendersVoiceDisplay in your manifest to receive them. otherwise the turn fails witherror.code: 'notDispatchable'.

{
  "rendersVoiceDisplay": true
}

driving the mic

await client.voice.pushToTalk(); // start a turn
await client.voice.release();    // stop capturing, let the turn finish
await client.voice.cancel();     // drop the turn

await client.voice.muteMic({ preserve: true });
await client.voice.unmuteMic({ preserve: true });

const res = await client.voice.stateGet();
client.voice.onStateChanged(s => setMic(s.muted, s.capturing, s.phase));

preserve keeps the mute in place after the turn ends. seeclient.voice for the full signatures.

d8 - surfaces

22 surfaces on client.<name>

asset4 methods
client.asset

Binary assets such as cover art, addressed by an opaque id.

audio13 methods
client.audio

Volume, mute, speech, and short sounds on the device.

blob2 methods
client.blob
bluetooth11 methods
client.bluetooth

Bluetooth pairing and connection state.

capabilities2 methods
client.capabilities

What the connected companion app can do.

config3 methods
client.config

Read-only settings the companion app holds for the active webapp.

doc5 methods
client.doc

Key/value state the active webapp shares with the companion app.

forward3 methods
client.forward
geo5 methods
client.geo

Position fixes from the connected phone.

hardware5 methods
client.hardware

The display backlight and the ambient light sensor.

library12 methods
client.library

The music library on the connected phone.

lyrics1 methods
client.lyrics
net13 methods
client.net

HTTP, WebSocket, and streaming network access for a webapp, proxied through the connected companion app.

notifications6 methods
client.notifications

Phone notifications mirrored to a webapp.

peer1 methods
client.peer

Every device the daemon knows about, with its pairing and companion-app connection state.

phone18 methods
client.phone

Call state from the connected phone.

player21 methods
client.player

Playback state and transport for a webapp.

store3 methods
client.store
system20 methods
client.system

Device identity, health, logs, and power control for a webapp.

time2 methods
client.time

Wall clock, locale, and timezone for a webapp.

voice9 methods
client.voice

Voice capture and intent results for a webapp.

webapp7 methods
client.webapp

The webapps installed on the device.