Skip to content

Roku Cloud Emulator support - #399

Draft
chrisdp wants to merge 12 commits into
feature/roku-deploy-v4from
feature/rce
Draft

Roku Cloud Emulator support#399
chrisdp wants to merge 12 commits into
feature/roku-deploy-v4from
feature/rce

Conversation

@chrisdp

@chrisdp chrisdp commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

Adds Roku Cloud Emulator (RCE) support to roku-debug. Stacked on #398 (the roku-deploy v4 migration) so this diff is purely the RCE work. Builds against a local checkout of roku-deploy (unified-device-option + RCE support, rokucommunity/roku-deploy#331).

The core change is a new device option on the launch configuration: a roku-deploy DeviceConfig is the canonical way to address the target device, supporting local network devices ({host}) and Cloud Emulator devices ({instanceUrl|id|esn, rceToken}). The deprecated top-level host field is read in exactly one place: normalizeLaunchConfig converts whatever addressing the client supplied into a concrete device config and returns a ResolvedLaunchConfiguration (typed without host, device required), which everything inside the debugger works against, switching between local and cloud transports based on the device config.

Done

  • device?: DeviceConfig on LaunchConfiguration, with host deprecated and converted in normalizeLaunchConfig - the only place the debugger reads it, which then deletes the field from the object. The session stores a ResolvedLaunchConfiguration, whose type has no host at all, so no internal reader can regress. A launch config with neither device nor host fails the launch immediately with a clear message
  • AdapterOptions, the debug protocol client, RokuECP, PerfettoManager, and RendezvousTracker all take a concrete DeviceConfig (their host fields are removed entirely); log and error messages identify the device by a token-safe label (host, instance URL, id, or esn) that never includes the rceToken
  • DNS resolution rides roku-deploy's resolveDns, which takes the whole device config, resolves the host for local devices, and passes cloud devices through unchanged
  • The Cloud Emulator api token can arrive as the ROKU_RCE_TOKEN environment variable instead of riding the launch config: normalizeLaunchConfig hydrates a tokenless RCE device config from it, and the launch configs echoed back in LaunchStartEvent/ChannelPublishedEvent scrub the token out, so it never travels over DAP in either direction
  • Device-info fetch, ECP key presses, screenshots, and sideload all pass the device option through to roku-deploy. Sideloading to a cloud device is live-verified end to end (the launch flow installs and the app runs). Key presses use roku-deploy's canonical RemoteKey casing (Home)
  • Spec coverage for the device option: host conversion during normalize, cloud config passthrough, token-safe labels, env-token hydration, event scrubbing, and the device option reaching sideload
  • Telnet console connections route through roku-deploy's createRokuDeploySocket: a real tcp socket for local devices, the instance api's /api/v0/ports/<port> websocket for cloud devices (Instance API v0.3.0 replaced the named telnet routes with numeric port routes), behind the same socket surface, so TelnetAdapter and TelnetRequestPipeline are unchanged apart from construction. Live-verified on both device types, including breaking into and resuming the micro debugger over the cloud transport
  • Debug protocol control and io sockets route through createRokuDeploySocket too, reaching a cloud device over the instance api's ports routes (8081 for control, plus the io port the device opens) - unblocked by Instance API v0.3.0. The transport is wired and unit-tested; the full protocol-mode session still needs a live end-to-end pass
  • SceneGraph debug server commands (including rendezvous tracking's port 8080 fallback) take a device config (the host-string constructor overload is gone) and connect through the same socket shim; telnet-client still drives the protocol via its injected-socket option. Connect waits for the > prompt (bounded by the connect timeout) before handing the socket to telnet-client, and an error listener stays attached for the socket's whole life so a transport error can never crash the adapter process. Live-verified against a cloud device on the v0.3.0 ports routes - the old telnet proxy's line-buffering swallowed the un-terminated > prompt and forced per-command timeouts, and the byte-unchanged ports routes resolved that
  • Registry, app-state, exit-app, and rendezvous tracking ECP calls use roku-deploy's dedicated queryRegistry/queryAppState/exitApp/queryRendezvous/setRendezvousTracking APIs, and RokuECP's remaining raw requests (the perfetto ECP commands) ride roku-deploy's ecp() transport, which addresses local and cloud devices through one HTTP ECP path (the instance's ecp1 proxy). RokuECP and RendezvousTracker no longer own transports or XML parsing, and device-reported errors (not keyed, limited-mode refusals) surface verbatim. Live-verified against a cloud device, including in limited ECP mode
  • The debug protocol adapter's compile-info telnet connection also routes through createRokuDeploySocket, so no raw sockets remain in the telnet paths. Live-verified connecting and settling over the cloud transport

To do

  • Perfetto tracing over a cloud device: the enable ECP command is live-verified through ecp() (200 OK, dev in enabled-channels), and the instance's /ecp1 proxy even forwards the perfetto-session websocket upgrade to the device - but the device firmware's own source-IP check rejects the connection (403 Source IP 127.0.0.6 not on a private/local network, the service-mesh sidecar address; the check reads the TCP peer, so forwarded-IP headers do nothing). Ask Roku to allow the mesh/loopback source for perfetto-session on RCE firmware; once that lands, capture needs only a URL change in PerfettoManager (the ports routes are a fixed whitelist without 8060, so the ecp1 upgrade path is the viable one)
  • Component library hosting story for cloud devices (the device cannot reach a dev machine's local web server)

@chrisdp chrisdp self-assigned this Jul 24, 2026
chrisdp added 9 commits July 24, 2026 14:32
…p, and rendezvous

RokuECP and RendezvousTracker no longer own transports or XML parsing for these commands; roku-deploy's device-aware wrappers handle both local and Cloud Emulator devices. Also fixes rendezvous trackingEnabled being a truthy string instead of a boolean.
roku-deploy now enforces the canonical RemoteKey names at the type level, so the home keypress calls use 'Home'.
…ocket

DebugProtocolClient received the device config but never used it - both
sockets connected raw tcp to options.host, which for an RCE device is
the unresolved ${promptForHost} placeholder. They now route through
roku-deploy's createTelnetSocket like the compile client already does,
so an RCE device reaches the control port (8081) and io port over the
instance api's /api/v0/ports/<port> WebSocket routes. Also drops the
removed channel option from the existing createTelnetSocket call sites.
…able

When the launch config's device option is an RCE config without an
rceToken, fill it from the env var (a config-supplied token always
wins). Hydration happens at the device-option accessors rather than in
normalizeLaunchConfig so the stored launch config stays tokenless and
never echoes the token back through logs or custom events.
The deprecated launch config host field is now read in exactly one
place: normalizeLaunchConfig converts it into the device config and
returns a ResolvedLaunchConfiguration (typed without host, device
required), which everything downstream uses. AdapterOptions,
DebugProtocolClient, RokuECP, PerfettoManager, and RendezvousTracker
all take a concrete DeviceConfig; the scattered host fallbacks and
device getters are gone. The RCE api token is hydrated from
ROKU_RCE_TOKEN during normalize, and the launch config copies echoed in
LaunchStartEvent/ChannelPublishedEvent scrub it back out. Log and error
messages use a new util.deviceLabel helper instead of host, and
RokuECP routes raw requests through rokuDeploy's ecp transport instead
of hand-building host urls.
- untrack RokuAppLaunchConfiguration.ts (stays as a local file)
- SceneGraphDebugCommandController: accept only a DeviceConfig, keep an
  error listener attached for the socket's whole life, and replace the
  quiet-period greeting drain with a prompt wait bounded by the connect
  timeout
- fail the launch fast when the config supplies no device addressing
- delete the deprecated host field from the config during normalize
Comment thread src/util.ts Outdated
* includes credentials like the rceToken). A local device is identified by its host and an RCE
* device by its instanceUrl, id, or esn.
*/
public deviceLabel(device: DeviceConfig): string {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe call it getDeviceLabel instead?

const options = {
controlPort: undefined as number,
host: '127.0.0.1'
host: '127.0.0.1',

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't take host at root level anymore.

*/
private get device(): DeviceOption {
return { host: this.launchConfiguration.host };
private get isLocalDevice(): boolean {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's call them physical and cloud devices. (local isn't great...)

return this.shutdown(`Could not resolve ip address for host '${this.launchConfiguration.host}'`);
//do a DNS lookup for the host to fix issues with roku rejecting ECP.
//only applies to local devices; other device types (like the Roku Cloud Emulator) are not addressed by host
if (this.isLocalDevice) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you were going to create a rokuDeploy (resolveDns) function or something for this, so we can pass the whole device in, and it resolves the device if it's a local one (but skips for rce devices).

chrisdp added 2 commits July 31, 2026 16:27
roku-deploy renamed its device-agnostic console transport: TelnetSocket
is now RokuDeploySocket, created by createRokuDeploySocket taking
SocketOptions. Rename the imports, the protected createRokuDeploySocket
test seams, and the spec fakes to match. No behavior changes.
- Rename util.deviceLabel to util.getDeviceLabel.
- DebugProtocolClient's spec no longer passes a root-level host to the
  client; the shared options literal is split into the server's
  {controlPort, host} (bind address) and the client's
  {controlPort, device}.
- The launch DNS lookup goes through the new rokuDeploy.resolveDns,
  which takes the whole device config and only resolves local devices,
  so the session's isLocalDevice branch (and the getter) are gone.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants