atom-nuclide
Version:
A unified developer experience for web and mobile development, built as a suite of features on top of Atom to provide hackability and the support of an active community.
222 lines (192 loc) • 8.76 kB
JavaScript
Object.defineProperty(exports, '__esModule', {
value: true
});
var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
exports.getDbgpMessageHandlerInstance = getDbgpMessageHandlerInstance;
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
/*
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
var _utils2;
function _utils() {
return _utils2 = _interopRequireDefault(require('./utils'));
}
var _assert2;
function _assert() {
return _assert2 = _interopRequireDefault(require('assert'));
}
var _commonsNodeSingleton2;
function _commonsNodeSingleton() {
return _commonsNodeSingleton2 = _interopRequireDefault(require('../../commons-node/singleton'));
}
var _xml2js2;
function _xml2js() {
return _xml2js2 = _interopRequireDefault(require('xml2js'));
}
var GLOBAL_HHVM_DEBUGGER_KEY = '_global_hhvm_debugger_key';
var DbgpMessageHandler = (function () {
function DbgpMessageHandler() {
_classCallCheck(this, DbgpMessageHandler);
}
/**
* A single dbgp message is of the format below:
* Completed message: length <NULL> xml-blob <NULL>
* Incompleted message: length <NULL> xml-blob-part1
* Once an incompleted message is received the next server message
* will be in following format:
* xml-blob-part2
*
* A single response from the server may contain
* multiple DbgpMessages.
*
* Throws if the message is malformatted.
*/
_createClass(DbgpMessageHandler, [{
key: 'parseMessages',
value: function parseMessages(data) {
var components = data.split('\x00');
/**
* components.length can be 1, 2 or more than 3:
* 1: The whole data block is part of a full message(xml-partX).
* 2: length<NULL>xml-part1.
* >=3: Other scenarios.
*/
(_utils2 || _utils()).default.log('Total components: ' + components.length);
// Merge head component with prevIncompletedMessage if needed.
var results = [];
var prevIncompletedMessage = this._prevIncompletedMessage;
if (prevIncompletedMessage) {
var firstMessageContent = components.shift();
prevIncompletedMessage.content += firstMessageContent;
if (this._isCompletedMessage(prevIncompletedMessage)) {
results.push(this._parseXml(prevIncompletedMessage));
prevIncompletedMessage = null;
} else if (this._isCompletedMessageWithGarbageAtTheEnd(prevIncompletedMessage)) {
// TODO(jonaldislarry): This is a hack to fix the issue of HHVM sending us XML messages with
// trailing numbers (e.g. "<response>....</response>3294839"). Remove it when the HHVM
// memory corruption bug causing this issue is fixed. See: t11895240.
var completeXml = this._stripGarbage(prevIncompletedMessage.content);
results.push(this._parseXml({ content: completeXml, length: completeXml.length }));
prevIncompletedMessage = null;
}
}
// Verify that we can't get another message without completing previous one.
if (prevIncompletedMessage && components.length !== 0) {
(_utils2 || _utils()).default.logErrorAndThrow('Error: got extra messages without completing previous message. ' + ('Previous message was: ' + JSON.stringify(prevIncompletedMessage) + '. ') + ('Remaining components: ' + JSON.stringify(components)));
}
var isIncompleteResponse = components.length % 2 === 0;
// Verify empty tail component for completed response.
if (!isIncompleteResponse) {
var lastComponent = components.pop();
if (lastComponent.length !== 0) {
(_utils2 || _utils()).default.logErrorAndThrow('The complete response should terminate with' + (' zero character while got: ' + lastComponent + ' '));
}
}
// Process two tail components into prevIncompletedMessage for incompleted response.
if (isIncompleteResponse && components.length > 0) {
(0, (_assert2 || _assert()).default)(components.length >= 2);
// content comes after length.
var _content = components.pop();
var _length = Number(components.pop());
var lastMessage = { length: _length, content: _content };
if (!this._isIncompletedMessage(lastMessage)) {
(_utils2 || _utils()).default.logErrorAndThrow('The last message should be a fragment of a full message: ' + JSON.stringify(lastMessage));
}
prevIncompletedMessage = lastMessage;
}
// The remaining middle components should all be completed messages.
(0, (_assert2 || _assert()).default)(components.length % 2 === 0);
while (components.length >= 2) {
var message = {
length: Number(components.shift()),
content: components.shift()
};
if (!this._isCompletedMessage(message)) {
(_utils2 || _utils()).default.logErrorAndThrow('Got message length(' + message.content.length + ') ' + ('not equal to expected(' + message.length + '). ') + ('Message was: ' + JSON.stringify(message)));
}
results.push(this._parseXml(message));
}
this._prevIncompletedMessage = prevIncompletedMessage;
return results;
}
}, {
key: '_isCompletedMessage',
value: function _isCompletedMessage(message) {
return message.length === message.content.length;
}
}, {
key: '_isIncompletedMessage',
value: function _isIncompletedMessage(message) {
return message.length > message.content.length;
}
}, {
key: '_isCompletedMessageWithGarbageAtTheEnd',
value: function _isCompletedMessageWithGarbageAtTheEnd(message) {
var length = message.length;
var content = message.content;
if (length >= content.length) {
return false;
}
return (/(<\/stream>|<\/response>)[0-9]+$/.test(content)
);
}
}, {
key: '_stripGarbage',
value: function _stripGarbage(content) {
var matches = content.match(/\d+$/);
(0, (_assert2 || _assert()).default)(matches != null, 'Garbage XML had no garbage: ' + content);
var garbageSize = matches[0].length;
return content.substring(0, content.length - garbageSize);
}
/**
* Convert xml to JS. Uses the xml2js package.
* xml2js has a rather ... unique ... callback based API for a
* synchronous operation. This functions purpose is to give a reasonable API.
*
* Format of the result:
* Children become fields.
* Multiple children of the same name become arrays.
* Attributes become children of the '$' member
* Body becomes either a string (if no attributes or children)
* or the '_' member.
* CDATA becomes an array containing a string, or just a string.
*
* Throws if the input is not valid xml.
*/
}, {
key: '_parseXml',
value: function _parseXml(message) {
var xml = message.content;
var errorValue = undefined;
var resultValue = undefined;
(_xml2js2 || _xml2js()).default.parseString(xml, function (error, result) {
errorValue = error;
resultValue = result;
});
if (errorValue !== null) {
throw new Error('Error ' + JSON.stringify(errorValue) + ' parsing xml: ' + xml);
}
(_utils2 || _utils()).default.log('Translating server message result json: ' + JSON.stringify(resultValue));
(0, (_assert2 || _assert()).default)(resultValue != null);
return resultValue;
}
// For testing purpose.
}, {
key: 'clearIncompletedMessage',
value: function clearIncompletedMessage() {
this._prevIncompletedMessage = null;
}
}]);
return DbgpMessageHandler;
})();
exports.DbgpMessageHandler = DbgpMessageHandler;
function getDbgpMessageHandlerInstance() {
return (_commonsNodeSingleton2 || _commonsNodeSingleton()).default.get(GLOBAL_HHVM_DEBUGGER_KEY, function () {
return new DbgpMessageHandler();
});
}