sussudio
Version:
An unofficial VS Code Internal API
718 lines (717 loc) • 31.1 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { VSBuffer } from "../../../common/buffer.mjs";
import { onUnexpectedError } from "../../../common/errors.mjs";
import { Emitter, Event } from "../../../common/event.mjs";
import { Disposable } from "../../../common/lifecycle.mjs";
import { join } from "../../../common/path.mjs";
import { platform } from "../../../common/platform.mjs";
import { generateUuid } from "../../../common/uuid.mjs";
import { IPCServer } from "../common/ipc.mjs";
import { ChunkStream, Client, Protocol, SocketDiagnostics } from "../common/ipc.net.mjs";
// TODO@bpasero remove me once electron utility process has landed
function getNodeDependencies() {
return {
crypto: globalThis._VSCODE_NODE_MODULES.crypto,
zlib: globalThis._VSCODE_NODE_MODULES.zlib,
net: globalThis._VSCODE_NODE_MODULES.net,
os: globalThis._VSCODE_NODE_MODULES.os,
};
}
export class NodeSocket {
debugLabel;
socket;
_errorListener;
_closeListener;
_endListener;
_canWrite = true;
traceSocketEvent(type, data) {
SocketDiagnostics.traceSocketEvent(this.socket, this.debugLabel, type, data);
}
constructor(socket, debugLabel = '') {
this.debugLabel = debugLabel;
this.socket = socket;
this.traceSocketEvent("created" /* SocketDiagnosticsEventType.Created */, { type: 'NodeSocket' });
this._errorListener = (err) => {
this.traceSocketEvent("error" /* SocketDiagnosticsEventType.Error */, { code: err?.code, message: err?.message });
if (err) {
if (err.code === 'EPIPE') {
// An EPIPE exception at the wrong time can lead to a renderer process crash
// so ignore the error since the socket will fire the close event soon anyways:
// > https://nodejs.org/api/errors.html#errors_common_system_errors
// > EPIPE (Broken pipe): A write on a pipe, socket, or FIFO for which there is no
// > process to read the data. Commonly encountered at the net and http layers,
// > indicative that the remote side of the stream being written to has been closed.
return;
}
onUnexpectedError(err);
}
};
this.socket.on('error', this._errorListener);
this._closeListener = (hadError) => {
this.traceSocketEvent("close" /* SocketDiagnosticsEventType.Close */, { hadError });
this._canWrite = false;
};
this.socket.on('close', this._closeListener);
this._endListener = () => {
this.traceSocketEvent("nodeEndReceived" /* SocketDiagnosticsEventType.NodeEndReceived */);
this._canWrite = false;
};
this.socket.on('end', this._endListener);
}
dispose() {
this.socket.off('error', this._errorListener);
this.socket.off('close', this._closeListener);
this.socket.off('end', this._endListener);
this.socket.destroy();
}
onData(_listener) {
const listener = (buff) => {
this.traceSocketEvent("read" /* SocketDiagnosticsEventType.Read */, buff);
_listener(VSBuffer.wrap(buff));
};
this.socket.on('data', listener);
return {
dispose: () => this.socket.off('data', listener)
};
}
onClose(listener) {
const adapter = (hadError) => {
listener({
type: 0 /* SocketCloseEventType.NodeSocketCloseEvent */,
hadError: hadError,
error: undefined
});
};
this.socket.on('close', adapter);
return {
dispose: () => this.socket.off('close', adapter)
};
}
onEnd(listener) {
const adapter = () => {
listener();
};
this.socket.on('end', adapter);
return {
dispose: () => this.socket.off('end', adapter)
};
}
write(buffer) {
// return early if socket has been destroyed in the meantime
if (this.socket.destroyed || !this._canWrite) {
return;
}
// we ignore the returned value from `write` because we would have to cached the data
// anyways and nodejs is already doing that for us:
// > https://nodejs.org/api/stream.html#stream_writable_write_chunk_encoding_callback
// > However, the false return value is only advisory and the writable stream will unconditionally
// > accept and buffer chunk even if it has not been allowed to drain.
try {
this.traceSocketEvent("write" /* SocketDiagnosticsEventType.Write */, buffer);
this.socket.write(buffer.buffer, (err) => {
if (err) {
if (err.code === 'EPIPE') {
// An EPIPE exception at the wrong time can lead to a renderer process crash
// so ignore the error since the socket will fire the close event soon anyways:
// > https://nodejs.org/api/errors.html#errors_common_system_errors
// > EPIPE (Broken pipe): A write on a pipe, socket, or FIFO for which there is no
// > process to read the data. Commonly encountered at the net and http layers,
// > indicative that the remote side of the stream being written to has been closed.
return;
}
onUnexpectedError(err);
}
});
}
catch (err) {
if (err.code === 'EPIPE') {
// An EPIPE exception at the wrong time can lead to a renderer process crash
// so ignore the error since the socket will fire the close event soon anyways:
// > https://nodejs.org/api/errors.html#errors_common_system_errors
// > EPIPE (Broken pipe): A write on a pipe, socket, or FIFO for which there is no
// > process to read the data. Commonly encountered at the net and http layers,
// > indicative that the remote side of the stream being written to has been closed.
return;
}
onUnexpectedError(err);
}
}
end() {
this.traceSocketEvent("nodeEndSent" /* SocketDiagnosticsEventType.NodeEndSent */);
this.socket.end();
}
drain() {
this.traceSocketEvent("nodeDrainBegin" /* SocketDiagnosticsEventType.NodeDrainBegin */);
return new Promise((resolve, reject) => {
if (this.socket.bufferSize === 0) {
this.traceSocketEvent("nodeDrainEnd" /* SocketDiagnosticsEventType.NodeDrainEnd */);
resolve();
return;
}
const finished = () => {
this.socket.off('close', finished);
this.socket.off('end', finished);
this.socket.off('error', finished);
this.socket.off('timeout', finished);
this.socket.off('drain', finished);
this.traceSocketEvent("nodeDrainEnd" /* SocketDiagnosticsEventType.NodeDrainEnd */);
resolve();
};
this.socket.on('close', finished);
this.socket.on('end', finished);
this.socket.on('error', finished);
this.socket.on('timeout', finished);
this.socket.on('drain', finished);
});
}
}
var Constants;
(function (Constants) {
Constants[Constants["MinHeaderByteSize"] = 2] = "MinHeaderByteSize";
})(Constants || (Constants = {}));
var ReadState;
(function (ReadState) {
ReadState[ReadState["PeekHeader"] = 1] = "PeekHeader";
ReadState[ReadState["ReadHeader"] = 2] = "ReadHeader";
ReadState[ReadState["ReadBody"] = 3] = "ReadBody";
ReadState[ReadState["Fin"] = 4] = "Fin";
})(ReadState || (ReadState = {}));
/**
* See https://tools.ietf.org/html/rfc6455#section-5.2
*/
export class WebSocketNodeSocket extends Disposable {
socket;
_flowManager;
_incomingData;
_onData = this._register(new Emitter());
_onClose = this._register(new Emitter());
_isEnded = false;
_state = {
state: 1 /* ReadState.PeekHeader */,
readLen: 2 /* Constants.MinHeaderByteSize */,
fin: 0,
compressed: false,
firstFrameOfMessage: true,
mask: 0
};
get permessageDeflate() {
return this._flowManager.permessageDeflate;
}
get recordedInflateBytes() {
return this._flowManager.recordedInflateBytes;
}
traceSocketEvent(type, data) {
this.socket.traceSocketEvent(type, data);
}
/**
* Create a socket which can communicate using WebSocket frames.
*
* **NOTE**: When using the permessage-deflate WebSocket extension, if parts of inflating was done
* in a different zlib instance, we need to pass all those bytes into zlib, otherwise the inflate
* might hit an inflated portion referencing a distance too far back.
*
* @param socket The underlying socket
* @param permessageDeflate Use the permessage-deflate WebSocket extension
* @param inflateBytes "Seed" zlib inflate with these bytes.
* @param recordInflateBytes Record all bytes sent to inflate
*/
constructor(socket, permessageDeflate, inflateBytes, recordInflateBytes) {
super();
this.socket = socket;
this.traceSocketEvent("created" /* SocketDiagnosticsEventType.Created */, { type: 'WebSocketNodeSocket', permessageDeflate, inflateBytesLength: inflateBytes?.byteLength || 0, recordInflateBytes });
this._flowManager = this._register(new WebSocketFlowManager(this, permessageDeflate, inflateBytes, recordInflateBytes, this._onData, (data, compressed) => this._write(data, compressed)));
this._register(this._flowManager.onError((err) => {
// zlib errors are fatal, since we have no idea how to recover
console.error(err);
onUnexpectedError(err);
this._onClose.fire({
type: 0 /* SocketCloseEventType.NodeSocketCloseEvent */,
hadError: true,
error: err
});
}));
this._incomingData = new ChunkStream();
this._register(this.socket.onData(data => this._acceptChunk(data)));
this._register(this.socket.onClose((e) => this._onClose.fire(e)));
}
dispose() {
if (this._flowManager.isProcessingWriteQueue()) {
// Wait for any outstanding writes to finish before disposing
this._register(this._flowManager.onDidFinishProcessingWriteQueue(() => {
this.dispose();
}));
}
else {
this.socket.dispose();
super.dispose();
}
}
onData(listener) {
return this._onData.event(listener);
}
onClose(listener) {
return this._onClose.event(listener);
}
onEnd(listener) {
return this.socket.onEnd(listener);
}
write(buffer) {
this._flowManager.writeMessage(buffer);
}
_write(buffer, compressed) {
if (this._isEnded) {
// Avoid ERR_STREAM_WRITE_AFTER_END
return;
}
this.traceSocketEvent("webSocketNodeSocketWrite" /* SocketDiagnosticsEventType.WebSocketNodeSocketWrite */, buffer);
let headerLen = 2 /* Constants.MinHeaderByteSize */;
if (buffer.byteLength < 126) {
headerLen += 0;
}
else if (buffer.byteLength < 2 ** 16) {
headerLen += 2;
}
else {
headerLen += 8;
}
const header = VSBuffer.alloc(headerLen);
if (compressed) {
// The RSV1 bit indicates a compressed frame
header.writeUInt8(0b11000010, 0);
}
else {
header.writeUInt8(0b10000010, 0);
}
if (buffer.byteLength < 126) {
header.writeUInt8(buffer.byteLength, 1);
}
else if (buffer.byteLength < 2 ** 16) {
header.writeUInt8(126, 1);
let offset = 1;
header.writeUInt8((buffer.byteLength >>> 8) & 0b11111111, ++offset);
header.writeUInt8((buffer.byteLength >>> 0) & 0b11111111, ++offset);
}
else {
header.writeUInt8(127, 1);
let offset = 1;
header.writeUInt8(0, ++offset);
header.writeUInt8(0, ++offset);
header.writeUInt8(0, ++offset);
header.writeUInt8(0, ++offset);
header.writeUInt8((buffer.byteLength >>> 24) & 0b11111111, ++offset);
header.writeUInt8((buffer.byteLength >>> 16) & 0b11111111, ++offset);
header.writeUInt8((buffer.byteLength >>> 8) & 0b11111111, ++offset);
header.writeUInt8((buffer.byteLength >>> 0) & 0b11111111, ++offset);
}
this.socket.write(VSBuffer.concat([header, buffer]));
}
end() {
this._isEnded = true;
this.socket.end();
}
_acceptChunk(data) {
if (data.byteLength === 0) {
return;
}
this._incomingData.acceptChunk(data);
while (this._incomingData.byteLength >= this._state.readLen) {
if (this._state.state === 1 /* ReadState.PeekHeader */) {
// peek to see if we can read the entire header
const peekHeader = this._incomingData.peek(this._state.readLen);
const firstByte = peekHeader.readUInt8(0);
const finBit = (firstByte & 0b10000000) >>> 7;
const rsv1Bit = (firstByte & 0b01000000) >>> 6;
const secondByte = peekHeader.readUInt8(1);
const hasMask = (secondByte & 0b10000000) >>> 7;
const len = (secondByte & 0b01111111);
this._state.state = 2 /* ReadState.ReadHeader */;
this._state.readLen = 2 /* Constants.MinHeaderByteSize */ + (hasMask ? 4 : 0) + (len === 126 ? 2 : 0) + (len === 127 ? 8 : 0);
this._state.fin = finBit;
if (this._state.firstFrameOfMessage) {
// if the frame is compressed, the RSV1 bit is set only for the first frame of the message
this._state.compressed = Boolean(rsv1Bit);
}
this._state.firstFrameOfMessage = Boolean(finBit);
this._state.mask = 0;
this.traceSocketEvent("webSocketNodeSocketPeekedHeader" /* SocketDiagnosticsEventType.WebSocketNodeSocketPeekedHeader */, { headerSize: this._state.readLen, compressed: this._state.compressed, fin: this._state.fin });
}
else if (this._state.state === 2 /* ReadState.ReadHeader */) {
// read entire header
const header = this._incomingData.read(this._state.readLen);
const secondByte = header.readUInt8(1);
const hasMask = (secondByte & 0b10000000) >>> 7;
let len = (secondByte & 0b01111111);
let offset = 1;
if (len === 126) {
len = (header.readUInt8(++offset) * 2 ** 8
+ header.readUInt8(++offset));
}
else if (len === 127) {
len = (header.readUInt8(++offset) * 0
+ header.readUInt8(++offset) * 0
+ header.readUInt8(++offset) * 0
+ header.readUInt8(++offset) * 0
+ header.readUInt8(++offset) * 2 ** 24
+ header.readUInt8(++offset) * 2 ** 16
+ header.readUInt8(++offset) * 2 ** 8
+ header.readUInt8(++offset));
}
let mask = 0;
if (hasMask) {
mask = (header.readUInt8(++offset) * 2 ** 24
+ header.readUInt8(++offset) * 2 ** 16
+ header.readUInt8(++offset) * 2 ** 8
+ header.readUInt8(++offset));
}
this._state.state = 3 /* ReadState.ReadBody */;
this._state.readLen = len;
this._state.mask = mask;
this.traceSocketEvent("webSocketNodeSocketPeekedHeader" /* SocketDiagnosticsEventType.WebSocketNodeSocketPeekedHeader */, { bodySize: this._state.readLen, compressed: this._state.compressed, fin: this._state.fin, mask: this._state.mask });
}
else if (this._state.state === 3 /* ReadState.ReadBody */) {
// read body
const body = this._incomingData.read(this._state.readLen);
this.traceSocketEvent("webSocketNodeSocketReadData" /* SocketDiagnosticsEventType.WebSocketNodeSocketReadData */, body);
unmask(body, this._state.mask);
this.traceSocketEvent("webSocketNodeSocketUnmaskedData" /* SocketDiagnosticsEventType.WebSocketNodeSocketUnmaskedData */, body);
this._state.state = 1 /* ReadState.PeekHeader */;
this._state.readLen = 2 /* Constants.MinHeaderByteSize */;
this._state.mask = 0;
this._flowManager.acceptFrame(body, this._state.compressed, !!this._state.fin);
}
}
}
async drain() {
this.traceSocketEvent("webSocketNodeSocketDrainBegin" /* SocketDiagnosticsEventType.WebSocketNodeSocketDrainBegin */);
if (this._flowManager.isProcessingWriteQueue()) {
await Event.toPromise(this._flowManager.onDidFinishProcessingWriteQueue);
}
await this.socket.drain();
this.traceSocketEvent("webSocketNodeSocketDrainEnd" /* SocketDiagnosticsEventType.WebSocketNodeSocketDrainEnd */);
}
}
class WebSocketFlowManager extends Disposable {
_tracer;
_onData;
_writeFn;
_onError = this._register(new Emitter());
onError = this._onError.event;
_zlibInflateStream;
_zlibDeflateStream;
_writeQueue = [];
_readQueue = [];
_onDidFinishProcessingWriteQueue = this._register(new Emitter());
onDidFinishProcessingWriteQueue = this._onDidFinishProcessingWriteQueue.event;
get permessageDeflate() {
return Boolean(this._zlibInflateStream && this._zlibDeflateStream);
}
get recordedInflateBytes() {
if (this._zlibInflateStream) {
return this._zlibInflateStream.recordedInflateBytes;
}
return VSBuffer.alloc(0);
}
constructor(_tracer, permessageDeflate, inflateBytes, recordInflateBytes, _onData, _writeFn) {
super();
this._tracer = _tracer;
this._onData = _onData;
this._writeFn = _writeFn;
if (permessageDeflate) {
// See https://tools.ietf.org/html/rfc7692#page-16
// To simplify our logic, we don't negotiate the window size
// and simply dedicate (2^15) / 32kb per web socket
this._zlibInflateStream = this._register(new ZlibInflateStream(this._tracer, recordInflateBytes, inflateBytes, { windowBits: 15 }));
this._zlibDeflateStream = this._register(new ZlibDeflateStream(this._tracer, { windowBits: 15 }));
this._register(this._zlibInflateStream.onError((err) => this._onError.fire(err)));
this._register(this._zlibDeflateStream.onError((err) => this._onError.fire(err)));
}
else {
this._zlibInflateStream = null;
this._zlibDeflateStream = null;
}
}
writeMessage(message) {
this._writeQueue.push(message);
this._processWriteQueue();
}
_isProcessingWriteQueue = false;
async _processWriteQueue() {
if (this._isProcessingWriteQueue) {
return;
}
this._isProcessingWriteQueue = true;
while (this._writeQueue.length > 0) {
const message = this._writeQueue.shift();
if (this._zlibDeflateStream) {
const data = await this._deflateMessage(this._zlibDeflateStream, message);
this._writeFn(data, true);
}
else {
this._writeFn(message, false);
}
}
this._isProcessingWriteQueue = false;
this._onDidFinishProcessingWriteQueue.fire();
}
isProcessingWriteQueue() {
return (this._isProcessingWriteQueue);
}
/**
* Subsequent calls should wait for the previous `_deflateBuffer` call to complete.
*/
_deflateMessage(zlibDeflateStream, buffer) {
return new Promise((resolve, reject) => {
zlibDeflateStream.write(buffer);
zlibDeflateStream.flush(data => resolve(data));
});
}
acceptFrame(data, isCompressed, isLastFrameOfMessage) {
this._readQueue.push({ data, isCompressed, isLastFrameOfMessage });
this._processReadQueue();
}
_isProcessingReadQueue = false;
async _processReadQueue() {
if (this._isProcessingReadQueue) {
return;
}
this._isProcessingReadQueue = true;
while (this._readQueue.length > 0) {
const frameInfo = this._readQueue.shift();
if (this._zlibInflateStream && frameInfo.isCompressed) {
// See https://datatracker.ietf.org/doc/html/rfc7692#section-9.2
// Even if permessageDeflate is negotiated, it is possible
// that the other side might decide to send uncompressed messages
// So only decompress messages that have the RSV 1 bit set
const data = await this._inflateFrame(this._zlibInflateStream, frameInfo.data, frameInfo.isLastFrameOfMessage);
this._onData.fire(data);
}
else {
this._onData.fire(frameInfo.data);
}
}
this._isProcessingReadQueue = false;
}
/**
* Subsequent calls should wait for the previous `transformRead` call to complete.
*/
_inflateFrame(zlibInflateStream, buffer, isLastFrameOfMessage) {
return new Promise((resolve, reject) => {
// See https://tools.ietf.org/html/rfc7692#section-7.2.2
zlibInflateStream.write(buffer);
if (isLastFrameOfMessage) {
zlibInflateStream.write(VSBuffer.fromByteArray([0x00, 0x00, 0xff, 0xff]));
}
zlibInflateStream.flush(data => resolve(data));
});
}
}
class ZlibInflateStream extends Disposable {
_tracer;
_recordInflateBytes;
_onError = this._register(new Emitter());
onError = this._onError.event;
_zlibInflate;
_recordedInflateBytes = [];
_pendingInflateData = [];
get recordedInflateBytes() {
if (this._recordInflateBytes) {
return VSBuffer.concat(this._recordedInflateBytes);
}
return VSBuffer.alloc(0);
}
constructor(_tracer, _recordInflateBytes, inflateBytes, options) {
super();
this._tracer = _tracer;
this._recordInflateBytes = _recordInflateBytes;
this._zlibInflate = getNodeDependencies().zlib.createInflateRaw(options);
this._zlibInflate.on('error', (err) => {
this._tracer.traceSocketEvent("zlibInflateError" /* SocketDiagnosticsEventType.zlibInflateError */, { message: err?.message, code: err?.code });
this._onError.fire(err);
});
this._zlibInflate.on('data', (data) => {
this._tracer.traceSocketEvent("zlibInflateData" /* SocketDiagnosticsEventType.zlibInflateData */, data);
this._pendingInflateData.push(VSBuffer.wrap(data));
});
if (inflateBytes) {
this._tracer.traceSocketEvent("zlibInflateInitialWrite" /* SocketDiagnosticsEventType.zlibInflateInitialWrite */, inflateBytes.buffer);
this._zlibInflate.write(inflateBytes.buffer);
this._zlibInflate.flush(() => {
this._tracer.traceSocketEvent("zlibInflateInitialFlushFired" /* SocketDiagnosticsEventType.zlibInflateInitialFlushFired */);
this._pendingInflateData.length = 0;
});
}
}
write(buffer) {
if (this._recordInflateBytes) {
this._recordedInflateBytes.push(buffer.clone());
}
this._tracer.traceSocketEvent("zlibInflateWrite" /* SocketDiagnosticsEventType.zlibInflateWrite */, buffer);
this._zlibInflate.write(buffer.buffer);
}
flush(callback) {
this._zlibInflate.flush(() => {
this._tracer.traceSocketEvent("zlibInflateFlushFired" /* SocketDiagnosticsEventType.zlibInflateFlushFired */);
const data = VSBuffer.concat(this._pendingInflateData);
this._pendingInflateData.length = 0;
callback(data);
});
}
}
class ZlibDeflateStream extends Disposable {
_tracer;
_onError = this._register(new Emitter());
onError = this._onError.event;
_zlibDeflate;
_pendingDeflateData = [];
constructor(_tracer, options) {
super();
this._tracer = _tracer;
this._zlibDeflate = getNodeDependencies().zlib.createDeflateRaw({
windowBits: 15
});
this._zlibDeflate.on('error', (err) => {
this._tracer.traceSocketEvent("zlibDeflateError" /* SocketDiagnosticsEventType.zlibDeflateError */, { message: err?.message, code: err?.code });
this._onError.fire(err);
});
this._zlibDeflate.on('data', (data) => {
this._tracer.traceSocketEvent("zlibDeflateData" /* SocketDiagnosticsEventType.zlibDeflateData */, data);
this._pendingDeflateData.push(VSBuffer.wrap(data));
});
}
write(buffer) {
this._tracer.traceSocketEvent("zlibDeflateWrite" /* SocketDiagnosticsEventType.zlibDeflateWrite */, buffer.buffer);
this._zlibDeflate.write(buffer.buffer);
}
flush(callback) {
// See https://zlib.net/manual.html#Constants
this._zlibDeflate.flush(/*Z_SYNC_FLUSH*/ 2, () => {
this._tracer.traceSocketEvent("zlibDeflateFlushFired" /* SocketDiagnosticsEventType.zlibDeflateFlushFired */);
let data = VSBuffer.concat(this._pendingDeflateData);
this._pendingDeflateData.length = 0;
// See https://tools.ietf.org/html/rfc7692#section-7.2.1
data = data.slice(0, data.byteLength - 4);
callback(data);
});
}
}
function unmask(buffer, mask) {
if (mask === 0) {
return;
}
const cnt = buffer.byteLength >>> 2;
for (let i = 0; i < cnt; i++) {
const v = buffer.readUInt32BE(i * 4);
buffer.writeUInt32BE(v ^ mask, i * 4);
}
const offset = cnt * 4;
const bytesLeft = buffer.byteLength - offset;
const m3 = (mask >>> 24) & 0b11111111;
const m2 = (mask >>> 16) & 0b11111111;
const m1 = (mask >>> 8) & 0b11111111;
if (bytesLeft >= 1) {
buffer.writeUInt8(buffer.readUInt8(offset) ^ m3, offset);
}
if (bytesLeft >= 2) {
buffer.writeUInt8(buffer.readUInt8(offset + 1) ^ m2, offset + 1);
}
if (bytesLeft >= 3) {
buffer.writeUInt8(buffer.readUInt8(offset + 2) ^ m1, offset + 2);
}
}
// Read this before there's any chance it is overwritten
// Related to https://github.com/microsoft/vscode/issues/30624
// TODO@bpasero revert me once electron utility process has landed
export const XDG_RUNTIME_DIR = typeof process !== 'undefined' ? process.env['XDG_RUNTIME_DIR'] : undefined;
const safeIpcPathLengths = {
[2 /* Platform.Linux */]: 107,
[1 /* Platform.Mac */]: 103
};
export function createRandomIPCHandle() {
const randomSuffix = generateUuid();
// Windows: use named pipe
if (process.platform === 'win32') {
return `\\\\.\\pipe\\vscode-ipc-${randomSuffix}-sock`;
}
// Mac/Unix: use socket file and prefer
// XDG_RUNTIME_DIR over tmpDir
let result;
if (XDG_RUNTIME_DIR) {
result = join(XDG_RUNTIME_DIR, `vscode-ipc-${randomSuffix}.sock`);
}
else {
result = join(getNodeDependencies().os.tmpdir(), `vscode-ipc-${randomSuffix}.sock`);
}
// Validate length
validateIPCHandleLength(result);
return result;
}
export function createStaticIPCHandle(directoryPath, type, version) {
const scope = getNodeDependencies().crypto.createHash('md5').update(directoryPath).digest('hex');
// Windows: use named pipe
if (process.platform === 'win32') {
return `\\\\.\\pipe\\${scope}-${version}-${type}-sock`;
}
// Mac/Unix: use socket file and prefer
// XDG_RUNTIME_DIR over user data path
// unless portable
let result;
if (XDG_RUNTIME_DIR && !process.env['VSCODE_PORTABLE']) {
result = join(XDG_RUNTIME_DIR, `vscode-${scope.substr(0, 8)}-${version}-${type}.sock`);
}
else {
result = join(directoryPath, `${version}-${type}.sock`);
}
// Validate length
validateIPCHandleLength(result);
return result;
}
function validateIPCHandleLength(handle) {
const limit = safeIpcPathLengths[platform];
if (typeof limit === 'number' && handle.length >= limit) {
// https://nodejs.org/api/net.html#net_identifying_paths_for_ipc_connections
console.warn(`WARNING: IPC handle "${handle}" is longer than ${limit} chars, try a shorter --user-data-dir`);
}
}
export class Server extends IPCServer {
static toClientConnectionEvent(server) {
const onConnection = Event.fromNodeEventEmitter(server, 'connection');
return Event.map(onConnection, socket => ({
protocol: new Protocol(new NodeSocket(socket, 'ipc-server-connection')),
onDidClientDisconnect: Event.once(Event.fromNodeEventEmitter(socket, 'close'))
}));
}
server;
constructor(server) {
super(Server.toClientConnectionEvent(server));
this.server = server;
}
dispose() {
super.dispose();
if (this.server) {
this.server.close();
this.server = null;
}
}
}
export function serve(hook) {
return new Promise((c, e) => {
const server = getNodeDependencies().net.createServer();
server.on('error', e);
server.listen(hook, () => {
server.removeListener('error', e);
c(new Server(server));
});
});
}
export function connect(hook, clientId) {
return new Promise((c, e) => {
const socket = getNodeDependencies().net.createConnection(hook, () => {
socket.removeListener('error', e);
c(Client.fromSocket(new NodeSocket(socket, `ipc-client${clientId}`), clientId));
});
socket.once('error', e);
});
}