UNPKG

appium-remote-debugger

Version:
294 lines 11.4 kB
"use strict"; 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