Socket
Socket
Sign inDemoInstall

node-pty

Package Overview
Dependencies
Maintainers
3
Versions
164
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

node-pty - npm Package Compare versions

Comparing version 0.9.0-beta1 to 0.9.0-beta10

26

lib/terminal.js

@@ -12,2 +12,9 @@ "use strict";

exports.DEFAULT_ROWS = 24;
/**
* Default messages to indicate PAUSE/RESUME for automatic flow control.
* To avoid conflicts with rebound XON/XOFF control codes (such as on-my-zsh),
* the sequences can be customized in `IPtyForkOptions`.
*/
var FLOW_CONTROL_PAUSE = '\x13'; // defaults to XOFF
var FLOW_CONTROL_RESUME = '\x11'; // defaults to XON
var Terminal = /** @class */ (function () {

@@ -32,2 +39,6 @@ function Terminal(opt) {

this._checkType('encoding', opt.encoding ? opt.encoding : null, 'string');
// setup flow control handling
this.handleFlowControl = !!(opt.handleFlowControl);
this._flowControlPause = opt.flowControlPause || FLOW_CONTROL_PAUSE;
this._flowControlResume = opt.flowControlResume || FLOW_CONTROL_RESUME;
}

@@ -59,2 +70,17 @@ Object.defineProperty(Terminal.prototype, "onData", {

});
Terminal.prototype.write = function (data) {
if (this.handleFlowControl) {
// PAUSE/RESUME messages are not forwarded to the pty
if (data === this._flowControlPause) {
this.pause();
return;
}
if (data === this._flowControlResume) {
this.resume();
return;
}
}
// everything else goes to the real pty
this._writeMethod(data);
};
Terminal.prototype._forwardEvents = function () {

@@ -61,0 +87,0 @@ var _this = this;

@@ -6,4 +6,44 @@ "use strict";

*/
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
Object.defineProperty(exports, "__esModule", { value: true });
var assert = require("assert");
var windowsTerminal_1 = require("./windowsTerminal");
var unixTerminal_1 = require("./unixTerminal");
var pollUntil = require("pollUntil");
var terminalConstructor = (process.platform === 'win32') ? windowsTerminal_1.WindowsTerminal : unixTerminal_1.UnixTerminal;
var SHELL = (process.platform === 'win32') ? 'cmd.exe' : '/bin/bash';
var terminalCtor;

@@ -22,3 +62,44 @@ if (process.platform === 'win32') {

});
describe('automatic flow control', function () {
it('should respect ctor flow control options', function () {
var pty = new terminalConstructor(SHELL, [], { handleFlowControl: true, flowControlPause: 'abc', flowControlResume: '123' });
assert.equal(pty.handleFlowControl, true);
assert.equal(pty._flowControlPause, 'abc');
assert.equal(pty._flowControlResume, '123');
});
it('should do flow control automatically', function () {
return __awaiter(this, void 0, void 0, function () {
var pty, read;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
this.timeout(10000);
pty = new terminalConstructor(SHELL, [], { handleFlowControl: true, flowControlPause: 'PAUSE', flowControlResume: 'RESUME' });
read = '';
pty.on('data', function (data) { return read += data; });
pty.on('pause', function () { return read += 'paused'; });
pty.on('resume', function () { return read += 'resumed'; });
pty.write('1');
pty.write('PAUSE');
pty.write('2');
pty.write('RESUME');
pty.write('3');
return [4 /*yield*/, pollUntil(function () {
// important here: no data should be delivered between 'paused' and 'resumed'
if (process.platform === 'win32') {
read.endsWith('1\u001b[0Kpausedresumed2\u001b[0K3\u001b[0K');
}
else {
read.endsWith('1pausedresumed23');
}
}, [], 20, 10)];
case 1:
_a.sent();
return [2 /*return*/];
}
});
});
});
});
});
//# sourceMappingURL=terminal.test.js.map

15

lib/unixTerminal.js

@@ -136,2 +136,4 @@ "use strict";

_this._forwardEvents();
// attach write method
_this._writeMethod = function (data) { return _this._socket.write(data); };
return _this;

@@ -163,10 +165,14 @@ }

var rows = opt.rows || terminal_1.DEFAULT_ROWS;
var encoding = opt.encoding ? 'utf8' : opt.encoding;
var encoding = (opt.encoding === undefined ? 'utf8' : opt.encoding);
// open
var term = pty.open(cols, rows);
self._master = new PipeSocket(term.master);
self._master.setEncoding(encoding);
if (encoding !== null) {
self._master.setEncoding(encoding);
}
self._master.resume();
self._slave = new PipeSocket(term.slave);
self._slave.setEncoding(encoding);
if (encoding !== null) {
self._slave.setEncoding(encoding);
}
self._slave.resume();

@@ -192,5 +198,2 @@ self._socket = self._master;

};
UnixTerminal.prototype.write = function (data) {
this._socket.write(data);
};
UnixTerminal.prototype.destroy = function () {

@@ -197,0 +200,0 @@ var _this = this;

6

lib/windowsPtyAgent.js

@@ -25,4 +25,5 @@ "use strict";

var WindowsPtyAgent = /** @class */ (function () {
function WindowsPtyAgent(file, args, env, cwd, cols, rows, debug, _useConpty) {
function WindowsPtyAgent(file, args, env, cwd, cols, rows, debug, _useConpty, conptyInheritCursor) {
var _this = this;
if (conptyInheritCursor === void 0) { conptyInheritCursor = false; }
this._useConpty = _useConpty;

@@ -60,3 +61,3 @@ if (this._useConpty === undefined || this._useConpty === true) {

if (this._useConpty) {
term = this._ptyNative.startProcess(file, cols, rows, debug, this._generatePipeName());
term = this._ptyNative.startProcess(file, cols, rows, debug, this._generatePipeName(), conptyInheritCursor);
}

@@ -175,3 +176,2 @@ else {

// Something went wrong, just send back the shell PID
console.error('Could not fetch console process list');
agent.kill();

@@ -178,0 +178,0 @@ resolve([_this._innerPid]);

@@ -49,3 +49,3 @@ "use strict";

// Create new termal.
_this._agent = new windowsPtyAgent_1.WindowsPtyAgent(file, args, parsedEnv, cwd, _this._cols, _this._rows, false, opt.experimentalUseConpty);
_this._agent = new windowsPtyAgent_1.WindowsPtyAgent(file, args, parsedEnv, cwd, _this._cols, _this._rows, false, opt.experimentalUseConpty, opt.conptyInheritCursor);
_this._socket = _this._agent.outSocket;

@@ -108,2 +108,4 @@ // Not available until `ready` event emitted.

_this._forwardEvents();
// attach write method
_this._writeMethod = function (data) { return _this._defer(function () { return _this._agent.inSocket.write(data); }); };
return _this;

@@ -118,11 +120,2 @@ }

/**
* Events
*/
WindowsTerminal.prototype.write = function (data) {
var _this = this;
this._defer(function () {
_this._agent.inSocket.write(data);
});
};
/**
* TTY

@@ -129,0 +122,0 @@ */

@@ -7,3 +7,3 @@ {

},
"version": "0.9.0-beta1",
"version": "0.9.0-beta10",
"license": "MIT",

@@ -43,3 +43,4 @@ "main": "./lib/index.js",

"test": "cross-env NODE_ENV=test mocha -R spec --exit lib/*.test.js",
"prepublish": "npm run tsc"
"prepare": "npm run tsc",
"prepublishOnly": "npm run tsc"
},

@@ -46,0 +47,0 @@ "dependencies": {

@@ -28,2 +28,3 @@ # node-pty

- [Extraterm](http://extraterm.org/)
- [Wetty](https://github.com/krishnasrinivas/wetty) Browser based Terminal over HTTP and HTTPS

@@ -103,2 +104,25 @@ Do you use node-pty in your application as well? Please open a [Pull Request](https://github.com/Tyriar/node-pty/pulls) to include it here. We would love to have it in our list.

## Flow Control
Automatic flow control can be enabled by either providing `handleFlowControl = true` in the constructor options or setting it later on:
```js
const PAUSE = '\x13'; // XOFF
const RESUME = '\x11'; // XON
const ptyProcess = pty.spawn(shell, [], {handleFlowControl: true});
// flow control in action
ptyProcess.write(PAUSE); // pty will block and pause the slave program
...
ptyProcess.write(RESUME); // pty will enter flow mode and resume the slave program
// temporarily disable/re-enable flow control
ptyProcess.handleFlowControl = false;
...
ptyProcess.handleFlowControl = true;
```
By default `PAUSE` and `RESUME` are XON/XOFF control codes (as shown above). To avoid conflicts in environments that use these control codes for different purposes the messages can be customized as `flowControlPause: string` and `flowControlResume: string` in the constructor options. `PAUSE` and `RESUME` are not passed to the underlying pseudoterminal if flow control is enabled.
## Troubleshooting

@@ -105,0 +129,0 @@

@@ -120,2 +120,5 @@ /**

experimentalUseConpty?: boolean | undefined;
handleFlowControl?: boolean;
flowControlPause?: string;
flowControlResume?: string;
}

@@ -122,0 +125,0 @@

@@ -6,3 +6,3 @@ /**

interface IConptyNative {
startProcess(file: string, cols: number, rows: number, debug: boolean, pipeName: string): IConptyProcess;
startProcess(file: string, cols: number, rows: number, debug: boolean, pipeName: string, conptyInheritCursor: boolean): IConptyProcess;
connect(ptyId: number, commandLine: string, cwd: string, env: string[], onExitCallback: (exitCode: number) => void): { pid: number };

@@ -9,0 +9,0 @@ resize(ptyId: number, cols: number, rows: number): void;

@@ -9,3 +9,7 @@ /**

import { UnixTerminal } from './unixTerminal';
import pollUntil = require('pollUntil');
const terminalConstructor = (process.platform === 'win32') ? WindowsTerminal : UnixTerminal;
const SHELL = (process.platform === 'win32') ? 'cmd.exe' : '/bin/bash';
let terminalCtor: WindowsTerminal | UnixTerminal;

@@ -18,2 +22,3 @@ if (process.platform === 'win32') {

describe('Terminal', () => {

@@ -28,2 +33,32 @@ describe('constructor', () => {

});
describe('automatic flow control', () => {
it('should respect ctor flow control options', () => {
const pty = new terminalConstructor(SHELL, [], {handleFlowControl: true, flowControlPause: 'abc', flowControlResume: '123'});
assert.equal(pty.handleFlowControl, true);
assert.equal((pty as any)._flowControlPause, 'abc');
assert.equal((pty as any)._flowControlResume, '123');
});
it('should do flow control automatically', async function(): Promise<void> {
this.timeout(10000);
const pty = new terminalConstructor(SHELL, [], {handleFlowControl: true, flowControlPause: 'PAUSE', flowControlResume: 'RESUME'});
let read: string = '';
pty.on('data', data => read += data);
pty.on('pause', () => read += 'paused');
pty.on('resume', () => read += 'resumed');
pty.write('1');
pty.write('PAUSE');
pty.write('2');
pty.write('RESUME');
pty.write('3');
await (<any>pollUntil)(() => {
// important here: no data should be delivered between 'paused' and 'resumed'
if (process.platform === 'win32') {
read.endsWith('1\u001b[0Kpausedresumed2\u001b[0K3\u001b[0K');
} else {
read.endsWith('1pausedresumed23');
}
}, [], 20, 10);
});
});
});

@@ -16,2 +16,10 @@ /**

/**
* Default messages to indicate PAUSE/RESUME for automatic flow control.
* To avoid conflicts with rebound XON/XOFF control codes (such as on-my-zsh),
* the sequences can be customized in `IPtyForkOptions`.
*/
const FLOW_CONTROL_PAUSE = '\x13'; // defaults to XOFF
const FLOW_CONTROL_RESUME = '\x11'; // defaults to XON
export abstract class Terminal implements ITerminal {

@@ -32,2 +40,6 @@ protected _socket: Socket;

protected _internalee: EventEmitter;
protected _writeMethod: (data: string) => void;
private _flowControlPause: string;
private _flowControlResume: string;
public handleFlowControl: boolean;

@@ -61,4 +73,25 @@ private _onData = new EventEmitter2<string>();

this._checkType('encoding', opt.encoding ? opt.encoding : null, 'string');
// setup flow control handling
this.handleFlowControl = !!(opt.handleFlowControl);
this._flowControlPause = opt.flowControlPause || FLOW_CONTROL_PAUSE;
this._flowControlResume = opt.flowControlResume || FLOW_CONTROL_RESUME;
}
public write(data: string): void {
if (this.handleFlowControl) {
// PAUSE/RESUME messages are not forwarded to the pty
if (data === this._flowControlPause) {
this.pause();
return;
}
if (data === this._flowControlResume) {
this.resume();
return;
}
}
// everything else goes to the real pty
this._writeMethod(data);
}
protected _forwardEvents(): void {

@@ -137,3 +170,2 @@ this.on('data', e => this._onData.fire(e));

public abstract write(data: string): void;
public abstract resize(cols: number, rows: number): void;

@@ -140,0 +172,0 @@ public abstract destroy(): void;

@@ -9,4 +9,4 @@ /**

export interface IExitEvent {
exitCode: number;
signal: number | undefined;
exitCode: number;
signal: number | undefined;
}

@@ -13,0 +13,0 @@

@@ -159,2 +159,5 @@ /**

this._forwardEvents();
// attach write method
this._writeMethod = (data: string) => this._socket.write(data);
}

@@ -179,3 +182,3 @@

const rows = opt.rows || DEFAULT_ROWS;
const encoding = opt.encoding ? 'utf8' : opt.encoding;
const encoding = (opt.encoding === undefined ? 'utf8' : opt.encoding);

@@ -186,7 +189,11 @@ // open

self._master = new PipeSocket(<number>term.master);
self._master.setEncoding(encoding);
if (encoding !== null) {
self._master.setEncoding(encoding);
}
self._master.resume();
self._slave = new PipeSocket(term.slave);
self._slave.setEncoding(encoding);
if (encoding !== null) {
self._slave.setEncoding(encoding);
}
self._slave.resume();

@@ -219,6 +226,2 @@

public write(data: string): void {
this._socket.write(data);
}
public destroy(): void {

@@ -225,0 +228,0 @@ this._close();

@@ -54,3 +54,4 @@ /**

debug: boolean,
private _useConpty: boolean | undefined
private _useConpty: boolean | undefined,
conptyInheritCursor: boolean = false
) {

@@ -88,3 +89,3 @@ if (this._useConpty === undefined || this._useConpty === true) {

if (this._useConpty) {
term = (this._ptyNative as IConptyNative).startProcess(file, cols, rows, debug, this._generatePipeName());
term = (this._ptyNative as IConptyNative).startProcess(file, cols, rows, debug, this._generatePipeName(), conptyInheritCursor);
} else {

@@ -182,3 +183,2 @@ term = (this._ptyNative as IWinptyNative).startProcess(file, commandLine, env, cwd, cols, rows, debug);

// Something went wrong, just send back the shell PID
console.error('Could not fetch console process list');
agent.kill();

@@ -185,0 +185,0 @@ resolve([ this._innerPid ]);

@@ -13,2 +13,3 @@ /**

import { assign } from './utils';
import { IWindowsPtyForkOptions } from 'node-pty';

@@ -23,3 +24,3 @@ const DEFAULT_FILE = 'cmd.exe';

constructor(file?: string, args?: ArgvOrCommandLine, opt?: IPtyForkOptions) {
constructor(file?: string, args?: ArgvOrCommandLine, opt?: IWindowsPtyForkOptions) {
super(opt);

@@ -51,3 +52,3 @@

// Create new termal.
this._agent = new WindowsPtyAgent(file, args, parsedEnv, cwd, this._cols, this._rows, false, opt.experimentalUseConpty);
this._agent = new WindowsPtyAgent(file, args, parsedEnv, cwd, this._cols, this._rows, false, opt.experimentalUseConpty, opt.conptyInheritCursor);
this._socket = this._agent.outSocket;

@@ -125,2 +126,5 @@

this._forwardEvents();
// attach write method
this._writeMethod = (data: string) => this._defer(() => this._agent.inSocket.write(data));
}

@@ -137,12 +141,2 @@

/**
* Events
*/
public write(data: string): void {
this._defer(() => {
this._agent.inSocket.write(data);
});
}
/**
* TTY

@@ -149,0 +143,0 @@ */

@@ -18,3 +18,3 @@ /**

*/
export function spawn(file: string, args: string[] | string, options: IPtyForkOptions): IPty;
export function spawn(file: string, args: string[] | string, options: IPtyForkOptions | IWindowsPtyForkOptions): IPty;

@@ -30,2 +30,11 @@ export interface IPtyForkOptions {

encoding?: string;
}
export interface IWindowsPtyForkOptions {
name?: string;
cols?: number;
rows?: number;
cwd?: string;
env?: { [key: string]: string };
encoding?: string;
/**

@@ -39,2 +48,24 @@ * Whether to use the experimental ConPTY system on Windows. When this is not set, ConPTY will

experimentalUseConpty?: boolean;
/**
* Whether to use PSEUDOCONSOLE_INHERIT_CURSOR in conpty.
* @see https://docs.microsoft.com/en-us/windows/console/createpseudoconsole
*/
conptyInheritCursor?: boolean;
/**
* Whether to enable flow control handling (false by default). If enabled a message of `flowControlPause`
* will pause the socket and thus blocking the slave program execution due to buffer back pressure.
* A message of `flowControlResume` will resume the socket into flow mode.
* For performance reasons only a single message as a whole will match (no message part matching).
* If flow control is enabled the `flowControlPause` and `flowControlResume` messages are not forwarded to
* the underlying pseudoterminal.
*/
handleFlowControl?: boolean;
/**
* The string that should pause the pty when `handleFlowControl` is true. Default is XOFF ('\x13').
*/
flowControlPause?: string;
/**
* The string that should resume the pty when `handleFlowControl` is true. Default is XON ('\x11').
*/
flowControlResume?: string;
}

@@ -49,3 +80,3 @@

*/
pid: number;
readonly pid: number;

@@ -55,3 +86,3 @@ /**

*/
cols: number;
readonly cols: number;

@@ -61,3 +92,3 @@ /**

*/
rows: number;
readonly rows: number;

@@ -67,5 +98,11 @@ /**

*/
process: string;
readonly process: string;
/**
* Whether to handle flow control. Useful to disable/re-enable flow control during runtime.
* Use this for binary data that is likely to contain the `flowControlPause` string by accident.
*/
handleFlowControl: boolean;
/**
* Adds an event listener for when a data event fires. This happens when data is returned from

@@ -75,3 +112,3 @@ * the pty.

*/
onData: IEvent<string>;
readonly onData: IEvent<string>;

@@ -82,3 +119,3 @@ /**

*/
onExit: IEvent<{ exitCode: number, signal?: number }>;
readonly onExit: IEvent<{ exitCode: number, signal?: number }>;

@@ -85,0 +122,0 @@ /**

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc