@appium/base-driver 10.5.1 → 10.6.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.map +1 -1
- package/build/lib/basedriver/capabilities.js +45 -45
- 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 +9 -13
- 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/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 +5 -9
- package/build/lib/basedriver/commands/timeout.js.map +1 -1
- package/build/lib/basedriver/core.js +12 -12
- 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 +13 -16
- 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 +27 -9
- 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 +28 -30
- 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 +155 -0
- package/build/lib/basedriver/ipc.js.map +1 -0
- package/build/lib/basedriver/validation.js +25 -28
- package/build/lib/basedriver/validation.js.map +1 -1
- package/build/lib/express/express-logging.d.ts.map +1 -1
- package/build/lib/express/express-logging.js +2 -3
- 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.map +1 -1
- package/build/lib/express/server.js +64 -54
- package/build/lib/express/server.js.map +1 -1
- package/build/lib/express/static.d.ts.map +1 -1
- package/build/lib/express/static.js +14 -7
- package/build/lib/express/static.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 +2 -6
- package/build/lib/helpers/levenshtein-match.js.map +1 -1
- package/build/lib/index.d.ts +1 -0
- package/build/lib/index.d.ts.map +1 -1
- package/build/lib/index.js +3 -14
- 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 +13 -17
- 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 +29 -26
- 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 +25 -29
- 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.map +1 -1
- package/build/lib/protocol/protocol.js +43 -48
- 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 +9 -12
- 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/utils.d.ts +16 -0
- package/build/lib/utils.d.ts.map +1 -0
- package/build/lib/utils.js +71 -0
- package/build/lib/utils.js.map +1 -0
- package/lib/basedriver/capabilities.ts +60 -55
- package/lib/basedriver/commands/bidi.ts +10 -10
- package/lib/basedriver/commands/event.ts +11 -10
- package/lib/basedriver/commands/execute.ts +3 -3
- package/lib/basedriver/commands/log.ts +3 -2
- package/lib/basedriver/commands/timeout.ts +5 -6
- package/lib/basedriver/core.ts +12 -12
- package/lib/basedriver/device-settings.ts +3 -4
- package/lib/basedriver/driver.ts +15 -13
- package/lib/basedriver/extension-core.ts +33 -7
- package/lib/basedriver/helpers.ts +28 -30
- package/lib/basedriver/ipc.ts +179 -0
- package/lib/basedriver/validation.ts +26 -26
- package/lib/express/express-logging.ts +3 -4
- package/lib/express/idempotency.ts +3 -3
- package/lib/express/middleware.ts +6 -8
- package/lib/express/server.ts +67 -61
- package/lib/express/static.ts +15 -7
- package/lib/express/websocket.ts +8 -10
- package/lib/helpers/capabilities.ts +18 -14
- package/lib/helpers/extension-command-name.ts +2 -2
- package/lib/helpers/levenshtein-match.ts +2 -5
- package/lib/index.js +1 -11
- package/lib/jsonwp-proxy/protocol-converter.ts +14 -15
- package/lib/jsonwp-proxy/proxy-request.ts +26 -26
- package/lib/jsonwp-proxy/proxy.ts +36 -37
- package/lib/protocol/errors.ts +29 -28
- package/lib/protocol/helpers.ts +9 -5
- package/lib/protocol/protocol.ts +44 -46
- package/lib/protocol/routes.ts +9 -9
- package/lib/protocol/validators.ts +1 -3
- package/lib/utils.ts +85 -0
- package/package.json +7 -9
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type {Constraint} from '@appium/types';
|
|
2
|
+
import {util} from '@appium/support';
|
|
2
3
|
import {log} from './logger';
|
|
3
|
-
import _ from 'lodash';
|
|
4
4
|
|
|
5
5
|
export class Validator {
|
|
6
6
|
private readonly _validators: Record<
|
|
@@ -8,27 +8,27 @@ export class Validator {
|
|
|
8
8
|
(value: any, options?: any, key?: string) => string | null
|
|
9
9
|
> = {
|
|
10
10
|
isString: (value: any, options?: any): string | null => {
|
|
11
|
-
if (
|
|
11
|
+
if (value === undefined || options == null) {
|
|
12
12
|
return null;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
if (
|
|
15
|
+
if (typeof value === 'string') {
|
|
16
16
|
return options ? null : 'must not be of type string';
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
return options ? 'must be of type string' : null;
|
|
20
20
|
},
|
|
21
21
|
isNumber: (value: any, options?: any): string | null => {
|
|
22
|
-
if (
|
|
22
|
+
if (value === undefined || options == null) {
|
|
23
23
|
return null;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
if (
|
|
26
|
+
if (typeof value === 'number') {
|
|
27
27
|
return options ? null : 'must not be of type number';
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
// allow a string value
|
|
31
|
-
if (options &&
|
|
31
|
+
if (options && typeof value === 'string' && !isNaN(Number(value))) {
|
|
32
32
|
log.warn('Number capability passed in as string. Functionality may be compromised.');
|
|
33
33
|
return null;
|
|
34
34
|
}
|
|
@@ -36,45 +36,45 @@ export class Validator {
|
|
|
36
36
|
return options ? 'must be of type number' : null;
|
|
37
37
|
},
|
|
38
38
|
isBoolean: (value: any, options?: any): string | null => {
|
|
39
|
-
if (
|
|
39
|
+
if (value === undefined || options == null) {
|
|
40
40
|
return null;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
if (
|
|
43
|
+
if (typeof value === 'boolean') {
|
|
44
44
|
return options ? null : 'must not be of type boolean';
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
// allow a string value
|
|
48
|
-
if (options &&
|
|
48
|
+
if (options && typeof value === 'string' && ['true', 'false', ''].includes(value)) {
|
|
49
49
|
return null;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
return options ? 'must be of type boolean' : null;
|
|
53
53
|
},
|
|
54
54
|
isObject: (value: any, options?: any): string | null => {
|
|
55
|
-
if (
|
|
55
|
+
if (value === undefined || options == null) {
|
|
56
56
|
return null;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
if (
|
|
59
|
+
if (util.isPlainObject(value)) {
|
|
60
60
|
return options ? null : 'must not be a plain object';
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
return options ? 'must be a plain object' : null;
|
|
64
64
|
},
|
|
65
65
|
isArray: (value: any, options?: any): string | null => {
|
|
66
|
-
if (
|
|
66
|
+
if (value === undefined || options == null) {
|
|
67
67
|
return null;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
if (
|
|
70
|
+
if (Array.isArray(value)) {
|
|
71
71
|
return options ? null : 'must not be of type array';
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
return options ? 'must be of type array' : null;
|
|
75
75
|
},
|
|
76
76
|
deprecated: (value: any, options?: any, key?: string): string | null => {
|
|
77
|
-
if (
|
|
77
|
+
if (value !== undefined && options) {
|
|
78
78
|
log.warn(
|
|
79
79
|
`The '${key}' capability has been deprecated and must not be used anymore. ` +
|
|
80
80
|
`Please check the driver documentation for possible alternatives.`
|
|
@@ -83,32 +83,32 @@ export class Validator {
|
|
|
83
83
|
return null;
|
|
84
84
|
},
|
|
85
85
|
inclusion: (value: any, options?: any): string | null => {
|
|
86
|
-
if (
|
|
86
|
+
if (value === undefined || !options) {
|
|
87
87
|
return null;
|
|
88
88
|
}
|
|
89
|
-
const optionsArr =
|
|
89
|
+
const optionsArr = Array.isArray(options) ? options : [options];
|
|
90
90
|
if (optionsArr.some((opt) => opt === value)) {
|
|
91
91
|
return null;
|
|
92
92
|
}
|
|
93
93
|
return `must be contained by ${JSON.stringify(optionsArr)}`;
|
|
94
94
|
},
|
|
95
95
|
inclusionCaseInsensitive: (value: any, options?: any): string | null => {
|
|
96
|
-
if (
|
|
96
|
+
if (value === undefined || !options) {
|
|
97
97
|
return null;
|
|
98
98
|
}
|
|
99
|
-
const optionsArr =
|
|
100
|
-
if (optionsArr.some((opt) =>
|
|
99
|
+
const optionsArr = Array.isArray(options) ? options : [options];
|
|
100
|
+
if (optionsArr.some((opt) => String(opt).toLowerCase() === String(value).toLowerCase())) {
|
|
101
101
|
return null;
|
|
102
102
|
}
|
|
103
103
|
return `must be contained by ${JSON.stringify(optionsArr)}`;
|
|
104
104
|
},
|
|
105
105
|
presence: (value: any, options?: any): string | null => {
|
|
106
|
-
if (
|
|
106
|
+
if (value === undefined && options) {
|
|
107
107
|
return 'is required to be present';
|
|
108
108
|
}
|
|
109
109
|
if (
|
|
110
110
|
!options?.allowEmpty &&
|
|
111
|
-
((
|
|
111
|
+
((value !== undefined && util.isEmpty(value)) || (typeof value === 'string' && !value.trim()))
|
|
112
112
|
) {
|
|
113
113
|
return 'must not be empty or blank';
|
|
114
114
|
}
|
|
@@ -118,15 +118,15 @@ export class Validator {
|
|
|
118
118
|
|
|
119
119
|
validate(values: Record<string, any>, constraints: Record<string, Constraint>): Record<string, string[]> | null {
|
|
120
120
|
const result: Record<string, string[]> = {};
|
|
121
|
-
for (const [key, constraint] of
|
|
121
|
+
for (const [key, constraint] of Object.entries(constraints)) {
|
|
122
122
|
const value = values[key];
|
|
123
|
-
for (const [validatorName, options] of
|
|
123
|
+
for (const [validatorName, options] of Object.entries(constraint)) {
|
|
124
124
|
if (!(validatorName in this._validators)) {
|
|
125
125
|
continue;
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
const validationError = this._validators[validatorName](value, options, key);
|
|
129
|
-
if (
|
|
128
|
+
const validationError = this._validators[validatorName as keyof Constraint](value, options, key);
|
|
129
|
+
if (validationError == null) {
|
|
130
130
|
continue;
|
|
131
131
|
}
|
|
132
132
|
|
|
@@ -137,7 +137,7 @@ export class Validator {
|
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
139
|
}
|
|
140
|
-
return
|
|
140
|
+
return util.isEmpty(result) ? null : result;
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {util, logger} from '@appium/support';
|
|
2
2
|
import '@colors/colors';
|
|
3
3
|
import morgan from 'morgan';
|
|
4
4
|
import type {Request, RequestHandler, Response} from 'express';
|
|
5
5
|
import {log} from './logger';
|
|
6
6
|
import {MAX_LOG_BODY_LENGTH} from '../constants';
|
|
7
|
-
import {logger} from '@appium/support';
|
|
8
7
|
|
|
9
8
|
/**
|
|
10
9
|
* Morgan middleware that logs when the HTTP response finishes.
|
|
@@ -32,8 +31,8 @@ function startLogFormatterHandler(tokens: unknown, req: Request, res: Response):
|
|
|
32
31
|
let reqBody = '';
|
|
33
32
|
if (req.body) {
|
|
34
33
|
try {
|
|
35
|
-
reqBody =
|
|
36
|
-
|
|
34
|
+
reqBody = util.truncateString(
|
|
35
|
+
typeof req.body === 'string' ? req.body : JSON.stringify(req.body),
|
|
37
36
|
{length: MAX_LOG_BODY_LENGTH}
|
|
38
37
|
);
|
|
39
38
|
} catch {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {log} from './logger';
|
|
2
2
|
import {LRUCache} from 'lru-cache';
|
|
3
|
-
import
|
|
3
|
+
import {util} from '@appium/support';
|
|
4
4
|
import {EventEmitter} from 'node:events';
|
|
5
5
|
import type {Request, Response, NextFunction} from 'express';
|
|
6
6
|
|
|
@@ -34,12 +34,12 @@ export async function handleIdempotency(
|
|
|
34
34
|
next: NextFunction
|
|
35
35
|
): Promise<void> {
|
|
36
36
|
const keyOrArr = req.headers[IDEMPOTENCY_KEY_HEADER];
|
|
37
|
-
if (
|
|
37
|
+
if (util.isEmpty(keyOrArr) || !keyOrArr) {
|
|
38
38
|
next();
|
|
39
39
|
return;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
const key =
|
|
42
|
+
const key = Array.isArray(keyOrArr) ? keyOrArr[0] : keyOrArr;
|
|
43
43
|
|
|
44
44
|
log.updateAsyncContext({idempotencyKey: key});
|
|
45
45
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {util} from '@appium/support';
|
|
2
2
|
import type {NextFunction, Request, RequestHandler, Response} from 'express';
|
|
3
3
|
import type {IncomingMessage} from 'node:http';
|
|
4
4
|
import type {Duplex} from 'node:stream';
|
|
@@ -6,7 +6,6 @@ import {log} from './logger';
|
|
|
6
6
|
import {errors} from '../protocol';
|
|
7
7
|
export {handleIdempotency} from './idempotency';
|
|
8
8
|
import {match} from 'path-to-regexp';
|
|
9
|
-
import {util} from '@appium/support';
|
|
10
9
|
import {calcSignature} from '../helpers/session';
|
|
11
10
|
import {getResponseForW3CError} from '../protocol/errors';
|
|
12
11
|
import type {StringRecord, WSServer} from '@appium/types';
|
|
@@ -46,7 +45,7 @@ export function allowCrossDomainAsyncExecute(basePath: string): RequestHandler {
|
|
|
46
45
|
next: NextFunction
|
|
47
46
|
): void {
|
|
48
47
|
const receiveAsyncResponseRegExp = new RegExp(
|
|
49
|
-
`${
|
|
48
|
+
`${util.escapeRegExp(basePath)}/session/[a-f0-9-]+/(appium/)?receive_async_response`
|
|
50
49
|
);
|
|
51
50
|
if (!receiveAsyncResponseRegExp.test(req.url)) {
|
|
52
51
|
next();
|
|
@@ -73,7 +72,7 @@ export function handleLogContext(req: Request, _res: Response, next: NextFunctio
|
|
|
73
72
|
{
|
|
74
73
|
requestId,
|
|
75
74
|
...sessionInfo,
|
|
76
|
-
isSensitive: ['true', '1', 'yes'].includes(
|
|
75
|
+
isSensitive: ['true', '1', 'yes'].includes(String(isSensitiveHeaderValue ?? '').toLowerCase()),
|
|
77
76
|
},
|
|
78
77
|
true
|
|
79
78
|
);
|
|
@@ -110,7 +109,7 @@ export function tryHandleWebSocketUpgrade(
|
|
|
110
109
|
head: Buffer,
|
|
111
110
|
webSocketsMapping: StringRecord<WSServer>
|
|
112
111
|
): boolean {
|
|
113
|
-
if (
|
|
112
|
+
if (String(req.headers?.upgrade ?? '').toLowerCase() !== 'websocket') {
|
|
114
113
|
return false;
|
|
115
114
|
}
|
|
116
115
|
|
|
@@ -120,7 +119,7 @@ export function tryHandleWebSocketUpgrade(
|
|
|
120
119
|
} catch {
|
|
121
120
|
currentPathname = req.url ?? '';
|
|
122
121
|
}
|
|
123
|
-
for (const [pathname, wsServer] of
|
|
122
|
+
for (const [pathname, wsServer] of Object.entries(webSocketsMapping)) {
|
|
124
123
|
if (match(pathname)(currentPathname)) {
|
|
125
124
|
wsServer.handleUpgrade(req, socket, head, (ws) => {
|
|
126
125
|
wsServer.emit('connection', ws, req);
|
|
@@ -182,6 +181,5 @@ export function catch404Handler(req: Request, res: Response): void {
|
|
|
182
181
|
|
|
183
182
|
function fetchHeaderValue(req: Request, name: string): string | undefined {
|
|
184
183
|
const value = req.headers[name];
|
|
185
|
-
return
|
|
184
|
+
return Array.isArray(value) ? value[0] : (value as string | undefined);
|
|
186
185
|
}
|
|
187
|
-
|
package/lib/express/server.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import _ from 'lodash';
|
|
2
1
|
import path from 'node:path';
|
|
3
2
|
import express from 'express';
|
|
4
3
|
import type {Express, RequestHandler} from 'express';
|
|
@@ -34,7 +33,6 @@ import {
|
|
|
34
33
|
removeAllWebSocketHandlers,
|
|
35
34
|
getWebSocketHandlers,
|
|
36
35
|
} from './websocket';
|
|
37
|
-
import B from 'bluebird';
|
|
38
36
|
import {DEFAULT_BASE_PATH} from '../constants';
|
|
39
37
|
import {fs, timing} from '@appium/support';
|
|
40
38
|
import type {
|
|
@@ -123,51 +121,51 @@ export async function server(opts: ServerOpts): Promise<AppiumServer> {
|
|
|
123
121
|
const app = express();
|
|
124
122
|
const httpServer = await createServer(app, cliArgs);
|
|
125
123
|
|
|
126
|
-
return await new
|
|
127
|
-
// we
|
|
128
|
-
//
|
|
129
|
-
//
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// once all configurations and updaters have been applied, make sure to set up a catchall
|
|
155
|
-
// handler so that anything unknown 404s. But do this after everything else since we don't
|
|
156
|
-
// want to block extensions' ability to add routes if they want.
|
|
157
|
-
app.all('/*all', catch404Handler);
|
|
158
|
-
|
|
159
|
-
await startServer({
|
|
160
|
-
httpServer,
|
|
161
|
-
hostname,
|
|
162
|
-
port,
|
|
163
|
-
keepAliveTimeout,
|
|
164
|
-
requestTimeout,
|
|
165
|
-
});
|
|
124
|
+
return await new Promise<AppiumServer>((resolve, reject) => {
|
|
125
|
+
// we use a promise here because some elements of server start failure only happen in
|
|
126
|
+
// httpServer listeners. The async IIFE runs setup serially (e.g. plugin updates) while
|
|
127
|
+
// configureHttp can still reject via the listener registered below.
|
|
128
|
+
void (async () => {
|
|
129
|
+
try {
|
|
130
|
+
const appiumServer = configureHttp({
|
|
131
|
+
httpServer,
|
|
132
|
+
reject,
|
|
133
|
+
keepAliveTimeout,
|
|
134
|
+
gracefulShutdownTimeout: cliArgs.shutdownTimeout,
|
|
135
|
+
});
|
|
136
|
+
const useLegacyUpgradeHandler = !hasShouldUpgradeCallback(httpServer);
|
|
137
|
+
configureServer({
|
|
138
|
+
app,
|
|
139
|
+
addRoutes: routeConfiguringFunction,
|
|
140
|
+
allowCors,
|
|
141
|
+
basePath,
|
|
142
|
+
extraMethodMap,
|
|
143
|
+
webSocketsMapping: appiumServer.webSocketsMapping,
|
|
144
|
+
useLegacyUpgradeHandler,
|
|
145
|
+
});
|
|
146
|
+
// allow extensions to update the app and http server objects
|
|
147
|
+
for (const updater of serverUpdaters) {
|
|
148
|
+
await updater(app, appiumServer, cliArgs);
|
|
149
|
+
}
|
|
166
150
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
151
|
+
// once all configurations and updaters have been applied, make sure to set up a catchall
|
|
152
|
+
// handler so that anything unknown 404s. But do this after everything else since we don't
|
|
153
|
+
// want to block extensions' ability to add routes if they want.
|
|
154
|
+
app.all('/*all', catch404Handler);
|
|
155
|
+
|
|
156
|
+
await startServer({
|
|
157
|
+
httpServer,
|
|
158
|
+
hostname,
|
|
159
|
+
port,
|
|
160
|
+
keepAliveTimeout,
|
|
161
|
+
requestTimeout,
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
resolve(appiumServer);
|
|
165
|
+
} catch (err) {
|
|
166
|
+
reject(err);
|
|
167
|
+
}
|
|
168
|
+
})();
|
|
171
169
|
});
|
|
172
170
|
}
|
|
173
171
|
|
|
@@ -237,7 +235,7 @@ export function configureServer({
|
|
|
237
235
|
* @returns Normalized base path
|
|
238
236
|
*/
|
|
239
237
|
export function normalizeBasePath(basePath: string): string {
|
|
240
|
-
if (
|
|
238
|
+
if (typeof basePath !== 'string') {
|
|
241
239
|
throw new Error(`Invalid path prefix ${basePath}`);
|
|
242
240
|
}
|
|
243
241
|
|
|
@@ -268,19 +266,18 @@ async function createServer(
|
|
|
268
266
|
}
|
|
269
267
|
|
|
270
268
|
const certKey = [sslCertificatePath, sslKeyPath];
|
|
271
|
-
const
|
|
272
|
-
|
|
273
|
-
['certificate',
|
|
274
|
-
|
|
275
|
-
)
|
|
276
|
-
for (const [exists, desc, p] of zipped) {
|
|
269
|
+
const [certExists, keyExists] = await Promise.all(certKey.map((p) => fs.exists(p)));
|
|
270
|
+
for (const [exists, desc, p] of [
|
|
271
|
+
[certExists, 'certificate', sslCertificatePath],
|
|
272
|
+
[keyExists, 'key', sslKeyPath],
|
|
273
|
+
]) {
|
|
277
274
|
if (!exists) {
|
|
278
275
|
throw new Error(
|
|
279
276
|
`The provided SSL ${desc} at '${p}' does not exist or is not accessible`
|
|
280
277
|
);
|
|
281
278
|
}
|
|
282
279
|
}
|
|
283
|
-
const [cert, key] = await
|
|
280
|
+
const [cert, key] = await Promise.all(
|
|
284
281
|
certKey.map((p) => fs.readFile(p, 'utf8'))
|
|
285
282
|
) as [string, string];
|
|
286
283
|
log.debug('Enabling TLS/SPDY on the server using the provided certificate');
|
|
@@ -333,7 +330,7 @@ function configureHttp({
|
|
|
333
330
|
if (hasShouldUpgradeCallback(httpServer)) {
|
|
334
331
|
// shouldUpgradeCallback only returns a boolean to indicate if the upgrade should proceed
|
|
335
332
|
(appiumServer as unknown as {shouldUpgradeCallback?: (req: http.IncomingMessage) => boolean}).shouldUpgradeCallback = (req) =>
|
|
336
|
-
|
|
333
|
+
String(req.headers?.upgrade ?? '').toLowerCase() === 'websocket';
|
|
337
334
|
appiumServer.on('upgrade', (req, socket, head) => {
|
|
338
335
|
if (!tryHandleWebSocketUpgrade(req, socket, head, appiumServer.webSocketsMapping)) {
|
|
339
336
|
socket.destroy();
|
|
@@ -345,7 +342,7 @@ function configureHttp({
|
|
|
345
342
|
// all connections are closed and the `close` event is emitted
|
|
346
343
|
const originalClose = appiumServer.close.bind(appiumServer);
|
|
347
344
|
appiumServer.close = async () =>
|
|
348
|
-
await new
|
|
345
|
+
await new Promise<void>((_resolve, _reject) => {
|
|
349
346
|
log.info('Closing Appium HTTP server');
|
|
350
347
|
const timer = new timing.Timer().start();
|
|
351
348
|
const onTimeout = setTimeout(() => {
|
|
@@ -405,12 +402,21 @@ async function startServer({
|
|
|
405
402
|
requestTimeout,
|
|
406
403
|
}: StartServerOpts): Promise<void> {
|
|
407
404
|
// If the hostname is omitted, the server will accept connections on any IP address
|
|
408
|
-
const
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
405
|
+
const startPromise = new Promise<void>((resolve, reject) => {
|
|
406
|
+
const onError = (err: Error) => {
|
|
407
|
+
httpServer.removeListener('listening', onListening);
|
|
408
|
+
reject(err);
|
|
409
|
+
};
|
|
410
|
+
const onListening = () => {
|
|
411
|
+
httpServer.removeListener('error', onError);
|
|
412
|
+
resolve();
|
|
413
|
+
};
|
|
414
|
+
httpServer.once('error', onError);
|
|
415
|
+
httpServer.once('listening', onListening);
|
|
416
|
+
httpServer.listen(port, hostname);
|
|
417
|
+
});
|
|
412
418
|
httpServer.keepAliveTimeout = keepAliveTimeout;
|
|
413
|
-
if (
|
|
419
|
+
if (Number.isInteger(requestTimeout)) {
|
|
414
420
|
httpServer.requestTimeout = Number(requestTimeout);
|
|
415
421
|
}
|
|
416
422
|
// headers timeout must be greater than keepAliveTimeout
|
package/lib/express/static.ts
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import {log} from './logger';
|
|
3
|
-
import _ from 'lodash';
|
|
4
3
|
import {fs} from '@appium/support';
|
|
5
|
-
import
|
|
4
|
+
import {sleep} from 'asyncbox';
|
|
6
5
|
import type {Request, Response} from 'express';
|
|
6
|
+
import {compileLodashTemplate} from '../utils';
|
|
7
7
|
|
|
8
|
-
export const STATIC_DIR =
|
|
9
|
-
? path.resolve(__dirname, '..', '..', 'static')
|
|
10
|
-
: path.resolve(__dirname, '..', '..', '..', 'static');
|
|
8
|
+
export const STATIC_DIR = resolveStaticDir();
|
|
11
9
|
|
|
12
10
|
type TemplateParams = Record<string, unknown>;
|
|
13
11
|
|
|
@@ -56,7 +54,7 @@ async function guineaPigTemplate(
|
|
|
56
54
|
log.debug(`Sending guinea pig response with params: ${JSON.stringify(params)}`);
|
|
57
55
|
if (delay) {
|
|
58
56
|
log.debug(`Waiting ${delay}ms before responding`);
|
|
59
|
-
await
|
|
57
|
+
await sleep(delay);
|
|
60
58
|
}
|
|
61
59
|
res.set('content-type', 'text/html');
|
|
62
60
|
res.cookie('guineacookie1', 'i am a cookie value', {path: '/'});
|
|
@@ -73,5 +71,15 @@ async function getTemplate(
|
|
|
73
71
|
templateName: string
|
|
74
72
|
): Promise<(params: TemplateParams) => string> {
|
|
75
73
|
const content = await fs.readFile(path.resolve(STATIC_DIR, 'test', templateName));
|
|
76
|
-
return
|
|
74
|
+
return compileLodashTemplate(content.toString());
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function resolveStaticDir(): string {
|
|
78
|
+
const fromDir = __dirname;
|
|
79
|
+
const parts = path.resolve(fromDir).split(path.sep);
|
|
80
|
+
const baseDriverIndex = parts.indexOf('base-driver');
|
|
81
|
+
if (baseDriverIndex < 0) {
|
|
82
|
+
throw new Error(`Could not find the module root folder in the path: ${fromDir}`);
|
|
83
|
+
}
|
|
84
|
+
return path.join(parts.slice(0, baseDriverIndex + 1).join(path.sep), 'static');
|
|
77
85
|
}
|
package/lib/express/websocket.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import B from 'bluebird';
|
|
1
|
+
import {util} from '@appium/support';
|
|
3
2
|
import type {AppiumServer, WSServer} from '@appium/types';
|
|
4
3
|
|
|
5
4
|
export const DEFAULT_WS_PATHNAME_PREFIX = '/ws';
|
|
@@ -24,9 +23,9 @@ export async function getWebSocketHandlers(
|
|
|
24
23
|
this: AppiumServer,
|
|
25
24
|
keysFilter: string | null = null
|
|
26
25
|
): Promise<Record<string, WSServer>> {
|
|
27
|
-
return
|
|
26
|
+
return Object.entries(this.webSocketsMapping).reduce<Record<string, WSServer>>(
|
|
28
27
|
(acc, [pathname, wsServer]) => {
|
|
29
|
-
if (
|
|
28
|
+
if (typeof keysFilter !== 'string' || pathname.includes(keysFilter)) {
|
|
30
29
|
acc[pathname] = wsServer;
|
|
31
30
|
}
|
|
32
31
|
return acc;
|
|
@@ -67,15 +66,14 @@ export async function removeWebSocketHandler(
|
|
|
67
66
|
* @see AppiumServerExtension.removeAllWebSocketHandlers
|
|
68
67
|
*/
|
|
69
68
|
export async function removeAllWebSocketHandlers(this: AppiumServer): Promise<boolean> {
|
|
70
|
-
if (
|
|
69
|
+
if (util.isEmpty(this.webSocketsMapping)) {
|
|
71
70
|
return false;
|
|
72
71
|
}
|
|
73
72
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
this.removeWebSocketHandler(pathname)
|
|
78
|
-
)
|
|
73
|
+
const results = await Promise.all(
|
|
74
|
+
Object.keys(this.webSocketsMapping).map((pathname) =>
|
|
75
|
+
this.removeWebSocketHandler(pathname)
|
|
79
76
|
)
|
|
80
77
|
);
|
|
78
|
+
return results.some(Boolean);
|
|
81
79
|
}
|
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
import type {Constraints, W3CCapabilities, Capabilities, AppiumLogger} from '@appium/types';
|
|
2
|
-
import
|
|
2
|
+
import {util} from '@appium/support';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Determine whether the given argument is valid
|
|
6
6
|
* W3C capabilities instance.
|
|
7
7
|
*/
|
|
8
8
|
export function isW3cCaps(caps: unknown): caps is W3CCapabilities<Constraints> {
|
|
9
|
-
if (!
|
|
9
|
+
if (!util.isPlainObject(caps)) {
|
|
10
10
|
return false;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
const c = caps as Record<string, unknown>;
|
|
14
14
|
const isFirstMatchValid = () =>
|
|
15
|
-
|
|
16
|
-
!
|
|
17
|
-
|
|
18
|
-
const isAlwaysMatchValid = () =>
|
|
19
|
-
if (
|
|
15
|
+
Array.isArray(c.firstMatch) &&
|
|
16
|
+
!util.isEmpty(c.firstMatch) &&
|
|
17
|
+
c.firstMatch.every((item) => util.isPlainObject(item));
|
|
18
|
+
const isAlwaysMatchValid = () => util.isPlainObject(c.alwaysMatch);
|
|
19
|
+
if (Object.hasOwn(c, 'firstMatch') && Object.hasOwn(c, 'alwaysMatch')) {
|
|
20
20
|
return isFirstMatchValid() && isAlwaysMatchValid();
|
|
21
21
|
}
|
|
22
|
-
if (
|
|
22
|
+
if (Object.hasOwn(c, 'firstMatch')) {
|
|
23
23
|
return isFirstMatchValid();
|
|
24
24
|
}
|
|
25
|
-
if (
|
|
25
|
+
if (Object.hasOwn(c, 'alwaysMatch')) {
|
|
26
26
|
return isAlwaysMatchValid();
|
|
27
27
|
}
|
|
28
28
|
return false;
|
|
@@ -36,16 +36,18 @@ export function fixCaps<C extends Constraints>(
|
|
|
36
36
|
desiredCapConstraints: C,
|
|
37
37
|
log: AppiumLogger
|
|
38
38
|
): Capabilities<C> {
|
|
39
|
-
const caps =
|
|
39
|
+
const caps = {...oldCaps} as Record<string, unknown>;
|
|
40
40
|
|
|
41
41
|
const logCastWarning = (prefix: string) => log.warn(`${prefix}. This may cause unexpected behavior`);
|
|
42
42
|
|
|
43
43
|
// boolean capabilities can be passed in as strings 'false' and 'true'
|
|
44
44
|
// which we want to translate into boolean values
|
|
45
|
-
const booleanCaps =
|
|
45
|
+
const booleanCaps = Object.keys(desiredCapConstraints).filter(
|
|
46
|
+
(key) => desiredCapConstraints[key as keyof C]?.isBoolean === true
|
|
47
|
+
);
|
|
46
48
|
for (const cap of booleanCaps) {
|
|
47
49
|
const value = oldCaps[cap];
|
|
48
|
-
if (
|
|
50
|
+
if (typeof value !== 'string') {
|
|
49
51
|
continue;
|
|
50
52
|
}
|
|
51
53
|
|
|
@@ -59,10 +61,12 @@ export function fixCaps<C extends Constraints>(
|
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
// int capabilities are often sent in as strings by frameworks
|
|
62
|
-
const intCaps =
|
|
64
|
+
const intCaps = Object.keys(desiredCapConstraints).filter(
|
|
65
|
+
(key) => desiredCapConstraints[key as keyof C]?.isNumber === true
|
|
66
|
+
);
|
|
63
67
|
for (const cap of intCaps) {
|
|
64
68
|
const value = oldCaps[cap];
|
|
65
|
-
if (
|
|
69
|
+
if (typeof value !== 'string') {
|
|
66
70
|
continue;
|
|
67
71
|
}
|
|
68
72
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {util} from '@appium/support';
|
|
2
2
|
import type {Constraints, Driver, DriverClass} from '@appium/types';
|
|
3
3
|
import type {BaseDriver} from '../basedriver/driver';
|
|
4
4
|
|
|
@@ -16,7 +16,7 @@ export function resolveExecuteExtensionName<C extends Constraints>(
|
|
|
16
16
|
const Driver = this.constructor as DriverClass<Driver<C>>;
|
|
17
17
|
const methodMap = Driver.executeMethodMap;
|
|
18
18
|
|
|
19
|
-
if (methodMap &&
|
|
19
|
+
if (methodMap && util.isPlainObject(methodMap) && commandName in methodMap) {
|
|
20
20
|
const command = methodMap[commandName]?.command;
|
|
21
21
|
if (typeof command === 'string') {
|
|
22
22
|
return command;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type {StringRecord} from '@appium/types';
|
|
2
2
|
import {distance} from 'fastest-levenshtein';
|
|
3
|
-
import _ from 'lodash';
|
|
4
3
|
|
|
5
4
|
/**
|
|
6
5
|
* Inclusive maximum Levenshtein edit distance for offering a "did you mean" hint.
|
|
@@ -39,10 +38,8 @@ export function rankLevenshteinCandidates(
|
|
|
39
38
|
}
|
|
40
39
|
return acc;
|
|
41
40
|
}, {});
|
|
42
|
-
const sortedDistanceKeys =
|
|
43
|
-
const sorted =
|
|
44
|
-
sortedDistanceKeys.map((k) => (matchesMap[k] ?? []).sort()),
|
|
45
|
-
);
|
|
41
|
+
const sortedDistanceKeys = Object.keys(matchesMap).sort((a, b) => parseInt(a, 10) - parseInt(b, 10));
|
|
42
|
+
const sorted = sortedDistanceKeys.flatMap((k) => (matchesMap[k] ?? []).sort());
|
|
46
43
|
|
|
47
44
|
const best = sorted[0];
|
|
48
45
|
const firstDistanceKey = sortedDistanceKeys[0];
|