diff --git a/src/daemon/handlers/session-open-prepare.ts b/src/daemon/handlers/session-open-prepare.ts index 8929800cb4..e53b85c216 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,18 +25,17 @@ type ResolveAndroidPackageForOpen = ( openTarget: string | undefined, ) => Promise; -type PreparedOpenCommand = { - device: DeviceInfo; - surface: SessionSurface; - openTarget?: string; - openPositionals: string[]; +type OpenCommandDetails = { appBundleId?: string; appName?: string; runtime: SessionRuntimeHints | undefined; - existingSession?: SessionState; }; -function invalidArgs(message: string): DaemonResponse { +export type PreparedOpenCommandDetailsResult = + | { type: 'response'; response: DaemonResponse } + | { type: 'details'; details: OpenCommandDetails }; + +export function invalidOpenArgs(message: string): DaemonResponse { return { ok: false, error: { @@ -48,7 +45,7 @@ function invalidArgs(message: string): DaemonResponse { }; } -function toSurfaceResponse( +export function resolveOpenSurfaceResponse( device: DeviceInfo, surfaceFlag: string | undefined, openTarget: string | undefined, @@ -72,7 +69,7 @@ function toSurfaceResponse( } } -function validatePreparedOpenRequest(params: { +export function validateResolvedOpenRequest(params: { shouldRelaunch: boolean; openTarget: string | undefined; surface: SessionSurface; @@ -81,22 +78,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; @@ -104,30 +101,29 @@ 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 prepareResolvedOpenCommand(params: { +export async function prepareOpenCommandDetails(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,22 +131,11 @@ async function prepareResolvedOpenCommand(params: { device, surface, openTarget, - openPositionals, ensureReady, resolveAndroidPackageForOpen, existingSession, clearRuntimeHints, } = params; - const validation = validatePreparedOpenRequest({ - shouldRelaunch: req.flags?.relaunch === true, - openTarget, - surface, - device, - }); - if (validation) { - return { response: validation }; - } - await ensureReady(device); const { appBundleId, appName } = await resolvePreparedOpenIdentity({ device, @@ -166,7 +151,10 @@ async function prepareResolvedOpenCommand(params: { device, }); if (!runtimeResult.ok) { - return { response: runtimeResult.response }; + return { + type: 'response', + response: runtimeResult.response, + }; } if (existingSession && clearRuntimeHints) { @@ -181,15 +169,11 @@ async function prepareResolvedOpenCommand(params: { } return { - prepared: { - device, - surface, - openTarget, - openPositionals, + type: 'details', + details: { appBundleId, appName, runtime: runtimeResult.data.runtime, - existingSession, }, }; } @@ -215,134 +199,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; -}): Promise<{ response: DaemonResponse } | { prepared: PreparedOpenCommand }> { - const { - req, - sessionName, - sessionStore, - session, - ensureReady, - resolveDevice, - clearRuntimeHints, - resolveAndroidPackageForOpen, - } = 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 { response: 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.'), - }; - } - - const validation = validatePreparedOpenRequest({ - shouldRelaunch, - openTarget, - surface: surfaceResult, - device: session.device, - }); - if (validation) { - return { response: validation }; - } - - const device = await refreshSessionDeviceIfNeeded(session.device, resolveDevice); - return await prepareResolvedOpenCommand({ - req, - sessionName, - sessionStore, - device, - surface: surfaceResult, - openTarget, - openPositionals: requestedOpenTarget ? (req.positionals ?? []) : openTarget ? [openTarget] : [], - ensureReady, - resolveAndroidPackageForOpen, - clearRuntimeHints, - existingSession: session, - }); -} - -export async function prepareNewOpenCommand(params: { - req: DaemonRequest; - sessionStore: SessionStore; - sessionName: string; - ensureReady: typeof ensureDeviceReady; - resolveDevice: typeof resolveTargetDevice; - resolveAndroidPackageForOpen: ResolveAndroidPackageForOpen; -}): Promise<{ response: DaemonResponse } | { prepared: PreparedOpenCommand }> { - const { - req, - sessionStore, - sessionName, - ensureReady, - resolveDevice, - resolveAndroidPackageForOpen, - } = params; - const shouldRelaunch = req.flags?.relaunch === true; - const openTarget = req.positionals?.[0]; - if (shouldRelaunch && !openTarget) { - return { response: invalidArgs('open --relaunch requires an app argument.') }; - } - - const preResolvedValidation = validatePreResolvedOpenRequest({ - shouldRelaunch, - openTarget, - platform: req.flags?.platform === 'android' ? 'android' : undefined, - }); - if (preResolvedValidation) { - return { response: preResolvedValidation }; - } - - const device = await resolveDevice(req.flags ?? {}); - const surfaceResult = toSurfaceResponse(device, req.flags?.surface, openTarget); - if (typeof surfaceResult !== 'string') { - return { response: 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 }, - }, - }, - }; - } - - return await prepareResolvedOpenCommand({ - req, - sessionName, - sessionStore, - device, - surface: surfaceResult, - openTarget, - openPositionals: req.positionals ?? [], - ensureReady, - resolveAndroidPackageForOpen, - }); -} diff --git a/src/daemon/handlers/session-open.ts b/src/daemon/handlers/session-open.ts index e41b510720..8254984856 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; @@ -233,67 +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, + clearRuntimeHints, + existingSession: session, }); - if ('response' in preparation) { - return preparation.response; + if (details.type === 'response') { + return details.response; } - const { prepared } = preparation; + return await completeOpenCommand({ req, sessionName, sessionStore, logPath, - device: prepared.device, + 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, + openTarget, + openPositionals: requestedOpenTarget + ? (req.positionals ?? []) + : openTarget + ? [openTarget] + : [], + appBundleId: details.details.appBundleId, + appName: details.details.appName, + runtime: details.details.runtime, + surface: surfaceResult, + existingSession: session, }); } - const preparation = 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, }); - if ('response' in preparation) { - return preparation.response; + if (details.type === 'response') { + return details.response; } - const { prepared } = preparation; + return await completeOpenCommand({ req, sessionName, sessionStore, logPath, - device: prepared.device, + device, dispatch, applyRuntimeHints, stopIosRunner, settleSimulator, - openTarget: prepared.openTarget, - openPositionals: prepared.openPositionals, - appName: prepared.appName, - surface: prepared.surface, - appBundleId: prepared.appBundleId, - runtime: prepared.runtime, + openTarget, + openPositionals: req.positionals ?? [], + appBundleId: details.details.appBundleId, + appName: details.details.appName, + runtime: details.details.runtime, + surface: surfaceResult, }); }