1
- import childProcess from "child_process" ;
2
1
import logger from "../../logger.js" ;
2
+ import childProcess from "child_process" ;
3
3
import {
4
- BrowserStackProducts ,
5
4
getDevicesAndBrowsers ,
5
+ BrowserStackProducts ,
6
6
} from "../../lib/device-cache.js" ;
7
- import { fuzzySearchDevices } from "./fuzzy-search.js" ;
8
7
import { sanitizeUrlParam } from "../../lib/utils.js" ;
9
8
import { 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" ;
18
12
19
13
interface StartSessionArgs {
20
14
appPath : string ;
@@ -24,192 +18,68 @@ interface StartSessionArgs {
24
18
}
25
19
26
20
/**
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.
31
22
*/
32
23
export 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 ;
35
26
27
+ // 1) Fetch devices for APP_LIVE
36
28
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 } ) ) ,
61
31
) ;
62
32
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 } "` ) ;
106
37
}
107
- return desiredPlatformVersion ;
108
- }
109
38
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 ) ;
125
41
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 ) ;
135
45
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 ) {
151
49
throw new Error (
152
- `No devices found matching "${ desiredPhone } " for ${ desiredPlatform } ${ desiredPlatformVersion } ` ,
50
+ `No devices for version "${ version } " on ${ desiredPlatform } ` ,
153
51
) ;
154
52
}
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.` ;
169
61
}
170
62
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 } ` ) ;
184
66
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
199
68
const deviceParam = sanitizeUrlParam (
200
- device . display_name . replace ( / \s + / g, "+" ) ,
69
+ selected . display_name . replace ( / \s + / g, "+" ) ,
201
70
) ;
202
-
203
71
const params = new URLSearchParams ( {
204
72
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 ( ) || "" ,
207
75
scale_to_fit : "true" ,
208
76
speed : "1" ,
209
77
start : "true" ,
210
78
} ) ;
79
+ const launchUrl = `https://app-live.browserstack.com/dashboard#${ params . toString ( ) } &device=${ deviceParam } ` ;
211
80
212
- return `https://app-live.browserstack.com/dashboard#${ params . toString ( ) } &device=${ deviceParam } ` ;
81
+ openBrowser ( launchUrl ) ;
82
+ return launchUrl + note ;
213
83
}
214
84
215
85
/**
0 commit comments