Surfaces ↓
Capabilities
client.capabilities Daemon -> webapp capabilities surface. Update is the broadcast event
fired on connect + on every change; Snapshot is the typed reply to
CapabilitiesGet. Webapps that auto-react to capability change
listen on Update and don't need to call Get.
Requests
you ask, the daemon answers; await the tagged result and check .ok
get(): Promise<TypedRequestResult<CapabilitiesSnapshot, never>>
push form: onSnapshot (subscribe instead of awaiting)
One-shot query: webapp asks the daemon for the current capabilities
snapshot. The daemon also broadcasts Update on every connect and
on every change, so most webapps don't need this.
const res = await client.capabilities.get();
if (res.ok) {
console.log(res.response.capabilities);
} type Result =
| { ok: true; response: CapabilitiesSnapshot }
| { ok: false; kind: 'domain'; error: never }
| { ok: false; kind: 'protocol'; error: WireError }; Events
the daemon pushes these unprompted; subscribing returns an unsubscribe function
onUpdate(handler: (CapabilitiesSnapshot) => void): () => void
const off = client.capabilities.onUpdate((snapshot) => {
console.log(snapshot.capabilities);
});
// call off() to unsubscribe Types
shapes referenced above, as the SDK types them
Wrapper carrying one Capabilities snapshot; reply to CapabilitiesGet
and payload of the Update broadcast event.
type CapabilitiesSnapshot = {
capabilities: Capabilities;
}; What the daemon advertises to webapps. gateway: None means no
companion is connected; webapps that depend on companion-routed
surfaces (Library, Net, Notifications, etc.) should branch on this.
authority is the live set of scopes the companion currently claims.
type Capabilities = {
gateway?: GatewayInfo;
available: SurfaceAvailability;
authority: CompanionAuthorityScope[];
uriSchemes: string[];
network: NetworkInfo;
audio: AudioCapabilities;
musicProvider: MusicProvider;
}; Identity payload describing the companion peer the daemon is talking
to. Address is the BT MAC the companion advertises; on transports without
a stable MAC (the network gateway's 0xfe:fe:... synthetic addrs) it
is the synthetic address as a string.
type GatewayInfo = {
address: string;
name: string;
osName: string;
appName: string;
appVersion: string;
adapterVersion: string;
libVersion: string;
libbridgethingVersion: string;
}; Bool feature flags the daemon exposes to webapps. Each is true when the
surface has both a backing implementation and (where applicable) a
connected companion claiming to provide it. False = the surface will
respond Unsupported or Unimplemented to verbs.
type SurfaceAvailability = {
geo: boolean;
notifications: boolean;
netFetch: boolean;
netWs: boolean;
audioTts: boolean;
lyrics: boolean;
}; One axis the companion can declare authority over. Each scope has a fallback source the daemon merges with when no claim is active or the claim has gone stale. Unknown scopes arriving at an older daemon are stored opaquely and ignored.
type CompanionAuthorityScope =
| 'nowPlayingMetadata' // Track metadata: title, album, artist, persistent_id, artwork_id, duration, liked. Excludes playback state. Companion claims when it has rich app-specific data for the currently-playing iOS app; releases when the user switches to a non-tracked app.
| 'nowPlayingPlayback' // Playback state: position, playing/paused, shuffle, repeat, app_bundle, app_display_name. On iOS, iAP2 is usually the better source (microsecond-fresh playhead). On Android the companion is the only source and always claims this.
| 'volume' // Host audio output volume + mute. iAP2 has no inbound volume CSM (HID step is one-way only), so without a companion claiming this scope the daemon can't see real volume - it broadcasts a fixed placeholder (0.5, unmuted) so the kiosk doesn't render a muted state, and forwards HID Inc/Dec/Mute presses best-effort.; What kind of network the companion's host is currently using. metered
is the OS-reported metered flag; webapps should treat it as a hint
(e.g. defer non-essential fetches) rather than a hard ban.
type NetworkInfo = {
kind: NetworkKind;
metered: boolean;
}; What the gateway-side audio backend supports. earcons are short
named sounds the companion can play; voices are TTS voices.
type AudioCapabilities = {
earcons: string[];
voices: VoiceDescriptor[];
}; Which music service the companion is currently logged into and
driving on behalf of the user. None when no glue is attached.
type MusicProvider = 'none' | 'spotify' | 'appleMusic' | 'tidal'; type NetworkKind = 'unknown' | 'wifi' | 'cellular' | 'ethernet'; One TTS voice the companion's audio backend can speak as. id is
platform-opaque (Apple/Android voice id); locale is BCP-47.
type VoiceDescriptor = {
id: string;
name: string;
locale: string;
};