From 6274f3c2b96fb4fe0ca66d024c1c6585e9d1b721 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanis=C5=82aw=20Chmiela?= Date: Fri, 19 Jun 2026 14:20:28 +0200 Subject: [PATCH 1/5] [build-tools] Report serve-sim preview URL --- .../src/steps/functions/startServeSimRemoteSession.ts | 5 ++--- .../src/steps/utils/remoteDeviceRunSession.ts | 11 +++++------ 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/build-tools/src/steps/functions/startServeSimRemoteSession.ts b/packages/build-tools/src/steps/functions/startServeSimRemoteSession.ts index 8daccbced5..bbc9f97acb 100644 --- a/packages/build-tools/src/steps/functions/startServeSimRemoteSession.ts +++ b/packages/build-tools/src/steps/functions/startServeSimRemoteSession.ts @@ -28,19 +28,18 @@ export function createStartServeSimRemoteSessionBuildFunction( await selectXcodeDeveloperDirectoryAsync({ env, logger }); - const { previewUrl, streamUrl } = await startServeSimWithTunnelAsync(ctx, { + const { previewUrl } = await startServeSimWithTunnelAsync(ctx, { baseDomain: ngrokTunnelDomain, env, logger, timeoutMs: STARTUP_TIMEOUT_MS, }); logger.info(`Preview URL: ${previewUrl}`); - logger.info(`Stream URL: ${streamUrl}`); await uploadRemoteSessionConfigAsync({ ctx, deviceRunSessionId, - remoteConfig: { previewUrl, streamUrl }, + remoteConfig: { previewUrl }, logger, }); diff --git a/packages/build-tools/src/steps/utils/remoteDeviceRunSession.ts b/packages/build-tools/src/steps/utils/remoteDeviceRunSession.ts index 5e084a1aa6..fd146ee8b4 100644 --- a/packages/build-tools/src/steps/utils/remoteDeviceRunSession.ts +++ b/packages/build-tools/src/steps/utils/remoteDeviceRunSession.ts @@ -258,7 +258,7 @@ export async function startServeSimWithTunnelAsync( logger: bunyan; timeoutMs: number; } -): Promise<{ previewUrl: string; streamUrl: string }> { +): Promise<{ previewUrl: string }> { logger.info('Launching serve-sim with tunnel.'); const turnArgs = await fetchServeSimTurnArgsAsync(ctx, { env, logger }); const serveSim = spawnDetached({ @@ -281,19 +281,18 @@ export async function startServeSimWithTunnelAsync( env, }); - logger.info('Waiting for serve-sim to report tunnel and stream URLs.'); + logger.info('Waiting for serve-sim to report tunnel URL.'); const deadline = Date.now() + timeoutMs; while (Date.now() < deadline) { const output = serveSim.getOutput(); const previewUrl = matchLabeledUrl({ output, label: 'Tunnel', baseDomain }); - const streamUrl = matchLabeledUrl({ output, label: 'Stream', baseDomain }); - if (previewUrl && streamUrl) { - return { previewUrl, streamUrl }; + if (previewUrl) { + return { previewUrl }; } await sleepAsync(1_000); } throw new SystemError( - `Timed out waiting for serve-sim to report Tunnel and Stream URLs. Last output:\n${serveSim.getOutput() || ''}` + `Timed out waiting for serve-sim to report Tunnel URL. Last output:\n${serveSim.getOutput() || ''}` ); } From 457a39c194f6bd6dfca287acacdd9ff350ccade8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanis=C5=82aw=20Chmiela?= Date: Fri, 19 Jun 2026 14:21:15 +0200 Subject: [PATCH 2/5] [build-tools] Use sjchmiela serve-sim tunnel args --- .../__tests__/remoteDeviceRunSession.test.ts | 46 ++++++++++++++++ .../src/steps/utils/remoteDeviceRunSession.ts | 53 +++++++++++++------ 2 files changed, 84 insertions(+), 15 deletions(-) diff --git a/packages/build-tools/src/steps/utils/__tests__/remoteDeviceRunSession.test.ts b/packages/build-tools/src/steps/utils/__tests__/remoteDeviceRunSession.test.ts index 4f90de3b03..8603733bcc 100644 --- a/packages/build-tools/src/steps/utils/__tests__/remoteDeviceRunSession.test.ts +++ b/packages/build-tools/src/steps/utils/__tests__/remoteDeviceRunSession.test.ts @@ -5,6 +5,7 @@ import { CustomBuildContext } from '../../../customBuildContext'; import { Sentry } from '../../../sentry'; import { turtleFetch } from '../../../utils/turtleFetch'; import { + createServeSimTunnelArgs, fetchServeSimTurnArgsAsync, turnIceServersToServeSimArgs, } from '../remoteDeviceRunSession'; @@ -36,6 +37,51 @@ function createEnvMock(): BuildStepEnv { return { DEVICE_RUN_SESSION_ID: 'drs-id' } as unknown as BuildStepEnv; } +describe(createServeSimTunnelArgs, () => { + it('builds ngrok/H264 serve-sim args and passes through TURN flags', () => { + expect( + createServeSimTunnelArgs({ + baseDomain: 'expo-simulator.ngrok.dev', + turnArgs: [ + '--stun-url', + 'stun:stun.cloudflare.com:3478', + '--turn-url', + 'turns:turn.cloudflare.com:443?transport=tcp', + '--turn-username', + 'u', + '--turn-credential', + 'c', + ], + }) + ).toEqual([ + 'serve-sim-sjchmiela@latest', + '--tunnel', + '--tunnel-provider', + 'ngrok', + '--tunnel-domain', + 'expo-simulator.ngrok.dev', + '--stream-max-dimension', + '1280', + '--stream-quality', + '0.55', + '--stream-fps', + '10', + '--h264-bitrate', + '3000000', + '--h264-max-fps', + '30', + '--stun-url', + 'stun:stun.cloudflare.com:3478', + '--turn-url', + 'turns:turn.cloudflare.com:443?transport=tcp', + '--turn-username', + 'u', + '--turn-credential', + 'c', + ]); + }); +}); + describe(turnIceServersToServeSimArgs, () => { it('returns no args for an empty ICE server list', () => { expect(turnIceServersToServeSimArgs([])).toEqual([]); diff --git a/packages/build-tools/src/steps/utils/remoteDeviceRunSession.ts b/packages/build-tools/src/steps/utils/remoteDeviceRunSession.ts index fd146ee8b4..4b1496fdda 100644 --- a/packages/build-tools/src/steps/utils/remoteDeviceRunSession.ts +++ b/packages/build-tools/src/steps/utils/remoteDeviceRunSession.ts @@ -16,6 +16,13 @@ import { turtleFetch } from '../../utils/turtleFetch'; const XCODE_DEVELOPER_DIR = '/Applications/Xcode.app/Contents/Developer'; +const SERVE_SIM_PACKAGE_SPEC = 'serve-sim-sjchmiela@latest'; +const SERVE_SIM_MJPEG_FALLBACK_MAX_DIMENSION = '1280'; +const SERVE_SIM_MJPEG_FALLBACK_QUALITY = '0.55'; +const SERVE_SIM_MJPEG_FALLBACK_FPS = '10'; +const SERVE_SIM_H264_BITRATE = '3000000'; +const SERVE_SIM_H264_MAX_FPS = '30'; + const START_DEVICE_RUN_SESSION_MUTATION = graphql(` mutation StartDeviceRunSession($deviceRunSessionId: ID!, $remoteConfig: JSONObject!) { deviceRunSession { @@ -263,21 +270,7 @@ export async function startServeSimWithTunnelAsync( const turnArgs = await fetchServeSimTurnArgsAsync(ctx, { env, logger }); const serveSim = spawnDetached({ command: 'npx', - args: [ - 'serve-sim-szdziedzic@latest', - '--tunnel', - '--tunnel-provider', - 'ngrok', - '--tunnel-domain', - baseDomain, - '--stream-max-dimension', - '1280', - '--stream-quality', - '0.55', - '--codec', - 'webrtc', - ...turnArgs, - ], + args: createServeSimTunnelArgs({ baseDomain, turnArgs }), env, }); @@ -296,6 +289,36 @@ export async function startServeSimWithTunnelAsync( ); } +export function createServeSimTunnelArgs({ + baseDomain, + turnArgs = [], +}: { + baseDomain: string; + turnArgs?: string[]; +}): string[] { + return [ + SERVE_SIM_PACKAGE_SPEC, + '--tunnel', + '--tunnel-provider', + 'ngrok', + '--tunnel-domain', + baseDomain, + // MJPEG remains the browser fallback when AVCC/H.264 is unavailable. + '--stream-max-dimension', + SERVE_SIM_MJPEG_FALLBACK_MAX_DIMENSION, + '--stream-quality', + SERVE_SIM_MJPEG_FALLBACK_QUALITY, + '--stream-fps', + SERVE_SIM_MJPEG_FALLBACK_FPS, + // The tunneled helper advertises /stream.avcc, so keep H.264 network usage bounded. + '--h264-bitrate', + SERVE_SIM_H264_BITRATE, + '--h264-max-fps', + SERVE_SIM_H264_MAX_FPS, + ...turnArgs, + ]; +} + function matchLabeledUrl({ output, label, From 92ea1e8d21096757c74eadde9383ec3b497ef65e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanis=C5=82aw=20Chmiela?= Date: Fri, 19 Jun 2026 14:36:10 +0200 Subject: [PATCH 3/5] [build-tools] Drop unused serve-sim TURN args --- .../__tests__/remoteDeviceRunSession.test.ts | 26 ++----------------- .../src/steps/utils/remoteDeviceRunSession.ts | 12 ++------- 2 files changed, 4 insertions(+), 34 deletions(-) diff --git a/packages/build-tools/src/steps/utils/__tests__/remoteDeviceRunSession.test.ts b/packages/build-tools/src/steps/utils/__tests__/remoteDeviceRunSession.test.ts index 8603733bcc..0a77b8183c 100644 --- a/packages/build-tools/src/steps/utils/__tests__/remoteDeviceRunSession.test.ts +++ b/packages/build-tools/src/steps/utils/__tests__/remoteDeviceRunSession.test.ts @@ -38,22 +38,8 @@ function createEnvMock(): BuildStepEnv { } describe(createServeSimTunnelArgs, () => { - it('builds ngrok/H264 serve-sim args and passes through TURN flags', () => { - expect( - createServeSimTunnelArgs({ - baseDomain: 'expo-simulator.ngrok.dev', - turnArgs: [ - '--stun-url', - 'stun:stun.cloudflare.com:3478', - '--turn-url', - 'turns:turn.cloudflare.com:443?transport=tcp', - '--turn-username', - 'u', - '--turn-credential', - 'c', - ], - }) - ).toEqual([ + it('builds ngrok/H264 serve-sim args', () => { + expect(createServeSimTunnelArgs({ baseDomain: 'expo-simulator.ngrok.dev' })).toEqual([ 'serve-sim-sjchmiela@latest', '--tunnel', '--tunnel-provider', @@ -70,14 +56,6 @@ describe(createServeSimTunnelArgs, () => { '3000000', '--h264-max-fps', '30', - '--stun-url', - 'stun:stun.cloudflare.com:3478', - '--turn-url', - 'turns:turn.cloudflare.com:443?transport=tcp', - '--turn-username', - 'u', - '--turn-credential', - 'c', ]); }); }); diff --git a/packages/build-tools/src/steps/utils/remoteDeviceRunSession.ts b/packages/build-tools/src/steps/utils/remoteDeviceRunSession.ts index 4b1496fdda..6bf40eac10 100644 --- a/packages/build-tools/src/steps/utils/remoteDeviceRunSession.ts +++ b/packages/build-tools/src/steps/utils/remoteDeviceRunSession.ts @@ -267,10 +267,9 @@ export async function startServeSimWithTunnelAsync( } ): Promise<{ previewUrl: string }> { logger.info('Launching serve-sim with tunnel.'); - const turnArgs = await fetchServeSimTurnArgsAsync(ctx, { env, logger }); const serveSim = spawnDetached({ command: 'npx', - args: createServeSimTunnelArgs({ baseDomain, turnArgs }), + args: createServeSimTunnelArgs({ baseDomain }), env, }); @@ -289,13 +288,7 @@ export async function startServeSimWithTunnelAsync( ); } -export function createServeSimTunnelArgs({ - baseDomain, - turnArgs = [], -}: { - baseDomain: string; - turnArgs?: string[]; -}): string[] { +export function createServeSimTunnelArgs({ baseDomain }: { baseDomain: string }): string[] { return [ SERVE_SIM_PACKAGE_SPEC, '--tunnel', @@ -315,7 +308,6 @@ export function createServeSimTunnelArgs({ SERVE_SIM_H264_BITRATE, '--h264-max-fps', SERVE_SIM_H264_MAX_FPS, - ...turnArgs, ]; } From b9cc36b6a397a118cf2e820e1da5b9be503a395f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanis=C5=82aw=20Chmiela?= Date: Fri, 19 Jun 2026 14:59:10 +0200 Subject: [PATCH 4/5] Use WebRTC H264 serve-sim previews --- .../__tests__/remoteDeviceRunSession.test.ts | 30 +++++++++++++++++-- .../src/steps/utils/remoteDeviceRunSession.ts | 16 ++++++++-- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/packages/build-tools/src/steps/utils/__tests__/remoteDeviceRunSession.test.ts b/packages/build-tools/src/steps/utils/__tests__/remoteDeviceRunSession.test.ts index 0a77b8183c..4ae51db8a5 100644 --- a/packages/build-tools/src/steps/utils/__tests__/remoteDeviceRunSession.test.ts +++ b/packages/build-tools/src/steps/utils/__tests__/remoteDeviceRunSession.test.ts @@ -38,14 +38,32 @@ function createEnvMock(): BuildStepEnv { } describe(createServeSimTunnelArgs, () => { - it('builds ngrok/H264 serve-sim args', () => { - expect(createServeSimTunnelArgs({ baseDomain: 'expo-simulator.ngrok.dev' })).toEqual([ + it('builds ngrok WebRTC/H264 serve-sim args and passes through TURN flags', () => { + expect( + createServeSimTunnelArgs({ + baseDomain: 'expo-simulator.ngrok.dev', + turnArgs: [ + '--stun-url', + 'stun:stun.cloudflare.com:3478', + '--turn-url', + 'turns:turn.cloudflare.com:443?transport=tcp', + '--turn-username', + 'u', + '--turn-credential', + 'c', + ], + }) + ).toEqual([ 'serve-sim-sjchmiela@latest', '--tunnel', '--tunnel-provider', 'ngrok', '--tunnel-domain', 'expo-simulator.ngrok.dev', + '--codec', + 'webrtc', + '--webrtc-codec', + 'h264', '--stream-max-dimension', '1280', '--stream-quality', @@ -56,6 +74,14 @@ describe(createServeSimTunnelArgs, () => { '3000000', '--h264-max-fps', '30', + '--stun-url', + 'stun:stun.cloudflare.com:3478', + '--turn-url', + 'turns:turn.cloudflare.com:443?transport=tcp', + '--turn-username', + 'u', + '--turn-credential', + 'c', ]); }); }); diff --git a/packages/build-tools/src/steps/utils/remoteDeviceRunSession.ts b/packages/build-tools/src/steps/utils/remoteDeviceRunSession.ts index 6bf40eac10..fbf65d4ab7 100644 --- a/packages/build-tools/src/steps/utils/remoteDeviceRunSession.ts +++ b/packages/build-tools/src/steps/utils/remoteDeviceRunSession.ts @@ -267,9 +267,10 @@ export async function startServeSimWithTunnelAsync( } ): Promise<{ previewUrl: string }> { logger.info('Launching serve-sim with tunnel.'); + const turnArgs = await fetchServeSimTurnArgsAsync(ctx, { env, logger }); const serveSim = spawnDetached({ command: 'npx', - args: createServeSimTunnelArgs({ baseDomain }), + args: createServeSimTunnelArgs({ baseDomain, turnArgs }), env, }); @@ -288,7 +289,13 @@ export async function startServeSimWithTunnelAsync( ); } -export function createServeSimTunnelArgs({ baseDomain }: { baseDomain: string }): string[] { +export function createServeSimTunnelArgs({ + baseDomain, + turnArgs = [], +}: { + baseDomain: string; + turnArgs?: string[]; +}): string[] { return [ SERVE_SIM_PACKAGE_SPEC, '--tunnel', @@ -296,6 +303,10 @@ export function createServeSimTunnelArgs({ baseDomain }: { baseDomain: string }) 'ngrok', '--tunnel-domain', baseDomain, + '--codec', + 'webrtc', + '--webrtc-codec', + 'h264', // MJPEG remains the browser fallback when AVCC/H.264 is unavailable. '--stream-max-dimension', SERVE_SIM_MJPEG_FALLBACK_MAX_DIMENSION, @@ -308,6 +319,7 @@ export function createServeSimTunnelArgs({ baseDomain }: { baseDomain: string }) SERVE_SIM_H264_BITRATE, '--h264-max-fps', SERVE_SIM_H264_MAX_FPS, + ...turnArgs, ]; } From 1fe93cbf66dc4c0e96b7cbf7240c78f9066d4764 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanis=C5=82aw=20Chmiela?= Date: Fri, 19 Jun 2026 15:20:53 +0200 Subject: [PATCH 5/5] Use AVCC serve-sim previews --- .../__tests__/remoteDeviceRunSession.test.ts | 30 ++----------------- .../src/steps/utils/remoteDeviceRunSession.ts | 18 ++--------- 2 files changed, 5 insertions(+), 43 deletions(-) diff --git a/packages/build-tools/src/steps/utils/__tests__/remoteDeviceRunSession.test.ts b/packages/build-tools/src/steps/utils/__tests__/remoteDeviceRunSession.test.ts index 4ae51db8a5..23aa6cf4a6 100644 --- a/packages/build-tools/src/steps/utils/__tests__/remoteDeviceRunSession.test.ts +++ b/packages/build-tools/src/steps/utils/__tests__/remoteDeviceRunSession.test.ts @@ -38,32 +38,14 @@ function createEnvMock(): BuildStepEnv { } describe(createServeSimTunnelArgs, () => { - it('builds ngrok WebRTC/H264 serve-sim args and passes through TURN flags', () => { - expect( - createServeSimTunnelArgs({ - baseDomain: 'expo-simulator.ngrok.dev', - turnArgs: [ - '--stun-url', - 'stun:stun.cloudflare.com:3478', - '--turn-url', - 'turns:turn.cloudflare.com:443?transport=tcp', - '--turn-username', - 'u', - '--turn-credential', - 'c', - ], - }) - ).toEqual([ + it('builds ngrok H264 serve-sim args without forcing WebRTC', () => { + expect(createServeSimTunnelArgs({ baseDomain: 'expo-simulator.ngrok.dev' })).toEqual([ 'serve-sim-sjchmiela@latest', '--tunnel', '--tunnel-provider', 'ngrok', '--tunnel-domain', 'expo-simulator.ngrok.dev', - '--codec', - 'webrtc', - '--webrtc-codec', - 'h264', '--stream-max-dimension', '1280', '--stream-quality', @@ -74,14 +56,6 @@ describe(createServeSimTunnelArgs, () => { '3000000', '--h264-max-fps', '30', - '--stun-url', - 'stun:stun.cloudflare.com:3478', - '--turn-url', - 'turns:turn.cloudflare.com:443?transport=tcp', - '--turn-username', - 'u', - '--turn-credential', - 'c', ]); }); }); diff --git a/packages/build-tools/src/steps/utils/remoteDeviceRunSession.ts b/packages/build-tools/src/steps/utils/remoteDeviceRunSession.ts index fbf65d4ab7..342d492798 100644 --- a/packages/build-tools/src/steps/utils/remoteDeviceRunSession.ts +++ b/packages/build-tools/src/steps/utils/remoteDeviceRunSession.ts @@ -253,7 +253,7 @@ export function spawnDetached({ } export async function startServeSimWithTunnelAsync( - ctx: CustomBuildContext, + _ctx: CustomBuildContext, { baseDomain, env, @@ -267,10 +267,9 @@ export async function startServeSimWithTunnelAsync( } ): Promise<{ previewUrl: string }> { logger.info('Launching serve-sim with tunnel.'); - const turnArgs = await fetchServeSimTurnArgsAsync(ctx, { env, logger }); const serveSim = spawnDetached({ command: 'npx', - args: createServeSimTunnelArgs({ baseDomain, turnArgs }), + args: createServeSimTunnelArgs({ baseDomain }), env, }); @@ -289,13 +288,7 @@ export async function startServeSimWithTunnelAsync( ); } -export function createServeSimTunnelArgs({ - baseDomain, - turnArgs = [], -}: { - baseDomain: string; - turnArgs?: string[]; -}): string[] { +export function createServeSimTunnelArgs({ baseDomain }: { baseDomain: string }): string[] { return [ SERVE_SIM_PACKAGE_SPEC, '--tunnel', @@ -303,10 +296,6 @@ export function createServeSimTunnelArgs({ 'ngrok', '--tunnel-domain', baseDomain, - '--codec', - 'webrtc', - '--webrtc-codec', - 'h264', // MJPEG remains the browser fallback when AVCC/H.264 is unavailable. '--stream-max-dimension', SERVE_SIM_MJPEG_FALLBACK_MAX_DIMENSION, @@ -319,7 +308,6 @@ export function createServeSimTunnelArgs({ SERVE_SIM_H264_BITRATE, '--h264-max-fps', SERVE_SIM_H264_MAX_FPS, - ...turnArgs, ]; }