appium-remote-debugger 15.7.3 → 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.
Files changed (70) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/build/lib/atoms.d.ts.map +1 -1
  3. package/build/lib/atoms.js +27 -28
  4. package/build/lib/atoms.js.map +1 -1
  5. package/build/lib/index.d.ts.map +1 -1
  6. package/build/lib/index.js +7 -0
  7. package/build/lib/index.js.map +1 -1
  8. package/build/lib/mixins/connect.d.ts.map +1 -1
  9. package/build/lib/mixins/connect.js +21 -25
  10. package/build/lib/mixins/connect.js.map +1 -1
  11. package/build/lib/mixins/execute.d.ts.map +1 -1
  12. package/build/lib/mixins/execute.js +2 -8
  13. package/build/lib/mixins/execute.js.map +1 -1
  14. package/build/lib/mixins/message-handlers.d.ts.map +1 -1
  15. package/build/lib/mixins/message-handlers.js +8 -12
  16. package/build/lib/mixins/message-handlers.js.map +1 -1
  17. package/build/lib/mixins/misc.d.ts.map +1 -1
  18. package/build/lib/mixins/misc.js +5 -39
  19. package/build/lib/mixins/misc.js.map +1 -1
  20. package/build/lib/mixins/navigate.d.ts.map +1 -1
  21. package/build/lib/mixins/navigate.js +20 -55
  22. package/build/lib/mixins/navigate.js.map +1 -1
  23. package/build/lib/mixins/property-accessors.d.ts +24 -0
  24. package/build/lib/mixins/property-accessors.d.ts.map +1 -1
  25. package/build/lib/mixins/property-accessors.js +24 -0
  26. package/build/lib/mixins/property-accessors.js.map +1 -1
  27. package/build/lib/remote-debugger.d.ts +38 -38
  28. package/build/lib/remote-debugger.d.ts.map +1 -1
  29. package/build/lib/remote-debugger.js +64 -69
  30. package/build/lib/remote-debugger.js.map +1 -1
  31. package/build/lib/rpc/remote-messages.d.ts.map +1 -1
  32. package/build/lib/rpc/remote-messages.js +7 -8
  33. package/build/lib/rpc/remote-messages.js.map +1 -1
  34. package/build/lib/rpc/rpc-client-real-device-shim.d.ts.map +1 -1
  35. package/build/lib/rpc/rpc-client-real-device-shim.js +3 -6
  36. package/build/lib/rpc/rpc-client-real-device-shim.js.map +1 -1
  37. package/build/lib/rpc/rpc-client-simulator.d.ts.map +1 -1
  38. package/build/lib/rpc/rpc-client-simulator.js +3 -5
  39. package/build/lib/rpc/rpc-client-simulator.js.map +1 -1
  40. package/build/lib/rpc/rpc-client.d.ts +27 -27
  41. package/build/lib/rpc/rpc-client.d.ts.map +1 -1
  42. package/build/lib/rpc/rpc-client.js +226 -224
  43. package/build/lib/rpc/rpc-client.js.map +1 -1
  44. package/build/lib/rpc/rpc-message-handler.js +7 -10
  45. package/build/lib/rpc/rpc-message-handler.js.map +1 -1
  46. package/build/lib/types.d.ts +19 -19
  47. package/build/lib/types.d.ts.map +1 -1
  48. package/build/lib/utils.d.ts +70 -4
  49. package/build/lib/utils.d.ts.map +1 -1
  50. package/build/lib/utils.js +171 -23
  51. package/build/lib/utils.js.map +1 -1
  52. package/lib/atoms.ts +31 -32
  53. package/lib/index.ts +7 -0
  54. package/lib/mixins/connect.ts +22 -23
  55. package/lib/mixins/execute.ts +3 -5
  56. package/lib/mixins/message-handlers.ts +9 -10
  57. package/lib/mixins/misc.ts +8 -7
  58. package/lib/mixins/navigate.ts +58 -63
  59. package/lib/mixins/property-accessors.ts +24 -0
  60. package/lib/remote-debugger.ts +74 -76
  61. package/lib/rpc/remote-messages.ts +10 -5
  62. package/lib/rpc/rpc-client-real-device-shim.ts +3 -3
  63. package/lib/rpc/rpc-client-simulator.ts +3 -5
  64. package/lib/rpc/rpc-client.ts +259 -247
  65. package/lib/rpc/rpc-message-handler.ts +7 -7
  66. package/lib/types.ts +24 -24
  67. package/lib/utils.ts +181 -23
  68. package/package.json +3 -7
  69. package/scripts/common.mjs +42 -37
  70. 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 = { length: 200 };
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 !lodash_1.default.isEmpty(target);
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 bluebird_1.default(async (resolve, reject) => {
302
- // promise to be resolved whenever remote debugger
303
- // replies to our request
304
- // keep track of the messages coming and going using a simple sequential id
305
- const msgId = this.msgId++;
306
- // for target-base communication, everything is wrapped up
307
- const wrapperMsgId = this.msgId++;
308
- // acknowledge wrapper message
309
- this.messageHandler.on(wrapperMsgId.toString(), function (err) {
310
- if (err) {
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
- else if (this.messageHandler.listenerCount(cmd.__selector)) {
365
- this.messageHandler.prependOnceListener(cmd.__selector, (err, ...args) => {
366
- if (err) {
367
- return reject(err);
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
- logger_1.log.debug(`Received response from send (id: ${msgId}): '${lodash_1.default.truncate(JSON.stringify(args), DATA_LOG_LENGTH)}'`);
370
- resolve(args);
371
- });
372
- }
373
- else if (hasSocketData) {
374
- this.messageHandler.once(msgId.toString(), (err, value) => {
375
- if (err) {
376
- return reject(new Error(`Remote debugger error with code '${err.code}': ${err.message}`));
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
- catch (err) {
401
- return reject(err);
402
- }
401
+ catch (err) {
402
+ return reject(err);
403
+ }
404
+ })();
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 (lodash_1.default.isNil(targetInfo?.targetId)) {
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 (!lodash_1.default.isPlainObject(this.targets[appIdKey])) {
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 (lodash_1.default.has(this.targets[appIdKey], pageIdKey)) {
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 (lodash_1.default.isNil(targetInfo?.targetId)) {
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 lodash_1.default.toPairs(appTargetsMap)) {
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 lodash_1.default.toPairs(targets)) {
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 bluebird_1.default.resolve(setupWebview()).timeout(timeoutMs, `Cannot set up page '${pageIdKey}' for app '${appIdKey}' within ${timeoutMs}ms`);
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 bluebird_1.default.resolve(this.send('Runtime.evaluate', {
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
- })).timeout(commandTimeoutMs);
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 bluebird_1.default.delay(100);
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').