appium-remote-debugger
Version:
Appium proxy for Remote Debugger protocol
294 lines • 11.4 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getModuleRoot = exports.RESPONSE_LOG_LENGTH = exports.convertResult = exports.deferredPromise = exports.simpleStringify = exports.checkParams = exports.getPossibleDebuggerAppKeys = exports.getDebuggerAppKey = exports.pageArrayFromDict = exports.appInfoFromDict = void 0;
const logger_1 = __importDefault(require("./logger"));
const lodash_1 = __importDefault(require("lodash"));
const bluebird_1 = __importDefault(require("bluebird"));
const base_driver_1 = require("@appium/base-driver");
const support_1 = require("@appium/support");
const MODULE_NAME = 'appium-remote-debugger';
const WEB_CONTENT_BUNDLE_ID = 'com.apple.WebKit.WebContent';
const WEB_CONTENT_PROCESS_BUNDLE_ID = 'process-com.apple.WebKit.WebContent';
const SAFARI_VIEW_PROCESS_BUNDLE_ID = 'process-SafariViewService';
const SAFARI_VIEW_BUNDLE_ID = 'com.apple.SafariViewService';
const WILDCARD_BUNDLE_ID = '*';
const INACTIVE_APP_CODE = 0;
// values for the page `WIRTypeKey` entry
const ACCEPTED_PAGE_TYPES = [
'WIRTypeWeb',
'WIRTypeWebPage',
'WIRTypePage', // iOS 11.4 webview
];
const RESPONSE_LOG_LENGTH = 100;
exports.RESPONSE_LOG_LENGTH = RESPONSE_LOG_LENGTH;
/**
* @typedef {Object} DeferredPromise
* @property {B<any>} promise
* @property {(...args: any[]) => void} resolve
* @property {(err?: Error) => void} reject
*/
/**
* @typedef {Object} AppInfo
* @property {string} id
* @property {boolean} isProxy
* @property {string} name
* @property {string} bundleId
* @property {string} hostId
* @property {boolean} isActive
* @property {boolean|string} isAutomationEnabled
* @property {any[]|undefined|DeferredPromise} [pageArray]
*/
/**
* Takes a dictionary from the remote debugger and makes a more manageable
* dictionary whose keys are understandable
*
* @param {Record<string, any>} dict
* @returns {[string, AppInfo]}
*/
function appInfoFromDict(dict) {
const id = dict.WIRApplicationIdentifierKey;
const isProxy = lodash_1.default.isString(dict.WIRIsApplicationProxyKey)
? dict.WIRIsApplicationProxyKey.toLowerCase() === 'true'
: dict.WIRIsApplicationProxyKey;
// automation enabled can be either from the keys
// - WIRRemoteAutomationEnabledKey (boolean)
// - WIRAutomationAvailabilityKey (string or boolean)
/** @type {boolean|string} */
let isAutomationEnabled = !!dict.WIRRemoteAutomationEnabledKey;
if (lodash_1.default.has(dict, 'WIRAutomationAvailabilityKey')) {
if (lodash_1.default.isString(dict.WIRAutomationAvailabilityKey)) {
isAutomationEnabled = dict.WIRAutomationAvailabilityKey === 'WIRAutomationAvailabilityUnknown'
? 'Unknown'
: dict.WIRAutomationAvailabilityKey === 'WIRAutomationAvailabilityAvailable';
}
else {
isAutomationEnabled = !!dict.WIRAutomationAvailabilityKey;
}
}
const entry = {
id,
isProxy,
name: dict.WIRApplicationNameKey,
bundleId: dict.WIRApplicationBundleIdentifierKey,
hostId: dict.WIRHostApplicationIdentifierKey,
isActive: dict.WIRIsApplicationActiveKey !== INACTIVE_APP_CODE,
isAutomationEnabled,
};
return [id, entry];
}
exports.appInfoFromDict = appInfoFromDict;
/*
* Take a dictionary from the remote debugger and makes a more manageable
* dictionary of pages available.
*/
function pageArrayFromDict(pageDict) {
if (pageDict.id) {
// the page is already translated, so wrap in an array and pass back
return [pageDict];
}
let newPageArray = [];
for (const dict of lodash_1.default.values(pageDict)) {
// count only WIRTypeWeb pages and ignore all others (WIRTypeJavaScript etc)
if (lodash_1.default.isUndefined(dict.WIRTypeKey) || ACCEPTED_PAGE_TYPES.includes(dict.WIRTypeKey)) {
newPageArray.push({
id: dict.WIRPageIdentifierKey,
title: dict.WIRTitleKey,
url: dict.WIRURLKey,
isKey: !lodash_1.default.isUndefined(dict.WIRConnectionIdentifierKey),
});
}
}
return newPageArray;
}
exports.pageArrayFromDict = pageArrayFromDict;
/**
* Given a bundle id, finds the correct remote debugger app that is
* connected.
* @param {string} bundleId
* @param {Record<string, any>} appDict
* @returns {string|undefined}
*/
function getDebuggerAppKey(bundleId, appDict) {
let appId;
for (const [key, data] of lodash_1.default.toPairs(appDict)) {
if (data.bundleId === bundleId) {
appId = key;
break;
}
}
// now we need to determine if we should pick a proxy for this instead
if (appId) {
logger_1.default.debug(`Found app id key '${appId}' for bundle '${bundleId}'`);
let proxyAppId;
for (const [key, data] of lodash_1.default.toPairs(appDict)) {
if (data.isProxy && data.hostId === appId) {
logger_1.default.debug(`Found separate bundleId '${data.bundleId}' ` +
`acting as proxy for '${bundleId}', with app id '${key}'`);
// set the app id... the last one will be used, so just keep re-assigning
proxyAppId = key;
}
}
if (proxyAppId) {
appId = proxyAppId;
logger_1.default.debug(`Using proxied app id '${appId}'`);
}
}
return appId;
}
exports.getDebuggerAppKey = getDebuggerAppKey;
/**
*
* @param {string} bundleId
* @param {Record<string, any>} appDict
* @returns {string|undefined}
*/
function appIdForBundle(bundleId, appDict) {
let appId;
for (const [key, data] of lodash_1.default.toPairs(appDict)) {
if (data.bundleId.endsWith(bundleId)) {
appId = key;
break;
}
}
// if nothing is found, try to get the generic app
if (!appId && bundleId !== WEB_CONTENT_BUNDLE_ID) {
return appIdForBundle(WEB_CONTENT_BUNDLE_ID, appDict);
}
return appId;
}
/**
* Find app keys based on assigned bundleIds from appDict
* When bundleIds includes a wildcard ('*'), returns all appKeys in appDict.
* @param {string[]} bundleIds
* @param {Record<string, any>} appDict
* @returns {string[]}
*/
function getPossibleDebuggerAppKeys(bundleIds, appDict) {
if (bundleIds.includes(WILDCARD_BUNDLE_ID)) {
logger_1.default.debug('Skip checking bundle identifiers because the bundleIds includes a wildcard');
return lodash_1.default.uniq(Object.keys(appDict));
}
let proxiedAppIds = [];
// go through the possible bundle identifiers
const possibleBundleIds = lodash_1.default.uniq([
WEB_CONTENT_BUNDLE_ID,
WEB_CONTENT_PROCESS_BUNDLE_ID,
SAFARI_VIEW_PROCESS_BUNDLE_ID,
SAFARI_VIEW_BUNDLE_ID,
WILDCARD_BUNDLE_ID,
...bundleIds,
]);
logger_1.default.debug(`Checking for bundle identifiers: ${possibleBundleIds.join(', ')}`);
for (const bundleId of possibleBundleIds) {
const appId = appIdForBundle(bundleId, appDict);
// now we need to determine if we should pick a proxy for this instead
if (appId) {
proxiedAppIds.push(appId);
logger_1.default.debug(`Found app id key '${appId}' for bundle '${bundleId}'`);
for (const [key, data] of lodash_1.default.toPairs(appDict)) {
if (data.isProxy && data.hostId === appId) {
logger_1.default.debug(`Found separate bundleId '${data.bundleId}' ` +
`acting as proxy for '${bundleId}', with app id '${key}'`);
proxiedAppIds.push(key);
}
}
}
}
return lodash_1.default.uniq(proxiedAppIds);
}
exports.getPossibleDebuggerAppKeys = getPossibleDebuggerAppKeys;
function checkParams(params) {
// check if all parameters have a value
const errors = lodash_1.default.toPairs(params)
.filter(([, value]) => lodash_1.default.isNil(value))
.map(([param]) => param);
if (errors.length) {
throw new Error(`Missing ${support_1.util.pluralize('parameter', errors.length)}: ${errors.join(', ')}`);
}
}
exports.checkParams = checkParams;
function simpleStringify(value, multiline = false) {
if (!value) {
return JSON.stringify(value);
}
// we get back objects sometimes with string versions of functions
// which muddy the logs
let cleanValue = lodash_1.default.clone(value);
for (const property of ['ceil', 'clone', 'floor', 'round', 'scale', 'toString']) {
delete cleanValue[property];
}
return multiline ? JSON.stringify(cleanValue, null, 2) : JSON.stringify(cleanValue);
}
exports.simpleStringify = simpleStringify;
/**
* @returns {DeferredPromise}
*/
function deferredPromise() {
// http://bluebirdjs.com/docs/api/deferred-migration.html
/** @type {(...args: any[]) => void} */
let resolve;
/** @type {(err?: Error) => void} */
let reject;
const promise = new bluebird_1.default((res, rej) => {
resolve = res;
reject = rej;
});
return {
promise,
// @ts-ignore It will be assigned eventually
resolve,
// @ts-ignore It will be assigned eventually
reject
};
}
exports.deferredPromise = deferredPromise;
function convertResult(res) {
if (lodash_1.default.isUndefined(res)) {
throw new Error(`Did not get OK result from remote debugger. Result was: ${lodash_1.default.truncate(simpleStringify(res), { length: RESPONSE_LOG_LENGTH })}`);
}
else if (lodash_1.default.isString(res)) {
try {
res = JSON.parse(res);
}
catch (err) {
// we might get a serialized object, but we might not
// if we get here, it is just a value
}
}
else if (!lodash_1.default.isObject(res)) {
throw new Error(`Result has unexpected type: (${typeof res}).`);
}
if (res.status && res.status !== 0) {
// we got some form of error.
throw (0, base_driver_1.errorFromMJSONWPStatusCode)(res.status, res.value.message || res.value);
}
// with either have an object with a `value` property (even if `null`),
// or a plain object
const value = lodash_1.default.has(res, 'value') ? res.value : res;
// get rid of noisy functions on objects
if (lodash_1.default.isObject(value)) {
for (const property of ['ceil', 'clone', 'floor', 'round', 'scale', 'toString']) {
delete value[property];
}
}
return value;
}
exports.convertResult = convertResult;
/**
* Calculates the path to the current module's root folder
*
* @returns {string} The full path to module root
* @throws {Error} If the current module root folder cannot be determined
*/
const getModuleRoot = lodash_1.default.memoize(function getModuleRoot() {
const root = support_1.node.getModuleRootSync(MODULE_NAME, __filename);
if (!root) {
throw new Error(`Cannot find the root folder of the ${MODULE_NAME} Node.js module`);
}
return root;
});
exports.getModuleRoot = getModuleRoot;
//# sourceMappingURL=utils.js.map