Skip to content

Commit 265259c

Browse files
committed
refactor: generalize deferred provider app selection
1 parent 24bdcd0 commit 265259c

8 files changed

Lines changed: 44 additions & 20 deletions

File tree

packages/contracts/src/application-lifecycle-interaction.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ test('direct lifecycle owners preserve the daemon runtime launch URL follow-up',
9191
expect(calls[1]?.options).toHaveProperty('launchArgs', undefined);
9292
});
9393

94-
test('direct lifecycle owners resolve app aliases once before target dispatch', async () => {
94+
test('direct lifecycle owners resolve provider app references before target dispatch', async () => {
9595
const open = vi.fn(async () => undefined);
9696
const binding = bindLocalApplicationLifecycleInteractor({
9797
device: WEB_DEVICE,
@@ -102,7 +102,7 @@ test('direct lifecycle owners resolve app aliases once before target dispatch',
102102
binding,
103103
owner: 'Provider',
104104
openTargetIdentity: 'bundle-id',
105-
resolveAppAlias: (app) => (app === 'Example.app.zip' ? 'com.example.app' : app),
105+
resolveAppReference: (app) => (app === 'Example.app.zip' ? 'com.example.app' : app),
106106
});
107107

108108
await expect(

packages/contracts/src/application-lifecycle-interaction.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ export type DirectApplicationLifecycleParams = Readonly<{
262262
openTargetIdentity: DirectOpenTargetIdentity;
263263
/** Owners whose native open does not replace a running application close it first. */
264264
closeBeforeRelaunch?: boolean;
265-
resolveAppAlias?(app: string): string;
265+
resolveAppReference?(app: string): string;
266266
/** Port reverse is the one non-direct operation a provider owner may still implement. */
267267
configureProviderPortReverse?: ApplicationLifecycleRuntimeOperations['configureProviderPortReverse'];
268268
}>;
@@ -284,7 +284,7 @@ export function bindDirectApplicationLifecycle(
284284
};
285285
return Object.freeze({
286286
resolveOpenTarget: async (input) =>
287-
resolveDirectOpenTarget(params.openTargetIdentity, resolveOpenTargetAlias(params, input)),
287+
resolveDirectOpenTarget(params.openTargetIdentity, resolveOpenTargetReference(params, input)),
288288
prepareApplicationOpen: async () => undefined,
289289
openApplication: async (input) => await openDirectApplication(params, input),
290290
applyRuntimeHints: unavailable,
@@ -306,7 +306,7 @@ async function openDirectApplication(
306306
params: DirectApplicationLifecycleParams,
307307
input: OpenApplicationInput,
308308
): Promise<OpenApplicationOutcome> {
309-
const resolvedInput = resolveOpenApplicationAliases(params, input);
309+
const resolvedInput = resolveOpenApplicationReferences(params, input);
310310
const { binding } = params;
311311
const interactor = await binding.resolveInteractor(
312312
resolvedInput.execution,
@@ -344,19 +344,19 @@ async function openDirectApplication(
344344
return { appBundleId: resolvedInput.appBundleId, timing: {} };
345345
}
346346

347-
function resolveOpenTargetAlias(
347+
function resolveOpenTargetReference(
348348
params: DirectApplicationLifecycleParams,
349349
input: OpenTargetResolutionInput,
350350
): OpenTargetResolutionInput {
351-
if (!input.target || !params.resolveAppAlias) return input;
352-
return { ...input, target: params.resolveAppAlias(input.target) };
351+
if (!input.target || !params.resolveAppReference) return input;
352+
return { ...input, target: params.resolveAppReference(input.target) };
353353
}
354354

355-
function resolveOpenApplicationAliases(
355+
function resolveOpenApplicationReferences(
356356
params: DirectApplicationLifecycleParams,
357357
input: OpenApplicationInput,
358358
): OpenApplicationInput {
359-
const resolve = params.resolveAppAlias;
359+
const resolve = params.resolveAppReference;
360360
if (!resolve) return input;
361361
return {
362362
...input,

packages/provider-limrun/src/app-log-runtime.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ export type LimrunPlatformRuntimeOwnerOptions = Omit<
5959
runtimeInstance: string;
6060
ownsDevice(device: DeviceInfo): boolean;
6161
getInteractor(device: DeviceInfo, runner?: RunnerContext): Interactor | undefined;
62-
resolveAppAlias?(device: DeviceInfo, app: string): string;
62+
resolveAppReference?(device: DeviceInfo, app: string): string;
6363
openCurrent(device: DeviceInfo): Promise<LimrunAppLogReader | undefined>;
6464
hasLiveSession(device: DeviceInfo): boolean;
6565
reconnect(
@@ -281,7 +281,7 @@ function bindLimrunAppLogs(
281281
device,
282282
signal,
283283
getInteractor: options.getInteractor,
284-
resolveAppAlias: (app) => options.resolveAppAlias?.(device, app) ?? app,
284+
resolveAppReference: (app) => options.resolveAppReference?.(device, app) ?? app,
285285
configurePortReverse: options.configurePortReverse,
286286
}),
287287
runtimeFacts.operations,

packages/provider-limrun/src/lifecycle.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ type LimrunLifecycleParams = Readonly<{
1414
configurePortReverse(
1515
options: ProviderPortReverseOptions,
1616
): Promise<Record<string, unknown> | undefined>;
17-
resolveAppAlias?(app: string): string;
17+
resolveAppReference?(app: string): string;
1818
}>;
1919

2020
/** Limrun owns its live-session lifecycle, relaunch, and exact port-reverse mechanics. */
@@ -25,7 +25,7 @@ export function bindLimrunApplicationLifecycle(
2525
owner: 'Limrun',
2626
openTargetIdentity: 'bundle-id',
2727
closeBeforeRelaunch: true,
28-
resolveAppAlias: params.resolveAppAlias,
28+
resolveAppReference: params.resolveAppReference,
2929
configureProviderPortReverse: async (input) => await params.configurePortReverse(input),
3030
binding: bindProviderApplicationLifecycleInteractor({
3131
device: params.device,

packages/provider-limrun/src/runtime.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ class LimrunRuntimeImplementation implements ProviderDeviceRuntime {
331331
return session?.platform === parsed.platform ? session : undefined;
332332
}
333333

334-
resolveAppAlias(device: DeviceInfo, app: string): string {
334+
resolveAppReference(device: DeviceInfo, app: string): string {
335335
const parsed = parseLimrunDeviceId(device.id);
336336
if (!parsed) return app;
337337
const alias = this.appAliases.get(parsed.leaseId);
@@ -386,7 +386,7 @@ async function loadLimrunPlatformRuntime(
386386
ownsDevice: (device) => runtime.ownsDevice(device),
387387
hasLiveSession: (device) => runtime.hasLiveSession(device),
388388
getInteractor: (device, runner) => runtime.getInteractor(device, runner),
389-
resolveAppAlias: (device, app) => runtime.resolveAppAlias(device, app),
389+
resolveAppReference: (device, app) => runtime.resolveAppReference(device, app),
390390
openCurrent: async (device) => runtime.currentAppLogReader(device),
391391
reconnect: async (descriptor, signal) =>
392392
await runtime.reconnectAppLogReader(descriptor, signal),

src/cli/commands/connection-runtime.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@ import type { AgentDeviceClient, Lease } from '../../agent-device-client.ts';
3333
import type { CloudProviderSessionResult } from '@agent-device/contracts/observability';
3434
import { INTERNAL_COMMANDS, PUBLIC_COMMANDS } from '../../command-catalog.ts';
3535
import { readMetroPrepareKind } from '../../commands/metro/prepare-kind.ts';
36-
import { connectionProviderRequiresRemoteDaemon } from '../connection/provider-policy.ts';
36+
import {
37+
connectionProviderRequiresRemoteDaemon,
38+
connectionProviderSupportsDeferredAppSelection,
39+
} from '../connection/provider-policy.ts';
3740
import { readCloudDeviceFeatureProfileFields } from '../connection/profile-fields.ts';
3841
import { isCloudWebDriverProviderName } from '@agent-device/provider-webdriver';
3942
import type { PreviousLeaseReleaseNotice } from './connection-presentation.ts';
@@ -112,7 +115,7 @@ export async function materializeRemoteConnectionForCommand(options: {
112115
);
113116
const nextFlags = { ...mergedFlags, session: state.session };
114117
if (
115-
state.leaseProvider === 'limrun' &&
118+
connectionProviderSupportsDeferredAppSelection(state.leaseProvider) &&
116119
command === PUBLIC_COMMANDS.open &&
117120
typeof options.positionals?.[0] === 'string'
118121
) {
@@ -385,14 +388,16 @@ type ConnectionLeasePolicy = {
385388

386389
function connectionLeasePolicyForState(state: RemoteConnectionState): ConnectionLeasePolicy {
387390
if (state.leaseProvider === 'proxy') return PROXY_CONNECTION_LEASE_POLICY;
388-
if (state.leaseProvider === 'limrun') return LIMRUN_CONNECTION_LEASE_POLICY;
391+
if (connectionProviderSupportsDeferredAppSelection(state.leaseProvider)) {
392+
return DEFERRED_APP_SELECTION_CONNECTION_LEASE_POLICY;
393+
}
389394
if (isCloudWebDriverProviderName(state.leaseProvider)) {
390395
return CLOUD_WEBDRIVER_CONNECTION_LEASE_POLICY;
391396
}
392397
return DEFAULT_CONNECTION_LEASE_POLICY;
393398
}
394399

395-
const LIMRUN_CONNECTION_LEASE_POLICY: ConnectionLeasePolicy = {
400+
const DEFERRED_APP_SELECTION_CONNECTION_LEASE_POLICY: ConnectionLeasePolicy = {
396401
shouldAllocate: (command) =>
397402
command !== PUBLIC_COMMANDS.apps && !leaseDeferredCommands.has(command),
398403
ttlMs: () => undefined,
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import assert from 'node:assert/strict';
2+
import { test } from 'vitest';
3+
import { connectionProviderSupportsDeferredAppSelection } from './provider-policy.ts';
4+
5+
test('only providers declaring deferred app selection use app catalog before allocation', () => {
6+
assert.equal(connectionProviderSupportsDeferredAppSelection('limrun'), true);
7+
assert.equal(connectionProviderSupportsDeferredAppSelection('browserstack'), false);
8+
assert.equal(connectionProviderSupportsDeferredAppSelection('aws-device-farm'), false);
9+
assert.equal(connectionProviderSupportsDeferredAppSelection('proxy'), false);
10+
assert.equal(connectionProviderSupportsDeferredAppSelection(undefined), false);
11+
});

src/cli/connection/provider-policy.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import {
77
export type DirectDeviceConnectProvider = CloudWebDriverKnownProviderName | 'limrun';
88
export type ConnectProvider = 'cloud' | 'proxy' | DirectDeviceConnectProvider;
99

10+
const DEFERRED_APP_SELECTION_PROVIDERS = new Set<DirectDeviceConnectProvider>(['limrun']);
11+
1012
export function isConnectProviderName(value: string | undefined): value is ConnectProvider {
1113
return value === 'cloud' || value === 'proxy' || isDirectDeviceConnectProvider(value);
1214
}
@@ -31,6 +33,12 @@ export function connectionProviderRequiresRemoteDaemon(provider: string | undefi
3133
return !isDirectDeviceConnectProvider(provider);
3234
}
3335

36+
export function connectionProviderSupportsDeferredAppSelection(
37+
provider: string | undefined,
38+
): boolean {
39+
return isDirectDeviceConnectProvider(provider) && DEFERRED_APP_SELECTION_PROVIDERS.has(provider);
40+
}
41+
3442
export function connectionProviderLeaseKind(
3543
provider: string | undefined,
3644
): 'proxy' | 'direct-device-provider' | 'remote-provider' {

0 commit comments

Comments
 (0)