@appium/base-driver 10.5.2 → 10.7.0

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 (189) hide show
  1. package/build/lib/basedriver/capabilities.d.ts +1 -1
  2. package/build/lib/basedriver/capabilities.d.ts.map +1 -1
  3. package/build/lib/basedriver/capabilities.js +58 -50
  4. package/build/lib/basedriver/capabilities.js.map +1 -1
  5. package/build/lib/basedriver/commands/bidi.d.ts.map +1 -1
  6. package/build/lib/basedriver/commands/bidi.js +10 -14
  7. package/build/lib/basedriver/commands/bidi.js.map +1 -1
  8. package/build/lib/basedriver/commands/event.d.ts.map +1 -1
  9. package/build/lib/basedriver/commands/event.js +4 -7
  10. package/build/lib/basedriver/commands/event.js.map +1 -1
  11. package/build/lib/basedriver/commands/execute.js +3 -6
  12. package/build/lib/basedriver/commands/execute.js.map +1 -1
  13. package/build/lib/basedriver/commands/find.d.ts.map +1 -1
  14. package/build/lib/basedriver/commands/find.js +2 -1
  15. package/build/lib/basedriver/commands/find.js.map +1 -1
  16. package/build/lib/basedriver/commands/log.d.ts.map +1 -1
  17. package/build/lib/basedriver/commands/log.js +1 -5
  18. package/build/lib/basedriver/commands/log.js.map +1 -1
  19. package/build/lib/basedriver/commands/timeout.d.ts.map +1 -1
  20. package/build/lib/basedriver/commands/timeout.js +9 -13
  21. package/build/lib/basedriver/commands/timeout.js.map +1 -1
  22. package/build/lib/basedriver/core.d.ts.map +1 -1
  23. package/build/lib/basedriver/core.js +17 -14
  24. package/build/lib/basedriver/core.js.map +1 -1
  25. package/build/lib/basedriver/device-settings.d.ts.map +1 -1
  26. package/build/lib/basedriver/device-settings.js +3 -7
  27. package/build/lib/basedriver/device-settings.js.map +1 -1
  28. package/build/lib/basedriver/driver.d.ts.map +1 -1
  29. package/build/lib/basedriver/driver.js +34 -38
  30. package/build/lib/basedriver/driver.js.map +1 -1
  31. package/build/lib/basedriver/extension-core.d.ts +4 -1
  32. package/build/lib/basedriver/extension-core.d.ts.map +1 -1
  33. package/build/lib/basedriver/extension-core.js +37 -13
  34. package/build/lib/basedriver/extension-core.js.map +1 -1
  35. package/build/lib/basedriver/helpers.d.ts.map +1 -1
  36. package/build/lib/basedriver/helpers.js +47 -33
  37. package/build/lib/basedriver/helpers.js.map +1 -1
  38. package/build/lib/basedriver/ipc.d.ts +36 -0
  39. package/build/lib/basedriver/ipc.d.ts.map +1 -0
  40. package/build/lib/basedriver/ipc.js +157 -0
  41. package/build/lib/basedriver/ipc.js.map +1 -0
  42. package/build/lib/basedriver/validation.d.ts.map +1 -1
  43. package/build/lib/basedriver/validation.js +27 -29
  44. package/build/lib/basedriver/validation.js.map +1 -1
  45. package/build/lib/express/express-logging.d.ts +0 -1
  46. package/build/lib/express/express-logging.d.ts.map +1 -1
  47. package/build/lib/express/express-logging.js +11 -11
  48. package/build/lib/express/express-logging.js.map +1 -1
  49. package/build/lib/express/idempotency.js +3 -6
  50. package/build/lib/express/idempotency.js.map +1 -1
  51. package/build/lib/express/middleware.d.ts.map +1 -1
  52. package/build/lib/express/middleware.js +6 -10
  53. package/build/lib/express/middleware.js.map +1 -1
  54. package/build/lib/express/server.d.ts +1 -1
  55. package/build/lib/express/server.d.ts.map +1 -1
  56. package/build/lib/express/server.js +82 -73
  57. package/build/lib/express/server.js.map +1 -1
  58. package/build/lib/express/websocket.d.ts.map +1 -1
  59. package/build/lib/express/websocket.js +6 -9
  60. package/build/lib/express/websocket.js.map +1 -1
  61. package/build/lib/helpers/capabilities.d.ts.map +1 -1
  62. package/build/lib/helpers/capabilities.js +14 -17
  63. package/build/lib/helpers/capabilities.js.map +1 -1
  64. package/build/lib/helpers/extension-command-name.js +2 -5
  65. package/build/lib/helpers/extension-command-name.js.map +1 -1
  66. package/build/lib/helpers/levenshtein-match.d.ts.map +1 -1
  67. package/build/lib/helpers/levenshtein-match.js +6 -7
  68. package/build/lib/helpers/levenshtein-match.js.map +1 -1
  69. package/build/lib/index.d.ts +2 -1
  70. package/build/lib/index.d.ts.map +1 -1
  71. package/build/lib/index.js +6 -16
  72. package/build/lib/index.js.map +1 -1
  73. package/build/lib/jsonwp-proxy/protocol-converter.d.ts.map +1 -1
  74. package/build/lib/jsonwp-proxy/protocol-converter.js +21 -18
  75. package/build/lib/jsonwp-proxy/protocol-converter.js.map +1 -1
  76. package/build/lib/jsonwp-proxy/proxy-request.d.ts +2 -2
  77. package/build/lib/jsonwp-proxy/proxy-request.d.ts.map +1 -1
  78. package/build/lib/jsonwp-proxy/proxy-request.js +25 -21
  79. package/build/lib/jsonwp-proxy/proxy-request.js.map +1 -1
  80. package/build/lib/jsonwp-proxy/proxy.d.ts.map +1 -1
  81. package/build/lib/jsonwp-proxy/proxy.js +45 -36
  82. package/build/lib/jsonwp-proxy/proxy.js.map +1 -1
  83. package/build/lib/protocol/errors.d.ts.map +1 -1
  84. package/build/lib/protocol/errors.js +33 -37
  85. package/build/lib/protocol/errors.js.map +1 -1
  86. package/build/lib/protocol/helpers.d.ts.map +1 -1
  87. package/build/lib/protocol/helpers.js +9 -8
  88. package/build/lib/protocol/helpers.js.map +1 -1
  89. package/build/lib/protocol/protocol.d.ts +1 -1
  90. package/build/lib/protocol/protocol.d.ts.map +1 -1
  91. package/build/lib/protocol/protocol.js +73 -61
  92. package/build/lib/protocol/protocol.js.map +1 -1
  93. package/build/lib/protocol/routes.d.ts +1 -1
  94. package/build/lib/protocol/routes.d.ts.map +1 -1
  95. package/build/lib/protocol/routes.js +16 -17
  96. package/build/lib/protocol/routes.js.map +1 -1
  97. package/build/lib/protocol/validators.d.ts.map +1 -1
  98. package/build/lib/protocol/validators.js +1 -5
  99. package/build/lib/protocol/validators.js.map +1 -1
  100. package/build/lib/test-pages/crash.d.ts.map +1 -0
  101. package/build/lib/test-pages/crash.js.map +1 -0
  102. package/build/lib/test-pages/env.d.ts +5 -0
  103. package/build/lib/test-pages/env.d.ts.map +1 -0
  104. package/build/lib/test-pages/env.js +12 -0
  105. package/build/lib/test-pages/env.js.map +1 -0
  106. package/build/lib/{express/static.d.ts → test-pages/handlers.d.ts} +1 -2
  107. package/build/lib/test-pages/handlers.d.ts.map +1 -0
  108. package/build/lib/{express/static.js → test-pages/handlers.js} +9 -12
  109. package/build/lib/test-pages/handlers.js.map +1 -0
  110. package/build/lib/test-pages/index.d.ts +6 -0
  111. package/build/lib/test-pages/index.d.ts.map +1 -0
  112. package/build/lib/test-pages/index.js +35 -0
  113. package/build/lib/test-pages/index.js.map +1 -0
  114. package/build/lib/test-pages/static-dir.d.ts +8 -0
  115. package/build/lib/test-pages/static-dir.d.ts.map +1 -0
  116. package/build/lib/test-pages/static-dir.js +24 -0
  117. package/build/lib/test-pages/static-dir.js.map +1 -0
  118. package/build/lib/test-pages/template.d.ts +3 -0
  119. package/build/lib/test-pages/template.d.ts.map +1 -0
  120. package/build/lib/test-pages/template.js +19 -0
  121. package/build/lib/test-pages/template.js.map +1 -0
  122. package/build/lib/utils.d.ts +14 -0
  123. package/build/lib/utils.d.ts.map +1 -0
  124. package/build/lib/utils.js +55 -0
  125. package/build/lib/utils.js.map +1 -0
  126. package/lib/basedriver/capabilities.ts +126 -115
  127. package/lib/basedriver/commands/bidi.ts +11 -11
  128. package/lib/basedriver/commands/event.ts +17 -11
  129. package/lib/basedriver/commands/execute.ts +15 -12
  130. package/lib/basedriver/commands/find.ts +20 -12
  131. package/lib/basedriver/commands/log.ts +4 -3
  132. package/lib/basedriver/commands/timeout.ts +22 -14
  133. package/lib/basedriver/core.ts +26 -26
  134. package/lib/basedriver/device-settings.ts +7 -12
  135. package/lib/basedriver/driver.ts +62 -50
  136. package/lib/basedriver/extension-core.ts +60 -18
  137. package/lib/basedriver/helpers.ts +81 -52
  138. package/lib/basedriver/ipc.ts +198 -0
  139. package/lib/basedriver/validation.ts +37 -30
  140. package/lib/express/express-logging.ts +16 -20
  141. package/lib/express/idempotency.ts +9 -9
  142. package/lib/express/middleware.ts +14 -18
  143. package/lib/express/server.ts +118 -120
  144. package/lib/express/websocket.ts +11 -15
  145. package/lib/helpers/capabilities.ts +21 -16
  146. package/lib/helpers/extension-command-name.ts +3 -3
  147. package/lib/helpers/levenshtein-match.ts +20 -14
  148. package/lib/index.js +3 -12
  149. package/lib/jsonwp-proxy/protocol-converter.ts +58 -35
  150. package/lib/jsonwp-proxy/proxy-request.ts +26 -26
  151. package/lib/jsonwp-proxy/proxy.ts +74 -75
  152. package/lib/protocol/errors.ts +69 -88
  153. package/lib/protocol/helpers.ts +9 -5
  154. package/lib/protocol/protocol.ts +149 -107
  155. package/lib/protocol/routes.ts +17 -17
  156. package/lib/protocol/validators.ts +1 -3
  157. package/lib/test-pages/env.ts +9 -0
  158. package/lib/{express/static.ts → test-pages/handlers.ts} +10 -22
  159. package/lib/test-pages/index.ts +34 -0
  160. package/lib/test-pages/static-dir.ts +19 -0
  161. package/lib/test-pages/template.ts +17 -0
  162. package/lib/utils.ts +65 -0
  163. package/package.json +10 -13
  164. package/tsconfig.json +1 -0
  165. package/build/lib/express/crash.d.ts.map +0 -1
  166. package/build/lib/express/crash.js.map +0 -1
  167. package/build/lib/express/static.d.ts.map +0 -1
  168. package/build/lib/express/static.js.map +0 -1
  169. /package/build/lib/{express → test-pages}/crash.d.ts +0 -0
  170. /package/build/lib/{express → test-pages}/crash.js +0 -0
  171. /package/lib/{express → test-pages}/crash.ts +0 -0
  172. /package/{static → test-fixtures/static}/appium.png +0 -0
  173. /package/{static → test-fixtures/static}/favicon.ico +0 -0
  174. /package/{static → test-fixtures/static}/js/jquery.min.js +0 -0
  175. /package/{static → test-fixtures/static}/test/frameset.html +0 -0
  176. /package/{static → test-fixtures/static}/test/guinea-pig-app-banner.html +0 -0
  177. /package/{static → test-fixtures/static}/test/guinea-pig-scrollable.html +0 -0
  178. /package/{static → test-fixtures/static}/test/guinea-pig.html +0 -0
  179. /package/{static → test-fixtures/static}/test/guinea-pig2.html +0 -0
  180. /package/{static → test-fixtures/static}/test/guinea-pig3.html +0 -0
  181. /package/{static → test-fixtures/static}/test/guinea-pig4.html +0 -0
  182. /package/{static → test-fixtures/static}/test/guinea-pig5.html +0 -0
  183. /package/{static → test-fixtures/static}/test/iframes.html +0 -0
  184. /package/{static → test-fixtures/static}/test/shadow-dom.html +0 -0
  185. /package/{static → test-fixtures/static}/test/subframe1.html +0 -0
  186. /package/{static → test-fixtures/static}/test/subframe2.html +0 -0
  187. /package/{static → test-fixtures/static}/test/subframe3.html +0 -0
  188. /package/{static → test-fixtures/static}/test/touch.html +0 -0
  189. /package/{static → test-fixtures/static}/test/welcome.html +0 -0
@@ -1,5 +1,4 @@
1
1
  import type {AppiumLogger, HTTPBody, ProxyResponse} from '@appium/types';
2
- import _ from 'lodash';
3
2
  import {logger, util} from '@appium/support';
4
3
  import {duplicateKeys} from '../basedriver/helpers';
5
4
  import {MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY, PROTOCOLS} from '../constants';
@@ -7,7 +6,7 @@ import {MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY, PROTOCOLS} from '../constants';
7
6
  export type ProxyFunction = (
8
7
  url: string,
9
8
  method: string,
10
- body?: HTTPBody
9
+ body?: HTTPBody,
11
10
  ) => Promise<[ProxyResponse, HTTPBody]>;
12
11
 
13
12
  export const COMMAND_URLS_CONFLICTS = [
@@ -20,7 +19,8 @@ export const COMMAND_URLS_CONFLICTS = [
20
19
  },
21
20
  {
22
21
  commandNames: ['getElementScreenshot'],
23
- jsonwpConverter: (url: string) => url.replace(/\/element\/([^/]+)\/screenshot$/, '/screenshot/$1'),
22
+ jsonwpConverter: (url: string) =>
23
+ url.replace(/\/element\/([^/]+)\/screenshot$/, '/screenshot/$1'),
24
24
  w3cConverter: (url: string) => url.replace(/\/screenshot\/([^/]+)/, '/element/$1/screenshot'),
25
25
  },
26
26
  {
@@ -60,7 +60,7 @@ export class ProtocolConverter {
60
60
  */
61
61
  constructor(
62
62
  public proxyFunc: ProxyFunction,
63
- log: AppiumLogger | null = null
63
+ log: AppiumLogger | null = null,
64
64
  ) {
65
65
  this._log = log;
66
66
  }
@@ -85,7 +85,7 @@ export class ProtocolConverter {
85
85
  commandName: string,
86
86
  url: string,
87
87
  method: string,
88
- body?: HTTPBody
88
+ body?: HTTPBody,
89
89
  ): Promise<[ProxyResponse, HTTPBody]> {
90
90
  if (!this.downstreamProtocol) {
91
91
  return await this.proxyFunc(url, method, body);
@@ -118,12 +118,12 @@ export class ProtocolConverter {
118
118
  this.downstreamProtocol === MJSONWP ? jsonwpConverter(url) : w3cConverter(url);
119
119
  if (rewrittenUrl === url) {
120
120
  this.log.debug(
121
- `Did not know how to rewrite the original URL '${url}' for ${this.downstreamProtocol} protocol`
121
+ `Did not know how to rewrite the original URL '${url}' for ${this.downstreamProtocol} protocol`,
122
122
  );
123
123
  break;
124
124
  }
125
125
  this.log.info(
126
- `Rewrote the original URL '${url}' to '${rewrittenUrl}' for ${this.downstreamProtocol} protocol`
126
+ `Rewrote the original URL '${url}' to '${rewrittenUrl}' for ${this.downstreamProtocol} protocol`,
127
127
  );
128
128
  return await this.proxyFunc(rewrittenUrl, method, body);
129
129
  }
@@ -138,12 +138,16 @@ export class ProtocolConverter {
138
138
  * provided in the request, we need to do 3 proxies and combine the result.
139
139
  */
140
140
  private getTimeoutRequestObjects(body: HTTPBody): Record<string, unknown>[] {
141
- if (_.isNil(body)) {
141
+ if (body == null) {
142
142
  return [];
143
143
  }
144
144
 
145
145
  const bodyObj = (util.safeJsonParse(body) as Record<string, unknown>) ?? {};
146
- if (this.downstreamProtocol === W3C && _.has(bodyObj, 'ms') && _.has(bodyObj, 'type')) {
146
+ if (
147
+ this.downstreamProtocol === W3C &&
148
+ Object.hasOwn(bodyObj, 'ms') &&
149
+ Object.hasOwn(bodyObj, 'type')
150
+ ) {
147
151
  const typeToW3C = (x: string) => (x === 'page load' ? 'pageLoad' : x);
148
152
  return [
149
153
  {
@@ -152,15 +156,20 @@ export class ProtocolConverter {
152
156
  ];
153
157
  }
154
158
 
155
- if (this.downstreamProtocol === MJSONWP && (!_.has(bodyObj, 'ms') || !_.has(bodyObj, 'type'))) {
159
+ if (
160
+ this.downstreamProtocol === MJSONWP &&
161
+ (!Object.hasOwn(bodyObj, 'ms') || !Object.hasOwn(bodyObj, 'type'))
162
+ ) {
156
163
  const typeToJSONWP = (x: string) => (x === 'pageLoad' ? 'page load' : x);
157
- return _.toPairs(bodyObj)
158
- // Only transform the entry if ms value is a valid positive float number
159
- .filter((pair) => /^\d+(?:[.,]\d*?)?$/.test(`${pair[1]}`))
160
- .map((pair) => ({
161
- type: typeToJSONWP(pair[0]),
162
- ms: pair[1],
163
- }));
164
+ return (
165
+ Object.entries(bodyObj)
166
+ // Only transform the entry if ms value is a valid positive float number
167
+ .filter((pair) => /^\d+(?:[.,]\d*?)?$/.test(`${pair[1]}`))
168
+ .map((pair) => ({
169
+ type: typeToJSONWP(pair[0]),
170
+ ms: pair[1],
171
+ }))
172
+ );
164
173
  }
165
174
 
166
175
  return [bodyObj];
@@ -172,14 +181,14 @@ export class ProtocolConverter {
172
181
  private async proxySetTimeouts(
173
182
  url: string,
174
183
  method: string,
175
- body?: HTTPBody
184
+ body?: HTTPBody,
176
185
  ): Promise<[ProxyResponse, HTTPBody]> {
177
186
  const timeoutRequestObjects = this.getTimeoutRequestObjects(body);
178
187
  if (timeoutRequestObjects.length === 0) {
179
188
  return await this.proxyFunc(url, method, body);
180
189
  }
181
190
  this.log.debug(
182
- `Will send the following request bodies to /timeouts: ${JSON.stringify(timeoutRequestObjects)}`
191
+ `Will send the following request bodies to /timeouts: ${JSON.stringify(timeoutRequestObjects)}`,
183
192
  );
184
193
 
185
194
  let response!: ProxyResponse;
@@ -203,19 +212,23 @@ export class ProtocolConverter {
203
212
  private async proxySetWindow(
204
213
  url: string,
205
214
  method: string,
206
- body: HTTPBody
215
+ body: HTTPBody,
207
216
  ): Promise<[ProxyResponse, HTTPBody]> {
208
217
  const bodyObj = util.safeJsonParse(body);
209
- if (_.isPlainObject(bodyObj)) {
218
+ if (util.isPlainObject(bodyObj)) {
210
219
  const obj = bodyObj as Record<string, unknown>;
211
- if (this.downstreamProtocol === W3C && _.has(bodyObj, 'name') && !_.has(bodyObj, 'handle')) {
220
+ if (
221
+ this.downstreamProtocol === W3C &&
222
+ Object.hasOwn(bodyObj, 'name') &&
223
+ !Object.hasOwn(bodyObj, 'handle')
224
+ ) {
212
225
  this.log.debug(`Copied 'name' value '${obj.name}' to 'handle' as per W3C spec`);
213
226
  return await this.proxyFunc(url, method, {...obj, handle: obj.name});
214
227
  }
215
228
  if (
216
229
  this.downstreamProtocol === MJSONWP &&
217
- _.has(bodyObj, 'handle') &&
218
- !_.has(bodyObj, 'name')
230
+ Object.hasOwn(bodyObj, 'handle') &&
231
+ !Object.hasOwn(bodyObj, 'name')
219
232
  ) {
220
233
  this.log.debug(`Copied 'handle' value '${obj.handle}' to 'name' as per JSONWP spec`);
221
234
  return await this.proxyFunc(url, method, {...obj, name: obj.handle});
@@ -227,16 +240,19 @@ export class ProtocolConverter {
227
240
  private async proxySetValue(
228
241
  url: string,
229
242
  method: string,
230
- body: HTTPBody
243
+ body: HTTPBody,
231
244
  ): Promise<[ProxyResponse, HTTPBody]> {
232
245
  const bodyObj = util.safeJsonParse(body) as Record<string, unknown> | undefined;
233
- if (_.isPlainObject(bodyObj) && (util.hasValue(bodyObj?.text) || util.hasValue(bodyObj?.value))) {
246
+ if (
247
+ util.isPlainObject(bodyObj) &&
248
+ (util.hasValue(bodyObj?.text) || util.hasValue(bodyObj?.value))
249
+ ) {
234
250
  let {text, value} = bodyObj;
235
251
  if (util.hasValue(text) && !util.hasValue(value)) {
236
- value = _.isString(text) ? [...text] : _.isArray(text) ? text : [];
252
+ value = typeof text === 'string' ? [...text] : Array.isArray(text) ? text : [];
237
253
  this.log.debug(`Added 'value' property to 'setValue' request body`);
238
254
  } else if (!util.hasValue(text) && util.hasValue(value)) {
239
- text = _.isArray(value) ? value.join('') : _.isString(value) ? value : '';
255
+ text = Array.isArray(value) ? value.join('') : typeof value === 'string' ? value : '';
240
256
  this.log.debug(`Added 'text' property to 'setValue' request body`);
241
257
  }
242
258
  return await this.proxyFunc(url, method, {...bodyObj, text, value});
@@ -247,13 +263,20 @@ export class ProtocolConverter {
247
263
  private async proxySetFrame(
248
264
  url: string,
249
265
  method: string,
250
- body: HTTPBody
266
+ body: HTTPBody,
251
267
  ): Promise<[ProxyResponse, HTTPBody]> {
252
268
  const bodyObj = util.safeJsonParse(body);
253
- if (_.has(bodyObj, 'id') && _.isPlainObject(bodyObj.id)) {
269
+ if (
270
+ Object.hasOwn(bodyObj ?? {}, 'id') &&
271
+ util.isPlainObject((bodyObj as Record<string, unknown>).id)
272
+ ) {
254
273
  return await this.proxyFunc(url, method, {
255
274
  ...(bodyObj as object),
256
- id: duplicateKeys(bodyObj.id as object, MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY),
275
+ id: duplicateKeys(
276
+ (bodyObj as Record<string, unknown>).id as object,
277
+ MJSONWP_ELEMENT_KEY,
278
+ W3C_ELEMENT_KEY,
279
+ ),
257
280
  });
258
281
  }
259
282
  return await this.proxyFunc(url, method, body);
@@ -262,14 +285,14 @@ export class ProtocolConverter {
262
285
  private async proxyPerformActions(
263
286
  url: string,
264
287
  method: string,
265
- body: HTTPBody
288
+ body: HTTPBody,
266
289
  ): Promise<[ProxyResponse, HTTPBody]> {
267
290
  const bodyObj = util.safeJsonParse(body);
268
- if (_.isPlainObject(bodyObj)) {
291
+ if (util.isPlainObject(bodyObj)) {
269
292
  return await this.proxyFunc(
270
293
  url,
271
294
  method,
272
- duplicateKeys(bodyObj as object, MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY)
295
+ duplicateKeys(bodyObj as object, MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY),
273
296
  );
274
297
  }
275
298
  return await this.proxyFunc(url, method, body);
@@ -277,7 +300,7 @@ export class ProtocolConverter {
277
300
 
278
301
  private async proxyReleaseActions(
279
302
  url: string,
280
- method: string
303
+ method: string,
281
304
  ): Promise<[ProxyResponse, HTTPBody]> {
282
305
  return await this.proxyFunc(url, method);
283
306
  }
@@ -1,19 +1,16 @@
1
1
  import axios from 'axios';
2
- import { CancellationError } from 'bluebird';
3
- import EventEmitter from 'node:events';
4
-
5
- const CANCEL_EVENT = 'cancel';
6
- const FINISH_EVENT = 'finish';
7
2
 
8
3
  export class ProxyRequest {
9
4
  private readonly _requestConfig: axios.RawAxiosRequestConfig;
10
- private readonly _ee: EventEmitter;
11
- private _resultPromise: Promise<any> | null;
5
+ private _resultPromise: Promise<axios.AxiosResponse> | null;
6
+ private _abortController: AbortController | null;
7
+ private _cancelled: boolean;
12
8
 
13
9
  constructor(requestConfig: axios.RawAxiosRequestConfig<any>) {
14
10
  this._requestConfig = requestConfig;
15
- this._ee = new EventEmitter();
16
11
  this._resultPromise = null;
12
+ this._abortController = null;
13
+ this._cancelled = false;
17
14
  }
18
15
 
19
16
  async execute(): Promise<axios.AxiosResponse> {
@@ -21,32 +18,35 @@ export class ProxyRequest {
21
18
  return await this._resultPromise;
22
19
  }
23
20
 
21
+ const abortController = new AbortController();
22
+ this._abortController = abortController;
23
+ this._cancelled = false;
24
+
24
25
  try {
25
- this._resultPromise = Promise.race([
26
- this._makeRacingTimer(),
27
- this._makeRequest(),
28
- ]);
26
+ this._resultPromise = this._makeRequest(abortController.signal);
29
27
  return await this._resultPromise;
30
28
  } finally {
31
- this._ee.emit(FINISH_EVENT);
32
- this._ee.removeAllListeners();
29
+ this._abortController = null;
33
30
  }
34
31
  }
35
32
 
36
33
  cancel(): void {
37
- this._ee.emit(CANCEL_EVENT);
38
- }
39
-
40
- private async _makeRequest(): Promise<axios.AxiosResponse> {
41
- return await axios(this._requestConfig);
34
+ this._cancelled = true;
35
+ this._abortController?.abort();
42
36
  }
43
37
 
44
- private async _makeRacingTimer(): Promise<void> {
45
- return await new Promise((resolve, reject) => {
46
- this._ee.once(FINISH_EVENT, resolve);
47
- this._ee.once(CANCEL_EVENT, () => reject(new CancellationError(
48
- 'The request has been cancelled'
49
- )));
50
- });
38
+ private async _makeRequest(signal: AbortSignal): Promise<axios.AxiosResponse> {
39
+ try {
40
+ return await axios({
41
+ ...this._requestConfig,
42
+ signal,
43
+ });
44
+ } catch (err) {
45
+ if (this._cancelled && axios.isCancel(err)) {
46
+ // The request was cancelled; do not propagate the error to callers.
47
+ return await new Promise<axios.AxiosResponse>(() => {});
48
+ }
49
+ throw err;
50
+ }
51
51
  }
52
52
  }
@@ -6,7 +6,6 @@ import type {
6
6
  ProxyOptions,
7
7
  ProxyResponse,
8
8
  } from '@appium/types';
9
- import _ from 'lodash';
10
9
  import {logger, util} from '@appium/support';
11
10
  import {getSummaryByCode} from '../jsonwp-status/status';
12
11
  import {
@@ -19,6 +18,7 @@ import {
19
18
  import {isSessionCommand, routeToCommandName} from '../protocol';
20
19
  import {MAX_LOG_BODY_LENGTH, DEFAULT_BASE_PATH, PROTOCOLS} from '../constants';
21
20
  import {ProtocolConverter} from './protocol-converter';
21
+ import {omit, pick} from '../utils';
22
22
  import {formatResponseValue, ensureW3cResponse} from '../protocol/helpers';
23
23
  import http from 'node:http';
24
24
  import https from 'node:https';
@@ -31,7 +31,7 @@ import type {AxiosError, AxiosResponse, RawAxiosRequestConfig} from 'axios';
31
31
  const DEFAULT_LOG = logger.getLogger('WD Proxy');
32
32
  const DEFAULT_REQUEST_TIMEOUT = 240000;
33
33
  const COMMAND_WITH_SESSION_ID_MATCHER = pathToRegexMatch(
34
- '{/*prefix}/session/:sessionId{/*command}'
34
+ '{/*prefix}/session/:sessionId{/*command}',
35
35
  );
36
36
 
37
37
  const {MJSONWP, W3C} = PROTOCOLS;
@@ -69,8 +69,11 @@ export class JWProxy {
69
69
  private readonly _log: AppiumLogger | undefined;
70
70
 
71
71
  constructor(opts: ProxyOptions = {}) {
72
- const filteredOpts = _.pick(opts, ALLOWED_OPTS);
73
- const options = _.defaults(_.omit(filteredOpts, 'log'), {
72
+ const filteredOptsWithoutLog = omit(
73
+ pick(opts as Record<string, unknown>, ALLOWED_OPTS),
74
+ 'log',
75
+ ) as Omit<ProxyOptions, 'log'>;
76
+ const options = {
74
77
  scheme: 'http',
75
78
  server: 'localhost',
76
79
  port: 4444,
@@ -78,7 +81,8 @@ export class JWProxy {
78
81
  reqBasePath: DEFAULT_BASE_PATH,
79
82
  sessionId: null,
80
83
  timeout: DEFAULT_REQUEST_TIMEOUT,
81
- }) as ProxyOptions & {
84
+ ...filteredOptsWithoutLog,
85
+ } as ProxyOptions & {
82
86
  scheme: string;
83
87
  server: string;
84
88
  port: number;
@@ -88,7 +92,14 @@ export class JWProxy {
88
92
  timeout: number;
89
93
  };
90
94
  options.scheme = options.scheme.toLowerCase();
91
- Object.assign(this, options);
95
+ this.scheme = options.scheme;
96
+ this.server = options.server;
97
+ this.port = options.port;
98
+ this.base = options.base;
99
+ this.reqBasePath = options.reqBasePath;
100
+ this.sessionId = options.sessionId;
101
+ this.timeout = options.timeout;
102
+ this.headers = options.headers;
92
103
 
93
104
  this._activeRequests = [];
94
105
  this._downstreamProtocol = null;
@@ -147,13 +158,10 @@ export class JWProxy {
147
158
  getUrlForProxy(url: string, method?: HTTPMethod): string {
148
159
  const parsedUrl = this._parseUrl(url);
149
160
  const normalizedPathname = this._toNormalizedPathname(parsedUrl);
150
- const commandName = normalizedPathname
151
- ? routeToCommandName(normalizedPathname, method)
152
- : '';
153
- const requiresSessionId =
154
- !commandName || (commandName && isSessionCommand(commandName));
161
+ const commandName = normalizedPathname ? routeToCommandName(normalizedPathname, method) : '';
162
+ const requiresSessionId = !commandName || (commandName && isSessionCommand(commandName));
155
163
  const proxyPrefix = `${this.scheme}://${this.server}:${this.port}${this.base}`;
156
- let proxySuffix = normalizedPathname ? `/${_.trimStart(normalizedPathname, '/')}` : '';
164
+ let proxySuffix = normalizedPathname ? `/${normalizedPathname.replace(/^\/+/, '')}` : '';
157
165
  if (parsedUrl.search) {
158
166
  proxySuffix += parsedUrl.search;
159
167
  }
@@ -161,9 +169,7 @@ export class JWProxy {
161
169
  return `${proxyPrefix}${proxySuffix}`;
162
170
  }
163
171
  if (!this.sessionId) {
164
- throw new ReferenceError(
165
- `Session ID is not set, but saw a URL that requires it (${url})`
166
- );
172
+ throw new ReferenceError(`Session ID is not set, but saw a URL that requires it (${url})`);
167
173
  }
168
174
  return `${proxyPrefix}/session/${this.sessionId}${proxySuffix}`;
169
175
  }
@@ -174,12 +180,12 @@ export class JWProxy {
174
180
  async proxy(
175
181
  url: string,
176
182
  method: string,
177
- body: HTTPBody = null
183
+ body: HTTPBody = null,
178
184
  ): Promise<[ProxyResponse, HTTPBody]> {
179
185
  method = method.toUpperCase();
180
186
  const newUrl = this.getUrlForProxy(url, method as HTTPMethod);
181
187
  const truncateBody = (content: unknown): string =>
182
- _.truncate(_.isString(content) ? content : JSON.stringify(content), {
188
+ util.truncateString(typeof content === 'string' ? content : JSON.stringify(content), {
183
189
  length: MAX_LOG_BODY_LENGTH,
184
190
  });
185
191
  const reqOpts: RawAxiosRequestConfig = {
@@ -206,11 +212,11 @@ export class JWProxy {
206
212
  this.log.warn(
207
213
  'Invalid body payload (%s): %s',
208
214
  (error as Error).message,
209
- logger.markSensitive(truncateBody(body))
215
+ logger.markSensitive(truncateBody(body)),
210
216
  );
211
217
  throw new Error(
212
218
  'Cannot interpret the request body as valid JSON. Check the server log for more details.',
213
- {cause: error}
219
+ {cause: error},
214
220
  );
215
221
  }
216
222
  } else {
@@ -224,7 +230,7 @@ export class JWProxy {
224
230
  url || '/',
225
231
  method,
226
232
  newUrl,
227
- reqOpts.data ? logger.markSensitive(truncateBody(reqOpts.data)) : 'no'
233
+ reqOpts.data ? logger.markSensitive(truncateBody(reqOpts.data)) : 'no',
228
234
  );
229
235
 
230
236
  const throwProxyError = (error: unknown): never => {
@@ -242,7 +248,7 @@ export class JWProxy {
242
248
  const {data, status, headers} = await this.request(reqOpts);
243
249
  // `data` might be really big
244
250
  // Be careful while handling it to avoid memory leaks
245
- if (!_.isPlainObject(data)) {
251
+ if (!util.isPlainObject(data)) {
246
252
  // The response should be a valid JSON object
247
253
  // If it cannot be coerced to an object then the response is wrong
248
254
  throwProxyError(data);
@@ -252,23 +258,14 @@ export class JWProxy {
252
258
  const isSessionCreationRequest = url.endsWith('/session') && method === 'POST';
253
259
  if (isSessionCreationRequest) {
254
260
  if (status === 200) {
255
- const value = (data as Record<string, unknown>).value as
256
- | Record<string, unknown>
257
- | undefined;
258
- const raw =
259
- (data as Record<string, unknown>).sessionId ?? value?.sessionId;
260
- this.sessionId =
261
- typeof raw === 'string' ? raw : raw != null ? String(raw) : null;
261
+ const value = data.value as Record<string, unknown> | undefined;
262
+ const raw = data.sessionId ?? value?.sessionId;
263
+ this.sessionId = typeof raw === 'string' ? raw : raw != null ? String(raw) : null;
262
264
  }
263
- this.downstreamProtocol = this.getProtocolFromResBody(
264
- data as Record<string, unknown>
265
- ) ?? this.downstreamProtocol;
265
+ this.downstreamProtocol = this.getProtocolFromResBody(data) ?? this.downstreamProtocol;
266
266
  this.log.info(`Determined the downstream protocol as '${this.downstreamProtocol}'`);
267
267
  }
268
- if (
269
- _.has(data, 'status') &&
270
- parseInt((data as Record<string, unknown>).status as string, 10) !== 0
271
- ) {
268
+ if (Object.hasOwn(data, 'status') && parseInt(data.status as string, 10) !== 0) {
272
269
  throwProxyError(data);
273
270
  }
274
271
  return [
@@ -288,18 +285,14 @@ export class JWProxy {
288
285
  this.log.info(
289
286
  util.hasValue(err.response.status)
290
287
  ? `Got response with status ${err.response.status}: ${error}`
291
- : `Got response with unknown status: ${error}`
288
+ : `Got response with unknown status: ${error}`,
292
289
  );
293
290
  }
294
291
  } else {
295
292
  proxyErrorMsg = `Could not proxy command to the remote server. Original error: ${err.message}`;
296
293
  this.log.info(err.message);
297
294
  }
298
- throw new errors.ProxyRequestError(
299
- proxyErrorMsg,
300
- err.response?.data,
301
- err.response?.status
302
- );
295
+ throw new errors.ProxyRequestError(proxyErrorMsg, err.response?.data, err.response?.status);
303
296
  }
304
297
  }
305
298
 
@@ -307,10 +300,10 @@ export class JWProxy {
307
300
  * Detects the downstream protocol from a response body.
308
301
  */
309
302
  getProtocolFromResBody(resObj: Record<string, unknown>): Protocol | undefined {
310
- if (_.isInteger(resObj.status)) {
303
+ if (Number.isInteger(resObj.status)) {
311
304
  return MJSONWP;
312
305
  }
313
- if (!_.isUndefined(resObj.value)) {
306
+ if (resObj.value !== undefined) {
314
307
  return W3C;
315
308
  }
316
309
  }
@@ -321,13 +314,11 @@ export class JWProxy {
321
314
  async proxyCommand(
322
315
  url: string,
323
316
  method: HTTPMethod,
324
- body: HTTPBody = null
317
+ body: HTTPBody = null,
325
318
  ): Promise<[ProxyResponse, HTTPBody]> {
326
319
  const parsedUrl = this._parseUrl(url);
327
320
  const normalizedPathname = this._toNormalizedPathname(parsedUrl);
328
- const commandName = normalizedPathname
329
- ? routeToCommandName(normalizedPathname, method)
330
- : '';
321
+ const commandName = normalizedPathname ? routeToCommandName(normalizedPathname, method) : '';
331
322
  if (!commandName) {
332
323
  return await this.proxy(url, method, body);
333
324
  }
@@ -339,11 +330,7 @@ export class JWProxy {
339
330
  /**
340
331
  * Executes a WebDriver command and returns the unwrapped `value` field (or throws).
341
332
  */
342
- async command(
343
- url: string,
344
- method: HTTPMethod,
345
- body: HTTPBody = null
346
- ): Promise<HTTPBody> {
333
+ async command(url: string, method: HTTPMethod, body: HTTPBody = null): Promise<HTTPBody> {
347
334
  let response: ProxyResponse;
348
335
  let resBodyObj: HTTPBody;
349
336
  try {
@@ -363,23 +350,26 @@ export class JWProxy {
363
350
  const status = parseInt(resBody.status as string, 10);
364
351
  if (!isNaN(status) && status !== 0) {
365
352
  let message: unknown = resBody.value;
366
- if (_.isPlainObject(message) && _.has(message, 'message')) {
353
+ if (util.isPlainObject(message) && Object.hasOwn(message, 'message')) {
367
354
  message = (message as Record<string, unknown>).message;
368
355
  }
369
- throw errorFromMJSONWPStatusCode(status, _.isEmpty(message)
370
- ? getSummaryByCode(status)
371
- : (message as string | {message: string}));
356
+ throw errorFromMJSONWPStatusCode(
357
+ status,
358
+ util.isEmpty(message)
359
+ ? getSummaryByCode(status)
360
+ : (message as string | {message: string}),
361
+ );
372
362
  }
373
363
  } else if (protocol === W3C) {
374
364
  if (response.statusCode < 300) {
375
365
  return resBody.value;
376
366
  }
377
- if (_.isPlainObject(resBody.value) && (resBody.value as Record<string, unknown>).error) {
378
- const value = resBody.value as Record<string, unknown>;
367
+ if (util.isPlainObject(resBody.value) && resBody.value.error) {
368
+ const value = resBody.value;
379
369
  throw errorFromW3CJsonCode(
380
370
  value.error as string,
381
371
  (value.message as string) ?? '',
382
- value.stacktrace as string | undefined
372
+ value.stacktrace as string | undefined,
383
373
  );
384
374
  }
385
375
  } else if (response.statusCode === 200) {
@@ -387,9 +377,9 @@ export class JWProxy {
387
377
  }
388
378
  throw new errors.UnknownError(
389
379
  `Did not know what to do with response code '${response.statusCode}' ` +
390
- `and response body '${_.truncate(JSON.stringify(resBodyObj), {
380
+ `and response body '${util.truncateString(JSON.stringify(resBodyObj), {
391
381
  length: 300,
392
- })}'`
382
+ })}'`,
393
383
  );
394
384
  }
395
385
 
@@ -414,26 +404,28 @@ export class JWProxy {
414
404
  const [response, body] = await this.proxyCommand(
415
405
  req.originalUrl,
416
406
  req.method as HTTPMethod,
417
- req.body
407
+ req.body,
418
408
  );
419
409
  statusCode = response.statusCode;
420
410
  resBodyObj = body;
421
411
  } catch (err: unknown) {
422
412
  [statusCode, resBodyObj] = getResponseForW3CError(
423
- isErrorType(err, errors.ProxyRequestError) ? (err as InstanceType<typeof errors.ProxyRequestError>).getActualError() : err
413
+ isErrorType(err, errors.ProxyRequestError)
414
+ ? (err as InstanceType<typeof errors.ProxyRequestError>).getActualError()
415
+ : err,
424
416
  );
425
417
  }
426
418
  res.setHeader('content-type', 'application/json; charset=utf-8');
427
- if (!_.isPlainObject(resBodyObj)) {
419
+ if (!util.isPlainObject(resBodyObj)) {
428
420
  const error = new errors.UnknownError(
429
421
  `The downstream server response with the status code ${statusCode} is not a valid JSON object: ` +
430
- _.truncate(`${resBodyObj}`, {length: 300})
422
+ util.truncateString(`${resBodyObj}`, {length: 300}),
431
423
  );
432
424
  [statusCode, resBodyObj] = getResponseForW3CError(error);
433
425
  }
434
426
 
435
427
  const resBody = resBodyObj as Record<string, unknown>;
436
- if (_.has(resBody, 'sessionId')) {
428
+ if (Object.hasOwn(resBody, 'sessionId')) {
437
429
  const reqSessionId = this.getSessionIdFromUrl(req.originalUrl);
438
430
  if (reqSessionId) {
439
431
  this.log.info(`Replacing sessionId ${resBody.sessionId} with ${reqSessionId}`);
@@ -459,7 +451,10 @@ export class JWProxy {
459
451
  try {
460
452
  return await req.execute();
461
453
  } finally {
462
- _.pull(this._activeRequests, req);
454
+ const reqIndex = this._activeRequests.indexOf(req);
455
+ if (reqIndex >= 0) {
456
+ this._activeRequests.splice(reqIndex, 1);
457
+ }
463
458
  }
464
459
  }
465
460
 
@@ -467,8 +462,8 @@ export class JWProxy {
467
462
  // eslint-disable-next-line n/no-deprecated-api -- we need relative URL support
468
463
  const parsedUrl = nodeUrl.parse(url || '/');
469
464
  if (
470
- _.isNil(parsedUrl.href) ||
471
- _.isNil(parsedUrl.pathname) ||
465
+ parsedUrl.href == null ||
466
+ parsedUrl.pathname == null ||
472
467
  (parsedUrl.protocol && !['http:', 'https:'].includes(parsedUrl.protocol))
473
468
  ) {
474
469
  throw new Error(`Did not know how to proxy the url '${url}'`);
@@ -477,7 +472,7 @@ export class JWProxy {
477
472
  }
478
473
 
479
474
  private _toNormalizedPathname(parsedUrl: nodeUrl.UrlWithStringQuery): string {
480
- if (!_.isString(parsedUrl.pathname)) {
475
+ if (typeof parsedUrl.pathname !== 'string') {
481
476
  return '';
482
477
  }
483
478
  let pathname =
@@ -488,20 +483,24 @@ export class JWProxy {
488
483
  // This is needed for the backward compatibility
489
484
  // if drivers don't set reqBasePath properly
490
485
  if (!this.reqBasePath) {
491
- if (match && match.params && _.isArray((match.params as Record<string, unknown>).prefix)) {
486
+ if (
487
+ match &&
488
+ match.params &&
489
+ Array.isArray((match.params as Record<string, unknown>).prefix)
490
+ ) {
492
491
  pathname = pathname.replace(
493
492
  `/${((match.params as Record<string, unknown>).prefix as string[]).join('/')}`,
494
- ''
493
+ '',
495
494
  );
496
- } else if (_.startsWith(pathname, '/wd/hub')) {
495
+ } else if (pathname.startsWith('/wd/hub')) {
497
496
  pathname = pathname.replace('/wd/hub', '');
498
497
  }
499
498
  }
500
499
  let result = pathname;
501
500
  if (match && match.params) {
502
501
  const command = (match.params as Record<string, unknown>).command;
503
- result = _.isArray(command) ? `/${(command as string[]).join('/')}` : '';
502
+ result = Array.isArray(command) ? `/${(command as string[]).join('/')}` : '';
504
503
  }
505
- return _.trimEnd(result, '/');
504
+ return result.replace(/\/+$/, '');
506
505
  }
507
506
  }