UNPKG

@microsoft/dev-tunnels-ssh

Version:
191 lines 8.55 kB
"use strict"; // // Copyright (c) Microsoft Corporation. All rights reserved. // Object.defineProperty(exports, "__esModule", { value: true }); exports.MultiChannelStream = void 0; const vscode_jsonrpc_1 = require("vscode-jsonrpc"); const sshChannel_1 = require("./sshChannel"); const sshStream_1 = require("./sshStream"); const sshSession_1 = require("./sshSession"); const sshSessionConfiguration_1 = require("./sshSessionConfiguration"); const transportMessages_1 = require("./messages/transportMessages"); const trace_1 = require("./trace"); const connectionMessages_1 = require("./messages/connectionMessages"); const sshReportProgressEventArgs_1 = require("./events/sshReportProgressEventArgs"); /** * Multiplexes multiple virtual streams (channels) over a single transport stream, using the * SSH protocol while providing a simplified interface without any encryption or authentication. * * This class is a complement to `SecureStream`, which provides only the encryption and * authentication functions of SSH. * * To communicate over multiple channels, two sides first establish a transport stream * over a pipe, socket, or anything else. Then one side accepts a channel while the * other side opens a channel. Either side can both open and accept channels over the * same transport stream, as long as the other side does the complementary action. */ class MultiChannelStream { /** * Creates a new multi-channel stream over an underlying transport stream. * @param transportStream Stream that is used to multiplex all the channels. */ constructor(transportStream) { this.transportStream = transportStream; this.disposed = false; this.disposables = []; this.reportProgressEmitter = new vscode_jsonrpc_1.Emitter(); /** * Event that is raised to report connection progress. * * See `Progress` for a description of the different progress events that can be reported. */ this.onReportProgress = this.reportProgressEmitter.event; /** * Gets or sets the maximum window size for channels within the multi-channel stream. * @see `SshChannel.maxWindowSize` */ this.channelMaxWindowSize = sshChannel_1.SshChannel.defaultMaxWindowSize; this.closedEmitter = new vscode_jsonrpc_1.Emitter(); this.onClosed = this.closedEmitter.event; this.channelOpeningEmitter = new vscode_jsonrpc_1.Emitter(); this.onChannelOpening = this.channelOpeningEmitter.event; if (!transportStream) throw new TypeError('transportStream is required.'); const noSecurityConfig = new sshSessionConfiguration_1.SshSessionConfiguration(false); this.session = new sshSession_1.SshSession(noSecurityConfig); this.session.onReportProgress((args) => this.raiseReportProgress(args.progress, args.sessionNumber), this, this.disposables); this.session.onClosed(this.onSessionClosed, this, this.disposables); this.session.onChannelOpening(this.onSessionChannelOpening, this, this.disposables); } get trace() { return this.session.trace; } set trace(trace) { this.session.trace = trace; } raiseReportProgress(progress, sessionNumber) { const args = new sshReportProgressEventArgs_1.SshReportProgressEventArgs(progress, sessionNumber); this.reportProgressEmitter.fire(args); } get isClosed() { return this.disposed || this.session.isClosed; } /** * Initiates the SSH session over the transport stream by exchanging initial messages with the * remote peer. Waits for the protocol version exchange and key exchange. Additional message * processing is kicked off as a background promise chain. * @param cancellation optional cancellation token. */ async connect(cancellation) { await this.session.connect(this.transportStream, cancellation); } /** * Asynchronously waits for the other side to open a channel. * @param channelType optional channel type * @param cancellation optional cancellation token. */ async acceptChannel(channelType, cancellation) { await this.session.connect(this.transportStream, cancellation); const channel = await this.session.acceptChannel(channelType, cancellation); return channel; } /** * Asynchronously waits for the other side to open a channel. * @param channelType optional channel type * @param cancellation optional cancellation token. */ async acceptStream(channelType, cancellation) { return this.createStream(await this.acceptChannel(channelType, cancellation)); } /** * Opens a channel and asynchronously waits for the other side to accept it. * @param channelType optional channel type * @param cancellation optional cancellation token. */ async openChannel(channelType, cancellation) { await this.session.connect(this.transportStream, cancellation); const openMessage = new connectionMessages_1.ChannelOpenMessage(); openMessage.channelType = channelType !== null && channelType !== void 0 ? channelType : sshChannel_1.SshChannel.sessionChannelType; openMessage.maxWindowSize = this.channelMaxWindowSize; const channel = await this.session.openChannel(openMessage, null, cancellation); return channel; } /** * Opens a channel and asynchronously waits for the other side to accept it. * @param channelType optional channel type * @param cancellation optional cancellation token. */ async openStream(channelType, cancellation) { return this.createStream(await this.openChannel(channelType, cancellation)); } /** * Creates a stream instance for a channel. May be overridden to create a `SshStream` subclass. */ createStream(channel) { return new sshStream_1.SshStream(channel); } /** * Connects, waits until the session closes or `cancellation` is cancelled, and then disposes the * session and the transport stream. * @param cancellation optional cancellation token. */ async connectAndRunUntilClosed(cancellation) { const disposables = []; const sessionClosedPromise = new Promise((resolve, reject) => { cancellation === null || cancellation === void 0 ? void 0 : cancellation.onCancellationRequested(reject, null, disposables); this.session.onClosed(resolve, null, disposables); }); try { await this.connect(cancellation); await sessionClosedPromise; } finally { disposables.forEach((d) => d.dispose()); await this.close(); } } dispose() { if (!this.disposed) { this.disposed = true; this.session.dispose(); this.unsubscribe(); try { if (this.transportStream) this.transportStream.close().catch((e) => { this.trace(trace_1.TraceLevel.Error, trace_1.SshTraceEventIds.streamCloseError, `Error closing transport stream: ${e.message}`, e); }); } catch (e) { if (!(e instanceof Error)) throw e; this.trace(trace_1.TraceLevel.Error, trace_1.SshTraceEventIds.streamCloseError, `Error closing transport stream: ${e.message}`, e); } } } async close() { if (!this.disposed) { this.disposed = true; await this.session.close(transportMessages_1.SshDisconnectReason.none, 'SshSession disposed'); this.session.dispose(); this.unsubscribe(); await this.transportStream.close(); } } onSessionClosed(e) { this.unsubscribe(); this.closedEmitter.fire(e); } onSessionChannelOpening(e) { if (e.isRemoteRequest) { e.channel.maxWindowSize = this.channelMaxWindowSize; } this.channelOpeningEmitter.fire(e); } unsubscribe() { this.disposables.forEach((d) => d.dispose()); this.disposables = []; } } exports.MultiChannelStream = MultiChannelStream; //# sourceMappingURL=multiChannelStream.js.map