@run-cloud/ui renders the signed viewer URL returned for either an iOS
simulator or an Android emulator. It adds embed mode, validates browser
messages against the iframe window and its exact origin, exposes typed lifecycle
and interaction events, and removes its listeners and timers when it unmounts.
The interaction payloads, coordinates, keys, buttons, and platform capability
matrix are documented in Control a Simulator.
Install
@run-cloud/sdk client or the run.cloud API. Never put a run.cloud
API key in a browser bundle.
Create a session on the server
Install the server SDK separately:session.url is the signed viewer URL
passed to the React component:
createSimulatorSession from an authenticated server route, return only
the session metadata and signed URL that the authorized browser needs, and call
releaseSimulatorSession from a server route when the preview is finished.
For scripts and test jobs, put cloud.simulators.delete(...) in finally.
The signed URL is a bearer secret scoped to the session. Do not place it in
logs, analytics, screenshots, or public page URLs.
Render the React component
Copy this component as-is into a React application. Its parent has an explicit, responsive size;RemoteControl fills that area and the embedded viewer adapts
to the available space.
"use client" boundary because refs, state, and
event callbacks run in the browser.
Unmounting RemoteControl removes its iframe message listener and availability
timer. It does not release the metered run.cloud session. Always call your
server-side release path with the session id and platform.
Component props
RemoteControl accepts the iframe attributes id, title, className,
style, allow, draggable, allowTransparency, onLoad, and onError.
These props control the iframe without changing the signed session URL.
The component adds
embed=1. When loadingGuard is true, it also adds
loadingGuard=1. It preserves the signed token and every other query
parameter.
Events
RemoteControlStatus includes streaming, appLaunched, reactNative,
device, bundleId, expectedBundleId, orientation, and
accessibilityOverlay. Treat the component as ready when:
Controls
Use the acknowledgedRemoteControlHandle methods for automation. They resolve
with the matching typed result and reject with RemoteControlInteractionError
when the iframe reports a failure:
setOrientation accepts the shared four-value orientation type. Current
iPhone Simulator sessions support portrait, landscape_left, and
landscape_right; portrait_upside_down rejects with a correlated,
non-retryable unsupported_action failure. Android Emulator sessions support
all four values.
Options accept requestId, timeoutMs, and signal. The timeout defaults to
15,000 milliseconds and accepts 100 to 60,000. Aborting, timing out, changing
the frame, or removing the component sends a correlated cancellation request
before rejecting the pending promise. Cancellation is best effort and cannot
undo an action the simulator already completed.
The older fire-and-forget command channel remains available through
sendCommand, reload, home, rotate, screenshot, and
toggleAccessibility. It accepts:
On current iPhone Simulator sessions, relative viewer rotation skips
portrait_upside_down so the legacy control stays within renderable states.
Each accepted command produces onCommandResult with command, ok, and an
optional error string. Prefer acknowledged interactions for tests and
automation.
Session timeouts
New sessions default to a 60-second inactivity timeout when the option is omitted. Set a duration explicitly for predictable product behavior, or passinactivityTimeout: null in the TypeScript SDK (--inactivity-timeout none in
the CLI) to disable inactivity auto-close. Hard timeouts and explicit release
still apply.
Pointer, touch, wheel, and keyboard input inside the iframe reset an enabled
inactivity timer. Watching the stream without interacting does not. The viewer
shows the final countdown and emits a session-ended event when the lease closes.
Raw iframe alternative
You can use the signed URL without React. Addembed=1, listen only to the
expected iframe window and exact URL origin, and use that same exact origin when
sending commands:
ios-simulator:* lifecycle and legacy-command message names. Acknowledged
interactions use platform-neutral run-cloud:* names.
Removing a raw iframe or its message listener also does not release its cloud
session. Call the authenticated server-side delete operation separately.