@@ -12,6 +12,8 @@ const INITIAL_DEVICE_RETRY_COUNT = 6;
1212const INITIAL_DEVICE_RETRY_DELAY_MS = 500 ;
1313const BACKGROUND_DEVICE_REFRESH_COUNT = 15 ;
1414const BACKGROUND_DEVICE_REFRESH_DELAY_MS = 2000 ;
15+ const SERVER_CHECK_RETRY_COUNT = 3 ;
16+ const SERVER_CHECK_RETRY_DELAY_MS = 250 ;
1517
1618// Platform-specific adb binary paths
1719const ADB_PATHS = {
@@ -81,7 +83,7 @@ class DeviceManager extends EventEmitter {
8183 this . _adbPath = findAdbPath ( ) ;
8284
8385 // Check if ADB server is already running
84- const isRunning = await this . _checkServerRunning ( ) ;
86+ const isRunning = await this . _checkServerRunningWithRetry ( ) ;
8587
8688 if ( ! isRunning ) {
8789 console . log ( '[ADB] ADB server not running, starting...' ) ;
@@ -97,6 +99,16 @@ class DeviceManager extends EventEmitter {
9799 console . log ( '[ADB] ADB server already running' ) ;
98100 }
99101
102+ this . _serverRunning = true ;
103+ this . _startHealthCheck ( ) ;
104+
105+ // Prime the device list via `adb devices` before loading adbkit.
106+ // adbkit pulls in the usb native addon on Windows, which can block the
107+ // main process for a long time in packaged builds. The binary query is
108+ // enough to surface an already-connected device to the UI immediately.
109+ await this . _updateDevicesWithRetry ( INITIAL_DEVICE_RETRY_COUNT , INITIAL_DEVICE_RETRY_DELAY_MS ) ;
110+ this . _scheduleBackgroundRefreshes ( ) ;
111+
100112 // Initialize adbkit client
101113 try {
102114 // Prefer adbkit from package.json; keep compatibility with devicefarmer fork if present.
@@ -115,15 +127,17 @@ class DeviceManager extends EventEmitter {
115127 ) ;
116128 }
117129 }
118- this . client = adbkit . createClient ( ) ;
119-
120- // Start device tracking
121- await this . _startTracking ( ) ;
122-
123- // Start health check
124- this . _startHealthCheck ( ) ;
130+ this . client = adbkit . createClient ( {
131+ bin : this . _adbPath ,
132+ host : '127.0.0.1' ,
133+ port : ADB_SERVER_PORT
134+ } ) ;
125135
126- this . _serverRunning = true ;
136+ // Start adbkit tracking in the background so a slow tracker does not
137+ // block packaged-app startup or initial device visibility.
138+ this . _startTracking ( ) . catch ( ( trackingErr ) => {
139+ console . error ( '[ADB] Background tracking failed:' , trackingErr . message ) ;
140+ } ) ;
127141 console . log ( '[ADB] Device manager initialized' ) ;
128142 } catch ( err ) {
129143 console . error ( '[ADB] Failed to initialize adbkit:' , err . message ) ;
@@ -155,6 +169,21 @@ class DeviceManager extends EventEmitter {
155169 } ) ;
156170 }
157171
172+ async _checkServerRunningWithRetry ( attempts = SERVER_CHECK_RETRY_COUNT , delayMs = SERVER_CHECK_RETRY_DELAY_MS ) {
173+ for ( let attempt = 1 ; attempt <= attempts ; attempt ++ ) {
174+ const running = await this . _checkServerRunning ( ) ;
175+ if ( running ) {
176+ return true ;
177+ }
178+
179+ if ( attempt < attempts ) {
180+ await new Promise ( ( resolve ) => setTimeout ( resolve , delayMs ) ) ;
181+ }
182+ }
183+
184+ return false ;
185+ }
186+
158187 /**
159188 * Start ADB server using binary adb
160189 */
@@ -185,7 +214,7 @@ class DeviceManager extends EventEmitter {
185214 finish ( { ok : true } ) ;
186215 } else {
187216 // Some adb variants return non-zero while server is actually up.
188- const running = await this . _checkServerRunning ( ) ;
217+ const running = await this . _checkServerRunningWithRetry ( ) ;
189218 if ( running ) {
190219 console . warn ( `[ADB] start-server exited with code ${ code } , but server is reachable` ) ;
191220 finish ( { ok : true } ) ;
@@ -220,11 +249,19 @@ class DeviceManager extends EventEmitter {
220249 } ) ;
221250
222251 // Timeout after 10 seconds
223- const timeout = setTimeout ( ( ) => {
252+ const timeout = setTimeout ( async ( ) => {
224253 if ( ! proc . killed ) {
225254 proc . kill ( ) ;
226255 console . log ( '[ADB] Server start timed out' ) ;
227256 }
257+
258+ const running = await this . _checkServerRunningWithRetry ( ) ;
259+ if ( running ) {
260+ console . warn ( '[ADB] start-server timed out, but server is reachable' ) ;
261+ finish ( { ok : true } ) ;
262+ return ;
263+ }
264+
228265 finish ( {
229266 ok : false ,
230267 message : 'Failed to start ADB server (timeout)' ,
@@ -243,7 +280,7 @@ class DeviceManager extends EventEmitter {
243280 }
244281
245282 this . _healthCheckInterval = setInterval ( async ( ) => {
246- const isRunning = await this . _checkServerRunning ( ) ;
283+ const isRunning = await this . _checkServerRunningWithRetry ( ) ;
247284
248285 if ( ! isRunning && this . _serverRunning ) {
249286 console . log ( '[ADB] Server died, attempting restart...' ) ;
@@ -313,46 +350,36 @@ class DeviceManager extends EventEmitter {
313350 async _startTracking ( ) {
314351 if ( ! this . client ) return ;
315352
316- try {
317- this . tracker = await this . client . trackDevices ( ) ;
318-
319- this . tracker . on ( 'add' , ( device ) => {
320- console . log ( `[ADB] Device connected: ${ device . id } (${ device . type } )` ) ;
321- this . _updateDevices ( ) ;
322- this . emit ( 'device:connected' , device ) ;
323- } ) ;
353+ this . tracker = await this . client . trackDevices ( ) ;
324354
325- this . tracker . on ( 'remove ' , ( device ) => {
326- console . log ( `[ADB] Device disconnected : ${ device . id } ` ) ;
327- this . _updateDevices ( ) ;
328- this . emit ( 'device:disconnected ' , device ) ;
329- } ) ;
355+ this . tracker . on ( 'add ' , ( device ) => {
356+ console . log ( `[ADB] Device connected : ${ device . id } ( ${ device . type } ) ` ) ;
357+ this . _updateDevices ( ) ;
358+ this . emit ( 'device:connected ' , device ) ;
359+ } ) ;
330360
331- this . tracker . on ( 'change ' , ( device ) => {
332- console . log ( `[ADB] Device changed : ${ device . id } ( ${ device . type } ) ` ) ;
333- this . _updateDevices ( ) ;
334- this . emit ( 'device:changed ' , device ) ;
335- } ) ;
361+ this . tracker . on ( 'remove ' , ( device ) => {
362+ console . log ( `[ADB] Device disconnected : ${ device . id } ` ) ;
363+ this . _updateDevices ( ) ;
364+ this . emit ( 'device:disconnected ' , device ) ;
365+ } ) ;
336366
337- this . tracker . on ( 'end' , ( ) => {
338- console . log ( '[ADB] Device tracking ended' ) ;
339- this . tracker = null ;
340- } ) ;
367+ this . tracker . on ( 'change' , ( device ) => {
368+ console . log ( `[ADB] Device changed: ${ device . id } (${ device . type } )` ) ;
369+ this . _updateDevices ( ) ;
370+ this . emit ( 'device:changed' , device ) ;
371+ } ) ;
341372
342- this . tracker . on ( 'error' , ( err ) => {
343- console . error ( '[ADB] Tracker error:' , err . message ) ;
344- } ) ;
373+ this . tracker . on ( 'end' , ( ) => {
374+ console . log ( '[ADB] Device tracking ended' ) ;
375+ this . tracker = null ;
376+ } ) ;
345377
346- // When adb has just started, listDevices() may briefly return empty even
347- // though a USB device is already authorized. Retry a few times so the UI
348- // does not get stuck on "No Devices" after startup.
349- await this . _updateDevicesWithRetry ( INITIAL_DEVICE_RETRY_COUNT , INITIAL_DEVICE_RETRY_DELAY_MS ) ;
350- this . _scheduleBackgroundRefreshes ( ) ;
378+ this . tracker . on ( 'error' , ( err ) => {
379+ console . error ( '[ADB] Tracker error:' , err . message ) ;
380+ } ) ;
351381
352- console . log ( '[ADB] Device tracking started' ) ;
353- } catch ( err ) {
354- console . error ( '[ADB] Failed to start device tracking:' , err . message ) ;
355- }
382+ console . log ( '[ADB] Device tracking started' ) ;
356383 }
357384
358385 async _updateDevicesWithRetry ( maxAttempts , delayMs ) {
@@ -374,33 +401,26 @@ class DeviceManager extends EventEmitter {
374401 * Update device list
375402 */
376403 async _updateDevices ( ) {
377- if ( ! this . client ) return this . devices ;
404+ let devices = await this . _listDevicesViaAdbBinary ( ) ;
405+ if ( devices . length > 0 ) {
406+ this . devices = devices ;
407+ this . emit ( 'devices:updated' , this . devices ) ;
408+ return this . devices ;
409+ }
378410
379- try {
380- let devices = await this . client . listDevices ( ) ;
381-
382- // adbkit can occasionally return an empty list during server/device warmup
383- // while `adb devices` already reports the device. Fall back to the binary
384- // query so the UI does not stay stuck on "No Devices".
385- if ( devices . length === 0 ) {
386- const cliDevices = await this . _listDevicesViaAdbBinary ( ) ;
387- if ( cliDevices . length > 0 ) {
388- console . warn ( `[ADB] adbkit returned 0 devices, using adb binary result (${ cliDevices . length } )` ) ;
389- devices = cliDevices ;
390- }
391- }
411+ if ( ! this . client ) {
412+ this . devices = [ ] ;
413+ this . emit ( 'devices:updated' , this . devices ) ;
414+ return this . devices ;
415+ }
392416
417+ try {
418+ devices = await this . client . listDevices ( ) ;
393419 this . devices = devices ;
394420 this . emit ( 'devices:updated' , this . devices ) ;
395421 return this . devices ;
396422 } catch ( err ) {
397- console . error ( '[ADB] Failed to list devices:' , err . message ) ;
398- const cliDevices = await this . _listDevicesViaAdbBinary ( ) ;
399- if ( cliDevices . length > 0 ) {
400- console . warn ( `[ADB] Recovered device list via adb binary after adbkit error (${ cliDevices . length } )` ) ;
401- this . devices = cliDevices ;
402- this . emit ( 'devices:updated' , this . devices ) ;
403- }
423+ console . error ( '[ADB] Failed to list devices via adbkit:' , err . message ) ;
404424 return this . devices ;
405425 }
406426 }
@@ -476,7 +496,7 @@ class DeviceManager extends EventEmitter {
476496 const tick = async ( ) => {
477497 this . _backgroundRefreshTimeout = null ;
478498
479- if ( ! this . client || this . devices . length > 0 || this . _backgroundRefreshRemaining <= 0 ) {
499+ if ( this . devices . length > 0 || this . _backgroundRefreshRemaining <= 0 ) {
480500 return ;
481501 }
482502
@@ -505,6 +525,10 @@ class DeviceManager extends EventEmitter {
505525 return this . devices ;
506526 }
507527
528+ getAdbPath ( ) {
529+ return this . _adbPath ;
530+ }
531+
508532 /**
509533 * Get first connected device
510534 */
0 commit comments