@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,3 +1,4 @@
1
+ import type AsyncLock from 'async-lock';
1
2
  import {util} from '@appium/support';
2
3
  import {
3
4
  BASE_DESIRED_CAP_CONSTRAINTS,
@@ -17,8 +18,6 @@ import {
17
18
  type SingularSessionData,
18
19
  type SessionCapabilities,
19
20
  } from '@appium/types';
20
- import B from 'bluebird';
21
- import _ from 'lodash';
22
21
  import {fixCaps, isW3cCaps} from '../helpers/capabilities';
23
22
  import {getLevenshteinSuggestion} from '../helpers/levenshtein-match';
24
23
  import {calcSignature} from '../helpers/session';
@@ -26,8 +25,12 @@ import {DELETE_SESSION_COMMAND, determineProtocol, errors} from '../protocol';
26
25
  import {processCapabilities, validateCaps} from './capabilities';
27
26
  import {DriverCore} from './core';
28
27
  import * as helpers from './helpers';
28
+ import {mergePlainObjects} from '../utils';
29
29
  import {resolveExecuteExtensionName} from '../helpers/extension-command-name';
30
30
 
31
+ type CommandInvoker<C extends Constraints> = BaseDriver<C> &
32
+ Record<string, ((...args: any[]) => any) | undefined>;
33
+
31
34
  const EVENT_SESSION_INIT = 'newSessionRequested';
32
35
  const EVENT_SESSION_START = 'newSessionStarted';
33
36
  const EVENT_SESSION_QUIT_START = 'quitSessionRequested';
@@ -35,20 +38,20 @@ const EVENT_SESSION_QUIT_DONE = 'quitSessionFinished';
35
38
  const ON_UNEXPECTED_SHUTDOWN_EVENT = 'onUnexpectedShutdown';
36
39
 
37
40
  export class BaseDriver<
38
- const C extends Constraints,
39
- CArgs extends StringRecord = StringRecord,
40
- Settings extends StringRecord = StringRecord,
41
- CreateResult = DefaultCreateSessionResult<C>,
42
- DeleteResult = DefaultDeleteSessionResult,
43
- SessionData extends StringRecord = StringRecord,
44
- >
41
+ const C extends Constraints,
42
+ CArgs extends StringRecord = StringRecord,
43
+ Settings extends StringRecord = StringRecord,
44
+ CreateResult = DefaultCreateSessionResult<C>,
45
+ DeleteResult = DefaultDeleteSessionResult,
46
+ SessionData extends StringRecord = StringRecord,
47
+ >
45
48
  extends DriverCore<C, Settings>
46
49
  implements Driver<C, CArgs, Settings, CreateResult, DeleteResult, SessionData>
47
50
  {
48
51
  cliArgs: CArgs & ServerArgs;
49
52
  caps: DriverCaps<C>;
50
- originalCaps: W3CDriverCaps<C>;
51
- desiredCapConstraints: C;
53
+ originalCaps!: W3CDriverCaps<C>;
54
+ desiredCapConstraints!: C;
52
55
  server?: AppiumServer;
53
56
  serverHost?: string;
54
57
  serverPort?: number;
@@ -69,7 +72,13 @@ export class BaseDriver<
69
72
  * @see {@link https://github.com/appium/appium/issues/new}
70
73
  */
71
74
  protected get _desiredCapConstraints(): Readonly<BaseDriverCapConstraints & C> {
72
- return Object.freeze(_.merge({}, BASE_DESIRED_CAP_CONSTRAINTS, this.desiredCapConstraints));
75
+ return Object.freeze(
76
+ mergePlainObjects(
77
+ {},
78
+ BASE_DESIRED_CAP_CONSTRAINTS,
79
+ this.desiredCapConstraints,
80
+ ) as BaseDriverCapConstraints & C,
81
+ );
73
82
  }
74
83
 
75
84
  /**
@@ -98,8 +107,10 @@ export class BaseDriver<
98
107
  throw new errors.NoSuchDriverError('The driver was unexpectedly shut down!');
99
108
  }
100
109
 
110
+ const invoker = this as unknown as CommandInvoker<C>;
111
+ const command = invoker[cmd];
101
112
  // If we don't have this command, it must not be implemented
102
- if (!this[cmd]) {
113
+ if (!command) {
103
114
  await this.startNewCommandTimeout();
104
115
  throw new errors.NotYetImplementedError();
105
116
  }
@@ -113,15 +124,15 @@ export class BaseDriver<
113
124
  unexpectedShutdownRejecter?.(e);
114
125
  };
115
126
  try {
116
- return await B.race([
117
- this[cmd](...args),
127
+ return await Promise.race([
128
+ command.call(this, ...args),
118
129
  // This promise is needed to monitor if the session has been
119
130
  // shut down unexpectedly while the command was running
120
- new B((resolve, reject) => {
131
+ new Promise((resolve, reject) => {
121
132
  unexpectedShutdownResolver = resolve;
122
133
  unexpectedShutdownRejecter = reject;
123
134
  this.eventEmitter.once(ON_UNEXPECTED_SHUTDOWN_EVENT, onUnexpectedShutdown);
124
- })
135
+ }),
125
136
  ]);
126
137
  } finally {
127
138
  if (unexpectedShutdownRejecter && unexpectedShutdownResolver) {
@@ -138,7 +149,11 @@ export class BaseDriver<
138
149
  // automatic session deletion in this.onCommandTimeout. Of course we don't
139
150
  // want to trigger the timer when the user is shutting down the session
140
151
  // intentionally
141
- if (!wasSessionShutdownUnexpectedly && this.isCommandsQueueEnabled && cmd !== DELETE_SESSION_COMMAND) {
152
+ if (
153
+ !wasSessionShutdownUnexpectedly &&
154
+ this.isCommandsQueueEnabled &&
155
+ cmd !== DELETE_SESSION_COMMAND
156
+ ) {
142
157
  // resetting existing timeout
143
158
  await this.startNewCommandTimeout();
144
159
  }
@@ -146,13 +161,15 @@ export class BaseDriver<
146
161
  };
147
162
 
148
163
  const synchronizationKey = BaseDriver.name;
149
- // eslint-disable-next-line dot-notation
150
- const commandsQueueLen: number = this.commandsQueueGuard['queues']?.[synchronizationKey]?.length ?? 0;
164
+ const commandsQueueGuard = this.commandsQueueGuard as AsyncLock & {
165
+ queues?: Record<string, unknown[]>;
166
+ };
167
+ const commandsQueueLen: number = commandsQueueGuard.queues?.[synchronizationKey]?.length ?? 0;
151
168
  if (this.isCommandsQueueEnabled && commandsQueueLen > 0) {
152
169
  this.log.debug(
153
170
  `Scheduling the '${cmd}' command to the ${this.constructor.name} commands queue. ` +
154
- `${util.pluralize('queue item', commandsQueueLen, true)} ${commandsQueueLen === 1 ? 'is' : 'are'} ` +
155
- `already waiting for execution.`
171
+ `${util.pluralize('queue item', commandsQueueLen, true)} ${commandsQueueLen === 1 ? 'is' : 'are'} ` +
172
+ `already waiting for execution.`,
156
173
  );
157
174
  }
158
175
 
@@ -180,8 +197,8 @@ export class BaseDriver<
180
197
  clarifyCommandName(cmd: string, args: string[]): string {
181
198
  if (cmd === 'execute') {
182
199
  const firstArg = args?.[0];
183
- if (_.isString(firstArg) && firstArg.trim().length > 0) {
184
- return resolveExecuteExtensionName.call(this, firstArg);
200
+ if (typeof firstArg === 'string' && firstArg.trim().length > 0) {
201
+ return resolveExecuteExtensionName.call(this as BaseDriver<Constraints>, firstArg);
185
202
  }
186
203
  }
187
204
 
@@ -239,15 +256,12 @@ export class BaseDriver<
239
256
  this.log.debug('Running generic full reset');
240
257
 
241
258
  // preserving state
242
- const currentConfig = {};
243
- for (const property of [
244
- 'implicitWaitMs',
245
- 'newCommandTimeoutMs',
246
- 'sessionId',
247
- 'resetOnUnexpectedShutdown',
248
- ]) {
249
- currentConfig[property] = this[property];
250
- }
259
+ const currentConfig = {
260
+ implicitWaitMs: this.implicitWaitMs,
261
+ newCommandTimeoutMs: this.newCommandTimeoutMs,
262
+ sessionId: this.sessionId,
263
+ shutdownUnexpectedly: this.shutdownUnexpectedly,
264
+ };
251
265
 
252
266
  try {
253
267
  if (this.sessionId !== null) {
@@ -257,9 +271,7 @@ export class BaseDriver<
257
271
  await this.createSession(this.originalCaps);
258
272
  } finally {
259
273
  // always restore state.
260
- for (const [key, value] of _.toPairs(currentConfig)) {
261
- this[key] = value;
262
- }
274
+ Object.assign(this, currentConfig);
263
275
  }
264
276
  await this.clearNewCommandTimeout();
265
277
  }
@@ -286,7 +298,7 @@ export class BaseDriver<
286
298
 
287
299
  this.log.debug();
288
300
 
289
- const originalCaps = _.cloneDeep(
301
+ const originalCaps = structuredClone(
290
302
  [w3cCapabilities, w3cCapabilities1, w3cCapabilities2].find(isW3cCaps),
291
303
  );
292
304
  if (!originalCaps) {
@@ -312,7 +324,8 @@ export class BaseDriver<
312
324
  ) as DriverCaps<C>;
313
325
  caps = fixCaps(caps, this._desiredCapConstraints, this.log) as DriverCaps<C>;
314
326
  } catch (e) {
315
- throw new errors.SessionNotCreatedError(e.message);
327
+ const message = e instanceof Error ? e.message : String(e);
328
+ throw new errors.SessionNotCreatedError(message);
316
329
  }
317
330
 
318
331
  this.validateDesiredCaps(caps);
@@ -321,7 +334,7 @@ export class BaseDriver<
321
334
  this.sessionCreationTimestampMs = Date.now();
322
335
  this.caps = caps;
323
336
  // merge caps onto opts so we don't need to worry about what's where
324
- this.opts = {..._.cloneDeep(this.initialOpts), ...this.caps};
337
+ this.opts = {...structuredClone(this.initialOpts), ...this.caps};
325
338
 
326
339
  // deal with resets
327
340
  // some people like to do weird things by setting noReset and fullReset
@@ -347,7 +360,7 @@ export class BaseDriver<
347
360
  delete this.opts.app;
348
361
  }
349
362
 
350
- if (!_.isUndefined(this.caps.newCommandTimeout)) {
363
+ if (this.caps.newCommandTimeout !== undefined) {
351
364
  this.newCommandTimeoutMs = (this.caps.newCommandTimeout as number) * 1000;
352
365
  }
353
366
 
@@ -388,7 +401,7 @@ export class BaseDriver<
388
401
  // simple hack to release pending commands if they exist
389
402
  // @ts-expect-error private API
390
403
  const queues = this.commandsQueueGuard.queues;
391
- for (const key of _.keys(queues)) {
404
+ for (const key of Object.keys(queues)) {
392
405
  queues[key] = [];
393
406
  }
394
407
  }
@@ -396,17 +409,14 @@ export class BaseDriver<
396
409
  }
397
410
 
398
411
  logExtraCaps(caps: Capabilities<C>) {
399
- const knownCaps = _.keys(this._desiredCapConstraints);
400
- const extraCaps = _.difference(_.keys(caps), knownCaps);
412
+ const knownCaps = Object.keys(this._desiredCapConstraints);
413
+ const knownCapsSet = new Set(knownCaps);
414
+ const extraCaps = Object.keys(caps).filter((cap) => !knownCapsSet.has(cap));
401
415
  if (extraCaps.length) {
402
416
  this.log.warn(`The following provided capabilities were not recognized by this driver:`);
403
417
  for (const cap of extraCaps) {
404
418
  const suggestion = getLevenshteinSuggestion(cap, knownCaps);
405
- this.log.warn(
406
- suggestion
407
- ? ` ${cap} (did you mean '${suggestion}'?)`
408
- : ` ${cap}`,
409
- );
419
+ this.log.warn(suggestion ? ` ${cap} (did you mean '${suggestion}'?)` : ` ${cap}`);
410
420
  }
411
421
  }
412
422
  }
@@ -419,11 +429,13 @@ export class BaseDriver<
419
429
  try {
420
430
  validateCaps(caps, this._desiredCapConstraints);
421
431
  } catch (e) {
432
+ const capError = e instanceof Error ? e : new Error(String(e));
422
433
  throw this.log.errorWithException(
423
434
  new errors.SessionNotCreatedError(
424
435
  `Session capabilities were not valid for the ` +
425
- `following reason(s): ${e.message}`, e
426
- )
436
+ `following reason(s): ${capError.message}`,
437
+ capError,
438
+ ),
427
439
  );
428
440
  }
429
441
 
@@ -1,17 +1,17 @@
1
- import {logger} from '@appium/support';
1
+ import {logger, util} from '@appium/support';
2
2
  import {EventEmitter} from 'node:events';
3
3
  import type {
4
4
  AppiumLogger,
5
5
  BidiModuleMap,
6
6
  BiDiResultData,
7
+ IAppiumIpc,
8
+ IIpcSubscription,
9
+ IpcData,
7
10
  StringRecord,
8
11
  } from '@appium/types';
9
- import {
10
- MAX_LOG_BODY_LENGTH,
11
- } from '../constants';
12
+ import {MAX_LOG_BODY_LENGTH} from '../constants';
12
13
  import {errors} from '../protocol';
13
14
  import {BIDI_COMMANDS} from '../protocol/bidi-commands';
14
- import _ from 'lodash';
15
15
  import {generateDriverLogPrefix} from './helpers';
16
16
 
17
17
  export class ExtensionCore {
@@ -20,8 +20,8 @@ export class ExtensionCore {
20
20
  _logPrefix?: string;
21
21
  // used to handle driver events
22
22
  readonly eventEmitter: NodeJS.EventEmitter;
23
- protected _log: AppiumLogger;
24
-
23
+ protected _log!: AppiumLogger;
24
+ private ipc?: IAppiumIpc;
25
25
 
26
26
  constructor(logPrefix?: string) {
27
27
  this._logPrefix = logPrefix;
@@ -41,9 +41,11 @@ export class ExtensionCore {
41
41
  }
42
42
 
43
43
  updateBidiCommands(cmds: BidiModuleMap): void {
44
- const overlappingKeys = _.intersection(Object.keys(cmds), Object.keys(this.bidiCommands));
44
+ const overlappingKeys = Object.keys(cmds).filter((key) => key in this.bidiCommands);
45
45
  if (overlappingKeys.length) {
46
- this.log.warn(`Overwriting existing bidi modules: ${JSON.stringify(overlappingKeys)}. This may not be intended!`);
46
+ this.log.warn(
47
+ `Overwriting existing bidi modules: ${JSON.stringify(overlappingKeys)}. This may not be intended!`,
48
+ );
47
49
  }
48
50
  this.bidiCommands = {
49
51
  ...this.bidiCommands,
@@ -71,7 +73,7 @@ export class ExtensionCore {
71
73
  }
72
74
 
73
75
  // if the command module or method isn't part of our spec, reject
74
- if (!(this.bidiCommands[moduleName]?.[methodName])) {
76
+ if (!this.bidiCommands[moduleName]?.[methodName]) {
75
77
  throw new errors.UnknownCommandError();
76
78
  }
77
79
 
@@ -82,13 +84,19 @@ export class ExtensionCore {
82
84
  }
83
85
 
84
86
  // If the driver doesn't have this command, it must not be implemented
85
- if (!this[command]) {
87
+ const handler = (this as ExtensionCore & Record<string, unknown>)[command];
88
+ if (typeof handler !== 'function') {
86
89
  throw new errors.NotYetImplementedError();
87
90
  }
88
91
  }
89
92
 
90
- async executeBidiCommand(bidiCmd: string, bidiParams: StringRecord, next?: () => Promise<any>, driver?: ExtensionCore): Promise<BiDiResultData> {
91
- const handlerType = (next && driver) ? 'plugin' : 'driver';
93
+ async executeBidiCommand(
94
+ bidiCmd: string,
95
+ bidiParams: StringRecord,
96
+ next?: () => Promise<any>,
97
+ driver?: ExtensionCore,
98
+ ): Promise<BiDiResultData> {
99
+ const handlerType = next && driver ? 'plugin' : 'driver';
92
100
  const [moduleName, methodName] = bidiCmd.split('.');
93
101
  this.ensureBidiCommandExists(moduleName, methodName);
94
102
  const {command, params} = this.bidiCommands[moduleName][methodName];
@@ -98,7 +106,7 @@ export class ExtensionCore {
98
106
  const args: any[] = [];
99
107
  if (params?.required?.length) {
100
108
  for (const requiredParam of params.required) {
101
- if (_.isUndefined(bidiParams[requiredParam])) {
109
+ if (bidiParams[requiredParam] === undefined) {
102
110
  throw new errors.InvalidArgumentError(
103
111
  `The ${requiredParam} parameter was required but you omitted it`,
104
112
  );
@@ -111,18 +119,52 @@ export class ExtensionCore {
111
119
  args.push(bidiParams[optionalParam]);
112
120
  }
113
121
  }
114
- const logParams = _.truncate(JSON.stringify(bidiParams), {length: MAX_LOG_BODY_LENGTH});
122
+ const logParams = util.truncateString(JSON.stringify(bidiParams), {
123
+ length: MAX_LOG_BODY_LENGTH,
124
+ });
115
125
  this.log.debug(
116
126
  `Executing bidi command '${bidiCmd}' with params ${logParams} by passing to ${handlerType} ` +
117
127
  `method '${command}'`,
118
128
  );
119
129
  // call the handler with the signature appropriate to extension type (plugin or driver)
120
- const response = (next && driver) ? await this[command](next, driver, ...args) : await this[command](...args);
121
- const finalResponse = _.isUndefined(response) ? {} : response;
130
+ const commandHandler = (
131
+ this as unknown as Record<string, (...handlerArgs: any[]) => Promise<unknown>>
132
+ )[command];
133
+ const response =
134
+ next && driver
135
+ ? await commandHandler.call(this, next, driver, ...args)
136
+ : await commandHandler.call(this, ...args);
137
+ const finalResponse: BiDiResultData =
138
+ response === undefined ? {} : (response as BiDiResultData);
122
139
  this.log.debug(
123
140
  `Responding to bidi command '${bidiCmd}' with ` +
124
- `${_.truncate(JSON.stringify(finalResponse), {length: MAX_LOG_BODY_LENGTH})}`
141
+ `${util.truncateString(JSON.stringify(finalResponse), {length: MAX_LOG_BODY_LENGTH})}`,
125
142
  );
126
143
  return finalResponse;
127
144
  }
145
+
146
+ /**
147
+ * @internal Used by AppiumDriver to wire session IPC; extension authors should use {@link onIpcInit} instead.
148
+ */
149
+ async assignIpc(ipc: IAppiumIpc): Promise<void> {
150
+ this.ipc = ipc;
151
+ try {
152
+ await this.onIpcInit();
153
+ } catch (e) {
154
+ this.log.error(`Error running onIpcInit: `, e);
155
+ }
156
+ }
157
+
158
+ async onIpcInit(): Promise<void> {}
159
+
160
+ ipcSubscribe<T extends IpcData>(topic: string): IIpcSubscription<T> {
161
+ if (!this.ipc) {
162
+ throw new Error(
163
+ `Cannot subscribe to an IPC topic without an IPC object assigned. ` +
164
+ `This is likely a programming error. ipcSubscribe should be called in the ` +
165
+ `onIpcInit handler or after you are certain that createSession has completed successfully.`,
166
+ );
167
+ }
168
+ return this.ipc.subscribe<T>(topic, generateDriverLogPrefix(this));
169
+ }
128
170
  }