// `connect()` — the node entrypoint of the rnxsim SDK. resolves the running // bridge world the same way the CLI does (development bridge registry, then // daemon, then default port), picks a sim, and returns a connected SimClient // over the shared ws transport. node-only by design: sim-client.ts stays // browser-safe. import { createCloudBridgeForParsed, resolveCloudSessionForParsed, } from '../cli/cloud-client' import { readCurrentSimId } from '../cli/current-sim' import { createBridge, resolveBridgeWorld, type BridgeSimInfo } from '../cli/ws-bridge' import { SimClient, SimConnectError } from './sim-client' export interface ConnectOptions { /** ws bridge port. default: the resolved bridge world (lockfiles). */ port?: number /** sim id to attach to (as shown by `rnx list`). default: the primary sim. */ sim?: string /** per-command bridge timeout in ms. default 15000. */ timeoutMs?: number } function pickSim( sims: BridgeSimInfo[], wanted: string | undefined, port: number, ): BridgeSimInfo { const open = sims.filter((sim) => sim.readyState === 'open') if (wanted) { const match = open.find((sim) => sim.id === wanted) if (!match) { throw new SimConnectError( `no sim connected with id ${wanted} on bridge :${port}` + (open.length ? ` — connected: ${open.map((sim) => sim.id).join(', ')}` : ' — no sims connected; open one with `rnx open `'), ) } return match } // same session affinity as the CLI: a sim pinned by `rnx open` / // `rnx use` in this terminal wins over the primary election, so a script // run next to CLI commands drives the sim the terminal is working with. a // stale pin (sim gone) falls through to the primary. const pinned = readCurrentSimId() if (pinned) { const match = open.find((sim) => sim.id === pinned) if (match) return match } const primary = open.find((sim) => sim.isPrimary) ?? open[0] if (!primary) { throw new SimConnectError( `no sims connected on bridge :${port} — open one with \`rnx open \``, ) } return primary } export async function connect(options: ConnectOptions = {}): Promise { const target = { simId: options.sim, simIdSource: options.sim ? 'flag' : 'none', } satisfies Parameters[0] const cloud = resolveCloudSessionForParsed(target) if (cloud) { const bridge = createCloudBridgeForParsed(target) if (!bridge) throw new SimConnectError('remote simulator session disappeared while connecting') return new SimClient(bridge, cloud.service === 'sim' ? cloud.simId : cloud.boxId, { commandTimeoutMs: options.timeoutMs ?? 15_000, }) } const port = options.port ?? resolveBridgeWorld().bridgePort const commandTimeoutMs = options.timeoutMs ?? 15_000 // discovery socket: list sims and pick the target. closed before the pinned // client socket is opened so a failed connect never leaks a connection. const discovery = createBridge(port, { commandTimeoutMs, cliLabel: 'sdk' }) let picked: BridgeSimInfo try { picked = pickSim(await discovery.listSims(), options.sim, port) } catch (error) { discovery.close() if (error instanceof SimConnectError) throw error const message = error instanceof Error ? error.message : String(error) throw new SimConnectError(`${message} — is the rnx daemon or dev shell running?`) } discovery.close() // pinned socket: every command targets exactly the picked sim, immune to the // CLI's saved-pin re-resolution. const bridge = createBridge(port, { commandTimeoutMs, cliLabel: 'sdk', simId: picked.id, simIdSource: 'flag', }) return new SimClient(bridge, picked.id, { commandTimeoutMs }) }