1- import childProcess from "child_process" ;
21import logger from "../../logger.js" ;
2+ import childProcess from "child_process" ;
33import {
4- BrowserStackProducts ,
54 getDevicesAndBrowsers ,
5+ BrowserStackProducts ,
66} from "../../lib/device-cache.js" ;
7- import { fuzzySearchDevices } from "./fuzzy-search.js" ;
87import { sanitizeUrlParam } from "../../lib/utils.js" ;
98import { uploadApp } from "./upload-app.js" ;
10-
11- export interface DeviceEntry {
12- device : string ;
13- display_name : string ;
14- os : string ;
15- os_version : string ;
16- real_mobile : boolean ;
17- }
9+ import { findDeviceByName } from "./device-search.js" ;
10+ import { pickVersion } from "./version-utils.js" ;
11+ import { DeviceEntry } from "./types.js" ;
1812
1913interface StartSessionArgs {
2014 appPath : string ;
@@ -24,192 +18,68 @@ interface StartSessionArgs {
2418}
2519
2620/**
27- * Starts an App Live session after filtering, fuzzy matching, and launching.
28- * @param args - The arguments for starting the session.
29- * @returns The launch URL for the session.
30- * @throws Will throw an error if no devices are found or if the app URL is invalid.
21+ * Start an App Live session: filter, select, upload, and open.
3122 */
3223export async function startSession ( args : StartSessionArgs ) : Promise < string > {
33- const { appPath, desiredPlatform, desiredPhone } = args ;
34- let { desiredPlatformVersion } = args ;
24+ const { appPath, desiredPlatform, desiredPhone, desiredPlatformVersion } =
25+ args ;
3526
27+ // 1) Fetch devices for APP_LIVE
3628 const data = await getDevicesAndBrowsers ( BrowserStackProducts . APP_LIVE ) ;
37-
38- const allDevices : DeviceEntry [ ] = data . mobile . flatMap ( ( group : any ) =>
39- group . devices . map ( ( dev : any ) => ( { ...dev , os : group . os } ) ) ,
40- ) ;
41-
42- desiredPlatformVersion = resolvePlatformVersion (
43- allDevices ,
44- desiredPlatform ,
45- desiredPlatformVersion ,
46- ) ;
47-
48- const filteredDevices = filterDevicesByPlatformAndVersion (
49- allDevices ,
50- desiredPlatform ,
51- desiredPlatformVersion ,
52- ) ;
53-
54- const matches = await fuzzySearchDevices ( filteredDevices , desiredPhone ) ;
55-
56- const selectedDevice = validateAndSelectDevice (
57- matches ,
58- desiredPhone ,
59- desiredPlatform ,
60- desiredPlatformVersion ,
29+ const all : DeviceEntry [ ] = data . mobile . flatMap ( ( grp : any ) =>
30+ grp . devices . map ( ( dev : any ) => ( { ...dev , os : grp . os } ) ) ,
6131 ) ;
6232
63- const { app_url } = await uploadApp ( appPath ) ;
64-
65- validateAppUrl ( app_url ) ;
66-
67- const launchUrl = constructLaunchUrl (
68- app_url ,
69- selectedDevice ,
70- desiredPlatform ,
71- desiredPlatformVersion ,
72- ) ;
73-
74- openBrowser ( launchUrl ) ;
75-
76- return launchUrl ;
77- }
78-
79- /**
80- * Resolves the platform version based on the desired platform and version.
81- * @param allDevices - The list of all devices.
82- * @param desiredPlatform - The desired platform (android or ios).
83- * @param desiredPlatformVersion - The desired platform version.
84- * @returns The resolved platform version.
85- * @throws Will throw an error if the platform version is not valid.
86- */
87- function resolvePlatformVersion (
88- allDevices : DeviceEntry [ ] ,
89- desiredPlatform : string ,
90- desiredPlatformVersion : string ,
91- ) : string {
92- if (
93- desiredPlatformVersion === "latest" ||
94- desiredPlatformVersion === "oldest"
95- ) {
96- const filtered = allDevices . filter ( ( d ) => d . os === desiredPlatform ) ;
97- filtered . sort ( ( a , b ) => {
98- const versionA = parseFloat ( a . os_version ) ;
99- const versionB = parseFloat ( b . os_version ) ;
100- return desiredPlatformVersion === "latest"
101- ? versionB - versionA
102- : versionA - versionB ;
103- } ) ;
104-
105- return filtered [ 0 ] . os_version ;
33+ // 2) Filter by OS
34+ const osMatches = all . filter ( ( d ) => d . os === desiredPlatform ) ;
35+ if ( ! osMatches . length ) {
36+ throw new Error ( `No devices for OS "${ desiredPlatform } "` ) ;
10637 }
107- return desiredPlatformVersion ;
108- }
10938
110- /**
111- * Filters devices based on the desired platform and version.
112- * @param allDevices - The list of all devices.
113- * @param desiredPlatform - The desired platform (android or ios).
114- * @param desiredPlatformVersion - The desired platform version.
115- * @returns The filtered list of devices.
116- * @throws Will throw an error if the platform version is not valid.
117- */
118- function filterDevicesByPlatformAndVersion (
119- allDevices : DeviceEntry [ ] ,
120- desiredPlatform : string ,
121- desiredPlatformVersion : string ,
122- ) : DeviceEntry [ ] {
123- return allDevices . filter ( ( d ) => {
124- if ( d . os !== desiredPlatform ) return false ;
39+ // 3) Select by name
40+ const nameMatches = findDeviceByName ( osMatches , desiredPhone ) ;
12541
126- try {
127- const versionA = parseFloat ( d . os_version ) ;
128- const versionB = parseFloat ( desiredPlatformVersion ) ;
129- return versionA === versionB ;
130- } catch {
131- return d . os_version === desiredPlatformVersion ;
132- }
133- } ) ;
134- }
42+ // 4) Resolve version
43+ const versions = [ ...new Set ( nameMatches . map ( ( d ) => d . os_version ) ) ] ;
44+ const version = pickVersion ( versions , desiredPlatformVersion ) ;
13545
136- /**
137- * Validates the selected device and handles multiple matches.
138- * @param matches - The list of device matches.
139- * @param desiredPhone - The desired phone name.
140- * @param desiredPlatform - The desired platform (android or ios).
141- * @param desiredPlatformVersion - The desired platform version.
142- * @returns The selected device entry.
143- */
144- function validateAndSelectDevice (
145- matches : DeviceEntry [ ] ,
146- desiredPhone : string ,
147- desiredPlatform : string ,
148- desiredPlatformVersion : string ,
149- ) : DeviceEntry {
150- if ( matches . length === 0 ) {
46+ // 5) Final candidates for version
47+ const final = nameMatches . filter ( ( d ) => d . os_version === version ) ;
48+ if ( ! final . length ) {
15149 throw new Error (
152- `No devices found matching "${ desiredPhone } " for ${ desiredPlatform } ${ desiredPlatformVersion } ` ,
50+ `No devices for version "${ version } " on ${ desiredPlatform } ` ,
15351 ) ;
15452 }
155-
156- const exactMatch = matches . find (
157- ( d ) => d . display_name . toLowerCase ( ) === desiredPhone . toLowerCase ( ) ,
158- ) ;
159-
160- if ( exactMatch ) {
161- return exactMatch ;
162- } else if ( matches . length >= 1 ) {
163- const names = matches . map ( ( d ) => d . display_name ) . join ( ", " ) ;
164- const error_message =
165- matches . length === 1
166- ? `Alternative device found: ${ names } . Would you like to use it?`
167- : `Multiple devices found: ${ names } . Please select one.` ;
168- throw new Error ( `${ error_message } ` ) ;
53+ const selected = final [ 0 ] ;
54+ let note = "" ;
55+ if (
56+ version != desiredPlatformVersion &&
57+ desiredPlatformVersion !== "latest" &&
58+ desiredPlatformVersion !== "oldest"
59+ ) {
60+ note = `\n Note: The requested version "${ desiredPlatformVersion } " is not available. Using "${ version } " instead.` ;
16961 }
17062
171- return matches [ 0 ] ;
172- }
173-
174- /**
175- * Validates the app URL.
176- * @param appUrl - The app URL to validate.
177- * @throws Will throw an error if the app URL is not valid.
178- */
179- function validateAppUrl ( appUrl : string ) : void {
180- if ( ! appUrl . match ( "bs://" ) ) {
181- throw new Error ( "The app path is not a valid BrowserStack app URL." ) ;
182- }
183- }
63+ // 6) Upload app
64+ const { app_url } = await uploadApp ( appPath ) ;
65+ logger . info ( `App uploaded: ${ app_url } ` ) ;
18466
185- /**
186- * Constructs the launch URL for the App Live session.
187- * @param appUrl - The app URL.
188- * @param device - The selected device entry.
189- * @param desiredPlatform - The desired platform (android or ios).
190- * @param desiredPlatformVersion - The desired platform version.
191- * @returns The constructed launch URL.
192- */
193- function constructLaunchUrl (
194- appUrl : string ,
195- device : DeviceEntry ,
196- desiredPlatform : string ,
197- desiredPlatformVersion : string ,
198- ) : string {
67+ // 7) Build URL & open
19968 const deviceParam = sanitizeUrlParam (
200- device . display_name . replace ( / \s + / g, "+" ) ,
69+ selected . display_name . replace ( / \s + / g, "+" ) ,
20170 ) ;
202-
20371 const params = new URLSearchParams ( {
20472 os : desiredPlatform ,
205- os_version : desiredPlatformVersion ,
206- app_hashed_id : appUrl . split ( "bs://" ) . pop ( ) || "" ,
73+ os_version : version ,
74+ app_hashed_id : app_url . split ( "bs://" ) . pop ( ) || "" ,
20775 scale_to_fit : "true" ,
20876 speed : "1" ,
20977 start : "true" ,
21078 } ) ;
79+ const launchUrl = `https://app-live.browserstack.com/dashboard#${ params . toString ( ) } &device=${ deviceParam } ` ;
21180
212- return `https://app-live.browserstack.com/dashboard#${ params . toString ( ) } &device=${ deviceParam } ` ;
81+ openBrowser ( launchUrl ) ;
82+ return launchUrl + note ;
21383}
21484
21585/**
0 commit comments