@appium/base-driver 9.5.4 → 9.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.
@@ -2,8 +2,16 @@ import _ from 'lodash';
2
2
  import log from './logger';
3
3
  import {errors} from '../protocol';
4
4
  import {handleIdempotency} from './idempotency';
5
+ import {pathToRegexp} from 'path-to-regexp';
5
6
 
6
- function allowCrossDomain(req, res, next) {
7
+ /**
8
+ *
9
+ * @param {import('express').Request} req
10
+ * @param {import('express').Response} res
11
+ * @param {import('express').NextFunction} next
12
+ * @returns {any}
13
+ */
14
+ export function allowCrossDomain(req, res, next) {
7
15
  try {
8
16
  res.header('Access-Control-Allow-Origin', '*');
9
17
  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, OPTIONS, DELETE');
@@ -22,7 +30,11 @@ function allowCrossDomain(req, res, next) {
22
30
  next();
23
31
  }
24
32
 
25
- function allowCrossDomainAsyncExecute(basePath) {
33
+ /**
34
+ * @param {string} basePath
35
+ * @returns {import('express').RequestHandler}
36
+ */
37
+ export function allowCrossDomainAsyncExecute(basePath) {
26
38
  return (req, res, next) => {
27
39
  // there are two paths for async responses, so cover both
28
40
  // https://regex101.com/r/txYiEz/1
@@ -36,12 +48,17 @@ function allowCrossDomainAsyncExecute(basePath) {
36
48
  };
37
49
  }
38
50
 
39
- function fixPythonContentType(basePath) {
51
+ /**
52
+ *
53
+ * @param {string} basePath
54
+ * @returns {import('express').RequestHandler}
55
+ */
56
+ export function fixPythonContentType(basePath) {
40
57
  return (req, res, next) => {
41
58
  // hack because python client library gives us wrong content-type
42
59
  if (
43
60
  new RegExp(`^${_.escapeRegExp(basePath)}`).test(req.path) &&
44
- /^Python/.test(req.headers['user-agent'])
61
+ /^Python/.test(req.headers['user-agent'] ?? '')
45
62
  ) {
46
63
  if (req.headers['content-type'] === 'application/x-www-form-urlencoded') {
47
64
  req.headers['content-type'] = 'application/json; charset=utf-8';
@@ -51,14 +68,55 @@ function fixPythonContentType(basePath) {
51
68
  };
52
69
  }
53
70
 
54
- function defaultToJSONContentType(req, res, next) {
71
+ /**
72
+ *
73
+ * @param {import('express').Request} req
74
+ * @param {import('express').Response} res
75
+ * @param {import('express').NextFunction} next
76
+ * @returns {any}
77
+ */
78
+ export function defaultToJSONContentType(req, res, next) {
55
79
  if (!req.headers['content-type']) {
56
80
  req.headers['content-type'] = 'application/json; charset=utf-8';
57
81
  }
58
82
  next();
59
83
  }
60
84
 
61
- function catchAllHandler(err, req, res, next) {
85
+ /**
86
+ *
87
+ * @param {import('@appium/types').StringRecord<import('@appium/types').WSServer>} webSocketsMapping
88
+ * @returns {import('express').RequestHandler}
89
+ */
90
+ export function handleUpgrade(webSocketsMapping) {
91
+ return (req, res, next) => {
92
+ if (!req.headers?.upgrade || _.toLower(req.headers.upgrade) !== 'websocket') {
93
+ return next();
94
+ }
95
+ let currentPathname;
96
+ try {
97
+ currentPathname = new URL(req.url ?? '').pathname;
98
+ } catch {
99
+ currentPathname = req.url ?? '';
100
+ }
101
+ for (const [pathname, wsServer] of _.toPairs(webSocketsMapping)) {
102
+ if (pathToRegexp(pathname).test(currentPathname)) {
103
+ return wsServer.handleUpgrade(req, req.socket, Buffer.from(''), (ws) => {
104
+ wsServer.emit('connection', ws, req);
105
+ });
106
+ }
107
+ }
108
+ log.info(`Did not match the websocket upgrade request at ${currentPathname} to any known route`);
109
+ next();
110
+ };
111
+ }
112
+
113
+ /**
114
+ * @param {Error} err
115
+ * @param {import('express').Request} req
116
+ * @param {import('express').Response} res
117
+ * @param {import('express').NextFunction} next
118
+ */
119
+ export function catchAllHandler(err, req, res, next) {
62
120
  if (res.headersSent) {
63
121
  return next(err);
64
122
  }
@@ -79,7 +137,11 @@ function catchAllHandler(err, req, res, next) {
79
137
  log.error(err);
80
138
  }
81
139
 
82
- function catch404Handler(req, res) {
140
+ /**
141
+ * @param {import('express').Request} req
142
+ * @param {import('express').Response} res
143
+ */
144
+ export function catch404Handler(req, res) {
83
145
  log.debug(`No route found for ${req.url}`);
84
146
  const error = errors.UnknownCommandError;
85
147
  res.status(error.w3cStatus()).json(
@@ -107,12 +169,4 @@ function patchWithSessionId(req, body) {
107
169
  return body;
108
170
  }
109
171
 
110
- export {
111
- allowCrossDomain,
112
- fixPythonContentType,
113
- defaultToJSONContentType,
114
- catchAllHandler,
115
- allowCrossDomainAsyncExecute,
116
- handleIdempotency,
117
- catch404Handler,
118
- };
172
+ export { handleIdempotency };
@@ -14,6 +14,7 @@ import {
14
14
  catchAllHandler,
15
15
  allowCrossDomainAsyncExecute,
16
16
  handleIdempotency,
17
+ handleUpgrade,
17
18
  catch404Handler,
18
19
  } from './middleware';
19
20
  import {guineaPig, guineaPigScrollable, guineaPigAppBanner, welcome, STATIC_DIR} from './static';
@@ -109,6 +110,7 @@ async function server(opts) {
109
110
  allowCors,
110
111
  basePath,
111
112
  extraMethodMap,
113
+ webSocketsMapping: appiumServer.webSocketsMapping,
112
114
  });
113
115
  // allow extensions to update the app and http server objects
114
116
  for (const updater of serverUpdaters) {
@@ -139,6 +141,7 @@ function configureServer({
139
141
  allowCors = true,
140
142
  basePath = DEFAULT_BASE_PATH,
141
143
  extraMethodMap = {},
144
+ webSocketsMapping = {},
142
145
  }) {
143
146
  basePath = normalizeBasePath(basePath);
144
147
 
@@ -152,7 +155,7 @@ function configureServer({
152
155
  app.use(`${basePath}/produce_error`, produceError);
153
156
  app.use(`${basePath}/crash`, produceCrash);
154
157
 
155
- // add middlewares
158
+ app.use(handleUpgrade(webSocketsMapping));
156
159
  if (allowCors) {
157
160
  app.use(allowCrossDomain);
158
161
  } else {
@@ -195,6 +198,7 @@ function configureHttp({httpServer, reject, keepAliveTimeout}) {
195
198
  * @type {AppiumServer}
196
199
  */
197
200
  const appiumServer = /** @type {any} */ (httpServer);
201
+ appiumServer.webSocketsMapping = {};
198
202
  appiumServer.addWebSocketHandler = addWebSocketHandler;
199
203
  appiumServer.removeWebSocketHandler = removeWebSocketHandler;
200
204
  appiumServer.removeAllWebSocketHandlers = removeAllWebSocketHandlers;
@@ -370,4 +374,5 @@ export {server, configureServer, normalizeBasePath};
370
374
  * @property {boolean} [allowCors]
371
375
  * @property {string} [basePath]
372
376
  * @property {MethodMap} [extraMethodMap]
377
+ * @property {import('@appium/types').StringRecord} [webSocketsMapping={}]
373
378
  */
@@ -1,8 +1,6 @@
1
1
  /* eslint-disable require-await */
2
2
  import _ from 'lodash';
3
- import {URL} from 'url';
4
3
  import B from 'bluebird';
5
- import { pathToRegexp } from 'path-to-regexp';
6
4
 
7
5
  const DEFAULT_WS_PATHNAME_PREFIX = '/ws';
8
6
 
@@ -11,27 +9,6 @@ const DEFAULT_WS_PATHNAME_PREFIX = '/ws';
11
9
  * @type {AppiumServer['addWebSocketHandler']}
12
10
  */
13
11
  async function addWebSocketHandler(handlerPathname, handlerServer) {
14
- if (_.isUndefined(this.webSocketsMapping)) {
15
- this.webSocketsMapping = {};
16
- // https://github.com/websockets/ws/pull/885
17
- this.on('upgrade', (request, socket, head) => {
18
- let currentPathname;
19
- try {
20
- currentPathname = new URL(request.url ?? '').pathname;
21
- } catch {
22
- currentPathname = request.url ?? '';
23
- }
24
- for (const [pathname, wsServer] of _.toPairs(this.webSocketsMapping)) {
25
- if (pathToRegexp(pathname).test(currentPathname)) {
26
- wsServer.handleUpgrade(request, socket, head, (ws) => {
27
- wsServer.emit('connection', ws, request);
28
- });
29
- return;
30
- }
31
- }
32
- socket.destroy();
33
- });
34
- }
35
12
  this.webSocketsMapping[handlerPathname] = handlerServer;
36
13
  }
37
14
 
@@ -40,10 +17,6 @@ async function addWebSocketHandler(handlerPathname, handlerServer) {
40
17
  * @type {AppiumServer['getWebSocketHandlers']}
41
18
  */
42
19
  async function getWebSocketHandlers(keysFilter = null) {
43
- if (_.isEmpty(this.webSocketsMapping)) {
44
- return {};
45
- }
46
-
47
20
  return _.toPairs(this.webSocketsMapping).reduce((acc, [pathname, wsServer]) => {
48
21
  if (!_.isString(keysFilter) || pathname.includes(keysFilter)) {
49
22
  acc[pathname] = wsServer;
@@ -134,10 +134,12 @@ class ProtocolConverter {
134
134
  const bodyObj = util.safeJsonParse(body);
135
135
  if (_.isPlainObject(bodyObj)) {
136
136
  if (this.downstreamProtocol === W3C && _.has(bodyObj, 'name') && !_.has(bodyObj, 'handle')) {
137
- this.log.debug(`Copied 'name' value '${bodyObj.name}' to 'handle' as per W3C spec`);
137
+ this.log.debug(
138
+ `Copied 'name' value '${/** @type {import('@appium/types').StringRecord} */ (bodyObj).name}' to 'handle' as per W3C spec`
139
+ );
138
140
  return await this.proxyFunc(url, method, {
139
- ...bodyObj,
140
- handle: bodyObj.name,
141
+ .../** @type {import('@appium/types').StringRecord} */ (bodyObj),
142
+ handle: /** @type {import('@appium/types').StringRecord} */ (bodyObj).name,
141
143
  });
142
144
  }
143
145
  if (
@@ -145,10 +147,12 @@ class ProtocolConverter {
145
147
  _.has(bodyObj, 'handle') &&
146
148
  !_.has(bodyObj, 'name')
147
149
  ) {
148
- this.log.debug(`Copied 'handle' value '${bodyObj.handle}' to 'name' as per JSONWP spec`);
150
+ this.log.debug(
151
+ `Copied 'handle' value '${/** @type {import('@appium/types').StringRecord} */ (bodyObj).handle}' to 'name' as per JSONWP spec`
152
+ );
149
153
  return await this.proxyFunc(url, method, {
150
- ...bodyObj,
151
- name: bodyObj.handle,
154
+ .../** @type {import('@appium/types').StringRecord} */ (bodyObj),
155
+ name: /** @type {import('@appium/types').StringRecord} */ (bodyObj).handle,
152
156
  });
153
157
  }
154
158
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@appium/base-driver",
3
- "version": "9.5.4",
3
+ "version": "9.7.0",
4
4
  "description": "Base driver class for Appium drivers",
5
5
  "keywords": [
6
6
  "automation",
@@ -44,30 +44,30 @@
44
44
  "test:types": "tsd"
45
45
  },
46
46
  "dependencies": {
47
- "@appium/support": "^4.2.4",
48
- "@appium/types": "^0.16.2",
47
+ "@appium/support": "^4.2.6",
48
+ "@appium/types": "^0.18.0",
49
49
  "@colors/colors": "1.6.0",
50
50
  "@types/async-lock": "1.4.2",
51
51
  "@types/bluebird": "3.5.42",
52
52
  "@types/express": "4.17.21",
53
- "@types/lodash": "4.17.0",
53
+ "@types/lodash": "4.17.4",
54
54
  "@types/method-override": "0.0.35",
55
55
  "@types/serve-favicon": "2.5.7",
56
56
  "async-lock": "1.4.1",
57
57
  "asyncbox": "3.0.0",
58
- "axios": "1.6.8",
58
+ "axios": "1.7.2",
59
59
  "bluebird": "3.7.2",
60
60
  "body-parser": "1.20.2",
61
61
  "express": "4.19.2",
62
62
  "http-status-codes": "2.3.0",
63
63
  "lodash": "4.17.21",
64
- "lru-cache": "10.2.0",
64
+ "lru-cache": "10.2.2",
65
65
  "method-override": "3.0.0",
66
66
  "morgan": "1.10.0",
67
67
  "path-to-regexp": "6.2.2",
68
68
  "serve-favicon": "2.5.0",
69
69
  "source-map-support": "0.5.21",
70
- "type-fest": "4.10.1",
70
+ "type-fest": "4.18.3",
71
71
  "validate.js": "0.13.1"
72
72
  },
73
73
  "optionalDependencies": {
@@ -80,7 +80,7 @@
80
80
  "publishConfig": {
81
81
  "access": "public"
82
82
  },
83
- "gitHead": "407706cf5e28d3471decde8b56c780d40952e8d5",
83
+ "gitHead": "a4138c6ec9524594519f29938008500b7cdcc0b8",
84
84
  "tsd": {
85
85
  "directory": "test/types"
86
86
  }