appium-remote-debugger
Version:
Appium proxy for Remote Debugger protocol
267 lines • 11.8 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.RPC_RESPONSE_TIMEOUT_MS = exports.REMOTE_DEBUGGER_PORT = exports.RemoteDebugger = void 0;
const events_1 = require("events");
const logger_1 = __importDefault(require("./logger"));
const rpc_1 = require("./rpc");
const utils_1 = require("./utils");
const mixins_1 = require("./mixins");
const lodash_1 = __importDefault(require("lodash"));
const bluebird_1 = __importDefault(require("bluebird"));
const path_1 = __importDefault(require("path"));
const async_lock_1 = __importDefault(require("async-lock"));
const REMOTE_DEBUGGER_PORT = 27753;
exports.REMOTE_DEBUGGER_PORT = REMOTE_DEBUGGER_PORT;
const SAFARI_BUNDLE_ID = 'com.apple.mobilesafari';
/* How many milliseconds to wait for webkit to return a response before timing out */
const RPC_RESPONSE_TIMEOUT_MS = 5000;
exports.RPC_RESPONSE_TIMEOUT_MS = RPC_RESPONSE_TIMEOUT_MS;
const PAGE_READY_TIMEOUT = 5000;
const GARBAGE_COLLECT_TIMEOUT = 5000;
class RemoteDebugger extends events_1.EventEmitter {
/*
* The constructor takes an opts hash with the following properties:
* - bundleId - id of the app being connected to
* - additionalBundleIds - array of possible bundle ids that the inspector
* could return
* - platformVersion - version of iOS
* - useNewSafari - for web inspector, whether this is a new Safari instance
* - pageLoadMs - the time, in ms, that should be waited for page loading
* - host - the remote debugger's host address
* - port - the remote debugger port through which to communicate
* - logAllCommunication - log plists sent and received from Web Inspector
* - logAllCommunicationHexDump - log communication from Web Inspector as hex dump
* - socketChunkSize - size, in bytes, of chunks of data sent to Web Inspector (real device only)
* - webInspectorMaxFrameLength - The maximum size in bytes of a single data frame
* in the device communication protocol
*/
constructor(opts = {}) {
super();
// eslint-disable-next-line @typescript-eslint/no-var-requires
logger_1.default.info(`Remote Debugger version ${require(path_1.default.resolve((0, utils_1.getModuleRoot)(), 'package.json')).version}`);
const { bundleId, additionalBundleIds = [], platformVersion, isSafari = true, includeSafari = false, useNewSafari = false, pageLoadMs, host, port = REMOTE_DEBUGGER_PORT, socketPath, pageReadyTimeout = PAGE_READY_TIMEOUT, remoteDebugProxy, garbageCollectOnExecute = false, logFullResponse = false, logAllCommunication = false, logAllCommunicationHexDump = false, webInspectorMaxFrameLength, socketChunkSize, fullPageInitialization, } = opts;
this.bundleId = bundleId;
this.additionalBundleIds = additionalBundleIds;
this.platformVersion = platformVersion;
this.isSafari = isSafari;
this.includeSafari = includeSafari;
this.useNewSafari = useNewSafari;
this.pageLoadMs = pageLoadMs;
logger_1.default.debug(`useNewSafari --> ${this.useNewSafari}`);
this.garbageCollectOnExecute = garbageCollectOnExecute;
this.host = host;
this.port = port;
this.socketPath = socketPath;
this.remoteDebugProxy = remoteDebugProxy;
this.pageReadyTimeout = pageReadyTimeout;
this.logAllCommunication = lodash_1.default.isNil(logAllCommunication) ? !!logFullResponse : !!logAllCommunication;
this.logAllCommunicationHexDump = logAllCommunicationHexDump;
this.socketChunkSize = socketChunkSize;
if (lodash_1.default.isInteger(webInspectorMaxFrameLength)) {
this.webInspectorMaxFrameLength = webInspectorMaxFrameLength;
}
this.fullPageInitialization = fullPageInitialization;
this._lock = new async_lock_1.default();
}
setup() {
// app handling configuration
this.appDict = {};
this.appIdKey = null;
this.pageIdKey = null;
this.pageLoading = false;
this._navigatingToPage = false;
this.allowNavigationWithoutReload = false;
this.rpcClient = null;
this._clientEventListeners = {};
}
teardown() {
logger_1.default.debug('Cleaning up listeners');
this.appDict = {};
this.appIdKey = null;
this.pageIdKey = null;
this.pageLoading = false;
this.rpcClient = null;
this.removeAllListeners(RemoteDebugger.EVENT_PAGE_CHANGE);
this.removeAllListeners(RemoteDebugger.EVENT_DISCONNECT);
}
initRpcClient() {
this.rpcClient = new rpc_1.RpcClientSimulator({
bundleId: this.bundleId,
platformVersion: this.platformVersion,
isSafari: this.isSafari,
host: this.host,
port: this.port,
socketPath: this.socketPath,
messageProxy: this.remoteDebugProxy,
logAllCommunication: this.logAllCommunication,
logAllCommunicationHexDump: this.logAllCommunicationHexDump,
fullPageInitialization: this.fullPageInitialization,
webInspectorMaxFrameLength: this.webInspectorMaxFrameLength,
});
}
get isConnected() {
return !!this.rpcClient?.isConnected;
}
async launchSafari() {
if (!this.rpcClient) {
throw new Error(`rpcClient is undefined. Has 'initRpcClient' been called before?`);
}
await this.rpcClient.send('launchApplication', {
bundleId: SAFARI_BUNDLE_ID
});
}
async startTimeline(fn) {
if (!this.rpcClient) {
throw new Error(`rpcClient is undefined. Has 'initRpcClient' been called before?`);
}
logger_1.default.debug('Starting to record the timeline');
this.rpcClient.on('Timeline.eventRecorded', fn);
return await this.rpcClient.send('Timeline.start', {
appIdKey: this.appIdKey,
pageIdKey: this.pageIdKey,
});
}
async stopTimeline() {
if (!this.rpcClient) {
throw new Error(`rpcClient is undefined. Has 'initRpcClient' been called before?`);
}
logger_1.default.debug('Stopping to record the timeline');
await this.rpcClient.send('Timeline.stop', {
appIdKey: this.appIdKey,
pageIdKey: this.pageIdKey,
});
}
/*
* Keep track of the client event listeners so they can be removed
*/
addClientEventListener(eventName, listener) {
if (!this.rpcClient) {
throw new Error(`rpcClient is undefined. Has 'initRpcClient' been called before?`);
}
this._clientEventListeners[eventName] = this._clientEventListeners[eventName] || [];
this._clientEventListeners[eventName].push(listener);
this.rpcClient.on(eventName, listener);
}
removeClientEventListener(eventName) {
if (!this.rpcClient) {
throw new Error(`rpcClient is undefined. Has 'initRpcClient' been called before?`);
}
for (const listener of (this._clientEventListeners[eventName] || [])) {
this.rpcClient.off(eventName, listener);
}
}
startConsole(listener) {
logger_1.default.debug('Starting to listen for JavaScript console');
this.addClientEventListener('Console.messageAdded', listener);
this.addClientEventListener('Console.messageRepeatCountUpdated', listener);
}
stopConsole() {
logger_1.default.debug('Stopping to listen for JavaScript console');
this.removeClientEventListener('Console.messageAdded');
this.removeClientEventListener('Console.messageRepeatCountUpdated');
}
startNetwork(listener) {
logger_1.default.debug('Starting to listen for network events');
this.addClientEventListener('NetworkEvent', listener);
}
stopNetwork() {
logger_1.default.debug('Stopping to listen for network events');
this.removeClientEventListener('NetworkEvent');
}
set allowNavigationWithoutReload(allow) {
this._allowNavigationWithoutReload = allow;
}
get allowNavigationWithoutReload() {
return this._allowNavigationWithoutReload;
}
// Potentially this does not work for mobile safari
async overrideUserAgent(value) {
logger_1.default.debug('Setting overrideUserAgent');
if (!this.rpcClient) {
throw new Error(`rpcClient is undefined. Has 'initRpcClient' been called before?`);
}
return await this.rpcClient.send('Page.overrideUserAgent', {
appIdKey: this.appIdKey,
pageIdKey: this.pageIdKey,
value
});
}
async getCookies() {
logger_1.default.debug('Getting cookies');
if (!this.rpcClient) {
throw new Error(`rpcClient is undefined. Has 'initRpcClient' been called before?`);
}
return await this.rpcClient.send('Page.getCookies', {
appIdKey: this.appIdKey,
pageIdKey: this.pageIdKey
});
}
async setCookie(cookie) {
logger_1.default.debug('Setting cookie');
if (!this.rpcClient) {
throw new Error(`rpcClient is undefined. Has 'initRpcClient' been called before?`);
}
return await this.rpcClient.send('Page.setCookie', {
appIdKey: this.appIdKey,
pageIdKey: this.pageIdKey,
cookie
});
}
async deleteCookie(cookieName, url) {
logger_1.default.debug(`Deleting cookie '${cookieName}' on '${url}'`);
if (!this.rpcClient) {
throw new Error(`rpcClient is undefined. Has 'initRpcClient' been called before?`);
}
return await this.rpcClient.send('Page.deleteCookie', {
appIdKey: this.appIdKey,
pageIdKey: this.pageIdKey,
cookieName,
url,
});
}
async garbageCollect(timeoutMs = GARBAGE_COLLECT_TIMEOUT) {
logger_1.default.debug(`Garbage collecting with ${timeoutMs}ms timeout`);
if (!this.rpcClient) {
throw new Error(`rpcClient is undefined. Has 'initRpcClient' been called before?`);
}
try {
(0, utils_1.checkParams)({ appIdKey: this.appIdKey, pageIdKey: this.pageIdKey });
}
catch (err) {
logger_1.default.debug(`Unable to collect garbage at this time`);
return;
}
await bluebird_1.default.resolve(this.rpcClient.send('Heap.gc', {
appIdKey: this.appIdKey,
pageIdKey: this.pageIdKey,
})).timeout(timeoutMs)
.then(function gcSuccess() {
logger_1.default.debug(`Garbage collection successful`);
}).catch(function gcError(err) {
if (err instanceof bluebird_1.default.TimeoutError) {
logger_1.default.debug(`Garbage collection timed out after ${timeoutMs}ms`);
}
else {
logger_1.default.debug(`Unable to collect garbage: ${err.message}`);
}
});
}
async useAppDictLock(fn) {
return await this._lock.acquire('appDict', fn);
}
get skippedApps() {
return this._skippedApps || [];
}
}
exports.RemoteDebugger = RemoteDebugger;
for (const [name, fn] of lodash_1.default.toPairs(mixins_1.mixins)) {
RemoteDebugger.prototype[name] = fn;
}
for (const [name, event] of lodash_1.default.toPairs(mixins_1.events)) {
RemoteDebugger[name] = event;
}
exports.default = RemoteDebugger;
//# sourceMappingURL=remote-debugger.js.map