@appium/base-driver 10.0.0-beta.0 → 10.0.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/README.md +0 -8
  2. package/build/lib/basedriver/capabilities.d.ts.map +1 -1
  3. package/build/lib/basedriver/capabilities.js +2 -4
  4. package/build/lib/basedriver/capabilities.js.map +1 -1
  5. package/build/lib/basedriver/commands/execute.js.map +1 -1
  6. package/build/lib/basedriver/commands/timeout.js +12 -31
  7. package/build/lib/basedriver/commands/timeout.js.map +1 -1
  8. package/build/lib/basedriver/core.d.ts +0 -8
  9. package/build/lib/basedriver/core.d.ts.map +1 -1
  10. package/build/lib/basedriver/core.js +8 -18
  11. package/build/lib/basedriver/core.js.map +1 -1
  12. package/build/lib/basedriver/driver.js +2 -2
  13. package/build/lib/basedriver/driver.js.map +1 -1
  14. package/build/lib/basedriver/helpers.d.ts +9 -1
  15. package/build/lib/basedriver/helpers.d.ts.map +1 -1
  16. package/build/lib/basedriver/helpers.js +56 -142
  17. package/build/lib/basedriver/helpers.js.map +1 -1
  18. package/build/lib/basedriver/validation.d.ts +7 -0
  19. package/build/lib/basedriver/validation.d.ts.map +1 -0
  20. package/build/lib/basedriver/validation.js +130 -0
  21. package/build/lib/basedriver/validation.js.map +1 -0
  22. package/build/lib/express/middleware.d.ts +0 -6
  23. package/build/lib/express/middleware.d.ts.map +1 -1
  24. package/build/lib/express/middleware.js +28 -60
  25. package/build/lib/express/middleware.js.map +1 -1
  26. package/build/lib/express/server.d.ts.map +1 -1
  27. package/build/lib/express/server.js +0 -1
  28. package/build/lib/express/server.js.map +1 -1
  29. package/build/lib/helpers/capabilities.d.ts +13 -6
  30. package/build/lib/helpers/capabilities.d.ts.map +1 -1
  31. package/build/lib/helpers/capabilities.js +7 -0
  32. package/build/lib/helpers/capabilities.js.map +1 -1
  33. package/build/lib/index.d.ts +1 -0
  34. package/build/lib/index.d.ts.map +1 -1
  35. package/build/lib/index.js +3 -1
  36. package/build/lib/index.js.map +1 -1
  37. package/build/lib/jsonwp-proxy/proxy.d.ts +0 -8
  38. package/build/lib/jsonwp-proxy/proxy.d.ts.map +1 -1
  39. package/build/lib/jsonwp-proxy/proxy.js +7 -38
  40. package/build/lib/jsonwp-proxy/proxy.js.map +1 -1
  41. package/build/lib/protocol/errors.d.ts +171 -277
  42. package/build/lib/protocol/errors.d.ts.map +1 -1
  43. package/build/lib/protocol/errors.js +201 -421
  44. package/build/lib/protocol/errors.js.map +1 -1
  45. package/build/lib/protocol/helpers.d.ts +6 -6
  46. package/build/lib/protocol/helpers.d.ts.map +1 -1
  47. package/build/lib/protocol/helpers.js +11 -7
  48. package/build/lib/protocol/helpers.js.map +1 -1
  49. package/build/lib/protocol/index.d.ts +2 -1
  50. package/build/lib/protocol/index.d.ts.map +1 -1
  51. package/build/lib/protocol/index.js +2 -1
  52. package/build/lib/protocol/index.js.map +1 -1
  53. package/build/lib/protocol/protocol.d.ts +16 -19
  54. package/build/lib/protocol/protocol.d.ts.map +1 -1
  55. package/build/lib/protocol/protocol.js +98 -119
  56. package/build/lib/protocol/protocol.js.map +1 -1
  57. package/build/lib/protocol/routes.d.ts +12 -714
  58. package/build/lib/protocol/routes.d.ts.map +1 -1
  59. package/build/lib/protocol/routes.js +24 -488
  60. package/build/lib/protocol/routes.js.map +1 -1
  61. package/build/lib/protocol/validators.d.ts +4 -7
  62. package/build/lib/protocol/validators.d.ts.map +1 -1
  63. package/build/lib/protocol/validators.js +4 -21
  64. package/build/lib/protocol/validators.js.map +1 -1
  65. package/lib/basedriver/capabilities.ts +2 -4
  66. package/lib/basedriver/commands/execute.ts +1 -1
  67. package/lib/basedriver/commands/timeout.ts +16 -43
  68. package/lib/basedriver/core.ts +10 -19
  69. package/lib/basedriver/driver.ts +3 -3
  70. package/lib/basedriver/helpers.js +61 -167
  71. package/lib/basedriver/validation.ts +145 -0
  72. package/lib/express/middleware.js +32 -70
  73. package/lib/express/server.js +0 -2
  74. package/lib/helpers/capabilities.js +9 -4
  75. package/lib/index.js +2 -0
  76. package/lib/jsonwp-proxy/proxy.js +8 -45
  77. package/lib/protocol/{errors.js → errors.ts} +322 -436
  78. package/lib/protocol/helpers.js +12 -8
  79. package/lib/protocol/index.js +8 -1
  80. package/lib/protocol/{protocol.js → protocol.ts} +147 -146
  81. package/lib/protocol/routes.js +26 -498
  82. package/lib/protocol/validators.ts +19 -0
  83. package/package.json +10 -11
  84. package/build/lib/basedriver/desired-caps.d.ts +0 -5
  85. package/build/lib/basedriver/desired-caps.d.ts.map +0 -1
  86. package/build/lib/basedriver/desired-caps.js +0 -92
  87. package/build/lib/basedriver/desired-caps.js.map +0 -1
  88. package/lib/basedriver/README.md +0 -36
  89. package/lib/basedriver/desired-caps.js +0 -103
  90. package/lib/express/README.md +0 -59
  91. package/lib/jsonwp-proxy/README.md +0 -52
  92. package/lib/jsonwp-status/README.md +0 -20
  93. package/lib/protocol/README.md +0 -100
  94. package/lib/protocol/validators.js +0 -38
@@ -7,17 +7,19 @@ import {MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY} from '../constants';
7
7
  * so they have keys for both W3C and JSONWP protocols.
8
8
  * The argument value is NOT mutated
9
9
  *
10
- * @param {?Object} resValue The actual response value
11
- * @returns {?Object} Either modified value or the same one if
10
+ * @param {Object | undefined} resValue The actual response value
11
+ * @returns {Object | null} Either modified value or the same one if
12
12
  * nothing has been modified
13
13
  */
14
- function formatResponseValue(resValue) {
14
+ export function formatResponseValue(resValue) {
15
15
  if (_.isUndefined(resValue)) {
16
16
  // convert undefined to null
17
17
  return null;
18
18
  }
19
- // If the MJSONWP element key format (ELEMENT) was provided, add a duplicate key (element-6066-11e4-a52e-4f735466cecf)
20
- // If the W3C element key format (element-6066-11e4-a52e-4f735466cecf) was provided, add a duplicate key (ELEMENT)
19
+ // If the MJSONWP element key format (ELEMENT) was provided,
20
+ // add a duplicate key (element-6066-11e4-a52e-4f735466cecf)
21
+ // If the W3C element key format (element-6066-11e4-a52e-4f735466cecf)
22
+ // was provided, add a duplicate key (ELEMENT)
21
23
  return duplicateKeys(resValue, MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY);
22
24
  }
23
25
 
@@ -28,8 +30,10 @@ function formatResponseValue(resValue) {
28
30
  * @param {Object} responseBody
29
31
  * @returns {Object} The fixed response body
30
32
  */
31
- function formatStatus(responseBody) {
32
- return _.isPlainObject(responseBody) ? _.omit(responseBody, ['status']) : responseBody;
33
+ export function ensureW3cResponse(responseBody) {
34
+ return _.isPlainObject(responseBody)
35
+ ? _.omit(responseBody, ['status', 'sessionId'])
36
+ : responseBody;
33
37
  }
34
38
 
35
- export {MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY, formatResponseValue, formatStatus};
39
+ export {MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY};
@@ -12,7 +12,13 @@ import {
12
12
  validateExecuteMethodParams,
13
13
  } from './protocol';
14
14
  import {NO_SESSION_ID_COMMANDS, ALL_COMMANDS, METHOD_MAP, routeToCommandName} from './routes';
15
- import {errors, isErrorType, errorFromMJSONWPStatusCode, errorFromW3CJsonCode} from './errors';
15
+ import {
16
+ errors,
17
+ isErrorType,
18
+ errorFromMJSONWPStatusCode,
19
+ errorFromW3CJsonCode,
20
+ getResponseForW3CError,
21
+ } from './errors';
16
22
 
17
23
  export {
18
24
  routeConfiguringFunction,
@@ -34,4 +40,5 @@ export {
34
40
  GET_STATUS_COMMAND,
35
41
  LIST_DRIVER_COMMANDS_COMMAND,
36
42
  LIST_DRIVER_EXTENSIONS_COMMAND,
43
+ getResponseForW3CError,
37
44
  };
@@ -7,14 +7,20 @@ import {
7
7
  getResponseForW3CError,
8
8
  errorFromMJSONWPStatusCode,
9
9
  errorFromW3CJsonCode,
10
+ BadParametersError,
10
11
  } from './errors';
11
12
  import {METHOD_MAP, NO_SESSION_ID_COMMANDS} from './routes';
12
13
  import B from 'bluebird';
13
- import {formatResponseValue, formatStatus} from './helpers';
14
+ import {formatResponseValue, ensureW3cResponse} from './helpers';
14
15
  import {MAX_LOG_BODY_LENGTH, PROTOCOLS, DEFAULT_BASE_PATH} from '../constants';
15
16
  import {isW3cCaps} from '../helpers/capabilities';
16
17
  import log from '../basedriver/logger';
17
18
  import { generateDriverLogPrefix } from '../basedriver/helpers';
19
+ import type { Core, AppiumLogger, PayloadParams, MethodMap, Driver, DriverMethodDef } from '@appium/types';
20
+ import type { BaseDriver } from '../basedriver/driver';
21
+ import type { Request, Response, Application } from 'express';
22
+ import type { MultidimensionalReadonlyArray } from 'type-fest';
23
+ import type { RouteConfiguringFunction } from '../express/server';
18
24
 
19
25
  export const CREATE_SESSION_COMMAND = 'createSession';
20
26
  export const DELETE_SESSION_COMMAND = 'deleteSession';
@@ -22,39 +28,33 @@ export const GET_STATUS_COMMAND = 'getStatus';
22
28
  export const LIST_DRIVER_COMMANDS_COMMAND = 'listCommands';
23
29
  export const LIST_DRIVER_EXTENSIONS_COMMAND = 'listExtensions';
24
30
 
25
- /** @type {Set<string>} */
26
- const deprecatedCommandsLogged = new Set();
31
+ export const deprecatedCommandsLogged: Set<string> = new Set();
27
32
 
28
- function determineProtocol(createSessionArgs) {
33
+ export function determineProtocol(createSessionArgs: any[]): keyof typeof PROTOCOLS {
29
34
  return _.some(createSessionArgs, isW3cCaps) ? PROTOCOLS.W3C : PROTOCOLS.MJSONWP;
30
35
  }
31
36
 
32
- function extractProtocol(driver, sessionId = null) {
33
- const dstDriver = _.isFunction(driver.driverForSession)
37
+
38
+ function extractProtocol(driver: Core<any>, sessionId: string | null = null): keyof typeof PROTOCOLS {
39
+ const dstDriver = _.isFunction(driver.driverForSession) && sessionId
34
40
  ? driver.driverForSession(sessionId)
35
41
  : driver;
36
42
  if (dstDriver === driver) {
37
43
  // Shortcircuit if the driver instance is not an umbrella driver
38
44
  // or it is Fake driver instance, where `driver.driverForSession`
39
45
  // always returns self instance
40
- return driver.protocol;
46
+ return driver.protocol ?? PROTOCOLS.W3C;
41
47
  }
42
48
 
43
49
  // Extract the protocol for the current session if the given driver is the umbrella one
44
50
  return dstDriver?.protocol ?? PROTOCOLS.W3C;
45
51
  }
46
52
 
47
- function isSessionCommand(command) {
53
+ export function isSessionCommand(command: string): boolean {
48
54
  return !_.includes(NO_SESSION_ID_COMMANDS, command);
49
55
  }
50
56
 
51
- /**
52
- *
53
- * @param {import('@appium/types').ExternalDriver} driver
54
- * @param {string?} [sessionId]
55
- * @returns {import('@appium/types').AppiumLogger}
56
- */
57
- function getLogger(driver, sessionId = null) {
57
+ function getLogger(driver: Core<any>, sessionId: string | null = null): AppiumLogger {
58
58
  const dstDriver =
59
59
  sessionId && _.isFunction(driver.driverForSession)
60
60
  ? driver.driverForSession(sessionId) ?? driver
@@ -67,94 +67,116 @@ function getLogger(driver, sessionId = null) {
67
67
  return logger.getLogger(logPrefix);
68
68
  }
69
69
 
70
- function wrapParams(paramSets, jsonObj) {
70
+ function wrapParams<T>(paramSets, jsonObj: T): T | Record<string, T> {
71
71
  /* There are commands like performTouch which take a single parameter (primitive type or array).
72
72
  * Some drivers choose to pass this parameter as a value (eg. [action1, action2...]) while others to
73
73
  * wrap it within an object(eg' {gesture: [action1, action2...]}), which makes it hard to validate.
74
74
  * The wrap option in the spec enforce wrapping before validation, so that all params are wrapped at
75
75
  * the time they are validated and later passed to the commands.
76
76
  */
77
- let res = jsonObj;
78
- if (_.isArray(jsonObj) || !_.isObject(jsonObj)) {
79
- res = {};
80
- res[paramSets.wrap] = jsonObj;
81
- }
82
- return res;
77
+ return (_.isArray(jsonObj) || !_.isObject(jsonObj)) && paramSets.wrap
78
+ ? {[paramSets.wrap]: jsonObj}
79
+ : jsonObj;
83
80
  }
84
81
 
85
- function unwrapParams(paramSets, jsonObj) {
82
+ function unwrapParams<T>(paramSets: PayloadParams, jsonObj: T): T | Record<string, T> {
86
83
  /* There are commands like setNetworkConnection which send parameters wrapped inside a key such as
87
84
  * "parameters". This function unwraps them (eg. {"parameters": {"type": 1}} becomes {"type": 1}).
88
85
  */
89
- let res = jsonObj;
90
- if (_.isObject(jsonObj)) {
91
- // some clients, like ruby, don't wrap
92
- if (jsonObj[paramSets.unwrap]) {
93
- res = jsonObj[paramSets.unwrap];
94
- }
95
- }
96
- return res;
86
+ return _.isObject(jsonObj) && paramSets.unwrap && jsonObj[paramSets.unwrap]
87
+ ? jsonObj[paramSets.unwrap]
88
+ : jsonObj;
97
89
  }
98
90
 
99
- export function checkParams(paramSets, jsonObj, protocol) {
100
- let requiredParams = [];
101
- let optionalParams = [];
102
- let receivedParams = _.keys(jsonObj);
103
-
104
- if (paramSets) {
105
- if (paramSets.required) {
106
- // we might have an array of parameters,
107
- // or an array of arrays of parameters, so standardize
108
- if (!_.isArray(_.first(paramSets.required))) {
109
- requiredParams = [paramSets.required];
110
- } else {
111
- requiredParams = paramSets.required;
112
- }
113
- }
114
- // optional parameters are just an array
115
- if (paramSets.optional) {
116
- optionalParams = paramSets.optional;
117
- }
91
+ function hasMultipleRequiredParamSets(
92
+ required: ReadonlyArray<string> | MultidimensionalReadonlyArray<string, 2> | undefined
93
+ ): required is MultidimensionalReadonlyArray<string, 2> {
94
+ //@ts-expect-error Needed to convince lodash typechecks
95
+ return Boolean(required && _.isArray(_.first(required)));
96
+ }
118
97
 
119
- // If a function was provided as the 'validate' key, it will here be called with
120
- // jsonObj as the param. If it returns something falsy, verification will be
121
- // considered to have passed. If it returns something else, that will be the
122
- // argument to an error which is thrown to the user
123
- if (paramSets.validate) {
124
- let message = paramSets.validate(jsonObj, protocol);
125
- if (message) {
126
- throw new errors.BadParametersError(message, jsonObj);
127
- }
128
- }
98
+ function pickKnownParams(args: Record<string, any>, unknownNames: string[]): Record<string, any> {
99
+ if (_.isEmpty(unknownNames)) {
100
+ return args;
129
101
  }
102
+ log.info(`The following arguments are not known and will be ignored: ${unknownNames}`);
103
+ return _.pickBy(args, (v, k) => !unknownNames.includes(k));
104
+ }
130
105
 
131
- // if we have no required parameters, all is well
132
- if (requiredParams.length === 0) {
133
- return;
106
+ export function checkParams(
107
+ paramSpec: PayloadParams,
108
+ args: Record<string, any>,
109
+ protocol?: keyof typeof PROTOCOLS
110
+ ): Record<string, any> {
111
+ let requiredParams: string[][] = [];
112
+ let optionalParams: string[] = [];
113
+ const actualParamNames: string[] = _.keys(args);
114
+
115
+ if (paramSpec.required) {
116
+ // we might have an array of parameters,
117
+ // or an array of arrays of parameters, so standardize
118
+ requiredParams = _.cloneDeep(
119
+ (hasMultipleRequiredParamSets(paramSpec.required)
120
+ ? paramSpec.required
121
+ : [paramSpec.required]
122
+ ) as string[][]
123
+ );
124
+ }
125
+ // optional parameters are just an array
126
+ if (paramSpec.optional) {
127
+ optionalParams = _.cloneDeep(paramSpec.optional as string[]);
128
+ }
129
+
130
+ // If a function was provided as the 'validate' key, it will here be called with
131
+ // args as the param. If it returns something falsy, verification will be
132
+ // considered to have passed. If it returns something else, that will be the
133
+ // argument to an error which is thrown to the user
134
+ if (paramSpec.validate) {
135
+ const message = paramSpec.validate(args, protocol ?? PROTOCOLS.W3C);
136
+ if (message) {
137
+ throw new errors.InvalidArgumentError(_.isString(message) ? message : undefined);
138
+ }
134
139
  }
135
140
 
136
141
  // some clients pass in the session id in the params
137
- if (optionalParams.indexOf('sessionId') === -1) {
142
+ if (!_.includes(optionalParams, 'sessionId')) {
138
143
  optionalParams.push('sessionId');
139
144
  }
140
-
141
145
  // some clients pass in an element id in the params
142
- if (optionalParams.indexOf('id') === -1) {
146
+ if (!_.includes(optionalParams, 'id')) {
143
147
  optionalParams.push('id');
144
148
  }
145
149
 
150
+ if (_.isEmpty(requiredParams)) {
151
+ // if we don't have any required parameters, then just filter out unknown ones
152
+ return pickKnownParams(args, _.difference(actualParamNames, optionalParams));
153
+ }
154
+
146
155
  // go through the required parameters and check against our arguments
147
- for (let params of requiredParams) {
148
- if (
149
- _.difference(receivedParams, params, optionalParams).length === 0 &&
150
- _.difference(params, receivedParams).length === 0
151
- ) {
152
- // we have a set of parameters that is correct
153
- // so short-circuit
154
- return;
156
+ let matchedReqParamSet: string[] = [];
157
+ for (const requiredParamsSet of requiredParams) {
158
+ if (!_.isArray(requiredParamsSet)) {
159
+ throw new Error(
160
+ `The required parameter set item ${JSON.stringify(requiredParamsSet)} ` +
161
+ `in ${JSON.stringify(paramSpec)} is not an array. ` +
162
+ `This is a bug in the method map definition.`
163
+ );
164
+ }
165
+ if (_.isEmpty(_.difference(requiredParamsSet, actualParamNames))) {
166
+ return pickKnownParams(
167
+ args,
168
+ _.difference(actualParamNames, requiredParamsSet, optionalParams)
169
+ );
170
+ }
171
+ if (!_.isEmpty(requiredParamsSet) && _.isEmpty(matchedReqParamSet)) {
172
+ matchedReqParamSet = requiredParamsSet;
155
173
  }
156
174
  }
157
- throw new errors.BadParametersError(paramSets, receivedParams);
175
+ throw new BadParametersError({
176
+ ...paramSpec,
177
+ required: matchedReqParamSet,
178
+ optional: optionalParams,
179
+ }, actualParamNames);
158
180
  }
159
181
 
160
182
  /*
@@ -164,25 +186,25 @@ export function checkParams(paramSets, jsonObj, protocol) {
164
186
  * on handling parameters. This method returns an array of arguments which will
165
187
  * be applied to a command.
166
188
  */
167
- export function makeArgs(requestParams, jsonObj, payloadParams, protocol) {
189
+ export function makeArgs(requestParams: PayloadParams, jsonObj: any, payloadParams: PayloadParams): any[] {
168
190
  // We want to pass the "url" parameters to the commands in reverse order
169
191
  // since the command will sometimes want to ignore, say, the sessionId.
170
192
  // This has the effect of putting sessionId last, which means in JS we can
171
193
  // omit it from the function signature if we're not going to use it.
172
- let urlParams = _.keys(requestParams).reverse();
194
+ const urlParams = _.keys(requestParams).reverse();
173
195
 
174
196
  // In the simple case, the required parameters are a basic array in
175
197
  // payloadParams.required, so start there. It's possible that there are
176
198
  // multiple optional sets of required params, though, so handle that case
177
199
  // too.
178
200
  let requiredParams = payloadParams.required;
179
- if (_.isArray(_.first(payloadParams.required))) {
201
+ if (hasMultipleRequiredParamSets(payloadParams.required)) {
180
202
  // If there are optional sets of required params, then we will have an
181
203
  // array of arrays in payloadParams.required, so loop through each set and
182
204
  // pick the one that matches which JSON params were actually sent. We've
183
205
  // already been through validation so we're guaranteed to find a match.
184
- let keys = _.keys(jsonObj);
185
- for (let params of payloadParams.required) {
206
+ const keys = _.keys(jsonObj);
207
+ for (const params of payloadParams.required) {
186
208
  if (_.without(params, ...keys).length === 0) {
187
209
  requiredParams = params;
188
210
  break;
@@ -199,7 +221,7 @@ export function makeArgs(requestParams, jsonObj, payloadParams, protocol) {
199
221
  // which will be applied to the handling command. For example if it returns
200
222
  // [1, 2, 3], we will call `command(1, 2, 3, ...)` (url params are separate
201
223
  // from JSON params and get concatenated below).
202
- args = payloadParams.makeArgs(jsonObj, protocol);
224
+ args = payloadParams.makeArgs(jsonObj);
203
225
  } else {
204
226
  // Otherwise, collect all the required and optional params and flatten them
205
227
  // into an argument array
@@ -214,7 +236,7 @@ export function makeArgs(requestParams, jsonObj, payloadParams, protocol) {
214
236
  return args;
215
237
  }
216
238
 
217
- function validateExecuteMethodParams(params, paramSpec) {
239
+ export function validateExecuteMethodParams(params: any[], paramSpec?: PayloadParams): any[] {
218
240
  // the w3c protocol will give us an array of arguments to apply to a javascript function.
219
241
  // that's not what we're doing. we're going to look for a JS object as the first arg, so we
220
242
  // can perform validation on it. we'll ignore everything else.
@@ -224,40 +246,29 @@ function validateExecuteMethodParams(params, paramSpec) {
224
246
  `arguments to execute script and instead received: ${JSON.stringify(params)}`
225
247
  );
226
248
  }
227
- let args = params[0] ?? {};
249
+ const args: Record<string, any> = params[0] ?? {};
228
250
  if (!_.isPlainObject(args)) {
229
251
  throw new errors.InvalidArgumentError(
230
252
  `Did not receive an appropriate execute method parameters object. It needs to be ` +
231
253
  `deserializable as a plain JS object`
232
254
  );
233
255
  }
234
- if (!paramSpec) {
235
- paramSpec = {required: [], optional: []};
236
- } else {
237
- paramSpec.required ??= [];
238
- paramSpec.optional ??= [];
239
- const unknownNames = _.difference(_.keys(args), paramSpec.required, paramSpec.optional);
240
- if (!_.isEmpty(unknownNames)) {
241
- log.info(`The following script arguments are not known and will be ignored: ${unknownNames}`);
242
- args = _.pickBy(args, (v, k) => !unknownNames.includes(k));
243
- }
244
- checkParams(paramSpec, args, null);
245
- }
246
- const argsToApply = makeArgs({}, args, paramSpec, null);
247
- return argsToApply;
256
+ const specToUse = {
257
+ ...(paramSpec ?? {}),
258
+ required: paramSpec?.required ?? [],
259
+ optional: paramSpec?.optional ?? [],
260
+ };
261
+ const filteredArgs = checkParams(specToUse, args);
262
+ return makeArgs({}, filteredArgs, specToUse);
248
263
  }
249
264
 
250
- /**
251
- *
252
- * @param {import('@appium/types').Core} driver
253
- * @returns {import('../express/server').RouteConfiguringFunction}
254
- */
255
- function routeConfiguringFunction(driver) {
265
+
266
+ export function routeConfiguringFunction(driver: Core<any>): RouteConfiguringFunction {
256
267
  if (!driver.sessionExists) {
257
268
  throw new Error('Drivers must implement `sessionExists` property');
258
269
  }
259
270
 
260
- if (!(/** @type {any} */ (driver).executeCommand || /** @type {any} */ (driver).execute)) {
271
+ if (!((driver as any).executeCommand || (driver as any).execute)) {
261
272
  throw new Error('Drivers must implement `executeCommand` or `execute` method');
262
273
  }
263
274
 
@@ -268,10 +279,10 @@ function routeConfiguringFunction(driver) {
268
279
  // for example in determining proxy avoidance
269
280
  driver.basePath = basePath;
270
281
 
271
- const allMethods = {...METHOD_MAP, ...extraMethodMap};
272
-
282
+ const allMethods: MethodMap<Driver> = {...METHOD_MAP, ...extraMethodMap};
273
283
  for (const [path, methods] of _.toPairs(allMethods)) {
274
284
  for (const [method, spec] of _.toPairs(methods)) {
285
+ const isSessCommand = spec.command ? isSessionCommand(spec.command) : false;
275
286
  // set up the express route handler
276
287
  buildHandler(
277
288
  app,
@@ -279,24 +290,31 @@ function routeConfiguringFunction(driver) {
279
290
  `${basePath}${path}`,
280
291
  spec,
281
292
  driver,
282
- isSessionCommand(/** @type {import('@appium/types').DriverMethodDef} */ (spec).command)
293
+ isSessCommand
283
294
  );
284
295
  }
285
296
  }
286
297
  };
287
298
  }
288
299
 
289
- function buildHandler(app, method, path, spec, driver, isSessCmd) {
290
- let asyncHandler = async (req, res) => {
300
+ function buildHandler(
301
+ app: Application,
302
+ method: string,
303
+ path: string,
304
+ spec: DriverMethodDef<Driver>,
305
+ driver: Core<any>,
306
+ isSessCmd: boolean
307
+ ): void {
308
+ const asyncHandler = async (req: Request, res: Response) => {
291
309
  let jsonObj = req.body;
292
- let httpResBody = {};
310
+ let httpResBody = {} as any;
293
311
  let httpStatus = 200;
294
- let newSessionId;
312
+ let newSessionId: string | undefined;
295
313
  let currentProtocol = extractProtocol(driver, req.params.sessionId);
296
314
 
297
315
  try {
298
316
  // if the route accessed is deprecated, log a warning
299
- if (spec.deprecated && !deprecatedCommandsLogged.has(spec.command)) {
317
+ if (spec.deprecated && spec.command && !deprecatedCommandsLogged.has(spec.command)) {
300
318
  deprecatedCommandsLogged.add(spec.command);
301
319
  getLogger(driver, req.params.sessionId).warn(
302
320
  `Command '${spec.command}' has been deprecated and will be removed in a future ` +
@@ -320,12 +338,12 @@ function buildHandler(app, method, path, spec, driver, isSessCmd) {
320
338
  // commands and generally would not want that command to be proxied instead of handled by the
321
339
  // plugin)
322
340
  let didPluginOverrideProxy = false;
323
- if (isSessCmd && !spec.neverProxy && driverShouldDoJwpProxy(driver, req, spec.command)) {
341
+ if (isSessCmd && !spec.neverProxy && spec.command && driverShouldDoJwpProxy(driver, req, spec.command)) {
324
342
  if (
325
- !driver.pluginsToHandleCmd ||
343
+ !('pluginsToHandleCmd' in driver) || !_.isFunction(driver.pluginsToHandleCmd) ||
326
344
  driver.pluginsToHandleCmd(spec.command, req.params.sessionId).length === 0
327
345
  ) {
328
- await doJwpProxy(driver, req, res);
346
+ await doJwpProxy(driver as BaseDriver<any>, req, res);
329
347
  return;
330
348
  }
331
349
  getLogger(driver, req.params.sessionId).debug(
@@ -361,12 +379,14 @@ function buildHandler(app, method, path, spec, driver, isSessCmd) {
361
379
  }
362
380
 
363
381
  // ensure that the json payload conforms to the spec
364
- checkParams(spec.payloadParams, jsonObj, currentProtocol);
382
+ if (spec.payloadParams) {
383
+ checkParams(spec.payloadParams, jsonObj, currentProtocol);
384
+ }
365
385
 
366
386
  // turn the command and json payload into an argument list for
367
387
  // the driver methods
368
- let args = makeArgs(req.params, jsonObj, spec.payloadParams || {}, currentProtocol);
369
- let driverRes;
388
+ const args = makeArgs(req.params, jsonObj, spec.payloadParams || {});
389
+ let driverRes: any;
370
390
  // validate command args according to MJSONWP
371
391
  if (validators[spec.command]) {
372
392
  validators[spec.command](...args);
@@ -386,7 +406,7 @@ function buildHandler(app, method, path, spec, driver, isSessCmd) {
386
406
  args.push({reqForProxy: req});
387
407
  }
388
408
 
389
- driverRes = await driver.executeCommand(spec.command, ...args);
409
+ driverRes = await (driver as BaseDriver<any>).executeCommand(spec.command, ...args);
390
410
 
391
411
  // Get the protocol after executeCommand
392
412
  currentProtocol = extractProtocol(driver, req.params.sessionId) || currentProtocol;
@@ -489,24 +509,14 @@ function buildHandler(app, method, path, spec, driver, isSessCmd) {
489
509
 
490
510
  // decode the response, which is either a string or json
491
511
  if (_.isString(httpResBody)) {
492
- res.status(httpStatus).send(httpResBody);
512
+ res.status(httpStatus)
513
+ .setHeader('content-type', 'application/json; charset=utf-8')
514
+ .send(httpResBody);
493
515
  } else {
494
- if (newSessionId) {
495
- if (currentProtocol === PROTOCOLS.W3C) {
496
- httpResBody.value.sessionId = newSessionId;
497
- } else {
498
- httpResBody.sessionId = newSessionId;
499
- }
500
- } else {
501
- httpResBody.sessionId = req.params.sessionId || null;
502
- }
503
- // Don't include sessionId in W3C responses
504
- if (currentProtocol === PROTOCOLS.W3C) {
505
- delete httpResBody.sessionId;
516
+ if (newSessionId && currentProtocol === PROTOCOLS.W3C) {
517
+ httpResBody.value.sessionId = newSessionId;
506
518
  }
507
-
508
- httpResBody = formatStatus(httpResBody);
509
- res.status(httpStatus).json(httpResBody);
519
+ res.status(httpStatus).json(ensureW3cResponse(httpResBody));
510
520
  }
511
521
  };
512
522
  // add the method to the app
@@ -515,7 +525,7 @@ function buildHandler(app, method, path, spec, driver, isSessCmd) {
515
525
  });
516
526
  }
517
527
 
518
- function driverShouldDoJwpProxy(driver, req, command) {
528
+ export function driverShouldDoJwpProxy(driver: Core<any>, req: import('express').Request, command: string): boolean {
519
529
  // drivers need to explicitly say when the proxy is active
520
530
  if (!driver.proxyActive(req.params.sessionId)) {
521
531
  return false;
@@ -536,7 +546,7 @@ function driverShouldDoJwpProxy(driver, req, command) {
536
546
  return true;
537
547
  }
538
548
 
539
- async function doJwpProxy(driver, req, res) {
549
+ async function doJwpProxy(driver: BaseDriver<any>, req: Request, res: Response): Promise<void> {
540
550
  getLogger(driver, req.params.sessionId).info(
541
551
  'Driver proxy active, passing request on via HTTP proxy'
542
552
  );
@@ -555,12 +565,3 @@ async function doJwpProxy(driver, req, res) {
555
565
  }
556
566
  }
557
567
  }
558
-
559
- export {
560
- routeConfiguringFunction,
561
- isSessionCommand,
562
- driverShouldDoJwpProxy,
563
- determineProtocol,
564
- deprecatedCommandsLogged,
565
- validateExecuteMethodParams,
566
- };