UNPKG

@microsoft/dev-tunnels-ssh

Version:

SSH library for Dev Tunnels

133 lines 5.28 kB
"use strict"; // // Copyright (c) Microsoft Corporation. All rights reserved. // Object.defineProperty(exports, "__esModule", { value: true }); exports.SshStream = void 0; const promiseCompletionSource_1 = require("./util/promiseCompletionSource"); const stream_1 = require("stream"); /** * Adapts an SshChannel as a Readable+Writable stream. */ class SshStream extends stream_1.Duplex { constructor(channel) { let readPaused = null; super({ async write(chunk, encoding, cb) { let error; try { let buffer; if (typeof chunk === 'string') { buffer = Buffer.from(chunk, encoding); } else if (chunk instanceof Buffer || chunk instanceof Uint8Array) { buffer = chunk; } else { throw new Error('Unsupported chunk type: ' + typeof chunk); } await channel.send(buffer); } catch (e) { if (!(e instanceof Error)) throw e; error = e; } if (cb) { cb(error); } }, async writev(chunks, cb) { let error; try { if (chunks.length === 1) { return this.write(chunks[0].chunk, chunks[0].encoding, cb); } else { function BufferReduce(accumulator, chunk) { if (chunk.chunk instanceof Buffer || chunk.chunk instanceof Uint8Array) { return accumulator + chunk.chunk.length; } else { throw new Error('Unsupported chunk type: ' + typeof chunk.chunk); } } const totalLength = chunks.reduce(BufferReduce, 0); const singleBuffer = Buffer.alloc(totalLength); let singleBufferIndex = 0; for (let i = 0; i < chunks.length; i++) { chunks[i].chunk.copy(singleBuffer, singleBufferIndex); singleBufferIndex += chunks[i].chunk.length; } await channel.send(singleBuffer); } } catch (e) { if (!(e instanceof Error)) throw e; error = e; } if (cb) { cb(error); } }, async final(cb) { let error; try { await channel.close(); } catch (e) { if (!(e instanceof Error)) throw e; error = e; } if (cb) { cb(error); } }, read() { if (readPaused) { readPaused.resolve(); readPaused = null; } }, }); channel.onDataReceived(async (data) => { const buffer = Buffer.alloc(data.length); data.copy(buffer); const result = this.push(buffer); // Our flow control isn't great. Once we hit the highWaterMark, // we stop adjusting the SSH window until our own reader has caught up, // and then *all* the data received and buffered in the interim suddenly // gets 'adjusted' so that we tend to be somewhat choppy about adjusting the window. // Improving this would require that we know when the data we push gets passed to // the Duplex reader, and I don't think there's a way to get that notification. // So I suspect we'd have to dump Duplex and implement the stream ourselves. if (!result) { if (!readPaused) { readPaused = new promiseCompletionSource_1.PromiseCompletionSource(); } await readPaused.promise; } // Notify the channel that the data has been consumed and more data may be sent. channel.adjustWindow(buffer.length); }); channel.onClosed(() => { this.push(null); }); this.channel = channel; } /** * Destroys the stream and closes the underlying SSH channel. */ destroy(error) { void this.channel.close().catch(); super.destroy(error); return this; } toString() { return `SshStream(Channel Type: ${this.channel.channelType}, Id: ${this.channel.channelId}, RemoteId: ${this.channel.remoteChannelId})`; } } exports.SshStream = SshStream; //# sourceMappingURL=sshStream.js.map