UNPKG

@microsoft/dev-tunnels-ssh

Version:
251 lines 8.87 kB
"use strict"; // // Copyright (c) Microsoft Corporation. All rights reserved. // Object.defineProperty(exports, "__esModule", { value: true }); exports.WebSocketStream = exports.NodeStream = exports.BaseStream = void 0; const buffer_1 = require("buffer"); const vscode_jsonrpc_1 = require("vscode-jsonrpc"); const cancellation_1 = require("./util/cancellation"); const errors_1 = require("./errors"); function handleCancellation(reject, cancellation) { if (cancellation) { if (cancellation.isCancellationRequested) { reject(new cancellation_1.CancellationError()); } else { cancellation.onCancellationRequested(() => { reject(new cancellation_1.CancellationError()); }); } } } /** * Base class for stream adapters. */ class BaseStream { constructor() { this.incomingData = []; this.pendingReads = []; this.error = null; this.disposed = false; this.closedEmitter = new vscode_jsonrpc_1.Emitter(); this.closed = this.closedEmitter.event; } onData(data) { while (this.pendingReads.length > 0) { const read = this.pendingReads.shift(); if (read.count >= data.length) { // This read operation consumes all of the incoming data. read.resolve(data); return; } else { // This read operation consumes part of the incoming data. const readData = data.slice(0, read.count); data = data.slice(read.count); read.resolve(readData); } } this.incomingData.push(data); } onEnd() { while (this.pendingReads.length > 0) { const read = this.pendingReads.shift(); read.resolve(null); } this.incomingData.push(buffer_1.Buffer.alloc(0)); } onError(error) { if (!this.error) { this.error = error; } while (this.pendingReads.length > 0) { const read = this.pendingReads.shift(); read.reject(error); } } async read(count, cancellation) { if (this.disposed) throw new errors_1.ObjectDisposedError(this); if (this.incomingData.length > 0) { // Consume data that has already arrived. let data = this.incomingData[0]; if (data.length === 0) { // Reached the end of the stream. return null; } else if (count >= data.length) { // Consuming the whole chunk. this.incomingData.shift(); } else { // Consuming part of the chunk. this.incomingData[0] = data.slice(count); data = data.slice(0, count); } return data; } else if (this.error) { throw this.error; } else { // Wait for more data to arrive. return await new Promise((resolve, reject) => { if (cancellation) { if (cancellation.isCancellationRequested) { reject(new cancellation_1.CancellationError()); return; } cancellation.onCancellationRequested(() => { // Discard any pending reads that use this cancellation token. for (let i = 0; i < this.pendingReads.length; i++) { if (Object.is(cancellation, this.pendingReads[i].cancellation)) { const read = this.pendingReads.splice(i--, 1)[0]; read.reject(new cancellation_1.CancellationError()); } } }); } this.pendingReads.push({ count, resolve, reject, cancellation }); }); } } dispose() { if (!this.disposed) { this.disposed = true; const error = new errors_1.ObjectDisposedError(this); this.onError(error); this.fireOnClose(error); } } fireOnClose(error) { this.closedEmitter.fire({ error }); } get isDisposed() { return this.disposed; } } exports.BaseStream = BaseStream; /** * Stream adapter for a Node.js Socket, Duplex stream, or Readable/Writable stream pair. */ class NodeStream extends BaseStream { constructor(duplexOrReadStream, writeStream) { super(); if (!duplexOrReadStream) throw new TypeError('Duplex or Readable/Writable stream are required.'); this.readStream = duplexOrReadStream; this.writeStream = writeStream || duplexOrReadStream; this.readStream.on('data', this.onData.bind(this)); this.readStream.on('end', this.onEnd.bind(this)); this.readStream.on('error', this.onError.bind(this)); this.readStream.on('close', () => { this.onEnd(); this.fireOnClose(); }); } async write(data, cancellation) { if (!data) throw new TypeError('Data is required.'); if (this.disposed) throw new errors_1.ObjectDisposedError(this); return new Promise((resolve, reject) => { handleCancellation(reject, cancellation); this.writeStream.write(data, (err) => { if (!err) { resolve(); } else { reject(err); } }); }); } async close(error, cancellation) { if (this.disposed) throw new errors_1.ObjectDisposedError(this); await new Promise((resolve, reject) => { handleCancellation(reject, cancellation); this.writeStream.end(resolve); }); this.disposed = true; this.onError(error || new errors_1.ObjectDisposedError(this)); this.closedEmitter.fire({ error }); } dispose() { if (!this.disposed) { const error = new errors_1.ObjectDisposedError(this); this.readStream.destroy(error); this.writeStream.destroy(error); } super.dispose(); } } exports.NodeStream = NodeStream; /** * Stream adapter for a browser websocket. */ class WebSocketStream extends BaseStream { constructor(websocket) { super(); this.websocket = websocket; if (!websocket) throw new TypeError('WebSocket is required.'); if (typeof websocket.binaryType === 'string' && websocket.binaryType !== 'arraybuffer') { throw new Error('WebSocket must use arraybuffer binary type.'); } websocket.onmessage = (e) => { this.onData(buffer_1.Buffer.from(e.data)); }; websocket.onclose = (e) => { if (e.wasClean) { this.onEnd(); } else { const error = new Error(e.reason); error.code = e.code; this.onError(error); } }; } get protocol() { return this.websocket.protocol; } async write(data, cancellation) { if (!data) throw new TypeError('Data is required.'); if (this.disposed) throw new errors_1.ObjectDisposedError(this); if ('readyState' in this.websocket) { if (this.websocket.readyState === 2 /* WebSocketReadyState.Closing */ || this.websocket.readyState === 3 /* WebSocketReadyState.Closed */) { throw new DOMException('WebSocket is already in CLOSING or CLOSED state.', 'InvalidStateError'); } } this.websocket.send(data); } async close(error, cancellation) { if (this.disposed) throw new errors_1.ObjectDisposedError(this); if (!error) { this.websocket.close(); } else { const code = typeof error.code === 'number' ? error.code : undefined; this.websocket.close(code, error.message); } this.disposed = true; this.closedEmitter.fire({ error }); this.onError(error || new Error('Stream closed.')); } dispose() { if (!this.disposed) { this.websocket.close(); } super.dispose(); } } exports.WebSocketStream = WebSocketStream; //# sourceMappingURL=streams.js.map