@appium/base-driver 10.2.0 → 10.2.2

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.
Files changed (138) hide show
  1. package/LICENSE +201 -0
  2. package/build/lib/basedriver/capabilities.js +7 -7
  3. package/build/lib/basedriver/capabilities.js.map +1 -1
  4. package/build/lib/basedriver/commands/event.d.ts +1 -1
  5. package/build/lib/basedriver/commands/event.d.ts.map +1 -1
  6. package/build/lib/basedriver/commands/execute.d.ts +1 -1
  7. package/build/lib/basedriver/commands/execute.d.ts.map +1 -1
  8. package/build/lib/basedriver/commands/find.d.ts +1 -1
  9. package/build/lib/basedriver/commands/find.d.ts.map +1 -1
  10. package/build/lib/basedriver/commands/mixin.d.ts +1 -1
  11. package/build/lib/basedriver/commands/mixin.d.ts.map +1 -1
  12. package/build/lib/basedriver/commands/timeout.d.ts +1 -1
  13. package/build/lib/basedriver/commands/timeout.d.ts.map +1 -1
  14. package/build/lib/basedriver/device-settings.d.ts +14 -23
  15. package/build/lib/basedriver/device-settings.d.ts.map +1 -1
  16. package/build/lib/basedriver/device-settings.js +11 -26
  17. package/build/lib/basedriver/device-settings.js.map +1 -1
  18. package/build/lib/basedriver/helpers.d.ts +36 -57
  19. package/build/lib/basedriver/helpers.d.ts.map +1 -1
  20. package/build/lib/basedriver/helpers.js +148 -239
  21. package/build/lib/basedriver/helpers.js.map +1 -1
  22. package/build/lib/basedriver/logger.d.ts +1 -2
  23. package/build/lib/basedriver/logger.d.ts.map +1 -1
  24. package/build/lib/basedriver/logger.js +2 -2
  25. package/build/lib/basedriver/logger.js.map +1 -1
  26. package/build/lib/basedriver/validation.d.ts.map +1 -1
  27. package/build/lib/basedriver/validation.js +3 -3
  28. package/build/lib/basedriver/validation.js.map +1 -1
  29. package/build/lib/constants.d.ts +1 -1
  30. package/build/lib/constants.d.ts.map +1 -1
  31. package/build/lib/express/crash.d.ts +8 -2
  32. package/build/lib/express/crash.d.ts.map +1 -1
  33. package/build/lib/express/crash.js +6 -0
  34. package/build/lib/express/crash.js.map +1 -1
  35. package/build/lib/express/express-logging.d.ts +12 -2
  36. package/build/lib/express/express-logging.d.ts.map +1 -1
  37. package/build/lib/express/express-logging.js +34 -26
  38. package/build/lib/express/express-logging.js.map +1 -1
  39. package/build/lib/express/idempotency.d.ts +4 -10
  40. package/build/lib/express/idempotency.d.ts.map +1 -1
  41. package/build/lib/express/idempotency.js +69 -73
  42. package/build/lib/express/idempotency.js.map +1 -1
  43. package/build/lib/express/logger.d.ts +1 -2
  44. package/build/lib/express/logger.d.ts.map +1 -1
  45. package/build/lib/express/logger.js +2 -2
  46. package/build/lib/express/logger.js.map +1 -1
  47. package/build/lib/express/middleware.d.ts +37 -41
  48. package/build/lib/express/middleware.d.ts.map +1 -1
  49. package/build/lib/express/middleware.js +48 -60
  50. package/build/lib/express/middleware.js.map +1 -1
  51. package/build/lib/express/server.d.ts +57 -101
  52. package/build/lib/express/server.d.ts.map +1 -1
  53. package/build/lib/express/server.js +51 -128
  54. package/build/lib/express/server.js.map +1 -1
  55. package/build/lib/express/static.d.ts +10 -5
  56. package/build/lib/express/static.d.ts.map +1 -1
  57. package/build/lib/express/static.js +32 -42
  58. package/build/lib/express/static.js.map +1 -1
  59. package/build/lib/express/websocket.d.ts +22 -6
  60. package/build/lib/express/websocket.d.ts.map +1 -1
  61. package/build/lib/express/websocket.js +10 -15
  62. package/build/lib/express/websocket.js.map +1 -1
  63. package/build/lib/helpers/capabilities.d.ts +4 -16
  64. package/build/lib/helpers/capabilities.d.ts.map +1 -1
  65. package/build/lib/helpers/capabilities.js +36 -48
  66. package/build/lib/helpers/capabilities.js.map +1 -1
  67. package/build/lib/jsonwp-proxy/protocol-converter.d.ts +42 -78
  68. package/build/lib/jsonwp-proxy/protocol-converter.d.ts.map +1 -1
  69. package/build/lib/jsonwp-proxy/protocol-converter.js +87 -139
  70. package/build/lib/jsonwp-proxy/protocol-converter.js.map +1 -1
  71. package/build/lib/jsonwp-proxy/proxy.d.ts +1 -1
  72. package/build/lib/jsonwp-proxy/proxy.d.ts.map +1 -1
  73. package/build/lib/jsonwp-proxy/proxy.js +2 -2
  74. package/build/lib/jsonwp-proxy/proxy.js.map +1 -1
  75. package/build/lib/jsonwp-status/status.d.ts +113 -158
  76. package/build/lib/jsonwp-status/status.d.ts.map +1 -1
  77. package/build/lib/jsonwp-status/status.js +10 -14
  78. package/build/lib/jsonwp-status/status.js.map +1 -1
  79. package/build/lib/protocol/bidi-commands.d.ts +31 -36
  80. package/build/lib/protocol/bidi-commands.d.ts.map +1 -1
  81. package/build/lib/protocol/bidi-commands.js +5 -5
  82. package/build/lib/protocol/bidi-commands.js.map +1 -1
  83. package/build/lib/protocol/errors.d.ts.map +1 -1
  84. package/build/lib/protocol/helpers.d.ts +7 -11
  85. package/build/lib/protocol/helpers.d.ts.map +1 -1
  86. package/build/lib/protocol/helpers.js +5 -9
  87. package/build/lib/protocol/helpers.js.map +1 -1
  88. package/build/lib/protocol/index.d.ts +4 -21
  89. package/build/lib/protocol/index.d.ts.map +1 -1
  90. package/build/lib/protocol/index.js.map +1 -1
  91. package/build/lib/protocol/protocol.d.ts +15 -1
  92. package/build/lib/protocol/protocol.d.ts.map +1 -1
  93. package/build/lib/protocol/protocol.js +50 -20
  94. package/build/lib/protocol/protocol.js.map +1 -1
  95. package/build/lib/protocol/routes.d.ts +8 -15
  96. package/build/lib/protocol/routes.d.ts.map +1 -1
  97. package/build/lib/protocol/routes.js +18 -33
  98. package/build/lib/protocol/routes.js.map +1 -1
  99. package/lib/basedriver/capabilities.ts +1 -1
  100. package/lib/basedriver/commands/event.ts +2 -2
  101. package/lib/basedriver/commands/execute.ts +2 -2
  102. package/lib/basedriver/commands/find.ts +2 -2
  103. package/lib/basedriver/commands/mixin.ts +1 -1
  104. package/lib/basedriver/commands/timeout.ts +2 -2
  105. package/lib/basedriver/{device-settings.js → device-settings.ts} +24 -35
  106. package/lib/basedriver/{helpers.js → helpers.ts} +208 -266
  107. package/lib/basedriver/logger.ts +3 -0
  108. package/lib/basedriver/validation.ts +2 -2
  109. package/lib/constants.ts +1 -1
  110. package/lib/express/crash.ts +15 -0
  111. package/lib/express/express-logging.ts +84 -0
  112. package/lib/express/{idempotency.js → idempotency.ts} +105 -89
  113. package/lib/express/logger.ts +3 -0
  114. package/lib/express/middleware.ts +187 -0
  115. package/lib/express/{server.js → server.ts} +175 -167
  116. package/lib/express/static.ts +77 -0
  117. package/lib/express/websocket.ts +81 -0
  118. package/lib/helpers/capabilities.ts +83 -0
  119. package/lib/jsonwp-proxy/protocol-converter.ts +284 -0
  120. package/lib/jsonwp-proxy/proxy.js +1 -1
  121. package/lib/jsonwp-status/{status.js → status.ts} +12 -15
  122. package/lib/protocol/{bidi-commands.js → bidi-commands.ts} +7 -5
  123. package/lib/protocol/errors.ts +1 -1
  124. package/lib/protocol/{helpers.js → helpers.ts} +8 -11
  125. package/lib/protocol/protocol.ts +57 -26
  126. package/lib/protocol/{routes.js → routes.ts} +29 -40
  127. package/package.json +11 -11
  128. package/tsconfig.json +3 -1
  129. package/lib/basedriver/logger.js +0 -4
  130. package/lib/express/crash.js +0 -11
  131. package/lib/express/express-logging.js +0 -60
  132. package/lib/express/logger.js +0 -4
  133. package/lib/express/middleware.js +0 -171
  134. package/lib/express/static.js +0 -76
  135. package/lib/express/websocket.js +0 -79
  136. package/lib/helpers/capabilities.js +0 -93
  137. package/lib/jsonwp-proxy/protocol-converter.js +0 -317
  138. /package/lib/protocol/{index.js → index.ts} +0 -0
@@ -14,13 +14,13 @@ import B from 'bluebird';
14
14
  import {formatResponseValue, ensureW3cResponse} from './helpers';
15
15
  import {MAX_LOG_BODY_LENGTH, PROTOCOLS, DEFAULT_BASE_PATH} from '../constants';
16
16
  import {isW3cCaps} from '../helpers/capabilities';
17
- import log from '../basedriver/logger';
18
- import { generateDriverLogPrefix } from '../basedriver/helpers';
19
- import type { Core, AppiumLogger, PayloadParams, MethodMap, Driver, DriverMethodDef } from '@appium/types';
20
- import type { BaseDriver } from '../basedriver/driver';
21
- import type { Request, Response, Application } from 'express';
22
- import type { MultidimensionalReadonlyArray } from 'type-fest';
23
- import type { RouteConfiguringFunction } from '../express/server';
17
+ import {log} from '../basedriver/logger';
18
+ import {generateDriverLogPrefix} from '../basedriver/helpers';
19
+ import type {Core, AppiumLogger, PayloadParams, MethodMap, Driver, DriverMethodDef} from '@appium/types';
20
+ import type {BaseDriver} from '../basedriver/driver';
21
+ import type {Request, Response, Application} from 'express';
22
+ import type {MultidimensionalReadonlyArray} from 'type-fest';
23
+ import type {RouteConfiguringFunction} from '../express/server';
24
24
 
25
25
  export const CREATE_SESSION_COMMAND = 'createSession';
26
26
  export const DELETE_SESSION_COMMAND = 'deleteSession';
@@ -34,6 +34,34 @@ export function determineProtocol(createSessionArgs: any[]): keyof typeof PROTOC
34
34
  return _.some(createSessionArgs, isW3cCaps) ? PROTOCOLS.W3C : PROTOCOLS.MJSONWP;
35
35
  }
36
36
 
37
+ /**
38
+ * Extract and validate the sessionId from the Express route parameter.
39
+ * Express may return route params as string | string[] | undefined.
40
+ * Appium uses standard routes (e.g., /session/:sessionId) which should always be strings.
41
+ * Only `*` such as `/session/*sessionId` can return `string[]`.
42
+ * Then, this method will return the first element as the session id.
43
+ * It may break existing appium routing handling also, thus this method will log
44
+ * received parameters as well to help debugging.
45
+ * @param driver Running driver
46
+ * @param req The request in Express
47
+ * @returns The normalized sessionId (string or undefined)
48
+ */
49
+ export function getSessionId(driver: Core<any>, req: Request): string | undefined {
50
+ if (Array.isArray(req.params.sessionId)) {
51
+ const sessionId = req.params.sessionId[0];
52
+ getLogger(driver, sessionId).warn(
53
+ `Received malformed sessionId as array from the route: ${req.originalUrl}. ` +
54
+ `This indicates the route definition issue. The route should start with '/session/:sessionId' (named parameter) ` +
55
+ `instead of '/session/*sessionId' (wildcard). ` +
56
+ `Using the first element as session id: ${sessionId}. ` +
57
+ `Please fix the route definition to prevent this error.`
58
+ );
59
+ // This is to not log the message multiple times.
60
+ req.params.sessionId = sessionId;
61
+ return sessionId;
62
+ }
63
+ return req.params.sessionId;
64
+ }
37
65
 
38
66
  function extractProtocol(driver: Core<any>, sessionId: string | null = null): keyof typeof PROTOCOLS {
39
67
  const dstDriver = _.isFunction(driver.driverForSession) && sessionId
@@ -310,13 +338,14 @@ function buildHandler(
310
338
  let httpResBody = {} as any;
311
339
  let httpStatus = 200;
312
340
  let newSessionId: string | undefined;
313
- let currentProtocol = extractProtocol(driver, req.params.sessionId);
341
+ const sessionId = getSessionId(driver, req);
342
+ let currentProtocol = extractProtocol(driver, sessionId);
314
343
 
315
344
  try {
316
345
  // if the route accessed is deprecated, log a warning
317
346
  if (spec.deprecated && spec.command && !deprecatedCommandsLogged.has(spec.command)) {
318
347
  deprecatedCommandsLogged.add(spec.command);
319
- getLogger(driver, req.params.sessionId).warn(
348
+ getLogger(driver, sessionId).warn(
320
349
  `The ${method} ${path} endpoint has been deprecated and will be removed in a future ` +
321
350
  `version of Appium or your driver/plugin. Please use a different endpoint or contact the ` +
322
351
  `driver/plugin author to add explicit support for the endpoint before it is removed`
@@ -325,7 +354,7 @@ function buildHandler(
325
354
 
326
355
  // if this is a session command but we don't have a session,
327
356
  // error out early (especially before proxying)
328
- if (isSessCmd && !driver.sessionExists(req.params.sessionId)) {
357
+ if (isSessCmd && !driver.sessionExists(sessionId)) {
329
358
  throw new errors.NoSuchDriverError();
330
359
  }
331
360
 
@@ -341,12 +370,12 @@ function buildHandler(
341
370
  if (isSessCmd && !spec.neverProxy && spec.command && driverShouldDoJwpProxy(driver, req, spec.command)) {
342
371
  if (
343
372
  !('pluginsToHandleCmd' in driver) || !_.isFunction(driver.pluginsToHandleCmd) ||
344
- driver.pluginsToHandleCmd(spec.command, req.params.sessionId).length === 0
373
+ driver.pluginsToHandleCmd(spec.command, sessionId).length === 0
345
374
  ) {
346
375
  await doJwpProxy(driver as BaseDriver<any>, req, res);
347
376
  return;
348
377
  }
349
- getLogger(driver, req.params.sessionId).debug(
378
+ getLogger(driver, sessionId).debug(
350
379
  `Would have proxied ` +
351
380
  `command directly, but a plugin exists which might require its value, so will let ` +
352
381
  `its value be collected internally and made part of plugin chain`
@@ -393,7 +422,7 @@ function buildHandler(
393
422
  }
394
423
 
395
424
  // run the driver command wrapped inside the argument validators
396
- getLogger(driver, req.params.sessionId).debug(
425
+ getLogger(driver, sessionId).debug(
397
426
  `Calling %s.%s() with args: %s`,
398
427
  driver.constructor.name, spec.command,
399
428
  logger.markSensitive(_.truncate(JSON.stringify(args), {length: MAX_LOG_BODY_LENGTH}))
@@ -409,7 +438,7 @@ function buildHandler(
409
438
  driverRes = await (driver as BaseDriver<any>).executeCommand(spec.command, ...args);
410
439
 
411
440
  // Get the protocol after executeCommand
412
- currentProtocol = extractProtocol(driver, req.params.sessionId) || currentProtocol;
441
+ currentProtocol = extractProtocol(driver, sessionId) || currentProtocol;
413
442
 
414
443
  // If `executeCommand` was overridden and the method returns an object
415
444
  // with a protocol and value/error property, re-assign the protocol
@@ -440,12 +469,12 @@ function buildHandler(
440
469
 
441
470
  // delete should not return anything even if successful
442
471
  if (spec.command === DELETE_SESSION_COMMAND) {
443
- getLogger(driver, req.params.sessionId).debug(
472
+ getLogger(driver, sessionId).debug(
444
473
  `Received response: ${_.truncate(JSON.stringify(driverRes), {
445
474
  length: MAX_LOG_BODY_LENGTH,
446
475
  })}`
447
476
  );
448
- getLogger(driver, req.params.sessionId).debug('But deleting session, so not returning');
477
+ getLogger(driver, sessionId).debug('But deleting session, so not returning');
449
478
  driverRes = null;
450
479
  }
451
480
 
@@ -467,7 +496,7 @@ function buildHandler(
467
496
  }
468
497
 
469
498
  httpResBody.value = driverRes;
470
- getLogger(driver, req.params.sessionId || newSessionId).debug(
499
+ getLogger(driver, sessionId || newSessionId).debug(
471
500
  `Responding ` +
472
501
  `to client with driver.${spec.command}() result: ${_.truncate(JSON.stringify(driverRes), {
473
502
  length: MAX_LOG_BODY_LENGTH,
@@ -480,7 +509,7 @@ function buildHandler(
480
509
  if (err instanceof Error || (_.has(err, 'stack') && _.has(err, 'message'))) {
481
510
  actualErr = err;
482
511
  } else {
483
- getLogger(driver, req.params.sessionId || newSessionId).warn(
512
+ getLogger(driver, sessionId || newSessionId).warn(
484
513
  'The thrown error object does not seem to be a valid instance of the Error class. This ' +
485
514
  'might be a genuine bug of a driver or a plugin.'
486
515
  );
@@ -488,7 +517,7 @@ function buildHandler(
488
517
  }
489
518
 
490
519
  currentProtocol =
491
- currentProtocol || extractProtocol(driver, req.params.sessionId || newSessionId);
520
+ currentProtocol || extractProtocol(driver, sessionId || newSessionId);
492
521
 
493
522
  let errMsg = err.stacktrace || err.stack;
494
523
  if (!_.includes(errMsg, err.message)) {
@@ -499,7 +528,7 @@ function buildHandler(
499
528
  if (isErrorType(err, errors.ProxyRequestError)) {
500
529
  actualErr = err.getActualError();
501
530
  } else {
502
- getLogger(driver, req.params.sessionId || newSessionId).debug(
531
+ getLogger(driver, sessionId || newSessionId).debug(
503
532
  `Encountered internal error running command: ${errMsg}`
504
533
  );
505
534
  }
@@ -525,9 +554,10 @@ function buildHandler(
525
554
  });
526
555
  }
527
556
 
528
- export function driverShouldDoJwpProxy(driver: Core<any>, req: import('express').Request, command: string): boolean {
557
+ export function driverShouldDoJwpProxy(driver: Core<any>, req: Request, command: string): boolean {
558
+ const sessionId = getSessionId(driver, req);
529
559
  // drivers need to explicitly say when the proxy is active
530
- if (!driver.proxyActive(req.params.sessionId)) {
560
+ if (!driver.proxyActive(sessionId)) {
531
561
  return false;
532
562
  }
533
563
 
@@ -539,7 +569,7 @@ export function driverShouldDoJwpProxy(driver: Core<any>, req: import('express')
539
569
 
540
570
  // validate avoidance schema, and say we shouldn't proxy if anything in the
541
571
  // avoid list matches our req
542
- if (driver.proxyRouteIsAvoided(req.params.sessionId, req.method, req.originalUrl, req.body)) {
572
+ if (driver.proxyRouteIsAvoided(sessionId as string, req.method, req.originalUrl, req.body)) {
543
573
  return false;
544
574
  }
545
575
 
@@ -547,16 +577,17 @@ export function driverShouldDoJwpProxy(driver: Core<any>, req: import('express')
547
577
  }
548
578
 
549
579
  async function doJwpProxy(driver: BaseDriver<any>, req: Request, res: Response): Promise<void> {
550
- getLogger(driver, req.params.sessionId).info(
580
+ const sessionId = getSessionId(driver, req) as string;
581
+ getLogger(driver, sessionId).info(
551
582
  'Driver proxy active, passing request on via HTTP proxy'
552
583
  );
553
584
 
554
585
  // check that the inner driver has a proxy function
555
- if (!driver.canProxy(req.params.sessionId)) {
586
+ if (!driver.canProxy(sessionId)) {
556
587
  throw new Error('Trying to proxy to a server but the driver is unable to proxy');
557
588
  }
558
589
  try {
559
- await driver.executeCommand('proxyReqRes', req, res, req.params.sessionId);
590
+ await driver.executeCommand('proxyReqRes', req, res, sessionId);
560
591
  } catch (err) {
561
592
  if (isErrorType(err, errors.ProxyRequestError)) {
562
593
  throw err;
@@ -1,20 +1,19 @@
1
+ import type {Driver, DriverMethodDef, HTTPMethod, MethodMap} from '@appium/types';
1
2
  import _ from 'lodash';
2
3
  import {DEFAULT_BASE_PATH} from '../constants';
3
4
  import {match} from 'path-to-regexp';
4
5
  import {LRUCache} from 'lru-cache';
5
6
 
6
- /** @type {LRUCache<string, string>} */
7
- const COMMAND_NAMES_CACHE = new LRUCache({
7
+ const COMMAND_NAMES_CACHE = new LRUCache<string, string>({
8
8
  max: 1024,
9
9
  });
10
10
 
11
11
  /**
12
- * define the routes, mapping of HTTP methods to particular driver commands, and
13
- * any parameters that are expected in a request parameters can be `required` or
14
- * `optional`
15
- * @satisfies {import('@appium/types').MethodMap<import('../basedriver/driver').BaseDriver>}
12
+ * Define the routes: mapping of HTTP methods to particular driver commands, and
13
+ * any parameters that are expected in a request. Parameters can be `required` or
14
+ * `optional`.
16
15
  */
17
- export const METHOD_MAP = /** @type {const} */ ({
16
+ export const METHOD_MAP = {
18
17
 
19
18
  // #region W3C WebDriver
20
19
  // https://www.w3.org/TR/webdriver2/
@@ -566,42 +565,40 @@ export const METHOD_MAP = /** @type {const} */ ({
566
565
  DELETE: {command: 'deleteVirtualPressureSource'},
567
566
  },
568
567
  // #endregion
569
- });
568
+ } as const satisfies MethodMap<Driver>;
570
569
 
571
570
  // driver command names
572
571
  export const ALL_COMMANDS = _.flatMap(_.values(METHOD_MAP).map(_.values))
573
572
  .filter((m) => Boolean(m.command))
574
573
  .map((m) => m.command);
575
574
 
576
- /**
577
- *
578
- * @param {string} endpoint
579
- * @param {import('@appium/types').HTTPMethod} [method]
580
- * @param {string} [basePath=DEFAULT_BASE_PATH]
581
- * @returns {string|undefined}
582
- */
583
- export function routeToCommandName(endpoint, method, basePath = DEFAULT_BASE_PATH) {
584
- let normalizedEndpoint = basePath
585
- ? endpoint.replace(new RegExp(`^${_.escapeRegExp(basePath)}`), '')
575
+ export function routeToCommandName(
576
+ endpoint: string,
577
+ method?: HTTPMethod,
578
+ basePath?: string
579
+ ): string | undefined {
580
+ const resolvedBasePath = basePath ?? DEFAULT_BASE_PATH;
581
+ let normalizedEndpoint = resolvedBasePath
582
+ ? endpoint.replace(new RegExp(`^${_.escapeRegExp(resolvedBasePath)}`), '')
586
583
  : endpoint;
587
584
  normalizedEndpoint = `${_.startsWith(normalizedEndpoint, '/') ? '' : '/'}${normalizedEndpoint}`;
588
- /** @type {string} */
589
- let normalizedPathname;
585
+ let normalizedPathname: string;
590
586
  try {
591
587
  // we could use any prefix there as we anyway need to only extract the pathname
592
588
  normalizedPathname = new URL(`https://appium.io${normalizedEndpoint}`).pathname;
593
- } catch (err) {
594
- throw new Error(`'${endpoint}' cannot be translated to a command name: ${err.message}`);
589
+ } catch (err: unknown) {
590
+ const msg = err instanceof Error ? err.message : String(err);
591
+ throw new Error(`'${endpoint}' cannot be translated to a command name: ${msg}`);
595
592
  }
596
593
 
597
- const normalizedMethod = _.toUpper(method);
594
+ const normalizedMethod = _.toUpper(method ?? '');
598
595
  const cacheKey = toCommandNameCacheKey(normalizedPathname, normalizedMethod);
599
- if (COMMAND_NAMES_CACHE.has(cacheKey)) {
600
- return COMMAND_NAMES_CACHE.get(cacheKey) || undefined;
596
+ const cached = COMMAND_NAMES_CACHE.get(cacheKey);
597
+ if (cached !== undefined) {
598
+ return cached || undefined;
601
599
  }
602
600
 
603
- /** @type {string[]} */
604
- const possiblePathnames = [];
601
+ const possiblePathnames: string[] = [];
605
602
  if (!normalizedPathname.startsWith('/session/')) {
606
603
  possiblePathnames.push(`/session/any-session-id${normalizedPathname}`);
607
604
  }
@@ -609,12 +606,10 @@ export function routeToCommandName(endpoint, method, basePath = DEFAULT_BASE_PAT
609
606
  for (const [routePath, routeSpec] of _.toPairs(METHOD_MAP)) {
610
607
  const routeMatcher = match(routePath);
611
608
  if (possiblePathnames.some((pp) => routeMatcher(pp))) {
612
- const commandForAnyMethod = () => _.first(
613
- (_.keys(routeSpec) ?? []).map((key) => routeSpec[key]?.command)
614
- );
615
- const commandName = normalizedMethod
616
- ? routeSpec?.[normalizedMethod]?.command
617
- : commandForAnyMethod();
609
+ const spec = routeSpec as Record<string, DriverMethodDef<Driver>>;
610
+ const commandForAnyMethod = () =>
611
+ _.first(_.keys(spec).map((key) => spec[key]?.command));
612
+ const commandName = normalizedMethod ? spec[normalizedMethod]?.command : commandForAnyMethod();
618
613
  if (commandName) {
619
614
  COMMAND_NAMES_CACHE.set(cacheKey, commandName);
620
615
  return commandName;
@@ -626,13 +621,7 @@ export function routeToCommandName(endpoint, method, basePath = DEFAULT_BASE_PAT
626
621
  COMMAND_NAMES_CACHE.set(cacheKey, '');
627
622
  }
628
623
 
629
- /**
630
- *
631
- * @param {string} endpoint
632
- * @param {string} [method]
633
- * @returns {string}
634
- */
635
- function toCommandNameCacheKey(endpoint, method) {
624
+ function toCommandNameCacheKey(endpoint: string, method?: string): string {
636
625
  return `${endpoint}:${method ?? ''}`;
637
626
  }
638
627
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@appium/base-driver",
3
- "version": "10.2.0",
3
+ "version": "10.2.2",
4
4
  "description": "Base driver class for Appium drivers",
5
5
  "keywords": [
6
6
  "automation",
@@ -39,30 +39,30 @@
39
39
  ],
40
40
  "scripts": {
41
41
  "test": "run-p test:unit test:types",
42
- "test:e2e": "mocha --exit --timeout 20s --slow 10s \"./test/e2e/**/*.spec.js\"",
42
+ "test:e2e": "mocha --exit --timeout 20s --slow 10s \"./test/e2e/**/*.spec.ts\"",
43
43
  "test:smoke": "node ./index.js",
44
- "test:unit": "mocha \"./test/unit/**/*.spec.js\"",
45
- "test:types": "tsd"
44
+ "test:unit": "mocha \"./test/unit/**/*.spec.ts\"",
45
+ "test:types": "tsd && tsc -p tsconfig.test.json"
46
46
  },
47
47
  "dependencies": {
48
- "@appium/support": "^7.0.5",
49
- "@appium/types": "^1.2.0",
48
+ "@appium/support": "^7.0.6",
49
+ "@appium/types": "^1.2.1",
50
50
  "@colors/colors": "1.6.0",
51
51
  "async-lock": "1.4.1",
52
- "asyncbox": "6.0.1",
53
- "axios": "1.13.3",
52
+ "asyncbox": "6.1.0",
53
+ "axios": "1.13.6",
54
54
  "bluebird": "3.7.2",
55
55
  "body-parser": "2.2.2",
56
56
  "express": "5.2.1",
57
57
  "fastest-levenshtein": "1.0.16",
58
58
  "http-status-codes": "2.3.0",
59
59
  "lodash": "4.17.23",
60
- "lru-cache": "11.2.5",
60
+ "lru-cache": "11.2.6",
61
61
  "method-override": "3.0.0",
62
62
  "morgan": "1.10.1",
63
63
  "path-to-regexp": "8.3.0",
64
64
  "serve-favicon": "2.5.1",
65
- "type-fest": "5.4.1"
65
+ "type-fest": "5.4.4"
66
66
  },
67
67
  "optionalDependencies": {
68
68
  "spdy": "4.0.2"
@@ -74,7 +74,7 @@
74
74
  "publishConfig": {
75
75
  "access": "public"
76
76
  },
77
- "gitHead": "f7b20335eab4022e5cbbb627ec86866a994444f8",
77
+ "gitHead": "c745352c6500937a4590d1ef6ef19785143a8870",
78
78
  "tsd": {
79
79
  "directory": "test/types"
80
80
  }
package/tsconfig.json CHANGED
@@ -7,8 +7,10 @@
7
7
  "@appium/types": ["../types"],
8
8
  "@appium/driver-test-support": ["../driver-test-support"]
9
9
  },
10
- "checkJs": true
10
+ "checkJs": true,
11
+ "types": ["node", "mocha"]
11
12
  },
12
13
  "include": ["lib"],
14
+ "exclude": ["build"],
13
15
  "references": [{"path": "../support"}, {"path": "../types"}]
14
16
  }
@@ -1,4 +0,0 @@
1
- import {logger} from '@appium/support';
2
-
3
- const log = logger.getLogger('BaseDriver');
4
- export default log;
@@ -1,11 +0,0 @@
1
- import {errors} from '../protocol';
2
-
3
- function produceError() {
4
- throw new errors.UnknownCommandError('Produced generic error for testing');
5
- }
6
-
7
- function produceCrash() {
8
- throw new Error('We just tried to crash Appium!');
9
- }
10
-
11
- export {produceError, produceCrash};
@@ -1,60 +0,0 @@
1
- import _ from 'lodash';
2
- import '@colors/colors';
3
- import morgan from 'morgan';
4
- import log from './logger';
5
- import {MAX_LOG_BODY_LENGTH} from '../constants';
6
- import {logger} from '@appium/support';
7
-
8
- // Copied the morgan compile function over so that cooler formats
9
- // may be configured
10
- function compile(fmt) {
11
- // escape quotes
12
- fmt = fmt.replace(/"/g, '\\"');
13
- fmt = fmt.replace(/:([-\w]{2,})(?:\[([^\]]+)\])?/g, function replace(_, name, arg) {
14
- return `"\n + (tokens["${name}"](req, res, "${arg}") || "-") + "`;
15
- });
16
- let js = ` return "${fmt}";`;
17
- return new Function('tokens, req, res', js);
18
- }
19
-
20
- function requestEndLoggingFormat(tokens, req, res) {
21
- let status = res.statusCode;
22
- let statusStr = ':status';
23
- if (status >= 500) {
24
- statusStr = statusStr.red;
25
- } else if (status >= 400) {
26
- statusStr = statusStr.yellow;
27
- } else if (status >= 300) {
28
- statusStr = statusStr.cyan;
29
- } else {
30
- statusStr = statusStr.green;
31
- }
32
- let fn = compile(
33
- `${'<-- :method :url '.white}${statusStr} ${':response-time ms - :res[content-length]'.grey}`
34
- );
35
- return fn(tokens, req, res);
36
- }
37
-
38
- const endLogFormatter = morgan((tokens, req, res) => {
39
- log.info(requestEndLoggingFormat(tokens, req, res), (res.jsonResp || '').grey);
40
- });
41
-
42
- const requestStartLoggingFormat = compile(`${'-->'.white} ${':method'.white} ${':url'.white}`);
43
-
44
- const startLogFormatter = morgan(
45
- (tokens, req, res) => {
46
- // morgan output is redirected straight to winston
47
- let reqBody = '';
48
- if (req.body) {
49
- try {
50
- reqBody = _.truncate(_.isString(req.body) ? req.body : JSON.stringify(req.body), {
51
- length: MAX_LOG_BODY_LENGTH,
52
- });
53
- } catch {}
54
- }
55
- log.info(requestStartLoggingFormat(tokens, req, res), logger.markSensitive(reqBody.grey));
56
- },
57
- {immediate: true}
58
- );
59
-
60
- export {endLogFormatter, startLogFormatter};
@@ -1,4 +0,0 @@
1
- import {logger} from '@appium/support';
2
-
3
- const log = logger.getLogger('HTTP');
4
- export default log;
@@ -1,171 +0,0 @@
1
- import _ from 'lodash';
2
- import log from './logger';
3
- import {errors} from '../protocol';
4
- export {handleIdempotency} from './idempotency';
5
- import {match} from 'path-to-regexp';
6
- import {util} from '@appium/support';
7
- import {calcSignature} from '../helpers/session';
8
- import {getResponseForW3CError} from '../protocol/errors';
9
-
10
- const SESSION_ID_PATTERN = /\/session\/([^/]+)/;
11
-
12
- /**
13
- *
14
- * @param {import('express').Request} req
15
- * @param {import('express').Response} res
16
- * @param {import('express').NextFunction} next
17
- * @returns {any}
18
- */
19
- export function allowCrossDomain(req, res, next) {
20
- res.header('Access-Control-Allow-Origin', '*');
21
- res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, OPTIONS, DELETE');
22
- res.header(
23
- 'Access-Control-Allow-Headers',
24
- 'Cache-Control, Pragma, Origin, X-Requested-With, Content-Type, Accept, User-Agent'
25
- );
26
-
27
- // need to respond 200 to OPTIONS
28
- if ('OPTIONS' === req.method) {
29
- return res.sendStatus(200);
30
- }
31
- next();
32
- }
33
-
34
- /**
35
- * @param {string} basePath
36
- * @returns {import('express').RequestHandler}
37
- */
38
- export function allowCrossDomainAsyncExecute(basePath) {
39
- return (req, res, next) => {
40
- // there are two paths for async responses, so cover both
41
- // https://regex101.com/r/txYiEz/1
42
- const receiveAsyncResponseRegExp = new RegExp(
43
- `${_.escapeRegExp(basePath)}/session/[a-f0-9-]+/(appium/)?receive_async_response`
44
- );
45
- if (!receiveAsyncResponseRegExp.test(req.url)) {
46
- return next();
47
- }
48
- allowCrossDomain(req, res, next);
49
- };
50
- }
51
-
52
- /**
53
- *
54
- * @param {import('express').Request} req
55
- * @param {import('express').Response} res
56
- * @param {import('express').NextFunction} next
57
- * @returns {any}
58
- */
59
- export function handleLogContext(req, res, next) {
60
- const requestId = fetchHeaderValue(req, 'x-request-id') || util.uuidV4();
61
-
62
- const sessionId = SESSION_ID_PATTERN.exec(req.url)?.[1];
63
- const sessionInfo = sessionId ? {sessionId, sessionSignature: calcSignature(sessionId)} : {};
64
- const isSensitiveHeaderValue = fetchHeaderValue(req, 'x-appium-is-sensitive');
65
-
66
- log.updateAsyncContext({
67
- requestId,
68
- ...sessionInfo,
69
- isSensitive: ['true', '1', 'yes'].includes(_.toLower(isSensitiveHeaderValue)),
70
- }, true);
71
-
72
- return next();
73
- }
74
-
75
- /**
76
- *
77
- * @param {import('express').Request} req
78
- * @param {import('express').Response} res
79
- * @param {import('express').NextFunction} next
80
- * @returns {any}
81
- */
82
- export function defaultToJSONContentType(req, res, next) {
83
- if (!req.headers['content-type']) {
84
- req.headers['content-type'] = 'application/json; charset=utf-8';
85
- }
86
- next();
87
- }
88
-
89
- /**
90
- * Core function to handle WebSocket upgrade requests by matching the request path
91
- * against registered WebSocket handlers in the webSocketsMapping.
92
- *
93
- * @param {import('http').IncomingMessage} req - The HTTP request
94
- * @param {import('stream').Duplex} socket - The network socket
95
- * @param {Buffer} head - The first packet of the upgraded stream
96
- * @param {import('@appium/types').StringRecord<import('@appium/types').WSServer>} webSocketsMapping - Mapping of paths to WebSocket servers
97
- * @returns {boolean} - Returns true if the upgrade was handled, false otherwise
98
- */
99
- export function tryHandleWebSocketUpgrade(req, socket, head, webSocketsMapping) {
100
- if (_.toLower(req.headers?.upgrade) !== 'websocket') {
101
- return false;
102
- }
103
-
104
- let currentPathname;
105
- try {
106
- currentPathname = new URL(req.url ?? '', 'http://localhost').pathname;
107
- } catch {
108
- currentPathname = req.url ?? '';
109
- }
110
- for (const [pathname, wsServer] of _.toPairs(webSocketsMapping)) {
111
- if (match(pathname)(currentPathname)) {
112
- wsServer.handleUpgrade(req, socket, head, (ws) => {
113
- wsServer.emit('connection', ws, req);
114
- });
115
- return true;
116
- }
117
- }
118
- log.info(`Did not match the websocket upgrade request at ${currentPathname} to any known route`);
119
- return false;
120
- }
121
-
122
- /**
123
- *
124
- * @param {import('@appium/types').StringRecord<import('@appium/types').WSServer>} webSocketsMapping
125
- * @returns {import('express').RequestHandler}
126
- */
127
- export function handleUpgrade(webSocketsMapping) {
128
- return (req, res, next) => {
129
- if (tryHandleWebSocketUpgrade(req, req.socket, Buffer.from(''), webSocketsMapping)) {
130
- return;
131
- }
132
- next();
133
- };
134
- }
135
-
136
- /**
137
- * @param {Error} err
138
- * @param {import('express').Request} req
139
- * @param {import('express').Response} res
140
- * @param {import('express').NextFunction} next
141
- */
142
- export function catchAllHandler(err, req, res, next) {
143
- if (res.headersSent) {
144
- return next(err);
145
- }
146
-
147
- log.error(`Uncaught error: ${err.message}`);
148
- const [status, body] = getResponseForW3CError(err);
149
- res.status(status).json(body);
150
- }
151
-
152
- /**
153
- * @param {import('express').Request} req
154
- * @param {import('express').Response} res
155
- */
156
- export function catch404Handler(req, res) {
157
- log.debug(`No route found for ${req.url}`);
158
- const [status, body] = getResponseForW3CError(new errors.UnknownCommandError());
159
- res.status(status).json(body);
160
- }
161
-
162
- /**
163
- * @param {import('express').Request} req
164
- * @param {string} name
165
- * @returns {string | undefined}
166
- */
167
- function fetchHeaderValue(req, name) {
168
- return _.isArray(req.headers[name])
169
- ? req.headers[name][0]
170
- : req.headers[name];
171
- }