USER DEVICE & Syncing

USER DEVICE halts a script exactly like GATHER and CONFIRM. It shows a single, fixed popup — one custom label, one Accept button. Accepting does not resubmit immediately: it triggers the browser's real permission prompts for camera, microphone, geolocation, push, and clipboard access, one after another. Only once every prompt has been answered — granted or denied — does the script resume, receiving both the permission results and the plain, permissionless device properties together.

USER DEVICE

The bare form requests everything at once — every permission, the screenshot, and every permissionless property.

Requesting Everything
USER DEVICE LABEL "We need your permission to access your browser" SET ?device

Requesting a Single Component

Any individual permission can be requested on its own, without asking for anything else. Every single-component request still includes all the free, permissionless properties, since those require no consent and no extra prompt.

USER DEVICE CAMERA
USER DEVICE CAMERA LABEL "We need camera access" SET ?device
USER DEVICE MICROPHONE
USER DEVICE MICROPHONE LABEL "We need microphone access" SET ?device
USER DEVICE GEOLOCATION
USER DEVICE GEOLOCATION LABEL "We need your location" SET ?device
USER DEVICE PUSH
USER DEVICE PUSH LABEL "Enable notifications" SET ?device
USER DEVICE CLIPBOARD_READ
USER DEVICE CLIPBOARD_READ LABEL "Allow reading your clipboard" SET ?device
USER DEVICE CLIPBOARD_WRITE
USER DEVICE CLIPBOARD_WRITE LABEL "Allow writing to your clipboard" SET ?device

USER DEVICE SCREENSHOT

Unlike every other permission field, granting screenshot access immediately produces content, not just a status. The browser shows its own native picker — the visitor chooses to share their entire screen, a single window, or a browser tab — and once granted, the actual captured frame is returned directly. ?device("screenshot") holds the captured image if granted, or null if denied or cancelled.

Capturing a Screenshot
USER DEVICE SCREENSHOT LABEL "Share your screen to continue" SET ?device
AFTER EMIT ?device("screenshot")

USER DEVICE PERMISSIONLESS

Requests only the properties that need no consent at all — no popup, no LABEL, nothing shown to the visitor. The browser silently collects these values and resubmits automatically, since the data still has to travel from the browser back to the server, but there is nothing to ask permission for.

Silent Collection — No Popup
USER DEVICE PERMISSIONLESS SET ?device
AFTER EMIT ?device("screen_width")
AFTER EMIT ?device("user_agent")

USER DEVICE PERMISSIONS

Requests all six status-based permissions together — camera, microphone, geolocation, push, clipboard read, clipboard write — in a single popup. Does not include the screenshot, since it behaves differently and is requested on its own via USER DEVICE SCREENSHOT.

Requesting All Status Permissions
USER DEVICE PERMISSIONS LABEL "We need your permission to access your browser" SET ?device

Resolution, Not Approval

The script resumes once the visitor has answered every permission prompt — not once every permission has been granted. A browser can never be forced into universal acceptance. Each permission field reports one of the three real states the browser's own Permissions API returns:

State Meaning
grantedThe visitor allowed this permission
deniedThe visitor refused this permission
promptNot yet decided — should not occur after USER DEVICE resumes, since resumption only happens once every prompt has been answered

Full Field Reference

Field Description
?device("permissions")("camera")Camera access — granted / denied
?device("permissions")("microphone")Microphone access — granted / denied
?device("permissions")("geolocation")Location access — granted / denied
?device("permissions")("push")Push messaging — granted / denied. In the browser's own Permissions API this is tied to the notifications permission; push messaging has no separate permission of its own.
?device("permissions")("clipboard_read")Reading the system clipboard — granted / denied
?device("permissions")("clipboard_write")Writing to the system clipboard — granted / denied
?device("screenshot")The captured image itself if granted, or null if denied/cancelled — not a status field
?device("screen_width") / ?device("screen_height")Full screen dimensions — permissionless
?device("viewport_width") / ?device("viewport_height")Visible browser viewport dimensions — permissionless
?device("pixel_ratio")Device pixel density — permissionless
?device("user_agent")Browser user agent string — permissionless
?device("language")Browser locale — permissionless
?device("timezone")IANA timezone name — permissionless
?device("online")Current network connectivity state — permissionless
?device("touch_support")Whether the device supports touch input — permissionless
?device("color_scheme")Preferred light/dark color scheme — permissionless

Reading Every Field

Full Result
USER DEVICE LABEL "We need your permission to access your browser" SET ?device
AFTER EMIT ?device("permissions")("camera")
AFTER EMIT ?device("permissions")("microphone")
AFTER EMIT ?device("permissions")("push")
AFTER EMIT ?device("permissions")("geolocation")
AFTER EMIT ?device("permissions")("clipboard_read")
AFTER EMIT ?device("permissions")("clipboard_write")
AFTER EMIT ?device("screenshot")
AFTER EMIT ?device("screen_width")
AFTER EMIT ?device("screen_height")
AFTER EMIT ?device("viewport_width")
AFTER EMIT ?device("viewport_height")
AFTER EMIT ?device("pixel_ratio")
AFTER EMIT ?device("user_agent")
AFTER EMIT ?device("language")
AFTER EMIT ?device("timezone")
AFTER EMIT ?device("online")
AFTER EMIT ?device("touch_support")
AFTER EMIT ?device("color_scheme")

Checking a Permission Before Using It

A denied permission is a normal, expected outcome, not an error — always check its state before relying on it.

Defensive Permission Check
USER DEVICE LABEL "We need your permission to access your browser" SET ?device
AFTER IF ?device("permissions")("camera") IS EQUAL TO "granted"
OPEN
  EMIT "Camera access granted — proceeding"
CLOSE
OR
OPEN
  EMIT "Camera access denied — falling back to manual upload"
CLOSE
Site Mode only. USER DEVICE uses the same halt-and-resume mechanism as GATHER and CONFIRM. It relies on a real visitor round-trip — rendering the popup, then resubmitting to the same script once every permission prompt has been answered. The plain credentialed API has no browser round-trip to resubmit against, the same isolation boundary documented on Session & Cookies.

Syncing — Real-Time Exchange with a WebSocket Channel

A one-time request/response, even a resubmit-and-resume one like USER DEVICE itself, cannot keep a live, ongoing exchange running for as long as a page stays open. NEW SYNC CHANNEL opens a genuine WebSocket channel — the frontend and the running script can exchange data continuously, in either direction, without the page ever reloading.

Open a Channel
NEW SYNC CHANNEL SET ?channel
AFTER EMIT ?channel("connect_url")
(* A raw WebSocket endpoint — the same connect_url shape already used by
   NEW VIDEO BRIDGE and NEW AUDIO BRIDGE. No hosted UI, no bundled JavaScript —
   you write your own client-side connection code. *)
Send Data to the Frontend
SYNC CHANNEL ?channel SEND ?payload
Receive Data from the Frontend
SYNC CHANNEL ?channel RECEIVE SET ?data
(* Non-blocking — returns the next queued message immediately, or null
   if nothing has arrived yet. Does not halt the script the way GATHER
   or CONFIRM do; poll it repeatedly, or across separate calls inside a
   START PERSIST timeline, to react to messages as they arrive. *)
Close a Channel
SYNC CHANNEL ?channel CLOSE

How This Works Internally

One persistent Node.js relay service runs continuously as standing Ocalt infrastructure — the same always-on architecture already used by ocalt-core and ocalt-accounts, not a process spawned fresh per channel. NEW SYNC CHANNEL registers a new channel inside that one already-running relay and hands back a connect_url scoped to it. ql-runtime talks to the relay over its own private, internal-only HTTP API — the same pattern already used for ql-runtime’s calls to ocalt-core and ocalt-accounts — to send and retrieve messages on a channel. The developer’s own frontend JavaScript connects directly to the relay’s public WebSocket endpoint via the connect_url, exactly as it does for a Bridge call.

NEW VIDEO BRIDGE and NEW AUDIO BRIDGE, documented on Audio & Video Bridge | WebRTC, are built on this exact same relay — a video call is simply a sync channel whose messages happen to carry WebRTC signaling data instead of arbitrary application data.