Lightweight TypeScript client and on-device component for Roku ODC — registry access, app UI inspection, and file management for sideloaded channels. Companion library to @danecodes/roku-ecp.
ODC is an HTTP API on port 8061 that provides runtime access to a dev-sideloaded Roku channel. This package includes both the TypeScript client and the BrightScript component that runs on the device, with automatic injection into your channel at sideload time.
npm install @danecodes/roku-odcimport { inject, OdcClient } from '@danecodes/roku-odc';
import { readFile } from 'node:fs/promises';
// Inject the ODC component into your channel zip before sideloading
const channelZip = await readFile('my-channel.zip');
const injectedZip = await inject(channelZip);
// Now sideload injectedZip to your Roku (e.g. via roku-ecp's sideload())
// Connect to the running channel's ODC server
const odc = new OdcClient('192.168.0.30');
// Read the channel's registry
const registry = await odc.getRegistry();
console.log(registry);
// { auth: { token: 'abc', userId: '42' }, settings: { theme: 'dark' } }
// Write registry values
await odc.setRegistry({
settings: { theme: 'light', volume: '80' },
});
// Clear specific sections
await odc.clearRegistry(['cache', 'temp']);
// Inspect the app UI tree
const ui = await odc.getAppUi();The ODC component must be running inside your channel for the client to connect. This package provides two ways to inject it:
Inject into a channel zip buffer. Returns a new zip with the ODC component added.
import { inject } from '@danecodes/roku-odc';
const original = await readFile('my-channel.zip');
const injected = await inject(original);
await writeFile('my-channel-odc.zip', injected);This:
- Adds the BrightScript ODC server component to
components/roku-odc/ - Adds the launch hook to
source/roku-odc/ - Patches your entry point (
MainorRunUserInterface) to initialize ODC at launch - Patches your Scene component to load the ODC server
- Creates the ODC task node after
screen.show()
Inject directly into a channel directory on disk. Useful during development.
import { injectDir } from '@danecodes/roku-odc';
await injectDir('./my-channel');Once injected, the ODC component supports launch-time configuration via ECP launch params:
import { EcpClient } from '@danecodes/roku-ecp';
const ecp = new EcpClient('192.168.0.30');
// Launch with pre-loaded registry state
await ecp.launch('dev', {
odc_registry: JSON.stringify({ auth: { token: 'test' } }),
});
// Clear registry on launch
await ecp.launch('dev', { odc_clear_registry: 'true' });
// Pass channel data (deeplink-like params)
await ecp.launch('dev', {
odc_channel_data: JSON.stringify({ contentId: 'abc' }),
});
// Launch to a specific entry point
await ecp.launch('dev', { odc_entry_point: 'screensaver' });
// Options: 'channel', 'screensaver', 'screensaver-settings'| Option | Type | Default | Description |
|---|---|---|---|
port |
number |
8061 |
ODC server port |
timeout |
number |
10000 |
Request timeout in milliseconds |
Read all registry sections and keys for the running channel.
const registry = await odc.getRegistry();
// { sectionName: { key: 'value', ... }, ... }Write registry values. Merges with existing data (PATCH semantics).
await odc.setRegistry({
auth: { token: 'new-token' },
prefs: { language: 'en' },
});Clear specific registry sections, or all sections if none specified.
// Clear specific sections
await odc.clearRegistry(['cache', 'temp']);
// Clear entire registry
await odc.clearRegistry();Get the current app UI tree as XML. Optionally filter to specific fields per component type.
const ui = await odc.getAppUi();
// Only fetch specific fields
const ui = await odc.getAppUi({ Label: ['text', 'color'] });Download a file from the device.
const data = await odc.pullFile('tmp:/data.json');Upload a file to the device.
const data = new TextEncoder().encode('{"mock": true}');
await odc.pushFile('tmp:/config.json', data);List files on the device.
const files = await odc.listFiles('tmp:/');All errors are typed for easy catch filtering:
import { OdcClient, OdcHttpError, OdcTimeoutError } from '@danecodes/roku-odc';
try {
await odc.getRegistry();
} catch (err) {
if (err instanceof OdcTimeoutError) {
console.log(`Timed out after ${err.timeoutMs}ms`);
} else if (err instanceof OdcHttpError) {
console.log(`${err.method} ${err.path} → ${err.status} ${err.statusText}`);
}
}- Node.js >= 22
- A Roku device on the same network
- The channel must be dev-sideloaded with the ODC component injected (via
inject()orinjectDir()) - Network access to the device on port 8061
MIT