@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
@@ -1,6 +1,6 @@
1
1
  import type {Constraint} from '@appium/types';
2
+ import {util} from '@appium/support';
2
3
  import {log} from './logger';
3
- import _ from 'lodash';
4
4
 
5
5
  export class Validator {
6
6
  private readonly _validators: Record<
@@ -8,27 +8,27 @@ export class Validator {
8
8
  (value: any, options?: any, key?: string) => string | null
9
9
  > = {
10
10
  isString: (value: any, options?: any): string | null => {
11
- if (_.isUndefined(value) || _.isNil(options)) {
11
+ if (value === undefined || options == null) {
12
12
  return null;
13
13
  }
14
14
 
15
- if (_.isString(value)) {
15
+ if (typeof value === 'string') {
16
16
  return options ? null : 'must not be of type string';
17
17
  }
18
18
 
19
19
  return options ? 'must be of type string' : null;
20
20
  },
21
21
  isNumber: (value: any, options?: any): string | null => {
22
- if (_.isUndefined(value) || _.isNil(options)) {
22
+ if (value === undefined || options == null) {
23
23
  return null;
24
24
  }
25
25
 
26
- if (_.isNumber(value)) {
26
+ if (typeof value === 'number') {
27
27
  return options ? null : 'must not be of type number';
28
28
  }
29
29
 
30
30
  // allow a string value
31
- if (options && _.isString(value) && !isNaN(Number(value))) {
31
+ if (options && typeof value === 'string' && !isNaN(Number(value))) {
32
32
  log.warn('Number capability passed in as string. Functionality may be compromised.');
33
33
  return null;
34
34
  }
@@ -36,45 +36,45 @@ export class Validator {
36
36
  return options ? 'must be of type number' : null;
37
37
  },
38
38
  isBoolean: (value: any, options?: any): string | null => {
39
- if (_.isUndefined(value) || _.isNil(options)) {
39
+ if (value === undefined || options == null) {
40
40
  return null;
41
41
  }
42
42
 
43
- if (_.isBoolean(value)) {
43
+ if (typeof value === 'boolean') {
44
44
  return options ? null : 'must not be of type boolean';
45
45
  }
46
46
 
47
47
  // allow a string value
48
- if (options && _.isString(value) && ['true', 'false', ''].includes(value)) {
48
+ if (options && typeof value === 'string' && ['true', 'false', ''].includes(value)) {
49
49
  return null;
50
50
  }
51
51
 
52
52
  return options ? 'must be of type boolean' : null;
53
53
  },
54
54
  isObject: (value: any, options?: any): string | null => {
55
- if (_.isUndefined(value) || _.isNil(options)) {
55
+ if (value === undefined || options == null) {
56
56
  return null;
57
57
  }
58
58
 
59
- if (_.isPlainObject(value)) {
59
+ if (util.isPlainObject(value)) {
60
60
  return options ? null : 'must not be a plain object';
61
61
  }
62
62
 
63
63
  return options ? 'must be a plain object' : null;
64
64
  },
65
65
  isArray: (value: any, options?: any): string | null => {
66
- if (_.isUndefined(value) || _.isNil(options)) {
66
+ if (value === undefined || options == null) {
67
67
  return null;
68
68
  }
69
69
 
70
- if (_.isArray(value)) {
70
+ if (Array.isArray(value)) {
71
71
  return options ? null : 'must not be of type array';
72
72
  }
73
73
 
74
74
  return options ? 'must be of type array' : null;
75
75
  },
76
76
  deprecated: (value: any, options?: any, key?: string): string | null => {
77
- if (!_.isUndefined(value) && options) {
77
+ if (value !== undefined && options) {
78
78
  log.warn(
79
79
  `The '${key}' capability has been deprecated and must not be used anymore. ` +
80
80
  `Please check the driver documentation for possible alternatives.`
@@ -83,32 +83,32 @@ export class Validator {
83
83
  return null;
84
84
  },
85
85
  inclusion: (value: any, options?: any): string | null => {
86
- if (_.isUndefined(value) || !options) {
86
+ if (value === undefined || !options) {
87
87
  return null;
88
88
  }
89
- const optionsArr = _.isArray(options) ? options : [options];
89
+ const optionsArr = Array.isArray(options) ? options : [options];
90
90
  if (optionsArr.some((opt) => opt === value)) {
91
91
  return null;
92
92
  }
93
93
  return `must be contained by ${JSON.stringify(optionsArr)}`;
94
94
  },
95
95
  inclusionCaseInsensitive: (value: any, options?: any): string | null => {
96
- if (_.isUndefined(value) || !options) {
96
+ if (value === undefined || !options) {
97
97
  return null;
98
98
  }
99
- const optionsArr = _.isArray(options) ? options : [options];
100
- if (optionsArr.some((opt) => _.toLower(opt) === _.toLower(value))) {
99
+ const optionsArr = Array.isArray(options) ? options : [options];
100
+ if (optionsArr.some((opt) => String(opt).toLowerCase() === String(value).toLowerCase())) {
101
101
  return null;
102
102
  }
103
103
  return `must be contained by ${JSON.stringify(optionsArr)}`;
104
104
  },
105
105
  presence: (value: any, options?: any): string | null => {
106
- if (_.isUndefined(value) && options) {
106
+ if (value === undefined && options) {
107
107
  return 'is required to be present';
108
108
  }
109
109
  if (
110
110
  !options?.allowEmpty &&
111
- ((!_.isUndefined(value) && _.isEmpty(value)) || (_.isString(value) && !_.trim(value)))
111
+ ((value !== undefined && util.isEmpty(value)) || (typeof value === 'string' && !value.trim()))
112
112
  ) {
113
113
  return 'must not be empty or blank';
114
114
  }
@@ -118,15 +118,15 @@ export class Validator {
118
118
 
119
119
  validate(values: Record<string, any>, constraints: Record<string, Constraint>): Record<string, string[]> | null {
120
120
  const result: Record<string, string[]> = {};
121
- for (const [key, constraint] of _.toPairs(constraints)) {
121
+ for (const [key, constraint] of Object.entries(constraints)) {
122
122
  const value = values[key];
123
- for (const [validatorName, options] of _.toPairs(constraint)) {
123
+ for (const [validatorName, options] of Object.entries(constraint)) {
124
124
  if (!(validatorName in this._validators)) {
125
125
  continue;
126
126
  }
127
127
 
128
- const validationError = this._validators[validatorName](value, options, key);
129
- if (_.isNil(validationError)) {
128
+ const validationError = this._validators[validatorName as keyof Constraint](value, options, key);
129
+ if (validationError == null) {
130
130
  continue;
131
131
  }
132
132
 
@@ -137,7 +137,7 @@ export class Validator {
137
137
  }
138
138
  }
139
139
  }
140
- return _.isEmpty(result) ? null : result;
140
+ return util.isEmpty(result) ? null : result;
141
141
  }
142
142
 
143
143
  }
@@ -1,10 +1,9 @@
1
- import _ from 'lodash';
1
+ import {util, logger} from '@appium/support';
2
2
  import '@colors/colors';
3
3
  import morgan from 'morgan';
4
4
  import type {Request, RequestHandler, Response} from 'express';
5
5
  import {log} from './logger';
6
6
  import {MAX_LOG_BODY_LENGTH} from '../constants';
7
- import {logger} from '@appium/support';
8
7
 
9
8
  /**
10
9
  * Morgan middleware that logs when the HTTP response finishes.
@@ -32,8 +31,8 @@ function startLogFormatterHandler(tokens: unknown, req: Request, res: Response):
32
31
  let reqBody = '';
33
32
  if (req.body) {
34
33
  try {
35
- reqBody = _.truncate(
36
- _.isString(req.body) ? req.body : JSON.stringify(req.body),
34
+ reqBody = util.truncateString(
35
+ typeof req.body === 'string' ? req.body : JSON.stringify(req.body),
37
36
  {length: MAX_LOG_BODY_LENGTH}
38
37
  );
39
38
  } catch {
@@ -1,6 +1,6 @@
1
1
  import {log} from './logger';
2
2
  import {LRUCache} from 'lru-cache';
3
- import _ from 'lodash';
3
+ import {util} from '@appium/support';
4
4
  import {EventEmitter} from 'node:events';
5
5
  import type {Request, Response, NextFunction} from 'express';
6
6
 
@@ -34,12 +34,12 @@ export async function handleIdempotency(
34
34
  next: NextFunction
35
35
  ): Promise<void> {
36
36
  const keyOrArr = req.headers[IDEMPOTENCY_KEY_HEADER];
37
- if (_.isEmpty(keyOrArr) || !keyOrArr) {
37
+ if (util.isEmpty(keyOrArr) || !keyOrArr) {
38
38
  next();
39
39
  return;
40
40
  }
41
41
 
42
- const key = _.isArray(keyOrArr) ? keyOrArr[0] : keyOrArr;
42
+ const key = Array.isArray(keyOrArr) ? keyOrArr[0] : keyOrArr;
43
43
 
44
44
  log.updateAsyncContext({idempotencyKey: key});
45
45
 
@@ -1,4 +1,4 @@
1
- import _ from 'lodash';
1
+ import {util} from '@appium/support';
2
2
  import type {NextFunction, Request, RequestHandler, Response} from 'express';
3
3
  import type {IncomingMessage} from 'node:http';
4
4
  import type {Duplex} from 'node:stream';
@@ -6,7 +6,6 @@ import {log} from './logger';
6
6
  import {errors} from '../protocol';
7
7
  export {handleIdempotency} from './idempotency';
8
8
  import {match} from 'path-to-regexp';
9
- import {util} from '@appium/support';
10
9
  import {calcSignature} from '../helpers/session';
11
10
  import {getResponseForW3CError} from '../protocol/errors';
12
11
  import type {StringRecord, WSServer} from '@appium/types';
@@ -46,7 +45,7 @@ export function allowCrossDomainAsyncExecute(basePath: string): RequestHandler {
46
45
  next: NextFunction
47
46
  ): void {
48
47
  const receiveAsyncResponseRegExp = new RegExp(
49
- `${_.escapeRegExp(basePath)}/session/[a-f0-9-]+/(appium/)?receive_async_response`
48
+ `${util.escapeRegExp(basePath)}/session/[a-f0-9-]+/(appium/)?receive_async_response`
50
49
  );
51
50
  if (!receiveAsyncResponseRegExp.test(req.url)) {
52
51
  next();
@@ -73,7 +72,7 @@ export function handleLogContext(req: Request, _res: Response, next: NextFunctio
73
72
  {
74
73
  requestId,
75
74
  ...sessionInfo,
76
- isSensitive: ['true', '1', 'yes'].includes(_.toLower(isSensitiveHeaderValue)),
75
+ isSensitive: ['true', '1', 'yes'].includes(String(isSensitiveHeaderValue ?? '').toLowerCase()),
77
76
  },
78
77
  true
79
78
  );
@@ -110,7 +109,7 @@ export function tryHandleWebSocketUpgrade(
110
109
  head: Buffer,
111
110
  webSocketsMapping: StringRecord<WSServer>
112
111
  ): boolean {
113
- if (_.toLower(req.headers?.upgrade) !== 'websocket') {
112
+ if (String(req.headers?.upgrade ?? '').toLowerCase() !== 'websocket') {
114
113
  return false;
115
114
  }
116
115
 
@@ -120,7 +119,7 @@ export function tryHandleWebSocketUpgrade(
120
119
  } catch {
121
120
  currentPathname = req.url ?? '';
122
121
  }
123
- for (const [pathname, wsServer] of _.toPairs(webSocketsMapping)) {
122
+ for (const [pathname, wsServer] of Object.entries(webSocketsMapping)) {
124
123
  if (match(pathname)(currentPathname)) {
125
124
  wsServer.handleUpgrade(req, socket, head, (ws) => {
126
125
  wsServer.emit('connection', ws, req);
@@ -182,6 +181,5 @@ export function catch404Handler(req: Request, res: Response): void {
182
181
 
183
182
  function fetchHeaderValue(req: Request, name: string): string | undefined {
184
183
  const value = req.headers[name];
185
- return _.isArray(value) ? value[0] : (value as string | undefined);
184
+ return Array.isArray(value) ? value[0] : (value as string | undefined);
186
185
  }
187
-
@@ -1,4 +1,3 @@
1
- import _ from 'lodash';
2
1
  import path from 'node:path';
3
2
  import express from 'express';
4
3
  import type {Express, RequestHandler} from 'express';
@@ -34,7 +33,6 @@ import {
34
33
  removeAllWebSocketHandlers,
35
34
  getWebSocketHandlers,
36
35
  } from './websocket';
37
- import B from 'bluebird';
38
36
  import {DEFAULT_BASE_PATH} from '../constants';
39
37
  import {fs, timing} from '@appium/support';
40
38
  import type {
@@ -123,51 +121,51 @@ export async function server(opts: ServerOpts): Promise<AppiumServer> {
123
121
  const app = express();
124
122
  const httpServer = await createServer(app, cliArgs);
125
123
 
126
- return await new B<AppiumServer>(async (resolve, reject) => {
127
- // we put an async function as the promise constructor because we want some things to happen in
128
- // serial (application of plugin updates, for example). But we still need to use a promise here
129
- // because some elements of server start failure only happen in httpServer listeners. So the
130
- // way we resolve it is to use an async function here but to wrap all the inner logic in
131
- // try/catch so any errors can be passed to reject.
132
- try {
133
- const appiumServer = configureHttp({
134
- httpServer,
135
- reject,
136
- keepAliveTimeout,
137
- gracefulShutdownTimeout: cliArgs.shutdownTimeout,
138
- });
139
- const useLegacyUpgradeHandler = !hasShouldUpgradeCallback(httpServer);
140
- configureServer({
141
- app,
142
- addRoutes: routeConfiguringFunction,
143
- allowCors,
144
- basePath,
145
- extraMethodMap,
146
- webSocketsMapping: appiumServer.webSocketsMapping,
147
- useLegacyUpgradeHandler,
148
- });
149
- // allow extensions to update the app and http server objects
150
- for (const updater of serverUpdaters) {
151
- await updater(app, appiumServer, cliArgs);
152
- }
153
-
154
- // once all configurations and updaters have been applied, make sure to set up a catchall
155
- // handler so that anything unknown 404s. But do this after everything else since we don't
156
- // want to block extensions' ability to add routes if they want.
157
- app.all('/*all', catch404Handler);
158
-
159
- await startServer({
160
- httpServer,
161
- hostname,
162
- port,
163
- keepAliveTimeout,
164
- requestTimeout,
165
- });
124
+ return await new Promise<AppiumServer>((resolve, reject) => {
125
+ // we use a promise here because some elements of server start failure only happen in
126
+ // httpServer listeners. The async IIFE runs setup serially (e.g. plugin updates) while
127
+ // configureHttp can still reject via the listener registered below.
128
+ void (async () => {
129
+ try {
130
+ const appiumServer = configureHttp({
131
+ httpServer,
132
+ reject,
133
+ keepAliveTimeout,
134
+ gracefulShutdownTimeout: cliArgs.shutdownTimeout,
135
+ });
136
+ const useLegacyUpgradeHandler = !hasShouldUpgradeCallback(httpServer);
137
+ configureServer({
138
+ app,
139
+ addRoutes: routeConfiguringFunction,
140
+ allowCors,
141
+ basePath,
142
+ extraMethodMap,
143
+ webSocketsMapping: appiumServer.webSocketsMapping,
144
+ useLegacyUpgradeHandler,
145
+ });
146
+ // allow extensions to update the app and http server objects
147
+ for (const updater of serverUpdaters) {
148
+ await updater(app, appiumServer, cliArgs);
149
+ }
166
150
 
167
- resolve(appiumServer);
168
- } catch (err) {
169
- reject(err);
170
- }
151
+ // once all configurations and updaters have been applied, make sure to set up a catchall
152
+ // handler so that anything unknown 404s. But do this after everything else since we don't
153
+ // want to block extensions' ability to add routes if they want.
154
+ app.all('/*all', catch404Handler);
155
+
156
+ await startServer({
157
+ httpServer,
158
+ hostname,
159
+ port,
160
+ keepAliveTimeout,
161
+ requestTimeout,
162
+ });
163
+
164
+ resolve(appiumServer);
165
+ } catch (err) {
166
+ reject(err);
167
+ }
168
+ })();
171
169
  });
172
170
  }
173
171
 
@@ -237,7 +235,7 @@ export function configureServer({
237
235
  * @returns Normalized base path
238
236
  */
239
237
  export function normalizeBasePath(basePath: string): string {
240
- if (!_.isString(basePath)) {
238
+ if (typeof basePath !== 'string') {
241
239
  throw new Error(`Invalid path prefix ${basePath}`);
242
240
  }
243
241
 
@@ -268,19 +266,18 @@ async function createServer(
268
266
  }
269
267
 
270
268
  const certKey = [sslCertificatePath, sslKeyPath];
271
- const zipped = _.zip(
272
- await B.all(certKey.map((p) => fs.exists(p))),
273
- ['certificate', 'key'],
274
- certKey
275
- ) as [boolean, string, string][];
276
- for (const [exists, desc, p] of zipped) {
269
+ const [certExists, keyExists] = await Promise.all(certKey.map((p) => fs.exists(p)));
270
+ for (const [exists, desc, p] of [
271
+ [certExists, 'certificate', sslCertificatePath],
272
+ [keyExists, 'key', sslKeyPath],
273
+ ]) {
277
274
  if (!exists) {
278
275
  throw new Error(
279
276
  `The provided SSL ${desc} at '${p}' does not exist or is not accessible`
280
277
  );
281
278
  }
282
279
  }
283
- const [cert, key] = await B.all(
280
+ const [cert, key] = await Promise.all(
284
281
  certKey.map((p) => fs.readFile(p, 'utf8'))
285
282
  ) as [string, string];
286
283
  log.debug('Enabling TLS/SPDY on the server using the provided certificate');
@@ -333,7 +330,7 @@ function configureHttp({
333
330
  if (hasShouldUpgradeCallback(httpServer)) {
334
331
  // shouldUpgradeCallback only returns a boolean to indicate if the upgrade should proceed
335
332
  (appiumServer as unknown as {shouldUpgradeCallback?: (req: http.IncomingMessage) => boolean}).shouldUpgradeCallback = (req) =>
336
- _.toLower(req.headers?.upgrade) === 'websocket';
333
+ String(req.headers?.upgrade ?? '').toLowerCase() === 'websocket';
337
334
  appiumServer.on('upgrade', (req, socket, head) => {
338
335
  if (!tryHandleWebSocketUpgrade(req, socket, head, appiumServer.webSocketsMapping)) {
339
336
  socket.destroy();
@@ -345,7 +342,7 @@ function configureHttp({
345
342
  // all connections are closed and the `close` event is emitted
346
343
  const originalClose = appiumServer.close.bind(appiumServer);
347
344
  appiumServer.close = async () =>
348
- await new B<void>((_resolve, _reject) => {
345
+ await new Promise<void>((_resolve, _reject) => {
349
346
  log.info('Closing Appium HTTP server');
350
347
  const timer = new timing.Timer().start();
351
348
  const onTimeout = setTimeout(() => {
@@ -405,12 +402,21 @@ async function startServer({
405
402
  requestTimeout,
406
403
  }: StartServerOpts): Promise<void> {
407
404
  // If the hostname is omitted, the server will accept connections on any IP address
408
- const start = B.promisify(httpServer.listen, {
409
- context: httpServer,
410
- }) as (port: number, hostname?: string) => B<HttpServer>;
411
- const startPromise = start(port, hostname);
405
+ const startPromise = new Promise<void>((resolve, reject) => {
406
+ const onError = (err: Error) => {
407
+ httpServer.removeListener('listening', onListening);
408
+ reject(err);
409
+ };
410
+ const onListening = () => {
411
+ httpServer.removeListener('error', onError);
412
+ resolve();
413
+ };
414
+ httpServer.once('error', onError);
415
+ httpServer.once('listening', onListening);
416
+ httpServer.listen(port, hostname);
417
+ });
412
418
  httpServer.keepAliveTimeout = keepAliveTimeout;
413
- if (_.isInteger(requestTimeout)) {
419
+ if (Number.isInteger(requestTimeout)) {
414
420
  httpServer.requestTimeout = Number(requestTimeout);
415
421
  }
416
422
  // headers timeout must be greater than keepAliveTimeout
@@ -1,13 +1,11 @@
1
1
  import path from 'node:path';
2
2
  import {log} from './logger';
3
- import _ from 'lodash';
4
3
  import {fs} from '@appium/support';
5
- import B from 'bluebird';
4
+ import {sleep} from 'asyncbox';
6
5
  import type {Request, Response} from 'express';
6
+ import {compileLodashTemplate} from '../utils';
7
7
 
8
- export const STATIC_DIR = _.isNull(path.resolve(__dirname).match(/build[/\\]lib[/\\]express$/))
9
- ? path.resolve(__dirname, '..', '..', 'static')
10
- : path.resolve(__dirname, '..', '..', '..', 'static');
8
+ export const STATIC_DIR = resolveStaticDir();
11
9
 
12
10
  type TemplateParams = Record<string, unknown>;
13
11
 
@@ -56,7 +54,7 @@ async function guineaPigTemplate(
56
54
  log.debug(`Sending guinea pig response with params: ${JSON.stringify(params)}`);
57
55
  if (delay) {
58
56
  log.debug(`Waiting ${delay}ms before responding`);
59
- await B.delay(delay);
57
+ await sleep(delay);
60
58
  }
61
59
  res.set('content-type', 'text/html');
62
60
  res.cookie('guineacookie1', 'i am a cookie value', {path: '/'});
@@ -73,5 +71,15 @@ async function getTemplate(
73
71
  templateName: string
74
72
  ): Promise<(params: TemplateParams) => string> {
75
73
  const content = await fs.readFile(path.resolve(STATIC_DIR, 'test', templateName));
76
- return _.template(content.toString()) as (params: TemplateParams) => string;
74
+ return compileLodashTemplate(content.toString());
75
+ }
76
+
77
+ function resolveStaticDir(): string {
78
+ const fromDir = __dirname;
79
+ const parts = path.resolve(fromDir).split(path.sep);
80
+ const baseDriverIndex = parts.indexOf('base-driver');
81
+ if (baseDriverIndex < 0) {
82
+ throw new Error(`Could not find the module root folder in the path: ${fromDir}`);
83
+ }
84
+ return path.join(parts.slice(0, baseDriverIndex + 1).join(path.sep), 'static');
77
85
  }
@@ -1,5 +1,4 @@
1
- import _ from 'lodash';
2
- import B from 'bluebird';
1
+ import {util} from '@appium/support';
3
2
  import type {AppiumServer, WSServer} from '@appium/types';
4
3
 
5
4
  export const DEFAULT_WS_PATHNAME_PREFIX = '/ws';
@@ -24,9 +23,9 @@ export async function getWebSocketHandlers(
24
23
  this: AppiumServer,
25
24
  keysFilter: string | null = null
26
25
  ): Promise<Record<string, WSServer>> {
27
- return _.toPairs(this.webSocketsMapping).reduce<Record<string, WSServer>>(
26
+ return Object.entries(this.webSocketsMapping).reduce<Record<string, WSServer>>(
28
27
  (acc, [pathname, wsServer]) => {
29
- if (!_.isString(keysFilter) || pathname.includes(keysFilter)) {
28
+ if (typeof keysFilter !== 'string' || pathname.includes(keysFilter)) {
30
29
  acc[pathname] = wsServer;
31
30
  }
32
31
  return acc;
@@ -67,15 +66,14 @@ export async function removeWebSocketHandler(
67
66
  * @see AppiumServerExtension.removeAllWebSocketHandlers
68
67
  */
69
68
  export async function removeAllWebSocketHandlers(this: AppiumServer): Promise<boolean> {
70
- if (_.isEmpty(this.webSocketsMapping)) {
69
+ if (util.isEmpty(this.webSocketsMapping)) {
71
70
  return false;
72
71
  }
73
72
 
74
- return _.some(
75
- await B.all(
76
- _.keys(this.webSocketsMapping).map((pathname) =>
77
- this.removeWebSocketHandler(pathname)
78
- )
73
+ const results = await Promise.all(
74
+ Object.keys(this.webSocketsMapping).map((pathname) =>
75
+ this.removeWebSocketHandler(pathname)
79
76
  )
80
77
  );
78
+ return results.some(Boolean);
81
79
  }
@@ -1,28 +1,28 @@
1
1
  import type {Constraints, W3CCapabilities, Capabilities, AppiumLogger} from '@appium/types';
2
- import _ from 'lodash';
2
+ import {util} from '@appium/support';
3
3
 
4
4
  /**
5
5
  * Determine whether the given argument is valid
6
6
  * W3C capabilities instance.
7
7
  */
8
8
  export function isW3cCaps(caps: unknown): caps is W3CCapabilities<Constraints> {
9
- if (!_.isPlainObject(caps)) {
9
+ if (!util.isPlainObject(caps)) {
10
10
  return false;
11
11
  }
12
12
 
13
13
  const c = caps as Record<string, unknown>;
14
14
  const isFirstMatchValid = () =>
15
- _.isArray(c.firstMatch) &&
16
- !_.isEmpty(c.firstMatch) &&
17
- _.every(c.firstMatch, _.isPlainObject);
18
- const isAlwaysMatchValid = () => _.isPlainObject(c.alwaysMatch);
19
- if (_.has(c, 'firstMatch') && _.has(c, 'alwaysMatch')) {
15
+ Array.isArray(c.firstMatch) &&
16
+ !util.isEmpty(c.firstMatch) &&
17
+ c.firstMatch.every((item) => util.isPlainObject(item));
18
+ const isAlwaysMatchValid = () => util.isPlainObject(c.alwaysMatch);
19
+ if (Object.hasOwn(c, 'firstMatch') && Object.hasOwn(c, 'alwaysMatch')) {
20
20
  return isFirstMatchValid() && isAlwaysMatchValid();
21
21
  }
22
- if (_.has(c, 'firstMatch')) {
22
+ if (Object.hasOwn(c, 'firstMatch')) {
23
23
  return isFirstMatchValid();
24
24
  }
25
- if (_.has(c, 'alwaysMatch')) {
25
+ if (Object.hasOwn(c, 'alwaysMatch')) {
26
26
  return isAlwaysMatchValid();
27
27
  }
28
28
  return false;
@@ -36,16 +36,18 @@ export function fixCaps<C extends Constraints>(
36
36
  desiredCapConstraints: C,
37
37
  log: AppiumLogger
38
38
  ): Capabilities<C> {
39
- const caps = _.clone(oldCaps) as Record<string, unknown>;
39
+ const caps = {...oldCaps} as Record<string, unknown>;
40
40
 
41
41
  const logCastWarning = (prefix: string) => log.warn(`${prefix}. This may cause unexpected behavior`);
42
42
 
43
43
  // boolean capabilities can be passed in as strings 'false' and 'true'
44
44
  // which we want to translate into boolean values
45
- const booleanCaps = _.keys(_.pickBy(desiredCapConstraints, (k) => k.isBoolean === true));
45
+ const booleanCaps = Object.keys(desiredCapConstraints).filter(
46
+ (key) => desiredCapConstraints[key as keyof C]?.isBoolean === true
47
+ );
46
48
  for (const cap of booleanCaps) {
47
49
  const value = oldCaps[cap];
48
- if (!_.isString(value)) {
50
+ if (typeof value !== 'string') {
49
51
  continue;
50
52
  }
51
53
 
@@ -59,10 +61,12 @@ export function fixCaps<C extends Constraints>(
59
61
  }
60
62
 
61
63
  // int capabilities are often sent in as strings by frameworks
62
- const intCaps = _.keys(_.pickBy(desiredCapConstraints, (k) => k.isNumber === true));
64
+ const intCaps = Object.keys(desiredCapConstraints).filter(
65
+ (key) => desiredCapConstraints[key as keyof C]?.isNumber === true
66
+ );
63
67
  for (const cap of intCaps) {
64
68
  const value = oldCaps[cap];
65
- if (!_.isString(value)) {
69
+ if (typeof value !== 'string') {
66
70
  continue;
67
71
  }
68
72
 
@@ -1,4 +1,4 @@
1
- import _ from 'lodash';
1
+ import {util} from '@appium/support';
2
2
  import type {Constraints, Driver, DriverClass} from '@appium/types';
3
3
  import type {BaseDriver} from '../basedriver/driver';
4
4
 
@@ -16,7 +16,7 @@ export function resolveExecuteExtensionName<C extends Constraints>(
16
16
  const Driver = this.constructor as DriverClass<Driver<C>>;
17
17
  const methodMap = Driver.executeMethodMap;
18
18
 
19
- if (methodMap && _.isPlainObject(methodMap) && commandName in methodMap) {
19
+ if (methodMap && util.isPlainObject(methodMap) && commandName in methodMap) {
20
20
  const command = methodMap[commandName]?.command;
21
21
  if (typeof command === 'string') {
22
22
  return command;
@@ -1,6 +1,5 @@
1
1
  import type {StringRecord} from '@appium/types';
2
2
  import {distance} from 'fastest-levenshtein';
3
- import _ from 'lodash';
4
3
 
5
4
  /**
6
5
  * Inclusive maximum Levenshtein edit distance for offering a "did you mean" hint.
@@ -39,10 +38,8 @@ export function rankLevenshteinCandidates(
39
38
  }
40
39
  return acc;
41
40
  }, {});
42
- const sortedDistanceKeys = _.keys(matchesMap).sort((a, b) => parseInt(a, 10) - parseInt(b, 10));
43
- const sorted = _.flatten(
44
- sortedDistanceKeys.map((k) => (matchesMap[k] ?? []).sort()),
45
- );
41
+ const sortedDistanceKeys = Object.keys(matchesMap).sort((a, b) => parseInt(a, 10) - parseInt(b, 10));
42
+ const sorted = sortedDistanceKeys.flatMap((k) => (matchesMap[k] ?? []).sort());
46
43
 
47
44
  const best = sorted[0];
48
45
  const firstDistanceKey = sortedDistanceKeys[0];