appium-remote-debugger 15.2.9 → 15.2.10
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 +6 -0
- package/build/lib/mixins/connect.d.ts +47 -31
- package/build/lib/mixins/connect.d.ts.map +1 -1
- package/build/lib/mixins/connect.js +105 -86
- package/build/lib/mixins/connect.js.map +1 -1
- package/build/lib/mixins/message-handlers.d.ts +63 -43
- package/build/lib/mixins/message-handlers.d.ts.map +1 -1
- package/build/lib/mixins/message-handlers.js +74 -57
- package/build/lib/mixins/message-handlers.js.map +1 -1
- package/build/lib/mixins/navigate.d.ts +39 -27
- package/build/lib/mixins/navigate.d.ts.map +1 -1
- package/build/lib/mixins/navigate.js +31 -31
- package/build/lib/mixins/navigate.js.map +1 -1
- package/build/tsconfig.tsbuildinfo +1 -1
- package/lib/mixins/{connect.js → connect.ts} +153 -113
- package/lib/mixins/message-handlers.ts +272 -0
- package/lib/mixins/{navigate.js → navigate.ts} +44 -41
- package/package.json +1 -1
- package/lib/mixins/message-handlers.js +0 -224
|
@@ -19,6 +19,8 @@ import {
|
|
|
19
19
|
getAdditionalBundleIds,
|
|
20
20
|
} from './property-accessors';
|
|
21
21
|
import { NEW_APP_CONNECTED_ERROR, EMPTY_PAGE_DICTIONARY_ERROR } from '../rpc/rpc-client';
|
|
22
|
+
import type { RemoteDebugger } from '../remote-debugger';
|
|
23
|
+
import type { AppDict, Page, AppIdKey, PageIdKey, AppPage } from '../types';
|
|
22
24
|
|
|
23
25
|
const APP_CONNECT_TIMEOUT_MS = 0;
|
|
24
26
|
const APP_CONNECT_INTERVAL_MS = 100;
|
|
@@ -32,11 +34,11 @@ const SAFARI_VIEW_BUNDLE_ID = 'com.apple.SafariViewService';
|
|
|
32
34
|
const WILDCARD_BUNDLE_ID = '*';
|
|
33
35
|
|
|
34
36
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
37
|
+
* Sends a connection key request to the Web Inspector.
|
|
38
|
+
* This method only waits to ensure the socket connection works, as the response
|
|
39
|
+
* from Web Inspector can take a long time.
|
|
38
40
|
*/
|
|
39
|
-
export async function setConnectionKey
|
|
41
|
+
export async function setConnectionKey(this: RemoteDebugger): Promise<void> {
|
|
40
42
|
this.log.debug('Sending connection key request');
|
|
41
43
|
|
|
42
44
|
// send but only wait to make sure the socket worked
|
|
@@ -45,12 +47,17 @@ export async function setConnectionKey () {
|
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
/**
|
|
50
|
+
* Establishes a connection to the remote debugger and initializes the RPC client.
|
|
51
|
+
* Sets up event listeners for debugger-level events and waits for applications
|
|
52
|
+
* to be reported if a timeout is specified.
|
|
48
53
|
*
|
|
49
|
-
* @
|
|
50
|
-
*
|
|
51
|
-
*
|
|
54
|
+
* @param timeout - Maximum time in milliseconds to wait for applications to be reported.
|
|
55
|
+
* Defaults to 0 (no waiting). If provided, the method will wait up to
|
|
56
|
+
* this duration for applications to appear in the app dictionary.
|
|
57
|
+
* @returns A promise that resolves to the application dictionary containing all
|
|
58
|
+
* connected applications.
|
|
52
59
|
*/
|
|
53
|
-
export async function connect
|
|
60
|
+
export async function connect(this: RemoteDebugger, timeout: number = APP_CONNECT_TIMEOUT_MS): Promise<AppDict> {
|
|
54
61
|
this.setup();
|
|
55
62
|
|
|
56
63
|
// initialize the rpc client
|
|
@@ -91,7 +98,7 @@ export async function connect (timeout = APP_CONNECT_TIMEOUT_MS) {
|
|
|
91
98
|
}
|
|
92
99
|
}
|
|
93
100
|
return this.appDict;
|
|
94
|
-
} catch (err) {
|
|
101
|
+
} catch (err: any) {
|
|
95
102
|
this.log.error(`Error setting connection key: ${err.message}`);
|
|
96
103
|
await this.disconnect();
|
|
97
104
|
throw err;
|
|
@@ -99,25 +106,36 @@ export async function connect (timeout = APP_CONNECT_TIMEOUT_MS) {
|
|
|
99
106
|
}
|
|
100
107
|
|
|
101
108
|
/**
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
* @returns {Promise<void>}
|
|
109
|
+
* Disconnects from the remote debugger by closing the RPC client connection,
|
|
110
|
+
* emitting a disconnect event, and performing cleanup via teardown.
|
|
105
111
|
*/
|
|
106
|
-
export async function disconnect
|
|
112
|
+
export async function disconnect(this: RemoteDebugger): Promise<void> {
|
|
107
113
|
await getRcpClient(this)?.disconnect();
|
|
108
114
|
this.emit(events.EVENT_DISCONNECT, true);
|
|
109
115
|
this.teardown();
|
|
110
116
|
}
|
|
111
117
|
|
|
112
118
|
/**
|
|
119
|
+
* Selects an application from the available connected applications.
|
|
120
|
+
* Searches for an app matching the provided URL and bundle IDs, then returns
|
|
121
|
+
* all pages from the selected application.
|
|
113
122
|
*
|
|
114
|
-
* @
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
* @param
|
|
118
|
-
*
|
|
123
|
+
* @param currentUrl - Optional URL to match when selecting an application.
|
|
124
|
+
* If provided, the method will try to find an app containing
|
|
125
|
+
* a page with this URL.
|
|
126
|
+
* @param maxTries - Maximum number of retry attempts when searching for an app.
|
|
127
|
+
* Defaults to SELECT_APP_RETRIES (20).
|
|
128
|
+
* @param ignoreAboutBlankUrl - If true, pages with 'about:blank' URL will be
|
|
129
|
+
* excluded from the results. Defaults to false.
|
|
130
|
+
* @returns A promise that resolves to an array of Page objects from the selected
|
|
131
|
+
* application. Returns an empty array if no applications are connected.
|
|
119
132
|
*/
|
|
120
|
-
export async function selectApp
|
|
133
|
+
export async function selectApp(
|
|
134
|
+
this: RemoteDebugger,
|
|
135
|
+
currentUrl: string | null = null,
|
|
136
|
+
maxTries: number = SELECT_APP_RETRIES,
|
|
137
|
+
ignoreAboutBlankUrl: boolean = false
|
|
138
|
+
): Promise<Page[]> {
|
|
121
139
|
this.log.debug('Selecting application');
|
|
122
140
|
|
|
123
141
|
const timer = new timing.Timer().start();
|
|
@@ -135,8 +153,7 @@ export async function selectApp (currentUrl = null, maxTries = SELECT_APP_RETRIE
|
|
|
135
153
|
// translate the dictionary into a useful form, and return to sender
|
|
136
154
|
this.log.debug(`Finally selecting app ${getAppIdKey(this)}`);
|
|
137
155
|
|
|
138
|
-
|
|
139
|
-
const fullPageArray = [];
|
|
156
|
+
const fullPageArray: Page[] = [];
|
|
140
157
|
for (const [app, info] of _.toPairs(getAppDict(this))) {
|
|
141
158
|
if (!_.isArray(info.pageArray) || !info.isActive) {
|
|
142
159
|
continue;
|
|
@@ -157,14 +174,22 @@ export async function selectApp (currentUrl = null, maxTries = SELECT_APP_RETRIE
|
|
|
157
174
|
}
|
|
158
175
|
|
|
159
176
|
/**
|
|
177
|
+
* Selects a specific page within an application and forwards socket setup.
|
|
178
|
+
* Optionally waits for the page to be ready based on the page load strategy.
|
|
160
179
|
*
|
|
161
|
-
* @
|
|
162
|
-
*
|
|
163
|
-
* @param
|
|
164
|
-
* @param
|
|
165
|
-
*
|
|
180
|
+
* @param appIdKey - The application identifier key. Will be prefixed with 'PID:'
|
|
181
|
+
* if not already present.
|
|
182
|
+
* @param pageIdKey - The page identifier key to select.
|
|
183
|
+
* @param skipReadyCheck - If true, skips the page readiness check. Defaults to false.
|
|
184
|
+
* When false, the method will wait for the page to be ready
|
|
185
|
+
* according to the configured page load strategy.
|
|
166
186
|
*/
|
|
167
|
-
export async function selectPage
|
|
187
|
+
export async function selectPage(
|
|
188
|
+
this: RemoteDebugger,
|
|
189
|
+
appIdKey: AppIdKey,
|
|
190
|
+
pageIdKey: PageIdKey,
|
|
191
|
+
skipReadyCheck: boolean = false
|
|
192
|
+
): Promise<void> {
|
|
168
193
|
const fullAppIdKey = _.startsWith(`${appIdKey}`, 'PID:') ? `${appIdKey}` : `PID:${appIdKey}`;
|
|
169
194
|
setAppIdKey(this, fullAppIdKey);
|
|
170
195
|
setPageIdKey(this, pageIdKey);
|
|
@@ -175,7 +200,7 @@ export async function selectPage (appIdKey, pageIdKey, skipReadyCheck = false) {
|
|
|
175
200
|
|
|
176
201
|
const pageReadinessDetector = skipReadyCheck ? undefined : {
|
|
177
202
|
timeoutMs: this.pageLoadMs,
|
|
178
|
-
readinessDetector: (
|
|
203
|
+
readinessDetector: (readyState: string) => this.isPageLoadingCompleted(readyState),
|
|
179
204
|
};
|
|
180
205
|
await this.requireRpcClient().selectPage(fullAppIdKey, pageIdKey, pageReadinessDetector);
|
|
181
206
|
|
|
@@ -183,16 +208,87 @@ export async function selectPage (appIdKey, pageIdKey, skipReadyCheck = false) {
|
|
|
183
208
|
}
|
|
184
209
|
|
|
185
210
|
/**
|
|
211
|
+
* Finds app keys based on assigned bundle IDs from the app dictionary.
|
|
212
|
+
* When bundleIds includes a wildcard ('*'), returns all app keys in the app dictionary.
|
|
213
|
+
* Also handles proxy applications that may act on behalf of other bundle IDs.
|
|
186
214
|
*
|
|
187
|
-
* @
|
|
188
|
-
*
|
|
189
|
-
* @
|
|
190
|
-
* @param {boolean} ignoreAboutBlankUrl
|
|
191
|
-
* @returns {Promise<import('../types').AppPage>}
|
|
215
|
+
* @param bundleIds - Array of bundle identifiers to match against. If the array
|
|
216
|
+
* contains a wildcard ('*'), all apps will be returned.
|
|
217
|
+
* @returns Array of application identifier keys that match the provided bundle IDs.
|
|
192
218
|
*/
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
219
|
+
export function getPossibleDebuggerAppKeys(this: RemoteDebugger, bundleIds: string[]): string[] {
|
|
220
|
+
const appDict = getAppDict(this);
|
|
221
|
+
|
|
222
|
+
if (bundleIds.includes(WILDCARD_BUNDLE_ID)) {
|
|
223
|
+
this.log.info(
|
|
224
|
+
'Returning all apps because the list of matching bundle identifiers includes a wildcard'
|
|
225
|
+
);
|
|
226
|
+
return _.keys(appDict);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// go through the possible bundle identifiers
|
|
230
|
+
const possibleBundleIds = _.uniq([
|
|
231
|
+
WEB_CONTENT_BUNDLE_ID,
|
|
232
|
+
WEB_CONTENT_PROCESS_BUNDLE_ID,
|
|
233
|
+
SAFARI_VIEW_PROCESS_BUNDLE_ID,
|
|
234
|
+
SAFARI_VIEW_BUNDLE_ID,
|
|
235
|
+
...bundleIds,
|
|
236
|
+
]);
|
|
237
|
+
this.log.debug(
|
|
238
|
+
`Checking for apps with matching bundle identifiers: ${possibleBundleIds.join(', ')}`
|
|
239
|
+
);
|
|
240
|
+
const proxiedAppIds: string[] = [];
|
|
241
|
+
for (const bundleId of possibleBundleIds) {
|
|
242
|
+
// now we need to determine if we should pick a proxy for this instead
|
|
243
|
+
for (const appId of appIdsForBundle(bundleId, appDict)) {
|
|
244
|
+
if (proxiedAppIds.includes(appId)) {
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
proxiedAppIds.push(appId);
|
|
249
|
+
this.log.debug(`Found app id key '${appId}' for bundle '${bundleId}'`);
|
|
250
|
+
for (const [key, data] of _.toPairs(appDict)) {
|
|
251
|
+
if (data.isProxy && data.hostId === appId && !proxiedAppIds.includes(key)) {
|
|
252
|
+
this.log.debug(
|
|
253
|
+
`Found separate bundleId '${data.bundleId}' ` +
|
|
254
|
+
`acting as proxy for '${bundleId}', with app id '${key}'`
|
|
255
|
+
);
|
|
256
|
+
proxiedAppIds.push(key);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
this.log.debug(
|
|
263
|
+
`You may also consider providing more values to 'additionalWebviewBundleIds' ` +
|
|
264
|
+
`capability to match other applications. Add a wildcard ('*') to match all apps.`
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
return _.uniq(proxiedAppIds);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Searches for an application matching the given criteria by retrying with
|
|
272
|
+
* exponential backoff. Attempts to connect to apps matching the bundle IDs
|
|
273
|
+
* and optionally filters by URL.
|
|
274
|
+
*
|
|
275
|
+
* @param currentUrl - Optional URL to match when searching for a page.
|
|
276
|
+
* If provided, only apps containing a page with this URL
|
|
277
|
+
* will be considered.
|
|
278
|
+
* @param maxTries - Maximum number of retry attempts.
|
|
279
|
+
* @param ignoreAboutBlankUrl - If true, pages with 'about:blank' URL will be
|
|
280
|
+
* ignored during the search.
|
|
281
|
+
* @returns A promise that resolves to an AppPage object containing the matched
|
|
282
|
+
* app ID key and page dictionary.
|
|
283
|
+
* @throws Error if no valid webapp can be connected after all retry attempts.
|
|
284
|
+
*/
|
|
285
|
+
async function searchForApp(
|
|
286
|
+
this: RemoteDebugger,
|
|
287
|
+
currentUrl: string | null,
|
|
288
|
+
maxTries: number,
|
|
289
|
+
ignoreAboutBlankUrl: boolean
|
|
290
|
+
): Promise<AppPage> {
|
|
291
|
+
const bundleIds: string[] = _.compact(
|
|
196
292
|
[
|
|
197
293
|
getBundleId(this),
|
|
198
294
|
...(getAdditionalBundleIds(this) ?? []),
|
|
@@ -200,9 +296,9 @@ async function searchForApp (currentUrl, maxTries, ignoreAboutBlankUrl) {
|
|
|
200
296
|
]
|
|
201
297
|
);
|
|
202
298
|
let retryCount = 0;
|
|
203
|
-
return
|
|
299
|
+
return await retryInterval(maxTries, SELECT_APP_RETRY_SLEEP_MS, async () => {
|
|
204
300
|
logApplicationDictionary.bind(this)();
|
|
205
|
-
const possibleAppIds = getPossibleDebuggerAppKeys.bind(this)(
|
|
301
|
+
const possibleAppIds = getPossibleDebuggerAppKeys.bind(this)(bundleIds);
|
|
206
302
|
this.log.debug(`Trying out the possible app ids: ${possibleAppIds.join(', ')} (try #${retryCount + 1} of ${maxTries})`);
|
|
207
303
|
for (const attemptedAppIdKey of possibleAppIds) {
|
|
208
304
|
const appInfo = getAppDict(this)[attemptedAppIdKey];
|
|
@@ -236,7 +332,7 @@ async function searchForApp (currentUrl, maxTries, ignoreAboutBlankUrl) {
|
|
|
236
332
|
} else {
|
|
237
333
|
this.log.debug('Received app, but no match was found. Trying again.');
|
|
238
334
|
}
|
|
239
|
-
} catch (err) {
|
|
335
|
+
} catch (err: any) {
|
|
240
336
|
if (![NEW_APP_CONNECTED_ERROR, EMPTY_PAGE_DICTIONARY_ERROR].some((msg) => msg === err.message)) {
|
|
241
337
|
this.log.debug(err.stack);
|
|
242
338
|
}
|
|
@@ -247,18 +343,25 @@ async function searchForApp (currentUrl, maxTries, ignoreAboutBlankUrl) {
|
|
|
247
343
|
throw new Error(
|
|
248
344
|
`Could not connect to a valid webapp. Make sure it is debuggable and has at least one active page.`
|
|
249
345
|
);
|
|
250
|
-
})
|
|
346
|
+
}) as Promise<AppPage>;
|
|
251
347
|
}
|
|
252
348
|
|
|
253
349
|
/**
|
|
350
|
+
* Searches through the application dictionary to find a page matching the given URL.
|
|
351
|
+
* Only considers active applications with non-empty page arrays.
|
|
254
352
|
*
|
|
255
|
-
* @
|
|
256
|
-
* @param
|
|
257
|
-
*
|
|
258
|
-
* @param
|
|
259
|
-
* @returns
|
|
353
|
+
* @param appsDict - The application dictionary to search through.
|
|
354
|
+
* @param currentUrl - Optional URL to match. If provided, only pages with this exact
|
|
355
|
+
* URL or with this URL followed by '/' will be considered.
|
|
356
|
+
* @param ignoreAboutBlankUrl - If true, pages with 'about:blank' URL will be ignored.
|
|
357
|
+
* @returns An AppPage object if a matching page is found, null otherwise.
|
|
260
358
|
*/
|
|
261
|
-
function searchForPage
|
|
359
|
+
function searchForPage(
|
|
360
|
+
this: RemoteDebugger,
|
|
361
|
+
appsDict: AppDict,
|
|
362
|
+
currentUrl: string | null = null,
|
|
363
|
+
ignoreAboutBlankUrl: boolean = false
|
|
364
|
+
): AppPage | null {
|
|
262
365
|
for (const appDict of _.values(appsDict)) {
|
|
263
366
|
if (!appDict || !appDict.isActive || !appDict.pageArray || _.isEmpty(appDict.pageArray)) {
|
|
264
367
|
continue;
|
|
@@ -278,10 +381,11 @@ function searchForPage (appsDict, currentUrl = null, ignoreAboutBlankUrl = false
|
|
|
278
381
|
}
|
|
279
382
|
|
|
280
383
|
/**
|
|
281
|
-
*
|
|
282
|
-
*
|
|
384
|
+
* Logs the current application dictionary to the debug log.
|
|
385
|
+
* Displays all applications, their properties, and their associated pages
|
|
386
|
+
* in a formatted structure.
|
|
283
387
|
*/
|
|
284
|
-
function logApplicationDictionary
|
|
388
|
+
function logApplicationDictionary(this: RemoteDebugger): void {
|
|
285
389
|
this.log.debug('Current applications available:');
|
|
286
390
|
for (const [app, info] of _.toPairs(getAppDict(this))) {
|
|
287
391
|
this.log.debug(` Application: "${app}"`);
|
|
@@ -302,67 +406,3 @@ function logApplicationDictionary () {
|
|
|
302
406
|
}
|
|
303
407
|
}
|
|
304
408
|
}
|
|
305
|
-
|
|
306
|
-
/**
|
|
307
|
-
* Find app keys based on assigned bundleIds from appDict
|
|
308
|
-
* When bundleIds includes a wildcard ('*'), returns all appKeys in appDict.
|
|
309
|
-
*
|
|
310
|
-
* @this {RemoteDebugger}
|
|
311
|
-
* @param {string[]} bundleIds
|
|
312
|
-
* @returns {string[]}
|
|
313
|
-
*/
|
|
314
|
-
export function getPossibleDebuggerAppKeys(bundleIds) {
|
|
315
|
-
const appDict = getAppDict(this);
|
|
316
|
-
|
|
317
|
-
if (bundleIds.includes(WILDCARD_BUNDLE_ID)) {
|
|
318
|
-
this.log.info(
|
|
319
|
-
'Returning all apps because the list of matching bundle identifiers includes a wildcard'
|
|
320
|
-
);
|
|
321
|
-
return _.keys(appDict);
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
// go through the possible bundle identifiers
|
|
325
|
-
const possibleBundleIds = _.uniq([
|
|
326
|
-
WEB_CONTENT_BUNDLE_ID,
|
|
327
|
-
WEB_CONTENT_PROCESS_BUNDLE_ID,
|
|
328
|
-
SAFARI_VIEW_PROCESS_BUNDLE_ID,
|
|
329
|
-
SAFARI_VIEW_BUNDLE_ID,
|
|
330
|
-
...bundleIds,
|
|
331
|
-
]);
|
|
332
|
-
this.log.debug(
|
|
333
|
-
`Checking for apps with matching bundle identifiers: ${possibleBundleIds.join(', ')}`
|
|
334
|
-
);
|
|
335
|
-
/** @type {string[]} */
|
|
336
|
-
const proxiedAppIds = [];
|
|
337
|
-
for (const bundleId of possibleBundleIds) {
|
|
338
|
-
// now we need to determine if we should pick a proxy for this instead
|
|
339
|
-
for (const appId of appIdsForBundle(bundleId, appDict)) {
|
|
340
|
-
if (proxiedAppIds.includes(appId)) {
|
|
341
|
-
continue;
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
proxiedAppIds.push(appId);
|
|
345
|
-
this.log.debug(`Found app id key '${appId}' for bundle '${bundleId}'`);
|
|
346
|
-
for (const [key, data] of _.toPairs(appDict)) {
|
|
347
|
-
if (data.isProxy && data.hostId === appId && !proxiedAppIds.includes(key)) {
|
|
348
|
-
this.log.debug(
|
|
349
|
-
`Found separate bundleId '${data.bundleId}' ` +
|
|
350
|
-
`acting as proxy for '${bundleId}', with app id '${key}'`
|
|
351
|
-
);
|
|
352
|
-
proxiedAppIds.push(key);
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
this.log.debug(
|
|
359
|
-
`You may also consider providing more values to 'additionalWebviewBundleIds' ` +
|
|
360
|
-
`capability to match other applications. Add a wildcard ('*') to match all apps.`
|
|
361
|
-
);
|
|
362
|
-
|
|
363
|
-
return _.uniq(proxiedAppIds);
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
/**
|
|
367
|
-
* @typedef {import('../remote-debugger').RemoteDebugger} RemoteDebugger
|
|
368
|
-
*/
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { events } from './events';
|
|
2
|
+
import {
|
|
3
|
+
pageArrayFromDict,
|
|
4
|
+
appInfoFromDict,
|
|
5
|
+
} from '../utils';
|
|
6
|
+
import _ from 'lodash';
|
|
7
|
+
import {
|
|
8
|
+
setAppIdKey,
|
|
9
|
+
getAppDict,
|
|
10
|
+
getAppIdKey,
|
|
11
|
+
getBundleId,
|
|
12
|
+
getNavigatingToPage,
|
|
13
|
+
setCurrentState,
|
|
14
|
+
setConnectedDrivers,
|
|
15
|
+
getSkippedApps,
|
|
16
|
+
} from './property-accessors';
|
|
17
|
+
import type { RemoteDebugger } from '../remote-debugger';
|
|
18
|
+
import type { StringRecord } from '@appium/types';
|
|
19
|
+
import type { AppDict } from '../types';
|
|
20
|
+
|
|
21
|
+
/*
|
|
22
|
+
* Generic callbacks used throughout the lifecycle of the Remote Debugger.
|
|
23
|
+
* These will be added to the prototype.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Handles page change notifications from the remote debugger.
|
|
28
|
+
* Updates the page array for the specified application and emits a page change
|
|
29
|
+
* event if the pages have actually changed and navigation is not in progress.
|
|
30
|
+
*
|
|
31
|
+
* @param err - Error object if an error occurred, null or undefined otherwise.
|
|
32
|
+
* @param appIdKey - The application identifier key for which pages have changed.
|
|
33
|
+
* @param pageDict - Dictionary containing the new page information.
|
|
34
|
+
*/
|
|
35
|
+
export async function onPageChange(
|
|
36
|
+
this: RemoteDebugger,
|
|
37
|
+
err: Error | null | undefined,
|
|
38
|
+
appIdKey: string,
|
|
39
|
+
pageDict: StringRecord
|
|
40
|
+
): Promise<void> {
|
|
41
|
+
if (_.isEmpty(pageDict)) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const currentPages = pageArrayFromDict(pageDict);
|
|
46
|
+
// save the page dict for this app
|
|
47
|
+
if (getAppDict(this)[appIdKey]) {
|
|
48
|
+
const previousPages = getAppDict(this)[appIdKey].pageArray;
|
|
49
|
+
// we have a pre-existing pageDict
|
|
50
|
+
if (previousPages && _.isEqual(previousPages, currentPages)) {
|
|
51
|
+
this.log.debug(
|
|
52
|
+
`Received page change notice for app '${appIdKey}' ` +
|
|
53
|
+
`but the listing has not changed. Ignoring.`
|
|
54
|
+
);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
// keep track of the page dictionary
|
|
58
|
+
getAppDict(this)[appIdKey].pageArray = currentPages;
|
|
59
|
+
this.log.debug(
|
|
60
|
+
`Pages changed for ${appIdKey}: ${JSON.stringify(previousPages)} -> ${JSON.stringify(currentPages)}`
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (getNavigatingToPage(this)) {
|
|
65
|
+
// in the middle of navigating, so reporting a page change will cause problems
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
this.emit(events.EVENT_PAGE_CHANGE, {
|
|
70
|
+
appIdKey: appIdKey.replace('PID:', ''),
|
|
71
|
+
pageArray: currentPages,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Handles notifications when a new application connects to the remote debugger.
|
|
77
|
+
* Updates the application dictionary with the new application information.
|
|
78
|
+
*
|
|
79
|
+
* @param err - Error object if an error occurred, null or undefined otherwise.
|
|
80
|
+
* @param dict - Dictionary containing the new application information including
|
|
81
|
+
* the WIRApplicationIdentifierKey.
|
|
82
|
+
*/
|
|
83
|
+
export async function onAppConnect(
|
|
84
|
+
this: RemoteDebugger,
|
|
85
|
+
err: Error | null | undefined,
|
|
86
|
+
dict: StringRecord
|
|
87
|
+
): Promise<void> {
|
|
88
|
+
const appIdKey = dict.WIRApplicationIdentifierKey;
|
|
89
|
+
this.log.debug(`Notified that new application '${appIdKey}' has connected`);
|
|
90
|
+
updateAppsWithDict.bind(this)(dict);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Handles notifications when an application disconnects from the remote debugger.
|
|
95
|
+
* Removes the application from the dictionary and attempts to find a replacement
|
|
96
|
+
* if the disconnected app was the currently selected one. Emits a disconnect event
|
|
97
|
+
* if no applications remain.
|
|
98
|
+
*
|
|
99
|
+
* @param err - Error object if an error occurred, null or undefined otherwise.
|
|
100
|
+
* @param dict - Dictionary containing the disconnected application information
|
|
101
|
+
* including the WIRApplicationIdentifierKey.
|
|
102
|
+
*/
|
|
103
|
+
export function onAppDisconnect(
|
|
104
|
+
this: RemoteDebugger,
|
|
105
|
+
err: Error | null | undefined,
|
|
106
|
+
dict: StringRecord
|
|
107
|
+
): void {
|
|
108
|
+
const appIdKey = dict.WIRApplicationIdentifierKey;
|
|
109
|
+
this.log.debug(`Application '${appIdKey}' disconnected. Removing from app dictionary.`);
|
|
110
|
+
this.log.debug(`Current app is '${getAppIdKey(this)}'`);
|
|
111
|
+
|
|
112
|
+
// get rid of the entry in our app dictionary,
|
|
113
|
+
// since it is no longer available
|
|
114
|
+
delete getAppDict(this)[appIdKey];
|
|
115
|
+
|
|
116
|
+
// if the disconnected app is the one we are connected to, try to find another
|
|
117
|
+
if (getAppIdKey(this) === appIdKey) {
|
|
118
|
+
this.log.debug(`No longer have app id. Attempting to find new one.`);
|
|
119
|
+
setAppIdKey(this, getDebuggerAppKey.bind(this)(getBundleId(this) as string));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (_.isEmpty(getAppDict(this))) {
|
|
123
|
+
// this means we no longer have any apps. what the what?
|
|
124
|
+
this.log.debug('Main app disconnected. Disconnecting altogether.');
|
|
125
|
+
this.emit(events.EVENT_DISCONNECT, true);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Handles notifications when an application's information is updated.
|
|
131
|
+
* Updates the application dictionary with the new information while preserving
|
|
132
|
+
* any existing page array data.
|
|
133
|
+
*
|
|
134
|
+
* @param err - Error object if an error occurred, null or undefined otherwise.
|
|
135
|
+
* @param dict - Dictionary containing the updated application information.
|
|
136
|
+
*/
|
|
137
|
+
export async function onAppUpdate(
|
|
138
|
+
this: RemoteDebugger,
|
|
139
|
+
err: Error | null | undefined,
|
|
140
|
+
dict: StringRecord
|
|
141
|
+
): Promise<void> {
|
|
142
|
+
this.log.debug(`Notified that an application has been updated`);
|
|
143
|
+
updateAppsWithDict.bind(this)(dict);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Handles notifications containing the list of connected drivers.
|
|
148
|
+
* Updates the internal connected drivers list with the received information.
|
|
149
|
+
*
|
|
150
|
+
* @param err - Error object if an error occurred, null or undefined otherwise.
|
|
151
|
+
* @param drivers - Dictionary containing the connected driver list with
|
|
152
|
+
* WIRDriverDictionaryKey.
|
|
153
|
+
*/
|
|
154
|
+
export function onConnectedDriverList(
|
|
155
|
+
this: RemoteDebugger,
|
|
156
|
+
err: Error | null | undefined,
|
|
157
|
+
drivers: StringRecord
|
|
158
|
+
): void {
|
|
159
|
+
setConnectedDrivers(this, drivers.WIRDriverDictionaryKey);
|
|
160
|
+
this.log.debug(`Received connected driver list: ${JSON.stringify(this.connectedDrivers)}`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Handles notifications about the current automation availability state.
|
|
165
|
+
* This state changes when 'Remote Automation' setting in Safari's advanced settings
|
|
166
|
+
* is toggled. The state can be either WIRAutomationAvailabilityAvailable or
|
|
167
|
+
* WIRAutomationAvailabilityNotAvailable.
|
|
168
|
+
*
|
|
169
|
+
* @param err - Error object if an error occurred, null or undefined otherwise.
|
|
170
|
+
* @param state - Dictionary containing the automation availability state with
|
|
171
|
+
* WIRAutomationAvailabilityKey.
|
|
172
|
+
*/
|
|
173
|
+
export function onCurrentState(
|
|
174
|
+
this: RemoteDebugger,
|
|
175
|
+
err: Error | null | undefined,
|
|
176
|
+
state: StringRecord
|
|
177
|
+
): void {
|
|
178
|
+
setCurrentState(this, state.WIRAutomationAvailabilityKey);
|
|
179
|
+
// This state changes when 'Remote Automation' in 'Settings app' > 'Safari' > 'Advanced' > 'Remote Automation' changes
|
|
180
|
+
// WIRAutomationAvailabilityAvailable or WIRAutomationAvailabilityNotAvailable
|
|
181
|
+
this.log.debug(`Received connected automation availability state: ${JSON.stringify(this.currentState)}`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Handles notifications containing the list of connected applications.
|
|
186
|
+
* Translates the received information into the application dictionary format,
|
|
187
|
+
* filtering out any applications that are in the skipped apps list.
|
|
188
|
+
*
|
|
189
|
+
* @param err - Error object if an error occurred, null or undefined otherwise.
|
|
190
|
+
* @param apps - Dictionary containing the connected applications list.
|
|
191
|
+
*/
|
|
192
|
+
export async function onConnectedApplicationList(
|
|
193
|
+
this: RemoteDebugger,
|
|
194
|
+
err: Error | null | undefined,
|
|
195
|
+
apps: StringRecord
|
|
196
|
+
): Promise<void> {
|
|
197
|
+
this.log.debug(`Received connected applications list: ${_.keys(apps).join(', ')}`);
|
|
198
|
+
|
|
199
|
+
// translate the received information into an easier-to-manage
|
|
200
|
+
// hash with app id as key, and app info as value
|
|
201
|
+
const newDict: AppDict = {};
|
|
202
|
+
for (const dict of _.values(apps)) {
|
|
203
|
+
const [id, entry] = appInfoFromDict(dict);
|
|
204
|
+
if (getSkippedApps(this).includes(entry.name)) {
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
newDict[id] = entry;
|
|
208
|
+
}
|
|
209
|
+
// update the object's list of apps
|
|
210
|
+
_.defaults(getAppDict(this), newDict);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Given a bundle ID, finds the correct remote debugger app identifier key
|
|
215
|
+
* that is currently connected. Also handles proxy applications that may act
|
|
216
|
+
* on behalf of the requested bundle ID.
|
|
217
|
+
*
|
|
218
|
+
* @param bundleId - The bundle identifier to search for.
|
|
219
|
+
* @returns The application identifier key if found, undefined otherwise.
|
|
220
|
+
* If a proxy application is found, returns the proxy's app ID instead.
|
|
221
|
+
*/
|
|
222
|
+
export function getDebuggerAppKey(this: RemoteDebugger, bundleId: string): string | undefined {
|
|
223
|
+
let appId: string | undefined;
|
|
224
|
+
for (const [key, data] of _.toPairs(getAppDict(this))) {
|
|
225
|
+
if (data.bundleId === bundleId) {
|
|
226
|
+
appId = key;
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
// now we need to determine if we should pick a proxy for this instead
|
|
231
|
+
if (appId) {
|
|
232
|
+
this.log.debug(`Found app id key '${appId}' for bundle '${bundleId}'`);
|
|
233
|
+
let proxyAppId: string | undefined;
|
|
234
|
+
for (const [key, data] of _.toPairs(getAppDict(this))) {
|
|
235
|
+
if (data.isProxy && data.hostId === appId) {
|
|
236
|
+
this.log.debug(`Found separate bundleId '${data.bundleId}' ` +
|
|
237
|
+
`acting as proxy for '${bundleId}', with app id '${key}'`);
|
|
238
|
+
// set the app id... the last one will be used, so just keep re-assigning
|
|
239
|
+
proxyAppId = key;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
if (proxyAppId) {
|
|
243
|
+
appId = proxyAppId;
|
|
244
|
+
this.log.debug(`Using proxied app id '${appId}'`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return appId;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Updates the application dictionary with information from the provided dictionary.
|
|
253
|
+
* Preserves existing page array data if the application already exists in the dictionary.
|
|
254
|
+
* Attempts to set the app ID key if one is not currently set.
|
|
255
|
+
*
|
|
256
|
+
* @param dict - Dictionary containing application information to add or update.
|
|
257
|
+
*/
|
|
258
|
+
function updateAppsWithDict(this: RemoteDebugger, dict: StringRecord): void {
|
|
259
|
+
// get the dictionary entry into a nice form, and add it to the
|
|
260
|
+
// application dictionary
|
|
261
|
+
const [id, entry] = appInfoFromDict(dict);
|
|
262
|
+
if (getAppDict(this)[id]?.pageArray) {
|
|
263
|
+
// preserve the page dictionary for this entry
|
|
264
|
+
entry.pageArray = getAppDict(this)[id].pageArray;
|
|
265
|
+
}
|
|
266
|
+
getAppDict(this)[id] = entry;
|
|
267
|
+
|
|
268
|
+
// try to get the app id from our connected apps
|
|
269
|
+
if (!getAppIdKey(this)) {
|
|
270
|
+
setAppIdKey(this, getDebuggerAppKey.bind(this)(getBundleId(this) as string));
|
|
271
|
+
}
|
|
272
|
+
}
|