JavaScript API
reference.
Everything ArdaPlayer injects into the page, grouped by capability. Each group is present only if the package manifest declared it — check with arda.can() before reaching for anything optional.
arda Working
The root namespace. Always present, regardless of declared capabilities.
await arda.ready()Resolves once the native bridge is fully initialised. The runtime injects arda before your entry script runs, so this is only needed if you defer work until the control channel has attempted its first connection.
arda.can(capability) → booleanReturns whether a capability was granted to this package. Cheap, synchronous, and the correct way to build an application that degrades on hardware without a particular peripheral.
if (arda.can('gpio')) {
await arda.gpio.mode(17, 'out')
} else {
ui.hide('#relay-panel')
}arda.version → stringThe ArdaPlayer runtime version, as a semantic version string.
arda.system Working
Device identity, hardware state and the controls an appliance legitimately needs.
await arda.system.info() → SystemInfoA snapshot of the machine. The same structure the device reports to the control plane on every connect.
{
deviceId: 'a1b2c3d4e5f6a1b2c3d4e5f6', // stable per machine
hostname: 'kiosk-04',
version: '0.1.1', // runtime version
uptimeSec: 8511,
os: { distro: 'Debian GNU/Linux 13', kernel: '6.12.90', arch: 'amd64' },
cpu: { model: 'Intel Celeron J1900', cores: 4 },
mem: { totalMB: 3716, availMB: 812 },
disk: { mount: '/', totalMB: 107315, freeMB: 98905 },
gpu: 'Intel Atom Z36xxx Graphics',
kiosk: true,
}await arda.system.display(options)Control the screen. All fields optional.
| Option | Type | Meaning |
|---|---|---|
on | string | Wake time as HH:MM, or true to wake now. |
off | string | Blank time as HH:MM, or true to blank now. |
brightness | number | 0–100, where the panel supports it. |
blankAfter | number | Idle seconds before blanking. 0 disables blanking. |
await arda.system.restart()Restart the application. The page is torn down and reloaded from the currently staged package; the machine is not rebooted.
await arda.system.reboot()Reboot the device. Requires the system:power capability. Returns only if the request was refused — otherwise the process ends with the machine.
await arda.system.log(level, message)Write a line to the device log. level is 'debug' | 'info' | 'warn' | 'error'. Lines are visible through arda logs and the portal's console view, and are retained on the device between reboots.
arda.db Working
Encrypted key/value stores on the device. Keys are strings; values are anything structured-cloneable. Writes are durable before the promise resolves — a power cut immediately after an await does not lose the write.
await arda.db(name) → Storecapability: dbOpen (or create) a named store. Opening the same name twice returns the same handle.
| Method | Returns | Notes |
|---|---|---|
await store.put(key, value) | — | Insert or replace. Durable on resolve. |
await store.get(key) | value or undefined | No throw on a missing key. |
await store.del(key) | — | Idempotent. |
await store.has(key) | boolean | Cheaper than get for large values. |
await store.keys(prefix?) | string[] | Sorted; optionally filtered by prefix. |
await store.scan(prefix) | [key, value][] | Prefix range read. |
await store.batch(ops) | — | Atomic multi-write. All or nothing. |
await store.clear() | — | Empties the store. |
store.meta() | object | { count, bytes, lastSync }. |
const db = await arda.db('orders')
await db.batch([
{ op: 'put', key: `order:${id}`, value: order },
{ op: 'put', key: 'lastOrderId', value: id },
])
const today = await db.scan('order:2026-07-24')arda.storage Working
Scoped file storage for media, cached assets and generated documents. Paths are relative to the application's own directory — there is no way to reach the wider filesystem.
await arda.storage.write(path, data, options?)capability: storagedata may be a string, ArrayBuffer, Blob or Uint8Array. Pass { encrypt: true } to store the file encrypted at rest.
await arda.storage.read(path, options?) → Blob | stringPass { as: 'text' } for a string, otherwise a Blob is returned. Rejects with NOT_FOUND if the path does not exist.
await arda.storage.list(prefix?) → FileEntry[]Each entry is { path, bytes, modified }.
await arda.storage.remove(path)await arda.storage.usage() → { bytes, quota, files }Exceeding the quota rejects writes with QUOTA_EXCEEDED rather than filling the device's disk.
arda.sync Working
The only path between the sandboxed page and your server. The application has no network stack of its own; everything moves through here.
await arda.sync.push(record) → { id }capability: syncQueue a record for delivery to your server. The record is persisted to encrypted storage before the promise resolves, so it survives power loss. Delivery is at-least-once: the device re-sends the whole queue on every reconnect until your server acknowledges each ID, so your server must deduplicate by ID.
await arda.sync.get(resource, params?) → anyRequest/response against your server. Resolves with whatever your server returns for that resource, or rejects with SYNC_FAILED if it errors or the device is offline with no cached copy.
await arda.sync.profile(name, options?) → anyFetch a versioned data profile — a catalogue, price list or configuration set. Profiles are cached to disk and gated by modification time, so a repeat call while offline returns the cached copy rather than failing.
arda.sync.state() → { online, queued, lastSync }Synchronous snapshot for status indicators. queued is the number of records still awaiting acknowledgement.
arda.sync.on(event, handler) → () => voidEvents: 'drain' when the queue empties, 'queued' when a record is added, 'error' on a delivery failure. Returns an unsubscribe function.
const off = arda.sync.on('drain', () => ui.badge('synced'))
await arda.sync.push({ type: 'sale', total: 1299 })
const menu = await arda.sync.profile('menu')
// later, when the screen unmounts
off()arda.net In progress
Connectivity state and Wi-Fi management — for devices that get moved to a different corner of the building without anyone telling you.
await arda.net.status() → NetStatuscapability: net:statusReturns { online, interface, ip, ssid, signal }. online reflects whether the control channel is actually connected, not merely whether a cable is plugged in.
await arda.net.interfaces() → Interface[]Each entry is { name, mac, ips, up }.
await arda.net.scan() → Network[]capability: net:wifiAvailable Wi-Fi networks as { ssid, signal, security }, strongest first.
await arda.net.join(ssid, password?, options?)Join a network and, unless { remember: false }, store the credentials in the device's encrypted configuration so the connection survives a reboot.
arda.net.on(event, handler) → () => voidEvents: 'online', 'offline', 'change'.
arda.usb Working
Enumerate and react to attached hardware.
await arda.usb.list() → UsbDevice[]capability: usb:read{
vendorId: '04b8',
productId: '0e15',
manufacturer: 'EPSON',
product: 'TM-T20III',
serial: '583239…',
type: 'printer', // printer|scanner|nfc|camera|modem|other
}arda.usb.on(event, handler) → () => voidEvents: 'attach' and 'detach', each receiving the UsbDevice. Useful for telling an operator that the scanner has fallen out mid-transaction rather than silently failing.
arda.printer In progress
Receipts, labels and reports on thermal and standard Linux printers.
await arda.printer.list() → Printer[]capability: printerawait arda.printer.default() → PrinterRejects with NO_PRINTER if none is attached.
await printer.receipt(job)| Field | Type | Meaning |
|---|---|---|
lines | array | Strings, or { text, bold, align, size } objects. |
width | number | Characters per line. Defaults to the printer's reported width. |
cut | boolean | Cut the paper after printing. |
copies | number | Defaults to 1. |
barcode | object | { type, value } — appended after the lines. |
image | Blob | A logo or QR image to print above the text. |
await printer.raw(bytes)Send raw ESC/POS to the printer. The escape hatch for anything the structured API doesn't cover yet.
await printer.drawer(pin?)Kick the cash drawer on the printer's drawer port. pin is 2 or 5, defaulting to 2.
await printer.status() → { online, paper, cover, error }PartialPresence and type detection work today. Live paper-out and cover-open reporting require a per-model protocol query and are still being implemented — treat the detailed fields as best-effort for now.
arda.camera In progress
await arda.camera.list() → CameraInfo[]capability: cameraawait arda.camera.open(options?) → CameraOptions: { id, facing, width, height }. Close it with camera.close() when the screen unmounts — an open camera holds the device.
await camera.capture(options?) → BlobOptions: { format: 'jpeg' | 'png' | 'webp', quality: 0–100 }.
await camera.scanQR(options?) → { text, format }Resolves on the first successful decode. Options: { timeout, formats }, where formats restricts to e.g. ['qr', 'ean13', 'code128']. Rejects with TIMEOUT if nothing is decoded in time.
camera.stream(handler) → () => voidReceive frames for on-screen preview. Returns an unsubscribe function.
arda.face Planned
Local detection and matching for attendance and identity confirmation. Designed to run entirely on the device — no image is uploaded for recognition, and templates never leave the hardware.
await arda.face.detect(image) → Face[]capability: faceawait arda.face.enrol(id, images)await arda.face.match(image) → { id, score }await arda.face.forget(id)arda.gpio In progress
Digital I/O on boards that expose it. Pin numbers follow the board's own numbering, reported by arda.gpio.board().
await arda.gpio.board() → { name, pins }capability: gpioawait arda.gpio.mode(pin, direction, options?)direction is 'in' or 'out'. Options: { pull: 'up' | 'down' | 'none', initial: boolean }. Rejects with PIN_IN_USE if another part of the application already claimed the pin.
await arda.gpio.read(pin) → booleanawait arda.gpio.write(pin, value)await arda.gpio.toggle(pin) → booleanawait arda.gpio.pulse(pin, ms)Drive a pin high for ms milliseconds and return it low — the common case for relays, latches and door strikes. Timing is handled natively rather than with a JavaScript timer, so a busy page does not leave a barrier open.
arda.gpio.watch(pin, options?, handler) → () => voidWatch an input for changes. Options: { debounce: ms, edge: 'rising' | 'falling' | 'both' }. The handler receives { pin, high, at }.
await arda.gpio.mode(17, 'out', { initial: false })
await arda.gpio.mode(27, 'in', { pull: 'up' })
const stop = arda.gpio.watch(27, { debounce: 40, edge: 'falling' }, async () => {
await arda.gpio.pulse(17, 400) // unlatch for 400ms
await arda.sync.push({ type: 'entry', at: Date.now() })
})arda.audio Planned
await arda.audio.play(source, options?)capability: audioawait arda.audio.stop()await arda.audio.volume(level?) → numberawait arda.audio.outputs() → Output[]Standard web audio still works inside the page for anything that doesn't need device-level output selection or volume control.
arda.app Working
The application's own lifecycle and the configuration pushed to it from the control plane.
await arda.app.config() → objectConfiguration assigned to this device or its group — site name, tax rate, printer width, whatever you defined. Changing it centrally does not require a new release.
arda.app.on('config', handler) → () => voidFires when configuration changes while the application is running, so you can re-render without a restart.
await arda.app.checkUpdate() → { available, version }Ask the control plane whether a newer release is assigned. Rarely needed — updates are normally pushed — but useful for applications that want to update only at a quiet moment.
await arda.app.applyUpdate()Apply a staged update now. Ends the current page. Call it when the device is idle rather than mid-transaction.
arda.app.info() → { name, version, release, channel }Error codes
Every rejection is an ArdaError with a stable code, a human-readable message and sometimes a detail object. Branch on code — the message text is not stable and will change.
| Code | Meaning |
|---|---|
NOT_PERMITTED | The capability was not declared in the package manifest. |
NOT_FOUND | The named path, key, device or resource does not exist. |
NOT_SUPPORTED | This hardware or runtime build does not implement the call. |
TIMEOUT | The operation exceeded its timeout — a scan, a print, a request. |
OFFLINE | The control channel is down and no cached result was available. |
SYNC_FAILED | The server rejected or failed to answer a sync request. |
QUOTA_EXCEEDED | A storage write would exceed the application's quota. |
NO_PRINTER | No printer is attached, or none matched the selector. |
PRINTER_OFFLINE | The printer is attached but not responding. |
PIN_IN_USE | The GPIO pin is already claimed. |
DEVICE_BUSY | The camera or peripheral is in use elsewhere. |
INVALID_ARGUMENT | A parameter was the wrong type or out of range. |
INTERNAL | An unexpected runtime failure. Report these. |
try {
await printer.receipt(job)
} catch (err) {
switch (err.code) {
case 'NO_PRINTER':
case 'PRINTER_OFFLINE':
await queueForLater(job)
ui.toast('Receipt queued — check the printer')
break
default:
arda.system.log('error', `print failed: ${err.code}`)
throw err
}
}