bridgething
Surfaces ↓

Webapp

client.webapp

Daemon -> webapp replies and events for the webapp-management surface. ListReply, CurrentReply, ActiveReply, and IconReply answer the matching ClientToBridgeWebappMsg request; WebappError replaces the reply on failure. WebappInstalled is a live event broadcast to every connected webapp whenever an installed-webapp transfer completes.

Requests

you ask, the daemon answers; await the tagged result and check .ok

list(): Promise<TypedRequestResult<WebappListReply, never>>

push form: onListReply (subscribe instead of awaiting)

const res = await client.webapp.list();
if (res.ok) {
  console.log(res.response.webapps);
}

current(): Promise<TypedRequestResult<WebappCurrentReply, never>>

push form: onCurrentReply (subscribe instead of awaiting)

Webapp asks which webapp (if any) is currently active in the kiosk.

const res = await client.webapp.current();
if (res.ok) {
  console.log(res.response.id);
}

activate(WebappActivate): Promise<TypedRequestResult<WebappActiveReply, WebappError>>

push form: onActiveReply (subscribe instead of awaiting)

Payload for the activate request: switch the kiosk to the given webapp. The kiosk runs exactly one webapp at a time, so the daemon navigates it away from whatever was previously active.

const res = await client.webapp.activate({ id: '...' });
if (res.ok) {
  console.log(res.response.id);
} else {
  console.warn(res.kind, res.error);
}

icon(WebappIcon): Promise<TypedRequestResult<WebappIconReply, WebappError>>

push form: onIconReply (subscribe instead of awaiting)

Icon byte fetch. Daemon reads the icon declared by the manifest from the bundle directory and returns the bytes + mime.

const res = await client.webapp.icon({ id: '...' });
if (res.ok) {
  console.log(res.response.bytes);
} else {
  console.warn(res.kind, res.error);
}

Events

the daemon pushes these unprompted; subscribing returns an unsubscribe function

onActiveChanged(handler: (WebappActiveChanged) => void): () => void

const off = client.webapp.onActiveChanged((webappActiveChanged) => {
  console.log(webappActiveChanged.id);
});
// call off() to unsubscribe

onWebappInstalled(handler: (WebappInfo) => void): () => void

event: a webapp install completed successfully

const off = client.webapp.onWebappInstalled((webappInfo) => {
  console.log(webappInfo.id);
});
// call off() to unsubscribe

onWebappUninstalled(handler: (WebappUninstalled) => void): () => void

const off = client.webapp.onWebappUninstalled((webappUninstalled) => {
  console.log(webappUninstalled.name);
});
// call off() to unsubscribe

Types

shapes referenced above, as the SDK types them

Event payload for an active-webapp change (any initiator). Distinct from WebappActive (a request response) so it carries the new app's declared art profile; the companion reads art directly to size its pushes.

type WebappActiveChanged = {
  id?: string;
  name?: string;
  art?: ArtProfile;
};
type WebappInfo = {
  id: string;
  name: string;
  source: WebappSource;
  role: WebappRole;
  version: string;
  description?: string;
  iconAvailable: boolean;
  iconMime?: string;
  icon?: number[]; // icon bytes, inlined on the gateway list so the companion never round-trips a separate fetch per app. omitted on the on-device client list.
  config: ConfigField[];
  permissions: string[];
  voiceGrammar?: string; // Plain-English description of the voice intents the webapp wants WEBAPP_INTENT routing for. Companion-side NLU folds this into the "currently active extensions" section of the system prompt at inference, which is what makes WEBAPP_INTENT emission context-aware. `None` opts the webapp out of voice integration.
  art?: ArtProfile; // Declared art render sizes; the companion warms exactly these. `None` means the canonical `{248, 96}` default applies.
};

Broadcast when an installed webapp is removed; carries the removed webapp's name.

type WebappUninstalled = {
  name: string;
};
type WebappListReply = {
  webapps: WebappInfo[]; // Excludes `Launcher`-role bundles used as alternate home screens.
};
type WebappCurrentReply = {
  id?: string; // `None` when no webapp is currently active in the kiosk.
  name?: string;
};

Payload for the activate request: switch the kiosk to the given webapp. The kiosk runs exactly one webapp at a time, so the daemon navigates it away from whatever was previously active.

type WebappActivate = {
  id: string; // Id of an installed webapp, from `webapp.list`.
};
type WebappActiveReply = {
  id?: string; // Id of the webapp that was just activated.
  name?: string;
};

Domain errors emitted by any webapp surface (gateway- or client-side). Single catalog: both protocols speak the same variant set.

type WebappError =
  | 'webappNotFound' // No installed webapp matches this id (uninstall / activate / icon / config target).
  | 'cannotUninstallBuiltin' // Built-in webapps cannot be uninstalled.
  | 'idReserved' // Install rejected: the manifest's id is in the reserved-uuid set (stock, hub, launcher, etc).
  | 'extractedTooLarge' // Extracted bundle exceeds the 1 GiB disk-protection cap.
  | 'zipMalformed' // Zip extraction failed: corrupt archive, unsafe entry names, etc.
  | 'missingIndexHtml' // Bundle has no index.html at its root.
  | 'invalidManifest' // manifest.json missing, unparseable, or failed schema validation.
  | 'iconNotAvailable' // The webapp's manifest doesn't declare an icon (or the icon file is missing on disk).
  | 'unknownConfigKey' // Config key is not declared in the webapp's manifest schema.
  | 'invalidConfigValue' // Value failed schema validation (out of range, regex mismatch, not in enum).
  | 'internal' // Catch-all for genuinely-unexpected failures (io errors, daemon-side bugs). Reason is human-readable; not a stable wire contract.;

Icon byte fetch. Daemon reads the icon declared by the manifest from the bundle directory and returns the bytes + mime.

type WebappIcon = {
  id: string;
};
type WebappIconReply = {
  bytes: number[];
  mime?: string;
};

Art render sizes a webapp declares so the companion warms exactly the pixels it renders: hero (now-playing / detail views) and thumb (queue / grid). Omitted in a manifest falls back to the canonical {248, 96}, which is also the stock webapp's profile.

type ArtProfile = {
  heroPx: number;
  thumbPx: number;
};
type WebappSource = 'builtin' | 'installed';

A webapp's launcher visibility. Standard shows up in user-facing listings (the hub grid, etc); Launcher is itself a launcher and is hidden from those listings. The daemon filters Launcher bundles out of client.webapp.list; the gateway list keeps everything.

type WebappRole = 'standard' | 'launcher';

One declared user-tunable setting. Adjacent-tagged on the wire to stay typeshare-compatible: {"type":"string","data":{"key":"zip",...}}.

type ConfigField =
  | { type: 'string'; data: StringField }
  | { type: 'number'; data: NumberField }
  | { type: 'boolean'; data: BoolField }
  | { type: 'enum'; data: EnumField }
  | { type: 'secret'; data: StringField } // String semantics, masked in companion UI. No actual secure storage.;
type StringField = {
  key: string;
  label: string;
  pattern?: string;
  minLength?: number;
  maxLength?: number;
  default?: string;
};
type NumberField = {
  key: string;
  label: string;
  min?: number;
  max?: number;
  step?: number;
  default?: number;
};
type BoolField = {
  key: string;
  label: string;
  default?: boolean;
};
type EnumField = {
  key: string;
  label: string;
  choices: string[];
  default?: string;
};