230 строки
5.8 KiB
JavaScript
230 строки
5.8 KiB
JavaScript
var extend = require('util')._extend;
|
|
var EventEmitter = require('events').EventEmitter,
|
|
inherits = require('util').inherits,
|
|
DebugConnection = require('./debugger.js');
|
|
|
|
function createFailingConnection(reason) {
|
|
return {
|
|
connected: false,
|
|
isRunning: false,
|
|
|
|
request: function(command, args, callback) {
|
|
callback({ message: new ErrorNotConnected(reason) });
|
|
},
|
|
|
|
close: function() {
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @constructor
|
|
* @param {number} debuggerPort
|
|
*/
|
|
function DebuggerClient(debuggerPort) {
|
|
this._conn = createFailingConnection('node-inspector server was restarted');
|
|
this._port = debuggerPort;
|
|
}
|
|
|
|
inherits(DebuggerClient, EventEmitter);
|
|
|
|
Object.defineProperties(DebuggerClient.prototype, {
|
|
/** @type {boolean} */
|
|
isRunning: {
|
|
get: function() {
|
|
return this._conn.isRunning;
|
|
}
|
|
},
|
|
|
|
isConnected: {
|
|
get: function() {
|
|
return this._conn.connected;
|
|
}
|
|
}
|
|
});
|
|
|
|
DebuggerClient.prototype.connect = function() {
|
|
this._conn = DebugConnection.attachDebugger(this._port);
|
|
|
|
this._conn.
|
|
on('connect', this._onConnectionOpen.bind(this)).
|
|
on('error', this.emit.bind(this, 'error')).
|
|
on('close', this._onConnectionClose.bind(this));
|
|
|
|
this._registerDebuggerEventHandlers('break', 'afterCompile', 'exception');
|
|
};
|
|
|
|
|
|
/**
|
|
* @param {...string} eventNames
|
|
*/
|
|
DebuggerClient.prototype._registerDebuggerEventHandlers = function(eventNames) {
|
|
for (var i in arguments) {
|
|
var name = arguments[i];
|
|
this._conn.on(name, this._emitDebuggerEvent.bind(this, name));
|
|
}
|
|
};
|
|
|
|
DebuggerClient.prototype._onConnectionOpen = function() {
|
|
//We need to update isRunning state before we continue with debugging.
|
|
//Send the dummy requestso that we can read the state from the response.
|
|
this.request('version', {}, function(error, result) {
|
|
this.emit('connect');
|
|
}.bind(this));
|
|
};
|
|
|
|
/**
|
|
* @param {string} reason
|
|
*/
|
|
DebuggerClient.prototype._onConnectionClose = function(reason) {
|
|
this._conn = createFailingConnection(reason);
|
|
this.emit('close', reason);
|
|
};
|
|
|
|
/**
|
|
* @param {string} name
|
|
* @param {Object} message
|
|
*/
|
|
DebuggerClient.prototype._emitDebuggerEvent = function(name, message) {
|
|
this.emit(name, message.body);
|
|
};
|
|
|
|
/**
|
|
* @param {string} command
|
|
* @param {!Object} args
|
|
* @param {function(error, response, refs)} callback
|
|
*/
|
|
DebuggerClient.prototype.request = function(command, args, callback) {
|
|
if (typeof callback !== 'function') {
|
|
callback = function(error) {
|
|
if (!error) return;
|
|
console.log('Warning: ignored V8 debugger error. %s', error);
|
|
};
|
|
}
|
|
|
|
// Note: we must not add args object if it was not sent.
|
|
// E.g. resume (V8 request 'continue') does no work
|
|
// correctly when args are empty instead of undefined
|
|
if (args && args.maxStringLength == null)
|
|
args.maxStringLength = 10000;
|
|
|
|
this._conn.request(command, { arguments: args }, function(response) {
|
|
var refsLookup;
|
|
if (!response.success)
|
|
callback(response.message);
|
|
else {
|
|
refsLookup = {};
|
|
if (response.refs)
|
|
response.refs.forEach(function(r) { refsLookup[r.handle] = r; });
|
|
callback(null, response.body, refsLookup);
|
|
}
|
|
});
|
|
};
|
|
|
|
/**
|
|
*/
|
|
DebuggerClient.prototype.close = function() {
|
|
this._conn.close();
|
|
};
|
|
|
|
/**
|
|
* @param {number} breakpointId
|
|
* @param {function(error, response, refs)} done
|
|
*/
|
|
DebuggerClient.prototype.clearBreakpoint = function(breakpointId, done) {
|
|
this.request(
|
|
'clearbreakpoint',
|
|
{
|
|
breakpoint: breakpointId
|
|
},
|
|
done
|
|
);
|
|
};
|
|
|
|
/**
|
|
* @param {string} expression
|
|
* @param {function(error, response)} done
|
|
*/
|
|
DebuggerClient.prototype.evaluateGlobal = function(expression, done) {
|
|
// Note: we can't simply evaluate JSON.stringify(`expression`)
|
|
// because V8 debugger protocol truncates returned value to 80 characters
|
|
// The workaround is to split the serialized value into multiple pieces,
|
|
// each piece 80 characters long, send an array over the wire,
|
|
// and reconstruct the value back here
|
|
var code = 'JSON.stringify(' + expression + ').match(/.{1,80}/g).slice()';
|
|
this.request(
|
|
'evaluate',
|
|
{
|
|
expression: code,
|
|
global: true
|
|
},
|
|
function _handleEvaluateResponse(err, result, refs) {
|
|
if (err) return done(err);
|
|
|
|
if (result.type != 'object' && result.className != 'Array') {
|
|
return done(
|
|
new Error(
|
|
'Evaluate returned unexpected result:' +
|
|
' type: ' + result.type +
|
|
' className: ' + result.className
|
|
)
|
|
);
|
|
}
|
|
|
|
var fullJsonString = result.properties
|
|
.filter(function isArrayIndex(p) { return /^\d+$/.test(p.name);})
|
|
.map(function resolvePropertyValue(p) { return refs[p.ref].value; })
|
|
.join('');
|
|
|
|
try {
|
|
done(null, JSON.parse(fullJsonString));
|
|
} catch (e) {
|
|
console.error('evaluateGlobal "%s" failed', expression);
|
|
console.error(e.stack);
|
|
console.error('--json-begin--\n%s--json-end--', fullJsonString);
|
|
done(e);
|
|
}
|
|
}
|
|
);
|
|
};
|
|
|
|
/**
|
|
* @param {number} id
|
|
* @param {function(Object, string?)} callback
|
|
*/
|
|
DebuggerClient.prototype.getScriptSourceById = function(id, callback) {
|
|
this.request(
|
|
'scripts',
|
|
{
|
|
includeSource: true,
|
|
types: 4,
|
|
ids: [id]
|
|
},
|
|
function handleScriptSourceResponse(err, result) {
|
|
if (err) return callback(err);
|
|
|
|
// Some modules gets unloaded (?) after they are parsed,
|
|
// e.g. node_modules/express/node_modules/methods/index.js
|
|
// V8 request 'scripts' returns an empty result in such case
|
|
var source = result.length > 0 ? result[0].source : undefined;
|
|
|
|
callback(null, source);
|
|
}
|
|
);
|
|
};
|
|
|
|
/**
|
|
* @param {string} message
|
|
* @constructor
|
|
*/
|
|
function ErrorNotConnected(message) {
|
|
Error.call(this);
|
|
this.name = ErrorNotConnected.name;
|
|
this.message = message;
|
|
}
|
|
|
|
inherits(ErrorNotConnected, Error);
|
|
|
|
exports.DebuggerClient = DebuggerClient;
|
|
exports.ErrorNotConnected = ErrorNotConnected;
|