appium-remote-debugger 12.2.10 → 13.1.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 (71) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/build/lib/mixins/connect.d.ts.map +1 -1
  3. package/build/lib/mixins/connect.js +32 -37
  4. package/build/lib/mixins/connect.js.map +1 -1
  5. package/build/lib/mixins/execute.d.ts.map +1 -1
  6. package/build/lib/mixins/execute.js +14 -12
  7. package/build/lib/mixins/execute.js.map +1 -1
  8. package/build/lib/mixins/misc.d.ts +7 -0
  9. package/build/lib/mixins/misc.d.ts.map +1 -1
  10. package/build/lib/mixins/misc.js +21 -0
  11. package/build/lib/mixins/misc.js.map +1 -1
  12. package/build/lib/mixins/navigate.d.ts.map +1 -1
  13. package/build/lib/mixins/navigate.js +9 -96
  14. package/build/lib/mixins/navigate.js.map +1 -1
  15. package/build/lib/protocol/index.d.ts +3 -1
  16. package/build/lib/protocol/index.d.ts.map +1 -1
  17. package/build/lib/protocol/index.js +22 -23
  18. package/build/lib/protocol/index.js.map +1 -1
  19. package/build/lib/remote-debugger.d.ts +1 -0
  20. package/build/lib/remote-debugger.d.ts.map +1 -1
  21. package/build/lib/remote-debugger.js +1 -0
  22. package/build/lib/remote-debugger.js.map +1 -1
  23. package/build/lib/rpc/index.d.ts +1 -2
  24. package/build/lib/rpc/index.d.ts.map +1 -1
  25. package/build/lib/rpc/index.js +1 -36
  26. package/build/lib/rpc/index.js.map +1 -1
  27. package/build/lib/rpc/remote-messages.d.ts +24 -18
  28. package/build/lib/rpc/remote-messages.d.ts.map +1 -1
  29. package/build/lib/rpc/remote-messages.js +24 -11
  30. package/build/lib/rpc/remote-messages.js.map +1 -1
  31. package/build/lib/rpc/rpc-client-real-device.d.ts.map +1 -1
  32. package/build/lib/rpc/rpc-client-real-device.js +1 -3
  33. package/build/lib/rpc/rpc-client-real-device.js.map +1 -1
  34. package/build/lib/rpc/rpc-client-simulator.d.ts.map +1 -1
  35. package/build/lib/rpc/rpc-client-simulator.js +1 -3
  36. package/build/lib/rpc/rpc-client-simulator.js.map +1 -1
  37. package/build/lib/rpc/rpc-client.d.ts +63 -44
  38. package/build/lib/rpc/rpc-client.d.ts.map +1 -1
  39. package/build/lib/rpc/rpc-client.js +209 -122
  40. package/build/lib/rpc/rpc-client.js.map +1 -1
  41. package/build/lib/rpc/rpc-message-handler.d.ts +8 -5
  42. package/build/lib/rpc/rpc-message-handler.d.ts.map +1 -1
  43. package/build/lib/rpc/rpc-message-handler.js +61 -75
  44. package/build/lib/rpc/rpc-message-handler.js.map +1 -1
  45. package/build/lib/types.d.ts +28 -3
  46. package/build/lib/types.d.ts.map +1 -1
  47. package/build/lib/utils.d.ts +4 -3
  48. package/build/lib/utils.d.ts.map +1 -1
  49. package/build/lib/utils.js +4 -2
  50. package/build/lib/utils.js.map +1 -1
  51. package/build/tsconfig.tsbuildinfo +1 -1
  52. package/lib/mixins/connect.js +34 -38
  53. package/lib/mixins/execute.js +15 -12
  54. package/lib/mixins/misc.js +22 -0
  55. package/lib/mixins/navigate.js +11 -99
  56. package/lib/protocol/index.js +22 -24
  57. package/lib/remote-debugger.ts +1 -0
  58. package/lib/rpc/index.js +1 -3
  59. package/lib/rpc/remote-messages.js +28 -11
  60. package/lib/rpc/rpc-client-real-device.js +1 -3
  61. package/lib/rpc/rpc-client-simulator.js +1 -3
  62. package/lib/rpc/rpc-client.js +244 -133
  63. package/lib/rpc/rpc-message-handler.js +62 -74
  64. package/lib/types.ts +33 -3
  65. package/lib/utils.js +4 -2
  66. package/package.json +2 -1
  67. package/build/lib/rpc/constants.d.ts +0 -2
  68. package/build/lib/rpc/constants.d.ts.map +0 -1
  69. package/build/lib/rpc/constants.js +0 -5
  70. package/build/lib/rpc/constants.js.map +0 -1
  71. package/lib/rpc/constants.js +0 -1
@@ -3,7 +3,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.RpcClient = void 0;
6
+ exports.RpcClient = exports.EMPTY_PAGE_DICTIONARY_ERROR = exports.NEW_APP_CONNECTED_ERROR = void 0;
7
+ exports.toLockKey = toLockKey;
7
8
  const remote_messages_1 = require("./remote-messages");
8
9
  const asyncbox_1 = require("asyncbox");
9
10
  const logger_1 = __importDefault(require("../logger"));
@@ -12,7 +13,7 @@ const bluebird_1 = __importDefault(require("bluebird"));
12
13
  const rpc_message_handler_1 = __importDefault(require("./rpc-message-handler"));
13
14
  const support_1 = require("@appium/support");
14
15
  const node_events_1 = require("node:events");
15
- const constants_1 = require("./constants");
16
+ const async_lock_1 = __importDefault(require("async-lock"));
16
17
  const DATA_LOG_LENGTH = { length: 200 };
17
18
  const WAIT_FOR_TARGET_TIMEOUT_MS = 10000;
18
19
  const WAIT_FOR_TARGET_INTERVAL_MS = 100;
@@ -25,6 +26,8 @@ const NO_TARGET_PRESENT_YET_ERRORS = [
25
26
  `some arguments of method`,
26
27
  `missing target`,
27
28
  ];
29
+ exports.NEW_APP_CONNECTED_ERROR = 'New application has connected';
30
+ exports.EMPTY_PAGE_DICTIONARY_ERROR = 'Empty page dictionary received';
28
31
  /**
29
32
  * @param {boolean} isSafari
30
33
  * @param {string} platformVersion
@@ -63,7 +66,6 @@ class RpcClient {
63
66
  this._targets = {};
64
67
  this._targetSubscriptions = new node_events_1.EventEmitter();
65
68
  // start with a best guess for the protocol
66
- this._shouldCheckForTarget = !!opts.shouldCheckForTarget;
67
69
  this.isTargetBased = platformVersion ? isTargetBased(isSafari, platformVersion) : true;
68
70
  }
69
71
  /**
@@ -76,23 +78,14 @@ class RpcClient {
76
78
  * @returns {boolean}
77
79
  */
78
80
  get needsTarget() {
79
- return this.shouldCheckForTarget && this.isTargetBased;
81
+ return this.isTargetBased;
80
82
  }
81
83
  /**
82
- * @returns {import('@appium/types').StringRecord}
84
+ * @returns {AppToTargetsMap}
83
85
  */
84
86
  get targets() {
85
87
  return this._targets;
86
88
  }
87
- /**
88
- * @returns {boolean}
89
- */
90
- get shouldCheckForTarget() {
91
- return this._shouldCheckForTarget;
92
- }
93
- set shouldCheckForTarget(shouldCheckForTarget) {
94
- this._shouldCheckForTarget = !!shouldCheckForTarget;
95
- }
96
89
  /**
97
90
  * @returns {boolean}
98
91
  */
@@ -178,47 +171,51 @@ class RpcClient {
178
171
  *
179
172
  * @param {import('../types').AppIdKey} appIdKey
180
173
  * @param {import('../types').PageIdKey} pageIdKey
181
- * @returns {Promise<void>}
174
+ * @returns {Promise<import('../types').TargetId | undefined>}
182
175
  */
183
176
  async waitForTarget(appIdKey, pageIdKey) {
184
177
  if (!this.needsTarget) {
185
178
  logger_1.default.debug(`Target-based communication is not needed, skipping wait for target`);
186
179
  return;
187
180
  }
188
- const target = this.getTarget(appIdKey, pageIdKey);
181
+ let target = this.getTarget(appIdKey, pageIdKey);
189
182
  if (target) {
190
183
  logger_1.default.debug(`The target '${target}' for app '${appIdKey}' and page '${pageIdKey}' already exists, no need to wait`);
191
- return;
184
+ return target;
192
185
  }
193
186
  // otherwise waiting is necessary to see what the target is
194
187
  try {
195
- await (0, asyncbox_1.waitForCondition)(() => !lodash_1.default.isEmpty(this.getTarget(appIdKey, pageIdKey)), {
188
+ await (0, asyncbox_1.waitForCondition)(() => {
189
+ target = this.getTarget(appIdKey, pageIdKey);
190
+ return !lodash_1.default.isEmpty(target);
191
+ }, {
196
192
  waitMs: WAIT_FOR_TARGET_TIMEOUT_MS,
197
193
  intervalMs: WAIT_FOR_TARGET_INTERVAL_MS,
198
- error: 'No targets found, unable to communicate with device',
199
194
  });
195
+ return target;
200
196
  }
201
197
  catch (err) {
202
198
  if (!err.message.includes('Condition unmet')) {
203
199
  throw err;
204
200
  }
205
- throw new Error('No targets found, unable to communicate with device');
201
+ throw new Error(`No targets could be matched for the app '${appIdKey}' and page '${pageIdKey}' after ` +
202
+ `${WAIT_FOR_TARGET_TIMEOUT_MS}ms. This is likely a bug.`);
206
203
  }
207
204
  }
208
205
  /**
209
206
  *
210
207
  * @param {string} command
211
208
  * @param {import('../types').RemoteCommandOpts} opts
212
- * @param {boolean} [waitForResponse]
209
+ * @param {boolean} [waitForResponse=true]
213
210
  * @returns {Promise<any>}
214
211
  */
215
212
  async send(command, opts, waitForResponse = true) {
216
213
  const timer = new support_1.timing.Timer().start();
217
- const { appIdKey, pageIdKey } = opts;
218
214
  try {
219
215
  return await this.sendToDevice(command, opts, waitForResponse);
220
216
  }
221
217
  catch (err) {
218
+ const { appIdKey, pageIdKey } = opts;
222
219
  const messageLc = (err.message || '').toLowerCase();
223
220
  if (messageLc.includes(NO_TARGET_SUPPORTED_ERROR)) {
224
221
  logger_1.default.info('The target device does not support Target based communication. ' +
@@ -239,11 +236,13 @@ class RpcClient {
239
236
  }
240
237
  /**
241
238
  *
239
+ * @template {boolean} TWaitForResponse
242
240
  * @param {string} command
243
241
  * @param {import('../types').RemoteCommandOpts} opts
244
- * @param {boolean} [waitForResponse]
245
- * @returns {Promise<any>}
242
+ * @param {TWaitForResponse} [waitForResponse=true]
243
+ * @returns {Promise<TWaitForResponse extends true ? import('../types').RemoteCommandOpts : any>}
246
244
  */
245
+ // @ts-ignore Compiler issue
247
246
  async sendToDevice(command, opts, waitForResponse = true) {
248
247
  return await new bluebird_1.default(async (resolve, reject) => {
249
248
  // promise to be resolved whenever remote debugger
@@ -264,23 +263,39 @@ class RpcClient {
264
263
  }
265
264
  const appIdKey = opts.appIdKey;
266
265
  const pageIdKey = opts.pageIdKey;
267
- const targetId = this.getTarget(appIdKey, pageIdKey);
266
+ const targetId = opts.targetId ?? this.getTarget(appIdKey, pageIdKey);
268
267
  // retrieve the correct command to send
268
+ /** @type {import('../types').RemoteCommandOpts} */
269
269
  const fullOpts = lodash_1.default.defaults({
270
270
  connId: this.connId,
271
271
  senderId: this.senderId,
272
272
  targetId,
273
273
  id: msgId,
274
274
  }, opts);
275
- // @ts-ignore remoteMessages must be defined
276
- const cmd = this.remoteMessages.getRemoteCommand(command, fullOpts);
277
- if (cmd?.__argument?.WIRSocketDataKey) {
275
+ /** @type {import('../types').RawRemoteCommand} */
276
+ let cmd;
277
+ try {
278
+ // @ts-ignore remoteMessages must be defined
279
+ cmd = this.remoteMessages.getRemoteCommand(command, fullOpts);
280
+ }
281
+ catch (err) {
282
+ logger_1.default.error(err);
283
+ return reject(err);
284
+ }
285
+ /** @type {import('../types').RemoteCommand} */
286
+ const finalCommand = {
287
+ __argument: lodash_1.default.omit(cmd.__argument, ['WIRSocketDataKey']),
288
+ __selector: cmd.__selector,
289
+ };
290
+ const hasSocketData = lodash_1.default.isPlainObject(cmd.__argument?.WIRSocketDataKey);
291
+ if (hasSocketData) {
278
292
  // make sure the message being sent has all the information that is needed
293
+ // @ts-ignore We have asserted it's a plain object above
279
294
  if (lodash_1.default.isNil(cmd.__argument.WIRSocketDataKey.id)) {
295
+ // @ts-ignore We have already asserted it's a plain object above
280
296
  cmd.__argument.WIRSocketDataKey.id = wrapperMsgId;
281
297
  }
282
- cmd.__argument.WIRSocketDataKey =
283
- Buffer.from(JSON.stringify(cmd.__argument.WIRSocketDataKey));
298
+ finalCommand.__argument.WIRSocketDataKey = Buffer.from(JSON.stringify(cmd.__argument.WIRSocketDataKey));
284
299
  }
285
300
  let messageHandled = true;
286
301
  if (!waitForResponse) {
@@ -292,7 +307,8 @@ class RpcClient {
292
307
  if (err) {
293
308
  // we are not waiting for this, and if it errors it is most likely
294
309
  // a protocol change. Log and check during testing
295
- logger_1.default.error(`Received error from send that is not being waited for (id: ${msgId}): '${lodash_1.default.truncate(JSON.stringify(err), DATA_LOG_LENGTH)}'`);
310
+ logger_1.default.error(`Received error from send that is not being waited for (id: ${msgId}): ` +
311
+ lodash_1.default.truncate(JSON.stringify(err), DATA_LOG_LENGTH));
296
312
  // reject, though it is very rare that this will be triggered, since
297
313
  // the promise is resolved directlty after send. On the off chance,
298
314
  // though, it will alert of a protocol change.
@@ -308,10 +324,11 @@ class RpcClient {
308
324
  return reject(err);
309
325
  }
310
326
  logger_1.default.debug(`Received response from send (id: ${msgId}): '${lodash_1.default.truncate(JSON.stringify(args), DATA_LOG_LENGTH)}'`);
327
+ // @ts-ignore This is ok
311
328
  resolve(args);
312
329
  });
313
330
  }
314
- else if (cmd?.__argument?.WIRSocketDataKey) {
331
+ else if (hasSocketData) {
315
332
  // @ts-ignore messageHandler must be defined
316
333
  this.messageHandler.once(msgId.toString(), function (err, value) {
317
334
  if (err) {
@@ -332,11 +349,12 @@ class RpcClient {
332
349
  ` (id: ${msgId}): '${command}'`;
333
350
  logger_1.default.debug(msg);
334
351
  try {
335
- const res = await this.sendMessage(cmd);
352
+ await this.sendMessage(finalCommand);
336
353
  if (!messageHandled) {
337
354
  // There are no handlers waiting for a response before resolving,
338
355
  // and no errors sending the message over the socket, so resolve
339
- resolve(res);
356
+ // @ts-ignore This is ok
357
+ resolve(fullOpts);
340
358
  }
341
359
  }
342
360
  catch (err) {
@@ -368,61 +386,99 @@ class RpcClient {
368
386
  }
369
387
  /**
370
388
  *
371
- * @param {Error?} err
372
- * @param {string} app
373
- * @param {Record<string, any>} targetInfo
374
- * @returns {void}
389
+ * @param {Error | undefined} err
390
+ * @param {import('../types').AppIdKey} app
391
+ * @param {import('../types').TargetInfo} targetInfo
392
+ * @returns {Promise<void>}
375
393
  */
376
- addTarget(err, app, targetInfo) {
394
+ async addTarget(err, app, targetInfo) {
377
395
  if (lodash_1.default.isNil(targetInfo?.targetId)) {
378
- logger_1.default.warn(`Received 'Target.targetCreated' event for app '${app}' with no target. Skipping`);
396
+ logger_1.default.info(`Received 'Target.targetCreated' event for app '${app}' with no target. Skipping`);
397
+ return;
398
+ }
399
+ if (!this._pendingTargetNotification) {
400
+ logger_1.default.info(`Received 'Target.targetCreated' event for app '${app}' with no pending request: ${JSON.stringify(targetInfo)}`);
379
401
  return;
380
402
  }
381
- if (lodash_1.default.isEmpty(this.pendingTargetNotification) && !targetInfo.isProvisional) {
382
- logger_1.default.warn(`Received 'Target.targetCreated' event for app '${app}' with no pending request: ${JSON.stringify(targetInfo)}`);
403
+ const [appIdKey, pageIdKey] = this._pendingTargetNotification;
404
+ if (appIdKey !== app) {
405
+ logger_1.default.info(`Received 'Target.targetCreated' event for app '${app}' with no pending request: ${JSON.stringify(targetInfo)}`);
383
406
  return;
384
407
  }
385
408
  if (targetInfo.isProvisional) {
386
- logger_1.default.debug(`Provisional target created for app '${app}', '${targetInfo.targetId}'. Ignoring until target update event`);
409
+ logger_1.default.debug(`Provisional target created for app '${appIdKey}' and page '${pageIdKey}': '${targetInfo.targetId}'`);
410
+ if (!targetInfo.isPaused) {
411
+ return;
412
+ }
413
+ logger_1.default.debug(`The target ${targetInfo.targetId}@${appIdKey} is paused`);
414
+ const appTargetsMap = this.targets[appIdKey];
415
+ if (appTargetsMap) {
416
+ await appTargetsMap.lock.acquire(toLockKey(appIdKey, pageIdKey), async () => {
417
+ try {
418
+ await this._initializePage(appIdKey, pageIdKey, targetInfo.targetId);
419
+ }
420
+ finally {
421
+ await this._resumeTarget(appIdKey, pageIdKey, targetInfo.targetId);
422
+ }
423
+ });
424
+ }
387
425
  return;
388
426
  }
389
- // @ts-ignore this.pendingTargetNotification must be defined here
390
- const [appIdKey, pageIdKey] = this.pendingTargetNotification;
391
427
  logger_1.default.debug(`Target created for app '${appIdKey}' and page '${pageIdKey}': ${JSON.stringify(targetInfo)}`);
392
428
  if (lodash_1.default.has(this.targets[appIdKey], pageIdKey)) {
393
429
  logger_1.default.debug(`There is already a target for this app and page ('${this.targets[appIdKey][pageIdKey]}'). This might cause problems`);
394
430
  }
395
- this.targets[app] = this.targets[app] || {};
431
+ const lock = new async_lock_1.default();
432
+ this.targets[app] = this.targets[app] || { lock };
396
433
  this.targets[appIdKey][pageIdKey] = targetInfo.targetId;
434
+ await lock.acquire(toLockKey(appIdKey, pageIdKey), async () => {
435
+ try {
436
+ await this.send('Target.setPauseOnStart', {
437
+ pauseOnStart: true,
438
+ appIdKey,
439
+ pageIdKey,
440
+ });
441
+ }
442
+ catch { }
443
+ try {
444
+ await this._initializePage(appIdKey, pageIdKey);
445
+ }
446
+ finally {
447
+ if (targetInfo.isPaused) {
448
+ await this._resumeTarget(appIdKey, pageIdKey);
449
+ }
450
+ }
451
+ });
397
452
  }
398
453
  /**
399
454
  *
400
- * @param {Error?} err
401
- * @param {string} app
402
- * @param {string} oldTargetId
403
- * @param {string} newTargetId
404
- * @returns {void}
455
+ * @param {Error | undefined} err
456
+ * @param {import('../types').AppIdKey} app
457
+ * @param {import('../types').ProvisionalTargetInfo} targetInfo
458
+ * @returns {Promise<void>}
405
459
  */
406
- updateTarget(err, app, oldTargetId, newTargetId) {
460
+ async updateTarget(err, app, targetInfo) {
461
+ const { oldTargetId, newTargetId, } = targetInfo;
407
462
  logger_1.default.debug(`Target updated for app '${app}'. Old target: '${oldTargetId}', new target: '${newTargetId}'`);
408
- if (!this.targets[app]) {
463
+ const appTargetsMap = this.targets[app];
464
+ if (!appTargetsMap) {
409
465
  logger_1.default.warn(`No existing target for app '${app}'. Not sure what to do`);
410
466
  return;
411
467
  }
412
468
  // save this, to be used if/when the existing target is destroyed
413
- this.targets[app].provisional = {
469
+ appTargetsMap.provisional = {
414
470
  oldTargetId,
415
471
  newTargetId,
416
472
  };
417
473
  }
418
474
  /**
419
475
  *
420
- * @param {Error?} err
421
- * @param {string} app
422
- * @param {Record<string, any>} targetInfo
423
- * @returns {void}
476
+ * @param {Error | undefined} err
477
+ * @param {import('../types').AppIdKey} app
478
+ * @param {import('../types').TargetInfo} targetInfo
479
+ * @returns {Promise<void>}
424
480
  */
425
- removeTarget(err, app, targetInfo) {
481
+ async removeTarget(err, app, targetInfo) {
426
482
  if (lodash_1.default.isNil(targetInfo?.targetId)) {
427
483
  logger_1.default.debug(`Received 'Target.targetDestroyed' event with no target. Skipping`);
428
484
  return;
@@ -433,30 +489,17 @@ class RpcClient {
433
489
  const { oldTargetId, newTargetId } = this.targets[app].provisional;
434
490
  delete this.targets[app].provisional;
435
491
  // we do not know the page, so go through and find the existing target
436
- const targets = this.targets[app];
437
- for (const [page, targetId] of lodash_1.default.toPairs(targets)) {
492
+ const appTargetsMap = this.targets[app];
493
+ for (const [page, targetId] of lodash_1.default.toPairs(appTargetsMap)) {
438
494
  if (targetId === oldTargetId) {
439
- logger_1.default.debug(`Found provisional target for app '${app}'. Old target: '${oldTargetId}', new target: '${newTargetId}'. Updating`);
440
- targets[page] = newTargetId;
441
- const opts = { appIdKey: app, pageIdKey: parseInt(page, 10) };
442
- (async () => {
443
- if (this.fullPageInitialization) {
444
- await this.initializePageFull(opts.appIdKey, opts.pageIdKey);
445
- }
446
- else {
447
- await this.initializePage(opts.appIdKey, opts.pageIdKey);
448
- }
449
- this._targetSubscriptions.emit(constants_1.ON_TARGET_PROVISIONED_EVENT, {
450
- ...opts,
451
- oldTargetId,
452
- targetId: newTargetId,
453
- });
454
- })();
495
+ logger_1.default.debug(`Found provisional target for app '${app}'. ` +
496
+ `Old target: '${oldTargetId}', new target: '${newTargetId}'. Updating`);
497
+ appTargetsMap[page] = newTargetId;
455
498
  return;
456
499
  }
457
500
  }
458
501
  logger_1.default.warn(`Provisional target for app '${app}' found, but no suitable existing target found. This may cause problems`);
459
- logger_1.default.warn(`Old target: '${oldTargetId}', new target: '${newTargetId}'. Existing targets: ${JSON.stringify(targets)}`);
502
+ logger_1.default.warn(`Old target: '${oldTargetId}', new target: '${newTargetId}'. Existing targets: ${JSON.stringify(appTargetsMap)}`);
460
503
  }
461
504
  // if there is no waiting provisional target, just get rid of the existing one
462
505
  const targets = this.targets[app];
@@ -485,9 +528,7 @@ class RpcClient {
485
528
  * @returns {Promise<void>}
486
529
  */
487
530
  async selectPage(appIdKey, pageIdKey) {
488
- /** @type {[import('../types').AppIdKey, import('../types').PageIdKey]} */
489
- this.pendingTargetNotification = [appIdKey, pageIdKey];
490
- this.shouldCheckForTarget = false;
531
+ this._pendingTargetNotification = [appIdKey, pageIdKey];
491
532
  // go through the steps that the Desktop Safari system
492
533
  // goes through to initialize the Web Inspector session
493
534
  const sendOpts = {
@@ -502,47 +543,15 @@ class RpcClient {
502
543
  }
503
544
  await this.send('setSenderKey', sendOpts);
504
545
  logger_1.default.debug('Sender key set');
546
+ if (!this.isTargetBased) {
547
+ await this._initializePage(appIdKey, pageIdKey);
548
+ return;
549
+ }
505
550
  if (this.isTargetBased && support_1.util.compareVersions(this.platformVersion, '<', MIN_PLATFORM_NO_TARGET_EXISTS)) {
506
551
  await this.send('Target.exists', sendOpts, false);
507
552
  }
508
- this.shouldCheckForTarget = true;
509
553
  await this.waitForTarget(appIdKey, pageIdKey);
510
- if (this.fullPageInitialization) {
511
- await this.initializePageFull(appIdKey, pageIdKey);
512
- }
513
- else {
514
- await this.initializePage(appIdKey, pageIdKey);
515
- }
516
- }
517
- /**
518
- * Perform the minimal initialization to get the Web Inspector working
519
- * @param {import('../types').AppIdKey} appIdKey
520
- * @param {import('../types').PageIdKey} pageIdKey
521
- * @returns {Promise<void>}
522
- */
523
- async initializePage(appIdKey, pageIdKey) {
524
- const sendOpts = {
525
- appIdKey,
526
- pageIdKey,
527
- };
528
- // The sequence of domains is important
529
- for (const domain of [
530
- 'Inspector.enable',
531
- 'Page.enable',
532
- 'Runtime.enable',
533
- 'Network.enable',
534
- 'Heap.enable',
535
- 'Debugger.enable',
536
- 'Console.enable',
537
- 'Inspector.initialized',
538
- ]) {
539
- try {
540
- await this.send(domain, sendOpts);
541
- }
542
- catch (err) {
543
- logger_1.default.info(`Cannot enable domain '${domain}' during initialization: ${err.message}`);
544
- }
545
- }
554
+ await this.waitForPageInitialization(appIdKey, pageIdKey);
546
555
  }
547
556
  /**
548
557
  * Mimic every step that Desktop Safari Develop tools uses to initialize a
@@ -550,13 +559,38 @@ class RpcClient {
550
559
  *
551
560
  * @param {import('../types').AppIdKey} appIdKey
552
561
  * @param {import('../types').PageIdKey} pageIdKey
562
+ * @param {import('../types').TargetId} [targetId]
553
563
  * @returns {Promise<void>}
554
564
  */
555
- async initializePageFull(appIdKey, pageIdKey) {
565
+ async _initializePage(appIdKey, pageIdKey, targetId) {
556
566
  const sendOpts = {
557
567
  appIdKey,
558
568
  pageIdKey,
569
+ targetId,
559
570
  };
571
+ logger_1.default.debug(`Initializing page '${pageIdKey}' for app '${appIdKey}'`);
572
+ if (!this.fullPageInitialization) {
573
+ // The sequence of domains is important
574
+ for (const domain of [
575
+ 'Inspector.enable',
576
+ 'Page.enable',
577
+ 'Runtime.enable',
578
+ 'Network.enable',
579
+ 'Heap.enable',
580
+ 'Debugger.enable',
581
+ 'Console.enable',
582
+ 'Inspector.initialized',
583
+ ]) {
584
+ try {
585
+ await this.send(domain, sendOpts);
586
+ }
587
+ catch (err) {
588
+ logger_1.default.info(`Cannot enable domain '${domain}' during initialization: ${err.message}`);
589
+ }
590
+ }
591
+ logger_1.default.debug(`Simple initialization of page '${pageIdKey}' for app '${appIdKey}' completed`);
592
+ return;
593
+ }
560
594
  // The sequence of commands here is important
561
595
  const domainsToOptsMap = {
562
596
  'Inspector.enable': sendOpts,
@@ -634,6 +668,7 @@ class RpcClient {
634
668
  logger_1.default.info(`Cannot enable domain '${domain}' during full initialization: ${err.message}`);
635
669
  }
636
670
  }
671
+ logger_1.default.debug(`Full initialization of page '${pageIdKey}' for app '${appIdKey}' completed`);
637
672
  }
638
673
  /**
639
674
  *
@@ -658,7 +693,7 @@ class RpcClient {
658
693
  logger_1.default.debug(`We were notified we might have connected to the wrong app. ` +
659
694
  `Using id ${correctAppIdKey} instead of ${oldAppIdKey}`);
660
695
  }
661
- reject(new Error('New application has connected'));
696
+ reject(new Error(exports.NEW_APP_CONNECTED_ERROR));
662
697
  };
663
698
  this.messageHandler?.prependOnceListener('_rpc_applicationConnected:', onAppChange);
664
699
  // do the actual connecting to the app
@@ -668,7 +703,7 @@ class RpcClient {
668
703
  // sometimes the connect logic happens, but with an empty dictionary
669
704
  // which leads to the remote debugger getting disconnected, and into a loop
670
705
  if (lodash_1.default.isEmpty(pageDict)) {
671
- reject(new Error('Empty page dictionary received'));
706
+ reject(new Error(exports.EMPTY_PAGE_DICTIONARY_ERROR));
672
707
  }
673
708
  else {
674
709
  resolve([connectedAppIdKey, pageDict]);
@@ -711,8 +746,54 @@ class RpcClient {
711
746
  // { scriptId: '13', url: '', startLine: 0, startColumn: 0, endLine: 82, endColumn: 3 }
712
747
  logger_1.default.debug(`Script parsed: ${JSON.stringify(scriptInfo)}`);
713
748
  }
749
+ /**
750
+ *
751
+ * @param {import('../types').AppIdKey} appIdKey
752
+ * @param {import('../types').PageIdKey} pageIdKey
753
+ * @param {import('../types').TargetId} [targetId]
754
+ * @returns {Promise<void>}
755
+ */
756
+ async _resumeTarget(appIdKey, pageIdKey, targetId) {
757
+ try {
758
+ await this.send('Target.resume', {
759
+ appIdKey,
760
+ pageIdKey,
761
+ targetId,
762
+ });
763
+ logger_1.default.debug(`Successfully resumed the target ${targetId}@${appIdKey}`);
764
+ }
765
+ catch { }
766
+ }
767
+ /**
768
+ *
769
+ * @param {import('../types').AppIdKey} appIdKey
770
+ * @param {import('../types').PageIdKey} pageIdKey
771
+ */
772
+ async waitForPageInitialization(appIdKey, pageIdKey) {
773
+ const appTargetsMap = this.targets[appIdKey];
774
+ if (!appTargetsMap) {
775
+ throw new Error(`No targets found for app '${appIdKey}'`);
776
+ }
777
+ /** @type {AsyncLock | undefined} */
778
+ const lock = appTargetsMap.lock;
779
+ if (lock?.isBusy()) {
780
+ const timer = new support_1.timing.Timer().start();
781
+ logger_1.default.debug(`Waiting until page ${pageIdKey}@${appIdKey} is fully initialized`);
782
+ await lock.acquire(toLockKey(appIdKey, pageIdKey), async () => await bluebird_1.default.delay(0));
783
+ logger_1.default.debug(`The initalization of the page ${pageIdKey}@${appIdKey} took ${timer.getDuration().asMilliSeconds}ms`);
784
+ }
785
+ }
714
786
  }
715
787
  exports.RpcClient = RpcClient;
788
+ /**
789
+ *
790
+ * @param {import('../types').AppIdKey} appIdKey
791
+ * @param {import('../types').PageIdKey} pageIdKey
792
+ * @returns {string}
793
+ */
794
+ function toLockKey(appIdKey, pageIdKey) {
795
+ return `${appIdKey}-${pageIdKey}`;
796
+ }
716
797
  exports.default = RpcClient;
717
798
  /**
718
799
  * @typedef {Object} RpcClientOptions
@@ -725,6 +806,12 @@ exports.default = RpcClient;
725
806
  * @property {number} [socketChunkSize]
726
807
  * @property {boolean} [fullPageInitialization=false]
727
808
  * @property {string} [udid]
728
- * @property {boolean} [shouldCheckForTarget]
729
809
  */
810
+ /**
811
+ * @typedef {{[key: import('../types').PageIdKey]: import('../types').TargetId}} PageDict
812
+ */
813
+ /**
814
+ * @typedef {PageDict & {provisional?: import('../types').ProvisionalTargetInfo, lock: import('async-lock').default}} PagesToTargets
815
+ * @typedef {{[key: import('../types').AppIdKey]: PagesToTargets}} AppToTargetsMap
816
+ */
730
817
  //# sourceMappingURL=rpc-client.js.map