docs / Native

Native Device APIs

The device.* API surface, the three access patterns (state bindings, callbacks, markup elements), the permission mapping tables, and every tangible element tag.

The native framework exposes Android device capabilities three ways — the page picks whichever fits the shape of its code: state bindings recompose automatically, callbacks hand results to vesk cells, and markup elements compile directly to native composables.

Access patterns

  • **A — state bindings**: `device.lastPhoto` reads drive recomposition. Use for values that change and should re-render automatically.
  • **B — callbacks**: `device.pickImage((uri) => { ... })` hands results to a vesk cell. Use for one-shot actions.
  • **C — markup elements**: `<camera video />`, `<battery-status />`, `<qr-scanner />`, `<contacts />` compile to native composables with the same runtime surface.

Script API reference

All device APIs are available on the `device` object in `.vsk` scripts. The full method surface:

Group APIs
Imaging pickImage, pickAudio, pickFile, capturePhoto, captureVideo, captureScreenshot, generateQrCode, scanQr, setWallpaper, toggleTorch
Recorder startRecording, stopRecording, playSound
Notification notify, toast, vibrate
Power getBattery, setKeepAwake
Network refreshNetwork, openUrl
Location getLocation, openMaps
Files listFiles, writeFile, readFile, deleteFile
Apps listApps, openApp, openSettings
Contacts & call listContacts, listCallLogs, dial, sendSms
Messages & email listMessages, sendEmail
Accounts listAccounts
Clipboard readClipboard, copyToClipboard, shareText, shareFile
Security checkBiometrics, authenticate
Bluetooth refreshBluetooth, toggleBluetooth, scanBluetooth
NFC refreshNfc
Display setScreenBrightness, resetScreenBrightness, lockOrientation
Volume & ringer refreshVolume, setVolume, setRingerMode
Storage refreshStorage
Sensors readSensor
Media session startScreenRecord, stopScreenRecord
Telephony refreshTelephony
SIM refreshDeviceInfo
Calendar listCalendarEvents, setAlarm
Speech speak, openLink

Permissions

The build scans the `.vsk` pages (AST walks — no regex) and declares in the Android manifest only the permissions the used device APIs need. `device.*` calls and their element-tag counterparts map through the same `API_PERMISSIONS` table:

Used API Manifest permissions
`startRecording` / `<recorder>` RECORD_AUDIO
`notify` / `<notification>` POST_NOTIFICATIONS
`scanQr` / `<qr-scanner>` CAMERA
`getLocation` / `<location>` ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION
`listContacts` / `<contacts>` READ_CONTACTS
`listCallLogs` / `<call-log>` READ_CALL_LOG
`listMessages` / `<messages>` READ_SMS
`listAccounts` / `<accounts>` GET_ACCOUNTS
`refreshNetwork` / `<network-status>` ACCESS_NETWORK_STATE, ACCESS_WIFI_STATE
`vibrate` / `<vibrate>` VIBRATE
`setVolume` / `setRingerMode` / `<set-volume>` MODIFY_AUDIO_SETTINGS
`setWallpaper` / `<wallpaper>` SET_WALLPAPER
Bluetooth (refresh/toggle/scan) BLUETOOTH_CONNECT, BLUETOOTH_SCAN + legacy BLUETOOTH/BLUETOOTH_ADMIN (maxSdkVersion-scoped)
`listCalendarEvents` / `<calendar>` READ_CALENDAR
`checkBiometrics` / `authenticate` / `<biometric-auth>` USE_BIOMETRIC
`startScreenRecord` / `<screen-record>` FOREGROUND_SERVICE, FOREGROUND_SERVICE_MEDIA_PROJECTION
`fetch` / WebSocket / EventSource INTERNET

Legacy permissions that only exist for a bounded SDK range are maxSdkVersion-scoped: READ_EXTERNAL_STORAGE to SDK 32 and the Bluetooth pair (BLUETOOTH/BLUETOOTH_ADMIN) to SDK 30. FileProvider / queries / service declarations and bundled assets are added only when a page actually needs them.

On iOS the same device-API usage drives Info.plist usage keys — NSCameraUsageDescription, NSMicrophoneUsageDescription, NSLocationWhenInUseUsageDescription, NSContactsUsageDescription, NSFaceIDUsageDescription, NSBluetoothAlwaysUsageDescription — never emitted 'just in case'.

Tangible elements

The declarative device elements compile to the same runtime surface as the script `device.*` API. Each tag maps to a composable and feeds the manifest scan:

app/camera/page.vsk
									
										export component Camera() {
  <camera video ondone={(uri) => { lastShot = uri }} />
  <battery-status />
  <qr-scanner onresult={(text) => { code = text }} />
  <contacts onresult={(list) => { people = list }} />
}
									
								

Available tags: `<photo-picker>`, `<camera>` (with `video` attribute), `<recorder>`, `<file-input>`, `<notification>`, `<battery-status>`, `<network-status>`, `<location>`, `<apps>`, `<contacts>`, `<call-log>`, `<messages>`, `<accounts>`, `<clipboard>`, `<copy-to-clipboard>`, `<vibrate>`, `<torch>`, `<screenshot>`, `<share-text>`, `<share-file>`, `<biometric-auth>`, `<bluetooth>`, `<bluetooth-toggle>`, `<bluetooth-scan>`, `<screen-record>`, `<qr-code>`, `<qr-scanner>`, `<volume>`, `<set-volume>`, `<brightness>`, `<keep-awake>`, `<orientation>`, `<device-info>`, `<storage-status>`, `<sensor>`, `<toast>`, `<sound>`, `<wallpaper>`, `<calendar>`, `<nfc>`, `<sim>`, `<dial>`, `<sms>`, `<email>`, `<open-link>`, `<map>`, `<alarm>`, `<open-settings>`, `<open-app>`, `<speak>`.

  • Attributes pass through as named composable args — `<alarm hour='8' minute='30' title='Wake up'>`.
  • Missing args fall back to the composable's Kotlin defaults, so `<battery-status/>` alone works.
  • Tags feed the same API_PERMISSIONS scan as `device.*` calls.

Media element tiers

Media and device elements split into three tiers by permission requirements:

Tier Elements Permission
No permission <photo-picker>, <audio-picker>, <file-input>, video/audio playback None
Runtime grant <recorder> RECORD_AUDIO (runtime prompt)
User consent <camera>, <screen-record>, <notification> CAMERA / MediaProjection / POST_NOTIFICATIONS — user grants explicitly

Drag & drop

Markup-level native drag & drop: a `draggable` element becomes a drag source (payload from its `dragdata` attribute or text content); an `ondrop={(text) => { ... }}` element is a drop target. Payloads also land in other apps (`DRAG_FLAG_GLOBAL`).

app/dnd/page.vsk
									
										export component DragDrop() {
  <div class="grid grid-cols-2 gap-4 p-6">
    <div class="rounded-2xl bg-blue-100 p-6 text-center"
         draggable dragdata="/media/photo.jpg">
      Drag me
    </div>
    <div class="rounded-2xl border-2 border-dashed p-6 text-center"
         ondrop={(text) => { droppedText = text }}>
      Drop here
    </div>
  </div>
}
									
								

Usage analysis

The CLI's `usage.ts` scans the compiled project and the `.vsk` sources to derive exactly what ships in the app:

  • `collectDeviceApiUsage(appDir)` — AST+IR walk over .vsk scripts for `device.<api>()` calls and device element tags; drives manifest permissions and runtime grants.
  • `collectBrowserApiUsage(appDir)` — finds `fetch`, `localStorage`/`sessionStorage`, `openSqlite`, auth (`signUp`/`signIn`/`signOut`/`currentUser`/`isSignedIn`), `WebSocket`, `EventSource` to decide their manifest needs.
  • `collectRuntimeUsage(appDir)` — scans generated Kotlin to pick exactly the runtime helpers actually called (video/audio helpers only when a page uses `<video>`/`<audio>`, the device runtime only when a device API or element appears).

Runtime permission prompts

Dangerous permissions are requested at the point of use through the Compose runtime. When a page triggers a device API that needs runtime consent (camera, microphone, location, contacts), the framework shows the Android system prompt and resumes the callback with the grant result.

app/media/page.vsk
									
										export component MediaLab() {
  const &[photoUri] = track('')

  <div class="p-4">
    <button onClick={() => {
      device.capturePhoto((uri) => { photoUri = uri })
    }} class="bg-blue-600 text-white rounded-xl px-4 py-2">
      Take a photo
    </button>
    if (photoUri !== '') {
      <img src={photoUri} class="mt-4 rounded-2xl w-full" />
    }
  </div>
}
									
								

`{#head}` blocks are not supported in native, and `{#server}` blocks compile to an explicit `error(...)` so nothing silently miscompiles. Web-only `on*` attributes not backed by a native runtime surface are rejected at compile time.