@appium/base-driver 10.5.1 → 10.6.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 (130) hide show
  1. package/build/lib/basedriver/capabilities.d.ts.map +1 -1
  2. package/build/lib/basedriver/capabilities.js +45 -45
  3. package/build/lib/basedriver/capabilities.js.map +1 -1
  4. package/build/lib/basedriver/commands/bidi.d.ts.map +1 -1
  5. package/build/lib/basedriver/commands/bidi.js +9 -13
  6. package/build/lib/basedriver/commands/bidi.js.map +1 -1
  7. package/build/lib/basedriver/commands/event.d.ts.map +1 -1
  8. package/build/lib/basedriver/commands/event.js +4 -7
  9. package/build/lib/basedriver/commands/event.js.map +1 -1
  10. package/build/lib/basedriver/commands/execute.js +3 -6
  11. package/build/lib/basedriver/commands/execute.js.map +1 -1
  12. package/build/lib/basedriver/commands/log.d.ts.map +1 -1
  13. package/build/lib/basedriver/commands/log.js +1 -5
  14. package/build/lib/basedriver/commands/log.js.map +1 -1
  15. package/build/lib/basedriver/commands/timeout.d.ts.map +1 -1
  16. package/build/lib/basedriver/commands/timeout.js +5 -9
  17. package/build/lib/basedriver/commands/timeout.js.map +1 -1
  18. package/build/lib/basedriver/core.js +12 -12
  19. package/build/lib/basedriver/core.js.map +1 -1
  20. package/build/lib/basedriver/device-settings.d.ts.map +1 -1
  21. package/build/lib/basedriver/device-settings.js +3 -7
  22. package/build/lib/basedriver/device-settings.js.map +1 -1
  23. package/build/lib/basedriver/driver.d.ts.map +1 -1
  24. package/build/lib/basedriver/driver.js +13 -16
  25. package/build/lib/basedriver/driver.js.map +1 -1
  26. package/build/lib/basedriver/extension-core.d.ts +4 -1
  27. package/build/lib/basedriver/extension-core.d.ts.map +1 -1
  28. package/build/lib/basedriver/extension-core.js +27 -9
  29. package/build/lib/basedriver/extension-core.js.map +1 -1
  30. package/build/lib/basedriver/helpers.d.ts.map +1 -1
  31. package/build/lib/basedriver/helpers.js +28 -30
  32. package/build/lib/basedriver/helpers.js.map +1 -1
  33. package/build/lib/basedriver/ipc.d.ts +36 -0
  34. package/build/lib/basedriver/ipc.d.ts.map +1 -0
  35. package/build/lib/basedriver/ipc.js +155 -0
  36. package/build/lib/basedriver/ipc.js.map +1 -0
  37. package/build/lib/basedriver/validation.js +25 -28
  38. package/build/lib/basedriver/validation.js.map +1 -1
  39. package/build/lib/express/express-logging.d.ts.map +1 -1
  40. package/build/lib/express/express-logging.js +2 -3
  41. package/build/lib/express/express-logging.js.map +1 -1
  42. package/build/lib/express/idempotency.js +3 -6
  43. package/build/lib/express/idempotency.js.map +1 -1
  44. package/build/lib/express/middleware.d.ts.map +1 -1
  45. package/build/lib/express/middleware.js +6 -10
  46. package/build/lib/express/middleware.js.map +1 -1
  47. package/build/lib/express/server.d.ts.map +1 -1
  48. package/build/lib/express/server.js +64 -54
  49. package/build/lib/express/server.js.map +1 -1
  50. package/build/lib/express/static.d.ts.map +1 -1
  51. package/build/lib/express/static.js +14 -7
  52. package/build/lib/express/static.js.map +1 -1
  53. package/build/lib/express/websocket.d.ts.map +1 -1
  54. package/build/lib/express/websocket.js +6 -9
  55. package/build/lib/express/websocket.js.map +1 -1
  56. package/build/lib/helpers/capabilities.d.ts.map +1 -1
  57. package/build/lib/helpers/capabilities.js +14 -17
  58. package/build/lib/helpers/capabilities.js.map +1 -1
  59. package/build/lib/helpers/extension-command-name.js +2 -5
  60. package/build/lib/helpers/extension-command-name.js.map +1 -1
  61. package/build/lib/helpers/levenshtein-match.d.ts.map +1 -1
  62. package/build/lib/helpers/levenshtein-match.js +2 -6
  63. package/build/lib/helpers/levenshtein-match.js.map +1 -1
  64. package/build/lib/index.d.ts +1 -0
  65. package/build/lib/index.d.ts.map +1 -1
  66. package/build/lib/index.js +3 -14
  67. package/build/lib/index.js.map +1 -1
  68. package/build/lib/jsonwp-proxy/protocol-converter.d.ts.map +1 -1
  69. package/build/lib/jsonwp-proxy/protocol-converter.js +13 -17
  70. package/build/lib/jsonwp-proxy/protocol-converter.js.map +1 -1
  71. package/build/lib/jsonwp-proxy/proxy-request.d.ts +2 -2
  72. package/build/lib/jsonwp-proxy/proxy-request.d.ts.map +1 -1
  73. package/build/lib/jsonwp-proxy/proxy-request.js +25 -21
  74. package/build/lib/jsonwp-proxy/proxy-request.js.map +1 -1
  75. package/build/lib/jsonwp-proxy/proxy.d.ts.map +1 -1
  76. package/build/lib/jsonwp-proxy/proxy.js +29 -26
  77. package/build/lib/jsonwp-proxy/proxy.js.map +1 -1
  78. package/build/lib/protocol/errors.d.ts.map +1 -1
  79. package/build/lib/protocol/errors.js +25 -29
  80. package/build/lib/protocol/errors.js.map +1 -1
  81. package/build/lib/protocol/helpers.d.ts.map +1 -1
  82. package/build/lib/protocol/helpers.js +9 -8
  83. package/build/lib/protocol/helpers.js.map +1 -1
  84. package/build/lib/protocol/protocol.d.ts.map +1 -1
  85. package/build/lib/protocol/protocol.js +43 -48
  86. package/build/lib/protocol/protocol.js.map +1 -1
  87. package/build/lib/protocol/routes.d.ts +1 -1
  88. package/build/lib/protocol/routes.d.ts.map +1 -1
  89. package/build/lib/protocol/routes.js +9 -12
  90. package/build/lib/protocol/routes.js.map +1 -1
  91. package/build/lib/protocol/validators.d.ts.map +1 -1
  92. package/build/lib/protocol/validators.js +1 -5
  93. package/build/lib/protocol/validators.js.map +1 -1
  94. package/build/lib/utils.d.ts +16 -0
  95. package/build/lib/utils.d.ts.map +1 -0
  96. package/build/lib/utils.js +71 -0
  97. package/build/lib/utils.js.map +1 -0
  98. package/lib/basedriver/capabilities.ts +60 -55
  99. package/lib/basedriver/commands/bidi.ts +10 -10
  100. package/lib/basedriver/commands/event.ts +11 -10
  101. package/lib/basedriver/commands/execute.ts +3 -3
  102. package/lib/basedriver/commands/log.ts +3 -2
  103. package/lib/basedriver/commands/timeout.ts +5 -6
  104. package/lib/basedriver/core.ts +12 -12
  105. package/lib/basedriver/device-settings.ts +3 -4
  106. package/lib/basedriver/driver.ts +15 -13
  107. package/lib/basedriver/extension-core.ts +33 -7
  108. package/lib/basedriver/helpers.ts +28 -30
  109. package/lib/basedriver/ipc.ts +179 -0
  110. package/lib/basedriver/validation.ts +26 -26
  111. package/lib/express/express-logging.ts +3 -4
  112. package/lib/express/idempotency.ts +3 -3
  113. package/lib/express/middleware.ts +6 -8
  114. package/lib/express/server.ts +67 -61
  115. package/lib/express/static.ts +15 -7
  116. package/lib/express/websocket.ts +8 -10
  117. package/lib/helpers/capabilities.ts +18 -14
  118. package/lib/helpers/extension-command-name.ts +2 -2
  119. package/lib/helpers/levenshtein-match.ts +2 -5
  120. package/lib/index.js +1 -11
  121. package/lib/jsonwp-proxy/protocol-converter.ts +14 -15
  122. package/lib/jsonwp-proxy/proxy-request.ts +26 -26
  123. package/lib/jsonwp-proxy/proxy.ts +36 -37
  124. package/lib/protocol/errors.ts +29 -28
  125. package/lib/protocol/helpers.ts +9 -5
  126. package/lib/protocol/protocol.ts +44 -46
  127. package/lib/protocol/routes.ts +9 -9
  128. package/lib/protocol/validators.ts +1 -3
  129. package/lib/utils.ts +85 -0
  130. package/package.json +7 -9
package/lib/index.js CHANGED
@@ -1,19 +1,9 @@
1
- import B from 'bluebird';
2
-
3
- try {
4
- B.config({
5
- cancellation: true,
6
- });
7
- } catch {
8
- // sometimes during testing this somehow gets required twice and results in an error about
9
- // cancellation not being able to be enabled after promise has been configured
10
- }
11
-
12
1
  // BaseDriver exports
13
2
  export {ExtensionCore} from './basedriver/extension-core';
14
3
  import {BaseDriver} from './basedriver/driver';
15
4
  export {DriverCore} from './basedriver/core';
16
5
  export {DeviceSettings} from './basedriver/device-settings';
6
+ export {AppiumIpc} from './basedriver/ipc';
17
7
 
18
8
  export {BaseDriver};
19
9
  export default BaseDriver;
@@ -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';
@@ -138,12 +137,12 @@ export class ProtocolConverter {
138
137
  * provided in the request, we need to do 3 proxies and combine the result.
139
138
  */
140
139
  private getTimeoutRequestObjects(body: HTTPBody): Record<string, unknown>[] {
141
- if (_.isNil(body)) {
140
+ if (body == null) {
142
141
  return [];
143
142
  }
144
143
 
145
144
  const bodyObj = (util.safeJsonParse(body) as Record<string, unknown>) ?? {};
146
- if (this.downstreamProtocol === W3C && _.has(bodyObj, 'ms') && _.has(bodyObj, 'type')) {
145
+ if (this.downstreamProtocol === W3C && Object.hasOwn(bodyObj, 'ms') && Object.hasOwn(bodyObj, 'type')) {
147
146
  const typeToW3C = (x: string) => (x === 'page load' ? 'pageLoad' : x);
148
147
  return [
149
148
  {
@@ -152,9 +151,9 @@ export class ProtocolConverter {
152
151
  ];
153
152
  }
154
153
 
155
- if (this.downstreamProtocol === MJSONWP && (!_.has(bodyObj, 'ms') || !_.has(bodyObj, 'type'))) {
154
+ if (this.downstreamProtocol === MJSONWP && (!Object.hasOwn(bodyObj, 'ms') || !Object.hasOwn(bodyObj, 'type'))) {
156
155
  const typeToJSONWP = (x: string) => (x === 'pageLoad' ? 'page load' : x);
157
- return _.toPairs(bodyObj)
156
+ return Object.entries(bodyObj)
158
157
  // Only transform the entry if ms value is a valid positive float number
159
158
  .filter((pair) => /^\d+(?:[.,]\d*?)?$/.test(`${pair[1]}`))
160
159
  .map((pair) => ({
@@ -206,16 +205,16 @@ export class ProtocolConverter {
206
205
  body: HTTPBody
207
206
  ): Promise<[ProxyResponse, HTTPBody]> {
208
207
  const bodyObj = util.safeJsonParse(body);
209
- if (_.isPlainObject(bodyObj)) {
208
+ if (util.isPlainObject(bodyObj)) {
210
209
  const obj = bodyObj as Record<string, unknown>;
211
- if (this.downstreamProtocol === W3C && _.has(bodyObj, 'name') && !_.has(bodyObj, 'handle')) {
210
+ if (this.downstreamProtocol === W3C && Object.hasOwn(bodyObj, 'name') && !Object.hasOwn(bodyObj, 'handle')) {
212
211
  this.log.debug(`Copied 'name' value '${obj.name}' to 'handle' as per W3C spec`);
213
212
  return await this.proxyFunc(url, method, {...obj, handle: obj.name});
214
213
  }
215
214
  if (
216
215
  this.downstreamProtocol === MJSONWP &&
217
- _.has(bodyObj, 'handle') &&
218
- !_.has(bodyObj, 'name')
216
+ Object.hasOwn(bodyObj, 'handle') &&
217
+ !Object.hasOwn(bodyObj, 'name')
219
218
  ) {
220
219
  this.log.debug(`Copied 'handle' value '${obj.handle}' to 'name' as per JSONWP spec`);
221
220
  return await this.proxyFunc(url, method, {...obj, name: obj.handle});
@@ -230,13 +229,13 @@ export class ProtocolConverter {
230
229
  body: HTTPBody
231
230
  ): Promise<[ProxyResponse, HTTPBody]> {
232
231
  const bodyObj = util.safeJsonParse(body) as Record<string, unknown> | undefined;
233
- if (_.isPlainObject(bodyObj) && (util.hasValue(bodyObj?.text) || util.hasValue(bodyObj?.value))) {
232
+ if (util.isPlainObject(bodyObj) && (util.hasValue(bodyObj?.text) || util.hasValue(bodyObj?.value))) {
234
233
  let {text, value} = bodyObj;
235
234
  if (util.hasValue(text) && !util.hasValue(value)) {
236
- value = _.isString(text) ? [...text] : _.isArray(text) ? text : [];
235
+ value = typeof text === 'string' ? [...text] : Array.isArray(text) ? text : [];
237
236
  this.log.debug(`Added 'value' property to 'setValue' request body`);
238
237
  } else if (!util.hasValue(text) && util.hasValue(value)) {
239
- text = _.isArray(value) ? value.join('') : _.isString(value) ? value : '';
238
+ text = Array.isArray(value) ? value.join('') : typeof value === 'string' ? value : '';
240
239
  this.log.debug(`Added 'text' property to 'setValue' request body`);
241
240
  }
242
241
  return await this.proxyFunc(url, method, {...bodyObj, text, value});
@@ -250,10 +249,10 @@ export class ProtocolConverter {
250
249
  body: HTTPBody
251
250
  ): Promise<[ProxyResponse, HTTPBody]> {
252
251
  const bodyObj = util.safeJsonParse(body);
253
- if (_.has(bodyObj, 'id') && _.isPlainObject(bodyObj.id)) {
252
+ if (Object.hasOwn(bodyObj ?? {}, 'id') && util.isPlainObject((bodyObj as Record<string, unknown>).id)) {
254
253
  return await this.proxyFunc(url, method, {
255
254
  ...(bodyObj as object),
256
- id: duplicateKeys(bodyObj.id as object, MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY),
255
+ id: duplicateKeys((bodyObj as Record<string, unknown>).id as object, MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY),
257
256
  });
258
257
  }
259
258
  return await this.proxyFunc(url, method, body);
@@ -265,7 +264,7 @@ export class ProtocolConverter {
265
264
  body: HTTPBody
266
265
  ): Promise<[ProxyResponse, HTTPBody]> {
267
266
  const bodyObj = util.safeJsonParse(body);
268
- if (_.isPlainObject(bodyObj)) {
267
+ if (util.isPlainObject(bodyObj)) {
269
268
  return await this.proxyFunc(
270
269
  url,
271
270
  method,
@@ -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';
@@ -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;
@@ -153,7 +157,7 @@ export class JWProxy {
153
157
  const requiresSessionId =
154
158
  !commandName || (commandName && isSessionCommand(commandName));
155
159
  const proxyPrefix = `${this.scheme}://${this.server}:${this.port}${this.base}`;
156
- let proxySuffix = normalizedPathname ? `/${_.trimStart(normalizedPathname, '/')}` : '';
160
+ let proxySuffix = normalizedPathname ? `/${normalizedPathname.replace(/^\/+/, '')}` : '';
157
161
  if (parsedUrl.search) {
158
162
  proxySuffix += parsedUrl.search;
159
163
  }
@@ -179,7 +183,7 @@ export class JWProxy {
179
183
  method = method.toUpperCase();
180
184
  const newUrl = this.getUrlForProxy(url, method as HTTPMethod);
181
185
  const truncateBody = (content: unknown): string =>
182
- _.truncate(_.isString(content) ? content : JSON.stringify(content), {
186
+ util.truncateString(typeof content === 'string' ? content : JSON.stringify(content), {
183
187
  length: MAX_LOG_BODY_LENGTH,
184
188
  });
185
189
  const reqOpts: RawAxiosRequestConfig = {
@@ -242,7 +246,7 @@ export class JWProxy {
242
246
  const {data, status, headers} = await this.request(reqOpts);
243
247
  // `data` might be really big
244
248
  // Be careful while handling it to avoid memory leaks
245
- if (!_.isPlainObject(data)) {
249
+ if (!util.isPlainObject(data)) {
246
250
  // The response should be a valid JSON object
247
251
  // If it cannot be coerced to an object then the response is wrong
248
252
  throwProxyError(data);
@@ -252,23 +256,15 @@ export class JWProxy {
252
256
  const isSessionCreationRequest = url.endsWith('/session') && method === 'POST';
253
257
  if (isSessionCreationRequest) {
254
258
  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;
259
+ const value = data.value as Record<string, unknown> | undefined;
260
+ const raw = data.sessionId ?? value?.sessionId;
260
261
  this.sessionId =
261
262
  typeof raw === 'string' ? raw : raw != null ? String(raw) : null;
262
263
  }
263
- this.downstreamProtocol = this.getProtocolFromResBody(
264
- data as Record<string, unknown>
265
- ) ?? this.downstreamProtocol;
264
+ this.downstreamProtocol = this.getProtocolFromResBody(data) ?? this.downstreamProtocol;
266
265
  this.log.info(`Determined the downstream protocol as '${this.downstreamProtocol}'`);
267
266
  }
268
- if (
269
- _.has(data, 'status') &&
270
- parseInt((data as Record<string, unknown>).status as string, 10) !== 0
271
- ) {
267
+ if (Object.hasOwn(data, 'status') && parseInt(data.status as string, 10) !== 0) {
272
268
  throwProxyError(data);
273
269
  }
274
270
  return [
@@ -307,10 +303,10 @@ export class JWProxy {
307
303
  * Detects the downstream protocol from a response body.
308
304
  */
309
305
  getProtocolFromResBody(resObj: Record<string, unknown>): Protocol | undefined {
310
- if (_.isInteger(resObj.status)) {
306
+ if (Number.isInteger(resObj.status)) {
311
307
  return MJSONWP;
312
308
  }
313
- if (!_.isUndefined(resObj.value)) {
309
+ if (resObj.value !== undefined) {
314
310
  return W3C;
315
311
  }
316
312
  }
@@ -363,10 +359,10 @@ export class JWProxy {
363
359
  const status = parseInt(resBody.status as string, 10);
364
360
  if (!isNaN(status) && status !== 0) {
365
361
  let message: unknown = resBody.value;
366
- if (_.isPlainObject(message) && _.has(message, 'message')) {
362
+ if (util.isPlainObject(message) && Object.hasOwn(message, 'message')) {
367
363
  message = (message as Record<string, unknown>).message;
368
364
  }
369
- throw errorFromMJSONWPStatusCode(status, _.isEmpty(message)
365
+ throw errorFromMJSONWPStatusCode(status, util.isEmpty(message)
370
366
  ? getSummaryByCode(status)
371
367
  : (message as string | {message: string}));
372
368
  }
@@ -374,8 +370,8 @@ export class JWProxy {
374
370
  if (response.statusCode < 300) {
375
371
  return resBody.value;
376
372
  }
377
- if (_.isPlainObject(resBody.value) && (resBody.value as Record<string, unknown>).error) {
378
- const value = resBody.value as Record<string, unknown>;
373
+ if (util.isPlainObject(resBody.value) && resBody.value.error) {
374
+ const value = resBody.value;
379
375
  throw errorFromW3CJsonCode(
380
376
  value.error as string,
381
377
  (value.message as string) ?? '',
@@ -387,7 +383,7 @@ export class JWProxy {
387
383
  }
388
384
  throw new errors.UnknownError(
389
385
  `Did not know what to do with response code '${response.statusCode}' ` +
390
- `and response body '${_.truncate(JSON.stringify(resBodyObj), {
386
+ `and response body '${util.truncateString(JSON.stringify(resBodyObj), {
391
387
  length: 300,
392
388
  })}'`
393
389
  );
@@ -424,16 +420,16 @@ export class JWProxy {
424
420
  );
425
421
  }
426
422
  res.setHeader('content-type', 'application/json; charset=utf-8');
427
- if (!_.isPlainObject(resBodyObj)) {
423
+ if (!util.isPlainObject(resBodyObj)) {
428
424
  const error = new errors.UnknownError(
429
425
  `The downstream server response with the status code ${statusCode} is not a valid JSON object: ` +
430
- _.truncate(`${resBodyObj}`, {length: 300})
426
+ util.truncateString(`${resBodyObj}`, {length: 300})
431
427
  );
432
428
  [statusCode, resBodyObj] = getResponseForW3CError(error);
433
429
  }
434
430
 
435
431
  const resBody = resBodyObj as Record<string, unknown>;
436
- if (_.has(resBody, 'sessionId')) {
432
+ if (Object.hasOwn(resBody, 'sessionId')) {
437
433
  const reqSessionId = this.getSessionIdFromUrl(req.originalUrl);
438
434
  if (reqSessionId) {
439
435
  this.log.info(`Replacing sessionId ${resBody.sessionId} with ${reqSessionId}`);
@@ -459,7 +455,10 @@ export class JWProxy {
459
455
  try {
460
456
  return await req.execute();
461
457
  } finally {
462
- _.pull(this._activeRequests, req);
458
+ const reqIndex = this._activeRequests.indexOf(req);
459
+ if (reqIndex >= 0) {
460
+ this._activeRequests.splice(reqIndex, 1);
461
+ }
463
462
  }
464
463
  }
465
464
 
@@ -467,8 +466,8 @@ export class JWProxy {
467
466
  // eslint-disable-next-line n/no-deprecated-api -- we need relative URL support
468
467
  const parsedUrl = nodeUrl.parse(url || '/');
469
468
  if (
470
- _.isNil(parsedUrl.href) ||
471
- _.isNil(parsedUrl.pathname) ||
469
+ parsedUrl.href == null ||
470
+ parsedUrl.pathname == null ||
472
471
  (parsedUrl.protocol && !['http:', 'https:'].includes(parsedUrl.protocol))
473
472
  ) {
474
473
  throw new Error(`Did not know how to proxy the url '${url}'`);
@@ -477,7 +476,7 @@ export class JWProxy {
477
476
  }
478
477
 
479
478
  private _toNormalizedPathname(parsedUrl: nodeUrl.UrlWithStringQuery): string {
480
- if (!_.isString(parsedUrl.pathname)) {
479
+ if (typeof parsedUrl.pathname !== 'string') {
481
480
  return '';
482
481
  }
483
482
  let pathname =
@@ -488,20 +487,20 @@ export class JWProxy {
488
487
  // This is needed for the backward compatibility
489
488
  // if drivers don't set reqBasePath properly
490
489
  if (!this.reqBasePath) {
491
- if (match && match.params && _.isArray((match.params as Record<string, unknown>).prefix)) {
490
+ if (match && match.params && Array.isArray((match.params as Record<string, unknown>).prefix)) {
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
  }
@@ -1,4 +1,3 @@
1
- import _ from 'lodash';
2
1
  import {util, logger} from '@appium/support';
3
2
  import {StatusCodes as HTTPStatusCodes} from 'http-status-codes';
4
3
  import type {ErrorBiDiCommandResponse, Class} from '@appium/types';
@@ -22,13 +21,13 @@ class BaseError extends Error {
22
21
 
23
22
  private _formatStack(): void {
24
23
  // eslint-disable-next-line no-prototype-builtins
25
- if (Error.hasOwnProperty('captureStackTrace') && _.isEmpty(this.stack)) {
24
+ if (Error.hasOwnProperty('captureStackTrace') && util.isEmpty(this.stack)) {
26
25
  Error.captureStackTrace(this, this.constructor);
27
26
  }
28
- if (!_.isString(this.cause?.stack)) {
27
+ if (typeof this.cause?.stack !== 'string') {
29
28
  return;
30
29
  }
31
- if (_.isEmpty(this.stack)) {
30
+ if (util.isEmpty(this.stack)) {
32
31
  this.stack = this.cause.stack;
33
32
  return;
34
33
  }
@@ -77,7 +76,7 @@ export class ProtocolError extends BaseError {
77
76
  bidiErrObject(id: string | number): ErrorBiDiCommandResponse {
78
77
  // if we don't have an id, the client didn't send one, so we have nothing to send back.
79
78
  // send back zero rather than making something up
80
- const intId = (_.isInteger(id) ? id : (parseInt(`${id}`, 10) || 0)) as number;
79
+ const intId = (Number.isInteger(id) ? id : (parseInt(`${id}`, 10) || 0)) as number;
81
80
  return {
82
81
  id: intId,
83
82
  type: 'error',
@@ -957,30 +956,30 @@ export class ProxyRequestError extends BaseError {
957
956
  ) {
958
957
  const [responseErrorObj, originalMessage] = ProxyRequestError._parseHttpResponse(httpResponseData);
959
958
  super(
960
- _.isEmpty(message)
959
+ util.isEmpty(message)
961
960
  ? `Proxy request unsuccessful.${originalMessage ? (' ' + originalMessage) : ''}`
962
961
  : message,
963
962
  cause,
964
963
  );
965
964
 
966
965
  // If the response error is an object and value is an object, it's a W3C error (for JSONWP value is a string)
967
- if (_.isPlainObject(responseErrorObj.value) && _.has(responseErrorObj.value, 'error')) {
968
- this._w3cError = responseErrorObj.value;
966
+ if (util.isPlainObject(responseErrorObj.value) && Object.hasOwn(responseErrorObj.value, 'error')) {
967
+ this._w3cError = responseErrorObj.value as unknown as typeof this._w3cError;
969
968
  this._w3cErrorStatus = httpStatus;
970
- } else if (_.has(responseErrorObj, 'status')) {
971
- this._jwpError = responseErrorObj;
969
+ } else if (Object.hasOwn(responseErrorObj, 'status')) {
970
+ this._jwpError = responseErrorObj as typeof this._jwpError;
972
971
  }
973
972
  }
974
973
 
975
974
  private static _parseHttpResponse(data: any): [Record<string, any>, string] {
976
975
  let responseErrorObj: Record<string, any> = util.safeJsonParse(data);
977
- if (!_.isPlainObject(responseErrorObj)) {
976
+ if (!util.isPlainObject(responseErrorObj)) {
978
977
  responseErrorObj = {};
979
978
  }
980
- let errorMessage: string = _.isString(data) ? data : '';
981
- if (_.isString(responseErrorObj.value)) {
979
+ let errorMessage: string = typeof data === 'string' ? data : '';
980
+ if (typeof responseErrorObj.value === 'string') {
982
981
  errorMessage = responseErrorObj.value;
983
- } else if (_.isString(responseErrorObj.value?.message)) {
982
+ } else if (typeof responseErrorObj.value?.message === 'string') {
984
983
  errorMessage = responseErrorObj.value.message;
985
984
  }
986
985
  return [responseErrorObj, errorMessage];
@@ -991,7 +990,7 @@ export class ProxyRequestError extends BaseError {
991
990
  // If it's MJSONWP error, returns actual error cause for request failure based on `jsonwp.status`
992
991
  return errorFromMJSONWPStatusCode(this._jwpError.status, this._jwpError.value);
993
992
  }
994
- if (util.hasValue(this._w3cError) && _.isNumber(this._w3cErrorStatus) && this._w3cErrorStatus >= 300) {
993
+ if (util.hasValue(this._w3cError) && typeof this._w3cErrorStatus === 'number' && this._w3cErrorStatus >= 300) {
995
994
  return errorFromW3CJsonCode(
996
995
  this._w3cError.error,
997
996
  this._w3cError.message || this.message,
@@ -1007,10 +1006,10 @@ function generateBadParametersMessage(
1007
1006
  paramNames: string[]
1008
1007
  ): string {
1009
1008
  const toArray = function <T> (x: T | T[]): T[] {
1010
- if (_.isUndefined(x)) {
1009
+ if (x === undefined) {
1011
1010
  return [];
1012
1011
  }
1013
- if (_.isArray(x)) {
1012
+ if (Array.isArray(x)) {
1014
1013
  return x;
1015
1014
  }
1016
1015
  return [x];
@@ -1018,26 +1017,28 @@ function generateBadParametersMessage(
1018
1017
 
1019
1018
  const requiredParamNames = toArray(paramRequirements.required);
1020
1019
  const actualParamNames = toArray(paramNames);
1021
- const missingRequiredParamNames = _.difference(requiredParamNames, actualParamNames);
1020
+ const missingRequiredParamNames = requiredParamNames.filter((name) => !actualParamNames.includes(name));
1022
1021
  const resultLines: string[] = [];
1023
1022
  resultLines.push(
1024
- _.isEmpty(missingRequiredParamNames)
1023
+ util.isEmpty(missingRequiredParamNames)
1025
1024
  ? // This should not happen
1026
1025
  'Some of the provided parameters are not known'
1027
1026
  : `The following required parameter${
1028
1027
  missingRequiredParamNames.length === 1 ? ' is' : 's are'
1029
1028
  } missing: ${JSON.stringify(missingRequiredParamNames)}`,
1030
1029
  );
1031
- if (!_.isEmpty(requiredParamNames)) {
1030
+ if (!util.isEmpty(requiredParamNames)) {
1032
1031
  resultLines.push(`Known required parameters are: ${JSON.stringify(requiredParamNames)}`);
1033
1032
  }
1034
- const optionalParamNames = _.difference(toArray(paramRequirements.optional), ['sessionId', 'id']);
1035
- if (!_.isEmpty(optionalParamNames)) {
1033
+ const optionalParamNames = toArray(paramRequirements.optional).filter(
1034
+ (name): name is string => typeof name === 'string' && !['sessionId', 'id'].includes(name)
1035
+ );
1036
+ if (!util.isEmpty(optionalParamNames)) {
1036
1037
  resultLines.push(`Known optional parameters are: ${JSON.stringify(optionalParamNames)}`);
1037
1038
  }
1038
1039
  resultLines.push(
1039
1040
  `You have provided${
1040
- _.isEmpty(actualParamNames) ? ' none' : ': ' + JSON.stringify(paramNames)
1041
+ util.isEmpty(actualParamNames) ? ' none' : ': ' + JSON.stringify(paramNames)
1041
1042
  }`,
1042
1043
  );
1043
1044
  return resultLines.join('\n');
@@ -1087,7 +1088,7 @@ export const errors = {
1087
1088
  ProxyRequestError,
1088
1089
  } as const;
1089
1090
 
1090
- const jsonwpErrorCodeMap: Record<string, Class<ProtocolError>> = _.values(errors)
1091
+ const jsonwpErrorCodeMap: Record<string, Class<ProtocolError>> = Object.values(errors)
1091
1092
  .reduce((acc: Record<string, Class<ProtocolError>>, ErrorClass: any) => {
1092
1093
  if ('code' in ErrorClass) {
1093
1094
  acc[ErrorClass.code()] = ErrorClass;
@@ -1095,7 +1096,7 @@ const jsonwpErrorCodeMap: Record<string, Class<ProtocolError>> = _.values(errors
1095
1096
  return acc;
1096
1097
  }, {});
1097
1098
 
1098
- const w3cErrorCodeMap: Record<string, Class<ProtocolError>> = _.values(errors)
1099
+ const w3cErrorCodeMap: Record<string, Class<ProtocolError>> = Object.values(errors)
1099
1100
  .reduce((acc: Record<string, Class<ProtocolError>>, ErrorClass: any) => {
1100
1101
  if ('error' in ErrorClass) {
1101
1102
  acc[ErrorClass.error()] = ErrorClass;
@@ -1151,7 +1152,7 @@ export function errorFromMJSONWPStatusCode(code: number, value: string | {messag
1151
1152
  * @return The error that is associated with the W3C error string
1152
1153
  */
1153
1154
  export function errorFromW3CJsonCode(signature: string, message: string, stacktrace?: string): ProtocolError {
1154
- const ErrorClass = w3cErrorCodeMap[_.toLower(signature)] ?? UnknownError;
1155
+ const ErrorClass = w3cErrorCodeMap[signature.toLowerCase()] ?? UnknownError;
1155
1156
  w3cLog.debug(`Matched W3C error code '${signature}' to ${ErrorClass.name}`);
1156
1157
  const resultError = new ErrorClass(message);
1157
1158
  resultError.stacktrace = stacktrace;
@@ -1177,12 +1178,12 @@ export function getResponseForW3CError(err: any): [number, { value: W3CError }]
1177
1178
  ];
1178
1179
 
1179
1180
  // err is ProtocolError
1180
- if (['error', 'w3cStatus'].every((prop) => _.has(err, prop))) {
1181
+ if (['error', 'w3cStatus'].every((prop) => Object.hasOwn(err, prop))) {
1181
1182
  return protocolErrorToResponse(err);
1182
1183
  }
1183
1184
 
1184
1185
  // err is ProxyRequestError
1185
- if (_.has(err, 'getActualError') && _.isFunction(err.getActualError)) {
1186
+ if (Object.hasOwn(err, 'getActualError') && typeof err.getActualError === 'function') {
1186
1187
  return protocolErrorToResponse(err.getActualError());
1187
1188
  }
1188
1189
 
@@ -1,4 +1,4 @@
1
- import _ from 'lodash';
1
+ import {util} from '@appium/support';
2
2
  import {duplicateKeys} from '../basedriver/helpers';
3
3
  import {MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY} from '../constants';
4
4
 
@@ -11,7 +11,7 @@ import {MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY} from '../constants';
11
11
  * @returns Either modified value or the same one if nothing has been modified
12
12
  */
13
13
  export function formatResponseValue(resValue: object | undefined): object | null {
14
- if (_.isUndefined(resValue)) {
14
+ if (resValue === undefined) {
15
15
  // convert undefined to null
16
16
  return null;
17
17
  }
@@ -30,7 +30,11 @@ export function formatResponseValue(resValue: object | undefined): object | null
30
30
  * @returns The fixed response body
31
31
  */
32
32
  export function ensureW3cResponse(responseBody: Record<string, unknown>): Record<string, unknown> {
33
- return _.isPlainObject(responseBody)
34
- ? (_.omit(responseBody, ['status', 'sessionId']) as Record<string, unknown>)
35
- : responseBody;
33
+ if (!util.isPlainObject(responseBody)) {
34
+ return responseBody;
35
+ }
36
+ const result = {...responseBody};
37
+ delete result.status;
38
+ delete result.sessionId;
39
+ return result;
36
40
  }