npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@signageos/supra-app-launcher

v0.1.0

Published

signageOS applet for launching Android apps on the redroid runtime on Supra Server

Readme

@signageos/supra-app-launcher

Launches an Android application in a local Redroid runtime and renders its stream in an iframe. The package provides both the default signageOS applet and a reusable browser SDK.

The launcher talks only to the co-located runtime daemon at 127.0.0.1:<daemonPort>. It ensures a runtime, polls until the configured application is ready, renders the daemon-provided loopback URL, and keeps the runtime lease alive with heartbeats.

Default applet

The applet waits for @signageos/front-applet readiness and launches from sos.config using sos.authHash as the current device identity.

| Configuration | Type | Default | Description | | --- | --- | --- | --- | | type | string | android | Application type. Only android is supported. | | runtime | string | redroid | Android runtime. Only redroid is supported. | | appId | string | none | Android package identifier to launch. | | daemonPort | number | 8100 | Port of the co-located runtime daemon. |

A missing device identity or unsupported/missing Android configuration is rendered as an applet state instead of contacting the daemon.

Query bootstrap

Opening the applet with either type or appId in the query string starts it directly without waiting for sos.onReady(). Every query entry becomes raw launcher configuration, and identity defaults to standalone when omitted.

index.html?type=android&runtime=redroid&appId=com.example.app&daemonPort=8100&identity=device-id

This bootstrap is intended for standalone development and integration environments.

SDK

Installation and host runtime

The SDK treats @signageos/front-applet as a peer dependency. A signageOS host must import and initialize front-applet exactly once; the SDK reads the host-owned window.sos and does not bundle or import front-applet itself.

import sos from '@signageos/front-applet';
import SupraAppLauncher from '@signageos/supra-app-launcher';

await sos.onReady();

const launcher = new SupraAppLauncher();
const app = await launcher.start({
	androidApp: 'com.example.app',
});

await app.stop();

Fully explicit options can be used in a non-signageOS host without window.sos:

const app = await launcher.start({
	androidApp: 'com.example.app',
	identity: 'device-id',
	display: { width: 1920, height: 1080 },
});

UMD bundle

Load dist-sdk/supra-app-launcher-sdk.js in a browser. The default SDK class is exposed as SupraAppLauncher.

<script src="supra-app-launcher-sdk.js"></script>
<script>
	const launcher = new SupraAppLauncher();
	launcher.start({
		androidApp: 'com.example.app',
		identity: 'device-id',
	});
</script>

Options

interface SupraAppLauncherOptions {
	androidApp?: string | {
		appId: string;
		type?: string;
		runtime?: string;
	};
	geometry?: { x: number; y: number; width: number; height: number };
	daemonPort?: number;
	identity?: string;
	display?: { width: number; height: number };
	pollIntervalMs?: number;
	heartbeatIntervalMs?: number;
	heartbeatMaxTransientFailures?: number;
	container?: HTMLElement;
	rawConfig?: Record<string, unknown>;
	onStarting?: () => void;
	onRuntimeEnsuring?: () => void;
	onRuntimeBooting?: () => void;
	onRuntimeInstalling?: () => void;
	onRuntimeReady?: () => void;
	onRendering?: () => void;
	onError?: (error: unknown) => void;
}

| Option | Default | Description | | --- | --- | --- | | androidApp | raw/host configuration | Android package identifier or full application descriptor. String form uses type: android and runtime: redroid. | | geometry | none | Iframe position and size in CSS pixels. Values must be finite; width and height must be positive. | | daemonPort | 8100 | Local daemon port. | | identity | host sos.authHash | Identity sent as daemon payload key authHash. | | display | derived | Runtime display resolution in positive integer pixels. | | pollIntervalMs | 2000 | Poll fallback delay when the daemon omits retryAfterMs. | | heartbeatIntervalMs | 10000 | Delay between serialized heartbeat requests. | | heartbeatMaxTransientFailures | 3 | Maximum consecutive retry budget for network and HTTP 5xx heartbeat failures. | | container | document.body | Element that receives launcher status content and the stream iframe. | | rawConfig | host sos.config | Raw configuration source, useful for embedded hosts and tests. | | lifecycle callbacks | none | Notifications described below. |

pollIntervalMs, heartbeatIntervalMs, and heartbeatMaxTransientFailures must be non-negative integers. A value of 0 is valid.

Configuration precedence

Values are resolved in this order:

  1. Explicit start() options.
  2. rawConfig passed to start().
  3. Host-owned window.sos (sos.config and sos.authHash).
  4. Launcher defaults.

An explicitly empty identity falls through to raw or host identity. After all sources are resolved, an empty identity produces the visible Device identity unavailable state.

Identity is validated before the rest of launcher configuration. This preserves the default applet behavior when both identity and configuration are invalid.

Geometry and display

geometry controls iframe placement and size. The launcher uses absolute positioning when geometry is supplied.

Runtime display resolution uses this order:

  1. Explicit display.
  2. Raw displayWidth and displayHeight configuration, including URL query parameters.
  3. Floored geometry.width and geometry.height.
  4. Physical window.screen width and height.
  5. Browser viewport width and height.

displayWidth and displayHeight must be supplied together as positive integers. SupraScreen uses these query parameters to pass its render geometry to the nested launcher applet. The daemon payload omits display only when none of these sources yields a positive resolution.

Lifecycle and callbacks

For an immediately ready runtime, callback order is:

onStarting → onRuntimeEnsuring → onRuntimeReady → onRendering

A pending runtime adds onRuntimeBooting and/or onRuntimeInstalling after onRuntimeEnsuring; callbacks follow daemon state transitions. onError receives launcher configuration, daemon, polling, rendering, and terminal heartbeat errors. Missing identity and unsupported Android configuration are represented by visible states and do not call onError.

start() handles launcher errors, renders a stable error state, and resolves to an already-stopped app handle. It does not reject for those handled errors.

Cleanup behavior

start() resolves to an app handle with an asynchronous, idempotent stop() method. Calling start() again first stops the active launch. Stopping an older handle does not affect a newer launch.

stop() and browser pagehide cleanup:

  • abort local in-flight requests;
  • clear poll and heartbeat timers;
  • remove page listeners;
  • remove launcher rendering.

Cleanup is intentionally non-destructive. It never terminates the daemon runtime, never sends DELETE, and never uses an /android/sessions endpoint.

Dependency injection

Constructor dependencies are optional and primarily support deterministic host integration and testing:

const launcher = new SupraAppLauncher({
	fetch: window.fetch.bind(window),
	getRawConfig: () => window.sos.config,
	getAuthHash: () => window.sos.authHash,
	detectDisplayResolution: () => ({ width: 1920, height: 1080 }),
	createGeneration: () => crypto.randomUUID(),
	getDefaultContainer: () => document.querySelector('#launcher'),
	renderStream: (url, container, geometry) => customStream,
	showState: (state, title, detail, container) => renderStatus(state, title, detail),
	addPageHideListener: (listener) => {
		window.addEventListener('pagehide', listener, { once: true });
		return () => window.removeEventListener('pagehide', listener);
	},
	setTimeout,
	clearTimeout,
});

Each dependency can be overridden independently. Production defaults use browser APIs and host window.sos.

Runtime daemon contract

The launcher uses only these requests:

POST /android/runtimes/ensure
GET  /android/runtimes/{runtimeId}
POST /android/runtimes/{runtimeId}/heartbeat

Ensure payload:

{
	"authHash": "device-id",
	"appId": "com.example.app",
	"generation": "launcher-generation",
	"display": { "width": 1920, "height": 1080 }
}

Heartbeat payload:

{ "generation": "launcher-generation" }

Only HTTP(S) render URLs on 127.0.0.1, localhost, or [::1] are accepted.

Development

Build and validation commands run on Node.js 20 or newer:

npm test                 # Compile and run TypeScript Node tests plus JS contract tests
npm run check-types      # Type-check applet and SDK source
npm run lint             # ESLint
npm run lint:prettier    # Verify formatting
npm run build            # Build default applet into dist/
npm run build:sdk        # Build UMD SDK into dist-sdk/
npm run build:sdk:es6    # Build ES6 JavaScript and declarations into es6/
npm run escheck          # Verify applet and UMD JavaScript compatibility
npm run clean-build      # Clean and build every output
npm run check            # Run lint, formatting, and source type checks

Build outputs:

| Directory | Contents | | --- | --- | | dist/ | Default applet bundle and static assets. | | dist-sdk/ | ES5-compatible UMD SDK bundle and source map. | | es6/ | ES6 CommonJS modules, declarations, and declaration maps. |