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.

Preview — method names and option shapes may change before 1.0

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) → boolean

Returns 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.

example
if (arda.can('gpio')) {
  await arda.gpio.mode(17, 'out')
} else {
  ui.hide('#relay-panel')
}
arda.version → string

The 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() → SystemInfo

A snapshot of the machine. The same structure the device reports to the control plane on every connect.

SystemInfo
{
  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.

OptionTypeMeaning
onstringWake time as HH:MM, or true to wake now.
offstringBlank time as HH:MM, or true to blank now.
brightnessnumber0–100, where the panel supports it.
blankAfternumberIdle 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: db

Open (or create) a named store. Opening the same name twice returns the same handle.

MethodReturnsNotes
await store.put(key, value)Insert or replace. Durable on resolve.
await store.get(key)value or undefinedNo throw on a missing key.
await store.del(key)Idempotent.
await store.has(key)booleanCheaper 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 }.
example
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: storage

data 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 | string

Pass { 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: sync

Queue 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?) → any

Request/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?) → any

Fetch 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) → () => void

Events: 'drain' when the queue empties, 'queued' when a record is added, 'error' on a delivery failure. Returns an unsubscribe function.

example
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:status

Returns { 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:wifi

Available 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) → () => void

Events: 'online', 'offline', 'change'.


arda.usb Working

Enumerate and react to attached hardware.

await arda.usb.list() → UsbDevice[]capability: usb:read
UsbDevice
{
  vendorId:     '04b8',
  productId:    '0e15',
  manufacturer: 'EPSON',
  product:      'TM-T20III',
  serial:       '583239…',
  type:         'printer',   // printer|scanner|nfc|camera|modem|other
}
arda.usb.on(event, handler) → () => void

Events: '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: printer
await arda.printer.default() → Printer

Rejects with NO_PRINTER if none is attached.

await printer.receipt(job)
FieldTypeMeaning
linesarrayStrings, or { text, bold, align, size } objects.
widthnumberCharacters per line. Defaults to the printer's reported width.
cutbooleanCut the paper after printing.
copiesnumberDefaults to 1.
barcodeobject{ type, value } — appended after the lines.
imageBlobA 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 }Partial

Presence 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: camera
await arda.camera.open(options?) → Camera

Options: { id, facing, width, height }. Close it with camera.close() when the screen unmounts — an open camera holds the device.

await camera.capture(options?) → Blob

Options: { 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) → () => void

Receive 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: face
await arda.face.enrol(id, images)
await arda.face.match(image) → { id, score }
await arda.face.forget(id)
i
Not implemented Documented so you can design around it. Do not ship against this group.

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: gpio
await 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) → boolean
await arda.gpio.write(pin, value)
await arda.gpio.toggle(pin) → boolean
await 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) → () => void

Watch an input for changes. Options: { debounce: ms, edge: 'rising' | 'falling' | 'both' }. The handler receives { pin, high, at }.

example
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: audio
await arda.audio.stop()
await arda.audio.volume(level?) → number
await 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() → object

Configuration 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) → () => void

Fires 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.

CodeMeaning
NOT_PERMITTEDThe capability was not declared in the package manifest.
NOT_FOUNDThe named path, key, device or resource does not exist.
NOT_SUPPORTEDThis hardware or runtime build does not implement the call.
TIMEOUTThe operation exceeded its timeout — a scan, a print, a request.
OFFLINEThe control channel is down and no cached result was available.
SYNC_FAILEDThe server rejected or failed to answer a sync request.
QUOTA_EXCEEDEDA storage write would exceed the application's quota.
NO_PRINTERNo printer is attached, or none matched the selector.
PRINTER_OFFLINEThe printer is attached but not responding.
PIN_IN_USEThe GPIO pin is already claimed.
DEVICE_BUSYThe camera or peripheral is in use elsewhere.
INVALID_ARGUMENTA parameter was the wrong type or out of range.
INTERNALAn unexpected runtime failure. Report these.
handling
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
  }
}