SDK
The TypeScript library your app page bundles, the protocol and the harness.
Devices pages
@evolent/os-app is the small TypeScript library an app page uses to run inside the Evolent OS (laptops, computers, the tablet, and full screen on terminals). Protocol 1, SDK 1.0.0. It is MIT licensed, so your app can carry any licence.
Installation
Add it to the devDependencies of your page's Vite project, with the path from your page's folder to the sdk/ folder of evolent_devices:
"@evolent/os-app": "file:../../evolent_devices/sdk"Then import { connectApp } from '@evolent/os-app'. It is bundled into your build. The example app evolent_os_hello is a Svelte 5 page set up this way.
A page that is not built can copy dist/os-app.js next to itself and import from ./os-app.js, or load dist/os-app.iife.js with a script tag and use window.EvolentOS.connectApp().
Never load it at run time from https://cfx-nui-evolent_devices/...: bundle it, so your page keeps working with the host stopped and needs no cross-origin module load.
Usage
import { connectApp } from '@evolent/os-app';
const os = connectApp();
os.onOpen(({ context }) => show(context.greeting));
os.on('greet', (data) => greet(data.name));
const reply = await os.post('roll', { side: 6 });
os.publish({ rolled: true });contextis what your server'sonOpenreturned.os.on('greet', ...)gets your client'sexports.evolent_devices:sendAppMessage(app, { action = 'greet', data = ... }).os.post('roll')reaches your resource'sRegisterNUICallback('roll'), or the host relay.os.publishis seen by people within 12 m, so never send names, money, ids, plates or typed text.
Outside the game os.hosted is false and post() is a normal NUI fetch.
Your page is its own document and gets no fonts from the host. To match the OS, bundle Poppins and JetBrains Mono (SIL Open Font License) in your resource with @font-face and list the files in files, as the example app does from web/public/fonts.
API
Properties
| Property | Description |
|---|---|
hosted | false outside the game. |
mode | use, watch, or ''. |
kind, app | The device kind and your app id. |
transport | frame, direct (terminals) or none. |
input | dui on laptops and computers, native on the tablet. |
token, context | The session token and what onOpen returned. |
user | { name, initials }. |
locale, brand, scale | The OS texts, the server brand and the page scale. |
opened, relayed | Whether the page is open, and whether posts go through the host relay. |
studio | The first view of the 3D studio on a terminal, or null. |
Methods
| Method | Description |
|---|---|
on(action, fn) | Pushes with { action, data }. |
onMessage(fn) | Every other push. |
onOpen(fn), onClose(fn) | The session opens or closes. |
onIntent(fn) | An intent from openApp(appId, intent) or a notification. |
onVisibility(fn) | Minimised, locked or no signal. |
onState(fn) | The published state, in watch mode. |
post(name, data?, { readOnly?, timeout? }) | Calls your NUI callback, or the relay. |
publish(state) | Shares a small state with people nearby. |
notify(notice) | A notification from the page. |
recent({ title, sub?, intent? }) | An entry in the launcher's recent list. |
badge(count) | A count on your app icon. |
privatePointer(on) | Hides the pointer from people nearby while a code is typed. |
studioSend(action) | Drives the 3D studio on a terminal. |
close(), leave() | Closes the app, or leaves the device. |
ready() | With connectApp({ manualReady: true }), tells the host the page is ready. |
disconnect() | Stops listening. |
The types are in sdk/dist/os-app.d.ts.
The harness
The harness is a host emulator for a normal browser. In the web/ folder of evolent_devices run npm run dev and open /preview/harness.html?load=1 on the address Vite prints. It loads the app page twice: the user's window and the watch copy that people nearby get. It answers hello with os:open and the context you type, sends typed keys as os:key, sends your own messages and intents, answers every post with the reply you set, and shows the published state and every message from the app.
The app page must come from the same server, because the SDK trusts the harness origin only with ?harness=1, which the harness adds. By default it loads a sample app. For your own page, add its folder to server.fs.allow in web/vite.config.ts and pass ?url=/@fs/ followed by the full path to its web/build/index.html. Other query parameters: app, kind, input, width, height.
Building the SDK
In the web/ folder of evolent_devices: npm run build:sdk. It writes dist/os-app.js and dist/os-app.iife.js (Chromium 103, not minified). App pages bundle the SDK when they are built, so rebuild them after an SDK change.
Protocol 1
For your own SDK. Every protocol message is a plain object with __os: 1, type and the session token.
os:open { protocol, app, kind, mode, input, context, user, locale, brand, scale, canvas, intent }, os:intent { intent }, os:visibility { visible }, os:key { kind: 'text', text } or { kind: 'key', key, ctrl, shift }, os:dblclick { x, y }, os:state { state }, os:close { reason }, os:reply { id, data }. Your own messages come as they were sent, without __os.
hello { protocol, sdk, app } (no token yet; repeat it until os:open), ready, activity, editing { on }, unhandled { key, ctrl }, cursor, publish { state }, privatePointer { on }, notify { notice }, recent { entry }, badge { count }, close, leave, post { id, name, data }.
The host posts with the iframe URL's origin as target and takes a message only from that iframe's window, that origin and that token. The SDK takes host messages only from window.parent with origin https://cfx-nui-evolent_devices.
Terminal pages (no parent window) post device:ready, device:state { state, editing?, privatePointer? }, device:studio { act, ... }, device:close and os:post { id, name, data, app } to https://evolent_devices/ plus the name, with browser and s from the page URL. They get init, open { app: { token, context }, studio? }, watch, state, deviceKey, deviceDblClick, osReply, close, studio and studioCar from the game. The SDK reports a field in use as device:state { editing }, so Escape and Backspace type into the field instead of closing the terminal.
Every message the game sends to a screen carries __s, the secret from the page's #s=: take none without it, since a frame inside your page could post the rest. The SDK does this.
Within protocol 1 changes only add; both sides ignore what they do not know. A page that says another protocol gets "This app needs an update" in its window.