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 -
--launcherscaffolds a home screen for the device's launcher slot. see launchers and overlays. -
--overlayscaffolds a system overlay, drawn over every webapp. -
--extensionadds a deno process on the desktop. combines with either. see extensions. --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.
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 }; -
kindis'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.
| surface | ios | android | macos | linux | windows | notes |
|---|---|---|---|---|---|---|
| asset | yes | yes | yes | yes | yes | |
| audio | yes | yes | yes | yes | yes | |
| bluetooth | yes | yes | yes | yes | yes | |
| capabilities | yes | yes | yes | yes | yes | |
| config | yes | yes | yes | yes | yes | |
| doc | yes | yes | yes | yes | yes | |
| forward | no | no | yes | yes | yes | available.forward is true while an extension for the active webapp is running. |
| geo | yes | yes | yes | yes | yes | available.geo reflects the location grant on the companion. |
| hardware | yes | yes | yes | yes | yes | |
| library | yes | yes | yes | yes | yes | spotify everywhere, apple music on ios. capabilities.musicProvider names the one in use. |
| lyrics | yes | yes | yes | yes | yes | resolves through the music provider, so a lookup can answer notSupported. |
| net | yes | yes | yes | yes | yes | |
| notifications | yes | yes | no | yes | no | ancs on ios, the notification listener on android, freedesktop on linux. |
| peer | yes | yes | yes | yes | yes | |
| phone | yes | yes | no | no | no | |
| player | yes | yes | yes | yes | yes | transferTo needs available.playbackTargets, which depends on the music provider. |
| store | yes | yes | yes | yes | yes | |
| system | yes | yes | yes | yes | yes | |
| time | yes | yes | yes | yes | yes | |
| voice | yes | yes | yes | yes | yes | a turn resolves with stage: noModel until the companion has the voice model installed. |
| webapp | yes | yes | yes | yes | yes |
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, and
moreLikeThis.
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 with
error.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. see
client.voice for the full signatures.
d8 - surfaces
21 surfaces on client.<name>
client.asset Binary assets such as cover art, addressed by an opaque id.
client.audio Volume, mute, speech, and short sounds on the device.
client.bluetooth Bluetooth pairing and connection state.
client.capabilities What the connected companion app can do.
client.config Read-only settings the companion app holds for the active webapp.
client.doc Key/value state the active webapp shares with the companion app.
client.forward client.geo Position fixes from the connected phone.
client.hardware The display backlight and the ambient light sensor.
client.library The music library on the connected phone.
client.lyrics client.net HTTP, WebSocket, and streaming network access for a webapp, proxied through the connected companion app.
client.notifications Phone notifications mirrored to a webapp.
client.peer Every device the daemon knows about, with its pairing and companion-app connection state.
client.phone Call state from the connected phone.
client.player Playback state and transport for a webapp.
client.store client.system Device identity, health, logs, and power control for a webapp.
client.time Wall clock, locale, and timezone for a webapp.
client.voice Voice capture and intent results for a webapp.
client.webapp The webapps installed on the device.