appium-remote-debugger 15.7.3 → 15.8.1
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/atoms.d.ts.map +1 -1
- package/build/lib/atoms.js +27 -28
- package/build/lib/atoms.js.map +1 -1
- package/build/lib/index.d.ts.map +1 -1
- package/build/lib/index.js +7 -0
- package/build/lib/index.js.map +1 -1
- package/build/lib/mixins/connect.d.ts.map +1 -1
- package/build/lib/mixins/connect.js +21 -25
- package/build/lib/mixins/connect.js.map +1 -1
- package/build/lib/mixins/execute.d.ts.map +1 -1
- package/build/lib/mixins/execute.js +2 -8
- package/build/lib/mixins/execute.js.map +1 -1
- package/build/lib/mixins/message-handlers.d.ts.map +1 -1
- package/build/lib/mixins/message-handlers.js +8 -12
- package/build/lib/mixins/message-handlers.js.map +1 -1
- package/build/lib/mixins/misc.d.ts.map +1 -1
- package/build/lib/mixins/misc.js +5 -39
- package/build/lib/mixins/misc.js.map +1 -1
- package/build/lib/mixins/navigate.d.ts.map +1 -1
- package/build/lib/mixins/navigate.js +20 -55
- package/build/lib/mixins/navigate.js.map +1 -1
- package/build/lib/mixins/property-accessors.d.ts +24 -0
- package/build/lib/mixins/property-accessors.d.ts.map +1 -1
- package/build/lib/mixins/property-accessors.js +24 -0
- package/build/lib/mixins/property-accessors.js.map +1 -1
- package/build/lib/remote-debugger.d.ts +38 -38
- package/build/lib/remote-debugger.d.ts.map +1 -1
- package/build/lib/remote-debugger.js +64 -69
- package/build/lib/remote-debugger.js.map +1 -1
- package/build/lib/rpc/remote-messages.d.ts.map +1 -1
- package/build/lib/rpc/remote-messages.js +7 -8
- package/build/lib/rpc/remote-messages.js.map +1 -1
- package/build/lib/rpc/rpc-client-real-device-shim.d.ts.map +1 -1
- package/build/lib/rpc/rpc-client-real-device-shim.js +3 -6
- package/build/lib/rpc/rpc-client-real-device-shim.js.map +1 -1
- package/build/lib/rpc/rpc-client-simulator.d.ts.map +1 -1
- package/build/lib/rpc/rpc-client-simulator.js +3 -5
- package/build/lib/rpc/rpc-client-simulator.js.map +1 -1
- package/build/lib/rpc/rpc-client.d.ts +27 -27
- package/build/lib/rpc/rpc-client.d.ts.map +1 -1
- package/build/lib/rpc/rpc-client.js +226 -224
- package/build/lib/rpc/rpc-client.js.map +1 -1
- package/build/lib/rpc/rpc-message-handler.js +7 -10
- package/build/lib/rpc/rpc-message-handler.js.map +1 -1
- package/build/lib/types.d.ts +19 -19
- package/build/lib/types.d.ts.map +1 -1
- package/build/lib/utils.d.ts +77 -8
- package/build/lib/utils.d.ts.map +1 -1
- package/build/lib/utils.js +197 -29
- package/build/lib/utils.js.map +1 -1
- package/lib/atoms.ts +31 -32
- package/lib/index.ts +7 -0
- package/lib/mixins/connect.ts +22 -23
- package/lib/mixins/execute.ts +3 -5
- package/lib/mixins/message-handlers.ts +9 -10
- package/lib/mixins/misc.ts +8 -7
- package/lib/mixins/navigate.ts +58 -63
- package/lib/mixins/property-accessors.ts +24 -0
- package/lib/remote-debugger.ts +74 -76
- package/lib/rpc/remote-messages.ts +10 -5
- package/lib/rpc/rpc-client-real-device-shim.ts +3 -3
- package/lib/rpc/rpc-client-simulator.ts +3 -5
- package/lib/rpc/rpc-client.ts +259 -247
- package/lib/rpc/rpc-message-handler.ts +7 -7
- package/lib/types.ts +24 -24
- package/lib/utils.ts +205 -29
- package/package.json +3 -7
- package/scripts/common.mjs +42 -37
- package/scripts/web_inspector_proxy.mjs +3 -5
|
@@ -7,14 +7,12 @@ exports.RpcClient = exports.EMPTY_PAGE_DICTIONARY_ERROR = exports.NEW_APP_CONNEC
|
|
|
7
7
|
const remote_messages_1 = require("./remote-messages");
|
|
8
8
|
const asyncbox_1 = require("asyncbox");
|
|
9
9
|
const logger_1 = require("../logger");
|
|
10
|
-
const lodash_1 = __importDefault(require("lodash"));
|
|
11
|
-
const bluebird_1 = __importDefault(require("bluebird"));
|
|
12
10
|
const rpc_message_handler_1 = __importDefault(require("./rpc-message-handler"));
|
|
13
11
|
const support_1 = require("@appium/support");
|
|
14
12
|
const node_events_1 = require("node:events");
|
|
15
13
|
const async_lock_1 = __importDefault(require("async-lock"));
|
|
16
14
|
const utils_1 = require("../utils");
|
|
17
|
-
const DATA_LOG_LENGTH =
|
|
15
|
+
const DATA_LOG_LENGTH = 200;
|
|
18
16
|
const MIN_WAIT_FOR_TARGET_TIMEOUT_MS = 30000;
|
|
19
17
|
const DEFAULT_TARGET_CREATION_TIMEOUT_MS = 3 * 60 * 1000; // 3 minutes
|
|
20
18
|
const WAIT_FOR_TARGET_INTERVAL_MS = 100;
|
|
@@ -117,14 +115,6 @@ class RpcClient {
|
|
|
117
115
|
get isConnected() {
|
|
118
116
|
return this.connected;
|
|
119
117
|
}
|
|
120
|
-
/**
|
|
121
|
-
* Sets the connection status.
|
|
122
|
-
*
|
|
123
|
-
* @param connected - The connection status to set.
|
|
124
|
-
*/
|
|
125
|
-
set isConnected(connected) {
|
|
126
|
-
this.connected = !!connected;
|
|
127
|
-
}
|
|
128
118
|
/**
|
|
129
119
|
* Gets the event emitter for target subscriptions.
|
|
130
120
|
*
|
|
@@ -133,6 +123,14 @@ class RpcClient {
|
|
|
133
123
|
get targetSubscriptions() {
|
|
134
124
|
return this._targetSubscriptions;
|
|
135
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* Sets the connection status.
|
|
128
|
+
*
|
|
129
|
+
* @param connected - The connection status to set.
|
|
130
|
+
*/
|
|
131
|
+
set isConnected(connected) {
|
|
132
|
+
this.connected = !!connected;
|
|
133
|
+
}
|
|
136
134
|
/**
|
|
137
135
|
* Registers an event listener on the message handler.
|
|
138
136
|
*
|
|
@@ -239,7 +237,7 @@ class RpcClient {
|
|
|
239
237
|
try {
|
|
240
238
|
await (0, asyncbox_1.waitForCondition)(() => {
|
|
241
239
|
target = this.getTarget(appIdKey, pageIdKey);
|
|
242
|
-
return !
|
|
240
|
+
return !(0, utils_1.isEmpty)(target);
|
|
243
241
|
}, {
|
|
244
242
|
waitMs,
|
|
245
243
|
intervalMs: WAIT_FOR_TARGET_INTERVAL_MS,
|
|
@@ -298,108 +296,112 @@ class RpcClient {
|
|
|
298
296
|
* - If false: resolves to the full options object
|
|
299
297
|
*/
|
|
300
298
|
async sendToDevice(command, opts, waitForResponse = true) {
|
|
301
|
-
return await new
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
reject(err);
|
|
312
|
-
}
|
|
313
|
-
});
|
|
314
|
-
const appIdKey = opts.appIdKey;
|
|
315
|
-
const pageIdKey = opts.pageIdKey;
|
|
316
|
-
const targetId = opts.targetId ?? this.getTarget(appIdKey, pageIdKey);
|
|
317
|
-
// retrieve the correct command to send
|
|
318
|
-
const fullOpts = lodash_1.default.defaults({
|
|
319
|
-
connId: this.connId,
|
|
320
|
-
senderId: this.senderId,
|
|
321
|
-
targetId,
|
|
322
|
-
id: msgId.toString(),
|
|
323
|
-
}, opts);
|
|
324
|
-
let cmd;
|
|
325
|
-
try {
|
|
326
|
-
cmd = this.remoteMessages.getRemoteCommand(command, fullOpts);
|
|
327
|
-
}
|
|
328
|
-
catch (err) {
|
|
329
|
-
logger_1.log.error(err);
|
|
330
|
-
return reject(err);
|
|
331
|
-
}
|
|
332
|
-
const finalCommand = {
|
|
333
|
-
__argument: lodash_1.default.omit(cmd.__argument, ['WIRSocketDataKey']),
|
|
334
|
-
__selector: cmd.__selector,
|
|
335
|
-
};
|
|
336
|
-
const hasSocketData = lodash_1.default.isPlainObject(cmd.__argument?.WIRSocketDataKey);
|
|
337
|
-
if (hasSocketData) {
|
|
338
|
-
// make sure the message being sent has all the information that is needed
|
|
339
|
-
const socketData = cmd.__argument.WIRSocketDataKey;
|
|
340
|
-
if (!lodash_1.default.isInteger(socketData.id)) {
|
|
341
|
-
// ! This must be a number
|
|
342
|
-
socketData.id = wrapperMsgId;
|
|
343
|
-
}
|
|
344
|
-
finalCommand.__argument.WIRSocketDataKey = Buffer.from(JSON.stringify(socketData));
|
|
345
|
-
}
|
|
346
|
-
let messageHandled = true;
|
|
347
|
-
if (!waitForResponse) {
|
|
348
|
-
// the promise will be resolved as soon as the socket has been sent
|
|
349
|
-
messageHandled = false;
|
|
350
|
-
// do not log receipts
|
|
351
|
-
this.messageHandler.once(msgId.toString(), (err) => {
|
|
299
|
+
return await new Promise((resolve, reject) => {
|
|
300
|
+
void (async () => {
|
|
301
|
+
// promise to be resolved whenever remote debugger
|
|
302
|
+
// replies to our request
|
|
303
|
+
// keep track of the messages coming and going using a simple sequential id
|
|
304
|
+
const msgId = this.msgId++;
|
|
305
|
+
// for target-base communication, everything is wrapped up
|
|
306
|
+
const wrapperMsgId = this.msgId++;
|
|
307
|
+
// acknowledge wrapper message
|
|
308
|
+
this.messageHandler.on(wrapperMsgId.toString(), function (err) {
|
|
352
309
|
if (err) {
|
|
353
|
-
// we are not waiting for this, and if it errors it is most likely
|
|
354
|
-
// a protocol change. Log and check during testing
|
|
355
|
-
logger_1.log.error(`Received error from send that is not being waited for (id: ${msgId}): ` +
|
|
356
|
-
lodash_1.default.truncate(JSON.stringify(err), DATA_LOG_LENGTH));
|
|
357
|
-
// reject, though it is very rare that this will be triggered, since
|
|
358
|
-
// the promise is resolved directly after send. On the off chance,
|
|
359
|
-
// though, it will alert of a protocol change.
|
|
360
310
|
reject(err);
|
|
361
311
|
}
|
|
362
312
|
});
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
this.
|
|
366
|
-
|
|
367
|
-
|
|
313
|
+
const appIdKey = opts.appIdKey;
|
|
314
|
+
const pageIdKey = opts.pageIdKey;
|
|
315
|
+
const targetId = opts.targetId ?? this.getTarget(appIdKey, pageIdKey);
|
|
316
|
+
// retrieve the correct command to send
|
|
317
|
+
const fullOpts = (0, utils_1.defaults)({
|
|
318
|
+
connId: this.connId,
|
|
319
|
+
senderId: this.senderId,
|
|
320
|
+
targetId,
|
|
321
|
+
id: msgId.toString(),
|
|
322
|
+
}, opts);
|
|
323
|
+
let cmd;
|
|
324
|
+
try {
|
|
325
|
+
cmd = this.remoteMessages.getRemoteCommand(command, fullOpts);
|
|
326
|
+
}
|
|
327
|
+
catch (err) {
|
|
328
|
+
logger_1.log.error(err);
|
|
329
|
+
return reject(err);
|
|
330
|
+
}
|
|
331
|
+
const finalCommandArgs = { ...cmd.__argument };
|
|
332
|
+
delete finalCommandArgs.WIRSocketDataKey;
|
|
333
|
+
const finalCommand = {
|
|
334
|
+
__argument: finalCommandArgs,
|
|
335
|
+
__selector: cmd.__selector,
|
|
336
|
+
};
|
|
337
|
+
const hasSocketData = (0, utils_1.isPlainObject)(cmd.__argument?.WIRSocketDataKey);
|
|
338
|
+
if (hasSocketData) {
|
|
339
|
+
// make sure the message being sent has all the information that is needed
|
|
340
|
+
const socketData = cmd.__argument.WIRSocketDataKey;
|
|
341
|
+
if (!Number.isInteger(socketData.id)) {
|
|
342
|
+
// ! This must be a number
|
|
343
|
+
socketData.id = wrapperMsgId;
|
|
368
344
|
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
345
|
+
finalCommand.__argument.WIRSocketDataKey = Buffer.from(JSON.stringify(socketData));
|
|
346
|
+
}
|
|
347
|
+
let messageHandled = true;
|
|
348
|
+
if (!waitForResponse) {
|
|
349
|
+
// the promise will be resolved as soon as the socket has been sent
|
|
350
|
+
messageHandled = false;
|
|
351
|
+
// do not log receipts
|
|
352
|
+
this.messageHandler.once(msgId.toString(), (err) => {
|
|
353
|
+
if (err) {
|
|
354
|
+
// we are not waiting for this, and if it errors it is most likely
|
|
355
|
+
// a protocol change. Log and check during testing
|
|
356
|
+
logger_1.log.error(`Received error from send that is not being waited for (id: ${msgId}): ` +
|
|
357
|
+
(0, utils_1.truncateString)(JSON.stringify(err), DATA_LOG_LENGTH));
|
|
358
|
+
// reject, though it is very rare that this will be triggered, since
|
|
359
|
+
// the promise is resolved directly after send. On the off chance,
|
|
360
|
+
// though, it will alert of a protocol change.
|
|
361
|
+
reject(err);
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
else if (this.messageHandler.listenerCount(cmd.__selector)) {
|
|
366
|
+
this.messageHandler.prependOnceListener(cmd.__selector, (err, ...args) => {
|
|
367
|
+
if (err) {
|
|
368
|
+
return reject(err);
|
|
369
|
+
}
|
|
370
|
+
logger_1.log.debug(`Received response from send (id: ${msgId}): '${(0, utils_1.truncateString)(JSON.stringify(args), DATA_LOG_LENGTH)}'`);
|
|
371
|
+
resolve(args);
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
else if (hasSocketData) {
|
|
375
|
+
this.messageHandler.once(msgId.toString(), (err, value) => {
|
|
376
|
+
if (err) {
|
|
377
|
+
return reject(new Error(`Remote debugger error with code '${err.code}': ${err.message}`));
|
|
378
|
+
}
|
|
379
|
+
logger_1.log.debug(`Received data response from send (id: ${msgId}): '${(0, utils_1.truncateString)(JSON.stringify(value), DATA_LOG_LENGTH)}'`);
|
|
380
|
+
resolve(value);
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
else {
|
|
384
|
+
// nothing else is handling things, so just resolve when the message is sent
|
|
385
|
+
messageHandled = false;
|
|
386
|
+
}
|
|
387
|
+
const msg = `Sending '${cmd.__selector}' message` +
|
|
388
|
+
(appIdKey ? ` to app '${appIdKey}'` : '') +
|
|
389
|
+
(pageIdKey ? `, page '${pageIdKey}'` : '') +
|
|
390
|
+
(targetId ? `, target '${targetId}'` : '') +
|
|
391
|
+
` (id: ${msgId}): '${command}'`;
|
|
392
|
+
logger_1.log.debug(msg);
|
|
393
|
+
try {
|
|
394
|
+
await this.sendMessage(finalCommand);
|
|
395
|
+
if (!messageHandled) {
|
|
396
|
+
// There are no handlers waiting for a response before resolving,
|
|
397
|
+
// and no errors sending the message over the socket, so resolve
|
|
398
|
+
resolve(fullOpts);
|
|
377
399
|
}
|
|
378
|
-
logger_1.log.debug(`Received data response from send (id: ${msgId}): '${lodash_1.default.truncate(JSON.stringify(value), DATA_LOG_LENGTH)}'`);
|
|
379
|
-
resolve(value);
|
|
380
|
-
});
|
|
381
|
-
}
|
|
382
|
-
else {
|
|
383
|
-
// nothing else is handling things, so just resolve when the message is sent
|
|
384
|
-
messageHandled = false;
|
|
385
|
-
}
|
|
386
|
-
const msg = `Sending '${cmd.__selector}' message` +
|
|
387
|
-
(appIdKey ? ` to app '${appIdKey}'` : '') +
|
|
388
|
-
(pageIdKey ? `, page '${pageIdKey}'` : '') +
|
|
389
|
-
(targetId ? `, target '${targetId}'` : '') +
|
|
390
|
-
` (id: ${msgId}): '${command}'`;
|
|
391
|
-
logger_1.log.debug(msg);
|
|
392
|
-
try {
|
|
393
|
-
await this.sendMessage(finalCommand);
|
|
394
|
-
if (!messageHandled) {
|
|
395
|
-
// There are no handlers waiting for a response before resolving,
|
|
396
|
-
// and no errors sending the message over the socket, so resolve
|
|
397
|
-
resolve(fullOpts);
|
|
398
400
|
}
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
}
|
|
401
|
+
catch (err) {
|
|
402
|
+
return reject(err);
|
|
403
|
+
}
|
|
404
|
+
})().catch(reject);
|
|
403
405
|
});
|
|
404
406
|
}
|
|
405
407
|
/**
|
|
@@ -445,7 +447,7 @@ class RpcClient {
|
|
|
445
447
|
* @param targetInfo - Information about the created target.
|
|
446
448
|
*/
|
|
447
449
|
async addTarget(err, app, targetInfo) {
|
|
448
|
-
if (
|
|
450
|
+
if (targetInfo?.targetId == null) {
|
|
449
451
|
logger_1.log.info(`Received 'Target.targetCreated' event for app '${app}' with no target. Skipping`);
|
|
450
452
|
return;
|
|
451
453
|
}
|
|
@@ -454,7 +456,7 @@ class RpcClient {
|
|
|
454
456
|
return;
|
|
455
457
|
}
|
|
456
458
|
const { appIdKey, pageIdKey, pageReadinessDetector } = pendingPageTargetDetails;
|
|
457
|
-
if (!
|
|
459
|
+
if (!(0, utils_1.isPlainObject)(this.targets[appIdKey])) {
|
|
458
460
|
this.targets[appIdKey] = {
|
|
459
461
|
lock: new async_lock_1.default({ maxOccupationTime: this._targetCreationTimeoutMs }),
|
|
460
462
|
};
|
|
@@ -503,7 +505,7 @@ class RpcClient {
|
|
|
503
505
|
return;
|
|
504
506
|
}
|
|
505
507
|
logger_1.log.debug(`Target created for app '${appIdKey}' and page '${pageIdKey}': ${JSON.stringify(targetInfo)}`);
|
|
506
|
-
if (
|
|
508
|
+
if (Object.hasOwn(this.targets[appIdKey], pageIdKey)) {
|
|
507
509
|
const existingTarget = this.targets[appIdKey][pageIdKey];
|
|
508
510
|
logger_1.log.debug(`There is already a target for this app and page ('${existingTarget}'). ` +
|
|
509
511
|
`This might cause problems`);
|
|
@@ -580,7 +582,7 @@ class RpcClient {
|
|
|
580
582
|
* @param targetInfo - Information about the destroyed target.
|
|
581
583
|
*/
|
|
582
584
|
async removeTarget(err, app, targetInfo) {
|
|
583
|
-
if (
|
|
585
|
+
if (targetInfo?.targetId == null) {
|
|
584
586
|
logger_1.log.debug(`Received 'Target.targetDestroyed' event with no target. Skipping`);
|
|
585
587
|
return;
|
|
586
588
|
}
|
|
@@ -591,7 +593,7 @@ class RpcClient {
|
|
|
591
593
|
delete this.targets[app].provisional;
|
|
592
594
|
// we do not know the page, so go through and find the existing target
|
|
593
595
|
const appTargetsMap = this.targets[app];
|
|
594
|
-
for (const [page, targetId] of
|
|
596
|
+
for (const [page, targetId] of Object.entries(appTargetsMap)) {
|
|
595
597
|
if (targetId === oldTargetId) {
|
|
596
598
|
logger_1.log.debug(`Found provisional target for app '${app}'. ` +
|
|
597
599
|
`Old target: '${oldTargetId}', new target: '${newTargetId}'. Updating`);
|
|
@@ -604,7 +606,7 @@ class RpcClient {
|
|
|
604
606
|
}
|
|
605
607
|
// if there is no waiting provisional target, just get rid of the existing one
|
|
606
608
|
const targets = this.targets[app];
|
|
607
|
-
for (const [page, targetId] of
|
|
609
|
+
for (const [page, targetId] of Object.entries(targets)) {
|
|
608
610
|
if (targetId === targetInfo.targetId) {
|
|
609
611
|
delete targets[page];
|
|
610
612
|
return;
|
|
@@ -660,7 +662,7 @@ class RpcClient {
|
|
|
660
662
|
}
|
|
661
663
|
await this.send('setSenderKey', sendOpts);
|
|
662
664
|
};
|
|
663
|
-
await
|
|
665
|
+
await (0, utils_1.withTimeout)(setupWebview(), timeoutMs, `Cannot set up page '${pageIdKey}' for app '${appIdKey}' within ${timeoutMs}ms`);
|
|
664
666
|
const msLeft = Math.max(timeoutMs - Math.trunc(timer.getDuration().asMilliSeconds), 1000);
|
|
665
667
|
logger_1.log.debug(`Waiting up to ${msLeft}ms for page '${pageIdKey}' of app '${appIdKey}' to be selected`);
|
|
666
668
|
await new Promise((resolve) => {
|
|
@@ -686,6 +688,113 @@ class RpcClient {
|
|
|
686
688
|
});
|
|
687
689
|
});
|
|
688
690
|
}
|
|
691
|
+
/**
|
|
692
|
+
* Connects to a specific application and returns its page dictionary.
|
|
693
|
+
*
|
|
694
|
+
* @param appIdKey - The application identifier key to connect to.
|
|
695
|
+
* @returns A promise that resolves to a tuple containing the connected app ID key
|
|
696
|
+
* and the page dictionary.
|
|
697
|
+
* @throws Error if a new application connects during the process or if the page
|
|
698
|
+
* dictionary is empty.
|
|
699
|
+
*/
|
|
700
|
+
async selectApp(appIdKey) {
|
|
701
|
+
return await new Promise((resolve, reject) => {
|
|
702
|
+
// local callback, temporarily added as callback to
|
|
703
|
+
// `_rpc_applicationConnected:` remote debugger response
|
|
704
|
+
// to handle the initial connection
|
|
705
|
+
const onAppChange = (err, dict) => {
|
|
706
|
+
if (err) {
|
|
707
|
+
return reject(err);
|
|
708
|
+
}
|
|
709
|
+
// from the dictionary returned, get the ids
|
|
710
|
+
const oldAppIdKey = dict.WIRHostApplicationIdentifierKey;
|
|
711
|
+
const correctAppIdKey = dict.WIRApplicationIdentifierKey;
|
|
712
|
+
// if this is a report of a proxy redirect from the remote debugger
|
|
713
|
+
// we want to update our dictionary and get a new app id
|
|
714
|
+
if (oldAppIdKey && correctAppIdKey !== oldAppIdKey) {
|
|
715
|
+
logger_1.log.debug(`We were notified we might have connected to the wrong app. ` +
|
|
716
|
+
`Using id ${correctAppIdKey} instead of ${oldAppIdKey}`);
|
|
717
|
+
}
|
|
718
|
+
reject(new Error(exports.NEW_APP_CONNECTED_ERROR));
|
|
719
|
+
};
|
|
720
|
+
this.messageHandler.prependOnceListener('_rpc_applicationConnected:', onAppChange);
|
|
721
|
+
// do the actual connecting to the app
|
|
722
|
+
void (async () => {
|
|
723
|
+
try {
|
|
724
|
+
const [connectedAppIdKey, pageDict] = await this.send('connectToApp', { appIdKey });
|
|
725
|
+
// sometimes the connect logic happens, but with an empty dictionary
|
|
726
|
+
// which leads to the remote debugger getting disconnected, and into a loop
|
|
727
|
+
if ((0, utils_1.isEmpty)(pageDict)) {
|
|
728
|
+
reject(new Error(exports.EMPTY_PAGE_DICTIONARY_ERROR));
|
|
729
|
+
}
|
|
730
|
+
else {
|
|
731
|
+
resolve([connectedAppIdKey, pageDict]);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
catch (err) {
|
|
735
|
+
logger_1.log.warn(`Unable to connect to the app: ${err.message}`);
|
|
736
|
+
reject(err);
|
|
737
|
+
}
|
|
738
|
+
finally {
|
|
739
|
+
this.messageHandler.off('_rpc_applicationConnected:', onAppChange);
|
|
740
|
+
}
|
|
741
|
+
})();
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
/**
|
|
745
|
+
* Handles execution context creation events by storing the context ID.
|
|
746
|
+
*
|
|
747
|
+
* @param err - Error if one occurred, undefined otherwise.
|
|
748
|
+
* @param context - The execution context information.
|
|
749
|
+
*/
|
|
750
|
+
onExecutionContextCreated(err, context) {
|
|
751
|
+
// { id: 2, isPageContext: true, name: '', frameId: '0.1' }
|
|
752
|
+
// right now we have no way to map contexts to apps/pages
|
|
753
|
+
// so just store
|
|
754
|
+
this.contexts.push(context.id);
|
|
755
|
+
}
|
|
756
|
+
/**
|
|
757
|
+
* Handles garbage collection events by logging them.
|
|
758
|
+
* Garbage collection can affect operation timing.
|
|
759
|
+
*/
|
|
760
|
+
onGarbageCollected() {
|
|
761
|
+
// just want to log that this is happening, as it can affect operation
|
|
762
|
+
logger_1.log.debug(`Web Inspector garbage collected`);
|
|
763
|
+
}
|
|
764
|
+
/**
|
|
765
|
+
* Handles script parsing events by logging script information.
|
|
766
|
+
*
|
|
767
|
+
* @param err - Error if one occurred, undefined otherwise.
|
|
768
|
+
* @param scriptInfo - Information about the parsed script.
|
|
769
|
+
*/
|
|
770
|
+
onScriptParsed(err, scriptInfo) {
|
|
771
|
+
// { scriptId: '13', url: '', startLine: 0, startColumn: 0, endLine: 82, endColumn: 3 }
|
|
772
|
+
logger_1.log.debug(`Script parsed: ${JSON.stringify(scriptInfo)}`);
|
|
773
|
+
}
|
|
774
|
+
/**
|
|
775
|
+
* Waits for a page to be initialized by acquiring locks on both the page
|
|
776
|
+
* target lock and the page selection lock.
|
|
777
|
+
*
|
|
778
|
+
* @param appIdKey - The application identifier key.
|
|
779
|
+
* @param pageIdKey - The page identifier key.
|
|
780
|
+
* @throws Error if no targets are found for the application.
|
|
781
|
+
*/
|
|
782
|
+
async waitForPage(appIdKey, pageIdKey) {
|
|
783
|
+
const appTargetsMap = this.targets[appIdKey];
|
|
784
|
+
if (!appTargetsMap) {
|
|
785
|
+
throw new Error(`No targets found for app '${appIdKey}'`);
|
|
786
|
+
}
|
|
787
|
+
const lock = appTargetsMap.lock;
|
|
788
|
+
const timer = new support_1.timing.Timer().start();
|
|
789
|
+
await Promise.all([
|
|
790
|
+
lock.acquire(pageIdKey, async () => await (0, utils_1.delay)(0)),
|
|
791
|
+
this._pageSelectionLock.acquire(toPageSelectionKey(appIdKey, pageIdKey), async () => await (0, utils_1.delay)(0)),
|
|
792
|
+
]);
|
|
793
|
+
const durationMs = timer.getDuration().asMilliSeconds;
|
|
794
|
+
if (durationMs > 10) {
|
|
795
|
+
logger_1.log.debug(`Waited ${durationMs}ms until the page ${pageIdKey}@${appIdKey} is initialized`);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
689
798
|
/**
|
|
690
799
|
* Initializes a page by enabling various Web Inspector domains.
|
|
691
800
|
* Can perform either simple or full initialization based on configuration.
|
|
@@ -818,89 +927,6 @@ class RpcClient {
|
|
|
818
927
|
`in ${timer.getDuration().asMilliSeconds}ms`);
|
|
819
928
|
return true;
|
|
820
929
|
}
|
|
821
|
-
/**
|
|
822
|
-
* Connects to a specific application and returns its page dictionary.
|
|
823
|
-
*
|
|
824
|
-
* @param appIdKey - The application identifier key to connect to.
|
|
825
|
-
* @returns A promise that resolves to a tuple containing the connected app ID key
|
|
826
|
-
* and the page dictionary.
|
|
827
|
-
* @throws Error if a new application connects during the process or if the page
|
|
828
|
-
* dictionary is empty.
|
|
829
|
-
*/
|
|
830
|
-
async selectApp(appIdKey) {
|
|
831
|
-
return await new bluebird_1.default((resolve, reject) => {
|
|
832
|
-
// local callback, temporarily added as callback to
|
|
833
|
-
// `_rpc_applicationConnected:` remote debugger response
|
|
834
|
-
// to handle the initial connection
|
|
835
|
-
const onAppChange = (err, dict) => {
|
|
836
|
-
if (err) {
|
|
837
|
-
return reject(err);
|
|
838
|
-
}
|
|
839
|
-
// from the dictionary returned, get the ids
|
|
840
|
-
const oldAppIdKey = dict.WIRHostApplicationIdentifierKey;
|
|
841
|
-
const correctAppIdKey = dict.WIRApplicationIdentifierKey;
|
|
842
|
-
// if this is a report of a proxy redirect from the remote debugger
|
|
843
|
-
// we want to update our dictionary and get a new app id
|
|
844
|
-
if (oldAppIdKey && correctAppIdKey !== oldAppIdKey) {
|
|
845
|
-
logger_1.log.debug(`We were notified we might have connected to the wrong app. ` +
|
|
846
|
-
`Using id ${correctAppIdKey} instead of ${oldAppIdKey}`);
|
|
847
|
-
}
|
|
848
|
-
reject(new Error(exports.NEW_APP_CONNECTED_ERROR));
|
|
849
|
-
};
|
|
850
|
-
this.messageHandler.prependOnceListener('_rpc_applicationConnected:', onAppChange);
|
|
851
|
-
// do the actual connecting to the app
|
|
852
|
-
(async () => {
|
|
853
|
-
try {
|
|
854
|
-
const [connectedAppIdKey, pageDict] = await this.send('connectToApp', { appIdKey });
|
|
855
|
-
// sometimes the connect logic happens, but with an empty dictionary
|
|
856
|
-
// which leads to the remote debugger getting disconnected, and into a loop
|
|
857
|
-
if (lodash_1.default.isEmpty(pageDict)) {
|
|
858
|
-
reject(new Error(exports.EMPTY_PAGE_DICTIONARY_ERROR));
|
|
859
|
-
}
|
|
860
|
-
else {
|
|
861
|
-
resolve([connectedAppIdKey, pageDict]);
|
|
862
|
-
}
|
|
863
|
-
}
|
|
864
|
-
catch (err) {
|
|
865
|
-
logger_1.log.warn(`Unable to connect to the app: ${err.message}`);
|
|
866
|
-
reject(err);
|
|
867
|
-
}
|
|
868
|
-
finally {
|
|
869
|
-
this.messageHandler.off('_rpc_applicationConnected:', onAppChange);
|
|
870
|
-
}
|
|
871
|
-
})();
|
|
872
|
-
});
|
|
873
|
-
}
|
|
874
|
-
/**
|
|
875
|
-
* Handles execution context creation events by storing the context ID.
|
|
876
|
-
*
|
|
877
|
-
* @param err - Error if one occurred, undefined otherwise.
|
|
878
|
-
* @param context - The execution context information.
|
|
879
|
-
*/
|
|
880
|
-
onExecutionContextCreated(err, context) {
|
|
881
|
-
// { id: 2, isPageContext: true, name: '', frameId: '0.1' }
|
|
882
|
-
// right now we have no way to map contexts to apps/pages
|
|
883
|
-
// so just store
|
|
884
|
-
this.contexts.push(context.id);
|
|
885
|
-
}
|
|
886
|
-
/**
|
|
887
|
-
* Handles garbage collection events by logging them.
|
|
888
|
-
* Garbage collection can affect operation timing.
|
|
889
|
-
*/
|
|
890
|
-
onGarbageCollected() {
|
|
891
|
-
// just want to log that this is happening, as it can affect operation
|
|
892
|
-
logger_1.log.debug(`Web Inspector garbage collected`);
|
|
893
|
-
}
|
|
894
|
-
/**
|
|
895
|
-
* Handles script parsing events by logging script information.
|
|
896
|
-
*
|
|
897
|
-
* @param err - Error if one occurred, undefined otherwise.
|
|
898
|
-
* @param scriptInfo - Information about the parsed script.
|
|
899
|
-
*/
|
|
900
|
-
onScriptParsed(err, scriptInfo) {
|
|
901
|
-
// { scriptId: '13', url: '', startLine: 0, startColumn: 0, endLine: 82, endColumn: 3 }
|
|
902
|
-
logger_1.log.debug(`Script parsed: ${JSON.stringify(scriptInfo)}`);
|
|
903
|
-
}
|
|
904
930
|
/**
|
|
905
931
|
* Resumes a paused target.
|
|
906
932
|
*
|
|
@@ -940,13 +966,13 @@ class RpcClient {
|
|
|
940
966
|
let readyState;
|
|
941
967
|
try {
|
|
942
968
|
const commandTimeoutMs = Math.max(100, Math.trunc((pageReadinessDetector.timeoutMs - timer.getDuration().asMilliSeconds) * 0.8));
|
|
943
|
-
const rawResult = await
|
|
969
|
+
const rawResult = await (0, utils_1.withTimeout)(this.send('Runtime.evaluate', {
|
|
944
970
|
expression: 'document.readyState;',
|
|
945
971
|
returnByValue: true,
|
|
946
972
|
appIdKey,
|
|
947
973
|
pageIdKey,
|
|
948
974
|
targetId,
|
|
949
|
-
})
|
|
975
|
+
}), commandTimeoutMs);
|
|
950
976
|
readyState = (0, utils_1.convertJavascriptEvaluationResult)(rawResult);
|
|
951
977
|
}
|
|
952
978
|
catch (e) {
|
|
@@ -958,35 +984,11 @@ class RpcClient {
|
|
|
958
984
|
`${timer.getDuration().asMilliSeconds}ms`);
|
|
959
985
|
return;
|
|
960
986
|
}
|
|
961
|
-
await
|
|
987
|
+
await (0, utils_1.delay)(100);
|
|
962
988
|
}
|
|
963
989
|
logger_1.log.warn(`Page '${pageIdKey}' for app '${appIdKey}' is not ready after ` +
|
|
964
990
|
`${timer.getDuration().asMilliSeconds}ms. Continuing anyway`);
|
|
965
991
|
}
|
|
966
|
-
/**
|
|
967
|
-
* Waits for a page to be initialized by acquiring locks on both the page
|
|
968
|
-
* target lock and the page selection lock.
|
|
969
|
-
*
|
|
970
|
-
* @param appIdKey - The application identifier key.
|
|
971
|
-
* @param pageIdKey - The page identifier key.
|
|
972
|
-
* @throws Error if no targets are found for the application.
|
|
973
|
-
*/
|
|
974
|
-
async waitForPage(appIdKey, pageIdKey) {
|
|
975
|
-
const appTargetsMap = this.targets[appIdKey];
|
|
976
|
-
if (!appTargetsMap) {
|
|
977
|
-
throw new Error(`No targets found for app '${appIdKey}'`);
|
|
978
|
-
}
|
|
979
|
-
const lock = appTargetsMap.lock;
|
|
980
|
-
const timer = new support_1.timing.Timer().start();
|
|
981
|
-
await Promise.all([
|
|
982
|
-
lock.acquire(pageIdKey, async () => await bluebird_1.default.delay(0)),
|
|
983
|
-
this._pageSelectionLock.acquire(toPageSelectionKey(appIdKey, pageIdKey), async () => await bluebird_1.default.delay(0)),
|
|
984
|
-
]);
|
|
985
|
-
const durationMs = timer.getDuration().asMilliSeconds;
|
|
986
|
-
if (durationMs > 10) {
|
|
987
|
-
logger_1.log.debug(`Waited ${durationMs}ms until the page ${pageIdKey}@${appIdKey} is initialized`);
|
|
988
|
-
}
|
|
989
|
-
}
|
|
990
992
|
/**
|
|
991
993
|
* Gets the pending target details if there is a pending request for the given app.
|
|
992
994
|
* Filters out non-page target types (e.g., 'frame').
|