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.
- package/CHANGELOG.md +16 -0
- package/build/lib/mixins/connect.d.ts.map +1 -1
- package/build/lib/mixins/connect.js +32 -37
- 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 +14 -12
- package/build/lib/mixins/execute.js.map +1 -1
- package/build/lib/mixins/misc.d.ts +7 -0
- package/build/lib/mixins/misc.d.ts.map +1 -1
- package/build/lib/mixins/misc.js +21 -0
- 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 +9 -96
- package/build/lib/mixins/navigate.js.map +1 -1
- package/build/lib/protocol/index.d.ts +3 -1
- package/build/lib/protocol/index.d.ts.map +1 -1
- package/build/lib/protocol/index.js +22 -23
- package/build/lib/protocol/index.js.map +1 -1
- package/build/lib/remote-debugger.d.ts +1 -0
- package/build/lib/remote-debugger.d.ts.map +1 -1
- package/build/lib/remote-debugger.js +1 -0
- package/build/lib/remote-debugger.js.map +1 -1
- package/build/lib/rpc/index.d.ts +1 -2
- package/build/lib/rpc/index.d.ts.map +1 -1
- package/build/lib/rpc/index.js +1 -36
- package/build/lib/rpc/index.js.map +1 -1
- package/build/lib/rpc/remote-messages.d.ts +24 -18
- package/build/lib/rpc/remote-messages.d.ts.map +1 -1
- package/build/lib/rpc/remote-messages.js +24 -11
- package/build/lib/rpc/remote-messages.js.map +1 -1
- package/build/lib/rpc/rpc-client-real-device.d.ts.map +1 -1
- package/build/lib/rpc/rpc-client-real-device.js +1 -3
- package/build/lib/rpc/rpc-client-real-device.js.map +1 -1
- package/build/lib/rpc/rpc-client-simulator.d.ts.map +1 -1
- package/build/lib/rpc/rpc-client-simulator.js +1 -3
- package/build/lib/rpc/rpc-client-simulator.js.map +1 -1
- package/build/lib/rpc/rpc-client.d.ts +63 -44
- package/build/lib/rpc/rpc-client.d.ts.map +1 -1
- package/build/lib/rpc/rpc-client.js +209 -122
- package/build/lib/rpc/rpc-client.js.map +1 -1
- package/build/lib/rpc/rpc-message-handler.d.ts +8 -5
- package/build/lib/rpc/rpc-message-handler.d.ts.map +1 -1
- package/build/lib/rpc/rpc-message-handler.js +61 -75
- package/build/lib/rpc/rpc-message-handler.js.map +1 -1
- package/build/lib/types.d.ts +28 -3
- package/build/lib/types.d.ts.map +1 -1
- package/build/lib/utils.d.ts +4 -3
- package/build/lib/utils.d.ts.map +1 -1
- package/build/lib/utils.js +4 -2
- package/build/lib/utils.js.map +1 -1
- package/build/tsconfig.tsbuildinfo +1 -1
- package/lib/mixins/connect.js +34 -38
- package/lib/mixins/execute.js +15 -12
- package/lib/mixins/misc.js +22 -0
- package/lib/mixins/navigate.js +11 -99
- package/lib/protocol/index.js +22 -24
- package/lib/remote-debugger.ts +1 -0
- package/lib/rpc/index.js +1 -3
- package/lib/rpc/remote-messages.js +28 -11
- package/lib/rpc/rpc-client-real-device.js +1 -3
- package/lib/rpc/rpc-client-simulator.js +1 -3
- package/lib/rpc/rpc-client.js +244 -133
- package/lib/rpc/rpc-message-handler.js +62 -74
- package/lib/types.ts +33 -3
- package/lib/utils.js +4 -2
- package/package.json +2 -1
- package/build/lib/rpc/constants.d.ts +0 -2
- package/build/lib/rpc/constants.d.ts.map +0 -1
- package/build/lib/rpc/constants.js +0 -5
- package/build/lib/rpc/constants.js.map +0 -1
- package/lib/rpc/constants.js +0 -1
package/lib/rpc/rpc-client.js
CHANGED
|
@@ -6,7 +6,7 @@ import B from 'bluebird';
|
|
|
6
6
|
import RpcMessageHandler from './rpc-message-handler';
|
|
7
7
|
import { util, timing } from '@appium/support';
|
|
8
8
|
import { EventEmitter } from 'node:events';
|
|
9
|
-
import
|
|
9
|
+
import AsyncLock from 'async-lock';
|
|
10
10
|
|
|
11
11
|
const DATA_LOG_LENGTH = {length: 200};
|
|
12
12
|
const WAIT_FOR_TARGET_TIMEOUT_MS = 10000;
|
|
@@ -20,6 +20,8 @@ const NO_TARGET_PRESENT_YET_ERRORS = [
|
|
|
20
20
|
`some arguments of method`,
|
|
21
21
|
`missing target`,
|
|
22
22
|
];
|
|
23
|
+
export const NEW_APP_CONNECTED_ERROR = 'New application has connected';
|
|
24
|
+
export const EMPTY_PAGE_DICTIONARY_ERROR = 'Empty page dictionary received';
|
|
23
25
|
|
|
24
26
|
/**
|
|
25
27
|
* @param {boolean} isSafari
|
|
@@ -85,14 +87,14 @@ export class RpcClient {
|
|
|
85
87
|
/** @type {string[]} */
|
|
86
88
|
_contexts;
|
|
87
89
|
|
|
88
|
-
/** @type {
|
|
90
|
+
/** @type {AppToTargetsMap} */
|
|
89
91
|
_targets;
|
|
90
92
|
|
|
91
93
|
/** @type {EventEmitter} */
|
|
92
94
|
_targetSubscriptions;
|
|
93
95
|
|
|
94
|
-
/** @type {
|
|
95
|
-
|
|
96
|
+
/** @type {[import('../types').AppIdKey, import('../types').PageIdKey] | undefined} */
|
|
97
|
+
_pendingTargetNotification;
|
|
96
98
|
|
|
97
99
|
/**
|
|
98
100
|
*
|
|
@@ -135,7 +137,6 @@ export class RpcClient {
|
|
|
135
137
|
this._targetSubscriptions = new EventEmitter();
|
|
136
138
|
|
|
137
139
|
// start with a best guess for the protocol
|
|
138
|
-
this._shouldCheckForTarget = !!opts.shouldCheckForTarget;
|
|
139
140
|
this.isTargetBased = platformVersion ? isTargetBased(isSafari, platformVersion) : true;
|
|
140
141
|
}
|
|
141
142
|
|
|
@@ -150,27 +151,16 @@ export class RpcClient {
|
|
|
150
151
|
* @returns {boolean}
|
|
151
152
|
*/
|
|
152
153
|
get needsTarget () {
|
|
153
|
-
return this.
|
|
154
|
+
return this.isTargetBased;
|
|
154
155
|
}
|
|
155
156
|
|
|
156
157
|
/**
|
|
157
|
-
* @returns {
|
|
158
|
+
* @returns {AppToTargetsMap}
|
|
158
159
|
*/
|
|
159
160
|
get targets () {
|
|
160
161
|
return this._targets;
|
|
161
162
|
}
|
|
162
163
|
|
|
163
|
-
/**
|
|
164
|
-
* @returns {boolean}
|
|
165
|
-
*/
|
|
166
|
-
get shouldCheckForTarget () {
|
|
167
|
-
return this._shouldCheckForTarget;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
set shouldCheckForTarget (shouldCheckForTarget) {
|
|
171
|
-
this._shouldCheckForTarget = !!shouldCheckForTarget;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
164
|
/**
|
|
175
165
|
* @returns {boolean}
|
|
176
166
|
*/
|
|
@@ -265,33 +255,39 @@ export class RpcClient {
|
|
|
265
255
|
*
|
|
266
256
|
* @param {import('../types').AppIdKey} appIdKey
|
|
267
257
|
* @param {import('../types').PageIdKey} pageIdKey
|
|
268
|
-
* @returns {Promise<
|
|
258
|
+
* @returns {Promise<import('../types').TargetId | undefined>}
|
|
269
259
|
*/
|
|
270
260
|
async waitForTarget (appIdKey, pageIdKey) {
|
|
271
261
|
if (!this.needsTarget) {
|
|
272
262
|
log.debug(`Target-based communication is not needed, skipping wait for target`);
|
|
273
263
|
return;
|
|
274
264
|
}
|
|
275
|
-
|
|
265
|
+
let target = this.getTarget(appIdKey, pageIdKey);
|
|
276
266
|
if (target) {
|
|
277
267
|
log.debug(
|
|
278
268
|
`The target '${target}' for app '${appIdKey}' and page '${pageIdKey}' already exists, no need to wait`
|
|
279
269
|
);
|
|
280
|
-
return;
|
|
270
|
+
return target;
|
|
281
271
|
}
|
|
282
272
|
|
|
283
273
|
// otherwise waiting is necessary to see what the target is
|
|
284
274
|
try {
|
|
285
|
-
await waitForCondition(() =>
|
|
275
|
+
await waitForCondition(() => {
|
|
276
|
+
target = this.getTarget(appIdKey, pageIdKey);
|
|
277
|
+
return !_.isEmpty(target);
|
|
278
|
+
}, {
|
|
286
279
|
waitMs: WAIT_FOR_TARGET_TIMEOUT_MS,
|
|
287
280
|
intervalMs: WAIT_FOR_TARGET_INTERVAL_MS,
|
|
288
|
-
error: 'No targets found, unable to communicate with device',
|
|
289
281
|
});
|
|
282
|
+
return target;
|
|
290
283
|
} catch (err) {
|
|
291
284
|
if (!err.message.includes('Condition unmet')) {
|
|
292
285
|
throw err;
|
|
293
286
|
}
|
|
294
|
-
throw new Error(
|
|
287
|
+
throw new Error(
|
|
288
|
+
`No targets could be matched for the app '${appIdKey}' and page '${pageIdKey}' after ` +
|
|
289
|
+
`${WAIT_FOR_TARGET_TIMEOUT_MS}ms. This is likely a bug.`
|
|
290
|
+
);
|
|
295
291
|
}
|
|
296
292
|
}
|
|
297
293
|
|
|
@@ -299,18 +295,18 @@ export class RpcClient {
|
|
|
299
295
|
*
|
|
300
296
|
* @param {string} command
|
|
301
297
|
* @param {import('../types').RemoteCommandOpts} opts
|
|
302
|
-
* @param {boolean} [waitForResponse]
|
|
298
|
+
* @param {boolean} [waitForResponse=true]
|
|
303
299
|
* @returns {Promise<any>}
|
|
304
300
|
*/
|
|
305
301
|
async send (command, opts, waitForResponse = true) {
|
|
306
302
|
const timer = new timing.Timer().start();
|
|
307
|
-
const {
|
|
308
|
-
appIdKey,
|
|
309
|
-
pageIdKey
|
|
310
|
-
} = opts;
|
|
311
303
|
try {
|
|
312
304
|
return await this.sendToDevice(command, opts, waitForResponse);
|
|
313
305
|
} catch (err) {
|
|
306
|
+
const {
|
|
307
|
+
appIdKey,
|
|
308
|
+
pageIdKey
|
|
309
|
+
} = opts;
|
|
314
310
|
const messageLc = (err.message || '').toLowerCase();
|
|
315
311
|
if (messageLc.includes(NO_TARGET_SUPPORTED_ERROR)) {
|
|
316
312
|
log.info(
|
|
@@ -332,11 +328,13 @@ export class RpcClient {
|
|
|
332
328
|
|
|
333
329
|
/**
|
|
334
330
|
*
|
|
331
|
+
* @template {boolean} TWaitForResponse
|
|
335
332
|
* @param {string} command
|
|
336
333
|
* @param {import('../types').RemoteCommandOpts} opts
|
|
337
|
-
* @param {
|
|
338
|
-
* @returns {Promise<any>}
|
|
334
|
+
* @param {TWaitForResponse} [waitForResponse=true]
|
|
335
|
+
* @returns {Promise<TWaitForResponse extends true ? import('../types').RemoteCommandOpts : any>}
|
|
339
336
|
*/
|
|
337
|
+
// @ts-ignore Compiler issue
|
|
340
338
|
async sendToDevice (command, opts, waitForResponse = true) {
|
|
341
339
|
return await new B(async (resolve, reject) => {
|
|
342
340
|
// promise to be resolved whenever remote debugger
|
|
@@ -359,25 +357,41 @@ export class RpcClient {
|
|
|
359
357
|
|
|
360
358
|
const appIdKey = opts.appIdKey;
|
|
361
359
|
const pageIdKey = opts.pageIdKey;
|
|
362
|
-
const targetId = this.getTarget(appIdKey, pageIdKey);
|
|
360
|
+
const targetId = opts.targetId ?? this.getTarget(appIdKey, pageIdKey);
|
|
363
361
|
|
|
364
362
|
// retrieve the correct command to send
|
|
363
|
+
/** @type {import('../types').RemoteCommandOpts} */
|
|
365
364
|
const fullOpts = _.defaults({
|
|
366
365
|
connId: this.connId,
|
|
367
366
|
senderId: this.senderId,
|
|
368
367
|
targetId,
|
|
369
368
|
id: msgId,
|
|
370
369
|
}, opts);
|
|
371
|
-
|
|
372
|
-
|
|
370
|
+
/** @type {import('../types').RawRemoteCommand} */
|
|
371
|
+
let cmd;
|
|
372
|
+
try {
|
|
373
|
+
// @ts-ignore remoteMessages must be defined
|
|
374
|
+
cmd = this.remoteMessages.getRemoteCommand(command, fullOpts);
|
|
375
|
+
} catch (err) {
|
|
376
|
+
log.error(err);
|
|
377
|
+
return reject(err);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/** @type {import('../types').RemoteCommand} */
|
|
381
|
+
const finalCommand = {
|
|
382
|
+
__argument: _.omit(cmd.__argument, ['WIRSocketDataKey']),
|
|
383
|
+
__selector: cmd.__selector,
|
|
384
|
+
};
|
|
373
385
|
|
|
374
|
-
|
|
386
|
+
const hasSocketData = _.isPlainObject(cmd.__argument?.WIRSocketDataKey);
|
|
387
|
+
if (hasSocketData) {
|
|
375
388
|
// make sure the message being sent has all the information that is needed
|
|
389
|
+
// @ts-ignore We have asserted it's a plain object above
|
|
376
390
|
if (_.isNil(cmd.__argument.WIRSocketDataKey.id)) {
|
|
391
|
+
// @ts-ignore We have already asserted it's a plain object above
|
|
377
392
|
cmd.__argument.WIRSocketDataKey.id = wrapperMsgId;
|
|
378
393
|
}
|
|
379
|
-
|
|
380
|
-
Buffer.from(JSON.stringify(cmd.__argument.WIRSocketDataKey));
|
|
394
|
+
finalCommand.__argument.WIRSocketDataKey = Buffer.from(JSON.stringify(cmd.__argument.WIRSocketDataKey));
|
|
381
395
|
}
|
|
382
396
|
|
|
383
397
|
let messageHandled = true;
|
|
@@ -390,7 +404,10 @@ export class RpcClient {
|
|
|
390
404
|
if (err) {
|
|
391
405
|
// we are not waiting for this, and if it errors it is most likely
|
|
392
406
|
// a protocol change. Log and check during testing
|
|
393
|
-
log.error(
|
|
407
|
+
log.error(
|
|
408
|
+
`Received error from send that is not being waited for (id: ${msgId}): ` +
|
|
409
|
+
_.truncate(JSON.stringify(err), DATA_LOG_LENGTH)
|
|
410
|
+
);
|
|
394
411
|
// reject, though it is very rare that this will be triggered, since
|
|
395
412
|
// the promise is resolved directlty after send. On the off chance,
|
|
396
413
|
// though, it will alert of a protocol change.
|
|
@@ -405,9 +422,10 @@ export class RpcClient {
|
|
|
405
422
|
return reject(err);
|
|
406
423
|
}
|
|
407
424
|
log.debug(`Received response from send (id: ${msgId}): '${_.truncate(JSON.stringify(args), DATA_LOG_LENGTH)}'`);
|
|
425
|
+
// @ts-ignore This is ok
|
|
408
426
|
resolve(args);
|
|
409
427
|
});
|
|
410
|
-
} else if (
|
|
428
|
+
} else if (hasSocketData) {
|
|
411
429
|
// @ts-ignore messageHandler must be defined
|
|
412
430
|
this.messageHandler.once(msgId.toString(), function (err, value) {
|
|
413
431
|
if (err) {
|
|
@@ -428,11 +446,12 @@ export class RpcClient {
|
|
|
428
446
|
` (id: ${msgId}): '${command}'`;
|
|
429
447
|
log.debug(msg);
|
|
430
448
|
try {
|
|
431
|
-
|
|
449
|
+
await this.sendMessage(finalCommand);
|
|
432
450
|
if (!messageHandled) {
|
|
433
451
|
// There are no handlers waiting for a response before resolving,
|
|
434
452
|
// and no errors sending the message over the socket, so resolve
|
|
435
|
-
|
|
453
|
+
// @ts-ignore This is ok
|
|
454
|
+
resolve(fullOpts);
|
|
436
455
|
}
|
|
437
456
|
} catch (err) {
|
|
438
457
|
return reject(err);
|
|
@@ -468,53 +487,101 @@ export class RpcClient {
|
|
|
468
487
|
|
|
469
488
|
/**
|
|
470
489
|
*
|
|
471
|
-
* @param {Error
|
|
472
|
-
* @param {
|
|
473
|
-
* @param {
|
|
474
|
-
* @returns {void}
|
|
490
|
+
* @param {Error | undefined} err
|
|
491
|
+
* @param {import('../types').AppIdKey} app
|
|
492
|
+
* @param {import('../types').TargetInfo} targetInfo
|
|
493
|
+
* @returns {Promise<void>}
|
|
475
494
|
*/
|
|
476
|
-
addTarget (err, app, targetInfo) {
|
|
495
|
+
async addTarget (err, app, targetInfo) {
|
|
477
496
|
if (_.isNil(targetInfo?.targetId)) {
|
|
478
|
-
log.
|
|
497
|
+
log.info(`Received 'Target.targetCreated' event for app '${app}' with no target. Skipping`);
|
|
479
498
|
return;
|
|
480
499
|
}
|
|
481
|
-
if (
|
|
482
|
-
log.
|
|
500
|
+
if (!this._pendingTargetNotification) {
|
|
501
|
+
log.info(
|
|
502
|
+
`Received 'Target.targetCreated' event for app '${app}' with no pending request: ${JSON.stringify(targetInfo)}`
|
|
503
|
+
);
|
|
483
504
|
return;
|
|
484
505
|
}
|
|
485
506
|
|
|
486
|
-
|
|
487
|
-
|
|
507
|
+
const [appIdKey, pageIdKey] = this._pendingTargetNotification;
|
|
508
|
+
if (appIdKey !== app) {
|
|
509
|
+
log.info(
|
|
510
|
+
`Received 'Target.targetCreated' event for app '${app}' with no pending request: ${JSON.stringify(targetInfo)}`
|
|
511
|
+
);
|
|
488
512
|
return;
|
|
489
513
|
}
|
|
490
514
|
|
|
491
|
-
|
|
492
|
-
|
|
515
|
+
if (targetInfo.isProvisional) {
|
|
516
|
+
log.debug(
|
|
517
|
+
`Provisional target created for app '${appIdKey}' and page '${pageIdKey}': '${targetInfo.targetId}'`
|
|
518
|
+
);
|
|
519
|
+
if (!targetInfo.isPaused) {
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
log.debug(`The target ${targetInfo.targetId}@${appIdKey} is paused`);
|
|
524
|
+
const appTargetsMap = this.targets[appIdKey];
|
|
525
|
+
if (appTargetsMap) {
|
|
526
|
+
await appTargetsMap.lock.acquire(toLockKey(appIdKey, pageIdKey), async () => {
|
|
527
|
+
try {
|
|
528
|
+
await this._initializePage(appIdKey, pageIdKey, targetInfo.targetId);
|
|
529
|
+
} finally {
|
|
530
|
+
await this._resumeTarget(appIdKey, pageIdKey, targetInfo.targetId);
|
|
531
|
+
}
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
493
536
|
|
|
494
537
|
log.debug(`Target created for app '${appIdKey}' and page '${pageIdKey}': ${JSON.stringify(targetInfo)}`);
|
|
495
538
|
if (_.has(this.targets[appIdKey], pageIdKey)) {
|
|
496
539
|
log.debug(`There is already a target for this app and page ('${this.targets[appIdKey][pageIdKey]}'). This might cause problems`);
|
|
497
540
|
}
|
|
498
|
-
|
|
541
|
+
const lock = new AsyncLock();
|
|
542
|
+
this.targets[app] = this.targets[app] || { lock };
|
|
499
543
|
this.targets[appIdKey][pageIdKey] = targetInfo.targetId;
|
|
544
|
+
|
|
545
|
+
await lock.acquire(toLockKey(appIdKey, pageIdKey), async () => {
|
|
546
|
+
try {
|
|
547
|
+
await this.send('Target.setPauseOnStart', {
|
|
548
|
+
pauseOnStart: true,
|
|
549
|
+
appIdKey,
|
|
550
|
+
pageIdKey,
|
|
551
|
+
});
|
|
552
|
+
} catch {}
|
|
553
|
+
try {
|
|
554
|
+
await this._initializePage(appIdKey, pageIdKey);
|
|
555
|
+
} finally {
|
|
556
|
+
if (targetInfo.isPaused) {
|
|
557
|
+
await this._resumeTarget(appIdKey, pageIdKey);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
});
|
|
500
561
|
}
|
|
501
562
|
|
|
502
563
|
/**
|
|
503
564
|
*
|
|
504
|
-
* @param {Error
|
|
505
|
-
* @param {
|
|
506
|
-
* @param {
|
|
507
|
-
* @
|
|
508
|
-
* @returns {void}
|
|
565
|
+
* @param {Error | undefined} err
|
|
566
|
+
* @param {import('../types').AppIdKey} app
|
|
567
|
+
* @param {import('../types').ProvisionalTargetInfo} targetInfo
|
|
568
|
+
* @returns {Promise<void>}
|
|
509
569
|
*/
|
|
510
|
-
updateTarget (err, app,
|
|
570
|
+
async updateTarget (err, app, targetInfo) {
|
|
571
|
+
const {
|
|
572
|
+
oldTargetId,
|
|
573
|
+
newTargetId,
|
|
574
|
+
} = targetInfo;
|
|
511
575
|
log.debug(`Target updated for app '${app}'. Old target: '${oldTargetId}', new target: '${newTargetId}'`);
|
|
512
|
-
|
|
576
|
+
|
|
577
|
+
const appTargetsMap = this.targets[app];
|
|
578
|
+
if (!appTargetsMap) {
|
|
513
579
|
log.warn(`No existing target for app '${app}'. Not sure what to do`);
|
|
514
580
|
return;
|
|
515
581
|
}
|
|
582
|
+
|
|
516
583
|
// save this, to be used if/when the existing target is destroyed
|
|
517
|
-
|
|
584
|
+
appTargetsMap.provisional = {
|
|
518
585
|
oldTargetId,
|
|
519
586
|
newTargetId,
|
|
520
587
|
};
|
|
@@ -522,12 +589,12 @@ export class RpcClient {
|
|
|
522
589
|
|
|
523
590
|
/**
|
|
524
591
|
*
|
|
525
|
-
* @param {Error
|
|
526
|
-
* @param {
|
|
527
|
-
* @param {
|
|
528
|
-
* @returns {void}
|
|
592
|
+
* @param {Error | undefined} err
|
|
593
|
+
* @param {import('../types').AppIdKey} app
|
|
594
|
+
* @param {import('../types').TargetInfo} targetInfo
|
|
595
|
+
* @returns {Promise<void>}
|
|
529
596
|
*/
|
|
530
|
-
removeTarget (err, app, targetInfo) {
|
|
597
|
+
async removeTarget (err, app, targetInfo) {
|
|
531
598
|
if (_.isNil(targetInfo?.targetId)) {
|
|
532
599
|
log.debug(`Received 'Target.targetDestroyed' event with no target. Skipping`);
|
|
533
600
|
return;
|
|
@@ -541,29 +608,23 @@ export class RpcClient {
|
|
|
541
608
|
delete this.targets[app].provisional;
|
|
542
609
|
|
|
543
610
|
// we do not know the page, so go through and find the existing target
|
|
544
|
-
const
|
|
545
|
-
for (const [page, targetId] of _.toPairs(
|
|
611
|
+
const appTargetsMap = this.targets[app];
|
|
612
|
+
for (const [page, targetId] of _.toPairs(appTargetsMap)) {
|
|
546
613
|
if (targetId === oldTargetId) {
|
|
547
|
-
log.debug(
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
await this.initializePageFull(opts.appIdKey, opts.pageIdKey);
|
|
553
|
-
} else {
|
|
554
|
-
await this.initializePage(opts.appIdKey, opts.pageIdKey);
|
|
555
|
-
}
|
|
556
|
-
this._targetSubscriptions.emit(ON_TARGET_PROVISIONED_EVENT, {
|
|
557
|
-
...opts,
|
|
558
|
-
oldTargetId,
|
|
559
|
-
targetId: newTargetId,
|
|
560
|
-
});
|
|
561
|
-
})();
|
|
614
|
+
log.debug(
|
|
615
|
+
`Found provisional target for app '${app}'. ` +
|
|
616
|
+
`Old target: '${oldTargetId}', new target: '${newTargetId}'. Updating`
|
|
617
|
+
);
|
|
618
|
+
appTargetsMap[page] = newTargetId;
|
|
562
619
|
return;
|
|
563
620
|
}
|
|
564
621
|
}
|
|
565
|
-
log.warn(
|
|
566
|
-
|
|
622
|
+
log.warn(
|
|
623
|
+
`Provisional target for app '${app}' found, but no suitable existing target found. This may cause problems`
|
|
624
|
+
);
|
|
625
|
+
log.warn(
|
|
626
|
+
`Old target: '${oldTargetId}', new target: '${newTargetId}'. Existing targets: ${JSON.stringify(appTargetsMap)}`
|
|
627
|
+
);
|
|
567
628
|
}
|
|
568
629
|
|
|
569
630
|
// if there is no waiting provisional target, just get rid of the existing one
|
|
@@ -595,9 +656,7 @@ export class RpcClient {
|
|
|
595
656
|
* @returns {Promise<void>}
|
|
596
657
|
*/
|
|
597
658
|
async selectPage (appIdKey, pageIdKey) {
|
|
598
|
-
|
|
599
|
-
this.pendingTargetNotification = [appIdKey, pageIdKey];
|
|
600
|
-
this.shouldCheckForTarget = false;
|
|
659
|
+
this._pendingTargetNotification = [appIdKey, pageIdKey];
|
|
601
660
|
|
|
602
661
|
// go through the steps that the Desktop Safari system
|
|
603
662
|
// goes through to initialize the Web Inspector session
|
|
@@ -617,51 +676,17 @@ export class RpcClient {
|
|
|
617
676
|
await this.send('setSenderKey', sendOpts);
|
|
618
677
|
log.debug('Sender key set');
|
|
619
678
|
|
|
679
|
+
if (!this.isTargetBased) {
|
|
680
|
+
await this._initializePage(appIdKey, pageIdKey);
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
|
|
620
684
|
if (this.isTargetBased && util.compareVersions(this.platformVersion, '<', MIN_PLATFORM_NO_TARGET_EXISTS)) {
|
|
621
685
|
await this.send('Target.exists', sendOpts, false);
|
|
622
686
|
}
|
|
623
687
|
|
|
624
|
-
this.shouldCheckForTarget = true;
|
|
625
|
-
|
|
626
688
|
await this.waitForTarget(appIdKey, pageIdKey);
|
|
627
|
-
|
|
628
|
-
await this.initializePageFull(appIdKey, pageIdKey);
|
|
629
|
-
} else {
|
|
630
|
-
await this.initializePage(appIdKey, pageIdKey);
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
/**
|
|
635
|
-
* Perform the minimal initialization to get the Web Inspector working
|
|
636
|
-
* @param {import('../types').AppIdKey} appIdKey
|
|
637
|
-
* @param {import('../types').PageIdKey} pageIdKey
|
|
638
|
-
* @returns {Promise<void>}
|
|
639
|
-
*/
|
|
640
|
-
async initializePage (appIdKey, pageIdKey) {
|
|
641
|
-
const sendOpts = {
|
|
642
|
-
appIdKey,
|
|
643
|
-
pageIdKey,
|
|
644
|
-
};
|
|
645
|
-
|
|
646
|
-
// The sequence of domains is important
|
|
647
|
-
for (const domain of [
|
|
648
|
-
'Inspector.enable',
|
|
649
|
-
'Page.enable',
|
|
650
|
-
'Runtime.enable',
|
|
651
|
-
|
|
652
|
-
'Network.enable',
|
|
653
|
-
'Heap.enable',
|
|
654
|
-
'Debugger.enable',
|
|
655
|
-
'Console.enable',
|
|
656
|
-
|
|
657
|
-
'Inspector.initialized',
|
|
658
|
-
]) {
|
|
659
|
-
try {
|
|
660
|
-
await this.send(domain, sendOpts);
|
|
661
|
-
} catch (err) {
|
|
662
|
-
log.info(`Cannot enable domain '${domain}' during initialization: ${err.message}`);
|
|
663
|
-
}
|
|
664
|
-
}
|
|
689
|
+
await this.waitForPageInitialization(appIdKey, pageIdKey);
|
|
665
690
|
}
|
|
666
691
|
|
|
667
692
|
/**
|
|
@@ -670,14 +695,41 @@ export class RpcClient {
|
|
|
670
695
|
*
|
|
671
696
|
* @param {import('../types').AppIdKey} appIdKey
|
|
672
697
|
* @param {import('../types').PageIdKey} pageIdKey
|
|
698
|
+
* @param {import('../types').TargetId} [targetId]
|
|
673
699
|
* @returns {Promise<void>}
|
|
674
700
|
*/
|
|
675
|
-
async
|
|
701
|
+
async _initializePage (appIdKey, pageIdKey, targetId) {
|
|
676
702
|
const sendOpts = {
|
|
677
703
|
appIdKey,
|
|
678
704
|
pageIdKey,
|
|
705
|
+
targetId,
|
|
679
706
|
};
|
|
680
707
|
|
|
708
|
+
log.debug(`Initializing page '${pageIdKey}' for app '${appIdKey}'`);
|
|
709
|
+
if (!this.fullPageInitialization) {
|
|
710
|
+
// The sequence of domains is important
|
|
711
|
+
for (const domain of [
|
|
712
|
+
'Inspector.enable',
|
|
713
|
+
'Page.enable',
|
|
714
|
+
'Runtime.enable',
|
|
715
|
+
|
|
716
|
+
'Network.enable',
|
|
717
|
+
'Heap.enable',
|
|
718
|
+
'Debugger.enable',
|
|
719
|
+
'Console.enable',
|
|
720
|
+
|
|
721
|
+
'Inspector.initialized',
|
|
722
|
+
]) {
|
|
723
|
+
try {
|
|
724
|
+
await this.send(domain, sendOpts);
|
|
725
|
+
} catch (err) {
|
|
726
|
+
log.info(`Cannot enable domain '${domain}' during initialization: ${err.message}`);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
log.debug(`Simple initialization of page '${pageIdKey}' for app '${appIdKey}' completed`);
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
|
|
681
733
|
// The sequence of commands here is important
|
|
682
734
|
const domainsToOptsMap = {
|
|
683
735
|
'Inspector.enable': sendOpts,
|
|
@@ -756,6 +808,7 @@ export class RpcClient {
|
|
|
756
808
|
log.info(`Cannot enable domain '${domain}' during full initialization: ${err.message}`);
|
|
757
809
|
}
|
|
758
810
|
}
|
|
811
|
+
log.debug(`Full initialization of page '${pageIdKey}' for app '${appIdKey}' completed`);
|
|
759
812
|
}
|
|
760
813
|
|
|
761
814
|
/**
|
|
@@ -783,7 +836,7 @@ export class RpcClient {
|
|
|
783
836
|
`Using id ${correctAppIdKey} instead of ${oldAppIdKey}`);
|
|
784
837
|
}
|
|
785
838
|
|
|
786
|
-
reject(new Error(
|
|
839
|
+
reject(new Error(NEW_APP_CONNECTED_ERROR));
|
|
787
840
|
};
|
|
788
841
|
this.messageHandler?.prependOnceListener('_rpc_applicationConnected:', onAppChange);
|
|
789
842
|
|
|
@@ -794,7 +847,7 @@ export class RpcClient {
|
|
|
794
847
|
// sometimes the connect logic happens, but with an empty dictionary
|
|
795
848
|
// which leads to the remote debugger getting disconnected, and into a loop
|
|
796
849
|
if (_.isEmpty(pageDict)) {
|
|
797
|
-
reject(new Error(
|
|
850
|
+
reject(new Error(EMPTY_PAGE_DICTIONARY_ERROR));
|
|
798
851
|
} else {
|
|
799
852
|
resolve([connectedAppIdKey, pageDict]);
|
|
800
853
|
}
|
|
@@ -837,6 +890,56 @@ export class RpcClient {
|
|
|
837
890
|
// { scriptId: '13', url: '', startLine: 0, startColumn: 0, endLine: 82, endColumn: 3 }
|
|
838
891
|
log.debug(`Script parsed: ${JSON.stringify(scriptInfo)}`);
|
|
839
892
|
}
|
|
893
|
+
|
|
894
|
+
/**
|
|
895
|
+
*
|
|
896
|
+
* @param {import('../types').AppIdKey} appIdKey
|
|
897
|
+
* @param {import('../types').PageIdKey} pageIdKey
|
|
898
|
+
* @param {import('../types').TargetId} [targetId]
|
|
899
|
+
* @returns {Promise<void>}
|
|
900
|
+
*/
|
|
901
|
+
async _resumeTarget (appIdKey, pageIdKey, targetId) {
|
|
902
|
+
try {
|
|
903
|
+
await this.send('Target.resume', {
|
|
904
|
+
appIdKey,
|
|
905
|
+
pageIdKey,
|
|
906
|
+
targetId,
|
|
907
|
+
});
|
|
908
|
+
log.debug(`Successfully resumed the target ${targetId}@${appIdKey}`);
|
|
909
|
+
} catch {}
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
/**
|
|
913
|
+
*
|
|
914
|
+
* @param {import('../types').AppIdKey} appIdKey
|
|
915
|
+
* @param {import('../types').PageIdKey} pageIdKey
|
|
916
|
+
*/
|
|
917
|
+
async waitForPageInitialization (appIdKey, pageIdKey) {
|
|
918
|
+
const appTargetsMap = this.targets[appIdKey];
|
|
919
|
+
if (!appTargetsMap) {
|
|
920
|
+
throw new Error(`No targets found for app '${appIdKey}'`);
|
|
921
|
+
}
|
|
922
|
+
/** @type {AsyncLock | undefined} */
|
|
923
|
+
const lock = appTargetsMap.lock;
|
|
924
|
+
if (lock?.isBusy()) {
|
|
925
|
+
const timer = new timing.Timer().start();
|
|
926
|
+
log.debug(`Waiting until page ${pageIdKey}@${appIdKey} is fully initialized`);
|
|
927
|
+
await lock.acquire(toLockKey(appIdKey, pageIdKey), async () => await B.delay(0));
|
|
928
|
+
log.debug(
|
|
929
|
+
`The initalization of the page ${pageIdKey}@${appIdKey} took ${timer.getDuration().asMilliSeconds}ms`
|
|
930
|
+
);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
/**
|
|
936
|
+
*
|
|
937
|
+
* @param {import('../types').AppIdKey} appIdKey
|
|
938
|
+
* @param {import('../types').PageIdKey} pageIdKey
|
|
939
|
+
* @returns {string}
|
|
940
|
+
*/
|
|
941
|
+
export function toLockKey(appIdKey, pageIdKey) {
|
|
942
|
+
return `${appIdKey}-${pageIdKey}`;
|
|
840
943
|
}
|
|
841
944
|
|
|
842
945
|
export default RpcClient;
|
|
@@ -852,5 +955,13 @@ export default RpcClient;
|
|
|
852
955
|
* @property {number} [socketChunkSize]
|
|
853
956
|
* @property {boolean} [fullPageInitialization=false]
|
|
854
957
|
* @property {string} [udid]
|
|
855
|
-
* @property {boolean} [shouldCheckForTarget]
|
|
856
958
|
*/
|
|
959
|
+
|
|
960
|
+
/**
|
|
961
|
+
* @typedef {{[key: import('../types').PageIdKey]: import('../types').TargetId}} PageDict
|
|
962
|
+
*/
|
|
963
|
+
|
|
964
|
+
/**
|
|
965
|
+
* @typedef {PageDict & {provisional?: import('../types').ProvisionalTargetInfo, lock: import('async-lock').default}} PagesToTargets
|
|
966
|
+
* @typedef {{[key: import('../types').AppIdKey]: PagesToTargets}} AppToTargetsMap
|
|
967
|
+
*/
|