@microsoft/dev-tunnels-ssh
Version:
SSH library for Dev Tunnels
133 lines • 5.28 kB
JavaScript
;
//
// 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