Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
193 changes: 23 additions & 170 deletions src/daemon/handlers/session-open-prepare.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -8,7 +7,6 @@ import {
classifyAndroidAppTarget,
formatAndroidInstalledPackageRequiredMessage,
} from '../../platforms/android/open-target.ts';
import { refreshSessionDeviceIfNeeded } from './session-device-utils.ts';
import {
maybeClearRemovedRuntimeTransportHints,
tryResolveOpenRuntimeHints,
Expand All @@ -27,18 +25,17 @@ type ResolveAndroidPackageForOpen = (
openTarget: string | undefined,
) => Promise<string | undefined>;

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: {
Expand All @@ -48,7 +45,7 @@ function invalidArgs(message: string): DaemonResponse {
};
}

function toSurfaceResponse(
export function resolveOpenSurfaceResponse(
device: DeviceInfo,
surfaceFlag: string | undefined,
openTarget: string | undefined,
Expand All @@ -72,7 +69,7 @@ function toSurfaceResponse(
}
}

function validatePreparedOpenRequest(params: {
export function validateResolvedOpenRequest(params: {
shouldRelaunch: boolean;
openTarget: string | undefined;
surface: SessionSurface;
Expand All @@ -81,76 +78,64 @@ 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;
}): DaemonResponse | null {
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<string | undefined>;
existingSession?: SessionState;
clearRuntimeHints?: typeof clearRuntimeHintsFromApp;
}): Promise<{ response: DaemonResponse } | { prepared: PreparedOpenCommand }> {
existingSession?: SessionState;
}): Promise<PreparedOpenCommandDetailsResult> {
const {
req,
sessionName,
sessionStore,
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,
Expand All @@ -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) {
Expand All @@ -181,15 +169,11 @@ async function prepareResolvedOpenCommand(params: {
}

return {
prepared: {
device,
surface,
openTarget,
openPositionals,
type: 'details',
details: {
appBundleId,
appName,
runtime: runtimeResult.data.runtime,
existingSession,
},
};
}
Expand All @@ -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,
});
}
Loading
Loading