JavaScript SDK
A custom app or widget runs in an iframe. Reaching its own Lua works without RoadVR, and being reached from its own Lua works throughsendToRoadVrApp. The
SDK is the third direction: the page asking RoadVR itself what is going on,
and being told when it changes.
Loading it
public/ belongs in it. There
is nothing to add to your own files {} block — the file is served by RoadVR,
which means there is no copy in your resource that can go stale against a
newer headset.
Use the
https://cfx-nui- form, not nui://. Both reach the same file, but
the address decides what your page is standing on: a browser sends a
Referer only from an http(s) document, so a page loaded over nui://
reaches every service on the web without one — and a service that checks it,
YouTube being the loud example, refuses to serve you. The cfx-nui- form
gives your page a real web origin, which is also what makes fetch and
browser storage behave the way you expect.The global is
RoadVr, matching the resource name. The product is written
RoadVR; the identifier is not.Getting started
RoadVr.ready()
Promise<context>
Settles once the headset has answered, with the context object below.
surface: null and usable defaults — rather than leaving
your page waiting forever.
Calling it twice is free: once it has settled it resolves immediately.
RoadVr.context
The same objectready() resolves with, kept up to date. Events update it
before the handlers run, so a handler that would rather read the whole
picture finds it already current — with two exceptions: placement does not
write window.distance, and headset.state keeps the value it had at
ready().
string | null
The SDK contract version the headset speaks, not the resource version. It
moves when a field is added, so you can check whether the headset you happen
to be running in knows about something.
null means nothing answered.RoadVr.version, on the global, is the version of the script file you
loaded.string | null
'app', 'widget', or null outside a headset.object | null
Apps only.
width and height are metres, not pixels — your page always
lays out 1280 CSS pixels wide however large the window stands in the world,
and 1280 × height / width tall. Reading these as pixels gives you a layout
that changes with window size.anchor is 'body' (follows the player), 'world' (stays put) or
'group' (the player’s windows are following as a group).object | null
Widgets only.
size is metres of width — one number, because the height
follows from aspect. mount is 'recess' or 'raised'.object
light is a boolean, accent a hex colour, iconTheme the icon set name.
palette is RoadVR’s own CSS custom properties — see
applyTheme.object
language is the player’s own interface choice, not the server’s
Config.Locale. Those are two different settings, and mixing them up shows
German text to a player who set English.object
state is off, passthrough, focus or shutdown, as it was when
ready() settled. focus is the mode in which the player can actually click
things.focused is this window being the focused one, not some window being
focused.visible is false while the pause menu or the big map is up, the camera is
out of first person, the drone is flying, or the cinema screen is up. One
field for all of them, because the answer is the same in all of them: stop
animating.object | null
null until you call watch('world'). See Weather and the
clock.RoadVr.on()
string
required
One of the events below.
function
required
Receives the changed subtree, not the whole context.
function
The counterpart. Calling it removes this one handler.
The events
message and the raw listener
RoadVr.on('message', …) and a plain window.addEventListener('message', …)
carry the same pushes from your own Lua. The difference is that the SDK keeps
its own traffic out of the first one; a raw listener sees both and has to tell
them apart itself.
Everything RoadVR forwards carries source: 'roadvr'. SDK traffic additionally
carries channel: 'sdk'.
RoadVr.applyTheme()
HTMLElement
default:"document.documentElement"
Where to write the custom properties.
ready().
The names are the ones the interface uses itself: --vr-text,
--vr-text-dim, --vr-text-3, --vr-glass-top, --vr-glass-bottom,
--vr-chrome-top, --vr-chrome-bottom, --vr-separator, --vr-stroke,
--fill-idle, --fill-hover, --fill-selected, --on-selected,
--view-recessed, --view-thin, --view-regular, --view-thick, plus
--vr-accent.
Point your own variables at them with a fallback and the page still opens in a
browser:
Watched topics
Two things are not sent unless you ask for them, because both tick and a ticker running for a page that never reads it is load nobody chose. Each one starts with its first watcher and stops with its last.Weather and the clock
weather, temperature, hours,
minutes, windSpeed, windBearing, sunrise, sunset, day, month,
year and weekday. RoadVr.context.world is null until you watch it.
How far away your window is
Apps only — a widget hangs in a wall and does not move.distance is metres from the player’s head to your window, four times a second
and only while it actually changes. It is the value to turn a volume down with
as the player walks away, and what a display carrying too much detail to read
from across the room can simplify itself on.
Nothing else about where anybody is standing travels — not the window’s
position and not the player’s. One number is the whole answer to “am I near
it”; the position would be a different question with a different answer.
A frame that goes away without calling
unwatch still stops counting — a
ticker does not stay on for a window nobody can see.RoadVr.setActions()
Puts buttons in your own window’s ornament — the bar along the bottom edge that already holds the close button, the grabber and the tilt. Yours go to the left of the close button.array
required
Up to three items of
{ id, label, icon, onSelect }. An empty array takes
them away again.string
Yours. It comes back as the payload of the
action event, and it is how the
SDK finds your onSelect again.string
Finished text, not a locale key. Your page carries its own strings and
RoadVR does not translate them — switch them yourself on the
locale event
and call setActions again.string
Either a name from RoadVR’s own bundled set, spelled
solar:alt-arrow-left-bold, or an SVG path in a 24×24 box. A name gets you
the same filled glyph the close button beside it uses, but only for the icons
RoadVR happens to bundle — a name it does not have logs a warning and draws
nothing. A path always works and is drawn as an outline.function
Runs in your page. The press travels as the id and the SDK finds your
function again, so a second
setActions with a shorter list cannot leave a
handler behind for a button that is gone.RoadVr.close()
Closes your own window.It does nothing for a widget. A widget hangs in a wall and belongs to the
player; taking one off is theirs to do.
RoadVr.usePointer()
Who draws the pointer inside your page.boolean
default:"true"
false hands the cursor back to your page — for a crosshair, a brush, a game,
anything that draws a pointer of its own.What the SDK cannot do
By design, this version is read plus events, plus three actions — closing your own window, hanging buttons in its ornament, and handing back the pointer. There is no way to play a sound, resize your own window, or open another app from the page. A notification goes through your own Lua andshowRoadVrNotification. Nor does
the SDK hand out game data — vehicle, health, position and the rest belong in
your own Lua, and RoadVR is not a general game API.
The message protocol is built so those can be added later without breaking
anything already written against it.
A game connects through a different SDK.
RoadVrGame covers the shared
room, the state every participant sees and the actions your page submits for
judgement — see the Game SDK.Related
Custom Apps
Getting your page onto the home screen
Custom Widgets
Getting your page onto a wall
Game SDK
The other browser API, for multiplayer games
Client API
The Lua half