From 3dfdffb978db8c424923b0178e84240b6e861fb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Fri, 27 Mar 2026 13:35:37 +0100 Subject: [PATCH 1/3] refactor: simplify session open preparation flow --- src/daemon/handlers/session-open-prepare.ts | 105 ++++++++++++-------- src/daemon/handlers/session-open.ts | 70 +++++-------- 2 files changed, 89 insertions(+), 86 deletions(-) diff --git a/src/daemon/handlers/session-open-prepare.ts b/src/daemon/handlers/session-open-prepare.ts index 8929800cb4..aec82f32b3 100644 --- a/src/daemon/handlers/session-open-prepare.ts +++ b/src/daemon/handlers/session-open-prepare.ts @@ -27,7 +27,13 @@ type ResolveAndroidPackageForOpen = ( openTarget: string | undefined, ) => Promise; -type PreparedOpenCommand = { +type ResolvedOpenDetails = { + appBundleId?: string; + appName?: string; + runtime: SessionRuntimeHints | undefined; +}; + +type CompleteOpenCommand = (params: { device: DeviceInfo; surface: SessionSurface; openTarget?: string; @@ -36,7 +42,7 @@ type PreparedOpenCommand = { appName?: string; runtime: SessionRuntimeHints | undefined; existingSession?: SessionState; -}; +}) => Promise; function invalidArgs(message: string): DaemonResponse { return { @@ -112,22 +118,21 @@ function validatePreResolvedOpenRequest(params: { return null; } -async function prepareResolvedOpenCommand(params: { +async function resolveOpenCommandDetails(params: { req: DaemonRequest; sessionName: string; sessionStore: SessionStore; device: DeviceInfo; surface: SessionSurface; openTarget: string | undefined; - openPositionals: string[]; ensureReady: typeof ensureDeviceReady; resolveAndroidPackageForOpen: ( device: DeviceInfo, openTarget: string | undefined, ) => Promise; - existingSession?: SessionState; clearRuntimeHints?: typeof clearRuntimeHintsFromApp; -}): Promise<{ response: DaemonResponse } | { prepared: PreparedOpenCommand }> { + existingSession?: SessionState; +}): Promise { const { req, sessionName, @@ -135,7 +140,6 @@ async function prepareResolvedOpenCommand(params: { device, surface, openTarget, - openPositionals, ensureReady, resolveAndroidPackageForOpen, existingSession, @@ -148,7 +152,7 @@ async function prepareResolvedOpenCommand(params: { device, }); if (validation) { - return { response: validation }; + return validation; } await ensureReady(device); @@ -166,7 +170,7 @@ async function prepareResolvedOpenCommand(params: { device, }); if (!runtimeResult.ok) { - return { response: runtimeResult.response }; + return runtimeResult.response; } if (existingSession && clearRuntimeHints) { @@ -181,16 +185,9 @@ async function prepareResolvedOpenCommand(params: { } return { - prepared: { - device, - surface, - openTarget, - openPositionals, - appBundleId, - appName, - runtime: runtimeResult.data.runtime, - existingSession, - }, + appBundleId, + appName, + runtime: runtimeResult.data.runtime, }; } @@ -225,7 +222,8 @@ export async function prepareExistingOpenCommand(params: { resolveDevice: typeof resolveTargetDevice; clearRuntimeHints: typeof clearRuntimeHintsFromApp; resolveAndroidPackageForOpen: ResolveAndroidPackageForOpen; -}): Promise<{ response: DaemonResponse } | { prepared: PreparedOpenCommand }> { + completeOpen: CompleteOpenCommand; +}): Promise { const { req, sessionName, @@ -235,6 +233,7 @@ export async function prepareExistingOpenCommand(params: { resolveDevice, clearRuntimeHints, resolveAndroidPackageForOpen, + completeOpen, } = params; const shouldRelaunch = req.flags?.relaunch === true; const requestedOpenTarget = req.positionals?.[0]; @@ -246,15 +245,13 @@ export async function prepareExistingOpenCommand(params: { session.surface, ); if (typeof surfaceResult !== 'string') { - return { response: surfaceResult }; + return surfaceResult; } if (!openTarget && surfaceResult === 'app') { - return { - response: shouldRelaunch - ? invalidArgs('open --relaunch requires an app name or an active session app.') - : invalidArgs('Session already active. Close it first or pass a new --session name.'), - }; + return shouldRelaunch + ? invalidArgs('open --relaunch requires an app name or an active session app.') + : invalidArgs('Session already active. Close it first or pass a new --session name.'); } const validation = validatePreparedOpenRequest({ @@ -264,23 +261,35 @@ export async function prepareExistingOpenCommand(params: { device: session.device, }); if (validation) { - return { response: validation }; + return validation; } const device = await refreshSessionDeviceIfNeeded(session.device, resolveDevice); - return await prepareResolvedOpenCommand({ + const details = await resolveOpenCommandDetails({ req, sessionName, sessionStore, device, surface: surfaceResult, openTarget, - openPositionals: requestedOpenTarget ? (req.positionals ?? []) : openTarget ? [openTarget] : [], ensureReady, resolveAndroidPackageForOpen, clearRuntimeHints, existingSession: session, }); + if ('ok' in details) { + return details; + } + return await completeOpen({ + device, + surface: surfaceResult, + openTarget, + openPositionals: requestedOpenTarget ? (req.positionals ?? []) : openTarget ? [openTarget] : [], + appBundleId: details.appBundleId, + appName: details.appName, + runtime: details.runtime, + existingSession: session, + }); } export async function prepareNewOpenCommand(params: { @@ -290,7 +299,8 @@ export async function prepareNewOpenCommand(params: { ensureReady: typeof ensureDeviceReady; resolveDevice: typeof resolveTargetDevice; resolveAndroidPackageForOpen: ResolveAndroidPackageForOpen; -}): Promise<{ response: DaemonResponse } | { prepared: PreparedOpenCommand }> { + completeOpen: CompleteOpenCommand; +}): Promise { const { req, sessionStore, @@ -298,11 +308,12 @@ export async function prepareNewOpenCommand(params: { ensureReady, resolveDevice, resolveAndroidPackageForOpen, + completeOpen, } = params; const shouldRelaunch = req.flags?.relaunch === true; const openTarget = req.positionals?.[0]; if (shouldRelaunch && !openTarget) { - return { response: invalidArgs('open --relaunch requires an app argument.') }; + return invalidArgs('open --relaunch requires an app argument.'); } const preResolvedValidation = validatePreResolvedOpenRequest({ @@ -311,38 +322,48 @@ export async function prepareNewOpenCommand(params: { platform: req.flags?.platform === 'android' ? 'android' : undefined, }); if (preResolvedValidation) { - return { response: preResolvedValidation }; + return preResolvedValidation; } const device = await resolveDevice(req.flags ?? {}); const surfaceResult = toSurfaceResponse(device, req.flags?.surface, openTarget); if (typeof surfaceResult !== 'string') { - return { response: surfaceResult }; + return surfaceResult; } const inUse = sessionStore.toArray().find((session) => session.device.id === device.id); if (inUse) { return { - response: { - ok: false, - error: { - code: 'DEVICE_IN_USE', - message: `Device is already in use by session "${inUse.name}".`, - details: { session: inUse.name, deviceId: device.id, deviceName: device.name }, - }, + ok: false, + error: { + code: 'DEVICE_IN_USE', + message: `Device is already in use by session "${inUse.name}".`, + details: { session: inUse.name, deviceId: device.id, deviceName: device.name }, }, }; } - return await prepareResolvedOpenCommand({ + const details = await resolveOpenCommandDetails({ req, sessionName, sessionStore, device, surface: surfaceResult, openTarget, - openPositionals: req.positionals ?? [], ensureReady, resolveAndroidPackageForOpen, }); + if ('ok' in details) { + return details; + } + + return await completeOpen({ + device, + surface: surfaceResult, + openTarget, + openPositionals: req.positionals ?? [], + appBundleId: details.appBundleId, + appName: details.appName, + runtime: details.runtime, + }); } diff --git a/src/daemon/handlers/session-open.ts b/src/daemon/handlers/session-open.ts index e41b510720..682e757c83 100644 --- a/src/daemon/handlers/session-open.ts +++ b/src/daemon/handlers/session-open.ts @@ -222,6 +222,28 @@ export async function handleOpenCommand(params: { resolveAndroidPackageForOpen: resolveAndroidPackageForOpenFn = resolveAndroidPackageForOpen, } = params; + const completeOpen = (openParams: { + device: DeviceInfo; + surface: SessionSurface; + openTarget?: string; + openPositionals: string[]; + appBundleId?: string; + appName?: string; + runtime: SessionRuntimeHints | undefined; + existingSession?: SessionState; + }) => + completeOpenCommand({ + req, + sessionName, + sessionStore, + logPath, + dispatch, + applyRuntimeHints, + stopIosRunner, + settleSimulator, + ...openParams, + }); + if (sessionStore.has(sessionName)) { const session = sessionStore.get(sessionName); if (!session) { @@ -242,58 +264,18 @@ export async function handleOpenCommand(params: { resolveDevice, clearRuntimeHints, resolveAndroidPackageForOpen: resolveAndroidPackageForOpenFn, + completeOpen, }); - if ('response' in preparation) { - return preparation.response; - } - const { prepared } = preparation; - return await completeOpenCommand({ - req, - sessionName, - sessionStore, - logPath, - device: prepared.device, - dispatch, - applyRuntimeHints, - stopIosRunner, - settleSimulator, - openTarget: prepared.openTarget, - openPositionals: prepared.openPositionals, - appName: prepared.appName, - surface: prepared.surface, - appBundleId: prepared.appBundleId, - runtime: prepared.runtime, - existingSession: prepared.existingSession, - }); + return preparation; } - const preparation = await prepareNewOpenCommand({ + return await prepareNewOpenCommand({ req, sessionStore, sessionName, ensureReady, resolveDevice, resolveAndroidPackageForOpen: resolveAndroidPackageForOpenFn, - }); - if ('response' in preparation) { - return preparation.response; - } - const { prepared } = preparation; - return await completeOpenCommand({ - req, - sessionName, - sessionStore, - logPath, - device: prepared.device, - dispatch, - applyRuntimeHints, - stopIosRunner, - settleSimulator, - openTarget: prepared.openTarget, - openPositionals: prepared.openPositionals, - appName: prepared.appName, - surface: prepared.surface, - appBundleId: prepared.appBundleId, - runtime: prepared.runtime, + completeOpen, }); } From 115ce11111694c1a0c447115e0019cb35cf65ca5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Fri, 27 Mar 2026 13:43:20 +0100 Subject: [PATCH 2/3] refactor: flatten session open flow --- src/daemon/handlers/session-open-prepare.ts | 202 ++------------------ src/daemon/handlers/session-open.ts | 173 +++++++++++++---- 2 files changed, 152 insertions(+), 223 deletions(-) diff --git a/src/daemon/handlers/session-open-prepare.ts b/src/daemon/handlers/session-open-prepare.ts index aec82f32b3..a717fc7ee5 100644 --- a/src/daemon/handlers/session-open-prepare.ts +++ b/src/daemon/handlers/session-open-prepare.ts @@ -1,4 +1,3 @@ -import { resolveTargetDevice } from '../../core/dispatch.ts'; import { isDeepLinkTarget } from '../../core/open-target.ts'; import { ensureDeviceReady } from '../device-ready.ts'; import type { DeviceInfo } from '../../utils/device.ts'; @@ -8,7 +7,6 @@ import { classifyAndroidAppTarget, formatAndroidInstalledPackageRequiredMessage, } from '../../platforms/android/open-target.ts'; -import { refreshSessionDeviceIfNeeded } from './session-device-utils.ts'; import { maybeClearRemovedRuntimeTransportHints, tryResolveOpenRuntimeHints, @@ -27,24 +25,13 @@ type ResolveAndroidPackageForOpen = ( openTarget: string | undefined, ) => Promise; -type ResolvedOpenDetails = { +type OpenCommandDetails = { appBundleId?: string; appName?: string; runtime: SessionRuntimeHints | undefined; }; -type CompleteOpenCommand = (params: { - device: DeviceInfo; - surface: SessionSurface; - openTarget?: string; - openPositionals: string[]; - appBundleId?: string; - appName?: string; - runtime: SessionRuntimeHints | undefined; - existingSession?: SessionState; -}) => Promise; - -function invalidArgs(message: string): DaemonResponse { +export function invalidOpenArgs(message: string): DaemonResponse { return { ok: false, error: { @@ -54,7 +41,7 @@ function invalidArgs(message: string): DaemonResponse { }; } -function toSurfaceResponse( +export function resolveOpenSurfaceResponse( device: DeviceInfo, surfaceFlag: string | undefined, openTarget: string | undefined, @@ -78,7 +65,7 @@ function toSurfaceResponse( } } -function validatePreparedOpenRequest(params: { +export function validateResolvedOpenRequest(params: { shouldRelaunch: boolean; openTarget: string | undefined; surface: SessionSurface; @@ -87,22 +74,22 @@ function validatePreparedOpenRequest(params: { const { shouldRelaunch, openTarget, surface, device } = params; if (!shouldRelaunch) return null; if (openTarget && isDeepLinkTarget(openTarget)) { - return invalidArgs('open --relaunch does not support URL targets.'); + return invalidOpenArgs('open --relaunch does not support URL targets.'); } if (surface !== 'app') { - return invalidArgs('open --relaunch is supported only for app surfaces.'); + return invalidOpenArgs('open --relaunch is supported only for app surfaces.'); } if ( device.platform === 'android' && openTarget && classifyAndroidAppTarget(openTarget) === 'binary' ) { - return invalidArgs(formatAndroidInstalledPackageRequiredMessage(openTarget)); + return invalidOpenArgs(formatAndroidInstalledPackageRequiredMessage(openTarget)); } return null; } -function validatePreResolvedOpenRequest(params: { +export function validatePreResolvedOpenRequest(params: { shouldRelaunch: boolean; openTarget: string | undefined; platform: DeviceInfo['platform'] | undefined; @@ -110,15 +97,15 @@ function validatePreResolvedOpenRequest(params: { const { shouldRelaunch, openTarget, platform } = params; if (!shouldRelaunch) return null; if (openTarget && isDeepLinkTarget(openTarget)) { - return invalidArgs('open --relaunch does not support URL targets.'); + return invalidOpenArgs('open --relaunch does not support URL targets.'); } if (platform === 'android' && openTarget && classifyAndroidAppTarget(openTarget) === 'binary') { - return invalidArgs(formatAndroidInstalledPackageRequiredMessage(openTarget)); + return invalidOpenArgs(formatAndroidInstalledPackageRequiredMessage(openTarget)); } return null; } -async function resolveOpenCommandDetails(params: { +export async function prepareOpenCommandDetails(params: { req: DaemonRequest; sessionName: string; sessionStore: SessionStore; @@ -132,7 +119,7 @@ async function resolveOpenCommandDetails(params: { ) => Promise; clearRuntimeHints?: typeof clearRuntimeHintsFromApp; existingSession?: SessionState; -}): Promise { +}): Promise { const { req, sessionName, @@ -145,16 +132,6 @@ async function resolveOpenCommandDetails(params: { existingSession, clearRuntimeHints, } = params; - const validation = validatePreparedOpenRequest({ - shouldRelaunch: req.flags?.relaunch === true, - openTarget, - surface, - device, - }); - if (validation) { - return validation; - } - await ensureReady(device); const { appBundleId, appName } = await resolvePreparedOpenIdentity({ device, @@ -212,158 +189,3 @@ async function resolvePreparedOpenIdentity(params: { appName: macOsSurfaceState.appName ?? openTarget, }; } - -export async function prepareExistingOpenCommand(params: { - req: DaemonRequest; - sessionName: string; - sessionStore: SessionStore; - session: SessionState; - ensureReady: typeof ensureDeviceReady; - resolveDevice: typeof resolveTargetDevice; - clearRuntimeHints: typeof clearRuntimeHintsFromApp; - resolveAndroidPackageForOpen: ResolveAndroidPackageForOpen; - completeOpen: CompleteOpenCommand; -}): Promise { - const { - req, - sessionName, - sessionStore, - session, - ensureReady, - resolveDevice, - clearRuntimeHints, - resolveAndroidPackageForOpen, - completeOpen, - } = params; - const shouldRelaunch = req.flags?.relaunch === true; - const requestedOpenTarget = req.positionals?.[0]; - const openTarget = requestedOpenTarget ?? (shouldRelaunch ? session.appName : undefined); - const surfaceResult = toSurfaceResponse( - session.device, - req.flags?.surface, - openTarget, - session.surface, - ); - if (typeof surfaceResult !== 'string') { - return surfaceResult; - } - - if (!openTarget && surfaceResult === 'app') { - return shouldRelaunch - ? invalidArgs('open --relaunch requires an app name or an active session app.') - : invalidArgs('Session already active. Close it first or pass a new --session name.'); - } - - const validation = validatePreparedOpenRequest({ - shouldRelaunch, - openTarget, - surface: surfaceResult, - device: session.device, - }); - if (validation) { - return validation; - } - - const device = await refreshSessionDeviceIfNeeded(session.device, resolveDevice); - const details = await resolveOpenCommandDetails({ - req, - sessionName, - sessionStore, - device, - surface: surfaceResult, - openTarget, - ensureReady, - resolveAndroidPackageForOpen, - clearRuntimeHints, - existingSession: session, - }); - if ('ok' in details) { - return details; - } - return await completeOpen({ - device, - surface: surfaceResult, - openTarget, - openPositionals: requestedOpenTarget ? (req.positionals ?? []) : openTarget ? [openTarget] : [], - appBundleId: details.appBundleId, - appName: details.appName, - runtime: details.runtime, - existingSession: session, - }); -} - -export async function prepareNewOpenCommand(params: { - req: DaemonRequest; - sessionStore: SessionStore; - sessionName: string; - ensureReady: typeof ensureDeviceReady; - resolveDevice: typeof resolveTargetDevice; - resolveAndroidPackageForOpen: ResolveAndroidPackageForOpen; - completeOpen: CompleteOpenCommand; -}): Promise { - const { - req, - sessionStore, - sessionName, - ensureReady, - resolveDevice, - resolveAndroidPackageForOpen, - completeOpen, - } = params; - const shouldRelaunch = req.flags?.relaunch === true; - const openTarget = req.positionals?.[0]; - if (shouldRelaunch && !openTarget) { - return invalidArgs('open --relaunch requires an app argument.'); - } - - const preResolvedValidation = validatePreResolvedOpenRequest({ - shouldRelaunch, - openTarget, - platform: req.flags?.platform === 'android' ? 'android' : undefined, - }); - if (preResolvedValidation) { - return preResolvedValidation; - } - - const device = await resolveDevice(req.flags ?? {}); - const surfaceResult = toSurfaceResponse(device, req.flags?.surface, openTarget); - if (typeof surfaceResult !== 'string') { - return surfaceResult; - } - - const inUse = sessionStore.toArray().find((session) => session.device.id === device.id); - if (inUse) { - return { - ok: false, - error: { - code: 'DEVICE_IN_USE', - message: `Device is already in use by session "${inUse.name}".`, - details: { session: inUse.name, deviceId: device.id, deviceName: device.name }, - }, - }; - } - - const details = await resolveOpenCommandDetails({ - req, - sessionName, - sessionStore, - device, - surface: surfaceResult, - openTarget, - ensureReady, - resolveAndroidPackageForOpen, - }); - if ('ok' in details) { - return details; - } - - return await completeOpen({ - device, - surface: surfaceResult, - openTarget, - openPositionals: req.positionals ?? [], - appBundleId: details.appBundleId, - appName: details.appName, - runtime: details.runtime, - }); -} diff --git a/src/daemon/handlers/session-open.ts b/src/daemon/handlers/session-open.ts index 682e757c83..be67511154 100644 --- a/src/daemon/handlers/session-open.ts +++ b/src/daemon/handlers/session-open.ts @@ -11,13 +11,20 @@ import { SessionStore } from '../session-store.ts'; import { IOS_SIMULATOR_POST_CLOSE_SETTLE_MS, IOS_SIMULATOR_POST_OPEN_SETTLE_MS, + refreshSessionDeviceIfNeeded, settleIosSimulator, } from './session-device-utils.ts'; import { countConfiguredRuntimeHints, setSessionRuntimeHintsForOpen } from './session-runtime.ts'; import { resolveAndroidPackageForOpen } from './session-open-target.ts'; import { STARTUP_SAMPLE_METHOD, type StartupPerfSample } from './session-startup-metrics.ts'; import { buildNextOpenSession, buildOpenResult } from './session-open-surface.ts'; -import { prepareExistingOpenCommand, prepareNewOpenCommand } from './session-open-prepare.ts'; +import { + invalidOpenArgs, + prepareOpenCommandDetails, + resolveOpenSurfaceResponse, + validatePreResolvedOpenRequest, + validateResolvedOpenRequest, +} from './session-open-prepare.ts'; async function relaunchCloseApp(params: { device: DeviceInfo; @@ -222,28 +229,6 @@ export async function handleOpenCommand(params: { resolveAndroidPackageForOpen: resolveAndroidPackageForOpenFn = resolveAndroidPackageForOpen, } = params; - const completeOpen = (openParams: { - device: DeviceInfo; - surface: SessionSurface; - openTarget?: string; - openPositionals: string[]; - appBundleId?: string; - appName?: string; - runtime: SessionRuntimeHints | undefined; - existingSession?: SessionState; - }) => - completeOpenCommand({ - req, - sessionName, - sessionStore, - logPath, - dispatch, - applyRuntimeHints, - stopIosRunner, - settleSimulator, - ...openParams, - }); - if (sessionStore.has(sessionName)) { const session = sessionStore.get(sessionName); if (!session) { @@ -255,27 +240,149 @@ export async function handleOpenCommand(params: { }, }; } - const preparation = await prepareExistingOpenCommand({ + const shouldRelaunch = req.flags?.relaunch === true; + const requestedOpenTarget = req.positionals?.[0]; + const openTarget = requestedOpenTarget ?? (shouldRelaunch ? session.appName : undefined); + const surfaceResult = resolveOpenSurfaceResponse( + session.device, + req.flags?.surface, + openTarget, + session.surface, + ); + if (typeof surfaceResult !== 'string') { + return surfaceResult; + } + if (!openTarget && surfaceResult === 'app') { + return shouldRelaunch + ? invalidOpenArgs('open --relaunch requires an app name or an active session app.') + : invalidOpenArgs('Session already active. Close it first or pass a new --session name.'); + } + + const validation = validateResolvedOpenRequest({ + shouldRelaunch, + openTarget, + surface: surfaceResult, + device: session.device, + }); + if (validation) { + return validation; + } + + const device = await refreshSessionDeviceIfNeeded(session.device, resolveDevice); + const details = await prepareOpenCommandDetails({ req, sessionName, sessionStore, - session, + device, + surface: surfaceResult, + openTarget, ensureReady, - resolveDevice, - clearRuntimeHints, resolveAndroidPackageForOpen: resolveAndroidPackageForOpenFn, - completeOpen, + clearRuntimeHints, + existingSession: session, + }); + if ('ok' in details) { + return details; + } + + return await completeOpenCommand({ + req, + sessionName, + sessionStore, + logPath, + device, + dispatch, + applyRuntimeHints, + stopIosRunner, + settleSimulator, + openTarget, + openPositionals: requestedOpenTarget + ? (req.positionals ?? []) + : openTarget + ? [openTarget] + : [], + appBundleId: details.appBundleId, + appName: details.appName, + runtime: details.runtime, + surface: surfaceResult, + existingSession: session, }); - return preparation; } - return await prepareNewOpenCommand({ + const shouldRelaunch = req.flags?.relaunch === true; + const openTarget = req.positionals?.[0]; + if (shouldRelaunch && !openTarget) { + return invalidOpenArgs('open --relaunch requires an app argument.'); + } + + const preResolvedValidation = validatePreResolvedOpenRequest({ + shouldRelaunch, + openTarget, + platform: req.flags?.platform === 'android' ? 'android' : undefined, + }); + if (preResolvedValidation) { + return preResolvedValidation; + } + + const device = await resolveDevice(req.flags ?? {}); + const surfaceResult = resolveOpenSurfaceResponse(device, req.flags?.surface, openTarget); + if (typeof surfaceResult !== 'string') { + return surfaceResult; + } + + const validation = validateResolvedOpenRequest({ + shouldRelaunch, + openTarget, + surface: surfaceResult, + device, + }); + if (validation) { + return validation; + } + + const inUse = sessionStore + .toArray() + .find((activeSession) => activeSession.device.id === device.id); + if (inUse) { + return { + ok: false, + error: { + code: 'DEVICE_IN_USE', + message: `Device is already in use by session "${inUse.name}".`, + details: { session: inUse.name, deviceId: device.id, deviceName: device.name }, + }, + }; + } + + const details = await prepareOpenCommandDetails({ req, - sessionStore, sessionName, + sessionStore, + device, + surface: surfaceResult, + openTarget, ensureReady, - resolveDevice, resolveAndroidPackageForOpen: resolveAndroidPackageForOpenFn, - completeOpen, + }); + if ('ok' in details) { + return details; + } + + return await completeOpenCommand({ + req, + sessionName, + sessionStore, + logPath, + device, + dispatch, + applyRuntimeHints, + stopIosRunner, + settleSimulator, + openTarget, + openPositionals: req.positionals ?? [], + appBundleId: details.appBundleId, + appName: details.appName, + runtime: details.runtime, + surface: surfaceResult, }); } From 4663dc1c143fae6701ecc7895293ee42b4c49d96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Fri, 27 Mar 2026 13:45:50 +0100 Subject: [PATCH 3/3] refactor: use explicit open details result --- src/daemon/handlers/session-open-prepare.ts | 20 +++++++++++++++----- src/daemon/handlers/session-open.ts | 20 ++++++++++---------- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/src/daemon/handlers/session-open-prepare.ts b/src/daemon/handlers/session-open-prepare.ts index a717fc7ee5..e53b85c216 100644 --- a/src/daemon/handlers/session-open-prepare.ts +++ b/src/daemon/handlers/session-open-prepare.ts @@ -31,6 +31,10 @@ type OpenCommandDetails = { runtime: SessionRuntimeHints | undefined; }; +export type PreparedOpenCommandDetailsResult = + | { type: 'response'; response: DaemonResponse } + | { type: 'details'; details: OpenCommandDetails }; + export function invalidOpenArgs(message: string): DaemonResponse { return { ok: false, @@ -119,7 +123,7 @@ export async function prepareOpenCommandDetails(params: { ) => Promise; clearRuntimeHints?: typeof clearRuntimeHintsFromApp; existingSession?: SessionState; -}): Promise { +}): Promise { const { req, sessionName, @@ -147,7 +151,10 @@ export async function prepareOpenCommandDetails(params: { device, }); if (!runtimeResult.ok) { - return runtimeResult.response; + return { + type: 'response', + response: runtimeResult.response, + }; } if (existingSession && clearRuntimeHints) { @@ -162,9 +169,12 @@ export async function prepareOpenCommandDetails(params: { } return { - appBundleId, - appName, - runtime: runtimeResult.data.runtime, + type: 'details', + details: { + appBundleId, + appName, + runtime: runtimeResult.data.runtime, + }, }; } diff --git a/src/daemon/handlers/session-open.ts b/src/daemon/handlers/session-open.ts index be67511154..8254984856 100644 --- a/src/daemon/handlers/session-open.ts +++ b/src/daemon/handlers/session-open.ts @@ -281,8 +281,8 @@ export async function handleOpenCommand(params: { clearRuntimeHints, existingSession: session, }); - if ('ok' in details) { - return details; + if (details.type === 'response') { + return details.response; } return await completeOpenCommand({ @@ -301,9 +301,9 @@ export async function handleOpenCommand(params: { : openTarget ? [openTarget] : [], - appBundleId: details.appBundleId, - appName: details.appName, - runtime: details.runtime, + appBundleId: details.details.appBundleId, + appName: details.details.appName, + runtime: details.details.runtime, surface: surfaceResult, existingSession: session, }); @@ -364,8 +364,8 @@ export async function handleOpenCommand(params: { ensureReady, resolveAndroidPackageForOpen: resolveAndroidPackageForOpenFn, }); - if ('ok' in details) { - return details; + if (details.type === 'response') { + return details.response; } return await completeOpenCommand({ @@ -380,9 +380,9 @@ export async function handleOpenCommand(params: { settleSimulator, openTarget, openPositionals: req.positionals ?? [], - appBundleId: details.appBundleId, - appName: details.appName, - runtime: details.runtime, + appBundleId: details.details.appBundleId, + appName: details.details.appName, + runtime: details.details.runtime, surface: surfaceResult, }); }