@appium/base-driver 10.2.2 → 10.3.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.
- package/build/lib/basedriver/commands/execute.js +2 -1
- package/build/lib/basedriver/commands/execute.js.map +1 -1
- package/build/lib/jsonwp-proxy/proxy.d.ts +52 -97
- package/build/lib/jsonwp-proxy/proxy.d.ts.map +1 -1
- package/build/lib/jsonwp-proxy/proxy.js +96 -139
- package/build/lib/jsonwp-proxy/proxy.js.map +1 -1
- package/lib/basedriver/commands/execute.ts +2 -1
- package/lib/jsonwp-proxy/proxy.ts +506 -0
- package/package.json +9 -9
- package/lib/jsonwp-proxy/proxy.js +0 -493
|
@@ -1,493 +0,0 @@
|
|
|
1
|
-
import _ from 'lodash';
|
|
2
|
-
import {logger, util} from '@appium/support';
|
|
3
|
-
import {getSummaryByCode} from '../jsonwp-status/status';
|
|
4
|
-
import {
|
|
5
|
-
errors,
|
|
6
|
-
isErrorType,
|
|
7
|
-
errorFromMJSONWPStatusCode,
|
|
8
|
-
errorFromW3CJsonCode,
|
|
9
|
-
getResponseForW3CError,
|
|
10
|
-
} from '../protocol/errors';
|
|
11
|
-
import {isSessionCommand, routeToCommandName} from '../protocol';
|
|
12
|
-
import {MAX_LOG_BODY_LENGTH, DEFAULT_BASE_PATH, PROTOCOLS} from '../constants';
|
|
13
|
-
import {ProtocolConverter} from './protocol-converter';
|
|
14
|
-
import {formatResponseValue, ensureW3cResponse} from '../protocol/helpers';
|
|
15
|
-
import http from 'node:http';
|
|
16
|
-
import https from 'node:https';
|
|
17
|
-
import { match as pathToRegexMatch } from 'path-to-regexp';
|
|
18
|
-
import nodeUrl from 'node:url';
|
|
19
|
-
import { ProxyRequest } from './proxy-request';
|
|
20
|
-
|
|
21
|
-
const DEFAULT_LOG = logger.getLogger('WD Proxy');
|
|
22
|
-
const DEFAULT_REQUEST_TIMEOUT = 240000;
|
|
23
|
-
const COMMAND_WITH_SESSION_ID_MATCHER = pathToRegexMatch('{/*prefix}/session/:sessionId{/*command}');
|
|
24
|
-
|
|
25
|
-
const {MJSONWP, W3C} = PROTOCOLS;
|
|
26
|
-
|
|
27
|
-
const ALLOWED_OPTS = [
|
|
28
|
-
'scheme',
|
|
29
|
-
'server',
|
|
30
|
-
'port',
|
|
31
|
-
'base',
|
|
32
|
-
'reqBasePath',
|
|
33
|
-
'sessionId',
|
|
34
|
-
'timeout',
|
|
35
|
-
'log',
|
|
36
|
-
'keepAlive',
|
|
37
|
-
'headers',
|
|
38
|
-
];
|
|
39
|
-
|
|
40
|
-
export class JWProxy {
|
|
41
|
-
/** @type {string} */
|
|
42
|
-
scheme;
|
|
43
|
-
/** @type {string} */
|
|
44
|
-
server;
|
|
45
|
-
/** @type {number} */
|
|
46
|
-
port;
|
|
47
|
-
/** @type {string} */
|
|
48
|
-
base;
|
|
49
|
-
/** @type {string} */
|
|
50
|
-
reqBasePath;
|
|
51
|
-
/** @type {string?} */
|
|
52
|
-
sessionId;
|
|
53
|
-
/** @type {number} */
|
|
54
|
-
timeout;
|
|
55
|
-
/** @type {import('@appium/types').HTTPHeaders | undefined} */
|
|
56
|
-
headers;
|
|
57
|
-
/** @type {Protocol | null | undefined} */
|
|
58
|
-
_downstreamProtocol;
|
|
59
|
-
/** @type {ProxyRequest[]} */
|
|
60
|
-
_activeRequests;
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* @param {import('@appium/types').ProxyOptions} [opts={}]
|
|
64
|
-
*/
|
|
65
|
-
constructor(opts = {}) {
|
|
66
|
-
const filteredOpts = _.pick(opts, ALLOWED_OPTS);
|
|
67
|
-
// omit 'log' in the defaults assignment here because 'log' is a getter and we are going to set
|
|
68
|
-
// it to this._log (which lies behind the getter) further down
|
|
69
|
-
/** @type {import('@appium/types').ProxyOptions} */
|
|
70
|
-
const options = _.defaults(_.omit(filteredOpts, 'log'), {
|
|
71
|
-
scheme: 'http',
|
|
72
|
-
server: 'localhost',
|
|
73
|
-
port: 4444,
|
|
74
|
-
base: DEFAULT_BASE_PATH,
|
|
75
|
-
reqBasePath: DEFAULT_BASE_PATH,
|
|
76
|
-
sessionId: null,
|
|
77
|
-
timeout: DEFAULT_REQUEST_TIMEOUT,
|
|
78
|
-
});
|
|
79
|
-
options.scheme = /** @type {string} */ (options.scheme).toLowerCase();
|
|
80
|
-
Object.assign(this, options);
|
|
81
|
-
|
|
82
|
-
this._activeRequests = [];
|
|
83
|
-
this._downstreamProtocol = null;
|
|
84
|
-
const agentOpts = {
|
|
85
|
-
keepAlive: opts.keepAlive ?? true,
|
|
86
|
-
maxSockets: 10,
|
|
87
|
-
maxFreeSockets: 5,
|
|
88
|
-
};
|
|
89
|
-
this.httpAgent = new http.Agent(agentOpts);
|
|
90
|
-
this.httpsAgent = new https.Agent(agentOpts);
|
|
91
|
-
this.protocolConverter = new ProtocolConverter(this.proxy.bind(this), opts.log);
|
|
92
|
-
this._log = opts.log;
|
|
93
|
-
|
|
94
|
-
this.log.debug(`${this.constructor.name} options: ${JSON.stringify(options)}`);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
get log() {
|
|
98
|
-
return this._log ?? DEFAULT_LOG;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Performs requests to the downstream server
|
|
103
|
-
*
|
|
104
|
-
* @private - Do not call this method directly,
|
|
105
|
-
* it uses client-specific arguments and responses!
|
|
106
|
-
*
|
|
107
|
-
* @param {import('axios').RawAxiosRequestConfig} requestConfig
|
|
108
|
-
* @returns {Promise<import('axios').AxiosResponse>}
|
|
109
|
-
*/
|
|
110
|
-
async request(requestConfig) {
|
|
111
|
-
const req = new ProxyRequest(requestConfig);
|
|
112
|
-
this._activeRequests.push(req);
|
|
113
|
-
try {
|
|
114
|
-
return await req.execute();
|
|
115
|
-
} finally {
|
|
116
|
-
_.pull(this._activeRequests, req);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* @returns {number}
|
|
122
|
-
*/
|
|
123
|
-
getActiveRequestsCount() {
|
|
124
|
-
return this._activeRequests.length;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
cancelActiveRequests() {
|
|
128
|
-
for (const ar of this._activeRequests) {
|
|
129
|
-
ar.cancel();
|
|
130
|
-
}
|
|
131
|
-
this._activeRequests = [];
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* @param {Protocol | null | undefined} value
|
|
136
|
-
*/
|
|
137
|
-
set downstreamProtocol(value) {
|
|
138
|
-
this._downstreamProtocol = value;
|
|
139
|
-
this.protocolConverter.downstreamProtocol = value;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
get downstreamProtocol() {
|
|
143
|
-
return this._downstreamProtocol;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
*
|
|
148
|
-
* @param {string} url
|
|
149
|
-
* @param {string} [method]
|
|
150
|
-
* @returns {string}
|
|
151
|
-
*/
|
|
152
|
-
getUrlForProxy(url, method) {
|
|
153
|
-
const parsedUrl = this._parseUrl(url);
|
|
154
|
-
const normalizedPathname = this._toNormalizedPathname(parsedUrl);
|
|
155
|
-
const commandName = normalizedPathname
|
|
156
|
-
? routeToCommandName(
|
|
157
|
-
normalizedPathname,
|
|
158
|
-
/** @type {import('@appium/types').HTTPMethod | undefined} */ (method)
|
|
159
|
-
)
|
|
160
|
-
: '';
|
|
161
|
-
const requiresSessionId = !commandName || (commandName && isSessionCommand(commandName));
|
|
162
|
-
const proxyPrefix = `${this.scheme}://${this.server}:${this.port}${this.base}`;
|
|
163
|
-
let proxySuffix = normalizedPathname ? `/${_.trimStart(normalizedPathname, '/')}` : '';
|
|
164
|
-
if (parsedUrl.search) {
|
|
165
|
-
proxySuffix += parsedUrl.search;
|
|
166
|
-
}
|
|
167
|
-
if (!requiresSessionId) {
|
|
168
|
-
return `${proxyPrefix}${proxySuffix}`;
|
|
169
|
-
}
|
|
170
|
-
if (!this.sessionId) {
|
|
171
|
-
throw new ReferenceError(`Session ID is not set, but saw a URL that requires it (${url})`);
|
|
172
|
-
}
|
|
173
|
-
return `${proxyPrefix}/session/${this.sessionId}${proxySuffix}`;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
*
|
|
178
|
-
* @param {string} url
|
|
179
|
-
* @param {string} method
|
|
180
|
-
* @param {import('@appium/types').HTTPBody} [body=null]
|
|
181
|
-
* @returns {Promise<[import('@appium/types').ProxyResponse, import('@appium/types').HTTPBody]>}
|
|
182
|
-
*/
|
|
183
|
-
async proxy(url, method, body = null) {
|
|
184
|
-
method = method.toUpperCase();
|
|
185
|
-
const newUrl = this.getUrlForProxy(url, method);
|
|
186
|
-
const truncateBody = (content) =>
|
|
187
|
-
_.truncate(_.isString(content) ? content : JSON.stringify(content), {
|
|
188
|
-
length: MAX_LOG_BODY_LENGTH,
|
|
189
|
-
});
|
|
190
|
-
/** @type {import('axios').RawAxiosRequestConfig} */
|
|
191
|
-
const reqOpts = {
|
|
192
|
-
url: newUrl,
|
|
193
|
-
method,
|
|
194
|
-
headers: {
|
|
195
|
-
'content-type': 'application/json; charset=utf-8',
|
|
196
|
-
'user-agent': 'appium',
|
|
197
|
-
accept: 'application/json, */*',
|
|
198
|
-
...(this.headers ?? {}),
|
|
199
|
-
},
|
|
200
|
-
proxy: false,
|
|
201
|
-
timeout: this.timeout,
|
|
202
|
-
httpAgent: this.httpAgent,
|
|
203
|
-
httpsAgent: this.httpsAgent,
|
|
204
|
-
};
|
|
205
|
-
// GET methods shouldn't have any body. Most servers are OK with this, but WebDriverAgent throws 400 errors
|
|
206
|
-
if (util.hasValue(body) && method !== 'GET') {
|
|
207
|
-
if (typeof body !== 'object') {
|
|
208
|
-
try {
|
|
209
|
-
reqOpts.data = JSON.parse(body);
|
|
210
|
-
} catch (error) {
|
|
211
|
-
this.log.warn('Invalid body payload (%s): %s', error.message, logger.markSensitive(truncateBody(body)));
|
|
212
|
-
throw new Error(
|
|
213
|
-
'Cannot interpret the request body as valid JSON. Check the server log for more details.'
|
|
214
|
-
);
|
|
215
|
-
}
|
|
216
|
-
} else {
|
|
217
|
-
reqOpts.data = body;
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
this.log.debug(
|
|
222
|
-
`Proxying [%s %s] to [%s %s] with ${reqOpts.data ? 'body: %s' : '%s body'}`,
|
|
223
|
-
method, url || '/', method, newUrl,
|
|
224
|
-
reqOpts.data ? logger.markSensitive(truncateBody(reqOpts.data)) : 'no'
|
|
225
|
-
);
|
|
226
|
-
|
|
227
|
-
const throwProxyError = (error) => {
|
|
228
|
-
const err = /** @type {ProxyError} */ (new Error(`The request to ${url} has failed`));
|
|
229
|
-
err.response = {
|
|
230
|
-
data: error,
|
|
231
|
-
status: 500,
|
|
232
|
-
};
|
|
233
|
-
throw err;
|
|
234
|
-
};
|
|
235
|
-
let isResponseLogged = false;
|
|
236
|
-
try {
|
|
237
|
-
const {data, status, headers} = await this.request(reqOpts);
|
|
238
|
-
// `data` might be really big
|
|
239
|
-
// Be careful while handling it to avoid memory leaks
|
|
240
|
-
if (!_.isPlainObject(data)) {
|
|
241
|
-
// The response should be a valid JSON object
|
|
242
|
-
// If it cannot be coerced to an object then the response is wrong
|
|
243
|
-
throwProxyError(data);
|
|
244
|
-
}
|
|
245
|
-
this.log.debug(`Got response with status ${status}: ${truncateBody(data)}`);
|
|
246
|
-
isResponseLogged = true;
|
|
247
|
-
const isSessionCreationRequest = url.endsWith('/session') && method === 'POST';
|
|
248
|
-
if (isSessionCreationRequest) {
|
|
249
|
-
if (status === 200) {
|
|
250
|
-
this.sessionId = data.sessionId || (data.value || {}).sessionId;
|
|
251
|
-
}
|
|
252
|
-
this.downstreamProtocol = this.getProtocolFromResBody(data);
|
|
253
|
-
this.log.info(`Determined the downstream protocol as '${this.downstreamProtocol}'`);
|
|
254
|
-
}
|
|
255
|
-
if (_.has(data, 'status') && parseInt(data.status, 10) !== 0) {
|
|
256
|
-
// Some servers, like chromedriver may return response code 200 for non-zero JSONWP statuses
|
|
257
|
-
throwProxyError(data);
|
|
258
|
-
}
|
|
259
|
-
const headersMap = /** @type {import('@appium/types').HTTPHeaders} */ (headers);
|
|
260
|
-
return [{
|
|
261
|
-
statusCode: status,
|
|
262
|
-
headers: headersMap,
|
|
263
|
-
body: data,
|
|
264
|
-
}, data];
|
|
265
|
-
} catch (e) {
|
|
266
|
-
// We only consider an error unexpected if this was not
|
|
267
|
-
// an async request module error or if the response cannot be cast to
|
|
268
|
-
// a valid JSON
|
|
269
|
-
let proxyErrorMsg = e.message;
|
|
270
|
-
if (util.hasValue(e.response)) {
|
|
271
|
-
if (!isResponseLogged) {
|
|
272
|
-
const error = truncateBody(e.response.data);
|
|
273
|
-
this.log.info(
|
|
274
|
-
util.hasValue(e.response.status)
|
|
275
|
-
? `Got response with status ${e.response.status}: ${error}`
|
|
276
|
-
: `Got response with unknown status: ${error}`
|
|
277
|
-
);
|
|
278
|
-
}
|
|
279
|
-
} else {
|
|
280
|
-
proxyErrorMsg = `Could not proxy command to the remote server. Original error: ${e.message}`;
|
|
281
|
-
this.log.info(e.message);
|
|
282
|
-
}
|
|
283
|
-
throw new errors.ProxyRequestError(proxyErrorMsg, e.response?.data, e.response?.status);
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
/**
|
|
288
|
-
*
|
|
289
|
-
* @param {Record<string, any>} resObj
|
|
290
|
-
* @returns {Protocol | undefined}
|
|
291
|
-
*/
|
|
292
|
-
getProtocolFromResBody(resObj) {
|
|
293
|
-
if (_.isInteger(resObj.status)) {
|
|
294
|
-
return MJSONWP;
|
|
295
|
-
}
|
|
296
|
-
if (!_.isUndefined(resObj.value)) {
|
|
297
|
-
return W3C;
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
/**
|
|
302
|
-
*
|
|
303
|
-
* @param {string} url
|
|
304
|
-
* @param {import('@appium/types').HTTPMethod} method
|
|
305
|
-
* @param {import('@appium/types').HTTPBody} [body=null]
|
|
306
|
-
* @returns {Promise<[import('@appium/types').ProxyResponse, import('@appium/types').HTTPBody]>}
|
|
307
|
-
*/
|
|
308
|
-
async proxyCommand(url, method, body = null) {
|
|
309
|
-
const parsedUrl = this._parseUrl(url);
|
|
310
|
-
const normalizedPathname = this._toNormalizedPathname(parsedUrl);
|
|
311
|
-
const commandName = normalizedPathname ? routeToCommandName(normalizedPathname, method) : '';
|
|
312
|
-
if (!commandName) {
|
|
313
|
-
return await this.proxy(url, method, body);
|
|
314
|
-
}
|
|
315
|
-
this.log.debug(`Matched '${url}' to command name '${commandName}'`);
|
|
316
|
-
|
|
317
|
-
return await this.protocolConverter.convertAndProxy(commandName, url, method, body);
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
/**
|
|
321
|
-
*
|
|
322
|
-
* @param {string} url
|
|
323
|
-
* @param {import('@appium/types').HTTPMethod} method
|
|
324
|
-
* @param {import('@appium/types').HTTPBody} [body=null]
|
|
325
|
-
* @returns {Promise<import('@appium/types').HTTPBody>}
|
|
326
|
-
*/
|
|
327
|
-
async command(url, method, body = null) {
|
|
328
|
-
let response;
|
|
329
|
-
let resBodyObj;
|
|
330
|
-
try {
|
|
331
|
-
[response, resBodyObj] = await this.proxyCommand(url, method, body);
|
|
332
|
-
} catch (err) {
|
|
333
|
-
if (isErrorType(err, errors.ProxyRequestError)) {
|
|
334
|
-
throw err.getActualError();
|
|
335
|
-
}
|
|
336
|
-
throw new errors.UnknownError(err.message);
|
|
337
|
-
}
|
|
338
|
-
const protocol = this.getProtocolFromResBody(resBodyObj);
|
|
339
|
-
if (protocol === MJSONWP) {
|
|
340
|
-
// Got response in MJSONWP format
|
|
341
|
-
if (response.statusCode === 200 && resBodyObj.status === 0) {
|
|
342
|
-
return resBodyObj.value;
|
|
343
|
-
}
|
|
344
|
-
const status = parseInt(resBodyObj.status, 10);
|
|
345
|
-
if (!isNaN(status) && status !== 0) {
|
|
346
|
-
let message = resBodyObj.value;
|
|
347
|
-
if (_.has(message, 'message')) {
|
|
348
|
-
message = message.message;
|
|
349
|
-
}
|
|
350
|
-
throw errorFromMJSONWPStatusCode(
|
|
351
|
-
status,
|
|
352
|
-
_.isEmpty(message) ? getSummaryByCode(status) : message
|
|
353
|
-
);
|
|
354
|
-
}
|
|
355
|
-
} else if (protocol === W3C) {
|
|
356
|
-
// Got response in W3C format
|
|
357
|
-
if (response.statusCode < 300) {
|
|
358
|
-
return resBodyObj.value;
|
|
359
|
-
}
|
|
360
|
-
if (_.isPlainObject(resBodyObj.value) && resBodyObj.value.error) {
|
|
361
|
-
throw errorFromW3CJsonCode(
|
|
362
|
-
resBodyObj.value.error,
|
|
363
|
-
resBodyObj.value.message,
|
|
364
|
-
resBodyObj.value.stacktrace
|
|
365
|
-
);
|
|
366
|
-
}
|
|
367
|
-
} else if (response.statusCode === 200) {
|
|
368
|
-
// Unknown protocol. Keeping it because of the backward compatibility
|
|
369
|
-
return resBodyObj;
|
|
370
|
-
}
|
|
371
|
-
throw new errors.UnknownError(
|
|
372
|
-
`Did not know what to do with response code '${response.statusCode}' ` +
|
|
373
|
-
`and response body '${_.truncate(JSON.stringify(resBodyObj), {
|
|
374
|
-
length: 300,
|
|
375
|
-
})}'`
|
|
376
|
-
);
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
/**
|
|
380
|
-
*
|
|
381
|
-
* @param {string} url
|
|
382
|
-
* @returns {string | null}
|
|
383
|
-
*/
|
|
384
|
-
getSessionIdFromUrl(url) {
|
|
385
|
-
const match = url.match(/\/session\/([^/]+)/);
|
|
386
|
-
return match ? match[1] : null;
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
/**
|
|
390
|
-
*
|
|
391
|
-
* @param {import('express').Request} req
|
|
392
|
-
* @param {import('express').Response} res
|
|
393
|
-
*/
|
|
394
|
-
async proxyReqRes(req, res) {
|
|
395
|
-
// ! this method must not throw any exceptions
|
|
396
|
-
// ! make sure to call res.send before return
|
|
397
|
-
/** @type {number} */
|
|
398
|
-
let statusCode;
|
|
399
|
-
/** @type {import('@appium/types').HTTPBody} */
|
|
400
|
-
let resBodyObj;
|
|
401
|
-
try {
|
|
402
|
-
let response;
|
|
403
|
-
[response, resBodyObj] = await this.proxyCommand(
|
|
404
|
-
req.originalUrl,
|
|
405
|
-
/** @type {import('@appium/types').HTTPMethod} */ (req.method),
|
|
406
|
-
req.body
|
|
407
|
-
);
|
|
408
|
-
statusCode = response.statusCode;
|
|
409
|
-
} catch (err) {
|
|
410
|
-
[statusCode, resBodyObj] = getResponseForW3CError(
|
|
411
|
-
isErrorType(err, errors.ProxyRequestError) ? err.getActualError() : err
|
|
412
|
-
);
|
|
413
|
-
}
|
|
414
|
-
res.setHeader('content-type', 'application/json; charset=utf-8');
|
|
415
|
-
if (!_.isPlainObject(resBodyObj)) {
|
|
416
|
-
const error = new errors.UnknownError(
|
|
417
|
-
`The downstream server response with the status code ${statusCode} is not a valid JSON object: ` +
|
|
418
|
-
_.truncate(`${resBodyObj}`, {length: 300})
|
|
419
|
-
);
|
|
420
|
-
[statusCode, resBodyObj] = getResponseForW3CError(error);
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
// if the proxied response contains a sessionId that the downstream
|
|
424
|
-
// driver has generated, we don't want to return that to the client.
|
|
425
|
-
// Instead, return the id from the request or from current session
|
|
426
|
-
if (_.has(resBodyObj, 'sessionId')) {
|
|
427
|
-
const reqSessionId = this.getSessionIdFromUrl(req.originalUrl);
|
|
428
|
-
if (reqSessionId) {
|
|
429
|
-
this.log.info(`Replacing sessionId ${resBodyObj.sessionId} with ${reqSessionId}`);
|
|
430
|
-
resBodyObj.sessionId = reqSessionId;
|
|
431
|
-
} else if (this.sessionId) {
|
|
432
|
-
this.log.info(`Replacing sessionId ${resBodyObj.sessionId} with ${this.sessionId}`);
|
|
433
|
-
resBodyObj.sessionId = this.sessionId;
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
resBodyObj.value = formatResponseValue(resBodyObj.value);
|
|
437
|
-
res.status(statusCode).json(ensureW3cResponse(resBodyObj));
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
/**
|
|
441
|
-
*
|
|
442
|
-
* @param {string} url
|
|
443
|
-
* @returns {ParsedUrl}
|
|
444
|
-
*/
|
|
445
|
-
_parseUrl(url) {
|
|
446
|
-
// eslint-disable-next-line n/no-deprecated-api -- we need relative URL support
|
|
447
|
-
const parsedUrl = nodeUrl.parse(url || '/');
|
|
448
|
-
if (
|
|
449
|
-
_.isNil(parsedUrl.href) || _.isNil(parsedUrl.pathname)
|
|
450
|
-
|| (parsedUrl.protocol && !['http:', 'https:'].includes(parsedUrl.protocol))
|
|
451
|
-
) {
|
|
452
|
-
throw new Error(`Did not know how to proxy the url '${url}'`);
|
|
453
|
-
}
|
|
454
|
-
return parsedUrl;
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
/**
|
|
458
|
-
*
|
|
459
|
-
* @param {ParsedUrl} parsedUrl
|
|
460
|
-
* @returns {string}
|
|
461
|
-
*/
|
|
462
|
-
_toNormalizedPathname(parsedUrl) {
|
|
463
|
-
if (!_.isString(parsedUrl.pathname)) {
|
|
464
|
-
return '';
|
|
465
|
-
}
|
|
466
|
-
let pathname = this.reqBasePath && parsedUrl.pathname.startsWith(this.reqBasePath)
|
|
467
|
-
? parsedUrl.pathname.replace(this.reqBasePath, '')
|
|
468
|
-
: parsedUrl.pathname;
|
|
469
|
-
const match = COMMAND_WITH_SESSION_ID_MATCHER(pathname);
|
|
470
|
-
// This is needed for the backward compatibility
|
|
471
|
-
// if drivers don't set reqBasePath properly
|
|
472
|
-
if (!this.reqBasePath) {
|
|
473
|
-
if (match && _.isArray(match.params?.prefix)) {
|
|
474
|
-
pathname = pathname.replace(`/${match.params?.prefix.join('/')}`, '');
|
|
475
|
-
} else if (_.startsWith(pathname, '/wd/hub')) {
|
|
476
|
-
pathname = pathname.replace('/wd/hub', '');
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
let result = pathname;
|
|
480
|
-
if (match) {
|
|
481
|
-
result = _.isArray(match.params?.command) ? `/${match.params.command.join('/')}` : '';
|
|
482
|
-
}
|
|
483
|
-
return _.trimEnd(result, '/');
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
export default JWProxy;
|
|
488
|
-
|
|
489
|
-
/**
|
|
490
|
-
* @typedef {Error & {response: {data: import('type-fest').JsonObject, status: import('http-status-codes').StatusCodes}}} ProxyError
|
|
491
|
-
* @typedef {nodeUrl.UrlWithStringQuery} ParsedUrl
|
|
492
|
-
* @typedef {typeof PROTOCOLS[keyof typeof PROTOCOLS]} Protocol
|
|
493
|
-
*/
|