346 lines
13 KiB
JavaScript
346 lines
13 KiB
JavaScript
"use strict";
|
|
var __extends = (this && this.__extends) || (function () {
|
|
var extendStatics = function (d, b) {
|
|
extendStatics = Object.setPrototypeOf ||
|
|
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
|
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
|
|
return extendStatics(d, b);
|
|
};
|
|
return function (d, b) {
|
|
extendStatics(d, b);
|
|
function __() { this.constructor = d; }
|
|
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
|
};
|
|
})();
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.UnixTerminal = void 0;
|
|
/**
|
|
* Copyright (c) 2012-2015, Christopher Jeffrey (MIT License)
|
|
* Copyright (c) 2016, Daniel Imms (MIT License).
|
|
* Copyright (c) 2018, Microsoft Corporation (MIT License).
|
|
*/
|
|
var fs = require("fs");
|
|
var path = require("path");
|
|
var tty = require("tty");
|
|
var terminal_1 = require("./terminal");
|
|
var utils_1 = require("./utils");
|
|
var native = utils_1.loadNativeModule('pty');
|
|
var pty = native.module;
|
|
var helperPath = native.dir + '/spawn-helper';
|
|
helperPath = path.resolve(__dirname, helperPath);
|
|
helperPath = helperPath.replace('app.asar', 'app.asar.unpacked');
|
|
helperPath = helperPath.replace('node_modules.asar', 'node_modules.asar.unpacked');
|
|
var DEFAULT_FILE = 'sh';
|
|
var DEFAULT_NAME = 'xterm';
|
|
var DESTROY_SOCKET_TIMEOUT_MS = 200;
|
|
var UnixTerminal = /** @class */ (function (_super) {
|
|
__extends(UnixTerminal, _super);
|
|
function UnixTerminal(file, args, opt) {
|
|
var _a, _b;
|
|
var _this = _super.call(this, opt) || this;
|
|
_this._boundClose = false;
|
|
_this._emittedClose = false;
|
|
if (typeof args === 'string') {
|
|
throw new Error('args as a string is not supported on unix.');
|
|
}
|
|
// Initialize arguments
|
|
args = args || [];
|
|
file = file || DEFAULT_FILE;
|
|
opt = opt || {};
|
|
opt.env = opt.env || process.env;
|
|
_this._cols = opt.cols || terminal_1.DEFAULT_COLS;
|
|
_this._rows = opt.rows || terminal_1.DEFAULT_ROWS;
|
|
var uid = (_a = opt.uid) !== null && _a !== void 0 ? _a : -1;
|
|
var gid = (_b = opt.gid) !== null && _b !== void 0 ? _b : -1;
|
|
var env = utils_1.assign({}, opt.env);
|
|
if (opt.env === process.env) {
|
|
_this._sanitizeEnv(env);
|
|
}
|
|
var cwd = opt.cwd || process.cwd();
|
|
env.PWD = cwd;
|
|
var name = opt.name || env.TERM || DEFAULT_NAME;
|
|
env.TERM = name;
|
|
var parsedEnv = _this._parseEnv(env);
|
|
var encoding = (opt.encoding === undefined ? 'utf8' : opt.encoding);
|
|
var onexit = function (code, signal) {
|
|
// XXX Sometimes a data event is emitted after exit. Wait til socket is
|
|
// destroyed.
|
|
if (!_this._emittedClose) {
|
|
if (_this._boundClose) {
|
|
return;
|
|
}
|
|
_this._boundClose = true;
|
|
// From macOS High Sierra 10.13.2 sometimes the socket never gets
|
|
// closed. A timeout is applied here to avoid the terminal never being
|
|
// destroyed when this occurs.
|
|
var timeout_1 = setTimeout(function () {
|
|
timeout_1 = null;
|
|
// Destroying the socket now will cause the close event to fire
|
|
_this._socket.destroy();
|
|
}, DESTROY_SOCKET_TIMEOUT_MS);
|
|
_this.once('close', function () {
|
|
if (timeout_1 !== null) {
|
|
clearTimeout(timeout_1);
|
|
}
|
|
_this.emit('exit', code, signal);
|
|
});
|
|
return;
|
|
}
|
|
_this.emit('exit', code, signal);
|
|
};
|
|
// fork
|
|
var term = pty.fork(file, args, parsedEnv, cwd, _this._cols, _this._rows, uid, gid, (encoding === 'utf8'), helperPath, onexit);
|
|
_this._socket = new tty.ReadStream(term.fd);
|
|
if (encoding !== null) {
|
|
_this._socket.setEncoding(encoding);
|
|
}
|
|
_this._writeStream = new CustomWriteStream(term.fd, (encoding || undefined));
|
|
// setup
|
|
_this._socket.on('error', function (err) {
|
|
// NOTE: fs.ReadStream gets EAGAIN twice at first:
|
|
if (err.code) {
|
|
if (~err.code.indexOf('EAGAIN')) {
|
|
return;
|
|
}
|
|
}
|
|
// close
|
|
_this._close();
|
|
// EIO on exit from fs.ReadStream:
|
|
if (!_this._emittedClose) {
|
|
_this._emittedClose = true;
|
|
_this.emit('close');
|
|
}
|
|
// EIO, happens when someone closes our child process: the only process in
|
|
// the terminal.
|
|
// node < 0.6.14: errno 5
|
|
// node >= 0.6.14: read EIO
|
|
if (err.code) {
|
|
if (~err.code.indexOf('errno 5') || ~err.code.indexOf('EIO')) {
|
|
return;
|
|
}
|
|
}
|
|
// throw anything else
|
|
if (_this.listeners('error').length < 2) {
|
|
throw err;
|
|
}
|
|
});
|
|
_this._pid = term.pid;
|
|
_this._fd = term.fd;
|
|
_this._pty = term.pty;
|
|
_this._file = file;
|
|
_this._name = name;
|
|
_this._readable = true;
|
|
_this._writable = true;
|
|
_this._socket.on('close', function () {
|
|
if (_this._emittedClose) {
|
|
return;
|
|
}
|
|
_this._emittedClose = true;
|
|
_this._close();
|
|
_this.emit('close');
|
|
});
|
|
_this._forwardEvents();
|
|
return _this;
|
|
}
|
|
Object.defineProperty(UnixTerminal.prototype, "master", {
|
|
get: function () { return this._master; },
|
|
enumerable: false,
|
|
configurable: true
|
|
});
|
|
Object.defineProperty(UnixTerminal.prototype, "slave", {
|
|
get: function () { return this._slave; },
|
|
enumerable: false,
|
|
configurable: true
|
|
});
|
|
UnixTerminal.prototype._write = function (data) {
|
|
this._writeStream.write(data);
|
|
};
|
|
Object.defineProperty(UnixTerminal.prototype, "fd", {
|
|
/* Accessors */
|
|
get: function () { return this._fd; },
|
|
enumerable: false,
|
|
configurable: true
|
|
});
|
|
Object.defineProperty(UnixTerminal.prototype, "ptsName", {
|
|
get: function () { return this._pty; },
|
|
enumerable: false,
|
|
configurable: true
|
|
});
|
|
/**
|
|
* openpty
|
|
*/
|
|
UnixTerminal.open = function (opt) {
|
|
var self = Object.create(UnixTerminal.prototype);
|
|
opt = opt || {};
|
|
if (arguments.length > 1) {
|
|
opt = {
|
|
cols: arguments[1],
|
|
rows: arguments[2]
|
|
};
|
|
}
|
|
var cols = opt.cols || terminal_1.DEFAULT_COLS;
|
|
var rows = opt.rows || terminal_1.DEFAULT_ROWS;
|
|
var encoding = (opt.encoding === undefined ? 'utf8' : opt.encoding);
|
|
// open
|
|
var term = pty.open(cols, rows);
|
|
self._master = new tty.ReadStream(term.master);
|
|
if (encoding !== null) {
|
|
self._master.setEncoding(encoding);
|
|
}
|
|
self._master.resume();
|
|
self._slave = new tty.ReadStream(term.slave);
|
|
if (encoding !== null) {
|
|
self._slave.setEncoding(encoding);
|
|
}
|
|
self._slave.resume();
|
|
self._socket = self._master;
|
|
self._pid = -1;
|
|
self._fd = term.master;
|
|
self._pty = term.pty;
|
|
self._file = process.argv[0] || 'node';
|
|
self._name = process.env.TERM || '';
|
|
self._readable = true;
|
|
self._writable = true;
|
|
self._socket.on('error', function (err) {
|
|
self._close();
|
|
if (self.listeners('error').length < 2) {
|
|
throw err;
|
|
}
|
|
});
|
|
self._socket.on('close', function () {
|
|
self._close();
|
|
});
|
|
return self;
|
|
};
|
|
UnixTerminal.prototype.destroy = function () {
|
|
var _this = this;
|
|
this._close();
|
|
// Need to close the read stream so node stops reading a dead file
|
|
// descriptor. Then we can safely SIGHUP the shell.
|
|
this._socket.once('close', function () {
|
|
_this.kill('SIGHUP');
|
|
});
|
|
this._socket.destroy();
|
|
this._writeStream.dispose();
|
|
};
|
|
UnixTerminal.prototype.kill = function (signal) {
|
|
try {
|
|
process.kill(this.pid, signal || 'SIGHUP');
|
|
}
|
|
catch (e) { /* swallow */ }
|
|
};
|
|
Object.defineProperty(UnixTerminal.prototype, "process", {
|
|
/**
|
|
* Gets the name of the process.
|
|
*/
|
|
get: function () {
|
|
if (process.platform === 'darwin') {
|
|
var title = pty.process(this._fd);
|
|
return (title !== 'kernel_task') ? title : this._file;
|
|
}
|
|
return pty.process(this._fd, this._pty) || this._file;
|
|
},
|
|
enumerable: false,
|
|
configurable: true
|
|
});
|
|
/**
|
|
* TTY
|
|
*/
|
|
UnixTerminal.prototype.resize = function (cols, rows) {
|
|
if (cols <= 0 || rows <= 0 || isNaN(cols) || isNaN(rows) || cols === Infinity || rows === Infinity) {
|
|
throw new Error('resizing must be done using positive cols and rows');
|
|
}
|
|
pty.resize(this._fd, cols, rows);
|
|
this._cols = cols;
|
|
this._rows = rows;
|
|
};
|
|
UnixTerminal.prototype.clear = function () {
|
|
};
|
|
UnixTerminal.prototype._sanitizeEnv = function (env) {
|
|
// Make sure we didn't start our server from inside tmux.
|
|
delete env['TMUX'];
|
|
delete env['TMUX_PANE'];
|
|
// Make sure we didn't start our server from inside screen.
|
|
// http://web.mit.edu/gnu/doc/html/screen_20.html
|
|
delete env['STY'];
|
|
delete env['WINDOW'];
|
|
// Delete some variables that might confuse our terminal.
|
|
delete env['WINDOWID'];
|
|
delete env['TERMCAP'];
|
|
delete env['COLUMNS'];
|
|
delete env['LINES'];
|
|
};
|
|
return UnixTerminal;
|
|
}(terminal_1.Terminal));
|
|
exports.UnixTerminal = UnixTerminal;
|
|
/**
|
|
* A custom write stream that writes directly to a file descriptor with proper
|
|
* handling of backpressure and errors. This avoids some event loop exhaustion
|
|
* issues that can occur when using the standard APIs in Node.
|
|
*/
|
|
var CustomWriteStream = /** @class */ (function () {
|
|
function CustomWriteStream(_fd, _encoding) {
|
|
this._fd = _fd;
|
|
this._encoding = _encoding;
|
|
this._writeQueue = [];
|
|
}
|
|
CustomWriteStream.prototype.dispose = function () {
|
|
clearImmediate(this._writeImmediate);
|
|
this._writeImmediate = undefined;
|
|
};
|
|
CustomWriteStream.prototype.write = function (data) {
|
|
// Writes are put in a queue and processed asynchronously in order to handle
|
|
// backpressure from the kernel buffer.
|
|
var buffer = typeof data === 'string'
|
|
? Buffer.from(data, this._encoding)
|
|
: Buffer.from(data);
|
|
if (buffer.byteLength !== 0) {
|
|
this._writeQueue.push({ buffer: buffer, offset: 0 });
|
|
if (this._writeQueue.length === 1) {
|
|
this._processWriteQueue();
|
|
}
|
|
}
|
|
};
|
|
CustomWriteStream.prototype._processWriteQueue = function () {
|
|
var _this = this;
|
|
this._writeImmediate = undefined;
|
|
if (this._writeQueue.length === 0) {
|
|
return;
|
|
}
|
|
var task = this._writeQueue[0];
|
|
// Write to the underlying file descriptor and handle it directly, rather
|
|
// than using the `net.Socket`/`tty.WriteStream` wrappers which swallow and
|
|
// mask errors like EAGAIN and can cause the thread to block indefinitely.
|
|
fs.write(this._fd, task.buffer, task.offset, function (err, written) {
|
|
if (err) {
|
|
if ('code' in err && err.code === 'EAGAIN') {
|
|
// `setImmediate` is used to yield to the event loop and re-attempt
|
|
// the write later.
|
|
_this._writeImmediate = setImmediate(function () { return _this._processWriteQueue(); });
|
|
}
|
|
else {
|
|
// Stop processing immediately on unexpected error and log
|
|
_this._writeQueue.length = 0;
|
|
console.error('Unhandled pty write error', err);
|
|
}
|
|
return;
|
|
}
|
|
task.offset += written;
|
|
if (task.offset >= task.buffer.byteLength) {
|
|
_this._writeQueue.shift();
|
|
}
|
|
// Since there is more room in the kernel buffer, we can continue to write
|
|
// until we hit EAGAIN or exhaust the queue.
|
|
//
|
|
// Note that old versions of bash, like v3.2 which ships in macOS, appears
|
|
// to have a bug in its readline implementation that causes data
|
|
// corruption when writes to the pty happens too quickly. Instead of
|
|
// trying to workaround that we just accept it so that large pastes are as
|
|
// fast as possible.
|
|
// Context: https://github.com/microsoft/node-pty/issues/833
|
|
_this._processWriteQueue();
|
|
});
|
|
};
|
|
return CustomWriteStream;
|
|
}());
|
|
//# sourceMappingURL=unixTerminal.js.map
|