@appium/base-driver 9.17.0 → 10.0.0-beta.1

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 (81) hide show
  1. package/build/lib/basedriver/capabilities.d.ts.map +1 -1
  2. package/build/lib/basedriver/capabilities.js +2 -4
  3. package/build/lib/basedriver/capabilities.js.map +1 -1
  4. package/build/lib/basedriver/commands/timeout.js +7 -25
  5. package/build/lib/basedriver/commands/timeout.js.map +1 -1
  6. package/build/lib/basedriver/core.d.ts +0 -8
  7. package/build/lib/basedriver/core.d.ts.map +1 -1
  8. package/build/lib/basedriver/core.js +8 -18
  9. package/build/lib/basedriver/core.js.map +1 -1
  10. package/build/lib/basedriver/driver.js +2 -2
  11. package/build/lib/basedriver/driver.js.map +1 -1
  12. package/build/lib/basedriver/helpers.d.ts +9 -1
  13. package/build/lib/basedriver/helpers.d.ts.map +1 -1
  14. package/build/lib/basedriver/helpers.js +56 -142
  15. package/build/lib/basedriver/helpers.js.map +1 -1
  16. package/build/lib/basedriver/validation.d.ts +7 -0
  17. package/build/lib/basedriver/validation.d.ts.map +1 -0
  18. package/build/lib/basedriver/validation.js +130 -0
  19. package/build/lib/basedriver/validation.js.map +1 -0
  20. package/build/lib/express/middleware.d.ts +0 -6
  21. package/build/lib/express/middleware.d.ts.map +1 -1
  22. package/build/lib/express/middleware.js +12 -64
  23. package/build/lib/express/middleware.js.map +1 -1
  24. package/build/lib/express/server.d.ts.map +1 -1
  25. package/build/lib/express/server.js +1 -2
  26. package/build/lib/express/server.js.map +1 -1
  27. package/build/lib/helpers/capabilities.d.ts +13 -6
  28. package/build/lib/helpers/capabilities.d.ts.map +1 -1
  29. package/build/lib/helpers/capabilities.js +7 -0
  30. package/build/lib/helpers/capabilities.js.map +1 -1
  31. package/build/lib/index.d.ts +1 -0
  32. package/build/lib/index.d.ts.map +1 -1
  33. package/build/lib/index.js +3 -1
  34. package/build/lib/index.js.map +1 -1
  35. package/build/lib/jsonwp-proxy/proxy.d.ts +0 -8
  36. package/build/lib/jsonwp-proxy/proxy.d.ts.map +1 -1
  37. package/build/lib/jsonwp-proxy/proxy.js +1 -29
  38. package/build/lib/jsonwp-proxy/proxy.js.map +1 -1
  39. package/build/lib/protocol/errors.d.ts +171 -277
  40. package/build/lib/protocol/errors.d.ts.map +1 -1
  41. package/build/lib/protocol/errors.js +201 -421
  42. package/build/lib/protocol/errors.js.map +1 -1
  43. package/build/lib/protocol/helpers.d.ts +6 -6
  44. package/build/lib/protocol/helpers.d.ts.map +1 -1
  45. package/build/lib/protocol/helpers.js +11 -7
  46. package/build/lib/protocol/helpers.js.map +1 -1
  47. package/build/lib/protocol/protocol.d.ts +5 -0
  48. package/build/lib/protocol/protocol.d.ts.map +1 -1
  49. package/build/lib/protocol/protocol.js +23 -23
  50. package/build/lib/protocol/protocol.js.map +1 -1
  51. package/build/lib/protocol/routes.d.ts +6 -715
  52. package/build/lib/protocol/routes.d.ts.map +1 -1
  53. package/build/lib/protocol/routes.js +16 -481
  54. package/build/lib/protocol/routes.js.map +1 -1
  55. package/build/lib/protocol/validators.d.ts +4 -7
  56. package/build/lib/protocol/validators.d.ts.map +1 -1
  57. package/build/lib/protocol/validators.js +4 -24
  58. package/build/lib/protocol/validators.js.map +1 -1
  59. package/lib/basedriver/capabilities.ts +2 -4
  60. package/lib/basedriver/commands/timeout.ts +11 -34
  61. package/lib/basedriver/core.ts +10 -19
  62. package/lib/basedriver/driver.ts +3 -3
  63. package/lib/basedriver/helpers.js +61 -167
  64. package/lib/basedriver/validation.ts +145 -0
  65. package/lib/express/middleware.js +16 -75
  66. package/lib/express/server.js +1 -3
  67. package/lib/helpers/capabilities.js +9 -4
  68. package/lib/index.js +2 -0
  69. package/lib/jsonwp-proxy/proxy.js +2 -33
  70. package/lib/protocol/{errors.js → errors.ts} +322 -436
  71. package/lib/protocol/helpers.js +12 -8
  72. package/lib/protocol/protocol.js +25 -23
  73. package/lib/protocol/routes.js +18 -491
  74. package/lib/protocol/validators.ts +19 -0
  75. package/package.json +10 -10
  76. package/build/lib/basedriver/desired-caps.d.ts +0 -5
  77. package/build/lib/basedriver/desired-caps.d.ts.map +0 -1
  78. package/build/lib/basedriver/desired-caps.js +0 -92
  79. package/build/lib/basedriver/desired-caps.js.map +0 -1
  80. package/lib/basedriver/desired-caps.js +0 -103
  81. package/lib/protocol/validators.js +0 -41
@@ -0,0 +1,145 @@
1
+ import type { Constraint } from '@appium/types';
2
+ import log from './logger';
3
+ import _ from 'lodash';
4
+
5
+ export class Validator {
6
+ private readonly _validators: Record<
7
+ keyof Constraint,
8
+ (value: any, options?: any, key?: string) => string | null
9
+ > = {
10
+ isString: (value: any, options?: any): string | null => {
11
+ if (_.isUndefined(value) || _.isNil(options)) {
12
+ return null;
13
+ }
14
+
15
+ if (_.isString(value)) {
16
+ return options ? null : 'must not be of type string';
17
+ }
18
+
19
+ return options ? 'must be of type string' : null;
20
+ },
21
+ isNumber: (value: any, options?: any): string | null => {
22
+ if (_.isUndefined(value) || _.isNil(options)) {
23
+ return null;
24
+ }
25
+
26
+ if (_.isNumber(value)) {
27
+ return options ? null : 'must not be of type number';
28
+ }
29
+
30
+ // allow a string value
31
+ if (options && _.isString(value) && !isNaN(Number(value))) {
32
+ log.warn('Number capability passed in as string. Functionality may be compromised.');
33
+ return null;
34
+ }
35
+
36
+ return options ? 'must be of type number' : null;
37
+ },
38
+ isBoolean: (value: any, options?: any): string | null => {
39
+ if (_.isUndefined(value) || _.isNil(options)) {
40
+ return null;
41
+ }
42
+
43
+ if (_.isBoolean(value)) {
44
+ return options ? null : 'must not be of type boolean';
45
+ }
46
+
47
+ // allow a string value
48
+ if (options && _.isString(value) && ['true', 'false', ''].includes(value)) {
49
+ return null;
50
+ }
51
+
52
+ return options ? 'must be of type boolean' : null;
53
+ },
54
+ isObject: (value: any, options?: any): string | null => {
55
+ if (_.isUndefined(value) || _.isNil(options)) {
56
+ return null;
57
+ }
58
+
59
+ if (_.isPlainObject(value)) {
60
+ return options ? null : 'must not be a plain object';
61
+ }
62
+
63
+ return options ? 'must be a plain object' : null;
64
+ },
65
+ isArray: (value: any, options?: any): string | null => {
66
+ if (_.isUndefined(value) || _.isNil(options)) {
67
+ return null;
68
+ }
69
+
70
+ if (_.isArray(value)) {
71
+ return options ? null : 'must not be of type array';
72
+ }
73
+
74
+ return options ? 'must be of type array' : null;
75
+ },
76
+ deprecated: (value: any, options?: any, key?: string): string | null => {
77
+ if (!_.isUndefined(value) && options) {
78
+ log.warn(
79
+ `The '${key}' capability has been deprecated and must not be used anymore. ` +
80
+ `Please check the driver documentation for possible alternatives.`
81
+ );
82
+ }
83
+ return null;
84
+ },
85
+ inclusion: (value: any, options?: any): string | null => {
86
+ if (_.isUndefined(value) || !options) {
87
+ return null;
88
+ }
89
+ const optionsArr = _.isArray(options) ? options : [options];
90
+ if (optionsArr.some((opt) => opt === value)) {
91
+ return null;
92
+ }
93
+ return `must be contained by ${JSON.stringify(optionsArr)}`;
94
+ },
95
+ inclusionCaseInsensitive: (value: any, options?: any): string | null => {
96
+ if (_.isUndefined(value) || !options) {
97
+ return null;
98
+ }
99
+ const optionsArr = _.isArray(options) ? options : [options];
100
+ if (optionsArr.some((opt) => _.toLower(opt) === _.toLower(value))) {
101
+ return null;
102
+ }
103
+ return `must be contained by ${JSON.stringify(optionsArr)}`;
104
+ },
105
+ presence: (value: any, options?: any): string | null => {
106
+ if (_.isUndefined(value) && options) {
107
+ return 'is required to be present';
108
+ }
109
+ if (
110
+ !options?.allowEmpty &&
111
+ ((!_.isUndefined(value) && _.isEmpty(value)) || (_.isString(value) && !_.trim(value)))
112
+ ) {
113
+ return 'must not be empty or blank';
114
+ }
115
+ return null;
116
+ }
117
+ };
118
+
119
+ validate(values: Record<string, any>, constraints: Record<string, Constraint>): Record<string, string[]> | null {
120
+ const result: Record<string, string[]> = {};
121
+ for (const [key, constraint] of _.toPairs(constraints)) {
122
+ const value = values[key];
123
+ for (const [validatorName, options] of _.toPairs(constraint)) {
124
+ if (!(validatorName in this._validators)) {
125
+ continue;
126
+ }
127
+
128
+ const validationError = this._validators[validatorName](value, options, key);
129
+ if (_.isNil(validationError)) {
130
+ continue;
131
+ }
132
+
133
+ if (key in result) {
134
+ result[key].push(validationError);
135
+ } else {
136
+ result[key] = [validationError];
137
+ }
138
+ }
139
+ }
140
+ return _.isEmpty(result) ? null : result;
141
+ }
142
+
143
+ }
144
+
145
+ export const validator = new Validator();
@@ -5,6 +5,9 @@ export {handleIdempotency} from './idempotency';
5
5
  import {match} from 'path-to-regexp';
6
6
  import {util} from '@appium/support';
7
7
  import {calcSignature} from '../helpers/session';
8
+ import {getResponseForW3CError} from '../protocol/errors';
9
+
10
+ const SESSION_ID_PATTERN = /\/session\/([^/]+)/;
8
11
 
9
12
  /**
10
13
  *
@@ -14,20 +17,16 @@ import {calcSignature} from '../helpers/session';
14
17
  * @returns {any}
15
18
  */
16
19
  export function allowCrossDomain(req, res, next) {
17
- try {
18
- res.header('Access-Control-Allow-Origin', '*');
19
- res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, OPTIONS, DELETE');
20
- res.header(
21
- 'Access-Control-Allow-Headers',
22
- 'Cache-Control, Pragma, Origin, X-Requested-With, Content-Type, Accept, User-Agent'
23
- );
20
+ res.header('Access-Control-Allow-Origin', '*');
21
+ res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, OPTIONS, DELETE');
22
+ res.header(
23
+ 'Access-Control-Allow-Headers',
24
+ 'Cache-Control, Pragma, Origin, X-Requested-With, Content-Type, Accept, User-Agent'
25
+ );
24
26
 
25
- // need to respond 200 to OPTIONS
26
- if ('OPTIONS' === req.method) {
27
- return res.sendStatus(200);
28
- }
29
- } catch (err) {
30
- log.error(`Unexpected error: ${err.stack}`);
27
+ // need to respond 200 to OPTIONS
28
+ if ('OPTIONS' === req.method) {
29
+ return res.sendStatus(200);
31
30
  }
32
31
  next();
33
32
  }
@@ -50,26 +49,6 @@ export function allowCrossDomainAsyncExecute(basePath) {
50
49
  };
51
50
  }
52
51
 
53
- /**
54
- *
55
- * @param {string} basePath
56
- * @returns {import('express').RequestHandler}
57
- */
58
- export function fixPythonContentType(basePath) {
59
- return (req, res, next) => {
60
- // hack because python client library gives us wrong content-type
61
- if (
62
- new RegExp(`^${_.escapeRegExp(basePath)}`).test(req.path) &&
63
- /^Python/.test(req.headers['user-agent'] ?? '')
64
- ) {
65
- if (req.headers['content-type'] === 'application/x-www-form-urlencoded') {
66
- req.headers['content-type'] = 'application/json; charset=utf-8';
67
- }
68
- }
69
- next();
70
- };
71
- }
72
-
73
52
  /**
74
53
  *
75
54
  * @param {import('express').Request} req
@@ -147,19 +126,8 @@ export function catchAllHandler(err, req, res, next) {
147
126
  }
148
127
 
149
128
  log.error(`Uncaught error: ${err.message}`);
150
- log.error('Sending generic error response');
151
- const error = errors.UnknownError;
152
- res.status(error.w3cStatus()).json(
153
- patchWithSessionId(req, {
154
- status: error.code(),
155
- value: {
156
- error: error.error(),
157
- message: `An unknown server-side error occurred while processing the command: ${err.message}`,
158
- stacktrace: err.stack,
159
- },
160
- })
161
- );
162
- log.error(err);
129
+ const [status, body] = getResponseForW3CError(err);
130
+ res.status(status).json(body);
163
131
  }
164
132
 
165
133
  /**
@@ -168,35 +136,8 @@ export function catchAllHandler(err, req, res, next) {
168
136
  */
169
137
  export function catch404Handler(req, res) {
170
138
  log.debug(`No route found for ${req.url}`);
171
- const error = errors.UnknownCommandError;
172
- res.status(error.w3cStatus()).json(
173
- patchWithSessionId(req, {
174
- status: error.code(),
175
- value: {
176
- error: error.error(),
177
- message:
178
- 'The requested resource could not be found, or a request was ' +
179
- 'received using an HTTP method that is not supported by the mapped ' +
180
- 'resource',
181
- stacktrace: '',
182
- },
183
- })
184
- );
185
- }
186
-
187
- const SESSION_ID_PATTERN = /\/session\/([^/]+)/;
188
-
189
- /**
190
- * @param {import('express').Request} req
191
- * @param {any} body
192
- * @returns {any}
193
- */
194
- function patchWithSessionId(req, body) {
195
- const match = SESSION_ID_PATTERN.exec(req.url);
196
- if (match) {
197
- body.sessionId = match[1];
198
- }
199
- return body;
139
+ const [status, body] = getResponseForW3CError(new errors.UnknownCommandError());
140
+ res.status(status).json(body);
200
141
  }
201
142
 
202
143
  /**
@@ -9,7 +9,6 @@ import log from './logger';
9
9
  import {startLogFormatter, endLogFormatter} from './express-logging';
10
10
  import {
11
11
  allowCrossDomain,
12
- fixPythonContentType,
13
12
  defaultToJSONContentType,
14
13
  catchAllHandler,
15
14
  allowCrossDomainAsyncExecute,
@@ -122,7 +121,7 @@ export async function server(opts) {
122
121
  // once all configurations and updaters have been applied, make sure to set up a catchall
123
122
  // handler so that anything unknown 404s. But do this after everything else since we don't
124
123
  // want to block extensions' ability to add routes if they want.
125
- app.all('*', catch404Handler);
124
+ app.all('/*all', catch404Handler);
126
125
 
127
126
  await startServer({
128
127
  httpServer,
@@ -172,7 +171,6 @@ export function configureServer({
172
171
  app.use(allowCrossDomainAsyncExecute(basePath));
173
172
  }
174
173
  app.use(handleIdempotency);
175
- app.use(fixPythonContentType(basePath));
176
174
  app.use(defaultToJSONContentType);
177
175
  app.use(bodyParser.urlencoded({extended: true}));
178
176
  app.use(methodOverride());
@@ -2,7 +2,14 @@
2
2
 
3
3
  import _ from 'lodash';
4
4
 
5
- function isW3cCaps(caps) {
5
+ /**
6
+ * Determine whether the given agument is valid
7
+ * W3C capabilities instance.
8
+ *
9
+ * @param {any} caps
10
+ * @returns {caps is import('@appium/types').W3CCapabilities}
11
+ */
12
+ export function isW3cCaps(caps) {
6
13
  if (!_.isPlainObject(caps)) {
7
14
  return false;
8
15
  }
@@ -32,7 +39,7 @@ function isW3cCaps(caps) {
32
39
  * @param {AppiumLogger} log
33
40
  * @returns {Capabilities<C>}
34
41
  */
35
- function fixCaps(oldCaps, desiredCapConstraints, log) {
42
+ export function fixCaps(oldCaps, desiredCapConstraints, log) {
36
43
  let caps = _.clone(oldCaps);
37
44
 
38
45
  // boolean capabilities can be passed in as strings 'false' and 'true'
@@ -73,8 +80,6 @@ function fixCaps(oldCaps, desiredCapConstraints, log) {
73
80
  return caps;
74
81
  }
75
82
 
76
- export {isW3cCaps, fixCaps};
77
-
78
83
  /**
79
84
  * @typedef {import('@appium/types').Constraints} Constraints
80
85
  * @typedef {import('@appium/types').AppiumLogger} AppiumLogger
package/lib/index.js CHANGED
@@ -54,6 +54,8 @@ export {BIDI_COMMANDS} from './protocol/bidi-commands';
54
54
 
55
55
  export {generateDriverLogPrefix} from './basedriver/helpers';
56
56
 
57
+ export {isW3cCaps} from './helpers/capabilities';
58
+
57
59
  /**
58
60
  * @typedef {import('./express/server').ServerOpts} ServerOpts
59
61
  */
@@ -11,7 +11,7 @@ import {
11
11
  import {isSessionCommand, routeToCommandName} from '../protocol';
12
12
  import {MAX_LOG_BODY_LENGTH, DEFAULT_BASE_PATH, PROTOCOLS} from '../constants';
13
13
  import ProtocolConverter from './protocol-converter';
14
- import {formatResponseValue, formatStatus} from '../protocol/helpers';
14
+ import {formatResponseValue, ensureW3cResponse} from '../protocol/helpers';
15
15
  import http from 'http';
16
16
  import https from 'https';
17
17
  import { match as pathToRegexMatch } from 'path-to-regexp';
@@ -290,37 +290,6 @@ export class JWProxy {
290
290
  }
291
291
  }
292
292
 
293
- /**
294
- * @deprecated This method is not used anymore and will be removed
295
- *
296
- * @param {string} url
297
- * @param {import('@appium/types').HTTPMethod} method
298
- * @returns {string|undefined}
299
- */
300
- requestToCommandName(url, method) {
301
- /**
302
- *
303
- * @param {RegExp} pattern
304
- * @returns {string|undefined}
305
- */
306
- const extractCommandName = (pattern) => {
307
- const pathMatch = pattern.exec(url);
308
- if (pathMatch) {
309
- return routeToCommandName(pathMatch[1], method, this.reqBasePath);
310
- }
311
- };
312
- let commandName = routeToCommandName(url, method, this.reqBasePath);
313
- if (!commandName && _.includes(url, `${this.reqBasePath}/session/`)) {
314
- commandName = extractCommandName(
315
- new RegExp(`${_.escapeRegExp(this.reqBasePath)}/session/[^/]+(.+)`)
316
- );
317
- }
318
- if (!commandName && _.includes(url, this.reqBasePath)) {
319
- commandName = extractCommandName(new RegExp(`${_.escapeRegExp(this.reqBasePath)}(/.+)`));
320
- }
321
- return commandName;
322
- }
323
-
324
293
  /**
325
294
  *
326
295
  * @param {string} url
@@ -457,7 +426,7 @@ export class JWProxy {
457
426
  }
458
427
  }
459
428
  resBodyObj.value = formatResponseValue(resBodyObj.value);
460
- res.status(statusCode).send(JSON.stringify(formatStatus(resBodyObj)));
429
+ res.status(statusCode).json(ensureW3cResponse(resBodyObj));
461
430
  }
462
431
 
463
432
  /**