appium-remote-debugger
Version:
Appium proxy for Remote Debugger protocol
214 lines • 9.46 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const node_events_1 = require("node:events");
const logger_1 = require("../logger");
const lodash_1 = __importDefault(require("lodash"));
const support_1 = require("@appium/support");
/**
* Handles messages from the Web Inspector and dispatches them as events.
* Extends EventEmitter to provide event-based message handling.
*/
class RpcMessageHandler extends node_events_1.EventEmitter {
/**
* Handles a message from the Web Inspector by parsing the selector
* and emitting appropriate events.
*
* @param plist - The plist message from the Web Inspector containing
* __selector and __argument properties.
*/
async handleMessage(plist) {
const selector = plist.__selector;
if (!selector) {
logger_1.log.debug('Got an invalid plist');
return;
}
const argument = plist.__argument;
switch (selector) {
case '_rpc_reportSetup:':
this.emit('_rpc_reportSetup:', null, argument.WIRSimulatorNameKey, argument.WIRSimulatorBuildKey, argument.WIRSimulatorProductVersionKey);
break;
case '_rpc_reportConnectedApplicationList:':
this.emit('_rpc_reportConnectedApplicationList:', null, argument.WIRApplicationDictionaryKey);
break;
case '_rpc_applicationSentListing:':
this.emit('_rpc_forwardGetListing:', null, argument.WIRApplicationIdentifierKey, argument.WIRListingKey);
break;
case '_rpc_applicationConnected:':
this.emit('_rpc_applicationConnected:', null, argument);
break;
case '_rpc_applicationDisconnected:':
this.emit('_rpc_applicationDisconnected:', null, argument);
break;
case '_rpc_applicationUpdated:':
this.emit('_rpc_applicationUpdated:', null, argument);
break;
case '_rpc_reportConnectedDriverList:':
this.emit('_rpc_reportConnectedDriverList:', null, argument);
break;
case '_rpc_reportCurrentState:':
this.emit('_rpc_reportCurrentState:', null, argument);
break;
case '_rpc_applicationSentData:':
await this.handleDataMessage(plist);
break;
default:
logger_1.log.debug(`Debugger got a message for '${selector}' and have no ` + `handler, doing nothing.`);
}
}
/**
* Parses the data key from a plist message.
* The data key is a JSON string that needs to be parsed.
*
* @param plist - The plist message containing the data key.
* @returns The parsed DataMessage object.
* @throws Error if the data key cannot be parsed.
*/
parseDataKey(plist) {
try {
return JSON.parse(plist.__argument.WIRMessageDataKey.toString('utf8'));
}
catch (err) {
logger_1.log.error(`Unparseable message data: ${lodash_1.default.truncate(JSON.stringify(plist), { length: 100 })}`);
throw new Error(`Unable to parse message data: ${err.message}`);
}
}
/**
* Dispatches a data message by emitting events.
* If msgId is provided, emits a message-specific event.
* Otherwise, emits method-based events with appropriate argument mapping.
*
* @param msgId - If not empty, emits an event with this ID: <msgId, error, result>.
* If empty, emits method-based events: <name, error, ...args>.
* @param method - The method name from the data message.
* @param params - The parameters from the data message.
* @param result - The result from the data message.
* @param error - Any error that occurred during message processing.
*/
async dispatchDataMessage(msgId, method, params, result, error) {
if (msgId) {
if (this.listenerCount(msgId)) {
if (lodash_1.default.has(result?.result, 'value')) {
result = result.result.value;
}
this.emit(msgId, error, result);
}
else {
logger_1.log.error(`Web Inspector returned data for message '${msgId}' ` +
`but we were not waiting for that message! ` +
`result: '${JSON.stringify(result)}'; ` +
`error: '${JSON.stringify(error)}'`);
}
return;
}
const eventNames = method ? [method] : [];
let args = [params];
// some events have different names, or the arguments are mapped from the
// parameters received
switch (method) {
case 'Page.frameStoppedLoading':
eventNames.push('Page.frameNavigated');
// eslint-disable-next-line no-fallthrough
case 'Page.frameNavigated':
args = [`'${method}' event`];
break;
case 'Timeline.eventRecorded':
args = [params || params?.record];
break;
case 'Console.messageAdded':
args = [params?.message];
break;
case 'Runtime.executionContextCreated':
args = [params?.context];
break;
default:
// pass
break;
}
if (method && lodash_1.default.startsWith(method, 'Network.')) {
// aggregate Network events, and add original method name to the arguments
eventNames.push('NetworkEvent');
args.push(method);
}
if (method && lodash_1.default.startsWith(method, 'Console.')) {
// aggregate Console events, and add original method name to the arguments
eventNames.push('ConsoleEvent');
args.push(method);
}
for (const name of eventNames) {
this.emit(name, error, ...args);
}
}
/**
* Handles a data message from the Web Inspector by parsing it and
* dispatching appropriate events based on the message type.
*
* @param plist - The plist message from the Web Inspector.
*/
async handleDataMessage(plist) {
const dataKey = this.parseDataKey(plist);
let msgId = (dataKey.id || '').toString();
let result = dataKey.result;
let method = dataKey.method;
let params = dataKey.params;
const parseError = () => {
const defaultMessage = 'Error occurred in handling data message';
if (result?.wasThrown) {
const message = result?.result?.value || result?.result?.description
? result?.result?.value || result?.result?.description
: (dataKey.error ?? defaultMessage);
return new Error(message);
}
if (dataKey.error) {
if (lodash_1.default.isPlainObject(dataKey.error)) {
const dataKeyError = dataKey.error;
const error = new Error(defaultMessage);
for (const key of Object.keys(dataKeyError)) {
error[key] = dataKeyError[key];
}
return error;
}
return new Error(String(dataKey.error || defaultMessage));
}
return undefined;
};
switch (method) {
case 'Target.targetCreated':
case 'Target.targetDestroyed':
case 'Target.didCommitProvisionalTarget': {
const app = plist.__argument.WIRApplicationIdentifierKey;
const args = method === 'Target.didCommitProvisionalTarget'
? params
: (params.targetInfo ?? { targetId: params.targetId });
this.emit(method, null, app, args);
return;
}
case 'Target.dispatchMessageFromTarget': {
if (!dataKey.error) {
try {
const message = JSON.parse(dataKey.params.message);
msgId = lodash_1.default.isUndefined(message.id) ? '' : String(message.id);
method = message.method;
result = message.result || message;
params = result.params;
}
catch (err) {
// if this happens then some aspect of the protocol is missing to us
// so print the entire message to get visibility into what is going on
logger_1.log.error(`Unexpected message format from Web Inspector: ${support_1.util.jsonStringify(plist, null)}`);
throw err;
}
}
await this.dispatchDataMessage(msgId, method, params, result, parseError());
return;
}
default: {
await this.dispatchDataMessage(msgId, method, params, result, parseError());
}
}
}
}
exports.default = RpcMessageHandler;
//# sourceMappingURL=rpc-message-handler.js.map