Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for iOS 12.2 #118

Merged
merged 2 commits into from
Mar 15, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 0 additions & 11 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,6 @@ env:
- _FORCE_LOGS=1
matrix:
include:
- osx_image: xcode8.3
node_js: "8"
env: COVERALLS=1
- osx_image: xcode8.3
node_js: "10"

- osx_image: xcode9.4
node_js: "8"
- osx_image: xcode9.4
node_js: "10"

- osx_image: xcode10
node_js: "8"
- osx_image: xcode10
Expand Down
11 changes: 4 additions & 7 deletions lib/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ const WEB_CONTENT_BUNDLE_ID = 'com.apple.WebKit.WebContent';
* dictionary whose keys are understandable
*/
function appInfoFromDict (dict) {
let id = dict.WIRApplicationIdentifierKey;
let isProxy = _.isString(dict.WIRIsApplicationProxyKey)
const id = dict.WIRApplicationIdentifierKey;
const isProxy = _.isString(dict.WIRIsApplicationProxyKey)
? dict.WIRIsApplicationProxyKey.toLowerCase() === 'true'
: dict.WIRIsApplicationProxyKey;
let entry = {
const entry = {
id,
isProxy,
name: dict.WIRApplicationNameKey,
Expand Down Expand Up @@ -113,10 +113,10 @@ function getPossibleDebuggerAppKeys (bundleId, platformVersion, appDict) {
let proxiedAppIds = [];
if (parseFloat(platformVersion) >= 8) {
const appId = appIdForBundle(bundleId, appDict);
proxiedAppIds.push(appId);

// now we need to determine if we should pick a proxy for this instead
if (appId) {
proxiedAppIds.push(appId);
log.debug(`Found app id key '${appId}' for bundle '${bundleId}'`);
for (const [key, data] of _.toPairs(appDict)) {
if (data.isProxy && data.hostId === appId) {
Expand All @@ -125,9 +125,6 @@ function getPossibleDebuggerAppKeys (bundleId, platformVersion, appDict) {
proxiedAppIds.push(key);
}
}
if (proxiedAppIds.length === 0) {
proxiedAppIds = [appId];
}
}
} else {
if (_.has(appDict, bundleId)) {
Expand Down
12 changes: 6 additions & 6 deletions lib/message-handlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,21 @@ function onPageChange (appIdKey, pageDict) {

// save the page dict for this app
if (this.appDict[appIdKey]) {
if (this.appDict[appIdKey].pageDict) {
if (this.appDict[appIdKey].pageDict.resolve) {
// pageDict is a promise, so resolve
this.appDict[appIdKey].pageDict.resolve(pageArray);
if (this.appDict[appIdKey].pageArray) {
if (this.appDict[appIdKey].pageArray.resolve) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isFunction ?

// pageDict is a pending promise, so resolve
this.appDict[appIdKey].pageArray.resolve();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

await?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolve is just a function. The promise itself it awaited elsewhere (or has already timed out).

} else {
// we have a pre-existing pageDict
if (_.isEqual(cleanPageArray(this.appDict[appIdKey].pageDict), cleanPageArray(pageArray))) {
if (_.isEqual(cleanPageArray(this.appDict[appIdKey].pageArray), cleanPageArray(pageArray))) {
log.debug(`Received page change notice for app '${appIdKey}' ` +
`but the listing has not changed. Ignoring.`);
return;
}
}
}
// keep track of the page dictionary
this.appDict[appIdKey].pageDict = pageArray;
this.appDict[appIdKey].pageArray = pageArray;
}

// only act if this is the correct app
Expand Down
142 changes: 85 additions & 57 deletions lib/remote-debugger-message-handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ const IGNORED_EVENTS = [
];

export default class RpcMessageHandler {
constructor (specialHandlers, targetBased = false) {
constructor (specialHandlers, isTargetBased = false) {
this.setHandlers();
this.errorHandlers = {};
this.specialHandlers = _.clone(specialHandlers);
this.dataHandlers = {};
this.willNavigateWithoutReload = false;

this.targetBased = targetBased;
this.isTargetBased = isTargetBased;
}

setDataMessageHandler (key, errorHandler, handler) {
Expand Down Expand Up @@ -107,6 +107,70 @@ export default class RpcMessageHandler {
}
}

async dispatchDataMessage (msgId, method, params, result, error) {
if (method === 'Profiler.resetProfiles') {
log.debug('Device is telling us to reset profiles. Should probably ' +
'do some kind of callback here');
} else if (method === 'Page.frameNavigated') {
if (!this.willNavigateWithoutReload && !this.pageLoading) {
log.debug('Frame navigated, unloading page');
if (_.isFunction(this.specialHandlers['Page.frameNavigated'])) {
await 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 (IGNORED_EVENTS.includes(method)) {
// pass
} else if (method === 'Page.loadEventFired' && _.isFunction(this.specialHandlers.pageLoad)) {
await this.specialHandlers.pageLoad();
} else if (method === 'Page.frameDetached' && _.isFunction(this.specialHandlers.frameDetached)) {
await this.specialHandlers.frameDetached();
} else if (method === 'Timeline.eventRecorded' && _.isFunction(this.timelineEventHandler)) {
this.timelineEventHandler(params || params.record);
} else if (method === 'Console.messageAdded' && _.isFunction(this.consoleLogEventHandler)) {
this.consoleLogEventHandler(params.message);
} else if (method && method.startsWith('Network.') && _.isFunction(this.networkLogEventHandler)) {
this.networkLogEventHandler(method, 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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

await?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are always regular functions.

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}'`);
}
}
}

logFullMessage (plist) {
// Buffers cannot be serialized in a readable way
const bufferToJSON = Buffer.prototype.toJSON;
delete Buffer.prototype.toJSON;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

interesting hack %)

try {
log(JSON.stringify(plist, (k, v) => Buffer.isBuffer(v) ? v.toString('utf8') : v, 2));
} finally {
// restore the function, so as to not break further serialization
Buffer.prototype.toJSON = bufferToJSON;
}
}

async handleDataMessage (plist) {
const dataKey = this.parseDataKey(plist);
let msgId = (dataKey.id || '').toString();
Expand Down Expand Up @@ -135,7 +199,7 @@ export default class RpcMessageHandler {

let method = dataKey.method;
let params;
if (this.targetBased) {
if (this.isTargetBased) {
if (method === 'Target.targetCreated') {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather move this code into a separate method. The current one is already quite long

// this is in response to a `_rpc_forwardSocketSetup:` call
// targetInfo: { targetId: 'page-1', type: 'page' }
Expand All @@ -149,17 +213,29 @@ export default class RpcMessageHandler {
await this.specialHandlers.targetDestroyed(app, targetInfo);
return;
} else if (dataKey.method !== 'Target.dispatchMessageFromTarget') {
// this sort of message, at this point, is just an acknowledgement
// that the original message was received
if (!_.isEmpty(msgId)) {
log.debug(`Received receipt for message '${msgId}'`);
}
return;
}

const message = JSON.parse(dataKey.params.message);
result = message.result || message;
msgId = message.id;
method = message.method;
params = result.params;
// at this point, we have a Target-based message wrapping a protocol message
let message;
try {
message = JSON.parse(dataKey.params.message);
msgId = 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 visibiity into what is going on
log.error(`Unexpected message format from Web Inspector:`);
this.logFullMessage(plist);
throw err;
}
} else {
params = dataKey.params;
}
Expand All @@ -168,55 +244,7 @@ export default class RpcMessageHandler {
log.debug(`Received response for message '${msgId}'`);
}

if (method === 'Profiler.resetProfiles') {
log.debug('Device is telling us to reset profiles. Should probably ' +
'do some kind of callback here');
} else if (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 (IGNORED_EVENTS.includes(method)) {
// pass
} else if (method === 'Page.loadEventFired' && _.isFunction(this.specialHandlers.pageLoad)) {
await this.specialHandlers.pageLoad();
} else if (method === 'Page.frameDetached' && _.isFunction(this.specialHandlers.frameDetached)) {
await this.specialHandlers.frameDetached();
} else if (method === 'Timeline.eventRecorded' && _.isFunction(this.timelineEventHandler)) {
this.timelineEventHandler(params || params.record);
} else if (method === 'Console.messageAdded' && _.isFunction(this.consoleLogEventHandler)) {
this.consoleLogEventHandler(params.message);
} else if (method && method.startsWith('Network.') && _.isFunction(this.networkLogEventHandler)) {
this.networkLogEventHandler(method, 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}'`);
}
}
await this.dispatchDataMessage(msgId, method, params, result, error);
}

setHandlers () {
Expand Down
13 changes: 8 additions & 5 deletions lib/remote-debugger-rpc-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export default class RemoteDebuggerRpcClient {
this.connected = false;
this.connId = UUID.create().toString();
this.senderId = UUID.create().toString();
this.curMsgId = 0;
this.msgId = 0;
this.received = Buffer.alloc(0);
this.readPos = 0;

Expand All @@ -45,9 +45,10 @@ export default class RemoteDebuggerRpcClient {
if (_.isString(platformVersion)) {
platformVersion = parseFloat(platformVersion);
}
const targetBased = platformVersion >= MIN_PLATFORM_FOR_TARGET_BASED;
this.remoteMessages = new RemoteMessages(targetBased);
this.messageHandler = new RpcMessageHandler(this.specialMessageHandlers, targetBased);
const isTargetBased = platformVersion >= MIN_PLATFORM_FOR_TARGET_BASED;
this.remoteMessages = new RemoteMessages(isTargetBased);
this.messageHandler = new RpcMessageHandler(this.specialMessageHandlers, isTargetBased);
log.debug(`Using '${isTargetBased ? 'Target-based' : 'full Web Inspector protocol'}' communication`);
}

async connect () {
Expand Down Expand Up @@ -196,7 +197,7 @@ export default class RemoteDebuggerRpcClient {
// replies to our request

// keep track of the messages coming and going using a simple sequential id
const msgId = this.curMsgId++;
const msgId = this.msgId++;

// retrieve the correct command to send
opts = _.defaults({connId: this.connId, senderId: this.senderId}, opts);
Expand Down Expand Up @@ -245,6 +246,8 @@ export default class RemoteDebuggerRpcClient {
log.debug(`Original command: ${command}`);
resolve(value);
});

// make sure the message being sent has all the information that is needed
if (cmd.__argument.WIRSocketDataKey.params) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hell it's full of magic

cmd.__argument.WIRSocketDataKey.params.id = msgId;
if (!cmd.__argument.WIRSocketDataKey.params.targetId) {
Expand Down
Loading