node-pty 1.2.0-beta.13 → 1.2.0-beta.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/windowsConoutConnection.js +18 -12
- package/lib/windowsPtyAgent.js +70 -13
- package/lib/windowsTerminal.js +6 -0
- package/package.json +3 -2
- package/prebuilds/darwin-arm64/pty.node +0 -0
- package/prebuilds/darwin-arm64/spawn-helper +0 -0
- package/prebuilds/darwin-x64/pty.node +0 -0
- package/prebuilds/darwin-x64/spawn-helper +0 -0
- package/prebuilds/linux-arm64/pty.node +0 -0
- package/prebuilds/linux-x64/pty.node +0 -0
- package/prebuilds/win32-arm64/conpty.node +0 -0
- package/prebuilds/win32-arm64/conpty.pdb +0 -0
- package/prebuilds/win32-arm64/conpty_console_list.node +0 -0
- package/prebuilds/win32-arm64/conpty_console_list.pdb +0 -0
- package/prebuilds/win32-x64/conpty.node +0 -0
- package/prebuilds/win32-x64/conpty.pdb +0 -0
- package/prebuilds/win32-x64/conpty_console_list.node +0 -0
- package/prebuilds/win32-x64/conpty_console_list.pdb +0 -0
- package/src/unix/pty.cc +1 -0
- package/src/win/conpty.cc +37 -10
|
@@ -50,25 +50,15 @@ var eventEmitter2_1 = require("./eventEmitter2");
|
|
|
50
50
|
* the timer has started.
|
|
51
51
|
*/
|
|
52
52
|
var FLUSH_DATA_INTERVAL = 1000;
|
|
53
|
-
/**
|
|
54
|
-
* Connects to and manages the lifecycle of the conout socket. This socket must be drained on
|
|
55
|
-
* another thread in order to avoid deadlocks where Conpty waits for the out socket to drain
|
|
56
|
-
* when `ClosePseudoConsole` is called. This happens when data is being written to the terminal when
|
|
57
|
-
* the pty is closed.
|
|
58
|
-
*
|
|
59
|
-
* See also:
|
|
60
|
-
* - https://github.com/microsoft/node-pty/issues/375
|
|
61
|
-
* - https://github.com/microsoft/vscode/issues/76548
|
|
62
|
-
* - https://github.com/microsoft/terminal/issues/1810
|
|
63
|
-
* - https://docs.microsoft.com/en-us/windows/console/closepseudoconsole
|
|
64
|
-
*/
|
|
65
53
|
var ConoutConnection = /** @class */ (function () {
|
|
66
54
|
function ConoutConnection(_conoutPipeName, _useConptyDll) {
|
|
67
55
|
var _this = this;
|
|
68
56
|
this._conoutPipeName = _conoutPipeName;
|
|
69
57
|
this._useConptyDll = _useConptyDll;
|
|
70
58
|
this._isDisposed = false;
|
|
59
|
+
this._isReady = false;
|
|
71
60
|
this._onReady = new eventEmitter2_1.EventEmitter2();
|
|
61
|
+
this._onError = new eventEmitter2_1.EventEmitter2();
|
|
72
62
|
var workerData = {
|
|
73
63
|
conoutPipeName: _conoutPipeName
|
|
74
64
|
};
|
|
@@ -77,18 +67,34 @@ var ConoutConnection = /** @class */ (function () {
|
|
|
77
67
|
this._worker.on('message', function (message) {
|
|
78
68
|
switch (message) {
|
|
79
69
|
case 1 /* ConoutWorkerMessage.READY */:
|
|
70
|
+
_this._isReady = true;
|
|
80
71
|
_this._onReady.fire();
|
|
81
72
|
return;
|
|
82
73
|
default:
|
|
83
74
|
console.warn('Unexpected ConoutWorkerMessage', message);
|
|
84
75
|
}
|
|
85
76
|
});
|
|
77
|
+
this._worker.on('error', function (error) {
|
|
78
|
+
if (!_this._isDisposed && !_this._isReady) {
|
|
79
|
+
_this._onError.fire(error);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
this._worker.on('exit', function (code) {
|
|
83
|
+
if (!_this._isDisposed && !_this._isReady) {
|
|
84
|
+
_this._onError.fire(new Error("Conout worker exited before connecting (code ".concat(code, ")")));
|
|
85
|
+
}
|
|
86
|
+
});
|
|
86
87
|
}
|
|
87
88
|
Object.defineProperty(ConoutConnection.prototype, "onReady", {
|
|
88
89
|
get: function () { return this._onReady.event; },
|
|
89
90
|
enumerable: false,
|
|
90
91
|
configurable: true
|
|
91
92
|
});
|
|
93
|
+
Object.defineProperty(ConoutConnection.prototype, "onError", {
|
|
94
|
+
get: function () { return this._onError.event; },
|
|
95
|
+
enumerable: false,
|
|
96
|
+
configurable: true
|
|
97
|
+
});
|
|
92
98
|
ConoutConnection.prototype.dispose = function () {
|
|
93
99
|
if (!this._useConptyDll && this._isDisposed) {
|
|
94
100
|
return;
|
package/lib/windowsPtyAgent.js
CHANGED
|
@@ -12,6 +12,7 @@ var path = require("path");
|
|
|
12
12
|
var child_process_1 = require("child_process");
|
|
13
13
|
var net_1 = require("net");
|
|
14
14
|
var windowsConoutConnection_1 = require("./windowsConoutConnection");
|
|
15
|
+
var eventEmitter2_1 = require("./eventEmitter2");
|
|
15
16
|
var utils_1 = require("./utils");
|
|
16
17
|
var conptyNative;
|
|
17
18
|
/**
|
|
@@ -20,16 +21,23 @@ var conptyNative;
|
|
|
20
21
|
* has started.
|
|
21
22
|
*/
|
|
22
23
|
var FLUSH_DATA_INTERVAL = 1000;
|
|
24
|
+
var CONNECTION_TIMEOUT = 5000;
|
|
25
|
+
var defaultWindowsPtyAgentOptions = {
|
|
26
|
+
connectionTimeout: CONNECTION_TIMEOUT,
|
|
27
|
+
conoutConnectionFactory: function (conoutPipeName, useConptyDll) { return new windowsConoutConnection_1.ConoutConnection(conoutPipeName, useConptyDll); }
|
|
28
|
+
};
|
|
23
29
|
/**
|
|
24
30
|
* This agent sits between the WindowsTerminal class and provides an interface for conpty.
|
|
25
31
|
*/
|
|
26
32
|
var WindowsPtyAgent = /** @class */ (function () {
|
|
27
|
-
function WindowsPtyAgent(file, args, env, cwd, cols, rows, debug, _useConptyDll, conptyInheritCursor) {
|
|
33
|
+
function WindowsPtyAgent(file, args, env, cwd, cols, rows, debug, _useConptyDll, conptyInheritCursor, options) {
|
|
28
34
|
if (_useConptyDll === void 0) { _useConptyDll = false; }
|
|
29
35
|
if (conptyInheritCursor === void 0) { conptyInheritCursor = false; }
|
|
36
|
+
if (options === void 0) { options = defaultWindowsPtyAgentOptions; }
|
|
30
37
|
var _this = this;
|
|
31
38
|
this._useConptyDll = _useConptyDll;
|
|
32
39
|
this._innerPid = 0;
|
|
40
|
+
this._onError = new eventEmitter2_1.EventEmitter2();
|
|
33
41
|
if (!conptyNative) {
|
|
34
42
|
conptyNative = (0, utils_1.loadNativeModule)('conpty').module;
|
|
35
43
|
}
|
|
@@ -52,24 +60,26 @@ var WindowsPtyAgent = /** @class */ (function () {
|
|
|
52
60
|
// We must wait for the worker to connect before calling conptyNative.connect()
|
|
53
61
|
// to avoid blocking the Node.js event loop in ConnectNamedPipe.
|
|
54
62
|
// See https://github.com/microsoft/node-pty/issues/763
|
|
55
|
-
this._conoutSocketWorker =
|
|
63
|
+
this._conoutSocketWorker = options.conoutConnectionFactory(term.conout, this._useConptyDll);
|
|
56
64
|
// Store pending connection info - we'll complete the connection when worker is ready
|
|
57
65
|
this._pendingPtyInfo = { pty: this._pty, commandLine: commandLine, cwd: cwd, env: env };
|
|
58
|
-
//
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
}, 5000);
|
|
66
|
+
// Never call connect() before the worker is ready, as ConnectNamedPipe
|
|
67
|
+
// would block the Node.js event loop while waiting for the output client.
|
|
68
|
+
this._connectionTimeout = setTimeout(function () {
|
|
69
|
+
_this._failPtyConnection(new Error('Timed out waiting for ConPTY output worker'));
|
|
70
|
+
}, options.connectionTimeout);
|
|
65
71
|
this._conoutSocketWorker.onReady(function () {
|
|
66
|
-
|
|
72
|
+
if (!_this._pendingPtyInfo) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
_this._clearConnectionTimeout();
|
|
67
76
|
_this._conoutSocketWorker.connectSocket(_this._outSocket);
|
|
68
77
|
// Now that the worker has connected to the output pipe, we can safely call
|
|
69
78
|
// conptyNative.connect() which calls ConnectNamedPipe - it won't block because
|
|
70
79
|
// the client (worker) is already connected
|
|
71
80
|
_this._completePtyConnection();
|
|
72
81
|
});
|
|
82
|
+
this._conoutSocketWorker.onError(function (error) { return _this._failPtyConnection(error); });
|
|
73
83
|
this._outSocket.on('connect', function () {
|
|
74
84
|
_this._outSocket.emit('ready_datapipe');
|
|
75
85
|
});
|
|
@@ -81,6 +91,11 @@ var WindowsPtyAgent = /** @class */ (function () {
|
|
|
81
91
|
});
|
|
82
92
|
this._inSocket.setEncoding('utf8');
|
|
83
93
|
}
|
|
94
|
+
Object.defineProperty(WindowsPtyAgent.prototype, "onError", {
|
|
95
|
+
get: function () { return this._onError.event; },
|
|
96
|
+
enumerable: false,
|
|
97
|
+
configurable: true
|
|
98
|
+
});
|
|
84
99
|
Object.defineProperty(WindowsPtyAgent.prototype, "inSocket", {
|
|
85
100
|
get: function () { return this._inSocket; },
|
|
86
101
|
enumerable: false,
|
|
@@ -108,13 +123,54 @@ var WindowsPtyAgent = /** @class */ (function () {
|
|
|
108
123
|
});
|
|
109
124
|
WindowsPtyAgent.prototype._completePtyConnection = function () {
|
|
110
125
|
var _this = this;
|
|
126
|
+
var _a;
|
|
127
|
+
if (!this._pendingPtyInfo) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
this._clearConnectionTimeout();
|
|
131
|
+
var _b = this._pendingPtyInfo, pty = _b.pty, commandLine = _b.commandLine, cwd = _b.cwd, env = _b.env;
|
|
132
|
+
this._pendingPtyInfo = undefined;
|
|
133
|
+
try {
|
|
134
|
+
var connect = conptyNative.connect(pty, commandLine, cwd, env, this._useConptyDll, function (c) { return _this._$onProcessExit(c); });
|
|
135
|
+
this._innerPid = connect.pid;
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
// connect() runs from the conout worker's onReady callback, so a throw
|
|
139
|
+
// here would otherwise surface as an
|
|
140
|
+
// uncaughtException with no way for the consumer to observe it.
|
|
141
|
+
var code = (_a = /error code: (\d+)/.exec(err.message)) === null || _a === void 0 ? void 0 : _a[1];
|
|
142
|
+
this._exitCode = code ? parseInt(code, 10) : -1;
|
|
143
|
+
try {
|
|
144
|
+
this._ptyNative.kill(this._pty, this._useConptyDll);
|
|
145
|
+
}
|
|
146
|
+
catch ( /* already gone */_c) { /* already gone */ }
|
|
147
|
+
this._conoutSocketWorker.dispose();
|
|
148
|
+
this._inSocket.destroy();
|
|
149
|
+
this._outSocket.destroy();
|
|
150
|
+
this._onError.fire(err);
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
WindowsPtyAgent.prototype._failPtyConnection = function (error) {
|
|
111
154
|
if (!this._pendingPtyInfo) {
|
|
112
155
|
return;
|
|
113
156
|
}
|
|
114
|
-
|
|
157
|
+
this._clearConnectionTimeout();
|
|
115
158
|
this._pendingPtyInfo = undefined;
|
|
116
|
-
|
|
117
|
-
|
|
159
|
+
this._exitCode = -1;
|
|
160
|
+
try {
|
|
161
|
+
this._ptyNative.kill(this._pty, this._useConptyDll);
|
|
162
|
+
}
|
|
163
|
+
catch ( /* already gone */_a) { /* already gone */ }
|
|
164
|
+
this._conoutSocketWorker.dispose();
|
|
165
|
+
this._inSocket.destroy();
|
|
166
|
+
this._outSocket.destroy();
|
|
167
|
+
this._onError.fire(error);
|
|
168
|
+
};
|
|
169
|
+
WindowsPtyAgent.prototype._clearConnectionTimeout = function () {
|
|
170
|
+
if (this._connectionTimeout) {
|
|
171
|
+
clearTimeout(this._connectionTimeout);
|
|
172
|
+
this._connectionTimeout = undefined;
|
|
173
|
+
}
|
|
118
174
|
};
|
|
119
175
|
WindowsPtyAgent.prototype.resize = function (cols, rows) {
|
|
120
176
|
if (this._exitCode !== undefined) {
|
|
@@ -128,6 +184,7 @@ var WindowsPtyAgent = /** @class */ (function () {
|
|
|
128
184
|
WindowsPtyAgent.prototype.kill = function () {
|
|
129
185
|
var _this = this;
|
|
130
186
|
// Prevent deferred connection from completing after kill
|
|
187
|
+
this._clearConnectionTimeout();
|
|
131
188
|
this._pendingPtyInfo = undefined;
|
|
132
189
|
// Tell the agent to kill the pty, this releases handles to the process
|
|
133
190
|
if (!this._useConptyDll) {
|
package/lib/windowsTerminal.js
CHANGED
|
@@ -52,6 +52,12 @@ var WindowsTerminal = /** @class */ (function (_super) {
|
|
|
52
52
|
// Create new termal.
|
|
53
53
|
_this._agent = new windowsPtyAgent_1.WindowsPtyAgent(file, args, parsedEnv, cwd, _this._cols, _this._rows, false, opt.useConptyDll, opt.conptyInheritCursor);
|
|
54
54
|
_this._socket = _this._agent.outSocket;
|
|
55
|
+
// The socket 'close' handler that drives 'exit' is only attached after
|
|
56
|
+
// ready_datapipe, which never fires when connect() fails.
|
|
57
|
+
_this._agent.onError(function () {
|
|
58
|
+
_this._close();
|
|
59
|
+
_this.emit('exit', _this._agent.exitCode);
|
|
60
|
+
});
|
|
55
61
|
// Not available until `ready` event emitted.
|
|
56
62
|
_this._pid = _this._agent.innerPid;
|
|
57
63
|
_this._fd = _this._agent.fd;
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"author": {
|
|
5
5
|
"name": "Microsoft Corporation"
|
|
6
6
|
},
|
|
7
|
-
"version": "1.2.0-beta.
|
|
7
|
+
"version": "1.2.0-beta.15",
|
|
8
8
|
"license": "MIT",
|
|
9
9
|
"main": "./lib/index.js",
|
|
10
10
|
"types": "./typings/node-pty.d.ts",
|
|
@@ -56,7 +56,8 @@
|
|
|
56
56
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
57
57
|
"@typescript-eslint/parser": "^8.0.0",
|
|
58
58
|
"cross-env": "^5.1.4",
|
|
59
|
-
"eslint": "^
|
|
59
|
+
"eslint": "^10.4.0",
|
|
60
|
+
"globals": "^14.0.0",
|
|
60
61
|
"mocha": "10",
|
|
61
62
|
"node-gyp": "^11.4.2",
|
|
62
63
|
"ps-list": "^6.0.0",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/src/unix/pty.cc
CHANGED
package/src/win/conpty.cc
CHANGED
|
@@ -46,7 +46,7 @@ struct pty_baton {
|
|
|
46
46
|
HANDLE hOut;
|
|
47
47
|
HPCON hpc;
|
|
48
48
|
|
|
49
|
-
HANDLE hShell;
|
|
49
|
+
HANDLE hShell = nullptr;
|
|
50
50
|
|
|
51
51
|
pty_baton(int _id, HANDLE _hIn, HANDLE _hOut, HPCON _hpc) : id(_id), hIn(_hIn), hOut(_hOut), hpc(_hpc) {};
|
|
52
52
|
};
|
|
@@ -384,6 +384,14 @@ static Napi::Value PtyConnect(const Napi::CallbackInfo& info) {
|
|
|
384
384
|
ConnectNamedPipe(handle->hIn, nullptr);
|
|
385
385
|
ConnectNamedPipe(handle->hOut, nullptr);
|
|
386
386
|
|
|
387
|
+
// Close hIn/hOut on every exit so a throw doesn't leak the named-pipe server handles.
|
|
388
|
+
auto closePipeHandles = [handle]() {
|
|
389
|
+
CloseHandle(handle->hIn);
|
|
390
|
+
CloseHandle(handle->hOut);
|
|
391
|
+
handle->hIn = nullptr;
|
|
392
|
+
handle->hOut = nullptr;
|
|
393
|
+
};
|
|
394
|
+
|
|
387
395
|
// Attach the pseudoconsole to the client application we're creating
|
|
388
396
|
STARTUPINFOEXW siEx{0};
|
|
389
397
|
siEx.StartupInfo.cb = sizeof(STARTUPINFOEXW);
|
|
@@ -394,12 +402,14 @@ static Napi::Value PtyConnect(const Napi::CallbackInfo& info) {
|
|
|
394
402
|
|
|
395
403
|
SIZE_T size = 0;
|
|
396
404
|
InitializeProcThreadAttributeList(NULL, 1, 0, &size);
|
|
397
|
-
BYTE
|
|
398
|
-
siEx.lpAttributeList = reinterpret_cast<PPROC_THREAD_ATTRIBUTE_LIST>(attrList);
|
|
405
|
+
std::unique_ptr<BYTE[]> attrList = std::make_unique<BYTE[]>(size);
|
|
406
|
+
siEx.lpAttributeList = reinterpret_cast<PPROC_THREAD_ATTRIBUTE_LIST>(attrList.get());
|
|
399
407
|
|
|
400
408
|
fSuccess = InitializeProcThreadAttributeList(siEx.lpAttributeList, 1, 0, &size);
|
|
401
409
|
if (!fSuccess) {
|
|
402
|
-
|
|
410
|
+
Napi::Error error = errorWithCode(info, "InitializeProcThreadAttributeList failed");
|
|
411
|
+
closePipeHandles();
|
|
412
|
+
throw error;
|
|
403
413
|
}
|
|
404
414
|
fSuccess = UpdateProcThreadAttribute(siEx.lpAttributeList,
|
|
405
415
|
0,
|
|
@@ -409,7 +419,10 @@ static Napi::Value PtyConnect(const Napi::CallbackInfo& info) {
|
|
|
409
419
|
NULL,
|
|
410
420
|
NULL);
|
|
411
421
|
if (!fSuccess) {
|
|
412
|
-
|
|
422
|
+
Napi::Error error = errorWithCode(info, "UpdateProcThreadAttribute failed");
|
|
423
|
+
DeleteProcThreadAttributeList(siEx.lpAttributeList);
|
|
424
|
+
closePipeHandles();
|
|
425
|
+
throw error;
|
|
413
426
|
}
|
|
414
427
|
|
|
415
428
|
PROCESS_INFORMATION piClient{};
|
|
@@ -426,9 +439,14 @@ static Napi::Value PtyConnect(const Napi::CallbackInfo& info) {
|
|
|
426
439
|
&piClient // lpProcessInformation
|
|
427
440
|
);
|
|
428
441
|
if (!fSuccess) {
|
|
429
|
-
|
|
442
|
+
Napi::Error error = errorWithCode(info, "Cannot create process");
|
|
443
|
+
DeleteProcThreadAttributeList(siEx.lpAttributeList);
|
|
444
|
+
closePipeHandles();
|
|
445
|
+
throw error;
|
|
430
446
|
}
|
|
431
447
|
|
|
448
|
+
DeleteProcThreadAttributeList(siEx.lpAttributeList);
|
|
449
|
+
|
|
432
450
|
HANDLE hLibrary = LoadConptyDll(info, useConptyDll);
|
|
433
451
|
bool fLoadedDll = hLibrary != nullptr;
|
|
434
452
|
if (useConptyDll && fLoadedDll)
|
|
@@ -447,8 +465,7 @@ static Napi::Value PtyConnect(const Napi::CallbackInfo& info) {
|
|
|
447
465
|
// Close the thread handle to avoid resource leak
|
|
448
466
|
CloseHandle(piClient.hThread);
|
|
449
467
|
// Close the input read and output write handle of the pseudoconsole
|
|
450
|
-
|
|
451
|
-
CloseHandle(handle->hOut);
|
|
468
|
+
closePipeHandles();
|
|
452
469
|
|
|
453
470
|
SetupExitCallback(env, exitCallback, handle);
|
|
454
471
|
|
|
@@ -550,7 +567,7 @@ static Napi::Value PtyKill(const Napi::CallbackInfo& info) {
|
|
|
550
567
|
const bool useConptyDll = info[1].As<Napi::Boolean>().Value();
|
|
551
568
|
|
|
552
569
|
std::lock_guard<std::mutex> lock(g_ptyHandlesMutex);
|
|
553
|
-
|
|
570
|
+
pty_baton* handle = get_pty_baton(lock, id);
|
|
554
571
|
|
|
555
572
|
if (handle != nullptr) {
|
|
556
573
|
HANDLE hLibrary = LoadConptyDll(info, useConptyDll);
|
|
@@ -565,7 +582,17 @@ static Napi::Value PtyKill(const Napi::CallbackInfo& info) {
|
|
|
565
582
|
pfnClosePseudoConsole(handle->hpc);
|
|
566
583
|
}
|
|
567
584
|
}
|
|
568
|
-
if (
|
|
585
|
+
// Defensive: if PtyConnect was never called (or failed before closing the
|
|
586
|
+
// pipe handles), release the named pipe server handles here.
|
|
587
|
+
if (handle->hIn != nullptr) {
|
|
588
|
+
CloseHandle(handle->hIn);
|
|
589
|
+
handle->hIn = nullptr;
|
|
590
|
+
}
|
|
591
|
+
if (handle->hOut != nullptr) {
|
|
592
|
+
CloseHandle(handle->hOut);
|
|
593
|
+
handle->hOut = nullptr;
|
|
594
|
+
}
|
|
595
|
+
if (useConptyDll && handle->hShell != nullptr) {
|
|
569
596
|
TerminateProcess(handle->hShell, 1);
|
|
570
597
|
}
|
|
571
598
|
}
|