appium-remote-debugger
Version:
Appium proxy for Remote Debugger protocol
220 lines • 9.37 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.WEB_CONTENT_BUNDLE_ID = void 0;
exports.appInfoFromDict = appInfoFromDict;
exports.pageArrayFromDict = pageArrayFromDict;
exports.appIdsForBundle = appIdsForBundle;
exports.checkParams = checkParams;
exports.simpleStringify = simpleStringify;
exports.convertJavascriptEvaluationResult = convertJavascriptEvaluationResult;
exports.getModuleProperties = getModuleProperties;
exports.canUseWebInspectorShim = canUseWebInspectorShim;
const lodash_1 = __importDefault(require("lodash"));
const base_driver_1 = require("@appium/base-driver");
const support_1 = require("@appium/support");
const node_fs_1 = __importDefault(require("node:fs"));
const node_path_1 = __importDefault(require("node:path"));
const MODULE_NAME = 'appium-remote-debugger';
exports.WEB_CONTENT_BUNDLE_ID = 'com.apple.WebKit.WebContent';
const INACTIVE_APP_CODE = 0;
// values for the page `WIRTypeKey` entry
const ACCEPTED_PAGE_TYPES = [
'WIRTypeWeb', // up to iOS 11.3
'WIRTypeWebPage', // iOS 11.4
'WIRTypePage', // iOS 11.4 webview
];
exports.RESPONSE_LOG_LENGTH = 100;
/**
* Takes a dictionary from the remote debugger and converts it into a more
* manageable AppInfo object with understandable keys.
*
* @param dict - Dictionary from the remote debugger containing application information.
* @returns A tuple containing the application ID and the AppInfo object.
*/
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)
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];
}
/**
* Takes a dictionary from the remote debugger and converts it into an array
* of Page objects with understandable keys. Filters out non-web pages.
*
* @param pageDict - Dictionary from the remote debugger containing page information.
* @returns An array of Page objects representing the available pages.
*/
function pageArrayFromDict(pageDict) {
return (lodash_1.default.values(pageDict)
// count only WIRTypeWeb pages and ignore all others (WIRTypeJavaScript etc)
.filter((dict) => lodash_1.default.isUndefined(dict.WIRTypeKey) || ACCEPTED_PAGE_TYPES.includes(dict.WIRTypeKey))
.map((dict) => ({
id: dict.WIRPageIdentifierKey,
title: dict.WIRTitleKey,
url: dict.WIRURLKey,
isKey: !lodash_1.default.isUndefined(dict.WIRConnectionIdentifierKey),
})));
}
/**
* Finds all application identifier keys that match the given bundle ID.
* If no matches are found and the bundle ID is not WEB_CONTENT_BUNDLE_ID,
* falls back to searching for WEB_CONTENT_BUNDLE_ID.
*
* @param bundleId - The bundle identifier to search for.
* @param appDict - The application dictionary to search in.
* @returns An array of unique application identifier keys matching the bundle ID.
*/
function appIdsForBundle(bundleId, appDict) {
const appIds = lodash_1.default.toPairs(appDict)
.filter(([, data]) => data.bundleId === bundleId)
.map(([key]) => key);
// if nothing is found, try to get the generic app
if (appIds.length === 0 && bundleId !== exports.WEB_CONTENT_BUNDLE_ID) {
return appIdsForBundle(exports.WEB_CONTENT_BUNDLE_ID, appDict);
}
return lodash_1.default.uniq(appIds);
}
/**
* Validates that all parameters in the provided object have non-nil values.
* Throws an error if any parameters are missing (null or undefined).
*
* @template T - The type of the parameters object.
* @param params - An object containing parameters to validate.
* @returns The same parameters object if all values are valid.
* @throws Error if any parameters are missing, listing all missing parameter names.
*/
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(', ')}`);
}
return params;
}
/**
* Converts a value to a JSON string, removing noisy function properties
* that can muddy the logs.
*
* @param value - The value to stringify.
* @param multiline - If true, formats the JSON with indentation. Defaults to false.
* @returns A JSON string representation of the value with noisy properties removed.
*/
function simpleStringify(value, multiline = false) {
if (!value) {
return JSON.stringify(value);
}
const cleanValue = removeNoisyProperties(lodash_1.default.clone(value));
return multiline ? JSON.stringify(cleanValue, null, 2) : JSON.stringify(cleanValue);
}
/**
* Converts the result from a JavaScript evaluation in the remote debugger
* into a usable format. Handles errors, serialization, and cleans up noisy
* function properties.
*
* @param res - The raw result from the remote debugger's JavaScript evaluation.
* @returns The cleaned and converted result value.
* @throws Error if the result is undefined, has an unexpected type, or contains
* an error status code.
*/
function convertJavascriptEvaluationResult(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: exports.RESPONSE_LOG_LENGTH })}`);
}
else if (lodash_1.default.isString(res)) {
try {
res = JSON.parse(res);
}
catch {
// 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;
return removeNoisyProperties(value);
}
/**
* Calculates the path to the current module's root folder.
* The result is memoized for performance.
*
* @returns The full path to the module root directory.
* @throws Error if the module root folder cannot be determined.
*/
exports.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;
});
/**
* Reads and parses the package.json file from the module root.
*
* @returns The parsed package.json contents as a StringRecord.
*/
function getModuleProperties() {
const fullPath = node_path_1.default.resolve((0, exports.getModuleRoot)(), 'package.json');
return JSON.parse(node_fs_1.default.readFileSync(fullPath, 'utf8'));
}
/**
* Determines if the WebInspector shim can be used based on the provided iOS platform version.
* @param platformVersion - The iOS platform version string (e.g., "18.0", "17.5.1")
* @returns true if the WebInspector shim can be used, false otherwise
*/
function canUseWebInspectorShim(platformVersion) {
return !!platformVersion && support_1.util.compareVersions(platformVersion, '>=', '18.0');
}
/**
* Removes noisy function properties from an object that can muddy the logs.
* These properties are often added by JavaScript number objects and similar.
*
* @param obj - The object to clean.
* @returns The cleaned object.
*/
function removeNoisyProperties(obj) {
if (lodash_1.default.isObject(obj)) {
for (const property of ['ceil', 'clone', 'floor', 'round', 'scale', 'toString']) {
delete obj[property];
}
}
return obj;
}
//# sourceMappingURL=utils.js.map