@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
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../lib/basedriver/helpers.js"],"names":[],"mappings":"AA2DA;;;;;;;;;;;;GAYG;AACH,kCAJW,MAAM,YACN,MAAM,GAAC,MAAM,EAAE,GAAC,OAAO,eAAe,EAAE,mBAAmB,GACzD,OAAO,CAAC,MAAM,CAAC,CA4L3B;AAED;;;GAGG;AACH,uCAHW,MAAM,GACJ,OAAO,CAInB;AAED;;;;;;;;;GASG;AACH,qCAJW,GAAC,4CA2BX;AAED;;;;;;GAMG;AACH,yCAHW,MAAM,GAAC,MAAM,EAAE,GACb,MAAM,EAAE,CAuBpB;AAED;;;;;;;GAOG;AAEH,6CANW,MAAM,cACN,MAAM,OAAC,GAEL,MAAM,CAKlB;;;;;;;;;;;kBAwRa,IAAI,OAAC;eACL,OAAO;YACP,MAAM,OAAC;UACP,MAAM,OAAC;;;;;;;;;YAKP,MAAM;;;;YACN,OAAO,QAAQ,EAAE,QAAQ;;;;aACzB,OAAO,OAAO,EAAE,uBAAuB,GAAG,OAAO,OAAO,EAAE,oBAAoB"}
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../lib/basedriver/helpers.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EACV,mBAAmB,EAKpB,MAAM,eAAe,CAAC;AAKvB,eAAO,MAAgB,cAAc,QAAqC,CAAC;AAkD3E;;;;;;;;;;;GAWG;AACH,wBAAsB,YAAY,CAChC,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,MAAM,GAAG,MAAM,EAAE,GAAG,mBAA+C,GAC3E,OAAO,CAAC,MAAM,CAAC,CAyLjB;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAEtD;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,CAAC,CAuBjF;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,MAAM,EAAE,CAqBpE;AAED;;;;;;GAMG;AAEH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAM9F;;;;;;;;AAkND,wBAM0B"}
@@ -11,7 +11,7 @@ exports.parseCapsArray = parseCapsArray;
11
11
  exports.generateDriverLogPrefix = generateDriverLogPrefix;
12
12
  const lodash_1 = __importDefault(require("lodash"));
13
13
  const node_path_1 = __importDefault(require("node:path"));
14
- const logger_1 = __importDefault(require("./logger"));
14
+ const logger_1 = require("./logger");
15
15
  const support_1 = require("@appium/support");
16
16
  const lru_cache_1 = require("lru-cache");
17
17
  const async_lock_1 = __importDefault(require("async-lock"));
@@ -26,13 +26,12 @@ const DEFAULT_REQ_HEADERS = Object.freeze({
26
26
  'user-agent': `Appium (BaseDriver v${exports.BASEDRIVER_VER})`,
27
27
  });
28
28
  const AVG_DOWNLOAD_SPEED_MEASUREMENT_THRESHOLD_SEC = 2;
29
- /** @type {LRUCache<string, import('@appium/types').CachedAppInfo>} */
30
29
  const APPLICATIONS_CACHE = new lru_cache_1.LRUCache({
31
30
  max: MAX_CACHED_APPS,
32
31
  ttl: CACHED_APPS_MAX_AGE_MS, // expire after 24 hours
33
32
  updateAgeOnGet: true,
34
33
  dispose: ({ fullPath }, app) => {
35
- logger_1.default.info(`The application '${app}' cached at '${fullPath}' has ` +
34
+ logger_1.log.info(`The application '${app}' cached at '${fullPath}' has ` +
36
35
  `expired after ${CACHED_APPS_MAX_AGE_MS}ms`);
37
36
  if (fullPath) {
38
37
  support_1.fs.rimraf(fullPath);
@@ -49,40 +48,40 @@ process.on('exit', () => {
49
48
  return;
50
49
  }
51
50
  const appPaths = [...APPLICATIONS_CACHE.values()].map(({ fullPath }) => fullPath);
52
- logger_1.default.debug(`Performing cleanup of ${appPaths.length} cached ` +
53
- support_1.util.pluralize('application', appPaths.length));
51
+ logger_1.log.debug(`Performing cleanup of ${support_1.util.pluralize('cached application', appPaths.length, true)}`);
54
52
  for (const appPath of appPaths) {
53
+ if (!appPath) {
54
+ continue;
55
+ }
55
56
  try {
56
- // @ts-ignore it's defined
57
57
  support_1.fs.rimrafSync(appPath);
58
58
  }
59
59
  catch (e) {
60
- logger_1.default.warn(e.message);
60
+ logger_1.log.warn(e.message);
61
61
  }
62
62
  }
63
63
  });
64
64
  /**
65
- * Perform initial application package configuration
66
- * to prepare it for the further consumption by a driver:
67
- *
68
- * - Manages caching logic
69
- * - Downloads the app from a remote URL to the local filesystem
70
- * - Determines package name
71
- * - Checks basic requirements on the application package
65
+ * Performs initial application package configuration so the app is ready for driver use.
66
+ * Resolves local paths, downloads remote apps (http/https) with optional caching, and
67
+ * runs optional post-process or custom download hooks.
72
68
  *
73
- * @param {string} app
74
- * @param {string|string[]|import('@appium/types').ConfigureAppOptions} options
75
- * @returns {Promise<string>}
69
+ * @param app - Path to a local app or URL of a downloadable app (http/https).
70
+ * @param options - Supported extensions and optional hooks. Either a single extension
71
+ * string, an array of extension strings, or {@link ConfigureAppOptions} (e.g.
72
+ * `supportedExtensions`, `onPostProcess`, `onDownload`).
73
+ * @returns Resolved path to the application (local path or path to downloaded/cached app).
74
+ * @throws {Error} If supported extensions are missing, the app path/URL is invalid, or download fails.
76
75
  */
77
- async function configureApp(app, options = /** @type {import('@appium/types').ConfigureAppOptions} */ ({})) {
76
+ async function configureApp(app, options = {}) {
78
77
  if (!lodash_1.default.isString(app)) {
79
78
  // immediately shortcircuit if not given an app
80
79
  return '';
81
80
  }
82
- /** @type {string[]} */
83
81
  let supportedAppExtensions;
84
- const onPostProcess = !lodash_1.default.isString(options) && !lodash_1.default.isArray(options) ? options.onPostProcess : undefined;
85
- const onDownload = !lodash_1.default.isString(options) && !lodash_1.default.isArray(options) ? options.onDownload : undefined;
82
+ const opts = !lodash_1.default.isString(options) && !lodash_1.default.isArray(options) ? options : undefined;
83
+ const onPostProcess = opts?.onPostProcess;
84
+ const onDownload = opts?.onDownload;
86
85
  if (lodash_1.default.isString(options)) {
87
86
  supportedAppExtensions = [options];
88
87
  }
@@ -90,18 +89,18 @@ async function configureApp(app, options = /** @type {import('@appium/types').Co
90
89
  supportedAppExtensions = options;
91
90
  }
92
91
  else if (lodash_1.default.isPlainObject(options)) {
93
- supportedAppExtensions = options.supportedExtensions;
92
+ supportedAppExtensions = options.supportedExtensions ?? [];
93
+ }
94
+ else {
95
+ supportedAppExtensions = [];
94
96
  }
95
- // @ts-ignore this is OK
96
97
  if (lodash_1.default.isEmpty(supportedAppExtensions)) {
97
98
  throw new Error(`One or more supported app extensions must be provided`);
98
99
  }
99
100
  let newApp = app;
100
101
  const originalAppLink = app;
101
102
  let packageHash = null;
102
- /** @type {import('axios').AxiosResponse['headers']|undefined} */
103
103
  let headers;
104
- /** @type {RemoteAppProps} */
105
104
  const remoteAppProps = {
106
105
  lastModified: null,
107
106
  immutable: false,
@@ -112,7 +111,7 @@ async function configureApp(app, options = /** @type {import('@appium/types').Co
112
111
  const isUrl = isSupportedUrl(app);
113
112
  if (!isUrl && !node_path_1.default.isAbsolute(newApp)) {
114
113
  newApp = node_path_1.default.resolve(process.cwd(), newApp);
115
- logger_1.default.warn(`The current application path '${app}' is not absolute ` +
114
+ logger_1.log.warn(`The current application path '${app}' is not absolute ` +
116
115
  `and has been rewritten to '${newApp}'. Consider using absolute paths rather than relative`);
117
116
  app = newApp;
118
117
  }
@@ -120,64 +119,68 @@ async function configureApp(app, options = /** @type {import('@appium/types').Co
120
119
  return await APPLICATIONS_CACHE_GUARD.acquire(appCacheKey, async () => {
121
120
  const cachedAppInfo = APPLICATIONS_CACHE.get(appCacheKey);
122
121
  if (cachedAppInfo) {
123
- logger_1.default.debug(`Cached app data: ${JSON.stringify(cachedAppInfo, null, 2)}`);
122
+ logger_1.log.debug(`Cached app data: ${JSON.stringify(cachedAppInfo, null, 2)}`);
124
123
  }
125
124
  if (isUrl) {
126
125
  // Use the app from remote URL
127
- logger_1.default.info(`Using downloadable app '${newApp}'`);
128
- const reqHeaders = {
129
- ...DEFAULT_REQ_HEADERS,
130
- };
126
+ logger_1.log.info(`Using downloadable app '${newApp}'`);
127
+ const reqHeaders = { ...DEFAULT_REQ_HEADERS };
131
128
  if (cachedAppInfo?.etag) {
132
129
  reqHeaders['if-none-match'] = cachedAppInfo.etag;
133
130
  }
134
131
  else if (cachedAppInfo?.lastModified) {
135
132
  reqHeaders['if-modified-since'] = cachedAppInfo.lastModified.toUTCString();
136
133
  }
137
- logger_1.default.debug(`Request headers: ${JSON.stringify(reqHeaders)}`);
138
- let { headers, stream, status } = await queryAppLink(newApp, reqHeaders);
139
- logger_1.default.debug(`Response status: ${status}`);
134
+ logger_1.log.debug(`Request headers: ${JSON.stringify(reqHeaders)}`);
135
+ let result = await queryAppLink(newApp, reqHeaders);
136
+ headers = result.headers;
137
+ let { stream, status } = result;
138
+ logger_1.log.debug(`Response status: ${status}`);
140
139
  try {
141
140
  if (!lodash_1.default.isEmpty(headers)) {
142
141
  if (headers.etag) {
143
- logger_1.default.debug(`Etag: ${headers.etag}`);
142
+ logger_1.log.debug(`Etag: ${headers.etag}`);
144
143
  remoteAppProps.etag = headers.etag;
145
144
  }
146
145
  if (headers['last-modified']) {
147
- logger_1.default.debug(`Last-Modified: ${headers['last-modified']}`);
146
+ logger_1.log.debug(`Last-Modified: ${headers['last-modified']}`);
148
147
  remoteAppProps.lastModified = new Date(headers['last-modified']);
149
148
  }
150
149
  if (headers['cache-control']) {
151
- logger_1.default.debug(`Cache-Control: ${headers['cache-control']}`);
152
- remoteAppProps.immutable = /\bimmutable\b/i.test(headers['cache-control']);
153
- const maxAgeMatch = /\bmax-age=(\d+)\b/i.exec(headers['cache-control']);
150
+ logger_1.log.debug(`Cache-Control: ${headers['cache-control']}`);
151
+ remoteAppProps.immutable = /\bimmutable\b/i.test(String(headers['cache-control']));
152
+ const maxAgeMatch = /\bmax-age=(\d+)\b/i.exec(String(headers['cache-control']));
154
153
  if (maxAgeMatch) {
155
154
  remoteAppProps.maxAge = parseInt(maxAgeMatch[1], 10);
156
155
  }
157
156
  }
158
157
  }
159
158
  if (cachedAppInfo && status === HTTP_STATUS_NOT_MODIFIED) {
160
- if (await isAppIntegrityOk(/** @type {string} */ (cachedAppInfo.fullPath), cachedAppInfo.integrity)) {
161
- logger_1.default.info(`Reusing previously downloaded application at '${cachedAppInfo.fullPath}'`);
162
- return verifyAppExtension(/** @type {string} */ (cachedAppInfo.fullPath), supportedAppExtensions);
159
+ const cachedPath = cachedAppInfo.fullPath ?? '';
160
+ if (cachedPath && (await isAppIntegrityOk(cachedPath, cachedAppInfo.integrity))) {
161
+ logger_1.log.info(`Reusing previously downloaded application at '${cachedPath}'`);
162
+ return verifyAppExtension(cachedPath, supportedAppExtensions);
163
163
  }
164
- logger_1.default.info(`The application at '${cachedAppInfo.fullPath}' does not exist anymore ` +
164
+ logger_1.log.info(`The application at '${cachedAppInfo.fullPath}' does not exist anymore ` +
165
165
  `or its integrity has been damaged. Deleting it from the internal cache`);
166
166
  APPLICATIONS_CACHE.delete(appCacheKey);
167
167
  if (!stream.closed) {
168
168
  stream.destroy();
169
169
  }
170
- ({ stream, headers, status } = await queryAppLink(newApp, { ...DEFAULT_REQ_HEADERS }));
170
+ result = await queryAppLink(newApp, { ...DEFAULT_REQ_HEADERS });
171
+ stream = result.stream;
172
+ headers = result.headers;
173
+ status = result.status;
171
174
  }
172
175
  if (onDownload) {
173
176
  newApp = await onDownload({
174
177
  url: originalAppLink,
175
- headers: /** @type {import('@appium/types').HTTPHeaders} */ (lodash_1.default.clone(headers)),
178
+ headers: lodash_1.default.clone(headers),
176
179
  stream,
177
180
  });
178
181
  }
179
182
  else {
180
- const fileName = determineFilename(headers, pathname, supportedAppExtensions);
183
+ const fileName = determineFilename(headers, pathname ?? '', supportedAppExtensions);
181
184
  newApp = await fetchApp(stream, await support_1.tempDir.path({
182
185
  prefix: fileName,
183
186
  suffix: '',
@@ -192,7 +195,7 @@ async function configureApp(app, options = /** @type {import('@appium/types').Co
192
195
  }
193
196
  else if (await support_1.fs.exists(newApp)) {
194
197
  // Use the local app
195
- logger_1.default.info(`Using local app '${newApp}'`);
198
+ logger_1.log.info(`Using local app '${newApp}'`);
196
199
  }
197
200
  else {
198
201
  let errorMessage = `The application at '${newApp}' does not exist or is not accessible`;
@@ -208,7 +211,6 @@ async function configureApp(app, options = /** @type {import('@appium/types').Co
208
211
  if (isPackageAFile) {
209
212
  packageHash = await calculateFileIntegrity(newApp);
210
213
  }
211
- /** @type {(appPathToCache: string) => Promise<string>} */
212
214
  const storeAppInCache = async (appPathToCache) => {
213
215
  const cachedFullPath = cachedAppInfo?.fullPath;
214
216
  if (cachedFullPath && cachedFullPath !== appPathToCache) {
@@ -231,14 +233,14 @@ async function configureApp(app, options = /** @type {import('@appium/types').Co
231
233
  return appPathToCache;
232
234
  };
233
235
  if (lodash_1.default.isFunction(onPostProcess)) {
234
- const result = await onPostProcess(
235
- /** @type {import('@appium/types').PostProcessOptions<import('axios').AxiosResponseHeaders>} */ ({
236
+ const postProcessArg = {
236
237
  cachedAppInfo: lodash_1.default.clone(cachedAppInfo),
237
238
  isUrl,
238
239
  originalAppLink,
239
240
  headers: lodash_1.default.clone(headers),
240
241
  appPath: newApp,
241
- }));
242
+ };
243
+ const result = await onPostProcess(postProcessArg);
242
244
  return !result?.appPath || app === result?.appPath || !(await support_1.fs.exists(result?.appPath))
243
245
  ? newApp
244
246
  : await storeAppInCache(result.appPath);
@@ -250,21 +252,23 @@ async function configureApp(app, options = /** @type {import('@appium/types').Co
250
252
  });
251
253
  }
252
254
  /**
253
- * @param {string} app
254
- * @returns {boolean}
255
+ * Returns whether the given string looks like a package or bundle identifier
256
+ * (e.g. `com.example.app` or `org.company.AnotherApp`).
257
+ *
258
+ * @param app - Value to check (e.g. app path or bundle id).
259
+ * @returns `true` if the value matches a dot-separated identifier pattern.
255
260
  */
256
261
  function isPackageOrBundle(app) {
257
262
  return /^([a-zA-Z0-9\-_]+\.[a-zA-Z0-9\-_]+)+$/.test(app);
258
263
  }
259
264
  /**
260
- * Finds all instances 'firstKey' and create a duplicate with the key 'secondKey',
261
- * Do the same thing in reverse. If we find 'secondKey', create a duplicate with the key 'firstKey'.
265
+ * Recursively ensures both keys exist with the same value in objects and arrays.
266
+ * For each object, if `firstKey` exists its value is also set at `secondKey`, and vice versa.
262
267
  *
263
- * This will cause keys to be overwritten if the object contains 'firstKey' and 'secondKey'.
264
-
265
- * @param {*} input Any type of input
266
- * @param {String} firstKey The first key to duplicate
267
- * @param {String} secondKey The second key to duplicate
268
+ * @param input - Object, array, or primitive to process (arrays/objects traversed recursively).
269
+ * @param firstKey - First key name to mirror.
270
+ * @param secondKey - Second key name to mirror.
271
+ * @returns A deep copy of `input` with both keys present where objects had either key.
268
272
  */
269
273
  function duplicateKeys(input, firstKey, secondKey) {
270
274
  // If array provided, recursively call on all elements
@@ -274,7 +278,7 @@ function duplicateKeys(input, firstKey, secondKey) {
274
278
  // If object, create duplicates for keys and then recursively call on values
275
279
  if (lodash_1.default.isPlainObject(input)) {
276
280
  const resultObj = {};
277
- for (let [key, value] of lodash_1.default.toPairs(input)) {
281
+ for (const [key, value] of lodash_1.default.toPairs(input)) {
278
282
  const recursivelyCalledValue = duplicateKeys(value, firstKey, secondKey);
279
283
  if (key === firstKey) {
280
284
  resultObj[secondKey] = recursivelyCalledValue;
@@ -290,11 +294,12 @@ function duplicateKeys(input, firstKey, secondKey) {
290
294
  return input;
291
295
  }
292
296
  /**
293
- * Takes a capability value and tries to JSON.parse it as an array,
294
- * and either returns the parsed array or a singleton array.
297
+ * Normalizes a capability value to a string array. If already an array, returns it;
298
+ * if a string, parses as JSON array when possible, otherwise returns a single-element array.
295
299
  *
296
- * @param {string|string[]} capValue Capability value
297
- * @returns {string[]}
300
+ * @param capValue - Capability value: string (including JSON array like `"[\"a\",\"b\"]"`) or string[].
301
+ * @returns Array of strings.
302
+ * @throws {TypeError} If value is not a string/array or JSON parsing fails for array-like input.
298
303
  */
299
304
  function parseCapsArray(capValue) {
300
305
  if (lodash_1.default.isArray(capValue)) {
@@ -311,7 +316,7 @@ function parseCapsArray(capValue) {
311
316
  if (lodash_1.default.isString(capValue) && lodash_1.default.startsWith(lodash_1.default.trimStart(capValue), '[')) {
312
317
  throw new TypeError(message);
313
318
  }
314
- logger_1.default.warn(message);
319
+ logger_1.log.warn(message);
315
320
  }
316
321
  if (lodash_1.default.isString(capValue)) {
317
322
  return [capValue];
@@ -319,25 +324,69 @@ function parseCapsArray(capValue) {
319
324
  throw new TypeError(`Expected a string or a valid JSON array; received '${capValue}'`);
320
325
  }
321
326
  /**
322
- * Generate a string that uniquely describes driver instance
327
+ * Builds a short log prefix for a driver instance (e.g. `UiAutomator2@a1b2`).
323
328
  *
324
- * @param {object} obj driver instance
325
- * @param {string?} [sessionId=null] session identifier (if exists).
326
- * This parameter is deprecated and is not used.
327
- * @returns {string}
329
+ * @param obj - Driver or other object; its constructor name and a short id are used.
330
+ * @param _sessionId - Deprecated and unused; kept for {@link DriverHelpers} interface compatibility.
331
+ * @returns Prefix string like `DriverName@xxxx`, or `UnknownDriver@????` if `obj` is null.
328
332
  */
329
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
330
- function generateDriverLogPrefix(obj, sessionId = null) {
333
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars -- DriverHelpers interface
334
+ function generateDriverLogPrefix(obj, _sessionId) {
335
+ if (!obj) {
336
+ // This should not happen
337
+ return 'UnknownDriver@????';
338
+ }
331
339
  return `${obj.constructor.name}@${support_1.node.getObjectId(obj).substring(0, 4)}`;
332
340
  }
341
+ function parseAppLink(appLink) {
342
+ try {
343
+ return new URL(appLink);
344
+ }
345
+ catch {
346
+ return {};
347
+ }
348
+ }
349
+ function isEnvOptionEnabled(optionName, defaultValue = null) {
350
+ const value = process.env[optionName];
351
+ if (!lodash_1.default.isNull(defaultValue) && lodash_1.default.isEmpty(value)) {
352
+ return defaultValue;
353
+ }
354
+ return !lodash_1.default.isEmpty(value) && !['0', 'false', 'no'].includes(lodash_1.default.toLower(value));
355
+ }
356
+ function isSupportedUrl(app) {
357
+ try {
358
+ const { protocol } = parseAppLink(app);
359
+ return ['http:', 'https:'].includes(protocol ?? '');
360
+ }
361
+ catch {
362
+ return false;
363
+ }
364
+ }
333
365
  /**
334
- * Sends a HTTP GET query to fetch the app with caching enabled.
335
- * Follows https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching
336
- *
337
- * @param {string} appLink The URL to download an app from
338
- * @param {import('axios').RawAxiosRequestHeaders} reqHeaders Additional HTTP request headers
339
- * @returns {Promise<RemoteAppData>}
366
+ * Transforms the given app link to the cache key.
367
+ * Necessary to properly cache apps having the same address but different query strings,
368
+ * e.g. ones stored in S3 using presigned URLs.
340
369
  */
370
+ function toCacheKey(app) {
371
+ if (!isEnvOptionEnabled('APPIUM_APPS_CACHE_IGNORE_URL_QUERY') || !isSupportedUrl(app)) {
372
+ return app;
373
+ }
374
+ try {
375
+ const parsed = parseAppLink(app);
376
+ const href = 'href' in parsed ? parsed.href : undefined;
377
+ const search = 'search' in parsed ? parsed.search : undefined;
378
+ if (href && search) {
379
+ return href.replace(search, '');
380
+ }
381
+ if (href) {
382
+ return href;
383
+ }
384
+ }
385
+ catch {
386
+ // ignore
387
+ }
388
+ return app;
389
+ }
341
390
  async function queryAppLink(appLink, reqHeaders) {
342
391
  const url = new URL(appLink);
343
392
  // Extract credentials, then remove them from the URL for axios
@@ -345,14 +394,7 @@ async function queryAppLink(appLink, reqHeaders) {
345
394
  url.username = '';
346
395
  url.password = '';
347
396
  const axiosUrl = url.href;
348
- /** @type {import('axios').AxiosBasicCredentials|undefined} */
349
- const axiosAuth = username ? {
350
- username,
351
- password,
352
- } : undefined;
353
- /**
354
- * @type {import('axios').RawAxiosRequestConfig}
355
- */
397
+ const axiosAuth = username ? { username, password } : undefined;
356
398
  const requestOpts = {
357
399
  url: axiosUrl,
358
400
  auth: axiosAuth,
@@ -363,24 +405,12 @@ async function queryAppLink(appLink, reqHeaders) {
363
405
  };
364
406
  try {
365
407
  const { data: stream, headers, status } = await (0, axios_1.default)(requestOpts);
366
- return {
367
- stream,
368
- headers,
369
- status,
370
- };
408
+ return { stream, headers, status };
371
409
  }
372
410
  catch (err) {
373
411
  throw new Error(`Cannot download the app from ${axiosUrl}: ${err.message}`);
374
412
  }
375
413
  }
376
- /**
377
- * Retrieves app payload from the given stream. Also meters the download performance.
378
- *
379
- * @param {import('stream').Readable} srcStream The incoming stream
380
- * @param {string} dstPath The target file path to be written
381
- * @returns {Promise<string>} The same dstPath
382
- * @throws {Error} If there was a failure while downloading the file
383
- */
384
414
  async function fetchApp(srcStream, dstPath) {
385
415
  const timer = new support_1.timing.Timer().start();
386
416
  try {
@@ -388,7 +418,7 @@ async function fetchApp(srcStream, dstPath) {
388
418
  srcStream.pipe(writer);
389
419
  await new bluebird_1.default((resolve, reject) => {
390
420
  srcStream.once('error', reject);
391
- writer.once('finish', resolve);
421
+ writer.once('finish', () => resolve());
392
422
  writer.once('error', (e) => {
393
423
  srcStream.unpipe(writer);
394
424
  reject(e);
@@ -400,78 +430,25 @@ async function fetchApp(srcStream, dstPath) {
400
430
  }
401
431
  const secondsElapsed = timer.getDuration().asSeconds;
402
432
  const { size } = await support_1.fs.stat(dstPath);
403
- logger_1.default.debug(`The application (${support_1.util.toReadableSizeString(size)}) ` +
433
+ logger_1.log.debug(`The application (${support_1.util.toReadableSizeString(size)}) ` +
404
434
  `has been downloaded to '${dstPath}' in ${secondsElapsed.toFixed(3)}s`);
405
435
  // it does not make much sense to approximate the speed for short downloads
406
436
  if (secondsElapsed >= AVG_DOWNLOAD_SPEED_MEASUREMENT_THRESHOLD_SEC) {
407
437
  const bytesPerSec = Math.floor(size / secondsElapsed);
408
- logger_1.default.debug(`Approximate download speed: ${support_1.util.toReadableSizeString(bytesPerSec)}/s`);
438
+ logger_1.log.debug(`Approximate download speed: ${support_1.util.toReadableSizeString(bytesPerSec)}/s`);
409
439
  }
410
440
  return dstPath;
411
441
  }
412
- /**
413
- * Transforms the given app link to the cache key.
414
- * This is necessary to properly cache apps
415
- * having the same address, but different query strings,
416
- * for example ones stored in S3 using presigned URLs.
417
- *
418
- * @param {string} app App link.
419
- * @returns {string} Transformed app link or the original arg if
420
- * no transformation is needed.
421
- */
422
- function toCacheKey(app) {
423
- if (!isEnvOptionEnabled('APPIUM_APPS_CACHE_IGNORE_URL_QUERY') || !isSupportedUrl(app)) {
424
- return app;
425
- }
426
- try {
427
- const { href, search } = parseAppLink(app);
428
- if (href && search) {
429
- return href.replace(search, '');
430
- }
431
- if (href) {
432
- return href;
433
- }
434
- }
435
- catch { }
436
- return app;
437
- }
438
- /**
439
- * Safely parses the given app link to a URL object
440
- *
441
- * @param {string} appLink
442
- * @returns {URL|import('@appium/types').StringRecord} Parsed URL object
443
- * or an empty object if the parsing has failed
444
- */
445
- function parseAppLink(appLink) {
446
- try {
447
- return new URL(appLink);
448
- }
449
- catch {
450
- return {};
451
- }
452
- }
453
- /**
454
- * Tries to determine the file name of the payload that is going
455
- * to be downloaded from an URL
456
- *
457
- * @param {import('axios').RawAxiosRequestHeaders} headers
458
- * @param {string} pathname
459
- * @param {string[]} supportedAppExtensions
460
- * @returns {string}
461
- */
462
442
  function determineFilename(headers, pathname, supportedAppExtensions) {
463
443
  const basename = support_1.fs.sanitizeName(node_path_1.default.basename(decodeURIComponent(pathname ?? '')), {
464
444
  replacement: SANITIZE_REPLACEMENT,
465
445
  });
466
446
  const extname = node_path_1.default.extname(basename);
467
- if (headers['content-disposition'] && /^attachment/i.test(
468
- /** @type {string} */ (headers['content-disposition']))) {
469
- logger_1.default.debug(`Content-Disposition: ${headers['content-disposition']}`);
470
- const match = /filename="([^"]+)/i.exec(/** @type {string} */ (headers['content-disposition']));
447
+ if (headers['content-disposition'] && /^attachment/i.test(String(headers['content-disposition']))) {
448
+ logger_1.log.debug(`Content-Disposition: ${headers['content-disposition']}`);
449
+ const match = /filename="([^"]+)/i.exec(String(headers['content-disposition']));
471
450
  if (match) {
472
- return support_1.fs.sanitizeName(match[1], {
473
- replacement: SANITIZE_REPLACEMENT,
474
- });
451
+ return support_1.fs.sanitizeName(match[1], { replacement: SANITIZE_REPLACEMENT });
475
452
  }
476
453
  }
477
454
  // assign the default file name and the extension if none has been detected
@@ -480,61 +457,12 @@ function determineFilename(headers, pathname, supportedAppExtensions) {
480
457
  : DEFAULT_BASENAME;
481
458
  let resultingExt = extname;
482
459
  if (!supportedAppExtensions.map(lodash_1.default.toLower).includes(lodash_1.default.toLower(resultingExt))) {
483
- logger_1.default.info(`The current file extension '${resultingExt}' is not supported. ` +
460
+ logger_1.log.info(`The current file extension '${resultingExt}' is not supported. ` +
484
461
  `Defaulting to '${lodash_1.default.first(supportedAppExtensions)}'`);
485
- resultingExt = /** @type {string} */ (lodash_1.default.first(supportedAppExtensions));
462
+ resultingExt = lodash_1.default.first(supportedAppExtensions);
486
463
  }
487
464
  return `${resultingName}${resultingExt}`;
488
465
  }
489
- /**
490
- * Checks whether we can threat the given app link
491
- * as a URL,
492
- *
493
- * @param {string} app
494
- * @returns {boolean} True if app is a supported URL
495
- */
496
- function isSupportedUrl(app) {
497
- try {
498
- const { protocol } = parseAppLink(app);
499
- return ['http:', 'https:'].includes(protocol);
500
- }
501
- catch {
502
- return false;
503
- }
504
- }
505
- /**
506
- * Check if the given environment option is enabled
507
- *
508
- * @param {string} optionName Option name
509
- * @param {boolean|null} [defaultValue=null] The value to return if the given env value
510
- * is not set explicitly
511
- * @returns {boolean} True if the option is enabled
512
- */
513
- function isEnvOptionEnabled(optionName, defaultValue = null) {
514
- const value = process.env[optionName];
515
- if (!lodash_1.default.isNull(defaultValue) && lodash_1.default.isEmpty(value)) {
516
- return defaultValue;
517
- }
518
- return !lodash_1.default.isEmpty(value) && !['0', 'false', 'no'].includes(lodash_1.default.toLower(value));
519
- }
520
- /**
521
- *
522
- * @param {string} [envVarName]
523
- * @param {number} defaultValue
524
- * @returns {number}
525
- */
526
- function toNaturalNumber(defaultValue, envVarName) {
527
- if (!envVarName || lodash_1.default.isUndefined(process.env[envVarName])) {
528
- return defaultValue;
529
- }
530
- const num = parseInt(`${process.env[envVarName]}`, 10);
531
- return num > 0 ? num : defaultValue;
532
- }
533
- /**
534
- * @param {string} app
535
- * @param {string[]} supportedAppExtensions
536
- * @returns {string}
537
- */
538
466
  function verifyAppExtension(app, supportedAppExtensions) {
539
467
  if (supportedAppExtensions.map(lodash_1.default.toLower).includes(lodash_1.default.toLower(node_path_1.default.extname(app)))) {
540
468
  return app;
@@ -543,25 +471,12 @@ function verifyAppExtension(app, supportedAppExtensions) {
543
471
  `${support_1.util.pluralize('extension', supportedAppExtensions.length, false)}: ` +
544
472
  supportedAppExtensions);
545
473
  }
546
- /**
547
- * @param {string} folderPath
548
- * @returns {Promise<number>}
549
- */
550
474
  async function calculateFolderIntegrity(folderPath) {
551
475
  return (await support_1.fs.glob('**/*', { cwd: folderPath })).length;
552
476
  }
553
- /**
554
- * @param {string} filePath
555
- * @returns {Promise<string>}
556
- */
557
477
  async function calculateFileIntegrity(filePath) {
558
478
  return await support_1.fs.hash(filePath);
559
479
  }
560
- /**
561
- * @param {string} currentPath
562
- * @param {import('@appium/types').StringRecord} expectedIntegrity
563
- * @returns {Promise<boolean>}
564
- */
565
480
  async function isAppIntegrityOk(currentPath, expectedIntegrity = {}) {
566
481
  if (!(await support_1.fs.exists(currentPath))) {
567
482
  return false;
@@ -574,10 +489,17 @@ async function isAppIntegrityOk(currentPath, expectedIntegrity = {}) {
574
489
  // more precise, but we don't need to be very precise here and also don't want to
575
490
  // overuse RAM and have a performance drop.
576
491
  return (await support_1.fs.stat(currentPath)).isDirectory()
577
- ? (await calculateFolderIntegrity(currentPath)) >= expectedIntegrity?.folder
492
+ ? (await calculateFolderIntegrity(currentPath)) >= (expectedIntegrity?.folder ?? 0)
578
493
  : (await calculateFileIntegrity(currentPath)) === expectedIntegrity?.file;
579
494
  }
580
- /** @type {import('@appium/types').DriverHelpers} */
495
+ function toNaturalNumber(defaultValue, envVarName) {
496
+ if (!envVarName || lodash_1.default.isUndefined(process.env[envVarName])) {
497
+ return defaultValue;
498
+ }
499
+ const num = parseInt(`${process.env[envVarName]}`, 10);
500
+ return num > 0 ? num : defaultValue;
501
+ }
502
+ // #endregion
581
503
  exports.default = {
582
504
  configureApp,
583
505
  isPackageOrBundle,
@@ -585,17 +507,4 @@ exports.default = {
585
507
  parseCapsArray,
586
508
  generateDriverLogPrefix,
587
509
  };
588
- /**
589
- * @typedef RemoteAppProps
590
- * @property {Date?} lastModified
591
- * @property {boolean} immutable
592
- * @property {number?} maxAge
593
- * @property {string?} etag
594
- */
595
- /**
596
- * @typedef RemoteAppData Properties of the remote application (e.g. GET HTTP response) to be downloaded.
597
- * @property {number} status The HTTP status of the response
598
- * @property {import('stream').Readable} stream The HTTP response body represented as readable stream
599
- * @property {import('axios').RawAxiosResponseHeaders | import('axios').AxiosResponseHeaders} headers HTTP response headers
600
- */
601
510
  //# sourceMappingURL=helpers.js.map