@signageos/supra-screen
v1.0.0
Published
signageOS applet
Keywords
Readme
Supra Screen
Supra Screen applet for signageOS — starts the Supra daemon, manages the screen app
process (reuse/restart), and attaches the screen renderer. Built on top of the
@supraio/client-sdk daemon SDK.
Build Targets
This project produces three targets:
- Applet (
npm run build) — a signageOS applet bundle (dist/index.html+ assets) that can be uploaded to Box and deployed to devices as-is. The entry point (src/index.ts) simply instantiates the SDK and starts it. - SDK bundle (
npm run build:sdk) — a UMD bundle (dist-sdk/supra-screen-sdk.js) exposing the public SDK surface (src/sdk.ts). SDK-owned dependencies are bundled in, so it can be dropped into a signageOS applet via a<script>tag. The host applet must provide one initialized@signageos/front-appletruntime throughwindow.sos. - SDK ES6 modules (
npm run build:sdk:es6) — plain TypeScript transpilation ofsrc/intoes6/(ES6 target, CommonJS modules, no bundling), including.d.tsdeclarations and source maps. This is the target consumers should import from when they do their own bundling and tree-shaking, mirroring thees6/output of@signageos/front-appletand@signageos/front-display.
Both SDK targets are built together by npm run clean-build:sdk.
Native Runtime Assets
SupraScreen depends on native binaries and runtime assets that cannot be bundled
into a JavaScript module: WASM binaries (supra-client-daemon.wasm,
supra-client-screen.wasm, samsung-wasm-decoder.wasm), the conditional H.264
fallback (h264decoder.js), NaCl executables (nacl-decoder_*.nexe), Web Workers
(samsung-wasm-decoder.worker.js), and pre-built polyfills (polyfill.js,
encoding.js). These files must be served at the document root alongside the
applet's index.html.
The authoritative list lives in tools/native-assets.js (17 entries). During
the applet build, webpack copies them from node_modules into dist/, then
transpile and monkey-patch post-process several of them. That finalized
dist/ directory ships in the npm package and is also the CLI's source. SDK
consumers never need to locate or process raw dependency files.
Build integration (one line)
Add the copy step after your bundler finishes:
# In package.json scripts (the binary is resolved from node_modules/.bin):
"build": "webpack --mode production && supra-screen copy-assets --to dist"
# Or run directly via npm exec:
npm exec supra-screen -- copy-assets --to distThe CLI copies all 17 native assets into the specified directory, creating it
recursively if needed. Existing files are overwritten deterministically so
rebuilds are idempotent. The source assets are resolved relative to the
installed @signageos/supra-screen package (not the consumer's working
directory), so no additional configuration is needed.
Direct CLI usage
supra-screen copy-assets --to <directory>
supra-screen --helpThe copy-assets command exits nonzero with a descriptive message when:
- the command or
--toflag is missing - the destination cannot be created
- a packaged source asset is missing (indicates a broken package build)
Why no postinstall?
A postinstall script would couple the copy destination to a hard-coded path,
break npm ci caching, slow down every install, and fail in monorepo hoisting
setups. The explicit build-script approach gives consumers full control over
where assets land and when they are copied.
Troubleshooting 404s for native assets
If the browser fails to load supra-client-daemon.wasm, polyfill.js, or
other native files:
- Verify the build script includes
supra-screen copy-assets --to <output-dir>. - Confirm the output directory matches the bundler's output path (e.g.
dist/). - Check that the dev server or CDN serves files from the same directory.
- Run
ls <output-dir>/supra-client-daemon.wasmto confirm the file exists. - If using
output.clean: truein webpack, place the copy step after webpack.
Usage
A consuming applet must initialize @signageos/front-applet once and wait for readiness
before starting Supra Screen:
import sos from "@signageos/front-applet";
import { SupraScreen } from "@signageos/supra-screen/es6/sdk";
sos.onReady().then(async () => {
const app = await new SupraScreen().start();
});@signageos/front-applet is a peer dependency and is deliberately not imported by the SDK.
This keeps sos.config, authentication, and runtime event state in the host-owned singleton.
start() resolves with a SupraScreenApp handle as soon as the local rendering
client is attached. The handle lets you stop that client later:
// Clean up and exit the local client. The server app keeps running.
await app.stop();
// Clean up the local client, then terminate the server app process.
await app.stop({ terminate: true });stop() resolves only after the local renderer reports that cleanup and exit
completed. It rejects if stop dispatch fails, cleanup fails, or the renderer
does not exit within 10 seconds. A failed local stop never proceeds with
server-process termination.
Note: most devices cannot run multiple instances at different positions simultaneously, even though the SDK API would allow it.
By default all configuration is read from the host-owned global sos.config and platform
defaults. Everything can be overridden via start() options:
await new SupraScreen().start({
appName: "chromium-browser",
appArgs: "--temp-profile --kiosk",
extraArgs: ["--headless"],
onAppStarted: ({ appName }) => console.log(`app ${appName} running`),
onError: (error) => report(error),
});Troubleshooting front-applet runtime errors
FrontAppletRuntimeError means window.sos is missing or has not been initialized. It can
also indicate that multiple front-applet copies or entry points created separate in-memory
runtime objects.
- Import
@signageos/front-appletonly in the host applet entry point. - Await
sos.onReady()before callingSupraScreen.start(). - Run
npm ls @signageos/front-appletand align incompatible versions. - Run
npm dedupe, or use package-manager overrides when transitive packages require incompatible copies. - Do not mix
@signageos/front-appletand@signageos/front-applet/es6/bundleimports. For legacy dependency graphs, alias all front-applet imports to one physical package and one entry point in the bundler.
Libraries must declare front-applet as a peer dependency instead of a production dependency. This prevents each library from bundling its own stateful runtime instance.
Options
All options are optional (SupraScreenOptions). When not set, values are resolved
from sos.config and platform-specific defaults.
| Option | Type | Description |
| ------ | ---- | ----------- |
| appName | string | App process to run (google-chrome, chromium-browser, display-supra, …) |
| appArgs | string \| string[] | Extra arguments passed to the app process |
| appDataPath | string | Persistent data path of the app |
| extraArgs | string \| string[] | Additional daemon CLI arguments |
| forceScreenSize | string | Forced screen resolution, e.g. 1920x1080 |
| daemonPort | number \| string | Port the daemon listens on |
| streamProtocol | StreamProtocol | Streaming protocol override (platform default otherwise) |
| logDriver | string | Daemon log driver |
| daemonRegistry | string | Daemon image registry |
| daemonUsername / daemonPassword | string | Daemon registry credentials |
| daemonVersion | string | Daemon image version |
| androidApp | string \| AndroidAppOptions | Android app to run via the supra-app-launcher applet. Pass a plain package ID string (e.g. com.example.myapp) for defaults, or an object { appId, instanceId?, baseUrl?, appletVersion?, runtime?, identity?, display? } to customize. instanceId is always forwarded and defaults to appId. identity defaults to sos.authHash (without it the launcher runs in standalone mode). Prepended to extraArgs. |
| geometry | { x, y, width, height } | Screen position and size (pixels) where the app is rendered. Requires platform renderers with geometry support. Defaults to fullscreen. For androidApp, width and height also set the Redroid runtime display resolution. |
| instanceId | string | Supra Screen slot used for process persistence and renderer switching. Defaults to androidApp.appId in Android mode, otherwise to appName. This outer slot does not replace androidApp.instanceId, which is always forwarded to the launcher. |
| debug | boolean | Enables debug mode: shows the debug overlay and appends --debug to the started app arguments (e.g. display-supra enables its own debug output). Also settable via debug in sos.config. The overlay can be hidden ad-hoc by its ✕ button (current applet session only, not persisted). |
Events / Callbacks
Lifecycle callbacks let the SDK consumer react to the boot process:
| Callback | When |
| -------- | ---- |
| onStarting | Before anything starts |
| onDaemonStarting | Daemon start requested |
| onDaemonReady | Daemon is ready |
| onAppStarting | New app process is being spawned |
| onAppResuming | Existing app process is being reused (no restart needed) |
| onAppRestarting | Existing app process is being closed and restarted |
| onAppStarted | App process is running |
| onScreenStarting | Screen renderer is being attached |
| onError | Boot failed with an error |
| onRefreshing | Recovery refresh/restart fallback is running (fires when rendering stops without a manual stop()) |
Android App Launcher
Instead of hand-building the complex supra-app-launcher URL, pass the Android package ID directly:
const app = await new SupraScreen().start({
androidApp: "com.example.myapp",
geometry: { x: 100, y: 100, width: 800, height: 600 },
});SupraScreen forwards the floored geometry dimensions to the launcher as displayWidth and displayHeight, so Redroid boots at the render viewport resolution instead of the physical display resolution. The generated URL is equivalent to:
https://2.signageos.io/applet/@signageos/supra-app-launcher/0.1.0/dist/index.html?type=android&runtime=redroid&appId=com.example.myapp&instanceId=com.example.myapp&displayWidth=800&displayHeight=600The launcher query contract is:
type=androidis always sent.runtimedefaults toredroid.appIdis the Android package ID.instanceIdis always sent. It defaults toappIdand can be overridden withandroidApp.instanceId.displayWidthanddisplayHeightare both sent when geometry is supplied.identityis sent whenandroidApp.identityorsos.authHashis available; without it the launcher runs in standalone mode.
Supra Screen itself runs the content renderer process (by default
chromium-browser) and passes the launcher applet URL through --url. The
launcher then owns the Android/Redroid runtime. An outer device host such as
display-supra is separate from this content-renderer process, so using the
browser default here does not replace or bypass that host.
Supra Screen and the launcher only select Android applications that are already
registered by package ID. They do not install APKs, and neither the string nor
object form of androidApp accepts an APK URL.
To customize the launcher URL, use the object form:
const app = await new SupraScreen().start({
androidApp: {
appId: "com.example.myapp",
instanceId: "android-slot", // default appId; always forwarded
baseUrl: "https://staging.signageos.io", // default https://2.signageos.io
appletVersion: "2.0.0", // default 1.1.0
runtime: "waydroid", // default redroid
identity: "…", // default sos.authHash
},
});Source Layout
| Path | Role |
| ---- | ---- |
| src/index.ts | Thin applet entry — new SupraScreen().start() |
| src/sdk.ts | Public SDK surface for the library target |
| src/SupraScreen.ts | SupraScreen class — orchestration, dependency injection |
| src/config/ | Config resolution, platform specs, raw sos.config access, parsers |
| src/apps/ | App process registry (per-app CLI arguments) |
| src/process/ | Process management, restart decision, process polyfills |
| src/polyfills/ | Browser polyfills applied at startup |
| src/utils/ | Small utilities (wait, …) |
| tools/native-assets.js | Authoritative manifest of 17 native runtime assets |
| tools/cli.js | supra-screen CLI binary (copy-assets --to <dir>) |
| tools/monkey-patch.sh | Post-build patches for transpiled daemon/polyfill files |
Testing
npm test # unit + integration tests (node:test, compiled via tsconfig.test.json)
npm run check # eslint + prettier + tsc --noEmitIntegration tests in src/SupraScreen.test.ts run the full SupraScreen.start()
flow with fully mocked dependencies and assert the exact call and callback order.
