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. see launchers and overlays.
  • --overlay scaffolds a system overlay, drawn over every webapp.
  • --extension adds 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 };
  • 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.

surface iosandroidmacoslinuxwindows 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>

asset 4 methods
client.asset

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

audio 13 methods
client.audio

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

bluetooth 11 methods
client.bluetooth

Bluetooth pairing and connection state.

capabilities 2 methods
client.capabilities

What the connected companion app can do.

config 3 methods
client.config

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

doc 5 methods
client.doc

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

forward 3 methods
client.forward
geo 5 methods
client.geo

Position fixes from the connected phone.

hardware 5 methods
client.hardware

The display backlight and the ambient light sensor.

library 11 methods
client.library

The music library on the connected phone.

lyrics 1 methods
client.lyrics
net 13 methods
client.net

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

notifications 6 methods
client.notifications

Phone notifications mirrored to a webapp.

peer 1 methods
client.peer

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

phone 18 methods
client.phone

Call state from the connected phone.

player 21 methods
client.player

Playback state and transport for a webapp.

store 3 methods
client.store
system 14 methods
client.system

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

time 2 methods
client.time

Wall clock, locale, and timezone for a webapp.

voice 9 methods
client.voice

Voice capture and intent results for a webapp.

webapp 7 methods
client.webapp

The webapps installed on the device.