webdriverio-automation
Version:
WebdriverIO-Automation android ios project
208 lines (185 loc) • 7.54 kB
JavaScript
import log from './logger';
import _ from 'lodash';
export default class RpcMessageHandler {
constructor (specialHandlers) {
this.setHandlers();
this.errorHandlers = {};
this.specialHandlers = _.clone(specialHandlers);
this.dataHandlers = {};
this.willNavigateWithoutReload = false;
}
setDataMessageHandler (key, errorHandler, handler) {
this.errorHandlers[key] = errorHandler;
this.dataHandlers[key] = handler;
}
setSpecialMessageHandler (key, errorHandler, handler) {
this.errorHandlers[key] = errorHandler;
this.specialHandlers[key] = handler;
}
getSpecialMessageHandler (key) {
return this.specialHandlers[key];
}
setTimelineEventHandler (timelineEventHandler) {
this.timelineEventHandler = timelineEventHandler;
}
setConsoleLogEventHandler (consoleLogEventHandler) {
this.consoleLogEventHandler = consoleLogEventHandler;
}
setNetworkEventHandler (networkLogEventHandler) {
this.networkLogEventHandler = networkLogEventHandler;
}
hasErrorHandler (key) {
return _.has(this.errorHandlers, key);
}
hasSpecialMessageHandler (key) {
return _.has(this.specialHandlers, key);
}
allowNavigationWithoutReload (allow = true) {
this.willNavigateWithoutReload = allow;
}
handleMessage (plist) {
let handlerFor = plist.__selector;
if (!handlerFor) {
log.debug('Got an invalid plist');
return;
}
if (_.has(this.handlers, handlerFor)) {
this.handlers[handlerFor](plist);
} else {
log.debug(`Debugger got a message for '${handlerFor}' and have no ` +
`handler, doing nothing.`);
}
}
handleSpecialMessage (handler, ...args) {
const fn = this.specialHandlers[handler];
if (fn) {
// most responses are only to be called once, then
// removed. But not the ones below, which handle
// page change and app connect/disconnect
if (handler !== '_rpc_forwardGetListing:' &&
handler !== '_rpc_applicationDisconnected:' &&
handler !== '_rpc_applicationConnected:' &&
handler !== '_rpc_applicationUpdated:' &&
handler !== '_rpc_reportConnectedDriverList:') {
this.specialHandlers[handler] = null;
}
fn(...args);
} else {
log.warn(`Tried to access special message handler '${handler}' ` +
`but none was found`);
}
}
parseDataKey (plist) {
try {
return JSON.parse(plist.__argument.WIRMessageDataKey.toString('utf8'));
} catch (err) {
log.error(`Unparseable message data: ${_.truncate(JSON.stringify(plist), {length: 100})}`);
throw new Error(`Unable to parse message data: ${err.message}`);
}
}
async handleDataMessage (plist) {
const dataKey = this.parseDataKey(plist);
const msgId = (dataKey.id || '').toString();
let result = dataKey.result;
let error = dataKey.error || null;
// we can get an error, or we can get a response that is an error
if (result && result.wasThrown) {
let message = (result.result && (result.result.value || result.result.description))
? (result.result.value || result.result.description)
: 'Error occurred in handling data message';
error = new Error(message);
}
if (error) {
if (this.hasErrorHandler(msgId)) {
this.errorHandlers[msgId](error);
} else {
log.error(`Error occurred in handling data message: ${error}`);
log.error('No error handler present, ignoring');
}
// short circuit
return;
}
if (dataKey.method === 'Profiler.resetProfiles') {
log.debug('Device is telling us to reset profiles. Should probably ' +
'do some kind of callback here');
} else if (dataKey.method === 'Page.frameNavigated') {
if (!this.willNavigateWithoutReload && !this.pageLoading) {
log.debug('Frame navigated, unloading page');
if (_.isFunction(this.specialHandlers['Page.frameNavigated'])) {
this.specialHandlers['Page.frameNavigated']('remote-debugger');
this.specialHandlers['Page.frameNavigated'] = null;
}
} else {
log.debug('Frame navigated but we were warned about it, not ' +
'considering page state unloaded');
this.willNavigateWithoutReload = false;
}
} else if (dataKey.method === 'Page.loadEventFired' && _.isFunction(this.specialHandlers.pageLoad)) {
await this.specialHandlers.pageLoad();
} else if (dataKey.method === 'Page.frameDetached' && _.isFunction(this.specialHandlers.frameDetached)) {
await this.specialHandlers.frameDetached();
} else if (dataKey.method === 'Timeline.eventRecorded' && _.isFunction(this.timelineEventHandler)) {
this.timelineEventHandler(dataKey.params.record);
} else if (dataKey.method === 'Console.messageAdded' && _.isFunction(this.consoleLogEventHandler)) {
this.consoleLogEventHandler(dataKey.params.message);
} else if (dataKey.method && dataKey.method.startsWith('Network.') && _.isFunction(this.networkLogEventHandler)) {
this.networkLogEventHandler(dataKey.method, dataKey.params);
} else if (_.isFunction(this.dataHandlers[msgId])) {
log.debug('Found data handler for response');
// we will either get back a result object that has a result.value
// in which case that is what we want,
// or else we return the whole thing
if (result.result && result.result.value) {
result = result.result.value;
}
this.dataHandlers[msgId](result);
this.dataHandlers[msgId] = null;
} else if (this.dataHandlers[msgId] === null) {
log.error(`Debugger returned data for message ${msgId} ` +
`but we already ran that callback! WTF??`);
} else {
if (msgId || result || error) {
log.error(`Debugger returned data for message '${msgId}' ` +
`but we were not waiting for that message! ` +
`result: '${JSON.stringify(result)}'; ` +
`error: '${error}'`);
}
}
}
setHandlers () {
this.handlers = {
'_rpc_reportSetup:': (plist) => {
this.handleSpecialMessage('_rpc_reportIdentifier:',
plist.__argument.WIRSimulatorNameKey,
plist.__argument.WIRSimulatorBuildKey,
plist.__argument.WIRSimulatorProductVersionKey);
},
'_rpc_reportConnectedApplicationList:': (plist) => {
this.handleSpecialMessage('_rpc_reportConnectedApplicationList:',
plist.__argument.WIRApplicationDictionaryKey);
},
'_rpc_applicationSentListing:': (plist) => {
this.handleSpecialMessage('_rpc_forwardGetListing:',
plist.__argument.WIRApplicationIdentifierKey,
plist.__argument.WIRListingKey);
},
'_rpc_applicationConnected:': (plist) => {
this.handleSpecialMessage('_rpc_applicationConnected:',
plist.__argument);
},
'_rpc_applicationDisconnected:': (plist) => {
this.handleSpecialMessage('_rpc_applicationDisconnected:',
plist.__argument);
},
'_rpc_applicationUpdated:': (plist) => {
this.handleSpecialMessage('_rpc_applicationUpdated:',
plist.__argument);
},
'_rpc_reportConnectedDriverList:': (plist) => {
this.handleSpecialMessage('_rpc_reportConnectedDriverList:',
plist.__argument);
},
'_rpc_applicationSentData:': this.handleDataMessage.bind(this),
};
}
}