@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.
- package/build/lib/basedriver/capabilities.d.ts +1 -1
- package/build/lib/basedriver/capabilities.d.ts.map +1 -1
- package/build/lib/basedriver/capabilities.js +58 -50
- package/build/lib/basedriver/capabilities.js.map +1 -1
- package/build/lib/basedriver/commands/bidi.d.ts.map +1 -1
- package/build/lib/basedriver/commands/bidi.js +10 -14
- package/build/lib/basedriver/commands/bidi.js.map +1 -1
- package/build/lib/basedriver/commands/event.d.ts.map +1 -1
- package/build/lib/basedriver/commands/event.js +4 -7
- package/build/lib/basedriver/commands/event.js.map +1 -1
- package/build/lib/basedriver/commands/execute.js +3 -6
- package/build/lib/basedriver/commands/execute.js.map +1 -1
- package/build/lib/basedriver/commands/find.d.ts.map +1 -1
- package/build/lib/basedriver/commands/find.js +2 -1
- package/build/lib/basedriver/commands/find.js.map +1 -1
- package/build/lib/basedriver/commands/log.d.ts.map +1 -1
- package/build/lib/basedriver/commands/log.js +1 -5
- package/build/lib/basedriver/commands/log.js.map +1 -1
- package/build/lib/basedriver/commands/timeout.d.ts.map +1 -1
- package/build/lib/basedriver/commands/timeout.js +9 -13
- package/build/lib/basedriver/commands/timeout.js.map +1 -1
- package/build/lib/basedriver/core.d.ts.map +1 -1
- package/build/lib/basedriver/core.js +17 -14
- package/build/lib/basedriver/core.js.map +1 -1
- package/build/lib/basedriver/device-settings.d.ts.map +1 -1
- package/build/lib/basedriver/device-settings.js +3 -7
- package/build/lib/basedriver/device-settings.js.map +1 -1
- package/build/lib/basedriver/driver.d.ts.map +1 -1
- package/build/lib/basedriver/driver.js +34 -38
- package/build/lib/basedriver/driver.js.map +1 -1
- package/build/lib/basedriver/extension-core.d.ts +4 -1
- package/build/lib/basedriver/extension-core.d.ts.map +1 -1
- package/build/lib/basedriver/extension-core.js +37 -13
- package/build/lib/basedriver/extension-core.js.map +1 -1
- package/build/lib/basedriver/helpers.d.ts.map +1 -1
- package/build/lib/basedriver/helpers.js +47 -33
- package/build/lib/basedriver/helpers.js.map +1 -1
- package/build/lib/basedriver/ipc.d.ts +36 -0
- package/build/lib/basedriver/ipc.d.ts.map +1 -0
- package/build/lib/basedriver/ipc.js +157 -0
- package/build/lib/basedriver/ipc.js.map +1 -0
- package/build/lib/basedriver/validation.d.ts.map +1 -1
- package/build/lib/basedriver/validation.js +27 -29
- package/build/lib/basedriver/validation.js.map +1 -1
- package/build/lib/express/express-logging.d.ts +0 -1
- package/build/lib/express/express-logging.d.ts.map +1 -1
- package/build/lib/express/express-logging.js +11 -11
- package/build/lib/express/express-logging.js.map +1 -1
- package/build/lib/express/idempotency.js +3 -6
- package/build/lib/express/idempotency.js.map +1 -1
- package/build/lib/express/middleware.d.ts.map +1 -1
- package/build/lib/express/middleware.js +6 -10
- package/build/lib/express/middleware.js.map +1 -1
- package/build/lib/express/server.d.ts +1 -1
- package/build/lib/express/server.d.ts.map +1 -1
- package/build/lib/express/server.js +82 -73
- package/build/lib/express/server.js.map +1 -1
- package/build/lib/express/websocket.d.ts.map +1 -1
- package/build/lib/express/websocket.js +6 -9
- package/build/lib/express/websocket.js.map +1 -1
- package/build/lib/helpers/capabilities.d.ts.map +1 -1
- package/build/lib/helpers/capabilities.js +14 -17
- package/build/lib/helpers/capabilities.js.map +1 -1
- package/build/lib/helpers/extension-command-name.js +2 -5
- package/build/lib/helpers/extension-command-name.js.map +1 -1
- package/build/lib/helpers/levenshtein-match.d.ts.map +1 -1
- package/build/lib/helpers/levenshtein-match.js +6 -7
- package/build/lib/helpers/levenshtein-match.js.map +1 -1
- package/build/lib/index.d.ts +2 -1
- package/build/lib/index.d.ts.map +1 -1
- package/build/lib/index.js +6 -16
- package/build/lib/index.js.map +1 -1
- package/build/lib/jsonwp-proxy/protocol-converter.d.ts.map +1 -1
- package/build/lib/jsonwp-proxy/protocol-converter.js +21 -18
- package/build/lib/jsonwp-proxy/protocol-converter.js.map +1 -1
- package/build/lib/jsonwp-proxy/proxy-request.d.ts +2 -2
- package/build/lib/jsonwp-proxy/proxy-request.d.ts.map +1 -1
- package/build/lib/jsonwp-proxy/proxy-request.js +25 -21
- package/build/lib/jsonwp-proxy/proxy-request.js.map +1 -1
- package/build/lib/jsonwp-proxy/proxy.d.ts.map +1 -1
- package/build/lib/jsonwp-proxy/proxy.js +45 -36
- package/build/lib/jsonwp-proxy/proxy.js.map +1 -1
- package/build/lib/protocol/errors.d.ts.map +1 -1
- package/build/lib/protocol/errors.js +33 -37
- package/build/lib/protocol/errors.js.map +1 -1
- package/build/lib/protocol/helpers.d.ts.map +1 -1
- package/build/lib/protocol/helpers.js +9 -8
- package/build/lib/protocol/helpers.js.map +1 -1
- package/build/lib/protocol/protocol.d.ts +1 -1
- package/build/lib/protocol/protocol.d.ts.map +1 -1
- package/build/lib/protocol/protocol.js +73 -61
- package/build/lib/protocol/protocol.js.map +1 -1
- package/build/lib/protocol/routes.d.ts +1 -1
- package/build/lib/protocol/routes.d.ts.map +1 -1
- package/build/lib/protocol/routes.js +16 -17
- package/build/lib/protocol/routes.js.map +1 -1
- package/build/lib/protocol/validators.d.ts.map +1 -1
- package/build/lib/protocol/validators.js +1 -5
- package/build/lib/protocol/validators.js.map +1 -1
- package/build/lib/test-pages/crash.d.ts.map +1 -0
- package/build/lib/test-pages/crash.js.map +1 -0
- package/build/lib/test-pages/env.d.ts +5 -0
- package/build/lib/test-pages/env.d.ts.map +1 -0
- package/build/lib/test-pages/env.js +12 -0
- package/build/lib/test-pages/env.js.map +1 -0
- package/build/lib/{express/static.d.ts → test-pages/handlers.d.ts} +1 -2
- package/build/lib/test-pages/handlers.d.ts.map +1 -0
- package/build/lib/{express/static.js → test-pages/handlers.js} +9 -12
- package/build/lib/test-pages/handlers.js.map +1 -0
- package/build/lib/test-pages/index.d.ts +6 -0
- package/build/lib/test-pages/index.d.ts.map +1 -0
- package/build/lib/test-pages/index.js +35 -0
- package/build/lib/test-pages/index.js.map +1 -0
- package/build/lib/test-pages/static-dir.d.ts +8 -0
- package/build/lib/test-pages/static-dir.d.ts.map +1 -0
- package/build/lib/test-pages/static-dir.js +24 -0
- package/build/lib/test-pages/static-dir.js.map +1 -0
- package/build/lib/test-pages/template.d.ts +3 -0
- package/build/lib/test-pages/template.d.ts.map +1 -0
- package/build/lib/test-pages/template.js +19 -0
- package/build/lib/test-pages/template.js.map +1 -0
- package/build/lib/utils.d.ts +14 -0
- package/build/lib/utils.d.ts.map +1 -0
- package/build/lib/utils.js +55 -0
- package/build/lib/utils.js.map +1 -0
- package/lib/basedriver/capabilities.ts +126 -115
- package/lib/basedriver/commands/bidi.ts +11 -11
- package/lib/basedriver/commands/event.ts +17 -11
- package/lib/basedriver/commands/execute.ts +15 -12
- package/lib/basedriver/commands/find.ts +20 -12
- package/lib/basedriver/commands/log.ts +4 -3
- package/lib/basedriver/commands/timeout.ts +22 -14
- package/lib/basedriver/core.ts +26 -26
- package/lib/basedriver/device-settings.ts +7 -12
- package/lib/basedriver/driver.ts +62 -50
- package/lib/basedriver/extension-core.ts +60 -18
- package/lib/basedriver/helpers.ts +81 -52
- package/lib/basedriver/ipc.ts +198 -0
- package/lib/basedriver/validation.ts +37 -30
- package/lib/express/express-logging.ts +16 -20
- package/lib/express/idempotency.ts +9 -9
- package/lib/express/middleware.ts +14 -18
- package/lib/express/server.ts +118 -120
- package/lib/express/websocket.ts +11 -15
- package/lib/helpers/capabilities.ts +21 -16
- package/lib/helpers/extension-command-name.ts +3 -3
- package/lib/helpers/levenshtein-match.ts +20 -14
- package/lib/index.js +3 -12
- package/lib/jsonwp-proxy/protocol-converter.ts +58 -35
- package/lib/jsonwp-proxy/proxy-request.ts +26 -26
- package/lib/jsonwp-proxy/proxy.ts +74 -75
- package/lib/protocol/errors.ts +69 -88
- package/lib/protocol/helpers.ts +9 -5
- package/lib/protocol/protocol.ts +149 -107
- package/lib/protocol/routes.ts +17 -17
- package/lib/protocol/validators.ts +1 -3
- package/lib/test-pages/env.ts +9 -0
- package/lib/{express/static.ts → test-pages/handlers.ts} +10 -22
- package/lib/test-pages/index.ts +34 -0
- package/lib/test-pages/static-dir.ts +19 -0
- package/lib/test-pages/template.ts +17 -0
- package/lib/utils.ts +65 -0
- package/package.json +10 -13
- package/tsconfig.json +1 -0
- package/build/lib/express/crash.d.ts.map +0 -1
- package/build/lib/express/crash.js.map +0 -1
- package/build/lib/express/static.d.ts.map +0 -1
- package/build/lib/express/static.js.map +0 -1
- /package/build/lib/{express → test-pages}/crash.d.ts +0 -0
- /package/build/lib/{express → test-pages}/crash.js +0 -0
- /package/lib/{express → test-pages}/crash.ts +0 -0
- /package/{static → test-fixtures/static}/appium.png +0 -0
- /package/{static → test-fixtures/static}/favicon.ico +0 -0
- /package/{static → test-fixtures/static}/js/jquery.min.js +0 -0
- /package/{static → test-fixtures/static}/test/frameset.html +0 -0
- /package/{static → test-fixtures/static}/test/guinea-pig-app-banner.html +0 -0
- /package/{static → test-fixtures/static}/test/guinea-pig-scrollable.html +0 -0
- /package/{static → test-fixtures/static}/test/guinea-pig.html +0 -0
- /package/{static → test-fixtures/static}/test/guinea-pig2.html +0 -0
- /package/{static → test-fixtures/static}/test/guinea-pig3.html +0 -0
- /package/{static → test-fixtures/static}/test/guinea-pig4.html +0 -0
- /package/{static → test-fixtures/static}/test/guinea-pig5.html +0 -0
- /package/{static → test-fixtures/static}/test/iframes.html +0 -0
- /package/{static → test-fixtures/static}/test/shadow-dom.html +0 -0
- /package/{static → test-fixtures/static}/test/subframe1.html +0 -0
- /package/{static → test-fixtures/static}/test/subframe2.html +0 -0
- /package/{static → test-fixtures/static}/test/subframe3.html +0 -0
- /package/{static → test-fixtures/static}/test/touch.html +0 -0
- /package/{static → test-fixtures/static}/test/welcome.html +0 -0
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type {AppiumLogger, HTTPBody, ProxyResponse} from '@appium/types';
|
|
2
|
-
import _ from 'lodash';
|
|
3
2
|
import {logger, util} from '@appium/support';
|
|
4
3
|
import {duplicateKeys} from '../basedriver/helpers';
|
|
5
4
|
import {MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY, PROTOCOLS} from '../constants';
|
|
@@ -7,7 +6,7 @@ import {MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY, PROTOCOLS} from '../constants';
|
|
|
7
6
|
export type ProxyFunction = (
|
|
8
7
|
url: string,
|
|
9
8
|
method: string,
|
|
10
|
-
body?: HTTPBody
|
|
9
|
+
body?: HTTPBody,
|
|
11
10
|
) => Promise<[ProxyResponse, HTTPBody]>;
|
|
12
11
|
|
|
13
12
|
export const COMMAND_URLS_CONFLICTS = [
|
|
@@ -20,7 +19,8 @@ export const COMMAND_URLS_CONFLICTS = [
|
|
|
20
19
|
},
|
|
21
20
|
{
|
|
22
21
|
commandNames: ['getElementScreenshot'],
|
|
23
|
-
jsonwpConverter: (url: string) =>
|
|
22
|
+
jsonwpConverter: (url: string) =>
|
|
23
|
+
url.replace(/\/element\/([^/]+)\/screenshot$/, '/screenshot/$1'),
|
|
24
24
|
w3cConverter: (url: string) => url.replace(/\/screenshot\/([^/]+)/, '/element/$1/screenshot'),
|
|
25
25
|
},
|
|
26
26
|
{
|
|
@@ -60,7 +60,7 @@ export class ProtocolConverter {
|
|
|
60
60
|
*/
|
|
61
61
|
constructor(
|
|
62
62
|
public proxyFunc: ProxyFunction,
|
|
63
|
-
log: AppiumLogger | null = null
|
|
63
|
+
log: AppiumLogger | null = null,
|
|
64
64
|
) {
|
|
65
65
|
this._log = log;
|
|
66
66
|
}
|
|
@@ -85,7 +85,7 @@ export class ProtocolConverter {
|
|
|
85
85
|
commandName: string,
|
|
86
86
|
url: string,
|
|
87
87
|
method: string,
|
|
88
|
-
body?: HTTPBody
|
|
88
|
+
body?: HTTPBody,
|
|
89
89
|
): Promise<[ProxyResponse, HTTPBody]> {
|
|
90
90
|
if (!this.downstreamProtocol) {
|
|
91
91
|
return await this.proxyFunc(url, method, body);
|
|
@@ -118,12 +118,12 @@ export class ProtocolConverter {
|
|
|
118
118
|
this.downstreamProtocol === MJSONWP ? jsonwpConverter(url) : w3cConverter(url);
|
|
119
119
|
if (rewrittenUrl === url) {
|
|
120
120
|
this.log.debug(
|
|
121
|
-
`Did not know how to rewrite the original URL '${url}' for ${this.downstreamProtocol} protocol
|
|
121
|
+
`Did not know how to rewrite the original URL '${url}' for ${this.downstreamProtocol} protocol`,
|
|
122
122
|
);
|
|
123
123
|
break;
|
|
124
124
|
}
|
|
125
125
|
this.log.info(
|
|
126
|
-
`Rewrote the original URL '${url}' to '${rewrittenUrl}' for ${this.downstreamProtocol} protocol
|
|
126
|
+
`Rewrote the original URL '${url}' to '${rewrittenUrl}' for ${this.downstreamProtocol} protocol`,
|
|
127
127
|
);
|
|
128
128
|
return await this.proxyFunc(rewrittenUrl, method, body);
|
|
129
129
|
}
|
|
@@ -138,12 +138,16 @@ export class ProtocolConverter {
|
|
|
138
138
|
* provided in the request, we need to do 3 proxies and combine the result.
|
|
139
139
|
*/
|
|
140
140
|
private getTimeoutRequestObjects(body: HTTPBody): Record<string, unknown>[] {
|
|
141
|
-
if (
|
|
141
|
+
if (body == null) {
|
|
142
142
|
return [];
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
const bodyObj = (util.safeJsonParse(body) as Record<string, unknown>) ?? {};
|
|
146
|
-
if (
|
|
146
|
+
if (
|
|
147
|
+
this.downstreamProtocol === W3C &&
|
|
148
|
+
Object.hasOwn(bodyObj, 'ms') &&
|
|
149
|
+
Object.hasOwn(bodyObj, 'type')
|
|
150
|
+
) {
|
|
147
151
|
const typeToW3C = (x: string) => (x === 'page load' ? 'pageLoad' : x);
|
|
148
152
|
return [
|
|
149
153
|
{
|
|
@@ -152,15 +156,20 @@ export class ProtocolConverter {
|
|
|
152
156
|
];
|
|
153
157
|
}
|
|
154
158
|
|
|
155
|
-
if (
|
|
159
|
+
if (
|
|
160
|
+
this.downstreamProtocol === MJSONWP &&
|
|
161
|
+
(!Object.hasOwn(bodyObj, 'ms') || !Object.hasOwn(bodyObj, 'type'))
|
|
162
|
+
) {
|
|
156
163
|
const typeToJSONWP = (x: string) => (x === 'pageLoad' ? 'page load' : x);
|
|
157
|
-
return
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
+
return (
|
|
165
|
+
Object.entries(bodyObj)
|
|
166
|
+
// Only transform the entry if ms value is a valid positive float number
|
|
167
|
+
.filter((pair) => /^\d+(?:[.,]\d*?)?$/.test(`${pair[1]}`))
|
|
168
|
+
.map((pair) => ({
|
|
169
|
+
type: typeToJSONWP(pair[0]),
|
|
170
|
+
ms: pair[1],
|
|
171
|
+
}))
|
|
172
|
+
);
|
|
164
173
|
}
|
|
165
174
|
|
|
166
175
|
return [bodyObj];
|
|
@@ -172,14 +181,14 @@ export class ProtocolConverter {
|
|
|
172
181
|
private async proxySetTimeouts(
|
|
173
182
|
url: string,
|
|
174
183
|
method: string,
|
|
175
|
-
body?: HTTPBody
|
|
184
|
+
body?: HTTPBody,
|
|
176
185
|
): Promise<[ProxyResponse, HTTPBody]> {
|
|
177
186
|
const timeoutRequestObjects = this.getTimeoutRequestObjects(body);
|
|
178
187
|
if (timeoutRequestObjects.length === 0) {
|
|
179
188
|
return await this.proxyFunc(url, method, body);
|
|
180
189
|
}
|
|
181
190
|
this.log.debug(
|
|
182
|
-
`Will send the following request bodies to /timeouts: ${JSON.stringify(timeoutRequestObjects)}
|
|
191
|
+
`Will send the following request bodies to /timeouts: ${JSON.stringify(timeoutRequestObjects)}`,
|
|
183
192
|
);
|
|
184
193
|
|
|
185
194
|
let response!: ProxyResponse;
|
|
@@ -203,19 +212,23 @@ export class ProtocolConverter {
|
|
|
203
212
|
private async proxySetWindow(
|
|
204
213
|
url: string,
|
|
205
214
|
method: string,
|
|
206
|
-
body: HTTPBody
|
|
215
|
+
body: HTTPBody,
|
|
207
216
|
): Promise<[ProxyResponse, HTTPBody]> {
|
|
208
217
|
const bodyObj = util.safeJsonParse(body);
|
|
209
|
-
if (
|
|
218
|
+
if (util.isPlainObject(bodyObj)) {
|
|
210
219
|
const obj = bodyObj as Record<string, unknown>;
|
|
211
|
-
if (
|
|
220
|
+
if (
|
|
221
|
+
this.downstreamProtocol === W3C &&
|
|
222
|
+
Object.hasOwn(bodyObj, 'name') &&
|
|
223
|
+
!Object.hasOwn(bodyObj, 'handle')
|
|
224
|
+
) {
|
|
212
225
|
this.log.debug(`Copied 'name' value '${obj.name}' to 'handle' as per W3C spec`);
|
|
213
226
|
return await this.proxyFunc(url, method, {...obj, handle: obj.name});
|
|
214
227
|
}
|
|
215
228
|
if (
|
|
216
229
|
this.downstreamProtocol === MJSONWP &&
|
|
217
|
-
|
|
218
|
-
!
|
|
230
|
+
Object.hasOwn(bodyObj, 'handle') &&
|
|
231
|
+
!Object.hasOwn(bodyObj, 'name')
|
|
219
232
|
) {
|
|
220
233
|
this.log.debug(`Copied 'handle' value '${obj.handle}' to 'name' as per JSONWP spec`);
|
|
221
234
|
return await this.proxyFunc(url, method, {...obj, name: obj.handle});
|
|
@@ -227,16 +240,19 @@ export class ProtocolConverter {
|
|
|
227
240
|
private async proxySetValue(
|
|
228
241
|
url: string,
|
|
229
242
|
method: string,
|
|
230
|
-
body: HTTPBody
|
|
243
|
+
body: HTTPBody,
|
|
231
244
|
): Promise<[ProxyResponse, HTTPBody]> {
|
|
232
245
|
const bodyObj = util.safeJsonParse(body) as Record<string, unknown> | undefined;
|
|
233
|
-
if (
|
|
246
|
+
if (
|
|
247
|
+
util.isPlainObject(bodyObj) &&
|
|
248
|
+
(util.hasValue(bodyObj?.text) || util.hasValue(bodyObj?.value))
|
|
249
|
+
) {
|
|
234
250
|
let {text, value} = bodyObj;
|
|
235
251
|
if (util.hasValue(text) && !util.hasValue(value)) {
|
|
236
|
-
value =
|
|
252
|
+
value = typeof text === 'string' ? [...text] : Array.isArray(text) ? text : [];
|
|
237
253
|
this.log.debug(`Added 'value' property to 'setValue' request body`);
|
|
238
254
|
} else if (!util.hasValue(text) && util.hasValue(value)) {
|
|
239
|
-
text =
|
|
255
|
+
text = Array.isArray(value) ? value.join('') : typeof value === 'string' ? value : '';
|
|
240
256
|
this.log.debug(`Added 'text' property to 'setValue' request body`);
|
|
241
257
|
}
|
|
242
258
|
return await this.proxyFunc(url, method, {...bodyObj, text, value});
|
|
@@ -247,13 +263,20 @@ export class ProtocolConverter {
|
|
|
247
263
|
private async proxySetFrame(
|
|
248
264
|
url: string,
|
|
249
265
|
method: string,
|
|
250
|
-
body: HTTPBody
|
|
266
|
+
body: HTTPBody,
|
|
251
267
|
): Promise<[ProxyResponse, HTTPBody]> {
|
|
252
268
|
const bodyObj = util.safeJsonParse(body);
|
|
253
|
-
if (
|
|
269
|
+
if (
|
|
270
|
+
Object.hasOwn(bodyObj ?? {}, 'id') &&
|
|
271
|
+
util.isPlainObject((bodyObj as Record<string, unknown>).id)
|
|
272
|
+
) {
|
|
254
273
|
return await this.proxyFunc(url, method, {
|
|
255
274
|
...(bodyObj as object),
|
|
256
|
-
id: duplicateKeys(
|
|
275
|
+
id: duplicateKeys(
|
|
276
|
+
(bodyObj as Record<string, unknown>).id as object,
|
|
277
|
+
MJSONWP_ELEMENT_KEY,
|
|
278
|
+
W3C_ELEMENT_KEY,
|
|
279
|
+
),
|
|
257
280
|
});
|
|
258
281
|
}
|
|
259
282
|
return await this.proxyFunc(url, method, body);
|
|
@@ -262,14 +285,14 @@ export class ProtocolConverter {
|
|
|
262
285
|
private async proxyPerformActions(
|
|
263
286
|
url: string,
|
|
264
287
|
method: string,
|
|
265
|
-
body: HTTPBody
|
|
288
|
+
body: HTTPBody,
|
|
266
289
|
): Promise<[ProxyResponse, HTTPBody]> {
|
|
267
290
|
const bodyObj = util.safeJsonParse(body);
|
|
268
|
-
if (
|
|
291
|
+
if (util.isPlainObject(bodyObj)) {
|
|
269
292
|
return await this.proxyFunc(
|
|
270
293
|
url,
|
|
271
294
|
method,
|
|
272
|
-
duplicateKeys(bodyObj as object, MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY)
|
|
295
|
+
duplicateKeys(bodyObj as object, MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY),
|
|
273
296
|
);
|
|
274
297
|
}
|
|
275
298
|
return await this.proxyFunc(url, method, body);
|
|
@@ -277,7 +300,7 @@ export class ProtocolConverter {
|
|
|
277
300
|
|
|
278
301
|
private async proxyReleaseActions(
|
|
279
302
|
url: string,
|
|
280
|
-
method: string
|
|
303
|
+
method: string,
|
|
281
304
|
): Promise<[ProxyResponse, HTTPBody]> {
|
|
282
305
|
return await this.proxyFunc(url, method);
|
|
283
306
|
}
|
|
@@ -1,19 +1,16 @@
|
|
|
1
1
|
import axios from 'axios';
|
|
2
|
-
import { CancellationError } from 'bluebird';
|
|
3
|
-
import EventEmitter from 'node:events';
|
|
4
|
-
|
|
5
|
-
const CANCEL_EVENT = 'cancel';
|
|
6
|
-
const FINISH_EVENT = 'finish';
|
|
7
2
|
|
|
8
3
|
export class ProxyRequest {
|
|
9
4
|
private readonly _requestConfig: axios.RawAxiosRequestConfig;
|
|
10
|
-
private
|
|
11
|
-
private
|
|
5
|
+
private _resultPromise: Promise<axios.AxiosResponse> | null;
|
|
6
|
+
private _abortController: AbortController | null;
|
|
7
|
+
private _cancelled: boolean;
|
|
12
8
|
|
|
13
9
|
constructor(requestConfig: axios.RawAxiosRequestConfig<any>) {
|
|
14
10
|
this._requestConfig = requestConfig;
|
|
15
|
-
this._ee = new EventEmitter();
|
|
16
11
|
this._resultPromise = null;
|
|
12
|
+
this._abortController = null;
|
|
13
|
+
this._cancelled = false;
|
|
17
14
|
}
|
|
18
15
|
|
|
19
16
|
async execute(): Promise<axios.AxiosResponse> {
|
|
@@ -21,32 +18,35 @@ export class ProxyRequest {
|
|
|
21
18
|
return await this._resultPromise;
|
|
22
19
|
}
|
|
23
20
|
|
|
21
|
+
const abortController = new AbortController();
|
|
22
|
+
this._abortController = abortController;
|
|
23
|
+
this._cancelled = false;
|
|
24
|
+
|
|
24
25
|
try {
|
|
25
|
-
this._resultPromise =
|
|
26
|
-
this._makeRacingTimer(),
|
|
27
|
-
this._makeRequest(),
|
|
28
|
-
]);
|
|
26
|
+
this._resultPromise = this._makeRequest(abortController.signal);
|
|
29
27
|
return await this._resultPromise;
|
|
30
28
|
} finally {
|
|
31
|
-
this.
|
|
32
|
-
this._ee.removeAllListeners();
|
|
29
|
+
this._abortController = null;
|
|
33
30
|
}
|
|
34
31
|
}
|
|
35
32
|
|
|
36
33
|
cancel(): void {
|
|
37
|
-
this.
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
private async _makeRequest(): Promise<axios.AxiosResponse> {
|
|
41
|
-
return await axios(this._requestConfig);
|
|
34
|
+
this._cancelled = true;
|
|
35
|
+
this._abortController?.abort();
|
|
42
36
|
}
|
|
43
37
|
|
|
44
|
-
private async
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
)
|
|
50
|
-
})
|
|
38
|
+
private async _makeRequest(signal: AbortSignal): Promise<axios.AxiosResponse> {
|
|
39
|
+
try {
|
|
40
|
+
return await axios({
|
|
41
|
+
...this._requestConfig,
|
|
42
|
+
signal,
|
|
43
|
+
});
|
|
44
|
+
} catch (err) {
|
|
45
|
+
if (this._cancelled && axios.isCancel(err)) {
|
|
46
|
+
// The request was cancelled; do not propagate the error to callers.
|
|
47
|
+
return await new Promise<axios.AxiosResponse>(() => {});
|
|
48
|
+
}
|
|
49
|
+
throw err;
|
|
50
|
+
}
|
|
51
51
|
}
|
|
52
52
|
}
|
|
@@ -6,7 +6,6 @@ import type {
|
|
|
6
6
|
ProxyOptions,
|
|
7
7
|
ProxyResponse,
|
|
8
8
|
} from '@appium/types';
|
|
9
|
-
import _ from 'lodash';
|
|
10
9
|
import {logger, util} from '@appium/support';
|
|
11
10
|
import {getSummaryByCode} from '../jsonwp-status/status';
|
|
12
11
|
import {
|
|
@@ -19,6 +18,7 @@ import {
|
|
|
19
18
|
import {isSessionCommand, routeToCommandName} from '../protocol';
|
|
20
19
|
import {MAX_LOG_BODY_LENGTH, DEFAULT_BASE_PATH, PROTOCOLS} from '../constants';
|
|
21
20
|
import {ProtocolConverter} from './protocol-converter';
|
|
21
|
+
import {omit, pick} from '../utils';
|
|
22
22
|
import {formatResponseValue, ensureW3cResponse} from '../protocol/helpers';
|
|
23
23
|
import http from 'node:http';
|
|
24
24
|
import https from 'node:https';
|
|
@@ -31,7 +31,7 @@ import type {AxiosError, AxiosResponse, RawAxiosRequestConfig} from 'axios';
|
|
|
31
31
|
const DEFAULT_LOG = logger.getLogger('WD Proxy');
|
|
32
32
|
const DEFAULT_REQUEST_TIMEOUT = 240000;
|
|
33
33
|
const COMMAND_WITH_SESSION_ID_MATCHER = pathToRegexMatch(
|
|
34
|
-
'{/*prefix}/session/:sessionId{/*command}'
|
|
34
|
+
'{/*prefix}/session/:sessionId{/*command}',
|
|
35
35
|
);
|
|
36
36
|
|
|
37
37
|
const {MJSONWP, W3C} = PROTOCOLS;
|
|
@@ -69,8 +69,11 @@ export class JWProxy {
|
|
|
69
69
|
private readonly _log: AppiumLogger | undefined;
|
|
70
70
|
|
|
71
71
|
constructor(opts: ProxyOptions = {}) {
|
|
72
|
-
const
|
|
73
|
-
|
|
72
|
+
const filteredOptsWithoutLog = omit(
|
|
73
|
+
pick(opts as Record<string, unknown>, ALLOWED_OPTS),
|
|
74
|
+
'log',
|
|
75
|
+
) as Omit<ProxyOptions, 'log'>;
|
|
76
|
+
const options = {
|
|
74
77
|
scheme: 'http',
|
|
75
78
|
server: 'localhost',
|
|
76
79
|
port: 4444,
|
|
@@ -78,7 +81,8 @@ export class JWProxy {
|
|
|
78
81
|
reqBasePath: DEFAULT_BASE_PATH,
|
|
79
82
|
sessionId: null,
|
|
80
83
|
timeout: DEFAULT_REQUEST_TIMEOUT,
|
|
81
|
-
|
|
84
|
+
...filteredOptsWithoutLog,
|
|
85
|
+
} as ProxyOptions & {
|
|
82
86
|
scheme: string;
|
|
83
87
|
server: string;
|
|
84
88
|
port: number;
|
|
@@ -88,7 +92,14 @@ export class JWProxy {
|
|
|
88
92
|
timeout: number;
|
|
89
93
|
};
|
|
90
94
|
options.scheme = options.scheme.toLowerCase();
|
|
91
|
-
|
|
95
|
+
this.scheme = options.scheme;
|
|
96
|
+
this.server = options.server;
|
|
97
|
+
this.port = options.port;
|
|
98
|
+
this.base = options.base;
|
|
99
|
+
this.reqBasePath = options.reqBasePath;
|
|
100
|
+
this.sessionId = options.sessionId;
|
|
101
|
+
this.timeout = options.timeout;
|
|
102
|
+
this.headers = options.headers;
|
|
92
103
|
|
|
93
104
|
this._activeRequests = [];
|
|
94
105
|
this._downstreamProtocol = null;
|
|
@@ -147,13 +158,10 @@ export class JWProxy {
|
|
|
147
158
|
getUrlForProxy(url: string, method?: HTTPMethod): string {
|
|
148
159
|
const parsedUrl = this._parseUrl(url);
|
|
149
160
|
const normalizedPathname = this._toNormalizedPathname(parsedUrl);
|
|
150
|
-
const commandName = normalizedPathname
|
|
151
|
-
|
|
152
|
-
: '';
|
|
153
|
-
const requiresSessionId =
|
|
154
|
-
!commandName || (commandName && isSessionCommand(commandName));
|
|
161
|
+
const commandName = normalizedPathname ? routeToCommandName(normalizedPathname, method) : '';
|
|
162
|
+
const requiresSessionId = !commandName || (commandName && isSessionCommand(commandName));
|
|
155
163
|
const proxyPrefix = `${this.scheme}://${this.server}:${this.port}${this.base}`;
|
|
156
|
-
let proxySuffix = normalizedPathname ? `/${
|
|
164
|
+
let proxySuffix = normalizedPathname ? `/${normalizedPathname.replace(/^\/+/, '')}` : '';
|
|
157
165
|
if (parsedUrl.search) {
|
|
158
166
|
proxySuffix += parsedUrl.search;
|
|
159
167
|
}
|
|
@@ -161,9 +169,7 @@ export class JWProxy {
|
|
|
161
169
|
return `${proxyPrefix}${proxySuffix}`;
|
|
162
170
|
}
|
|
163
171
|
if (!this.sessionId) {
|
|
164
|
-
throw new ReferenceError(
|
|
165
|
-
`Session ID is not set, but saw a URL that requires it (${url})`
|
|
166
|
-
);
|
|
172
|
+
throw new ReferenceError(`Session ID is not set, but saw a URL that requires it (${url})`);
|
|
167
173
|
}
|
|
168
174
|
return `${proxyPrefix}/session/${this.sessionId}${proxySuffix}`;
|
|
169
175
|
}
|
|
@@ -174,12 +180,12 @@ export class JWProxy {
|
|
|
174
180
|
async proxy(
|
|
175
181
|
url: string,
|
|
176
182
|
method: string,
|
|
177
|
-
body: HTTPBody = null
|
|
183
|
+
body: HTTPBody = null,
|
|
178
184
|
): Promise<[ProxyResponse, HTTPBody]> {
|
|
179
185
|
method = method.toUpperCase();
|
|
180
186
|
const newUrl = this.getUrlForProxy(url, method as HTTPMethod);
|
|
181
187
|
const truncateBody = (content: unknown): string =>
|
|
182
|
-
|
|
188
|
+
util.truncateString(typeof content === 'string' ? content : JSON.stringify(content), {
|
|
183
189
|
length: MAX_LOG_BODY_LENGTH,
|
|
184
190
|
});
|
|
185
191
|
const reqOpts: RawAxiosRequestConfig = {
|
|
@@ -206,11 +212,11 @@ export class JWProxy {
|
|
|
206
212
|
this.log.warn(
|
|
207
213
|
'Invalid body payload (%s): %s',
|
|
208
214
|
(error as Error).message,
|
|
209
|
-
logger.markSensitive(truncateBody(body))
|
|
215
|
+
logger.markSensitive(truncateBody(body)),
|
|
210
216
|
);
|
|
211
217
|
throw new Error(
|
|
212
218
|
'Cannot interpret the request body as valid JSON. Check the server log for more details.',
|
|
213
|
-
{cause: error}
|
|
219
|
+
{cause: error},
|
|
214
220
|
);
|
|
215
221
|
}
|
|
216
222
|
} else {
|
|
@@ -224,7 +230,7 @@ export class JWProxy {
|
|
|
224
230
|
url || '/',
|
|
225
231
|
method,
|
|
226
232
|
newUrl,
|
|
227
|
-
reqOpts.data ? logger.markSensitive(truncateBody(reqOpts.data)) : 'no'
|
|
233
|
+
reqOpts.data ? logger.markSensitive(truncateBody(reqOpts.data)) : 'no',
|
|
228
234
|
);
|
|
229
235
|
|
|
230
236
|
const throwProxyError = (error: unknown): never => {
|
|
@@ -242,7 +248,7 @@ export class JWProxy {
|
|
|
242
248
|
const {data, status, headers} = await this.request(reqOpts);
|
|
243
249
|
// `data` might be really big
|
|
244
250
|
// Be careful while handling it to avoid memory leaks
|
|
245
|
-
if (!
|
|
251
|
+
if (!util.isPlainObject(data)) {
|
|
246
252
|
// The response should be a valid JSON object
|
|
247
253
|
// If it cannot be coerced to an object then the response is wrong
|
|
248
254
|
throwProxyError(data);
|
|
@@ -252,23 +258,14 @@ export class JWProxy {
|
|
|
252
258
|
const isSessionCreationRequest = url.endsWith('/session') && method === 'POST';
|
|
253
259
|
if (isSessionCreationRequest) {
|
|
254
260
|
if (status === 200) {
|
|
255
|
-
const value =
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
const raw =
|
|
259
|
-
(data as Record<string, unknown>).sessionId ?? value?.sessionId;
|
|
260
|
-
this.sessionId =
|
|
261
|
-
typeof raw === 'string' ? raw : raw != null ? String(raw) : null;
|
|
261
|
+
const value = data.value as Record<string, unknown> | undefined;
|
|
262
|
+
const raw = data.sessionId ?? value?.sessionId;
|
|
263
|
+
this.sessionId = typeof raw === 'string' ? raw : raw != null ? String(raw) : null;
|
|
262
264
|
}
|
|
263
|
-
this.downstreamProtocol = this.getProtocolFromResBody(
|
|
264
|
-
data as Record<string, unknown>
|
|
265
|
-
) ?? this.downstreamProtocol;
|
|
265
|
+
this.downstreamProtocol = this.getProtocolFromResBody(data) ?? this.downstreamProtocol;
|
|
266
266
|
this.log.info(`Determined the downstream protocol as '${this.downstreamProtocol}'`);
|
|
267
267
|
}
|
|
268
|
-
if (
|
|
269
|
-
_.has(data, 'status') &&
|
|
270
|
-
parseInt((data as Record<string, unknown>).status as string, 10) !== 0
|
|
271
|
-
) {
|
|
268
|
+
if (Object.hasOwn(data, 'status') && parseInt(data.status as string, 10) !== 0) {
|
|
272
269
|
throwProxyError(data);
|
|
273
270
|
}
|
|
274
271
|
return [
|
|
@@ -288,18 +285,14 @@ export class JWProxy {
|
|
|
288
285
|
this.log.info(
|
|
289
286
|
util.hasValue(err.response.status)
|
|
290
287
|
? `Got response with status ${err.response.status}: ${error}`
|
|
291
|
-
: `Got response with unknown status: ${error}
|
|
288
|
+
: `Got response with unknown status: ${error}`,
|
|
292
289
|
);
|
|
293
290
|
}
|
|
294
291
|
} else {
|
|
295
292
|
proxyErrorMsg = `Could not proxy command to the remote server. Original error: ${err.message}`;
|
|
296
293
|
this.log.info(err.message);
|
|
297
294
|
}
|
|
298
|
-
throw new errors.ProxyRequestError(
|
|
299
|
-
proxyErrorMsg,
|
|
300
|
-
err.response?.data,
|
|
301
|
-
err.response?.status
|
|
302
|
-
);
|
|
295
|
+
throw new errors.ProxyRequestError(proxyErrorMsg, err.response?.data, err.response?.status);
|
|
303
296
|
}
|
|
304
297
|
}
|
|
305
298
|
|
|
@@ -307,10 +300,10 @@ export class JWProxy {
|
|
|
307
300
|
* Detects the downstream protocol from a response body.
|
|
308
301
|
*/
|
|
309
302
|
getProtocolFromResBody(resObj: Record<string, unknown>): Protocol | undefined {
|
|
310
|
-
if (
|
|
303
|
+
if (Number.isInteger(resObj.status)) {
|
|
311
304
|
return MJSONWP;
|
|
312
305
|
}
|
|
313
|
-
if (
|
|
306
|
+
if (resObj.value !== undefined) {
|
|
314
307
|
return W3C;
|
|
315
308
|
}
|
|
316
309
|
}
|
|
@@ -321,13 +314,11 @@ export class JWProxy {
|
|
|
321
314
|
async proxyCommand(
|
|
322
315
|
url: string,
|
|
323
316
|
method: HTTPMethod,
|
|
324
|
-
body: HTTPBody = null
|
|
317
|
+
body: HTTPBody = null,
|
|
325
318
|
): Promise<[ProxyResponse, HTTPBody]> {
|
|
326
319
|
const parsedUrl = this._parseUrl(url);
|
|
327
320
|
const normalizedPathname = this._toNormalizedPathname(parsedUrl);
|
|
328
|
-
const commandName = normalizedPathname
|
|
329
|
-
? routeToCommandName(normalizedPathname, method)
|
|
330
|
-
: '';
|
|
321
|
+
const commandName = normalizedPathname ? routeToCommandName(normalizedPathname, method) : '';
|
|
331
322
|
if (!commandName) {
|
|
332
323
|
return await this.proxy(url, method, body);
|
|
333
324
|
}
|
|
@@ -339,11 +330,7 @@ export class JWProxy {
|
|
|
339
330
|
/**
|
|
340
331
|
* Executes a WebDriver command and returns the unwrapped `value` field (or throws).
|
|
341
332
|
*/
|
|
342
|
-
async command(
|
|
343
|
-
url: string,
|
|
344
|
-
method: HTTPMethod,
|
|
345
|
-
body: HTTPBody = null
|
|
346
|
-
): Promise<HTTPBody> {
|
|
333
|
+
async command(url: string, method: HTTPMethod, body: HTTPBody = null): Promise<HTTPBody> {
|
|
347
334
|
let response: ProxyResponse;
|
|
348
335
|
let resBodyObj: HTTPBody;
|
|
349
336
|
try {
|
|
@@ -363,23 +350,26 @@ export class JWProxy {
|
|
|
363
350
|
const status = parseInt(resBody.status as string, 10);
|
|
364
351
|
if (!isNaN(status) && status !== 0) {
|
|
365
352
|
let message: unknown = resBody.value;
|
|
366
|
-
if (
|
|
353
|
+
if (util.isPlainObject(message) && Object.hasOwn(message, 'message')) {
|
|
367
354
|
message = (message as Record<string, unknown>).message;
|
|
368
355
|
}
|
|
369
|
-
throw errorFromMJSONWPStatusCode(
|
|
370
|
-
|
|
371
|
-
|
|
356
|
+
throw errorFromMJSONWPStatusCode(
|
|
357
|
+
status,
|
|
358
|
+
util.isEmpty(message)
|
|
359
|
+
? getSummaryByCode(status)
|
|
360
|
+
: (message as string | {message: string}),
|
|
361
|
+
);
|
|
372
362
|
}
|
|
373
363
|
} else if (protocol === W3C) {
|
|
374
364
|
if (response.statusCode < 300) {
|
|
375
365
|
return resBody.value;
|
|
376
366
|
}
|
|
377
|
-
if (
|
|
378
|
-
const value = resBody.value
|
|
367
|
+
if (util.isPlainObject(resBody.value) && resBody.value.error) {
|
|
368
|
+
const value = resBody.value;
|
|
379
369
|
throw errorFromW3CJsonCode(
|
|
380
370
|
value.error as string,
|
|
381
371
|
(value.message as string) ?? '',
|
|
382
|
-
value.stacktrace as string | undefined
|
|
372
|
+
value.stacktrace as string | undefined,
|
|
383
373
|
);
|
|
384
374
|
}
|
|
385
375
|
} else if (response.statusCode === 200) {
|
|
@@ -387,9 +377,9 @@ export class JWProxy {
|
|
|
387
377
|
}
|
|
388
378
|
throw new errors.UnknownError(
|
|
389
379
|
`Did not know what to do with response code '${response.statusCode}' ` +
|
|
390
|
-
`and response body '${
|
|
380
|
+
`and response body '${util.truncateString(JSON.stringify(resBodyObj), {
|
|
391
381
|
length: 300,
|
|
392
|
-
})}'
|
|
382
|
+
})}'`,
|
|
393
383
|
);
|
|
394
384
|
}
|
|
395
385
|
|
|
@@ -414,26 +404,28 @@ export class JWProxy {
|
|
|
414
404
|
const [response, body] = await this.proxyCommand(
|
|
415
405
|
req.originalUrl,
|
|
416
406
|
req.method as HTTPMethod,
|
|
417
|
-
req.body
|
|
407
|
+
req.body,
|
|
418
408
|
);
|
|
419
409
|
statusCode = response.statusCode;
|
|
420
410
|
resBodyObj = body;
|
|
421
411
|
} catch (err: unknown) {
|
|
422
412
|
[statusCode, resBodyObj] = getResponseForW3CError(
|
|
423
|
-
isErrorType(err, errors.ProxyRequestError)
|
|
413
|
+
isErrorType(err, errors.ProxyRequestError)
|
|
414
|
+
? (err as InstanceType<typeof errors.ProxyRequestError>).getActualError()
|
|
415
|
+
: err,
|
|
424
416
|
);
|
|
425
417
|
}
|
|
426
418
|
res.setHeader('content-type', 'application/json; charset=utf-8');
|
|
427
|
-
if (!
|
|
419
|
+
if (!util.isPlainObject(resBodyObj)) {
|
|
428
420
|
const error = new errors.UnknownError(
|
|
429
421
|
`The downstream server response with the status code ${statusCode} is not a valid JSON object: ` +
|
|
430
|
-
|
|
422
|
+
util.truncateString(`${resBodyObj}`, {length: 300}),
|
|
431
423
|
);
|
|
432
424
|
[statusCode, resBodyObj] = getResponseForW3CError(error);
|
|
433
425
|
}
|
|
434
426
|
|
|
435
427
|
const resBody = resBodyObj as Record<string, unknown>;
|
|
436
|
-
if (
|
|
428
|
+
if (Object.hasOwn(resBody, 'sessionId')) {
|
|
437
429
|
const reqSessionId = this.getSessionIdFromUrl(req.originalUrl);
|
|
438
430
|
if (reqSessionId) {
|
|
439
431
|
this.log.info(`Replacing sessionId ${resBody.sessionId} with ${reqSessionId}`);
|
|
@@ -459,7 +451,10 @@ export class JWProxy {
|
|
|
459
451
|
try {
|
|
460
452
|
return await req.execute();
|
|
461
453
|
} finally {
|
|
462
|
-
|
|
454
|
+
const reqIndex = this._activeRequests.indexOf(req);
|
|
455
|
+
if (reqIndex >= 0) {
|
|
456
|
+
this._activeRequests.splice(reqIndex, 1);
|
|
457
|
+
}
|
|
463
458
|
}
|
|
464
459
|
}
|
|
465
460
|
|
|
@@ -467,8 +462,8 @@ export class JWProxy {
|
|
|
467
462
|
// eslint-disable-next-line n/no-deprecated-api -- we need relative URL support
|
|
468
463
|
const parsedUrl = nodeUrl.parse(url || '/');
|
|
469
464
|
if (
|
|
470
|
-
|
|
471
|
-
|
|
465
|
+
parsedUrl.href == null ||
|
|
466
|
+
parsedUrl.pathname == null ||
|
|
472
467
|
(parsedUrl.protocol && !['http:', 'https:'].includes(parsedUrl.protocol))
|
|
473
468
|
) {
|
|
474
469
|
throw new Error(`Did not know how to proxy the url '${url}'`);
|
|
@@ -477,7 +472,7 @@ export class JWProxy {
|
|
|
477
472
|
}
|
|
478
473
|
|
|
479
474
|
private _toNormalizedPathname(parsedUrl: nodeUrl.UrlWithStringQuery): string {
|
|
480
|
-
if (
|
|
475
|
+
if (typeof parsedUrl.pathname !== 'string') {
|
|
481
476
|
return '';
|
|
482
477
|
}
|
|
483
478
|
let pathname =
|
|
@@ -488,20 +483,24 @@ export class JWProxy {
|
|
|
488
483
|
// This is needed for the backward compatibility
|
|
489
484
|
// if drivers don't set reqBasePath properly
|
|
490
485
|
if (!this.reqBasePath) {
|
|
491
|
-
if (
|
|
486
|
+
if (
|
|
487
|
+
match &&
|
|
488
|
+
match.params &&
|
|
489
|
+
Array.isArray((match.params as Record<string, unknown>).prefix)
|
|
490
|
+
) {
|
|
492
491
|
pathname = pathname.replace(
|
|
493
492
|
`/${((match.params as Record<string, unknown>).prefix as string[]).join('/')}`,
|
|
494
|
-
''
|
|
493
|
+
'',
|
|
495
494
|
);
|
|
496
|
-
} else if (
|
|
495
|
+
} else if (pathname.startsWith('/wd/hub')) {
|
|
497
496
|
pathname = pathname.replace('/wd/hub', '');
|
|
498
497
|
}
|
|
499
498
|
}
|
|
500
499
|
let result = pathname;
|
|
501
500
|
if (match && match.params) {
|
|
502
501
|
const command = (match.params as Record<string, unknown>).command;
|
|
503
|
-
result =
|
|
502
|
+
result = Array.isArray(command) ? `/${(command as string[]).join('/')}` : '';
|
|
504
503
|
}
|
|
505
|
-
return
|
|
504
|
+
return result.replace(/\/+$/, '');
|
|
506
505
|
}
|
|
507
506
|
}
|