appium-remote-debugger 15.2.13 → 15.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/CHANGELOG.md +12 -0
- package/build/lib/protocol/index.d.ts +13 -8
- package/build/lib/protocol/index.d.ts.map +1 -1
- package/build/lib/protocol/index.js +17 -12
- package/build/lib/protocol/index.js.map +1 -1
- package/build/lib/rpc/index.d.ts +2 -3
- package/build/lib/rpc/index.d.ts.map +1 -1
- package/build/lib/rpc/index.js +2 -2
- package/build/lib/rpc/index.js.map +1 -1
- package/build/lib/rpc/remote-messages.d.ts +62 -41
- package/build/lib/rpc/remote-messages.d.ts.map +1 -1
- package/build/lib/rpc/remote-messages.js +56 -41
- package/build/lib/rpc/remote-messages.js.map +1 -1
- package/build/lib/rpc/rpc-client-real-device.d.ts +26 -8
- package/build/lib/rpc/rpc-client-real-device.d.ts.map +1 -1
- package/build/lib/rpc/rpc-client-real-device.js +21 -16
- package/build/lib/rpc/rpc-client-real-device.js.map +1 -1
- package/build/lib/rpc/rpc-client-simulator.d.ts +36 -28
- package/build/lib/rpc/rpc-client-simulator.d.ts.map +1 -1
- package/build/lib/rpc/rpc-client-simulator.js +39 -36
- package/build/lib/rpc/rpc-client-simulator.js.map +1 -1
- package/build/lib/rpc/rpc-client.d.ts +278 -189
- package/build/lib/rpc/rpc-client.d.ts.map +1 -1
- package/build/lib/rpc/rpc-client.js +222 -178
- package/build/lib/rpc/rpc-client.js.map +1 -1
- package/build/lib/rpc/rpc-message-handler.d.ts +32 -39
- package/build/lib/rpc/rpc-message-handler.d.ts.map +1 -1
- package/build/lib/rpc/rpc-message-handler.js +39 -53
- package/build/lib/rpc/rpc-message-handler.js.map +1 -1
- package/build/lib/types.d.ts +28 -0
- package/build/lib/types.d.ts.map +1 -1
- package/build/test/functional/safari-e2e-specs.js +5 -1
- package/build/test/functional/safari-e2e-specs.js.map +1 -1
- package/build/tsconfig.tsbuildinfo +1 -1
- package/lib/protocol/{index.js → index.ts} +29 -17
- package/lib/rpc/index.ts +2 -0
- package/lib/rpc/{remote-messages.js → remote-messages.ts} +73 -59
- package/lib/rpc/rpc-client-real-device.ts +68 -0
- package/lib/rpc/{rpc-client-simulator.js → rpc-client-simulator.ts} +54 -57
- package/lib/rpc/{rpc-client.js → rpc-client.ts} +368 -284
- package/lib/rpc/{rpc-message-handler.js → rpc-message-handler.ts} +73 -64
- package/lib/types.ts +31 -0
- package/package.json +1 -1
- package/lib/rpc/index.js +0 -4
- package/lib/rpc/rpc-client-real-device.js +0 -64
|
@@ -8,6 +8,19 @@ import { util, timing } from '@appium/support';
|
|
|
8
8
|
import { EventEmitter } from 'node:events';
|
|
9
9
|
import AsyncLock from 'async-lock';
|
|
10
10
|
import { convertJavascriptEvaluationResult } from '../utils';
|
|
11
|
+
import type { StringRecord } from '@appium/types';
|
|
12
|
+
import type {
|
|
13
|
+
AppIdKey,
|
|
14
|
+
PageIdKey,
|
|
15
|
+
TargetId,
|
|
16
|
+
TargetInfo,
|
|
17
|
+
ProvisionalTargetInfo,
|
|
18
|
+
RemoteCommandOpts,
|
|
19
|
+
RemoteCommand,
|
|
20
|
+
RawRemoteCommand,
|
|
21
|
+
RpcClientOptions,
|
|
22
|
+
RemoteCommandId,
|
|
23
|
+
} from '../types';
|
|
11
24
|
|
|
12
25
|
const DATA_LOG_LENGTH = {length: 200};
|
|
13
26
|
const MIN_WAIT_FOR_TARGET_TIMEOUT_MS = 30000;
|
|
@@ -24,76 +37,72 @@ export const NEW_APP_CONNECTED_ERROR = 'New application has connected';
|
|
|
24
37
|
export const EMPTY_PAGE_DICTIONARY_ERROR = 'Empty page dictionary received';
|
|
25
38
|
const ON_PAGE_INITIALIZED_EVENT = 'onPageInitialized';
|
|
26
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Details about a pending page target notification.
|
|
42
|
+
*/
|
|
43
|
+
interface PendingPageTargetDetails {
|
|
44
|
+
appIdKey: AppIdKey;
|
|
45
|
+
pageIdKey: PageIdKey;
|
|
46
|
+
pageReadinessDetector?: PageReadinessDetector;
|
|
47
|
+
}
|
|
27
48
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
connected;
|
|
37
|
-
|
|
38
|
-
/** @type {boolean} */
|
|
39
|
-
isSafari;
|
|
40
|
-
|
|
41
|
-
/** @type {string} */
|
|
42
|
-
connId;
|
|
43
|
-
|
|
44
|
-
/** @type {string} */
|
|
45
|
-
senderId;
|
|
46
|
-
|
|
47
|
-
/** @type {number} */
|
|
48
|
-
msgId;
|
|
49
|
-
|
|
50
|
-
/** @type {string|undefined} */
|
|
51
|
-
udid;
|
|
52
|
-
|
|
53
|
-
/** @type {boolean|undefined} */
|
|
54
|
-
logAllCommunication;
|
|
55
|
-
|
|
56
|
-
/** @type {boolean|undefined} */
|
|
57
|
-
logAllCommunicationHexDump;
|
|
58
|
-
|
|
59
|
-
/** @type {number|undefined} */
|
|
60
|
-
socketChunkSize;
|
|
61
|
-
|
|
62
|
-
/** @type {number|undefined} */
|
|
63
|
-
webInspectorMaxFrameLength;
|
|
64
|
-
|
|
65
|
-
/** @type {boolean|undefined} */
|
|
66
|
-
fullPageInitialization;
|
|
67
|
-
|
|
68
|
-
/** @type {string|undefined} */
|
|
69
|
-
bundleId;
|
|
70
|
-
|
|
71
|
-
/** @type {number | undefined} */
|
|
72
|
-
pageLoadTimeoutMs;
|
|
73
|
-
|
|
74
|
-
/** @type {string} */
|
|
75
|
-
platformVersion;
|
|
76
|
-
|
|
77
|
-
/** @type {string[]} */
|
|
78
|
-
_contexts;
|
|
79
|
-
|
|
80
|
-
/** @type {AppToTargetsMap} */
|
|
81
|
-
_targets;
|
|
49
|
+
/**
|
|
50
|
+
* Pages to targets mapping with optional provisional target info and lock.
|
|
51
|
+
*/
|
|
52
|
+
interface PagesToTargets {
|
|
53
|
+
[key: string]: TargetId | ProvisionalTargetInfo | AsyncLock | undefined;
|
|
54
|
+
provisional?: ProvisionalTargetInfo;
|
|
55
|
+
lock: AsyncLock;
|
|
56
|
+
}
|
|
82
57
|
|
|
83
|
-
|
|
84
|
-
|
|
58
|
+
/**
|
|
59
|
+
* Mapping of application IDs to their pages and targets.
|
|
60
|
+
*/
|
|
61
|
+
type AppToTargetsMap = Record<AppIdKey, PagesToTargets>;
|
|
85
62
|
|
|
86
|
-
|
|
87
|
-
|
|
63
|
+
/**
|
|
64
|
+
* Detector for determining when a page is ready.
|
|
65
|
+
*/
|
|
66
|
+
interface PageReadinessDetector {
|
|
67
|
+
timeoutMs: number;
|
|
68
|
+
readinessDetector: (readyState: string) => boolean;
|
|
69
|
+
}
|
|
88
70
|
|
|
89
|
-
|
|
90
|
-
|
|
71
|
+
/**
|
|
72
|
+
* Base class for RPC clients that communicate with the Web Inspector.
|
|
73
|
+
* Provides functionality for managing targets, sending commands, and handling
|
|
74
|
+
* page initialization. Subclasses must implement device-specific connection logic.
|
|
75
|
+
*/
|
|
76
|
+
export class RpcClient {
|
|
77
|
+
protected readonly messageHandler: RpcMessageHandler;
|
|
78
|
+
protected readonly remoteMessages: RemoteMessages;
|
|
79
|
+
protected connected: boolean;
|
|
80
|
+
protected readonly isSafari: boolean;
|
|
81
|
+
protected readonly connId: string;
|
|
82
|
+
protected readonly senderId: string;
|
|
83
|
+
protected msgId: number;
|
|
84
|
+
protected readonly udid?: string;
|
|
85
|
+
protected readonly logAllCommunication?: boolean;
|
|
86
|
+
protected readonly logAllCommunicationHexDump?: boolean;
|
|
87
|
+
protected readonly socketChunkSize?: number;
|
|
88
|
+
protected readonly webInspectorMaxFrameLength?: number;
|
|
89
|
+
protected readonly fullPageInitialization?: boolean;
|
|
90
|
+
protected readonly bundleId?: string;
|
|
91
|
+
protected readonly pageLoadTimeoutMs?: number;
|
|
92
|
+
protected readonly platformVersion: string;
|
|
93
|
+
protected readonly _contexts: number[];
|
|
94
|
+
protected readonly _targets: AppToTargetsMap;
|
|
95
|
+
protected readonly _targetSubscriptions: EventEmitter;
|
|
96
|
+
protected _pendingTargetNotification?: PendingPageTargetDetails;
|
|
97
|
+
protected readonly _targetCreationTimeoutMs: number;
|
|
98
|
+
protected readonly _provisionedPages: Set<PageIdKey>;
|
|
99
|
+
protected readonly _pageSelectionLock: AsyncLock;
|
|
100
|
+
protected readonly _pageSelectionMonitor: EventEmitter;
|
|
91
101
|
|
|
92
102
|
/**
|
|
93
|
-
*
|
|
94
|
-
* @param {RpcClientOptions} [opts={}]
|
|
103
|
+
* @param opts - Options for configuring the RPC client.
|
|
95
104
|
*/
|
|
96
|
-
constructor
|
|
105
|
+
constructor(opts: RpcClientOptions = {}) {
|
|
97
106
|
const {
|
|
98
107
|
bundleId,
|
|
99
108
|
platformVersion = '',
|
|
@@ -110,7 +119,7 @@ export class RpcClient {
|
|
|
110
119
|
|
|
111
120
|
this.isSafari = isSafari;
|
|
112
121
|
|
|
113
|
-
this.
|
|
122
|
+
this.connected = false;
|
|
114
123
|
this.connId = util.uuidV4();
|
|
115
124
|
this.senderId = util.uuidV4();
|
|
116
125
|
this.msgId = 0;
|
|
@@ -148,82 +157,147 @@ export class RpcClient {
|
|
|
148
157
|
}
|
|
149
158
|
|
|
150
159
|
/**
|
|
151
|
-
*
|
|
160
|
+
* Gets the list of execution context IDs.
|
|
161
|
+
*
|
|
162
|
+
* @returns Array of execution context IDs.
|
|
152
163
|
*/
|
|
153
|
-
get contexts
|
|
164
|
+
get contexts(): number[] {
|
|
154
165
|
return this._contexts;
|
|
155
166
|
}
|
|
156
167
|
|
|
157
168
|
/**
|
|
158
|
-
*
|
|
169
|
+
* Gets the mapping of applications to their pages and targets.
|
|
170
|
+
*
|
|
171
|
+
* @returns The targets mapping structure.
|
|
159
172
|
*/
|
|
160
|
-
get targets
|
|
173
|
+
get targets(): AppToTargetsMap {
|
|
161
174
|
return this._targets;
|
|
162
175
|
}
|
|
163
176
|
|
|
164
177
|
/**
|
|
165
|
-
*
|
|
178
|
+
* Gets whether the client is currently connected.
|
|
179
|
+
*
|
|
180
|
+
* @returns True if connected, false otherwise.
|
|
166
181
|
*/
|
|
167
|
-
get isConnected
|
|
182
|
+
get isConnected(): boolean {
|
|
168
183
|
return this.connected;
|
|
169
184
|
}
|
|
170
185
|
|
|
171
186
|
/**
|
|
172
|
-
*
|
|
187
|
+
* Sets the connection status.
|
|
188
|
+
*
|
|
189
|
+
* @param connected - The connection status to set.
|
|
173
190
|
*/
|
|
174
|
-
set isConnected
|
|
191
|
+
set isConnected(connected: boolean) {
|
|
175
192
|
this.connected = !!connected;
|
|
176
193
|
}
|
|
177
194
|
|
|
178
195
|
/**
|
|
179
|
-
*
|
|
196
|
+
* Gets the event emitter for target subscriptions.
|
|
197
|
+
*
|
|
198
|
+
* @returns The target subscriptions event emitter.
|
|
180
199
|
*/
|
|
181
|
-
get targetSubscriptions() {
|
|
200
|
+
get targetSubscriptions(): EventEmitter {
|
|
182
201
|
return this._targetSubscriptions;
|
|
183
202
|
}
|
|
184
203
|
|
|
185
204
|
/**
|
|
205
|
+
* Registers an event listener on the message handler.
|
|
206
|
+
*
|
|
207
|
+
* Supported events include:
|
|
208
|
+
*
|
|
209
|
+
* **RPC-level events:**
|
|
210
|
+
* - `_rpc_reportSetup:` - Emitted when the debugger setup is reported
|
|
211
|
+
* - `_rpc_reportConnectedApplicationList:` - Emitted when the list of connected applications is reported
|
|
212
|
+
* - `_rpc_forwardGetListing:` - Emitted when an application sends a page listing
|
|
213
|
+
* - `_rpc_applicationConnected:` - Emitted when a new application connects
|
|
214
|
+
* - `_rpc_applicationDisconnected:` - Emitted when an application disconnects
|
|
215
|
+
* - `_rpc_applicationUpdated:` - Emitted when an application is updated
|
|
216
|
+
* - `_rpc_reportConnectedDriverList:` - Emitted when the list of connected drivers is reported
|
|
217
|
+
* - `_rpc_reportCurrentState:` - Emitted when the current state is reported
|
|
218
|
+
*
|
|
219
|
+
* **Target events:**
|
|
220
|
+
* - `Target.targetCreated` - Emitted when a new target is created (args: error, appIdKey, targetInfo)
|
|
221
|
+
* - `Target.targetDestroyed` - Emitted when a target is destroyed (args: error, appIdKey, targetInfo)
|
|
222
|
+
* - `Target.didCommitProvisionalTarget` - Emitted when a provisional target commits (args: error, appIdKey, provisionalTargetInfo)
|
|
186
223
|
*
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
*
|
|
224
|
+
* **Page events:**
|
|
225
|
+
* - `Page.frameStoppedLoading` - Emitted when a frame stops loading
|
|
226
|
+
* - `Page.frameNavigated` - Emitted when a frame navigates
|
|
227
|
+
* - `Page.frameDetached` - Emitted when a frame is detached
|
|
228
|
+
* - `Page.loadEventFired` - Emitted when the page load event fires
|
|
229
|
+
*
|
|
230
|
+
* **Runtime events:**
|
|
231
|
+
* - `Runtime.executionContextCreated` - Emitted when an execution context is created (args: error, context)
|
|
232
|
+
*
|
|
233
|
+
* **Console events:**
|
|
234
|
+
* - `Console.messageAdded` - Emitted when a console message is added (args: error, message)
|
|
235
|
+
* - `Console.messageRepeatCountUpdated` - Emitted when a console message repeat count is updated
|
|
236
|
+
* - `ConsoleEvent` - Aggregate event for all Console.* events (args: error, params, methodName)
|
|
237
|
+
*
|
|
238
|
+
* **Network events:**
|
|
239
|
+
* - `NetworkEvent` - Aggregate event for all Network.* events (args: error, params, methodName)
|
|
240
|
+
*
|
|
241
|
+
* **Timeline events:**
|
|
242
|
+
* - `Timeline.eventRecorded` - Emitted when a timeline event is recorded (args: error, record)
|
|
243
|
+
*
|
|
244
|
+
* **Heap events:**
|
|
245
|
+
* - `Heap.garbageCollected` - Emitted when garbage collection occurs
|
|
246
|
+
*
|
|
247
|
+
* **Message ID events:**
|
|
248
|
+
* - Any numeric string (message ID) - Emitted for command responses (args: error, result)
|
|
249
|
+
*
|
|
250
|
+
* @param event - The event name to listen for.
|
|
251
|
+
* @param listener - The listener function to call when the event is emitted.
|
|
252
|
+
* The listener receives (error, ...args) where error may be null/undefined.
|
|
253
|
+
* @returns This instance for method chaining.
|
|
190
254
|
*/
|
|
191
|
-
on
|
|
192
|
-
// @ts-ignore messageHandler must be defined here
|
|
255
|
+
on(event: string, listener: (...args: any[]) => void): this {
|
|
193
256
|
this.messageHandler.on(event, listener);
|
|
194
257
|
return this;
|
|
195
258
|
}
|
|
196
259
|
|
|
197
260
|
/**
|
|
261
|
+
* Registers a one-time event listener on the message handler.
|
|
262
|
+
* The listener will be automatically removed after being called once.
|
|
263
|
+
*
|
|
264
|
+
* See {@link RpcClient.on} for a list of supported events.
|
|
198
265
|
*
|
|
199
|
-
* @param
|
|
200
|
-
* @param
|
|
201
|
-
*
|
|
266
|
+
* @param event - The event name to listen for.
|
|
267
|
+
* @param listener - The listener function to call when the event is emitted.
|
|
268
|
+
* The listener receives (error, ...args) where error may be null/undefined.
|
|
269
|
+
* @returns This instance for method chaining.
|
|
202
270
|
*/
|
|
203
|
-
once
|
|
204
|
-
// @ts-ignore messageHandler must be defined here
|
|
271
|
+
once(event: string, listener: (...args: any[]) => void): this {
|
|
205
272
|
this.messageHandler.once(event, listener);
|
|
206
273
|
return this;
|
|
207
274
|
}
|
|
208
275
|
|
|
209
276
|
/**
|
|
210
|
-
*
|
|
211
|
-
*
|
|
212
|
-
* @
|
|
277
|
+
* Removes an event listener from the message handler.
|
|
278
|
+
*
|
|
279
|
+
* See {@link RpcClient.on} for a list of supported events.
|
|
280
|
+
*
|
|
281
|
+
* @param event - The event name to stop listening for.
|
|
282
|
+
* @param listener - The listener function to remove.
|
|
283
|
+
* @returns This instance for method chaining.
|
|
213
284
|
*/
|
|
214
|
-
off
|
|
215
|
-
// @ts-ignore messageHandler must be defined here
|
|
285
|
+
off(event: string, listener: (...args: any[]) => void): this {
|
|
216
286
|
this.messageHandler.off(event, listener);
|
|
217
287
|
return this;
|
|
218
288
|
}
|
|
219
289
|
|
|
220
290
|
/**
|
|
291
|
+
* Waits for a target to be created for the specified app and page.
|
|
292
|
+
* If the target already exists, returns it immediately. Otherwise,
|
|
293
|
+
* waits up to the configured timeout for the target to be created.
|
|
221
294
|
*
|
|
222
|
-
* @param
|
|
223
|
-
* @param
|
|
224
|
-
* @returns
|
|
295
|
+
* @param appIdKey - The application identifier key.
|
|
296
|
+
* @param pageIdKey - The page identifier key.
|
|
297
|
+
* @returns A promise that resolves to the target ID if found, undefined otherwise.
|
|
298
|
+
* @throws Error if no target is found after the timeout.
|
|
225
299
|
*/
|
|
226
|
-
async waitForTarget
|
|
300
|
+
async waitForTarget(appIdKey: AppIdKey, pageIdKey: PageIdKey): Promise<TargetId | undefined> {
|
|
227
301
|
let target = this.getTarget(appIdKey, pageIdKey);
|
|
228
302
|
if (target) {
|
|
229
303
|
log.debug(
|
|
@@ -247,7 +321,7 @@ export class RpcClient {
|
|
|
247
321
|
intervalMs: WAIT_FOR_TARGET_INTERVAL_MS,
|
|
248
322
|
});
|
|
249
323
|
return target;
|
|
250
|
-
} catch (err) {
|
|
324
|
+
} catch (err: any) {
|
|
251
325
|
if (!err.message.includes('Condition unmet')) {
|
|
252
326
|
throw err;
|
|
253
327
|
}
|
|
@@ -258,17 +332,20 @@ export class RpcClient {
|
|
|
258
332
|
}
|
|
259
333
|
|
|
260
334
|
/**
|
|
335
|
+
* Sends a command to the remote debugger with automatic retry logic
|
|
336
|
+
* for target-related errors. Handles cases where targets are not yet
|
|
337
|
+
* available or not supported.
|
|
261
338
|
*
|
|
262
|
-
* @param
|
|
263
|
-
* @param
|
|
264
|
-
* @param
|
|
265
|
-
* @returns
|
|
339
|
+
* @param command - The command name to send.
|
|
340
|
+
* @param opts - Options for the command.
|
|
341
|
+
* @param waitForResponse - Whether to wait for a response. Defaults to true.
|
|
342
|
+
* @returns A promise that resolves to the command result or options.
|
|
266
343
|
*/
|
|
267
|
-
async send
|
|
344
|
+
async send(command: string, opts: RemoteCommandOpts, waitForResponse: boolean = true): Promise<any> {
|
|
268
345
|
const timer = new timing.Timer().start();
|
|
269
346
|
try {
|
|
270
347
|
return await this.sendToDevice(command, opts, waitForResponse);
|
|
271
|
-
} catch (err) {
|
|
348
|
+
} catch (err: any) {
|
|
272
349
|
const {
|
|
273
350
|
appIdKey,
|
|
274
351
|
pageIdKey
|
|
@@ -277,7 +354,7 @@ export class RpcClient {
|
|
|
277
354
|
if (messageLc.includes(NO_TARGET_SUPPORTED_ERROR)) {
|
|
278
355
|
return await this.sendToDevice(command, opts, waitForResponse);
|
|
279
356
|
} else if (appIdKey && NO_TARGET_PRESENT_YET_ERRORS.some((error) => messageLc.includes(error))) {
|
|
280
|
-
await this.waitForTarget(appIdKey,
|
|
357
|
+
await this.waitForTarget(appIdKey, pageIdKey as PageIdKey);
|
|
281
358
|
return await this.sendToDevice(command, opts, waitForResponse);
|
|
282
359
|
}
|
|
283
360
|
throw err;
|
|
@@ -287,16 +364,23 @@ export class RpcClient {
|
|
|
287
364
|
}
|
|
288
365
|
|
|
289
366
|
/**
|
|
367
|
+
* Sends a command directly to the device, handling message routing,
|
|
368
|
+
* response waiting, and error handling.
|
|
290
369
|
*
|
|
291
|
-
* @template
|
|
292
|
-
* @param
|
|
293
|
-
* @param
|
|
294
|
-
* @param
|
|
295
|
-
* @returns
|
|
370
|
+
* @template TWaitForResponse - Whether to wait for a response.
|
|
371
|
+
* @param command - The command name to send.
|
|
372
|
+
* @param opts - Options for the command.
|
|
373
|
+
* @param waitForResponse - Whether to wait for a response. Defaults to true.
|
|
374
|
+
* @returns A promise that resolves based on waitForResponse:
|
|
375
|
+
* - If true: resolves to the response value
|
|
376
|
+
* - If false: resolves to the full options object
|
|
296
377
|
*/
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
378
|
+
async sendToDevice<TWaitForResponse extends boolean = true>(
|
|
379
|
+
command: string,
|
|
380
|
+
opts: RemoteCommandOpts,
|
|
381
|
+
waitForResponse: TWaitForResponse = true as TWaitForResponse
|
|
382
|
+
): Promise<TWaitForResponse extends true ? any : RemoteCommandOpts> {
|
|
383
|
+
return await new B<any>(async (resolve, reject) => {
|
|
300
384
|
// promise to be resolved whenever remote debugger
|
|
301
385
|
// replies to our request
|
|
302
386
|
|
|
@@ -305,8 +389,7 @@ export class RpcClient {
|
|
|
305
389
|
// for target-base communication, everything is wrapped up
|
|
306
390
|
const wrapperMsgId = this.msgId++;
|
|
307
391
|
// acknowledge wrapper message
|
|
308
|
-
|
|
309
|
-
this.messageHandler.on(wrapperMsgId.toString(), function (err) {
|
|
392
|
+
this.messageHandler.on(wrapperMsgId.toString(), function (err: Error | null) {
|
|
310
393
|
if (err) {
|
|
311
394
|
reject(err);
|
|
312
395
|
}
|
|
@@ -317,38 +400,34 @@ export class RpcClient {
|
|
|
317
400
|
const targetId = opts.targetId ?? this.getTarget(appIdKey, pageIdKey);
|
|
318
401
|
|
|
319
402
|
// retrieve the correct command to send
|
|
320
|
-
|
|
321
|
-
const fullOpts = _.defaults({
|
|
403
|
+
const fullOpts: RemoteCommandOpts & RemoteCommandId = _.defaults({
|
|
322
404
|
connId: this.connId,
|
|
323
405
|
senderId: this.senderId,
|
|
324
406
|
targetId,
|
|
325
|
-
id: msgId,
|
|
407
|
+
id: msgId.toString(),
|
|
326
408
|
}, opts);
|
|
327
|
-
|
|
328
|
-
let cmd;
|
|
409
|
+
let cmd: RawRemoteCommand;
|
|
329
410
|
try {
|
|
330
|
-
// @ts-ignore remoteMessages must be defined
|
|
331
411
|
cmd = this.remoteMessages.getRemoteCommand(command, fullOpts);
|
|
332
|
-
} catch (err) {
|
|
412
|
+
} catch (err: any) {
|
|
333
413
|
log.error(err);
|
|
334
414
|
return reject(err);
|
|
335
415
|
}
|
|
336
416
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
__argument: _.omit(cmd.__argument, ['WIRSocketDataKey']),
|
|
417
|
+
const finalCommand: RemoteCommand = {
|
|
418
|
+
__argument: _.omit(cmd.__argument, ['WIRSocketDataKey']) as any,
|
|
340
419
|
__selector: cmd.__selector,
|
|
341
420
|
};
|
|
342
421
|
|
|
343
422
|
const hasSocketData = _.isPlainObject(cmd.__argument?.WIRSocketDataKey);
|
|
344
423
|
if (hasSocketData) {
|
|
345
424
|
// make sure the message being sent has all the information that is needed
|
|
346
|
-
|
|
347
|
-
if (_.
|
|
348
|
-
//
|
|
349
|
-
|
|
425
|
+
const socketData = cmd.__argument.WIRSocketDataKey as StringRecord;
|
|
426
|
+
if (!_.isInteger(socketData.id)) {
|
|
427
|
+
// ! This must be a number
|
|
428
|
+
socketData.id = wrapperMsgId;
|
|
350
429
|
}
|
|
351
|
-
finalCommand.__argument.WIRSocketDataKey = Buffer.from(JSON.stringify(
|
|
430
|
+
finalCommand.__argument.WIRSocketDataKey = Buffer.from(JSON.stringify(socketData));
|
|
352
431
|
}
|
|
353
432
|
|
|
354
433
|
let messageHandled = true;
|
|
@@ -356,8 +435,7 @@ export class RpcClient {
|
|
|
356
435
|
// the promise will be resolved as soon as the socket has been sent
|
|
357
436
|
messageHandled = false;
|
|
358
437
|
// do not log receipts
|
|
359
|
-
|
|
360
|
-
this.messageHandler.once(msgId.toString(), (err) => {
|
|
438
|
+
this.messageHandler.once(msgId.toString(), (err: Error | null) => {
|
|
361
439
|
if (err) {
|
|
362
440
|
// we are not waiting for this, and if it errors it is most likely
|
|
363
441
|
// a protocol change. Log and check during testing
|
|
@@ -366,27 +444,23 @@ export class RpcClient {
|
|
|
366
444
|
_.truncate(JSON.stringify(err), DATA_LOG_LENGTH)
|
|
367
445
|
);
|
|
368
446
|
// reject, though it is very rare that this will be triggered, since
|
|
369
|
-
// the promise is resolved
|
|
447
|
+
// the promise is resolved directly after send. On the off chance,
|
|
370
448
|
// though, it will alert of a protocol change.
|
|
371
449
|
reject(err);
|
|
372
450
|
}
|
|
373
451
|
});
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
// @ts-ignore messageHandler must be defined
|
|
377
|
-
this.messageHandler.prependOnceListener(cmd.__selector, (err, ...args) => {
|
|
452
|
+
} else if (this.messageHandler.listenerCount(cmd.__selector)) {
|
|
453
|
+
this.messageHandler.prependOnceListener(cmd.__selector, (err: Error | null, ...args: any[]) => {
|
|
378
454
|
if (err) {
|
|
379
455
|
return reject(err);
|
|
380
456
|
}
|
|
381
457
|
log.debug(`Received response from send (id: ${msgId}): '${_.truncate(JSON.stringify(args), DATA_LOG_LENGTH)}'`);
|
|
382
|
-
// @ts-ignore This is ok
|
|
383
458
|
resolve(args);
|
|
384
459
|
});
|
|
385
460
|
} else if (hasSocketData) {
|
|
386
|
-
|
|
387
|
-
this.messageHandler.once(msgId.toString(), (err, value) => {
|
|
461
|
+
this.messageHandler.once(msgId.toString(), (err: Error | null, value: any) => {
|
|
388
462
|
if (err) {
|
|
389
|
-
return reject(new Error(`Remote debugger error with code '${err.code}': ${err.message}`));
|
|
463
|
+
return reject(new Error(`Remote debugger error with code '${(err as any).code}': ${err.message}`));
|
|
390
464
|
}
|
|
391
465
|
log.debug(`Received data response from send (id: ${msgId}): '${_.truncate(JSON.stringify(value), DATA_LOG_LENGTH)}'`);
|
|
392
466
|
resolve(value);
|
|
@@ -407,8 +481,7 @@ export class RpcClient {
|
|
|
407
481
|
if (!messageHandled) {
|
|
408
482
|
// There are no handlers waiting for a response before resolving,
|
|
409
483
|
// and no errors sending the message over the socket, so resolve
|
|
410
|
-
|
|
411
|
-
resolve(fullOpts);
|
|
484
|
+
resolve(fullOpts as any);
|
|
412
485
|
}
|
|
413
486
|
} catch (err) {
|
|
414
487
|
return reject(err);
|
|
@@ -416,40 +489,53 @@ export class RpcClient {
|
|
|
416
489
|
});
|
|
417
490
|
}
|
|
418
491
|
|
|
419
|
-
|
|
492
|
+
/**
|
|
493
|
+
* Connects to the remote debugger. Must be implemented by subclasses.
|
|
494
|
+
*
|
|
495
|
+
* @throws Error indicating that subclasses must implement this method.
|
|
496
|
+
*/
|
|
497
|
+
async connect(): Promise<void> {
|
|
420
498
|
throw new Error(`Sub-classes need to implement a 'connect' function`);
|
|
421
499
|
}
|
|
422
500
|
|
|
423
|
-
|
|
424
|
-
|
|
501
|
+
/**
|
|
502
|
+
* Disconnects from the remote debugger and cleans up event listeners.
|
|
503
|
+
*/
|
|
504
|
+
async disconnect(): Promise<void> {
|
|
505
|
+
this.messageHandler.removeAllListeners();
|
|
425
506
|
}
|
|
426
507
|
|
|
427
508
|
/**
|
|
428
|
-
*
|
|
429
|
-
*
|
|
509
|
+
* Sends a message to the device. Must be implemented by subclasses.
|
|
510
|
+
*
|
|
511
|
+
* @param _command - The command to send.
|
|
512
|
+
* @throws Error indicating that subclasses must implement this method.
|
|
430
513
|
*/
|
|
431
514
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
432
|
-
async sendMessage
|
|
515
|
+
async sendMessage(_command: RemoteCommand): Promise<void> {
|
|
433
516
|
throw new Error(`Sub-classes need to implement a 'sendMessage' function`);
|
|
434
517
|
}
|
|
435
518
|
|
|
436
519
|
/**
|
|
437
|
-
*
|
|
438
|
-
*
|
|
520
|
+
* Receives data from the device. Must be implemented by subclasses.
|
|
521
|
+
*
|
|
522
|
+
* @param _data - The data received from the device.
|
|
523
|
+
* @throws Error indicating that subclasses must implement this method.
|
|
439
524
|
*/
|
|
440
525
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
441
|
-
async receive
|
|
526
|
+
async receive(_data: any): Promise<void> {
|
|
442
527
|
throw new Error(`Sub-classes need to implement a 'receive' function`);
|
|
443
528
|
}
|
|
444
529
|
|
|
445
530
|
/**
|
|
531
|
+
* Handles the creation of a new target for an application and page.
|
|
532
|
+
* Initializes the page and waits for readiness if configured.
|
|
446
533
|
*
|
|
447
|
-
* @param
|
|
448
|
-
* @param
|
|
449
|
-
* @param
|
|
450
|
-
* @returns {Promise<void>}
|
|
534
|
+
* @param err - Error if one occurred, undefined otherwise.
|
|
535
|
+
* @param app - The application identifier key.
|
|
536
|
+
* @param targetInfo - Information about the created target.
|
|
451
537
|
*/
|
|
452
|
-
async addTarget
|
|
538
|
+
async addTarget(err: Error | undefined, app: AppIdKey, targetInfo: TargetInfo): Promise<void> {
|
|
453
539
|
if (_.isNil(targetInfo?.targetId)) {
|
|
454
540
|
log.info(`Received 'Target.targetCreated' event for app '${app}' with no target. Skipping`);
|
|
455
541
|
return;
|
|
@@ -468,11 +554,11 @@ export class RpcClient {
|
|
|
468
554
|
if (!_.isPlainObject(this.targets[appIdKey])) {
|
|
469
555
|
this.targets[appIdKey] = {
|
|
470
556
|
lock: new AsyncLock({maxOccupationTime: this._targetCreationTimeoutMs}),
|
|
471
|
-
};
|
|
557
|
+
} as PagesToTargets;
|
|
472
558
|
}
|
|
473
559
|
const timer = new timing.Timer().start();
|
|
474
560
|
|
|
475
|
-
const adjustPageReadinessDetector = () => {
|
|
561
|
+
const adjustPageReadinessDetector = (): PageReadinessDetector | undefined => {
|
|
476
562
|
if (!pageReadinessDetector) {
|
|
477
563
|
return;
|
|
478
564
|
}
|
|
@@ -516,7 +602,7 @@ export class RpcClient {
|
|
|
516
602
|
);
|
|
517
603
|
}
|
|
518
604
|
});
|
|
519
|
-
} catch (e) {
|
|
605
|
+
} catch (e: any) {
|
|
520
606
|
log.warn(
|
|
521
607
|
`Cannot complete the initialization of the provisional target '${targetInfo.targetId}' ` +
|
|
522
608
|
`after ${timer.getDuration().asMilliSeconds}ms: ${e.message}`
|
|
@@ -527,8 +613,9 @@ export class RpcClient {
|
|
|
527
613
|
|
|
528
614
|
log.debug(`Target created for app '${appIdKey}' and page '${pageIdKey}': ${JSON.stringify(targetInfo)}`);
|
|
529
615
|
if (_.has(this.targets[appIdKey], pageIdKey)) {
|
|
616
|
+
const existingTarget = this.targets[appIdKey][pageIdKey] as TargetId;
|
|
530
617
|
log.debug(
|
|
531
|
-
`There is already a target for this app and page ('${
|
|
618
|
+
`There is already a target for this app and page ('${existingTarget}'). ` +
|
|
532
619
|
`This might cause problems`
|
|
533
620
|
);
|
|
534
621
|
}
|
|
@@ -540,7 +627,7 @@ export class RpcClient {
|
|
|
540
627
|
appIdKey,
|
|
541
628
|
pageIdKey,
|
|
542
629
|
});
|
|
543
|
-
} catch (e) {
|
|
630
|
+
} catch (e: any) {
|
|
544
631
|
log.debug(
|
|
545
632
|
`Cannot setup pause on start for app '${appIdKey}' and page '${pageIdKey}': ${e.message}`
|
|
546
633
|
);
|
|
@@ -567,7 +654,7 @@ export class RpcClient {
|
|
|
567
654
|
);
|
|
568
655
|
}
|
|
569
656
|
});
|
|
570
|
-
} catch (e) {
|
|
657
|
+
} catch (e: any) {
|
|
571
658
|
log.warn(e.message);
|
|
572
659
|
} finally {
|
|
573
660
|
// Target creation is happening after provisioning,
|
|
@@ -578,13 +665,13 @@ export class RpcClient {
|
|
|
578
665
|
}
|
|
579
666
|
|
|
580
667
|
/**
|
|
668
|
+
* Handles updates to provisional targets when they commit.
|
|
581
669
|
*
|
|
582
|
-
* @param
|
|
583
|
-
* @param
|
|
584
|
-
* @param
|
|
585
|
-
* @returns {Promise<void>}
|
|
670
|
+
* @param err - Error if one occurred, undefined otherwise.
|
|
671
|
+
* @param app - The application identifier key.
|
|
672
|
+
* @param targetInfo - Information about the provisional target update.
|
|
586
673
|
*/
|
|
587
|
-
async updateTarget
|
|
674
|
+
async updateTarget(err: Error | undefined, app: AppIdKey, targetInfo: ProvisionalTargetInfo): Promise<void> {
|
|
588
675
|
const {
|
|
589
676
|
oldTargetId,
|
|
590
677
|
newTargetId,
|
|
@@ -605,13 +692,13 @@ export class RpcClient {
|
|
|
605
692
|
}
|
|
606
693
|
|
|
607
694
|
/**
|
|
695
|
+
* Handles the destruction of a target, including cleanup of provisional targets.
|
|
608
696
|
*
|
|
609
|
-
* @param
|
|
610
|
-
* @param
|
|
611
|
-
* @param
|
|
612
|
-
* @returns {Promise<void>}
|
|
697
|
+
* @param err - Error if one occurred, undefined otherwise.
|
|
698
|
+
* @param app - The application identifier key.
|
|
699
|
+
* @param targetInfo - Information about the destroyed target.
|
|
613
700
|
*/
|
|
614
|
-
async removeTarget
|
|
701
|
+
async removeTarget(err: Error | undefined, app: AppIdKey, targetInfo: TargetInfo): Promise<void> {
|
|
615
702
|
if (_.isNil(targetInfo?.targetId)) {
|
|
616
703
|
log.debug(`Received 'Target.targetDestroyed' event with no target. Skipping`);
|
|
617
704
|
return;
|
|
@@ -656,24 +743,34 @@ export class RpcClient {
|
|
|
656
743
|
}
|
|
657
744
|
|
|
658
745
|
/**
|
|
659
|
-
*
|
|
660
|
-
*
|
|
661
|
-
* @
|
|
746
|
+
* Gets the target ID for a specific app and page combination.
|
|
747
|
+
*
|
|
748
|
+
* @param appIdKey - The application identifier key.
|
|
749
|
+
* @param pageIdKey - The page identifier key.
|
|
750
|
+
* @returns The target ID if found, undefined otherwise.
|
|
662
751
|
*/
|
|
663
|
-
getTarget
|
|
752
|
+
getTarget(appIdKey?: AppIdKey, pageIdKey?: PageIdKey): TargetId | undefined {
|
|
664
753
|
if (!appIdKey || !pageIdKey) {
|
|
665
754
|
return;
|
|
666
755
|
}
|
|
667
|
-
|
|
756
|
+
const target = this.targets[appIdKey]?.[pageIdKey];
|
|
757
|
+
return target && typeof target === 'string' ? target : undefined;
|
|
668
758
|
}
|
|
669
759
|
|
|
670
760
|
/**
|
|
671
|
-
*
|
|
672
|
-
*
|
|
673
|
-
*
|
|
674
|
-
*
|
|
761
|
+
* Selects a page within an application, setting up the Web Inspector session
|
|
762
|
+
* and waiting for the page to be initialized. Mimics the steps that Desktop
|
|
763
|
+
* Safari uses to initialize a Web Inspector session.
|
|
764
|
+
*
|
|
765
|
+
* @param appIdKey - The application identifier key.
|
|
766
|
+
* @param pageIdKey - The page identifier key.
|
|
767
|
+
* @param pageReadinessDetector - Optional detector for determining when the page is ready.
|
|
675
768
|
*/
|
|
676
|
-
async selectPage
|
|
769
|
+
async selectPage(
|
|
770
|
+
appIdKey: AppIdKey,
|
|
771
|
+
pageIdKey: PageIdKey,
|
|
772
|
+
pageReadinessDetector?: PageReadinessDetector
|
|
773
|
+
): Promise<void> {
|
|
677
774
|
await this._pageSelectionLock.acquire(toPageSelectionKey(appIdKey, pageIdKey), async () => {
|
|
678
775
|
this._pendingTargetNotification = {appIdKey, pageIdKey, pageReadinessDetector};
|
|
679
776
|
this._provisionedPages.clear();
|
|
@@ -710,10 +807,10 @@ export class RpcClient {
|
|
|
710
807
|
log.debug(
|
|
711
808
|
`Waiting up to ${msLeft}ms for page '${pageIdKey}' of app '${appIdKey}' to be selected`
|
|
712
809
|
);
|
|
713
|
-
await new Promise((resolve) => {
|
|
810
|
+
await new Promise<void>((resolve) => {
|
|
714
811
|
const onPageInitialized = (
|
|
715
|
-
|
|
716
|
-
|
|
812
|
+
notifiedAppIdKey: AppIdKey,
|
|
813
|
+
notifiedPageIdKey: PageIdKey
|
|
717
814
|
) => {
|
|
718
815
|
const timeoutHandler = setTimeout(() => {
|
|
719
816
|
this._pageSelectionMonitor.off(ON_PAGE_INITIALIZED_EVENT, onPageInitialized);
|
|
@@ -721,7 +818,7 @@ export class RpcClient {
|
|
|
721
818
|
`Page '${pageIdKey}' for app '${appIdKey}' has not been selected ` +
|
|
722
819
|
`within ${timer.getDuration().asMilliSeconds}ms. Continuing anyway`
|
|
723
820
|
);
|
|
724
|
-
resolve(
|
|
821
|
+
resolve();
|
|
725
822
|
}, msLeft);
|
|
726
823
|
|
|
727
824
|
if (notifiedAppIdKey === appIdKey && notifiedPageIdKey === pageIdKey) {
|
|
@@ -730,7 +827,7 @@ export class RpcClient {
|
|
|
730
827
|
log.debug(
|
|
731
828
|
`Selected the page ${pageIdKey}@${appIdKey} after ${timer.getDuration().asMilliSeconds}ms`
|
|
732
829
|
);
|
|
733
|
-
resolve(
|
|
830
|
+
resolve();
|
|
734
831
|
} else {
|
|
735
832
|
log.debug(
|
|
736
833
|
`Got notified that page ${notifiedPageIdKey}@${notifiedAppIdKey} is initialized, ` +
|
|
@@ -745,16 +842,22 @@ export class RpcClient {
|
|
|
745
842
|
}
|
|
746
843
|
|
|
747
844
|
/**
|
|
748
|
-
*
|
|
749
|
-
*
|
|
845
|
+
* Initializes a page by enabling various Web Inspector domains.
|
|
846
|
+
* Can perform either simple or full initialization based on configuration.
|
|
847
|
+
* Mimics the steps that Desktop Safari Develop tools uses to initialize
|
|
848
|
+
* a Web Inspector session.
|
|
750
849
|
*
|
|
751
|
-
* @param
|
|
752
|
-
* @param
|
|
753
|
-
* @param
|
|
754
|
-
* @returns
|
|
850
|
+
* @param appIdKey - The application identifier key.
|
|
851
|
+
* @param pageIdKey - The page identifier key.
|
|
852
|
+
* @param targetId - Optional target ID. If not provided, will be retrieved from the targets map.
|
|
853
|
+
* @returns A promise that resolves to true if initialization succeeded, false otherwise.
|
|
755
854
|
*/
|
|
756
|
-
async _initializePage
|
|
757
|
-
|
|
855
|
+
private async _initializePage(
|
|
856
|
+
appIdKey: AppIdKey,
|
|
857
|
+
pageIdKey: PageIdKey,
|
|
858
|
+
targetId?: TargetId
|
|
859
|
+
): Promise<boolean> {
|
|
860
|
+
const sendOpts: RemoteCommandOpts = {
|
|
758
861
|
appIdKey,
|
|
759
862
|
pageIdKey,
|
|
760
863
|
targetId,
|
|
@@ -778,7 +881,7 @@ export class RpcClient {
|
|
|
778
881
|
]) {
|
|
779
882
|
try {
|
|
780
883
|
await this.send(domain, sendOpts);
|
|
781
|
-
} catch (err) {
|
|
884
|
+
} catch (err: any) {
|
|
782
885
|
log.info(`Cannot enable domain '${domain}' during initialization: ${err.message}`);
|
|
783
886
|
if (MISSING_TARGET_ERROR_PATTERN.test(err.message)) {
|
|
784
887
|
return false;
|
|
@@ -793,7 +896,7 @@ export class RpcClient {
|
|
|
793
896
|
}
|
|
794
897
|
|
|
795
898
|
// The sequence of commands here is important
|
|
796
|
-
const domainsToOptsMap = {
|
|
899
|
+
const domainsToOptsMap: Record<string, RemoteCommandOpts> = {
|
|
797
900
|
'Inspector.enable': sendOpts,
|
|
798
901
|
'Page.enable': sendOpts,
|
|
799
902
|
'Runtime.enable': sendOpts,
|
|
@@ -855,13 +958,13 @@ export class RpcClient {
|
|
|
855
958
|
try {
|
|
856
959
|
const res = await this.send(domain, opts);
|
|
857
960
|
if (domain === 'Console.getLoggingChannels') {
|
|
858
|
-
for (const source of (res?.channels || []).map((
|
|
961
|
+
for (const source of (res?.channels || []).map((entry: { source: any }) => entry.source)) {
|
|
859
962
|
try {
|
|
860
963
|
await this.send('Console.setLoggingChannelLevel', Object.assign({
|
|
861
964
|
source,
|
|
862
965
|
level: 'verbose',
|
|
863
966
|
}, sendOpts));
|
|
864
|
-
} catch (err) {
|
|
967
|
+
} catch (err: any) {
|
|
865
968
|
log.info(`Cannot set logging channel level for '${source}': ${err.message}`);
|
|
866
969
|
if (MISSING_TARGET_ERROR_PATTERN.test(err.message)) {
|
|
867
970
|
return false;
|
|
@@ -869,7 +972,7 @@ export class RpcClient {
|
|
|
869
972
|
}
|
|
870
973
|
}
|
|
871
974
|
}
|
|
872
|
-
} catch (err) {
|
|
975
|
+
} catch (err: any) {
|
|
873
976
|
log.info(`Cannot enable domain '${domain}' during full initialization: ${err.message}`);
|
|
874
977
|
if (MISSING_TARGET_ERROR_PATTERN.test(err.message)) {
|
|
875
978
|
return false;
|
|
@@ -884,16 +987,20 @@ export class RpcClient {
|
|
|
884
987
|
}
|
|
885
988
|
|
|
886
989
|
/**
|
|
990
|
+
* Connects to a specific application and returns its page dictionary.
|
|
887
991
|
*
|
|
888
|
-
* @param
|
|
889
|
-
* @returns
|
|
992
|
+
* @param appIdKey - The application identifier key to connect to.
|
|
993
|
+
* @returns A promise that resolves to a tuple containing the connected app ID key
|
|
994
|
+
* and the page dictionary.
|
|
995
|
+
* @throws Error if a new application connects during the process or if the page
|
|
996
|
+
* dictionary is empty.
|
|
890
997
|
*/
|
|
891
|
-
async selectApp
|
|
892
|
-
return await new B((resolve, reject) => {
|
|
998
|
+
async selectApp(appIdKey: AppIdKey): Promise<[string, StringRecord]> {
|
|
999
|
+
return await new B<[string, StringRecord]>((resolve, reject) => {
|
|
893
1000
|
// local callback, temporarily added as callback to
|
|
894
1001
|
// `_rpc_applicationConnected:` remote debugger response
|
|
895
1002
|
// to handle the initial connection
|
|
896
|
-
const onAppChange = (err, dict) => {
|
|
1003
|
+
const onAppChange = (err: Error | null, dict: StringRecord) => {
|
|
897
1004
|
if (err) {
|
|
898
1005
|
return reject(err);
|
|
899
1006
|
}
|
|
@@ -910,7 +1017,7 @@ export class RpcClient {
|
|
|
910
1017
|
|
|
911
1018
|
reject(new Error(NEW_APP_CONNECTED_ERROR));
|
|
912
1019
|
};
|
|
913
|
-
this.messageHandler
|
|
1020
|
+
this.messageHandler.prependOnceListener('_rpc_applicationConnected:', onAppChange);
|
|
914
1021
|
|
|
915
1022
|
// do the actual connecting to the app
|
|
916
1023
|
(async () => {
|
|
@@ -923,22 +1030,23 @@ export class RpcClient {
|
|
|
923
1030
|
} else {
|
|
924
1031
|
resolve([connectedAppIdKey, pageDict]);
|
|
925
1032
|
}
|
|
926
|
-
} catch (err) {
|
|
1033
|
+
} catch (err: any) {
|
|
927
1034
|
log.warn(`Unable to connect to the app: ${err.message}`);
|
|
928
1035
|
reject(err);
|
|
929
1036
|
} finally {
|
|
930
|
-
this.messageHandler
|
|
1037
|
+
this.messageHandler.off('_rpc_applicationConnected:', onAppChange);
|
|
931
1038
|
}
|
|
932
1039
|
})();
|
|
933
1040
|
});
|
|
934
1041
|
}
|
|
935
1042
|
|
|
936
1043
|
/**
|
|
1044
|
+
* Handles execution context creation events by storing the context ID.
|
|
937
1045
|
*
|
|
938
|
-
* @param
|
|
939
|
-
* @param
|
|
1046
|
+
* @param err - Error if one occurred, undefined otherwise.
|
|
1047
|
+
* @param context - The execution context information.
|
|
940
1048
|
*/
|
|
941
|
-
onExecutionContextCreated
|
|
1049
|
+
onExecutionContextCreated(err: Error | undefined, context: { id: number }): void {
|
|
942
1050
|
// { id: 2, isPageContext: true, name: '', frameId: '0.1' }
|
|
943
1051
|
// right now we have no way to map contexts to apps/pages
|
|
944
1052
|
// so just store
|
|
@@ -946,31 +1054,33 @@ export class RpcClient {
|
|
|
946
1054
|
}
|
|
947
1055
|
|
|
948
1056
|
/**
|
|
949
|
-
*
|
|
1057
|
+
* Handles garbage collection events by logging them.
|
|
1058
|
+
* Garbage collection can affect operation timing.
|
|
950
1059
|
*/
|
|
951
|
-
onGarbageCollected
|
|
952
|
-
// just want to log that this is happening, as it can affect
|
|
1060
|
+
onGarbageCollected(): void {
|
|
1061
|
+
// just want to log that this is happening, as it can affect operation
|
|
953
1062
|
log.debug(`Web Inspector garbage collected`);
|
|
954
1063
|
}
|
|
955
1064
|
|
|
956
1065
|
/**
|
|
1066
|
+
* Handles script parsing events by logging script information.
|
|
957
1067
|
*
|
|
958
|
-
* @param
|
|
959
|
-
* @param
|
|
1068
|
+
* @param err - Error if one occurred, undefined otherwise.
|
|
1069
|
+
* @param scriptInfo - Information about the parsed script.
|
|
960
1070
|
*/
|
|
961
|
-
onScriptParsed
|
|
1071
|
+
onScriptParsed(err: Error | undefined, scriptInfo: StringRecord): void {
|
|
962
1072
|
// { scriptId: '13', url: '', startLine: 0, startColumn: 0, endLine: 82, endColumn: 3 }
|
|
963
1073
|
log.debug(`Script parsed: ${JSON.stringify(scriptInfo)}`);
|
|
964
1074
|
}
|
|
965
1075
|
|
|
966
1076
|
/**
|
|
1077
|
+
* Resumes a paused target.
|
|
967
1078
|
*
|
|
968
|
-
* @param
|
|
969
|
-
* @param
|
|
970
|
-
* @param
|
|
971
|
-
* @returns {Promise<void>}
|
|
1079
|
+
* @param appIdKey - The application identifier key.
|
|
1080
|
+
* @param pageIdKey - The page identifier key.
|
|
1081
|
+
* @param targetId - The target ID to resume.
|
|
972
1082
|
*/
|
|
973
|
-
async _resumeTarget
|
|
1083
|
+
private async _resumeTarget(appIdKey: AppIdKey, pageIdKey: PageIdKey, targetId: TargetId): Promise<void> {
|
|
974
1084
|
try {
|
|
975
1085
|
await this.send('Target.resume', {
|
|
976
1086
|
appIdKey,
|
|
@@ -978,20 +1088,26 @@ export class RpcClient {
|
|
|
978
1088
|
targetId,
|
|
979
1089
|
});
|
|
980
1090
|
log.debug(`Successfully resumed the target ${targetId}@${appIdKey}`);
|
|
981
|
-
} catch (e) {
|
|
1091
|
+
} catch (e: any) {
|
|
982
1092
|
log.warn(`Could not resume the target ${targetId}@${appIdKey}: ${e.message}`);
|
|
983
1093
|
}
|
|
984
1094
|
}
|
|
985
1095
|
|
|
986
1096
|
/**
|
|
1097
|
+
* Waits for a page to be ready by periodically checking the document readyState.
|
|
1098
|
+
* Uses the provided readiness detector to determine when the page is ready.
|
|
987
1099
|
*
|
|
988
|
-
* @param
|
|
989
|
-
* @param
|
|
990
|
-
* @param
|
|
991
|
-
* @param
|
|
992
|
-
* @returns {Promise<void>}
|
|
1100
|
+
* @param appIdKey - The application identifier key.
|
|
1101
|
+
* @param pageIdKey - The page identifier key.
|
|
1102
|
+
* @param targetId - The target ID.
|
|
1103
|
+
* @param pageReadinessDetector - The detector for determining page readiness.
|
|
993
1104
|
*/
|
|
994
|
-
async _waitForPageReadiness(
|
|
1105
|
+
private async _waitForPageReadiness(
|
|
1106
|
+
appIdKey: AppIdKey,
|
|
1107
|
+
pageIdKey: PageIdKey,
|
|
1108
|
+
targetId: TargetId,
|
|
1109
|
+
pageReadinessDetector?: PageReadinessDetector
|
|
1110
|
+
): Promise<void> {
|
|
995
1111
|
if (!pageReadinessDetector) {
|
|
996
1112
|
return;
|
|
997
1113
|
}
|
|
@@ -999,8 +1115,7 @@ export class RpcClient {
|
|
|
999
1115
|
log.debug(`Waiting up to ${pageReadinessDetector.timeoutMs}ms for page readiness`);
|
|
1000
1116
|
const timer = new timing.Timer().start();
|
|
1001
1117
|
while (pageReadinessDetector.timeoutMs - timer.getDuration().asMilliSeconds > 0) {
|
|
1002
|
-
|
|
1003
|
-
let readyState;
|
|
1118
|
+
let readyState: string;
|
|
1004
1119
|
try {
|
|
1005
1120
|
const commandTimeoutMs = Math.max(
|
|
1006
1121
|
100,
|
|
@@ -1014,7 +1129,7 @@ export class RpcClient {
|
|
|
1014
1129
|
targetId,
|
|
1015
1130
|
})).timeout(commandTimeoutMs);
|
|
1016
1131
|
readyState = convertJavascriptEvaluationResult(rawResult);
|
|
1017
|
-
} catch (e) {
|
|
1132
|
+
} catch (e: any) {
|
|
1018
1133
|
log.debug(`Cannot determine page readiness: ${e.message}`);
|
|
1019
1134
|
continue;
|
|
1020
1135
|
}
|
|
@@ -1034,12 +1149,14 @@ export class RpcClient {
|
|
|
1034
1149
|
}
|
|
1035
1150
|
|
|
1036
1151
|
/**
|
|
1152
|
+
* Waits for a page to be initialized by acquiring locks on both the page
|
|
1153
|
+
* target lock and the page selection lock.
|
|
1037
1154
|
*
|
|
1038
|
-
* @param
|
|
1039
|
-
* @param
|
|
1040
|
-
* @
|
|
1155
|
+
* @param appIdKey - The application identifier key.
|
|
1156
|
+
* @param pageIdKey - The page identifier key.
|
|
1157
|
+
* @throws Error if no targets are found for the application.
|
|
1041
1158
|
*/
|
|
1042
|
-
async waitForPage
|
|
1159
|
+
async waitForPage(appIdKey: AppIdKey, pageIdKey: PageIdKey): Promise<void> {
|
|
1043
1160
|
const appTargetsMap = this.targets[appIdKey];
|
|
1044
1161
|
if (!appTargetsMap) {
|
|
1045
1162
|
throw new Error(`No targets found for app '${appIdKey}'`);
|
|
@@ -1057,16 +1174,21 @@ export class RpcClient {
|
|
|
1057
1174
|
}
|
|
1058
1175
|
|
|
1059
1176
|
/**
|
|
1060
|
-
*
|
|
1177
|
+
* Gets the pending target details if there is a pending request for the given app.
|
|
1178
|
+
* Filters out non-page target types (e.g., 'frame').
|
|
1061
1179
|
*
|
|
1062
|
-
* @param
|
|
1063
|
-
* @param
|
|
1064
|
-
* @returns
|
|
1180
|
+
* @param appId - The application identifier key.
|
|
1181
|
+
* @param targetInfo - Information about the target.
|
|
1182
|
+
* @returns The pending page target details if there's a match, undefined otherwise.
|
|
1065
1183
|
*/
|
|
1066
|
-
_getPendingPageTargetDetails(
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1184
|
+
private _getPendingPageTargetDetails(
|
|
1185
|
+
appId: AppIdKey,
|
|
1186
|
+
targetInfo: TargetInfo
|
|
1187
|
+
): PendingPageTargetDetails | undefined {
|
|
1188
|
+
const logInfo = (message: string): undefined =>
|
|
1189
|
+
void log.info(
|
|
1190
|
+
`Skipping 'Target.targetCreated' event ${message} for app '${appId}': ${JSON.stringify(targetInfo)}`
|
|
1191
|
+
);
|
|
1070
1192
|
if (!this._pendingTargetNotification) {
|
|
1071
1193
|
return logInfo('with no pending request');
|
|
1072
1194
|
}
|
|
@@ -1085,50 +1207,12 @@ export class RpcClient {
|
|
|
1085
1207
|
}
|
|
1086
1208
|
|
|
1087
1209
|
/**
|
|
1210
|
+
* Creates a unique key for page selection based on app and page IDs.
|
|
1088
1211
|
*
|
|
1089
|
-
* @param
|
|
1090
|
-
* @param
|
|
1091
|
-
* @returns
|
|
1212
|
+
* @param appIdKey - The application identifier key.
|
|
1213
|
+
* @param pageIdKey - The page identifier key.
|
|
1214
|
+
* @returns A string key combining both identifiers.
|
|
1092
1215
|
*/
|
|
1093
|
-
function toPageSelectionKey(appIdKey, pageIdKey) {
|
|
1216
|
+
function toPageSelectionKey(appIdKey: AppIdKey, pageIdKey: PageIdKey): string {
|
|
1094
1217
|
return `${appIdKey}:${pageIdKey}`;
|
|
1095
1218
|
}
|
|
1096
|
-
|
|
1097
|
-
export default RpcClient;
|
|
1098
|
-
|
|
1099
|
-
/**
|
|
1100
|
-
* @typedef {Object} RpcClientOptions
|
|
1101
|
-
* @property {string} [bundleId]
|
|
1102
|
-
* @property {string} [platformVersion='']
|
|
1103
|
-
* @property {boolean} [isSafari=true]
|
|
1104
|
-
* @property {boolean} [logAllCommunication=false]
|
|
1105
|
-
* @property {boolean} [logAllCommunicationHexDump=false]
|
|
1106
|
-
* @property {number} [webInspectorMaxFrameLength]
|
|
1107
|
-
* @property {number} [socketChunkSize]
|
|
1108
|
-
* @property {boolean} [fullPageInitialization=false]
|
|
1109
|
-
* @property {number} [pageLoadTimeoutMs]
|
|
1110
|
-
* @property {string} [udid]
|
|
1111
|
-
* @property {number} [targetCreationTimeoutMs]
|
|
1112
|
-
*/
|
|
1113
|
-
|
|
1114
|
-
/**
|
|
1115
|
-
* @typedef {Object} PendingPageTargetDetails
|
|
1116
|
-
* @property {import('../types').AppIdKey} appIdKey
|
|
1117
|
-
* @property {import('../types').PageIdKey} pageIdKey
|
|
1118
|
-
* @property {PageReadinessDetector | undefined} pageReadinessDetector
|
|
1119
|
-
*/
|
|
1120
|
-
|
|
1121
|
-
/**
|
|
1122
|
-
* @typedef {{[key: import('../types').PageIdKey]: import('../types').TargetId}} PageDict
|
|
1123
|
-
*/
|
|
1124
|
-
|
|
1125
|
-
/**
|
|
1126
|
-
* @typedef {PageDict & {provisional?: import('../types').ProvisionalTargetInfo, lock: AsyncLock}} PagesToTargets
|
|
1127
|
-
* @typedef {{[key: import('../types').AppIdKey]: PagesToTargets}} AppToTargetsMap
|
|
1128
|
-
*/
|
|
1129
|
-
|
|
1130
|
-
/**
|
|
1131
|
-
* @typedef {Object} PageReadinessDetector
|
|
1132
|
-
* @property {number} timeoutMs
|
|
1133
|
-
* @property {(readyState: string) => boolean} readinessDetector
|
|
1134
|
-
*/
|