From 472eb9de6dcb29a89f558cd579c9c0d7b5c81f91 Mon Sep 17 00:00:00 2001 From: Jordan Date: Mon, 21 Sep 2026 16:39:05 +0200 Subject: [PATCH] fix(android): configure ADB timeout through environment Use NATIVE_RUN_ADB_TIMEOUT env variable for all standard ADB operations while retaining ADB_TIMEOUT as a legacy fallback, and document the setting in Android CLI help. This prevents ADB timeout errors on setups with high latency. --- src/android/help.ts | 6 +++++ src/android/utils/__tests__/adb.ts | 39 ++++++++++++++++++++++++++++++ src/android/utils/adb.ts | 26 ++++++++++++-------- 3 files changed, 61 insertions(+), 10 deletions(-) diff --git a/src/android/help.ts b/src/android/help.ts index d8a7e5a..fae38bd 100644 --- a/src/android/help.ts +++ b/src/android/help.ts @@ -28,6 +28,12 @@ const help = ` --target ........... Use a specific target --connect ............... Tie process to app process --forward ... Forward a port from device to host + + ADB operations use a default timeout of 5000 milliseconds. Set + NATIVE_RUN_ADB_TIMEOUT to change it for all ADB operations. + The legacy ADB_TIMEOUT variable is also supported, but + NATIVE_RUN_ADB_TIMEOUT takes precedence when both are set. + Invalid or non-positive values fall back to the 5000 millisecond default. `; export async function run(args: readonly string[]): Promise { diff --git a/src/android/utils/__tests__/adb.ts b/src/android/utils/__tests__/adb.ts index 663160c..24bebb7 100644 --- a/src/android/utils/__tests__/adb.ts +++ b/src/android/utils/__tests__/adb.ts @@ -3,6 +3,45 @@ import * as os from 'os'; import type * as adb from '../adb'; describe('android/utils/adb', () => { + describe('getAdbTimeout', () => { + let adbUtils: typeof adb; + + beforeEach(async () => { + jest.resetModules(); + adbUtils = await import('../adb'); + }); + + afterEach(() => { + delete process.env.NATIVE_RUN_ADB_TIMEOUT; + delete process.env.ADB_TIMEOUT; + }); + + it('should read the timeout from NATIVE_RUN_ADB_TIMEOUT', () => { + process.env.NATIVE_RUN_ADB_TIMEOUT = '12345'; + + expect(adbUtils.getAdbTimeout()).toBe(12345); + }); + + it('should prefer NATIVE_RUN_ADB_TIMEOUT over the legacy ADB_TIMEOUT', () => { + process.env.NATIVE_RUN_ADB_TIMEOUT = '12345'; + process.env.ADB_TIMEOUT = '67890'; + + expect(adbUtils.getAdbTimeout()).toBe(12345); + }); + + it('should support the legacy ADB_TIMEOUT variable', () => { + process.env.ADB_TIMEOUT = '12345'; + + expect(adbUtils.getAdbTimeout()).toBe(12345); + }); + + it('should fall back to 5000 when the timeout is invalid', () => { + process.env.ADB_TIMEOUT = 'nope'; + + expect(adbUtils.getAdbTimeout()).toBe(5000); + }); + }); + describe('parseAdbDevices', () => { let adbUtils: typeof adb; diff --git a/src/android/utils/adb.ts b/src/android/utils/adb.ts index 3d0a81f..c25e7d9 100644 --- a/src/android/utils/adb.ts +++ b/src/android/utils/adb.ts @@ -57,7 +57,7 @@ export async function getDevices(sdk: SDK): Promise { const args = ['devices', '-l']; debug('Invoking adb with args: %O', args); - const stdout = await execAdb(sdk, args, { timeout: 5000 }); + const stdout = await execAdb(sdk, args); const devices = parseAdbDevices(stdout); @@ -85,7 +85,7 @@ export async function getDeviceProperty(sdk: SDK, device: Device, property: stri const args = ['-s', device.serial, 'shell', 'getprop', property]; debug('Invoking adb with args: %O', args); - const stdout = await execAdb(sdk, args, { timeout: 5000 }); + const stdout = await execAdb(sdk, args); return stdout.trim(); } @@ -95,7 +95,7 @@ export async function getDeviceProperties(sdk: SDK, device: Device): Promise { @@ -348,7 +348,12 @@ export async function unforwardPorts(sdk: SDK, device: Device, ports: Ports): Pr const args = ['-s', device.serial, 'reverse', '--remove', `tcp:${ports.device}`]; debug('Invoking adb with args: %O', args); - await execAdb(sdk, args, { timeout: 5000 }); + await execAdb(sdk, args); +} + +export function getAdbTimeout(): number { + const timeout = Number.parseFloat(process.env.NATIVE_RUN_ADB_TIMEOUT || process.env.ADB_TIMEOUT || '5000'); + return Number.isFinite(timeout) && timeout > 0 ? timeout : 5000; } export interface ExecADBOptions { @@ -358,16 +363,17 @@ export interface ExecADBOptions { export async function execAdb(sdk: SDK, args: string[], options: ExecADBOptions = {}): Promise { const debug = Debug(`${modulePrefix}:${execAdb.name}`); let timer: NodeJS.Timer | undefined; + const timeout = typeof options.timeout === 'number' ? options.timeout : getAdbTimeout(); const retry = async () => { - const msg = `ADBs is unresponsive after ${options.timeout}ms, killing server and retrying...\n`; + const msg = `ADBs is unresponsive after ${timeout}ms, killing server and retrying...\n`; if (process.argv.includes('--json')) { debug(msg); } else { process.stderr.write(msg); } - debug('ADB timeout of %O reached, killing server and retrying...', options.timeout); + debug('ADB timeout of %O reached, killing server and retrying...', timeout); debug('Invoking adb with args: %O', ['kill-server']); await execAdb(sdk, ['kill-server']); debug('Invoking adb with args: %O', ['start-server']); @@ -392,8 +398,8 @@ export async function execAdb(sdk: SDK, args: string[], options: ExecADBOptions }; return new Promise((resolve, reject) => { - if (options.timeout) { - timer = setTimeout(() => retry().then(resolve, reject), options.timeout); + if (timeout) { + timer = setTimeout(() => retry().then(resolve, reject), timeout); } run().then(resolve, (err) => {