appium-adb 14.0.2 → 14.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -77,7 +77,7 @@ const path_1 = __importDefault(require("path"));
77
77
  const logger_js_1 = require("../logger.js");
78
78
  const bluebird_1 = __importDefault(require("bluebird"));
79
79
  const support_1 = require("@appium/support");
80
- const helpers_1 = require("../helpers");
80
+ const helpers_js_1 = require("../helpers.js");
81
81
  const teen_process_1 = require("teen_process");
82
82
  const asyncbox_1 = require("asyncbox");
83
83
  const lodash_1 = __importDefault(require("lodash"));
@@ -103,22 +103,12 @@ const SDK_BINARY_ROOTS = [
103
103
  const MIN_DELAY_ADB_API_LEVEL = 28;
104
104
  const REQUIRED_SERVICES = ['activity', 'package', 'window'];
105
105
  const MAX_SHELL_BUFFER_LENGTH = 1000;
106
- /**
107
- * Retrieve full path to the given binary.
108
- *
109
- * @this {import('../adb.js').ADB}
110
- * @param {string} binaryName - The name of the binary.
111
- * @return {Promise<string>} Full path to the given binary including current SDK root.
112
- */
113
- async function getSdkBinaryPath(binaryName) {
114
- return await this.getBinaryFromSdkRoot(binaryName);
115
- }
106
+ // Private methods (defined early as they're used by public methods)
116
107
  /**
117
108
  * Retrieve full binary name for the current operating system.
118
109
  *
119
- * @param {string} binaryName - simple binary name, for example 'android'.
120
- * @return {string} Formatted binary name depending on the current platform,
121
- * for example, 'android.bat' on Windows.
110
+ * @param binaryName - The name of the binary
111
+ * @returns The binary name with appropriate extension for the current OS
122
112
  */
123
113
  function _getBinaryNameForOS(binaryName) {
124
114
  if (!support_1.system.isWindows()) {
@@ -132,30 +122,61 @@ function _getBinaryNameForOS(binaryName) {
132
122
  }
133
123
  return binaryName;
134
124
  }
125
+ /**
126
+ * Returns the Android binaries locations
127
+ *
128
+ * @param sdkRoot - The Android SDK root directory path
129
+ * @param fullBinaryName - The full name of the binary (with extension)
130
+ * @returns Array of possible binary location paths
131
+ */
132
+ function getSdkBinaryLocationCandidates(sdkRoot, fullBinaryName) {
133
+ return SDK_BINARY_ROOTS.map((x) => path_1.default.resolve(sdkRoot, ...(lodash_1.default.isArray(x) ? x : [x]), fullBinaryName));
134
+ }
135
+ /**
136
+ * Get the path to the openssl binary for the current operating system
137
+ *
138
+ * @returns The full path to the openssl binary
139
+ * @throws {Error} If openssl is not found in PATH
140
+ */
141
+ async function getOpenSslForOs() {
142
+ const binaryName = `openssl${support_1.system.isWindows() ? '.exe' : ''}`;
143
+ try {
144
+ return await support_1.fs.which(binaryName);
145
+ }
146
+ catch {
147
+ throw new Error('The openssl tool must be installed on the system and available on the path');
148
+ }
149
+ }
150
+ // Public methods
151
+ /**
152
+ * Retrieve full path to the given binary.
153
+ *
154
+ * @param binaryName - The name of the binary
155
+ * @returns The full path to the binary
156
+ */
157
+ async function getSdkBinaryPath(binaryName) {
158
+ return await this.getBinaryFromSdkRoot(binaryName);
159
+ }
135
160
  exports.getBinaryNameForOS = lodash_1.default.memoize(_getBinaryNameForOS);
136
161
  /**
137
162
  * Retrieve full path to the given binary and caches it into `binaries`
138
163
  * property of the current ADB instance.
139
164
  *
140
- * @this {import('../adb.js').ADB}
141
- * @param {string} binaryName - Simple name of a binary file.
142
- * @return {Promise<string>} Full path to the given binary. The method tries
143
- * to enumerate all the known locations where the binary
144
- * might be located and stops the search as soon as the first
145
- * match is found on the local file system.
146
- * @throws {Error} If the binary with given name is not present at any
147
- * of known locations or Android SDK is not installed on the
148
- * local file system.
165
+ * @param binaryName - The name of the binary
166
+ * @returns The full path to the binary
167
+ * @throws {Error} If SDK root is not set or binary cannot be found
149
168
  */
150
169
  async function getBinaryFromSdkRoot(binaryName) {
151
- if (( /** @type {import('./types').StringRecord} */(this.binaries))[binaryName]) {
152
- return ( /** @type {import('./types').StringRecord} */(this.binaries))[binaryName];
170
+ if (this.binaries?.[binaryName]) {
171
+ return this.binaries[binaryName];
153
172
  }
154
173
  const fullBinaryName = this.getBinaryNameForOS(binaryName);
155
- const binaryLocs = getSdkBinaryLocationCandidates(
156
- /** @type {string} */ (this.sdkRoot), fullBinaryName);
174
+ if (!this.sdkRoot) {
175
+ throw new Error('SDK root is not set');
176
+ }
177
+ const binaryLocs = getSdkBinaryLocationCandidates(this.sdkRoot, fullBinaryName);
157
178
  // get subpaths for currently installed build tool directories
158
- let buildToolsDirs = await (0, exports.getBuildToolsDirs)(/** @type {string} */ (this.sdkRoot));
179
+ let buildToolsDirs = await (0, exports.getBuildToolsDirs)(this.sdkRoot);
159
180
  if (this.buildToolsVersion) {
160
181
  buildToolsDirs = buildToolsDirs
161
182
  .filter((x) => path_1.default.basename(x) === this.buildToolsVersion);
@@ -184,38 +205,23 @@ async function getBinaryFromSdkRoot(binaryName) {
184
205
  `installed at '${this.sdkRoot}'?`);
185
206
  }
186
207
  logger_js_1.log.info(`Using '${fullBinaryName}' from '${binaryLoc}'`);
187
- ( /** @type {import('./types').StringRecord} */(this.binaries))[binaryName] = binaryLoc;
208
+ if (!this.binaries) {
209
+ this.binaries = {};
210
+ }
211
+ this.binaries[binaryName] = binaryLoc;
188
212
  return binaryLoc;
189
213
  }
190
- /**
191
- * Returns the Android binaries locations
192
- *
193
- * @param {string} sdkRoot The path to Android SDK root.
194
- * @param {string} fullBinaryName The name of full binary name.
195
- * @return {string[]} The list of SDK_BINARY_ROOTS paths
196
- * with sdkRoot and fullBinaryName.
197
- */
198
- function getSdkBinaryLocationCandidates(sdkRoot, fullBinaryName) {
199
- return SDK_BINARY_ROOTS.map((x) => path_1.default.resolve(sdkRoot, ...(lodash_1.default.isArray(x) ? x : [x]), fullBinaryName));
200
- }
201
214
  /**
202
215
  * Retrieve full path to the given binary.
203
216
  * This method does not have cache.
204
217
  *
205
- * @param {string} binaryName - Simple name of a binary file.
206
- * e.g. 'adb', 'android'
207
- * @return {Promise<string>} Full path to the given binary. The method tries
208
- * to enumerate all the known locations where the binary
209
- * might be located and stops the search as soon as the first
210
- * match is found on the local file system.
211
- * e.g. '/Path/To/Android/sdk/platform-tools/adb'
212
- * @throws {Error} If the binary with given name is not present at any
213
- * of known locations or Android SDK is not installed on the
214
- * local file system.
218
+ * @param binaryName - The name of the binary
219
+ * @returns The full path to the binary
220
+ * @throws {Error} If binary cannot be found in the Android SDK
215
221
  */
216
222
  async function getAndroidBinaryPath(binaryName) {
217
223
  const fullBinaryName = (0, exports.getBinaryNameForOS)(binaryName);
218
- const sdkRoot = (0, helpers_1.getSdkRootFromEnv)();
224
+ const sdkRoot = (0, helpers_js_1.getSdkRootFromEnv)();
219
225
  const binaryLocs = getSdkBinaryLocationCandidates(sdkRoot ?? '', fullBinaryName);
220
226
  for (const loc of binaryLocs) {
221
227
  if (await support_1.fs.exists(loc)) {
@@ -228,21 +234,22 @@ async function getAndroidBinaryPath(binaryName) {
228
234
  /**
229
235
  * Retrieve full path to a binary file using the standard system lookup tool.
230
236
  *
231
- * @this {import('../adb.js').ADB}
232
- * @param {string} binaryName - The name of the binary.
233
- * @return {Promise<string>} Full path to the binary received from 'which'/'where'
234
- * output.
235
- * @throws {Error} If lookup tool returns non-zero return code.
237
+ * @param binaryName - The name of the binary
238
+ * @returns The full path to the binary
239
+ * @throws {Error} If binary cannot be found in PATH
236
240
  */
237
241
  async function getBinaryFromPath(binaryName) {
238
- if (( /** @type {import('./types').StringRecord} */(this.binaries))[binaryName]) {
239
- return ( /** @type {import('./types').StringRecord} */(this.binaries))[binaryName];
242
+ if (this.binaries?.[binaryName]) {
243
+ return this.binaries[binaryName];
240
244
  }
241
245
  const fullBinaryName = this.getBinaryNameForOS(binaryName);
242
246
  try {
243
247
  const binaryLoc = await support_1.fs.which(fullBinaryName);
244
248
  logger_js_1.log.info(`Using '${fullBinaryName}' from '${binaryLoc}'`);
245
- ( /** @type {import('./types').StringRecord} */(this.binaries))[binaryName] = binaryLoc;
249
+ if (!this.binaries) {
250
+ this.binaries = {};
251
+ }
252
+ this.binaries[binaryName] = binaryLoc;
246
253
  return binaryLoc;
247
254
  }
248
255
  catch {
@@ -253,11 +260,9 @@ async function getBinaryFromPath(binaryName) {
253
260
  /**
254
261
  * Retrieve the list of devices visible to adb.
255
262
  *
256
- * @this {import('../adb.js').ADB}
257
- * @param {import('./types').ConnectedDevicesOptions} [opts={}] - Additional options mapping.
258
- * @return {Promise<import('./types').Device[]>} The list of devices or an empty list if
259
- * no devices are connected.
260
- * @throws {Error} If there was an error while listing devices.
263
+ * @param opts - Options for device retrieval
264
+ * @returns Array of connected devices
265
+ * @throws {Error} If adb devices command fails or returns unexpected output
261
266
  */
262
267
  async function getConnectedDevices(opts = {}) {
263
268
  logger_js_1.log.debug('Getting connected devices');
@@ -270,7 +275,8 @@ async function getConnectedDevices(opts = {}) {
270
275
  ({ stdout } = await (0, teen_process_1.exec)(this.executable.path, args));
271
276
  }
272
277
  catch (e) {
273
- throw new Error(`Error while getting connected devices. Original error: ${e.message}`);
278
+ const error = e;
279
+ throw new Error(`Error while getting connected devices. Original error: ${error.message}`);
274
280
  }
275
281
  const listHeader = 'List of devices';
276
282
  // expecting adb devices to return output as
@@ -282,7 +288,7 @@ async function getConnectedDevices(opts = {}) {
282
288
  }
283
289
  // slicing output we care about
284
290
  stdout = stdout.slice(startingIndex);
285
- let excludedLines = [listHeader, 'adb server', '* daemon'];
291
+ const excludedLines = [listHeader, 'adb server', '* daemon'];
286
292
  if (!this.allowOfflineDevices) {
287
293
  excludedLines.push('offline');
288
294
  }
@@ -315,16 +321,14 @@ async function getConnectedDevices(opts = {}) {
315
321
  /**
316
322
  * Retrieve the list of devices visible to adb within the given timeout.
317
323
  *
318
- * @this {import('../adb.js').ADB}
319
- * @param {number} timeoutMs - The maximum number of milliseconds to get at least
320
- * one list item.
321
- * @return {Promise<import('./types').Device[]>} The list of connected devices.
322
- * @throws {Error} If no connected devices can be detected within the given timeout.
324
+ * @param timeoutMs - Maximum time to wait for devices (default: 20000ms)
325
+ * @returns Array of connected devices
326
+ * @throws {Error} If no devices are found within the timeout period
323
327
  */
324
328
  async function getDevicesWithRetry(timeoutMs = 20000) {
325
329
  logger_js_1.log.debug('Trying to find connected Android devices');
326
330
  try {
327
- let devices;
331
+ let devices = [];
328
332
  await (0, asyncbox_1.waitForCondition)(async () => {
329
333
  try {
330
334
  devices = await this.getConnectedDevices();
@@ -334,8 +338,9 @@ async function getDevicesWithRetry(timeoutMs = 20000) {
334
338
  logger_js_1.log.debug('Could not find online devices');
335
339
  }
336
340
  catch (err) {
337
- logger_js_1.log.debug(err.stack);
338
- logger_js_1.log.warn(`Got an unexpected error while fetching connected devices list: ${err.message}`);
341
+ const error = err;
342
+ logger_js_1.log.debug(error.stack);
343
+ logger_js_1.log.warn(`Got an unexpected error while fetching connected devices list: ${error.message}`);
339
344
  }
340
345
  try {
341
346
  await this.reconnect();
@@ -348,10 +353,11 @@ async function getDevicesWithRetry(timeoutMs = 20000) {
348
353
  waitMs: timeoutMs,
349
354
  intervalMs: 200,
350
355
  });
351
- return /** @type {any} */ (devices);
356
+ return devices;
352
357
  }
353
358
  catch (e) {
354
- if (/Condition unmet/.test(e.message)) {
359
+ const error = e;
360
+ if (/Condition unmet/.test(error.message)) {
355
361
  throw new Error(`Could not find a connected Android device in ${timeoutMs}ms`);
356
362
  }
357
363
  else {
@@ -362,13 +368,8 @@ async function getDevicesWithRetry(timeoutMs = 20000) {
362
368
  /**
363
369
  * Kick current connection from host/device side and make it reconnect
364
370
  *
365
- * @this {import('../adb.js').ADB}
366
- * @param {string} [target=offline] One of possible targets to reconnect:
367
- * offline, device or null
368
- * Providing `null` will cause reconnection to happen from the host side.
369
- *
370
- * @throws {Error} If either ADB version is too old and does not support this
371
- * command or there was a failure during reconnect.
371
+ * @param target - The target to reconnect (default: 'offline')
372
+ * @throws {Error} If reconnect command fails
372
373
  */
373
374
  async function reconnect(target = 'offline') {
374
375
  logger_js_1.log.debug(`Reconnecting adb (target ${target})`);
@@ -380,13 +381,12 @@ async function reconnect(target = 'offline') {
380
381
  await this.adbExec(args);
381
382
  }
382
383
  catch (e) {
383
- throw new Error(`Cannot reconnect adb. Original error: ${e.stderr || e.message}`);
384
+ const error = e;
385
+ throw new Error(`Cannot reconnect adb. Original error: ${error.stderr || error.message}`);
384
386
  }
385
387
  }
386
388
  /**
387
389
  * Restart adb server, unless _this.suppressKillServer_ property is true.
388
- *
389
- * @this {import('../adb.js').ADB}
390
390
  */
391
391
  async function restartAdb() {
392
392
  if (this.suppressKillServer) {
@@ -404,7 +404,6 @@ async function restartAdb() {
404
404
  }
405
405
  /**
406
406
  * Kill adb server.
407
- * @this {import('../adb.js').ADB}
408
407
  */
409
408
  async function killServer() {
410
409
  logger_js_1.log.debug(`Killing adb server on port '${this.adbPort}'`);
@@ -416,8 +415,7 @@ async function killServer() {
416
415
  * Reset Telnet authentication token.
417
416
  * @see {@link http://tools.android.com/recent/emulator2516releasenotes} for more details.
418
417
  *
419
- * @this {import('../adb.js').ADB}
420
- * @returns {Promise<boolean>} If token reset was successful.
418
+ * @returns True if token was reset successfully, false otherwise
421
419
  */
422
420
  exports.resetTelnetAuthToken = lodash_1.default.memoize(async function resetTelnetAuthToken() {
423
421
  // The methods is used to remove telnet auth token
@@ -433,7 +431,8 @@ exports.resetTelnetAuthToken = lodash_1.default.memoize(async function resetTeln
433
431
  await support_1.fs.writeFile(dstPath, '');
434
432
  }
435
433
  catch (e) {
436
- logger_js_1.log.warn(`Error ${e.message} while resetting the content of ${dstPath}. Ignoring resetting of emulator's telnet authentication token`);
434
+ const error = e;
435
+ logger_js_1.log.warn(`Error ${error.message} while resetting the content of ${dstPath}. Ignoring resetting of emulator's telnet authentication token`);
437
436
  return false;
438
437
  }
439
438
  return true;
@@ -441,8 +440,8 @@ exports.resetTelnetAuthToken = lodash_1.default.memoize(async function resetTeln
441
440
  /**
442
441
  * Execute the given emulator command using _adb emu_ tool.
443
442
  *
444
- * @this {import('../adb.js').ADB}
445
- * @param {string[]} cmd - The array of rest command line parameters.
443
+ * @param cmd - Array of command arguments
444
+ * @throws {Error} If emulator is not connected or command execution fails
446
445
  */
447
446
  async function adbExecEmu(cmd) {
448
447
  await this.verifyEmulatorConnected();
@@ -450,38 +449,25 @@ async function adbExecEmu(cmd) {
450
449
  await this.adbExec(['emu', ...cmd]);
451
450
  }
452
451
  let isExecLocked = false;
453
- /** @type {{STDOUT: 'stdout', FULL: 'full'}} */
454
- exports.EXEC_OUTPUT_FORMAT = Object.freeze({
452
+ exports.EXEC_OUTPUT_FORMAT = {
455
453
  STDOUT: 'stdout',
456
454
  FULL: 'full',
457
- });
455
+ };
458
456
  /**
459
457
  * Execute the given adb command.
460
458
  *
461
- * @template {import('teen_process').TeenProcessExecOptions & import('./types').ShellExecOptions & import('./types').SpecialAdbExecOptions} TExecOpts
462
- * @this {import('../adb.js').ADB}
463
- * @param {string|string[]} cmd - The array of rest command line parameters
464
- * or a single string parameter.
465
- * @param {TExecOpts} [opts] Additional options mapping. See
466
- * {@link https://github.com/appium/node-teen_process}
467
- * for more details.
468
- * You can also set the additional `exclusive` param
469
- * to `true` that assures no other parallel adb commands
470
- * are going to be executed while the current one is running
471
- * You can set the `outputFormat` param to `stdout` to receive just the stdout
472
- * output (default) or `full` to receive the stdout and stderr response from a
473
- * command with a zero exit code
474
- * @return {Promise<TExecOpts extends import('./types').TFullOutputOption ? import('teen_process').TeenProcessExecResult : string>}
475
- * Command's stdout or an object containing stdout and stderr.
476
- * @throws {Error} If the command returned non-zero exit code.
459
+ * @param cmd - Command string or array of command arguments
460
+ * @param opts - Execution options
461
+ * @returns Command output (string or ExecResult depending on outputFormat)
462
+ * @throws {Error} If command execution fails or timeout is exceeded
477
463
  */
478
464
  async function adbExec(cmd, opts) {
479
465
  if (!cmd) {
480
466
  throw new Error('You need to pass in a command to adbExec()');
481
467
  }
482
- const optsCopy = lodash_1.default.cloneDeep(opts) ?? /** @type {TExecOpts} */ ({});
468
+ const optsCopy = lodash_1.default.cloneDeep(opts ?? {});
483
469
  // setting default timeout for each command to prevent infinite wait.
484
- optsCopy.timeout = optsCopy.timeout || this.adbExecTimeout || helpers_1.DEFAULT_ADB_EXEC_TIMEOUT;
470
+ optsCopy.timeout = optsCopy.timeout || this.adbExecTimeout || helpers_js_1.DEFAULT_ADB_EXEC_TIMEOUT;
485
471
  optsCopy.timeoutCapName = optsCopy.timeoutCapName || 'adbExecTimeout'; // For error message
486
472
  const { outputFormat = this.EXEC_OUTPUT_FORMAT.STDOUT } = optsCopy;
487
473
  cmd = lodash_1.default.isArray(cmd) ? cmd : [cmd];
@@ -491,37 +477,37 @@ async function adbExec(cmd, opts) {
491
477
  const args = [...this.executable.defaultArgs, ...cmd];
492
478
  logger_js_1.log.debug(`Running '${this.executable.path} ` +
493
479
  (args.find((arg) => /\s+/.test(arg)) ? support_1.util.quote(args) : args.join(' ')) + `'`);
494
- let { stdout, stderr } = await (0, teen_process_1.exec)(this.executable.path, args, optsCopy);
480
+ const { stdout: rawStdout, stderr } = await (0, teen_process_1.exec)(this.executable.path, args, optsCopy);
495
481
  // sometimes ADB prints out weird stdout warnings that we don't want
496
482
  // to include in any of the response data, so let's strip it out
497
- stdout = stdout.replace(LINKER_WARNING_REGEXP, '').trim();
483
+ const stdout = rawStdout.replace(LINKER_WARNING_REGEXP, '').trim();
498
484
  return outputFormat === this.EXEC_OUTPUT_FORMAT.FULL ? { stdout, stderr } : stdout;
499
485
  }
500
486
  catch (e) {
501
- const errText = `${e.message}, ${e.stdout}, ${e.stderr}`;
487
+ const error = e;
488
+ const errText = `${error.message}, ${error.stdout}, ${error.stderr}`;
502
489
  if (ADB_RETRY_ERROR_PATTERNS.some((p) => p.test(errText))) {
503
490
  logger_js_1.log.info(`Error sending command, reconnecting device and retrying: ${cmd}`);
504
- await (0, asyncbox_1.sleep)(1000);
505
491
  await this.getDevicesWithRetry();
506
492
  // try again one time
507
- if (adbRetried) {
493
+ if (!adbRetried) {
508
494
  adbRetried = true;
509
495
  return await execFunc();
510
496
  }
511
497
  }
512
- if (e.code === 0 && e.stdout) {
513
- return e.stdout.replace(LINKER_WARNING_REGEXP, '').trim();
498
+ if (error.code === 0 && error.stdout) {
499
+ return error.stdout.replace(LINKER_WARNING_REGEXP, '').trim();
514
500
  }
515
- if (lodash_1.default.isNull(e.code)) {
516
- e.message = `Error executing adbExec. Original error: '${e.message}'. ` +
501
+ if (lodash_1.default.isNull(error.code)) {
502
+ error.message = `Error executing adbExec. Original error: '${error.message}'. ` +
517
503
  `Try to increase the ${optsCopy.timeout}ms adb execution timeout ` +
518
504
  `represented by '${optsCopy.timeoutCapName}' capability`;
519
505
  }
520
506
  else {
521
- e.message = `Error executing adbExec. Original error: '${e.message}'; ` +
522
- `Command output: ${e.stderr || e.stdout || '<empty>'}`;
507
+ error.message = `Error executing adbExec. Original error: '${error.message}'; ` +
508
+ `Command output: ${error.stderr || error.stdout || '<empty>'}`;
523
509
  }
524
- throw e;
510
+ throw error;
525
511
  }
526
512
  };
527
513
  if (isExecLocked) {
@@ -547,17 +533,13 @@ async function adbExec(cmd, opts) {
547
533
  /**
548
534
  * Execute the given command using _adb shell_ prefix.
549
535
  *
550
- * @this {import('../adb.js').ADB}
551
- * @template {import('./types').ShellExecOptions} TShellExecOpts
552
- * @param {string|string[]} cmd - The array of rest command line parameters or a single
553
- * string parameter.
554
- * @param {TShellExecOpts} [opts] - Additional options mapping.
555
- * @return {Promise<TShellExecOpts extends import('./types').TFullOutputOption ? import('teen_process').TeenProcessExecResult : string>}
556
- * Command's stdout.
557
- * @throws {Error} If the command returned non-zero exit code.
536
+ * @param cmd - Command string or array of command arguments
537
+ * @param opts - Execution options
538
+ * @returns Command output (string or ExecResult depending on outputFormat)
539
+ * @throws {Error} If command execution fails
558
540
  */
559
541
  async function shell(cmd, opts) {
560
- const { privileged, } = opts ?? /** @type {TShellExecOpts} */ ({});
542
+ const { privileged, } = opts ?? {};
561
543
  const cmdArr = lodash_1.default.isArray(cmd) ? cmd : [cmd];
562
544
  const fullCmd = ['shell'];
563
545
  if (privileged) {
@@ -576,10 +558,10 @@ async function shell(cmd, opts) {
576
558
  return await this.adbExec(fullCmd, opts);
577
559
  }
578
560
  /**
561
+ * Create a new ADB subprocess with the given arguments
579
562
  *
580
- * @this {import('../adb.js').ADB}
581
- * @param {string[]} [args=[]]
582
- * @returns {import('teen_process').SubProcess}
563
+ * @param args - Array of command arguments (default: empty array)
564
+ * @returns A SubProcess instance
583
565
  */
584
566
  function createSubProcess(args = []) {
585
567
  // add the default arguments
@@ -590,28 +572,27 @@ function createSubProcess(args = []) {
590
572
  /**
591
573
  * Retrieve the current adb port.
592
574
  * @todo can probably deprecate this now that the logic is just to read this.adbPort
575
+ * @deprecated Use this.adbPort instead
593
576
  *
594
- * @this {import('../adb.js').ADB}
595
- * @return {number} The current adb port number.
577
+ * @returns The ADB server port number
596
578
  */
597
579
  function getAdbServerPort() {
598
- return /** @type {number} */ (this.adbPort);
580
+ return this.adbPort;
599
581
  }
600
582
  /**
601
- * Retrieve the current emulator port from _adb devives_ output.
583
+ * Retrieve the current emulator port from _adb devices_ output.
602
584
  *
603
- * @this {import('../adb.js').ADB}
604
- * @return {Promise<number>} The current emulator port.
605
- * @throws {Error} If there are no connected devices.
585
+ * @returns The emulator port number
586
+ * @throws {Error} If no devices are connected or emulator port cannot be found
606
587
  */
607
588
  async function getEmulatorPort() {
608
589
  logger_js_1.log.debug('Getting running emulator port');
609
- if (this.emulatorPort !== null) {
610
- return /** @type {number} */ (this.emulatorPort);
590
+ if (!lodash_1.default.isNil(this.emulatorPort)) {
591
+ return this.emulatorPort;
611
592
  }
612
593
  try {
613
- let devices = await this.getConnectedDevices();
614
- let port = this.getPortFromEmulatorString(devices[0].udid);
594
+ const devices = await this.getConnectedDevices();
595
+ const port = this.getPortFromEmulatorString(devices[0].udid);
615
596
  if (port) {
616
597
  return port;
617
598
  }
@@ -620,38 +601,35 @@ async function getEmulatorPort() {
620
601
  }
621
602
  }
622
603
  catch (e) {
623
- throw new Error(`No devices connected. Original error: ${e.message}`);
604
+ const error = e;
605
+ throw new Error(`No devices connected. Original error: ${error.message}`);
624
606
  }
625
607
  }
626
608
  /**
627
609
  * Retrieve the current emulator port by parsing emulator name string.
628
610
  *
629
- * @this {import('../adb.js').ADB}
630
- * @param {string} emStr - Emulator name string.
631
- * @return {number|false} Either the current emulator port or
632
- * _false_ if port number cannot be parsed.
611
+ * @param emStr - The emulator string (e.g., 'emulator-5554')
612
+ * @returns The port number if found, false otherwise
633
613
  */
634
614
  function getPortFromEmulatorString(emStr) {
635
- let portPattern = /emulator-(\d+)/;
636
- if (portPattern.test(emStr)) {
637
- return parseInt(( /** @type {RegExpExecArray} */(portPattern.exec(emStr)))[1], 10);
638
- }
639
- return false;
615
+ const portPattern = /emulator-(\d+)/;
616
+ const match = portPattern.exec(emStr);
617
+ return match ? parseInt(match[1], 10) : false;
640
618
  }
641
619
  /**
642
620
  * Retrieve the list of currently connected emulators.
643
621
  *
644
- * @this {import('../adb.js').ADB}
645
- * @param {import('./types').ConnectedDevicesOptions} [opts={}] - Additional options mapping.
646
- * @return {Promise<import('./types').Device[]>} The list of connected devices.
622
+ * @param opts - Options for device retrieval
623
+ * @returns Array of connected emulator devices
624
+ * @throws {Error} If error occurs while getting emulators
647
625
  */
648
626
  async function getConnectedEmulators(opts = {}) {
649
627
  logger_js_1.log.debug('Getting connected emulators');
650
628
  try {
651
- let devices = await this.getConnectedDevices(opts);
652
- let emulators = [];
653
- for (let device of devices) {
654
- let port = this.getPortFromEmulatorString(device.udid);
629
+ const devices = await this.getConnectedDevices(opts);
630
+ const emulators = [];
631
+ for (const device of devices) {
632
+ const port = this.getPortFromEmulatorString(device.udid);
655
633
  if (port) {
656
634
  device.port = port;
657
635
  emulators.push(device);
@@ -661,14 +639,14 @@ async function getConnectedEmulators(opts = {}) {
661
639
  return emulators;
662
640
  }
663
641
  catch (e) {
664
- throw new Error(`Error getting emulators. Original error: ${e.message}`);
642
+ const error = e;
643
+ throw new Error(`Error getting emulators. Original error: ${error.message}`);
665
644
  }
666
645
  }
667
646
  /**
668
647
  * Set _emulatorPort_ property of the current class.
669
648
  *
670
- * @this {import('../adb.js').ADB}
671
- * @param {number} emPort - The emulator port to be set.
649
+ * @param emPort - The emulator port number
672
650
  */
673
651
  function setEmulatorPort(emPort) {
674
652
  this.emulatorPort = emPort;
@@ -676,13 +654,12 @@ function setEmulatorPort(emPort) {
676
654
  /**
677
655
  * Set the identifier of the current device (_this.curDeviceId_).
678
656
  *
679
- * @this {import('../adb.js').ADB}
680
- * @param {string} deviceId - The device identifier.
657
+ * @param deviceId - The device identifier
681
658
  */
682
659
  function setDeviceId(deviceId) {
683
660
  logger_js_1.log.debug(`Setting device id to ${deviceId}`);
684
661
  this.curDeviceId = deviceId;
685
- let argsHasDevice = this.executable.defaultArgs.indexOf('-s');
662
+ const argsHasDevice = this.executable.defaultArgs.indexOf('-s');
686
663
  if (argsHasDevice !== -1) {
687
664
  // remove the old device id from the arguments
688
665
  this.executable.defaultArgs.splice(argsHasDevice, 2);
@@ -690,10 +667,9 @@ function setDeviceId(deviceId) {
690
667
  this.executable.defaultArgs.push('-s', deviceId);
691
668
  }
692
669
  /**
693
- * Set the the current device object.
670
+ * Set the current device object.
694
671
  *
695
- * @this {import('../adb.js').ADB}
696
- * @param {import('./types').Device} deviceObj - The device object to be set.
672
+ * @param deviceObj - The device object containing udid and other properties
697
673
  */
698
674
  function setDevice(deviceObj) {
699
675
  const deviceId = deviceObj.udid;
@@ -709,9 +685,9 @@ function setDevice(deviceObj) {
709
685
  * `deviceId` (only if AVD with a matching name is found)
710
686
  * and `emulatorPort` instance properties.
711
687
  *
712
- * @this {import('../adb.js').ADB}
713
- * @param {string} avdName - Emulator name.
714
- * @return {Promise<import('./types').Device|null>} Currently running emulator or _null_.
688
+ * @param avdName - The name of the AVD to find
689
+ * @returns The device object if found, null otherwise
690
+ * @throws {Error} If error occurs while getting AVD
715
691
  */
716
692
  async function getRunningAVD(avdName) {
717
693
  logger_js_1.log.debug(`Trying to find '${avdName}' emulator`);
@@ -736,47 +712,47 @@ async function getRunningAVD(avdName) {
736
712
  return null;
737
713
  }
738
714
  catch (e) {
739
- throw new Error(`Error getting AVD. Original error: ${e.message}`);
715
+ const error = e;
716
+ throw new Error(`Error getting AVD. Original error: ${error.message}`);
740
717
  }
741
718
  }
742
719
  /**
743
- * Get the object for the currently running emulator.
720
+ * Get the object for the currently running emulator with retry.
744
721
  *
745
- * @this {import('../adb.js').ADB}
746
- * @param {string} avdName - Emulator name.
747
- * @param {number} [timeoutMs=20000] - The maximum number of milliseconds
748
- * to wait until at least one running AVD object
749
- * is detected.
750
- * @return {Promise<import('./types').Device|null>} Currently running emulator or _null_.
751
- * @throws {Error} If no device has been detected within the timeout.
722
+ * @param avdName - The name of the AVD to find
723
+ * @param timeoutMs - Maximum time to wait (default: 20000ms)
724
+ * @returns The device object if found, null otherwise
725
+ * @throws {Error} If error occurs while getting AVD with retry
752
726
  */
753
727
  async function getRunningAVDWithRetry(avdName, timeoutMs = 20000) {
754
728
  try {
755
- return /** @type {import('./types').Device|null} */ (await (0, asyncbox_1.waitForCondition)(async () => {
729
+ return await (0, asyncbox_1.waitForCondition)(async () => {
756
730
  try {
757
731
  return await this.getRunningAVD(avdName.replace('@', ''));
758
732
  }
759
733
  catch (e) {
760
- logger_js_1.log.debug(e.message);
734
+ const error = e;
735
+ logger_js_1.log.debug(error.message);
761
736
  return false;
762
737
  }
763
738
  }, {
764
739
  waitMs: timeoutMs,
765
740
  intervalMs: 1000,
766
- }));
741
+ });
767
742
  }
768
743
  catch (e) {
769
- throw new Error(`Error getting AVD with retry. Original error: ${e.message}`);
744
+ const error = e;
745
+ throw new Error(`Error getting AVD with retry. Original error: ${error.message}`);
770
746
  }
771
747
  }
772
748
  /**
773
749
  * Shutdown all running emulators by killing their processes.
774
750
  *
775
- * @this {import('../adb.js').ADB}
776
- * @throws {Error} If killing tool returned non-zero return code.
751
+ * @throws {Error} If error occurs while killing emulators
777
752
  */
778
753
  async function killAllEmulators() {
779
- let cmd, args;
754
+ let cmd;
755
+ let args;
780
756
  if (support_1.system.isWindows()) {
781
757
  cmd = 'TASKKILL';
782
758
  args = ['TASKKILL', '/IM', 'emulator.exe'];
@@ -789,20 +765,18 @@ async function killAllEmulators() {
789
765
  await (0, teen_process_1.exec)(cmd, args);
790
766
  }
791
767
  catch (e) {
792
- throw new Error(`Error killing emulators. Original error: ${e.message}`);
768
+ const error = e;
769
+ throw new Error(`Error killing emulators. Original error: ${error.message}`);
793
770
  }
794
771
  }
795
772
  /**
796
773
  * Kill emulator with the given name. No error
797
- * is thrown is given avd does not exist/is not running.
774
+ * is thrown if given avd does not exist/is not running.
798
775
  *
799
- * @this {import('../adb.js').ADB}
800
- * @param {string?} [avdName=null] - The name of the emulator to be killed. If empty,
801
- * the current emulator will be killed.
802
- * @param {number} [timeout=60000] - The amount of time to wait before throwing
803
- * an exception about unsuccessful killing
804
- * @return {Promise<boolean>} - True if the emulator was killed, false otherwise.
805
- * @throws {Error} if there was a failure by killing the emulator
776
+ * @param avdName - The name of the AVD to kill (null to kill current AVD)
777
+ * @param timeout - Maximum time to wait for emulator to be killed (default: 60000ms)
778
+ * @returns True if emulator was killed, false if it was not running
779
+ * @throws {Error} If emulator is still running after timeout
806
780
  */
807
781
  async function killEmulator(avdName = null, timeout = 60000) {
808
782
  if (support_1.util.hasValue(avdName)) {
@@ -830,8 +804,9 @@ async function killEmulator(avdName = null, timeout = 60000) {
830
804
  ? !await this.getRunningAVD(avdName)
831
805
  : !await this.isEmulatorConnected();
832
806
  }
833
- catch { }
834
- return false;
807
+ catch {
808
+ return false;
809
+ }
835
810
  }, {
836
811
  waitMs: timeout,
837
812
  intervalMs: 2000,
@@ -846,23 +821,22 @@ async function killEmulator(avdName = null, timeout = 60000) {
846
821
  /**
847
822
  * Start an emulator with given parameters and wait until it is fully started.
848
823
  *
849
- * @this {import('../adb.js').ADB}
850
- * @param {string} avdName - The name of an existing emulator.
851
- * @param {import('./types').AvdLaunchOptions} [opts={}]
852
- * @returns {Promise<SubProcess>} Emulator subprocess instance
853
- * @throws {Error} If the emulator fails to start within the given timeout.
824
+ * @param avdName - The name of the AVD to launch
825
+ * @param opts - Launch options
826
+ * @returns The SubProcess instance for the launched emulator
827
+ * @throws {Error} If emulator fails to launch or boot
854
828
  */
855
829
  async function launchAVD(avdName, opts = {}) {
856
830
  const { args = [], env = {}, language, country, launchTimeout = 60000, readyTimeout = 60000, retryTimes = 1, } = opts;
857
831
  logger_js_1.log.debug(`Launching Emulator with AVD ${avdName}, launchTimeout ` +
858
832
  `${launchTimeout}ms and readyTimeout ${readyTimeout}ms`);
859
833
  const emulatorBinaryPath = await this.getSdkBinaryPath('emulator');
860
- if (avdName.startsWith('@')) {
861
- avdName = avdName.slice(1);
834
+ let processedAvdName = avdName;
835
+ if (processedAvdName.startsWith('@')) {
836
+ processedAvdName = processedAvdName.slice(1);
862
837
  }
863
- await this.checkAvdExist(avdName);
864
- /** @type {string[]} */
865
- const launchArgs = ['-avd', avdName];
838
+ await this.checkAvdExist(processedAvdName);
839
+ const launchArgs = ['-avd', processedAvdName];
866
840
  launchArgs.push(...(toAvdLocaleArgs(language ?? null, country ?? null)));
867
841
  let isDelayAdbFeatureEnabled = false;
868
842
  if (this.allowDelayAdb) {
@@ -870,7 +844,7 @@ async function launchAVD(avdName, opts = {}) {
870
844
  if (revision && support_1.util.compareVersions(revision, '>=', '29.0.7')) {
871
845
  // https://androidstudio.googleblog.com/2019/05/emulator-2907-canary.html
872
846
  try {
873
- const { target } = await this.getEmuImageProperties(avdName);
847
+ const { target } = await this.getEmuImageProperties(processedAvdName);
874
848
  const apiMatch = /\d+/.exec(target);
875
849
  // https://issuetracker.google.com/issues/142533355
876
850
  if (apiMatch && parseInt(apiMatch[0], 10) >= MIN_DELAY_ADB_API_LEVEL) {
@@ -882,8 +856,9 @@ async function launchAVD(avdName, opts = {}) {
882
856
  }
883
857
  }
884
858
  catch (e) {
859
+ const error = e;
885
860
  logger_js_1.log.info(`The -delay-adb emulator startup detection feature will not be enabled. ` +
886
- `Original error: ${e.message}`);
861
+ `Original error: ${error.message}`);
887
862
  }
888
863
  }
889
864
  }
@@ -891,7 +866,7 @@ async function launchAVD(avdName, opts = {}) {
891
866
  logger_js_1.log.info('The -delay-adb emulator startup detection feature has been explicitly disabled');
892
867
  }
893
868
  if (!lodash_1.default.isEmpty(args)) {
894
- launchArgs.push(...(lodash_1.default.isArray(args) ? args : /** @type {string[]} */ (support_1.util.shellParse(`${args}`))));
869
+ launchArgs.push(...(lodash_1.default.isArray(args) ? args : support_1.util.shellParse(`${args}`)));
895
870
  }
896
871
  logger_js_1.log.debug(`Running '${emulatorBinaryPath}' with args: ${support_1.util.quote(launchArgs)}`);
897
872
  if (!lodash_1.default.isEmpty(env)) {
@@ -905,9 +880,9 @@ async function launchAVD(avdName, opts = {}) {
905
880
  proc.on(`line-${streamName}`, (line) => logger_js_1.log.debug(`[AVD OUTPUT] ${line}`));
906
881
  }
907
882
  proc.on('die', (code, signal) => {
908
- logger_js_1.log.warn(`Emulator avd ${avdName} exited with code ${code}${signal ? `, signal ${signal}` : ''}`);
883
+ logger_js_1.log.warn(`Emulator avd ${processedAvdName} exited with code ${code}${signal ? `, signal ${signal}` : ''}`);
909
884
  });
910
- await (0, asyncbox_1.retry)(retryTimes, async () => await this.getRunningAVDWithRetry(avdName, launchTimeout));
885
+ await (0, asyncbox_1.retry)(retryTimes, async () => await this.getRunningAVDWithRetry(processedAvdName, launchTimeout));
911
886
  // At this point we have deviceId already assigned
912
887
  const timer = new support_1.timing.Timer().start();
913
888
  if (isDelayAdbFeatureEnabled) {
@@ -915,7 +890,8 @@ async function launchAVD(avdName, opts = {}) {
915
890
  await this.adbExec(['wait-for-device'], { timeout: readyTimeout });
916
891
  }
917
892
  catch (e) {
918
- throw new Error(`'${avdName}' Emulator has failed to boot: ${e.stderr || e.message}`);
893
+ const error = e;
894
+ throw new Error(`'${processedAvdName}' Emulator has failed to boot: ${error.stderr || error.message}`);
919
895
  }
920
896
  }
921
897
  await this.waitForEmulatorReady(Math.trunc(readyTimeout - timer.getDuration().asMilliSeconds));
@@ -924,9 +900,8 @@ async function launchAVD(avdName, opts = {}) {
924
900
  /**
925
901
  * Get the adb version. The result of this method is cached.
926
902
  *
927
- * @this {import('../adb.js').ADB}
928
- * @return {Promise<import('./types').Version>}
929
- * @throws {Error} If it is not possible to parse adb binary version.
903
+ * @returns Version information object
904
+ * @throws {Error} If error occurs while getting adb version
930
905
  */
931
906
  exports.getVersion = lodash_1.default.memoize(async function getVersion() {
932
907
  let stdout;
@@ -934,20 +909,21 @@ exports.getVersion = lodash_1.default.memoize(async function getVersion() {
934
909
  stdout = await this.adbExec('version');
935
910
  }
936
911
  catch (e) {
937
- throw new Error(`Error getting adb version: ${e.stderr || e.message}`);
912
+ const error = e;
913
+ throw new Error(`Error getting adb version: ${error.stderr || error.message}`);
938
914
  }
939
915
  const result = {};
940
916
  const binaryVersionMatch = BINARY_VERSION_PATTERN.exec(stdout);
941
917
  if (binaryVersionMatch) {
942
918
  result.binary = {
943
- version: semver.coerce(binaryVersionMatch[1]),
919
+ version: semver.coerce(binaryVersionMatch[1])?.version || binaryVersionMatch[1],
944
920
  build: parseInt(binaryVersionMatch[2], 10),
945
921
  };
946
922
  }
947
923
  const bridgeVersionMatch = BRIDGE_VERSION_PATTERN.exec(stdout);
948
924
  if (bridgeVersionMatch) {
949
925
  result.bridge = {
950
- version: semver.coerce(bridgeVersionMatch[1]),
926
+ version: semver.coerce(bridgeVersionMatch[1])?.version || bridgeVersionMatch[1],
951
927
  };
952
928
  }
953
929
  return result;
@@ -955,14 +931,11 @@ exports.getVersion = lodash_1.default.memoize(async function getVersion() {
955
931
  /**
956
932
  * Check if the current emulator is ready to accept further commands (booting completed).
957
933
  *
958
- * @this {import('../adb.js').ADB}
959
- * @param {number} [timeoutMs=20000] - The maximum number of milliseconds to wait.
960
- * @returns {Promise<void>}
961
- * @throws {Error} If the emulator is not ready within the given timeout.
934
+ * @param timeoutMs - Maximum time to wait (default: 20000ms)
935
+ * @throws {Error} If emulator is not ready within the timeout period
962
936
  */
963
937
  async function waitForEmulatorReady(timeoutMs = 20000) {
964
938
  logger_js_1.log.debug(`Waiting up to ${timeoutMs}ms for the emulator to be ready`);
965
- /** @type {RegExp[]} */
966
939
  const requiredServicesRe = REQUIRED_SERVICES.map((name) => new RegExp(`\\b${name}:`));
967
940
  let services;
968
941
  const timer = new support_1.timing.Timer().start();
@@ -990,15 +963,17 @@ async function waitForEmulatorReady(timeoutMs = 20000) {
990
963
  }
991
964
  isBootCompleted = true;
992
965
  }
993
- services = await this.shell(['service', 'list']);
994
- if (!requiredServicesRe.every((pattern) => pattern.test(services))) {
995
- logger_js_1.log.debug(`Running services: ${services}`);
966
+ const servicesOutput = await this.shell(['service', 'list']);
967
+ services = servicesOutput;
968
+ if (!servicesOutput || !requiredServicesRe.every((pattern) => pattern.test(servicesOutput))) {
969
+ logger_js_1.log.debug(`Running services: ${servicesOutput}`);
996
970
  return false;
997
971
  }
998
972
  return true;
999
973
  }
1000
974
  catch (err) {
1001
- logger_js_1.log.debug(`Intermediate error: ${err.message}`);
975
+ const error = err;
976
+ logger_js_1.log.debug(`Intermediate error: ${error.message}`);
1002
977
  return false;
1003
978
  }
1004
979
  }, {
@@ -1008,9 +983,10 @@ async function waitForEmulatorReady(timeoutMs = 20000) {
1008
983
  }
1009
984
  catch {
1010
985
  let suffix = '';
1011
- if (services !== undefined) {
986
+ const servicesValue = services;
987
+ if (servicesValue) {
1012
988
  const missingServices = lodash_1.default.zip(REQUIRED_SERVICES, requiredServicesRe)
1013
- .filter(([, pattern]) => !( /** @type {RegExp} */(pattern)).test(services))
989
+ .filter(([, pattern]) => !pattern.test(servicesValue))
1014
990
  .map(([name]) => name);
1015
991
  suffix = ` (${missingServices} service${missingServices.length === 1 ? ' is' : 's are'} not running)`;
1016
992
  }
@@ -1025,14 +1001,12 @@ async function waitForEmulatorReady(timeoutMs = 20000) {
1025
1001
  /**
1026
1002
  * Check if the current device is ready to accept further commands (booting completed).
1027
1003
  *
1028
- * @this {import('../adb.js').ADB}
1029
- * @param {number} [appDeviceReadyTimeout=30] - The maximum number of seconds to wait.
1030
- * @throws {Error} If the device is not ready within the given timeout.
1004
+ * @param appDeviceReadyTimeout - Timeout in seconds (default: 30)
1005
+ * @throws {Error} If device is not ready within the timeout period
1031
1006
  */
1032
1007
  async function waitForDevice(appDeviceReadyTimeout = 30) {
1033
1008
  const timeoutMs = appDeviceReadyTimeout * 1000;
1034
- /** @type {Error|null} */
1035
- let lastError = null;
1009
+ let lastErrorMessage = null;
1036
1010
  try {
1037
1011
  await (0, asyncbox_1.waitForCondition)(async () => {
1038
1012
  try {
@@ -1041,7 +1015,8 @@ async function waitForDevice(appDeviceReadyTimeout = 30) {
1041
1015
  return true;
1042
1016
  }
1043
1017
  catch (e) {
1044
- lastError = e;
1018
+ const error = e;
1019
+ lastErrorMessage = error.message;
1045
1020
  try {
1046
1021
  try {
1047
1022
  await this.reconnect();
@@ -1051,7 +1026,9 @@ async function waitForDevice(appDeviceReadyTimeout = 30) {
1051
1026
  }
1052
1027
  await this.getConnectedDevices();
1053
1028
  }
1054
- catch { }
1029
+ catch {
1030
+ // Ignore errors during reconnection
1031
+ }
1055
1032
  return false;
1056
1033
  }
1057
1034
  }, {
@@ -1061,8 +1038,8 @@ async function waitForDevice(appDeviceReadyTimeout = 30) {
1061
1038
  }
1062
1039
  catch {
1063
1040
  let suffix = '';
1064
- if (lastError) {
1065
- suffix = ` Original error: ${ /** @type {Error} */(lastError).message}`;
1041
+ if (lastErrorMessage) {
1042
+ suffix = ` Original error: ${lastErrorMessage}`;
1066
1043
  }
1067
1044
  throw new Error(`The device is not ready after ${appDeviceReadyTimeout}s.${suffix}`);
1068
1045
  }
@@ -1070,9 +1047,8 @@ async function waitForDevice(appDeviceReadyTimeout = 30) {
1070
1047
  /**
1071
1048
  * Reboot the current device and wait until it is completed.
1072
1049
  *
1073
- * @this {import('../adb.js').ADB}
1074
- * @param {number} [retries=DEFAULT_ADB_REBOOT_RETRIES] - The maximum number of reboot retries.
1075
- * @throws {Error} If the device failed to reboot and number of retries is exceeded.
1050
+ * @param retries - Number of retry attempts (default: 90)
1051
+ * @throws {Error} If reboot fails or device is not ready after reboot
1076
1052
  */
1077
1053
  async function reboot(retries = DEFAULT_ADB_REBOOT_RETRIES) {
1078
1054
  // Get root access so we can run the next shell commands which require root access
@@ -1087,13 +1063,14 @@ async function reboot(retries = DEFAULT_ADB_REBOOT_RETRIES) {
1087
1063
  await this.shell(['start']);
1088
1064
  }
1089
1065
  catch (e) {
1090
- const { message } = e;
1066
+ const error = e;
1067
+ const { message } = error;
1091
1068
  // provide a helpful error message if the reason reboot failed was because ADB couldn't gain root access
1092
1069
  if (message.includes('must be root')) {
1093
1070
  throw new Error(`Could not reboot device. Rebooting requires root access and ` +
1094
1071
  `attempt to get root access on device failed with error: '${message}'`);
1095
1072
  }
1096
- throw e;
1073
+ throw error;
1097
1074
  }
1098
1075
  finally {
1099
1076
  // Return root state to what it was before
@@ -1115,9 +1092,8 @@ async function reboot(retries = DEFAULT_ADB_REBOOT_RETRIES) {
1115
1092
  /**
1116
1093
  * Switch adb server root privileges.
1117
1094
  *
1118
- * @this {import('../adb.js').ADB}
1119
- * @param {boolean} isElevated - Should we elevate to to root or unroot? (default true)
1120
- * @return {Promise<import('./types').RootResult>}
1095
+ * @param isElevated - True to enable root, false to disable
1096
+ * @returns Result object indicating success and whether device was already rooted
1121
1097
  */
1122
1098
  async function changeUserPrivileges(isElevated) {
1123
1099
  const cmd = isElevated ? 'root' : 'unroot';
@@ -1126,10 +1102,11 @@ async function changeUserPrivileges(isElevated) {
1126
1102
  return await cmdFunc();
1127
1103
  }
1128
1104
  catch (err) {
1105
+ const error = err;
1129
1106
  // Check the output of the stdErr to see if there's any clues that show that the device went offline
1130
1107
  // and if it did go offline, restart ADB
1131
1108
  if (['closed', 'device offline', 'timeout expired']
1132
- .some((x) => (err.stderr || '').toLowerCase().includes(x))) {
1109
+ .some((x) => (error.stderr || '').toLowerCase().includes(x))) {
1133
1110
  logger_js_1.log.warn(`Attempt to ${cmd} caused ADB to think the device went offline`);
1134
1111
  try {
1135
1112
  await this.reconnect();
@@ -1140,7 +1117,7 @@ async function changeUserPrivileges(isElevated) {
1140
1117
  return await cmdFunc();
1141
1118
  }
1142
1119
  else {
1143
- throw err;
1120
+ throw error;
1144
1121
  }
1145
1122
  }
1146
1123
  };
@@ -1166,7 +1143,8 @@ async function changeUserPrivileges(isElevated) {
1166
1143
  return { isSuccessful: true, wasAlreadyRooted };
1167
1144
  }
1168
1145
  catch (err) {
1169
- const { stderr = '', message } = err;
1146
+ const error = err;
1147
+ const { stderr = '', message } = error;
1170
1148
  logger_js_1.log.warn(`Unable to ${cmd} adb daemon. Original error: '${message}'. Stderr: '${stderr}'. Continuing.`);
1171
1149
  return { isSuccessful: false, wasAlreadyRooted };
1172
1150
  }
@@ -1174,8 +1152,7 @@ async function changeUserPrivileges(isElevated) {
1174
1152
  /**
1175
1153
  * Switch adb server to root mode
1176
1154
  *
1177
- * @this {import('../adb.js').ADB}
1178
- * @return {Promise<import('./types').RootResult>}
1155
+ * @returns Result object indicating success and whether device was already rooted
1179
1156
  */
1180
1157
  async function root() {
1181
1158
  return await this.changeUserPrivileges(true);
@@ -1183,8 +1160,7 @@ async function root() {
1183
1160
  /**
1184
1161
  * Switch adb server to non-root mode.
1185
1162
  *
1186
- * @this {import('../adb.js').ADB}
1187
- * @return {Promise<import('./types').RootResult>}
1163
+ * @returns Result object indicating success and whether device was already rooted
1188
1164
  */
1189
1165
  async function unroot() {
1190
1166
  return await this.changeUserPrivileges(false);
@@ -1192,10 +1168,7 @@ async function unroot() {
1192
1168
  /**
1193
1169
  * Checks whether the current user is root
1194
1170
  *
1195
- * @this {import('../adb.js').ADB}
1196
- * @return {Promise<boolean>} True if the user is root
1197
- * @throws {Error} if there was an error while identifying
1198
- * the user.
1171
+ * @returns True if current user is root, false otherwise
1199
1172
  */
1200
1173
  async function isRoot() {
1201
1174
  return (await this.shell(['whoami'])).trim() === 'root';
@@ -1209,11 +1182,8 @@ async function isRoot() {
1209
1182
  * Read https://github.com/appium/appium/issues/10964
1210
1183
  * for more details on this topic
1211
1184
  *
1212
- * @this {import('../adb.js').ADB}
1213
- * @param {Buffer|string} cert - base64-decoded content of the actual certificate
1214
- * represented as a string or a buffer
1215
- * @throws {Error} If openssl tool is not available on the destination system
1216
- * or if there was an error while installing the certificate
1185
+ * @param cert - Certificate as Buffer or base64-encoded string
1186
+ * @throws {Error} If certificate installation fails
1217
1187
  */
1218
1188
  async function installMitmCertificate(cert) {
1219
1189
  const openSsl = await getOpenSslForOs();
@@ -1245,10 +1215,11 @@ async function installMitmCertificate(cert) {
1245
1215
  await this.adbExec(['remount']);
1246
1216
  }
1247
1217
  catch (err) {
1218
+ const error = err;
1248
1219
  throw new Error(`Cannot inject the custom certificate. ` +
1249
1220
  `Is the certificate properly encoded into base64-string? ` +
1250
1221
  `Do you have root permissions on the device? ` +
1251
- `Original error: ${err.message}`);
1222
+ `Original error: ${error.message}`);
1252
1223
  }
1253
1224
  finally {
1254
1225
  await support_1.fs.rimraf(tmpRoot);
@@ -1257,12 +1228,9 @@ async function installMitmCertificate(cert) {
1257
1228
  /**
1258
1229
  * Verifies if the given root certificate is already installed on the device.
1259
1230
  *
1260
- * @this {import('../adb.js').ADB}
1261
- * @param {Buffer|string} cert - base64-decoded content of the actual certificate
1262
- * represented as a string or a buffer
1263
- * @throws {Error} If openssl tool is not available on the destination system
1264
- * or if there was an error while checking the certificate
1265
- * @returns {Promise<boolean>} true if the given certificate is already installed
1231
+ * @param cert - Certificate as Buffer or base64-encoded string
1232
+ * @returns True if certificate is installed, false otherwise
1233
+ * @throws {Error} If certificate hash cannot be retrieved
1266
1234
  */
1267
1235
  async function isMitmCertificateInstalled(cert) {
1268
1236
  const openSsl = await getOpenSslForOs();
@@ -1275,9 +1243,10 @@ async function isMitmCertificateInstalled(cert) {
1275
1243
  certHash = stdout.trim();
1276
1244
  }
1277
1245
  catch (err) {
1246
+ const error = err;
1278
1247
  throw new Error(`Cannot retrieve the certificate hash. ` +
1279
1248
  `Is the certificate properly encoded into base64-string? ` +
1280
- `Original error: ${err.message}`);
1249
+ `Original error: ${error.message}`);
1281
1250
  }
1282
1251
  finally {
1283
1252
  await support_1.fs.rimraf(tmpRoot);
@@ -1292,18 +1261,12 @@ async function isMitmCertificateInstalled(cert) {
1292
1261
  * there is a limit for a maximum length of a single adb command. that is why
1293
1262
  * we need all this complicated logic.
1294
1263
  *
1295
- * @this {import('../adb.js').ADB}
1296
- * @param {(x: string) => string[]} argTransformer A function, that receives single argument
1297
- * from the `args` array and transforms it into a shell command. The result
1298
- * of the function must be an array, where each item is a part of a single command.
1299
- * The last item of the array could be ';'. If this is not a semicolon then it is going to
1300
- * be added automatically.
1301
- * @param {string[]} args Array of argument values to create chunks for
1302
- * @throws {Error} If any of the chunks returns non-zero exit code after being executed
1264
+ * @param argTransformer - Function to transform each argument into command array
1265
+ * @param args - Array of arguments to process
1266
+ * @throws {Error} If argument transformer returns invalid result or command execution fails
1303
1267
  */
1304
1268
  async function shellChunks(argTransformer, args) {
1305
1269
  const commands = [];
1306
- /** @type {string[]} */
1307
1270
  let cmdChunk = [];
1308
1271
  for (const arg of args) {
1309
1272
  const nextCmd = argTransformer(arg);
@@ -1336,15 +1299,13 @@ async function shellChunks(argTransformer, args) {
1336
1299
  throw lastError;
1337
1300
  }
1338
1301
  }
1339
- // #region Private functions
1340
1302
  /**
1341
1303
  * Transforms the given language and country abbreviations
1342
1304
  * to AVD arguments array
1343
1305
  *
1344
- * @param {?string} language Language name, for example 'fr'
1345
- * @param {?string} country Country name, for example 'CA'
1346
- * @returns {Array<string>} The generated arguments. The
1347
- * resulting array might be empty if both arguments are empty
1306
+ * @param language - Language code (e.g., 'en', 'fr')
1307
+ * @param country - Country code (e.g., 'US', 'FR')
1308
+ * @returns Array of AVD locale arguments
1348
1309
  */
1349
1310
  function toAvdLocaleArgs(language, country) {
1350
1311
  const result = [];
@@ -1373,7 +1334,8 @@ function toAvdLocaleArgs(language, country) {
1373
1334
  * Retrieves full paths to all 'build-tools' subfolders under the particular
1374
1335
  * SDK root folder
1375
1336
  *
1376
- * @type {(sdkRoot: string) => Promise<string[]>}
1337
+ * @param sdkRoot - The Android SDK root directory path
1338
+ * @returns Array of build-tools directory paths (newest first)
1377
1339
  */
1378
1340
  exports.getBuildToolsDirs = lodash_1.default.memoize(async function getBuildToolsDirs(sdkRoot) {
1379
1341
  let buildToolsDirs = await support_1.fs.glob('*/', {
@@ -1387,34 +1349,19 @@ exports.getBuildToolsDirs = lodash_1.default.memoize(async function getBuildTool
1387
1349
  .map((pair) => pair[1]);
1388
1350
  }
1389
1351
  catch (err) {
1352
+ const error = err;
1390
1353
  logger_js_1.log.warn(`Cannot sort build-tools folders ${JSON.stringify(buildToolsDirs.map((dir) => path_1.default.basename(dir)))} ` +
1391
1354
  `by semantic version names.`);
1392
- logger_js_1.log.warn(`Falling back to sorting by modification date. Original error: ${err.message}`);
1393
- /** @type {[number, string][]} */
1355
+ logger_js_1.log.warn(`Falling back to sorting by modification date. Original error: ${error.message}`);
1394
1356
  const pairs = await bluebird_1.default.map(buildToolsDirs, async (dir) => [(await support_1.fs.stat(dir)).mtime.valueOf(), dir]);
1395
1357
  buildToolsDirs = pairs
1396
- // @ts-ignore This sorting works
1397
- .sort((a, b) => a[0] < b[0])
1358
+ .sort((a, b) => a[0] < b[0] ? 1 : -1)
1398
1359
  .map((pair) => pair[1]);
1399
1360
  }
1400
1361
  logger_js_1.log.info(`Found ${buildToolsDirs.length} 'build-tools' folders under '${sdkRoot}' (newest first):`);
1401
- for (let dir of buildToolsDirs) {
1362
+ for (const dir of buildToolsDirs) {
1402
1363
  logger_js_1.log.info(` ${dir}`);
1403
1364
  }
1404
1365
  return buildToolsDirs;
1405
1366
  });
1406
- /**
1407
- *
1408
- * @returns {Promise<string>}
1409
- */
1410
- async function getOpenSslForOs() {
1411
- const binaryName = `openssl${support_1.system.isWindows() ? '.exe' : ''}`;
1412
- try {
1413
- return await support_1.fs.which(binaryName);
1414
- }
1415
- catch {
1416
- throw new Error('The openssl tool must be installed on the system and available on the path');
1417
- }
1418
- }
1419
- // #endregion
1420
1367
  //# sourceMappingURL=system-calls.js.map