@kurtharriger/nel
Version:
Node.js Evaluation Loop (NEL): npm package to implement a Node.js REPL session
727 lines (596 loc) • 38.1 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Server = undefined;
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
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; }; }(); /*
* BSD 3-Clause License
*
* Copyright (c) 2015, Nicolas Riesco and others as credited in the AUTHORS file
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
var _stream = require("stream");
var _stream2 = _interopRequireDefault(_stream);
var _util = require("util");
var _util2 = _interopRequireDefault(_util);
var _vm = require("vm");
var _vm2 = _interopRequireDefault(_vm);
var _console = require("console");
var _console2 = _interopRequireDefault(_console);
var _socket = require("socket.io");
var _socket2 = _interopRequireDefault(_socket);
var _events = require("events");
var _events2 = _interopRequireDefault(_events);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var channel = void 0;
var DEBUG = !!process.env.DEBUG;
var log = DEBUG ? function doLog() {
var msg = {
log: "SERVER: " + _util2.default.format.apply(this, arguments)
};
if (channel) {
channel.send(msg);
}
_console2.default.log(msg);
} : function noop() {};
function Stdout(id, opt) {
_stream2.default.Transform.call(this, opt);
this._id = id;
}
Stdout.prototype = Object.create(_stream2.default.Transform.prototype);
Stdout.prototype._transform = function (data, encoding, callback) {
var response = {
id: this._id,
stdout: data.toString()
};
log("STDOUT:", response);
channel.send(response);
this.push(data);
callback();
};
function Stderr(id, opt) {
_stream2.default.Transform.call(this, opt);
this._id = id;
}
Stderr.prototype = Object.create(_stream2.default.Transform.prototype);
Stderr.prototype._transform = function (data, encoding, callback) {
var response = {
id: this._id,
stderr: data.toString()
};
log("STDERR:", response);
channel.send(response);
this.push(data);
callback();
};
function Context(id) {
this.id = id;
this.stdout = new Stdout(this.id);
this.stderr = new Stderr(this.id);
this.console = new _console2.default.Console(this.stdout, this.stderr);
this._capturedStdout = null;
this._capturedStderr = null;
this._capturedConsole = null;
this._async = false;
this._done = false;
// `$$` provides an interface for users to access the execution context
this.$$ = Object.create(null);
this.$$.async = Context.prototype.async.bind(this);
this.$$.sendResult = Context.prototype.sendResult.bind(this);
this.$$.sendError = Context.prototype.sendError.bind(this);
this.$$.done = Context.prototype.done.bind(this);
this.$$.mime = function sendMime(mimeBundle) {
this.done({
mime: mimeBundle
});
}.bind(this);
this.$$.text = function sendText(text) {
this.done({
mime: {
"text/plain": text
}
});
}.bind(this);
this.$$.html = function sendHtml(html) {
this.done({
mime: {
"text/html": html
}
});
}.bind(this);
this.$$.svg = function sendSvg(svg) {
this.done({
mime: {
"image/svg+xml": svg
}
});
}.bind(this);
this.$$.png = function sendPng(png) {
this.done({
mime: {
"image/png": png
}
});
}.bind(this);
this.$$.jpeg = function sendJpeg(jpeg) {
this.done({
mime: {
"image/jpeg": jpeg
}
});
}.bind(this);
}
Context.prototype.send = function send(message) {
message.id = this.id;
if (this._done) {
log("RESULT: DROPPED:", message);
return;
}
log("RESULT:", message);
channel.send(message);
};
Context.prototype.async = function async() {
this._async = true;
};
Context.prototype.sendResult = function sendResult(result) {
this.done({
mime: toMime(result)
});
};
Context.prototype.sendError = function sendError(error) {
this.done({
error: formatError(error)
});
};
Context.prototype.done = function done(response) {
response = response || {};
response.end = true;
this.send(response);
this._async = false;
this._done = true;
};
Context.prototype.captureGlobalContext = function captureGlobalContext() {
this._capturedStdout = process.stdout;
this._capturedStderr = process.stderr;
this._capturedConsole = _console2.default;
this.stdout.pipe(this._capturedStdout);
this.stderr.pipe(this._capturedStderr);
this.console.Console = this._capturedConsole.Console;
delete process.stdout;
process.stdout = this.stdout;
delete process.stderr;
process.stderr = this.stderr;
delete global.console;
global.console = this.console;
delete global.$$;
global.$$ = this.$$;
if (typeof global.$$mimer$$ !== "function") {
global.$$mimer$$ = defaultMimer;
}
delete global.$$mime$$;
Object.defineProperty(global, "$$mime$$", {
set: this.$$.mime,
configurable: true,
enumerable: false
});
delete global.$$html$$;
Object.defineProperty(global, "$$html$$", {
set: this.$$.html,
configurable: true,
enumerable: false
});
delete global.$$svg$$;
Object.defineProperty(global, "$$svg$$", {
set: this.$$.svg,
configurable: true,
enumerable: false
});
delete global.$$png$$;
Object.defineProperty(global, "$$png$$", {
set: this.$$.png,
configurable: true,
enumerable: false
});
delete global.$$jpeg$$;
Object.defineProperty(global, "$$jpeg$$", {
set: this.$$.jpeg,
configurable: true,
enumerable: false
});
delete global.$$async$$;
Object.defineProperty(global, "$$async$$", {
get: function () {
return this._async;
}.bind(this),
set: function (value) {
if (value) {
this.async();
}
this._async = value;
}.bind(this),
configurable: true,
enumerable: false
});
global.$$done$$ = this.$$.sendResult;
};
Context.prototype.releaseGlobalContext = function releaseGlobalContext() {
if (process.stdout === this.stdout) {
this.stdout.unpipe();
delete process.stdout;
process.stdout = this._capturedStdout;
this._capturedStdout = null;
}
if (process.stderr === this.stderr) {
this.stderr.unpipe();
delete process.stderr;
process.stderr = this._capturedStderr;
this._capturedStderr = null;
}
if (global.console === this.console) {
delete global.console;
global.console = this._capturedConsole;
this._capturedConsole = null;
}
};
var IpcChannel = function () {
function IpcChannel() {
_classCallCheck(this, IpcChannel);
if (!process.send) {
throw new Error('Process must be spawned with ipc channel');
}
}
_createClass(IpcChannel, [{
key: "onMessage",
value: function onMessage(callback) {
process.on("message", callback);
}
}, {
key: "send",
value: function send(msg) {
process.send(msg);
}
}]);
return IpcChannel;
}();
var SocketChannel = function () {
function SocketChannel() {
var _this = this;
var port = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 6001;
_classCallCheck(this, SocketChannel);
this.emitter = new _events2.default();
this.channel = new _socket2.default(port);
this.channel.on('connection', function (socket) {
log('client connected');
socket.on("message", function (msg) {
return _this.emitter.emit('message', msg);
});
});
log("listening on " + port);
}
_createClass(SocketChannel, [{
key: "onMessage",
value: function onMessage(callback) {
this.emitter.on('message', callback);
}
}, {
key: "send",
value: function send(msg) {
this.channel.send(msg);
}
}]);
return SocketChannel;
}();
var Server = exports.Server = function () {
function Server() {
var config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
_classCallCheck(this, Server);
this.config = config;
}
_createClass(Server, [{
key: "start",
value: function start() {
channel = this.channel = this.config.port || !process.send ? new SocketChannel(this.config.port) : new IpcChannel();
// Capture the initial context
// (id left undefined to indicate this is the initial context)
this.initialContext = new Context();
this.initialContext.captureGlobalContext();
Object.defineProperty(global, "$$defaultMimer$$", {
value: defaultMimer,
configurable: false,
writable: false,
enumerable: false
});
channel.onMessage(this.onMessage.bind(this));
process.on("uncaughtException", this.onUncaughtException.bind(this));
}
}, {
key: "onUncaughtException",
value: function onUncaughtException(error) {
log("UNCAUGHTEXCEPTION:", error.stack);
channel.send({
stderr: error.stack.toString()
});
}
}, {
key: "onMessage",
value: function onMessage(request) {
var _this2 = this;
log("REQUEST:", request);
var action = request[0];
var code = request[1];
var id = request[2];
this.initialContext.releaseGlobalContext();
var context = new Context(id);
context.captureGlobalContext();
var cleanup = function cleanup() {
context.releaseGlobalContext();
_this2.initialContext.captureGlobalContext();
_this2.initialContext._done = false;
};
return new Promise(function (resolve, reject) {
if (action === "getAllPropertyNames") {
_this2.onNameRequest(code, context).then(resolve, reject);
} else if (action === "inspect") {
_this2.onInspectRequest(code, context).then(resolve, reject);
} else if (action === "run") {
_this2.onRunRequest(code, context).then(resolve, reject);
} else {
_this2.context.sendError(new Error("NEL: Unhandled action request: " + action));
resolve();
}
}).then(cleanup, cleanup);
}
}, {
key: "onNameRequest",
value: function onNameRequest(code, context) {
var message = {
id: context.id,
names: getAllPropertyNames(this.run(code))
};
log("RESULT:", message);
context.done(message);
return Promise.resolve();
}
}, {
key: "onInspectRequest",
value: function onInspectRequest(code, context) {
var message = {
id: context.id,
inspection: inspect(this.run(code))
};
log("RESULT:", message);
context.done(message);
return Promise.resolve();
}
}, {
key: "onRunRequest",
value: function onRunRequest(code, context) {
var _this3 = this;
var result = new Promise(function (resolve) {
return resolve(_this3.run(code));
});
// Drop result if the run request initiated the async mode
if (context._async) {
return Promise.resolve();
}
// Drop result if the run request has already invoked context.done()
if (context._done) {
return Promise.resolve();
}
return result.then(function (value) {
context.sendResult(value);
}, function (error) {
context.sendError(error);
});
}
}, {
key: "run",
value: function run(code) {
return _vm2.default.runInThisContext(code);
}
}]);
return Server;
}();
function formatError(error) {
return {
ename: error && error.name ? error.name : typeof error === "undefined" ? "undefined" : _typeof(error),
evalue: error && error.message ? error.message : _util2.default.inspect(error),
traceback: error && error.stack ? error.stack.split("\n") : ""
};
}
function toMime(result) {
var mimer = typeof global.$$mimer$$ === "function" ? global.$$mimer$$ : defaultMimer;
return mimer(result);
}
function defaultMimer(result) {
if (typeof result === "undefined") {
return {
"text/plain": "undefined"
};
}
if (result === null) {
return {
"text/plain": "null"
};
}
var mime;
if (result._toMime) {
try {
mime = result._toMime();
} catch (error) {}
}
if ((typeof mime === "undefined" ? "undefined" : _typeof(mime)) !== "object") {
mime = {};
}
if (!("text/plain" in mime)) {
try {
mime["text/plain"] = _util2.default.inspect(result);
} catch (error) {}
}
if (result._toHtml && !("text/html" in mime)) {
try {
mime["text/html"] = result._toHtml();
} catch (error) {}
}
if (result._toSvg && !("image/svg+xml" in mime)) {
try {
mime["image/svg+xml"] = result._toSvg();
} catch (error) {}
}
if (result._toPng && !("image/png" in mime)) {
try {
mime["image/png"] = result._toPng();
} catch (error) {}
}
if (result._toJpeg && !("image/jpeg" in mime)) {
try {
mime["image/jpeg"] = result._toJpeg();
} catch (error) {}
}
return mime;
}
function getAllPropertyNames(object) {
var propertyList = [];
if (object === undefined) {
return [];
}
if (object === null) {
return [];
}
var prototype;
if (typeof object === "boolean") {
prototype = Boolean.prototype;
} else if (typeof object === "number") {
prototype = Number.prototype;
} else if (typeof object === "string") {
prototype = String.prototype;
} else {
prototype = object;
}
var prototypeList = [prototype];
function pushToPropertyList(e) {
if (propertyList.indexOf(e) === -1) {
propertyList.push(e);
}
}
while (true) {
var names;
try {
names = Object.getOwnPropertyNames(prototype).sort();
} catch (e) {
break;
}
names.forEach(pushToPropertyList);
prototype = Object.getPrototypeOf(prototype);
if (prototype === null) {
break;
}
if (prototypeList.indexOf(prototype) === -1) {
prototypeList.push(prototype);
}
}
return propertyList;
}
function inspect(object) {
if (object === undefined) {
return {
string: "undefined",
type: "Undefined"
};
}
if (object === null) {
return {
string: "null",
type: "Null"
};
}
if (typeof object === "boolean") {
return {
string: object ? "true" : "false",
type: "Boolean",
constructorList: ["Boolean", "Object"]
};
}
if (typeof object === "number") {
return {
string: _util2.default.inspect(object),
type: "Number",
constructorList: ["Number", "Object"]
};
}
if (typeof object === "string") {
return {
string: object,
type: "String",
constructorList: ["String", "Object"],
length: object.length
};
}
if (typeof object === "function") {
return {
string: object.toString(),
type: "Function",
constructorList: ["Function", "Object"],
length: object.length
};
}
var constructorList = getConstructorList(object);
var result = {
string: toString(object),
type: constructorList[0] || "",
constructorList: constructorList
};
if ("length" in object) {
result.length = object.length;
}
return result;
function toString(object) {
try {
return _util2.default.inspect(object.valueOf());
} catch (e) {
return _util2.default.inspect(object);
}
}
function getConstructorList(object) {
var constructorList = [];
var prototype = Object.getPrototypeOf(object);
while (true) {
try {
constructorList.push(prototype.constructor.name);
} catch (e) {
break;
}
prototype = Object.getPrototypeOf(prototype);
}
return constructorList;
}
}