index ↓

webapp

client.webapp

The webapps installed on the device. list returns the ones a user can switch to, current reports which one is showing, activate switches to another, and icon returns icon bytes. onActiveChanged, onWebappInstalled, and onWebappUninstalled report changes as they happen.

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)

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)

Switches the device to another webapp. The device shows one webapp at a time.

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)

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

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

type WebappActiveChanged = {
  id: string | null;
  name?: string;
  art?: ArtProfile;
};
type WebappInfo = {
  id: string;
  name: string;
  source: WebappSource;
  role: WebappRole;
  version: string;
  description?: string;
  iconHash?: string;
  settingsHash?: string;
  overlayHash?: string;
  config: ConfigField[];
  permissions: string[];
  rendersVoiceDisplay: boolean;
  art?: ArtProfile;
  provenance?: string;
  extension?: ExtensionInfo;
};
type WebappUninstalled = {
  name: string;
};
type WebappListReply = {
  webapps: WebappInfo[];
};
type WebappCurrentReply = {
  id: string | null; // `null` when the device shows no webapp.
  name?: string;
};

Switches the device to another webapp. The device shows one webapp at a time.

type WebappActivate = {
  id: string; // An `id` from `list`.
};
type WebappActiveReply = {
  id: string | null;
  name?: string;
};
type WebappError =
  | 'webappNotFound'
  | 'cannotUninstallBuiltin'
  | 'idReserved' // The manifest id is one the device reserves. Give the webapp a different uuid.
  | 'extractedTooLarge' // The extracted bundle is over the 1 GiB cap.
  | 'provenanceTooLong' // The provenance string is over 2048 bytes.
  | 'zipMalformed'
  | 'missingIndexHtml' // Put an `index.html` at the root of the bundle.
  | 'invalidManifest' // `manifest.json` is missing, unparseable, or failed validation. `reason` says which.
  | 'resourceNotAvailable' // The manifest must declare the resource and the file must exist in the bundle.
  | 'notALauncher' // A bundle must declare `role` as `launcher` to take the launcher slot.
  | 'noOverlay' // A bundle must declare `overlay` to take the overlay slot.
  | 'unknownConfigKey' // The manifest declares no config field with this key.
  | 'invalidConfigValue' // The value failed the field's declared constraints. `reason` says which.
  | 'invalidDocValue' // The doc value is over 256 KiB.
  | 'internal' // An unexpected failure.;
type WebappIcon = {
  id: string;
};
type WebappIconReply = {
  bytes: Uint8Array;
  mime?: string; // The icon's MIME type. `null` when the webapp declares none.
};

A manifest that omits it gets 248 for heroPx and 96 for thumbPx.

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

launcher makes the bundle eligible for the launcher slot and keeps it out of webapp.list.

type WebappRole = 'standard' | 'launcher';

One setting the user can tune. In manifest.json it reads {"type":"string","data":{"key":"zip","label":"ZIP code"}}.

type ConfigField =
  | { type: 'string'; data: StringField }
  | { type: 'number'; data: NumberField }
  | { type: 'boolean'; data: BoolField }
  | { type: 'enum'; data: EnumField }
  | { type: 'secret'; data: StringField };
type ExtensionInfo = {
  permissions: string[];
  api: number;
};
type StringField = {
  key: string;
  label: string;
  pattern?: string;
  minLength?: number; // In characters.
  maxLength?: number; // In characters.
  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;
};