appium-remote-debugger 15.7.2 → 15.8.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/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 +70 -4
- package/build/lib/utils.d.ts.map +1 -1
- package/build/lib/utils.js +171 -23
- 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 +181 -23
- package/package.json +4 -8
- package/scripts/common.mjs +42 -37
- package/scripts/web_inspector_proxy.mjs +3 -5
package/lib/rpc/rpc-client.ts
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
import {RemoteMessages} from './remote-messages';
|
|
2
2
|
import {waitForCondition} from 'asyncbox';
|
|
3
3
|
import {log} from '../logger';
|
|
4
|
-
import _ from 'lodash';
|
|
5
|
-
import B from 'bluebird';
|
|
6
4
|
import RpcMessageHandler from './rpc-message-handler';
|
|
7
5
|
import {util, timing} from '@appium/support';
|
|
8
6
|
import {EventEmitter} from 'node:events';
|
|
9
7
|
import AsyncLock from 'async-lock';
|
|
10
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
convertJavascriptEvaluationResult,
|
|
10
|
+
delay,
|
|
11
|
+
defaults,
|
|
12
|
+
isEmpty,
|
|
13
|
+
isPlainObject,
|
|
14
|
+
truncateString,
|
|
15
|
+
withTimeout,
|
|
16
|
+
} from '../utils';
|
|
11
17
|
import type {StringRecord} from '@appium/types';
|
|
12
18
|
import type {
|
|
13
19
|
AppIdKey,
|
|
@@ -22,7 +28,7 @@ import type {
|
|
|
22
28
|
RemoteCommandId,
|
|
23
29
|
} from '../types';
|
|
24
30
|
|
|
25
|
-
const DATA_LOG_LENGTH =
|
|
31
|
+
const DATA_LOG_LENGTH = 200;
|
|
26
32
|
const MIN_WAIT_FOR_TARGET_TIMEOUT_MS = 30000;
|
|
27
33
|
const DEFAULT_TARGET_CREATION_TIMEOUT_MS = 3 * 60 * 1000; // 3 minutes
|
|
28
34
|
const WAIT_FOR_TARGET_INTERVAL_MS = 100;
|
|
@@ -187,21 +193,21 @@ export class RpcClient {
|
|
|
187
193
|
}
|
|
188
194
|
|
|
189
195
|
/**
|
|
190
|
-
*
|
|
196
|
+
* Gets the event emitter for target subscriptions.
|
|
191
197
|
*
|
|
192
|
-
* @
|
|
198
|
+
* @returns The target subscriptions event emitter.
|
|
193
199
|
*/
|
|
194
|
-
|
|
195
|
-
this.
|
|
200
|
+
get targetSubscriptions(): EventEmitter {
|
|
201
|
+
return this._targetSubscriptions;
|
|
196
202
|
}
|
|
197
203
|
|
|
198
204
|
/**
|
|
199
|
-
*
|
|
205
|
+
* Sets the connection status.
|
|
200
206
|
*
|
|
201
|
-
* @
|
|
207
|
+
* @param connected - The connection status to set.
|
|
202
208
|
*/
|
|
203
|
-
|
|
204
|
-
|
|
209
|
+
set isConnected(connected: boolean) {
|
|
210
|
+
this.connected = !!connected;
|
|
205
211
|
}
|
|
206
212
|
|
|
207
213
|
/**
|
|
@@ -319,7 +325,7 @@ export class RpcClient {
|
|
|
319
325
|
await waitForCondition(
|
|
320
326
|
() => {
|
|
321
327
|
target = this.getTarget(appIdKey, pageIdKey);
|
|
322
|
-
return !
|
|
328
|
+
return !isEmpty(target);
|
|
323
329
|
},
|
|
324
330
|
{
|
|
325
331
|
waitMs,
|
|
@@ -390,125 +396,129 @@ export class RpcClient {
|
|
|
390
396
|
opts: RemoteCommandOpts,
|
|
391
397
|
waitForResponse: TWaitForResponse = true as TWaitForResponse,
|
|
392
398
|
): Promise<TWaitForResponse extends true ? any : RemoteCommandOpts> {
|
|
393
|
-
return await new
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
399
|
+
return await new Promise<any>((resolve, reject) => {
|
|
400
|
+
void (async () => {
|
|
401
|
+
// promise to be resolved whenever remote debugger
|
|
402
|
+
// replies to our request
|
|
403
|
+
|
|
404
|
+
// keep track of the messages coming and going using a simple sequential id
|
|
405
|
+
const msgId = this.msgId++;
|
|
406
|
+
// for target-base communication, everything is wrapped up
|
|
407
|
+
const wrapperMsgId = this.msgId++;
|
|
408
|
+
// acknowledge wrapper message
|
|
409
|
+
this.messageHandler.on(wrapperMsgId.toString(), function (err: Error | null) {
|
|
410
|
+
if (err) {
|
|
411
|
+
reject(err);
|
|
412
|
+
}
|
|
413
|
+
});
|
|
407
414
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
415
|
+
const appIdKey = opts.appIdKey;
|
|
416
|
+
const pageIdKey = opts.pageIdKey;
|
|
417
|
+
const targetId = opts.targetId ?? this.getTarget(appIdKey, pageIdKey);
|
|
411
418
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
419
|
+
// retrieve the correct command to send
|
|
420
|
+
const fullOpts: RemoteCommandOpts & RemoteCommandId = defaults(
|
|
421
|
+
{
|
|
422
|
+
connId: this.connId,
|
|
423
|
+
senderId: this.senderId,
|
|
424
|
+
targetId,
|
|
425
|
+
id: msgId.toString(),
|
|
426
|
+
},
|
|
427
|
+
opts,
|
|
428
|
+
);
|
|
429
|
+
let cmd: RawRemoteCommand;
|
|
430
|
+
try {
|
|
431
|
+
cmd = this.remoteMessages.getRemoteCommand(command, fullOpts);
|
|
432
|
+
} catch (err: any) {
|
|
433
|
+
log.error(err);
|
|
434
|
+
return reject(err);
|
|
435
|
+
}
|
|
429
436
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
437
|
+
const finalCommandArgs = {...cmd.__argument};
|
|
438
|
+
delete finalCommandArgs.WIRSocketDataKey;
|
|
439
|
+
const finalCommand: RemoteCommand = {
|
|
440
|
+
__argument: finalCommandArgs as any,
|
|
441
|
+
__selector: cmd.__selector,
|
|
442
|
+
};
|
|
434
443
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
444
|
+
const hasSocketData = isPlainObject(cmd.__argument?.WIRSocketDataKey);
|
|
445
|
+
if (hasSocketData) {
|
|
446
|
+
// make sure the message being sent has all the information that is needed
|
|
447
|
+
const socketData = cmd.__argument.WIRSocketDataKey as StringRecord;
|
|
448
|
+
if (!Number.isInteger(socketData.id)) {
|
|
449
|
+
// ! This must be a number
|
|
450
|
+
socketData.id = wrapperMsgId;
|
|
451
|
+
}
|
|
452
|
+
finalCommand.__argument.WIRSocketDataKey = Buffer.from(JSON.stringify(socketData));
|
|
442
453
|
}
|
|
443
|
-
finalCommand.__argument.WIRSocketDataKey = Buffer.from(JSON.stringify(socketData));
|
|
444
|
-
}
|
|
445
454
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
if (err) {
|
|
453
|
-
// we are not waiting for this, and if it errors it is most likely
|
|
454
|
-
// a protocol change. Log and check during testing
|
|
455
|
-
log.error(
|
|
456
|
-
`Received error from send that is not being waited for (id: ${msgId}): ` +
|
|
457
|
-
_.truncate(JSON.stringify(err), DATA_LOG_LENGTH),
|
|
458
|
-
);
|
|
459
|
-
// reject, though it is very rare that this will be triggered, since
|
|
460
|
-
// the promise is resolved directly after send. On the off chance,
|
|
461
|
-
// though, it will alert of a protocol change.
|
|
462
|
-
reject(err);
|
|
463
|
-
}
|
|
464
|
-
});
|
|
465
|
-
} else if (this.messageHandler.listenerCount(cmd.__selector)) {
|
|
466
|
-
this.messageHandler.prependOnceListener(
|
|
467
|
-
cmd.__selector,
|
|
468
|
-
(err: Error | null, ...args: any[]) => {
|
|
455
|
+
let messageHandled = true;
|
|
456
|
+
if (!waitForResponse) {
|
|
457
|
+
// the promise will be resolved as soon as the socket has been sent
|
|
458
|
+
messageHandled = false;
|
|
459
|
+
// do not log receipts
|
|
460
|
+
this.messageHandler.once(msgId.toString(), (err: Error | null) => {
|
|
469
461
|
if (err) {
|
|
470
|
-
|
|
462
|
+
// we are not waiting for this, and if it errors it is most likely
|
|
463
|
+
// a protocol change. Log and check during testing
|
|
464
|
+
log.error(
|
|
465
|
+
`Received error from send that is not being waited for (id: ${msgId}): ` +
|
|
466
|
+
truncateString(JSON.stringify(err), DATA_LOG_LENGTH),
|
|
467
|
+
);
|
|
468
|
+
// reject, though it is very rare that this will be triggered, since
|
|
469
|
+
// the promise is resolved directly after send. On the off chance,
|
|
470
|
+
// though, it will alert of a protocol change.
|
|
471
|
+
reject(err);
|
|
472
|
+
}
|
|
473
|
+
});
|
|
474
|
+
} else if (this.messageHandler.listenerCount(cmd.__selector)) {
|
|
475
|
+
this.messageHandler.prependOnceListener(
|
|
476
|
+
cmd.__selector,
|
|
477
|
+
(err: Error | null, ...args: any[]) => {
|
|
478
|
+
if (err) {
|
|
479
|
+
return reject(err);
|
|
480
|
+
}
|
|
481
|
+
log.debug(
|
|
482
|
+
`Received response from send (id: ${msgId}): '${truncateString(JSON.stringify(args), DATA_LOG_LENGTH)}'`,
|
|
483
|
+
);
|
|
484
|
+
resolve(args);
|
|
485
|
+
},
|
|
486
|
+
);
|
|
487
|
+
} else if (hasSocketData) {
|
|
488
|
+
this.messageHandler.once(msgId.toString(), (err: Error | null, value: any) => {
|
|
489
|
+
if (err) {
|
|
490
|
+
return reject(
|
|
491
|
+
new Error(`Remote debugger error with code '${(err as any).code}': ${err.message}`),
|
|
492
|
+
);
|
|
471
493
|
}
|
|
472
494
|
log.debug(
|
|
473
|
-
`Received response from send (id: ${msgId}): '${
|
|
495
|
+
`Received data response from send (id: ${msgId}): '${truncateString(JSON.stringify(value), DATA_LOG_LENGTH)}'`,
|
|
474
496
|
);
|
|
475
|
-
resolve(
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
return reject(
|
|
482
|
-
new Error(`Remote debugger error with code '${(err as any).code}': ${err.message}`),
|
|
483
|
-
);
|
|
484
|
-
}
|
|
485
|
-
log.debug(
|
|
486
|
-
`Received data response from send (id: ${msgId}): '${_.truncate(JSON.stringify(value), DATA_LOG_LENGTH)}'`,
|
|
487
|
-
);
|
|
488
|
-
resolve(value);
|
|
489
|
-
});
|
|
490
|
-
} else {
|
|
491
|
-
// nothing else is handling things, so just resolve when the message is sent
|
|
492
|
-
messageHandled = false;
|
|
493
|
-
}
|
|
497
|
+
resolve(value);
|
|
498
|
+
});
|
|
499
|
+
} else {
|
|
500
|
+
// nothing else is handling things, so just resolve when the message is sent
|
|
501
|
+
messageHandled = false;
|
|
502
|
+
}
|
|
494
503
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
504
|
+
const msg =
|
|
505
|
+
`Sending '${cmd.__selector}' message` +
|
|
506
|
+
(appIdKey ? ` to app '${appIdKey}'` : '') +
|
|
507
|
+
(pageIdKey ? `, page '${pageIdKey}'` : '') +
|
|
508
|
+
(targetId ? `, target '${targetId}'` : '') +
|
|
509
|
+
` (id: ${msgId}): '${command}'`;
|
|
510
|
+
log.debug(msg);
|
|
511
|
+
try {
|
|
512
|
+
await this.sendMessage(finalCommand);
|
|
513
|
+
if (!messageHandled) {
|
|
514
|
+
// There are no handlers waiting for a response before resolving,
|
|
515
|
+
// and no errors sending the message over the socket, so resolve
|
|
516
|
+
resolve(fullOpts as any);
|
|
517
|
+
}
|
|
518
|
+
} catch (err) {
|
|
519
|
+
return reject(err);
|
|
508
520
|
}
|
|
509
|
-
}
|
|
510
|
-
return reject(err);
|
|
511
|
-
}
|
|
521
|
+
})();
|
|
512
522
|
});
|
|
513
523
|
}
|
|
514
524
|
|
|
@@ -559,7 +569,7 @@ export class RpcClient {
|
|
|
559
569
|
* @param targetInfo - Information about the created target.
|
|
560
570
|
*/
|
|
561
571
|
async addTarget(err: Error | undefined, app: AppIdKey, targetInfo: TargetInfo): Promise<void> {
|
|
562
|
-
if (
|
|
572
|
+
if (targetInfo?.targetId == null) {
|
|
563
573
|
log.info(`Received 'Target.targetCreated' event for app '${app}' with no target. Skipping`);
|
|
564
574
|
return;
|
|
565
575
|
}
|
|
@@ -570,7 +580,7 @@ export class RpcClient {
|
|
|
570
580
|
}
|
|
571
581
|
const {appIdKey, pageIdKey, pageReadinessDetector} = pendingPageTargetDetails;
|
|
572
582
|
|
|
573
|
-
if (!
|
|
583
|
+
if (!isPlainObject(this.targets[appIdKey])) {
|
|
574
584
|
this.targets[appIdKey] = {
|
|
575
585
|
lock: new AsyncLock({maxOccupationTime: this._targetCreationTimeoutMs}),
|
|
576
586
|
} as PagesToTargets;
|
|
@@ -634,7 +644,7 @@ export class RpcClient {
|
|
|
634
644
|
log.debug(
|
|
635
645
|
`Target created for app '${appIdKey}' and page '${pageIdKey}': ${JSON.stringify(targetInfo)}`,
|
|
636
646
|
);
|
|
637
|
-
if (
|
|
647
|
+
if (Object.hasOwn(this.targets[appIdKey], pageIdKey)) {
|
|
638
648
|
const existingTarget = this.targets[appIdKey][pageIdKey] as TargetId;
|
|
639
649
|
log.debug(
|
|
640
650
|
`There is already a target for this app and page ('${existingTarget}'). ` +
|
|
@@ -727,7 +737,7 @@ export class RpcClient {
|
|
|
727
737
|
* @param targetInfo - Information about the destroyed target.
|
|
728
738
|
*/
|
|
729
739
|
async removeTarget(err: Error | undefined, app: AppIdKey, targetInfo: TargetInfo): Promise<void> {
|
|
730
|
-
if (
|
|
740
|
+
if (targetInfo?.targetId == null) {
|
|
731
741
|
log.debug(`Received 'Target.targetDestroyed' event with no target. Skipping`);
|
|
732
742
|
return;
|
|
733
743
|
}
|
|
@@ -741,7 +751,7 @@ export class RpcClient {
|
|
|
741
751
|
|
|
742
752
|
// we do not know the page, so go through and find the existing target
|
|
743
753
|
const appTargetsMap = this.targets[app];
|
|
744
|
-
for (const [page, targetId] of
|
|
754
|
+
for (const [page, targetId] of Object.entries(appTargetsMap)) {
|
|
745
755
|
if (targetId === oldTargetId) {
|
|
746
756
|
log.debug(
|
|
747
757
|
`Found provisional target for app '${app}'. ` +
|
|
@@ -761,7 +771,7 @@ export class RpcClient {
|
|
|
761
771
|
|
|
762
772
|
// if there is no waiting provisional target, just get rid of the existing one
|
|
763
773
|
const targets = this.targets[app];
|
|
764
|
-
for (const [page, targetId] of
|
|
774
|
+
for (const [page, targetId] of Object.entries(targets)) {
|
|
765
775
|
if (targetId === targetInfo.targetId) {
|
|
766
776
|
delete targets[page];
|
|
767
777
|
return;
|
|
@@ -837,7 +847,8 @@ export class RpcClient {
|
|
|
837
847
|
}
|
|
838
848
|
await this.send('setSenderKey', sendOpts);
|
|
839
849
|
};
|
|
840
|
-
await
|
|
850
|
+
await withTimeout(
|
|
851
|
+
setupWebview(),
|
|
841
852
|
timeoutMs,
|
|
842
853
|
`Cannot set up page '${pageIdKey}' for app '${appIdKey}' within ${timeoutMs}ms`,
|
|
843
854
|
);
|
|
@@ -877,6 +888,123 @@ export class RpcClient {
|
|
|
877
888
|
});
|
|
878
889
|
}
|
|
879
890
|
|
|
891
|
+
/**
|
|
892
|
+
* Connects to a specific application and returns its page dictionary.
|
|
893
|
+
*
|
|
894
|
+
* @param appIdKey - The application identifier key to connect to.
|
|
895
|
+
* @returns A promise that resolves to a tuple containing the connected app ID key
|
|
896
|
+
* and the page dictionary.
|
|
897
|
+
* @throws Error if a new application connects during the process or if the page
|
|
898
|
+
* dictionary is empty.
|
|
899
|
+
*/
|
|
900
|
+
async selectApp(appIdKey: AppIdKey): Promise<[string, StringRecord]> {
|
|
901
|
+
return await new Promise<[string, StringRecord]>((resolve, reject) => {
|
|
902
|
+
// local callback, temporarily added as callback to
|
|
903
|
+
// `_rpc_applicationConnected:` remote debugger response
|
|
904
|
+
// to handle the initial connection
|
|
905
|
+
const onAppChange = (err: Error | null, dict: StringRecord) => {
|
|
906
|
+
if (err) {
|
|
907
|
+
return reject(err);
|
|
908
|
+
}
|
|
909
|
+
// from the dictionary returned, get the ids
|
|
910
|
+
const oldAppIdKey = dict.WIRHostApplicationIdentifierKey;
|
|
911
|
+
const correctAppIdKey = dict.WIRApplicationIdentifierKey;
|
|
912
|
+
|
|
913
|
+
// if this is a report of a proxy redirect from the remote debugger
|
|
914
|
+
// we want to update our dictionary and get a new app id
|
|
915
|
+
if (oldAppIdKey && correctAppIdKey !== oldAppIdKey) {
|
|
916
|
+
log.debug(
|
|
917
|
+
`We were notified we might have connected to the wrong app. ` +
|
|
918
|
+
`Using id ${correctAppIdKey} instead of ${oldAppIdKey}`,
|
|
919
|
+
);
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
reject(new Error(NEW_APP_CONNECTED_ERROR));
|
|
923
|
+
};
|
|
924
|
+
this.messageHandler.prependOnceListener('_rpc_applicationConnected:', onAppChange);
|
|
925
|
+
|
|
926
|
+
// do the actual connecting to the app
|
|
927
|
+
void (async () => {
|
|
928
|
+
try {
|
|
929
|
+
const [connectedAppIdKey, pageDict] = await this.send('connectToApp', {appIdKey});
|
|
930
|
+
// sometimes the connect logic happens, but with an empty dictionary
|
|
931
|
+
// which leads to the remote debugger getting disconnected, and into a loop
|
|
932
|
+
if (isEmpty(pageDict)) {
|
|
933
|
+
reject(new Error(EMPTY_PAGE_DICTIONARY_ERROR));
|
|
934
|
+
} else {
|
|
935
|
+
resolve([connectedAppIdKey, pageDict]);
|
|
936
|
+
}
|
|
937
|
+
} catch (err: any) {
|
|
938
|
+
log.warn(`Unable to connect to the app: ${err.message}`);
|
|
939
|
+
reject(err);
|
|
940
|
+
} finally {
|
|
941
|
+
this.messageHandler.off('_rpc_applicationConnected:', onAppChange);
|
|
942
|
+
}
|
|
943
|
+
})();
|
|
944
|
+
});
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
/**
|
|
948
|
+
* Handles execution context creation events by storing the context ID.
|
|
949
|
+
*
|
|
950
|
+
* @param err - Error if one occurred, undefined otherwise.
|
|
951
|
+
* @param context - The execution context information.
|
|
952
|
+
*/
|
|
953
|
+
onExecutionContextCreated(err: Error | undefined, context: {id: number}): void {
|
|
954
|
+
// { id: 2, isPageContext: true, name: '', frameId: '0.1' }
|
|
955
|
+
// right now we have no way to map contexts to apps/pages
|
|
956
|
+
// so just store
|
|
957
|
+
this.contexts.push(context.id);
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
/**
|
|
961
|
+
* Handles garbage collection events by logging them.
|
|
962
|
+
* Garbage collection can affect operation timing.
|
|
963
|
+
*/
|
|
964
|
+
onGarbageCollected(): void {
|
|
965
|
+
// just want to log that this is happening, as it can affect operation
|
|
966
|
+
log.debug(`Web Inspector garbage collected`);
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
/**
|
|
970
|
+
* Handles script parsing events by logging script information.
|
|
971
|
+
*
|
|
972
|
+
* @param err - Error if one occurred, undefined otherwise.
|
|
973
|
+
* @param scriptInfo - Information about the parsed script.
|
|
974
|
+
*/
|
|
975
|
+
onScriptParsed(err: Error | undefined, scriptInfo: StringRecord): void {
|
|
976
|
+
// { scriptId: '13', url: '', startLine: 0, startColumn: 0, endLine: 82, endColumn: 3 }
|
|
977
|
+
log.debug(`Script parsed: ${JSON.stringify(scriptInfo)}`);
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
/**
|
|
981
|
+
* Waits for a page to be initialized by acquiring locks on both the page
|
|
982
|
+
* target lock and the page selection lock.
|
|
983
|
+
*
|
|
984
|
+
* @param appIdKey - The application identifier key.
|
|
985
|
+
* @param pageIdKey - The page identifier key.
|
|
986
|
+
* @throws Error if no targets are found for the application.
|
|
987
|
+
*/
|
|
988
|
+
async waitForPage(appIdKey: AppIdKey, pageIdKey: PageIdKey): Promise<void> {
|
|
989
|
+
const appTargetsMap = this.targets[appIdKey];
|
|
990
|
+
if (!appTargetsMap) {
|
|
991
|
+
throw new Error(`No targets found for app '${appIdKey}'`);
|
|
992
|
+
}
|
|
993
|
+
const lock = appTargetsMap.lock;
|
|
994
|
+
const timer = new timing.Timer().start();
|
|
995
|
+
await Promise.all([
|
|
996
|
+
lock.acquire(pageIdKey, async () => await delay(0)),
|
|
997
|
+
this._pageSelectionLock.acquire(
|
|
998
|
+
toPageSelectionKey(appIdKey, pageIdKey),
|
|
999
|
+
async () => await delay(0),
|
|
1000
|
+
),
|
|
1001
|
+
]);
|
|
1002
|
+
const durationMs = timer.getDuration().asMilliSeconds;
|
|
1003
|
+
if (durationMs > 10) {
|
|
1004
|
+
log.debug(`Waited ${durationMs}ms until the page ${pageIdKey}@${appIdKey} is initialized`);
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
|
|
880
1008
|
/**
|
|
881
1009
|
* Initializes a page by enabling various Web Inspector domains.
|
|
882
1010
|
* Can perform either simple or full initialization based on configuration.
|
|
@@ -1028,95 +1156,6 @@ export class RpcClient {
|
|
|
1028
1156
|
return true;
|
|
1029
1157
|
}
|
|
1030
1158
|
|
|
1031
|
-
/**
|
|
1032
|
-
* Connects to a specific application and returns its page dictionary.
|
|
1033
|
-
*
|
|
1034
|
-
* @param appIdKey - The application identifier key to connect to.
|
|
1035
|
-
* @returns A promise that resolves to a tuple containing the connected app ID key
|
|
1036
|
-
* and the page dictionary.
|
|
1037
|
-
* @throws Error if a new application connects during the process or if the page
|
|
1038
|
-
* dictionary is empty.
|
|
1039
|
-
*/
|
|
1040
|
-
async selectApp(appIdKey: AppIdKey): Promise<[string, StringRecord]> {
|
|
1041
|
-
return await new B<[string, StringRecord]>((resolve, reject) => {
|
|
1042
|
-
// local callback, temporarily added as callback to
|
|
1043
|
-
// `_rpc_applicationConnected:` remote debugger response
|
|
1044
|
-
// to handle the initial connection
|
|
1045
|
-
const onAppChange = (err: Error | null, dict: StringRecord) => {
|
|
1046
|
-
if (err) {
|
|
1047
|
-
return reject(err);
|
|
1048
|
-
}
|
|
1049
|
-
// from the dictionary returned, get the ids
|
|
1050
|
-
const oldAppIdKey = dict.WIRHostApplicationIdentifierKey;
|
|
1051
|
-
const correctAppIdKey = dict.WIRApplicationIdentifierKey;
|
|
1052
|
-
|
|
1053
|
-
// if this is a report of a proxy redirect from the remote debugger
|
|
1054
|
-
// we want to update our dictionary and get a new app id
|
|
1055
|
-
if (oldAppIdKey && correctAppIdKey !== oldAppIdKey) {
|
|
1056
|
-
log.debug(
|
|
1057
|
-
`We were notified we might have connected to the wrong app. ` +
|
|
1058
|
-
`Using id ${correctAppIdKey} instead of ${oldAppIdKey}`,
|
|
1059
|
-
);
|
|
1060
|
-
}
|
|
1061
|
-
|
|
1062
|
-
reject(new Error(NEW_APP_CONNECTED_ERROR));
|
|
1063
|
-
};
|
|
1064
|
-
this.messageHandler.prependOnceListener('_rpc_applicationConnected:', onAppChange);
|
|
1065
|
-
|
|
1066
|
-
// do the actual connecting to the app
|
|
1067
|
-
(async () => {
|
|
1068
|
-
try {
|
|
1069
|
-
const [connectedAppIdKey, pageDict] = await this.send('connectToApp', {appIdKey});
|
|
1070
|
-
// sometimes the connect logic happens, but with an empty dictionary
|
|
1071
|
-
// which leads to the remote debugger getting disconnected, and into a loop
|
|
1072
|
-
if (_.isEmpty(pageDict)) {
|
|
1073
|
-
reject(new Error(EMPTY_PAGE_DICTIONARY_ERROR));
|
|
1074
|
-
} else {
|
|
1075
|
-
resolve([connectedAppIdKey, pageDict]);
|
|
1076
|
-
}
|
|
1077
|
-
} catch (err: any) {
|
|
1078
|
-
log.warn(`Unable to connect to the app: ${err.message}`);
|
|
1079
|
-
reject(err);
|
|
1080
|
-
} finally {
|
|
1081
|
-
this.messageHandler.off('_rpc_applicationConnected:', onAppChange);
|
|
1082
|
-
}
|
|
1083
|
-
})();
|
|
1084
|
-
});
|
|
1085
|
-
}
|
|
1086
|
-
|
|
1087
|
-
/**
|
|
1088
|
-
* Handles execution context creation events by storing the context ID.
|
|
1089
|
-
*
|
|
1090
|
-
* @param err - Error if one occurred, undefined otherwise.
|
|
1091
|
-
* @param context - The execution context information.
|
|
1092
|
-
*/
|
|
1093
|
-
onExecutionContextCreated(err: Error | undefined, context: {id: number}): void {
|
|
1094
|
-
// { id: 2, isPageContext: true, name: '', frameId: '0.1' }
|
|
1095
|
-
// right now we have no way to map contexts to apps/pages
|
|
1096
|
-
// so just store
|
|
1097
|
-
this.contexts.push(context.id);
|
|
1098
|
-
}
|
|
1099
|
-
|
|
1100
|
-
/**
|
|
1101
|
-
* Handles garbage collection events by logging them.
|
|
1102
|
-
* Garbage collection can affect operation timing.
|
|
1103
|
-
*/
|
|
1104
|
-
onGarbageCollected(): void {
|
|
1105
|
-
// just want to log that this is happening, as it can affect operation
|
|
1106
|
-
log.debug(`Web Inspector garbage collected`);
|
|
1107
|
-
}
|
|
1108
|
-
|
|
1109
|
-
/**
|
|
1110
|
-
* Handles script parsing events by logging script information.
|
|
1111
|
-
*
|
|
1112
|
-
* @param err - Error if one occurred, undefined otherwise.
|
|
1113
|
-
* @param scriptInfo - Information about the parsed script.
|
|
1114
|
-
*/
|
|
1115
|
-
onScriptParsed(err: Error | undefined, scriptInfo: StringRecord): void {
|
|
1116
|
-
// { scriptId: '13', url: '', startLine: 0, startColumn: 0, endLine: 82, endColumn: 3 }
|
|
1117
|
-
log.debug(`Script parsed: ${JSON.stringify(scriptInfo)}`);
|
|
1118
|
-
}
|
|
1119
|
-
|
|
1120
1159
|
/**
|
|
1121
1160
|
* Resumes a paused target.
|
|
1122
1161
|
*
|
|
@@ -1169,7 +1208,7 @@ export class RpcClient {
|
|
|
1169
1208
|
100,
|
|
1170
1209
|
Math.trunc((pageReadinessDetector.timeoutMs - timer.getDuration().asMilliSeconds) * 0.8),
|
|
1171
1210
|
);
|
|
1172
|
-
const rawResult = await
|
|
1211
|
+
const rawResult = await withTimeout(
|
|
1173
1212
|
this.send('Runtime.evaluate', {
|
|
1174
1213
|
expression: 'document.readyState;',
|
|
1175
1214
|
returnByValue: true,
|
|
@@ -1177,7 +1216,8 @@ export class RpcClient {
|
|
|
1177
1216
|
pageIdKey,
|
|
1178
1217
|
targetId,
|
|
1179
1218
|
}),
|
|
1180
|
-
|
|
1219
|
+
commandTimeoutMs,
|
|
1220
|
+
);
|
|
1181
1221
|
readyState = convertJavascriptEvaluationResult(rawResult);
|
|
1182
1222
|
} catch (e: any) {
|
|
1183
1223
|
log.debug(`Cannot determine page readiness: ${e.message}`);
|
|
@@ -1190,7 +1230,7 @@ export class RpcClient {
|
|
|
1190
1230
|
);
|
|
1191
1231
|
return;
|
|
1192
1232
|
}
|
|
1193
|
-
await
|
|
1233
|
+
await delay(100);
|
|
1194
1234
|
}
|
|
1195
1235
|
log.warn(
|
|
1196
1236
|
`Page '${pageIdKey}' for app '${appIdKey}' is not ready after ` +
|
|
@@ -1198,34 +1238,6 @@ export class RpcClient {
|
|
|
1198
1238
|
);
|
|
1199
1239
|
}
|
|
1200
1240
|
|
|
1201
|
-
/**
|
|
1202
|
-
* Waits for a page to be initialized by acquiring locks on both the page
|
|
1203
|
-
* target lock and the page selection lock.
|
|
1204
|
-
*
|
|
1205
|
-
* @param appIdKey - The application identifier key.
|
|
1206
|
-
* @param pageIdKey - The page identifier key.
|
|
1207
|
-
* @throws Error if no targets are found for the application.
|
|
1208
|
-
*/
|
|
1209
|
-
async waitForPage(appIdKey: AppIdKey, pageIdKey: PageIdKey): Promise<void> {
|
|
1210
|
-
const appTargetsMap = this.targets[appIdKey];
|
|
1211
|
-
if (!appTargetsMap) {
|
|
1212
|
-
throw new Error(`No targets found for app '${appIdKey}'`);
|
|
1213
|
-
}
|
|
1214
|
-
const lock = appTargetsMap.lock;
|
|
1215
|
-
const timer = new timing.Timer().start();
|
|
1216
|
-
await Promise.all([
|
|
1217
|
-
lock.acquire(pageIdKey, async () => await B.delay(0)),
|
|
1218
|
-
this._pageSelectionLock.acquire(
|
|
1219
|
-
toPageSelectionKey(appIdKey, pageIdKey),
|
|
1220
|
-
async () => await B.delay(0),
|
|
1221
|
-
),
|
|
1222
|
-
]);
|
|
1223
|
-
const durationMs = timer.getDuration().asMilliSeconds;
|
|
1224
|
-
if (durationMs > 10) {
|
|
1225
|
-
log.debug(`Waited ${durationMs}ms until the page ${pageIdKey}@${appIdKey} is initialized`);
|
|
1226
|
-
}
|
|
1227
|
-
}
|
|
1228
|
-
|
|
1229
1241
|
/**
|
|
1230
1242
|
* Gets the pending target details if there is a pending request for the given app.
|
|
1231
1243
|
* Filters out non-page target types (e.g., 'frame').
|