UNPKG

@microsoft/dev-tunnels-ssh

Version:
182 lines 8.76 kB
"use strict"; // // Copyright (c) Microsoft Corporation. All rights reserved. // Object.defineProperty(exports, "__esModule", { value: true }); exports.PipeExtensions = void 0; const buffer_1 = require("buffer"); const vscode_jsonrpc_1 = require("vscode-jsonrpc"); const transportMessages_1 = require("./messages/transportMessages"); const connectionMessages_1 = require("./messages/connectionMessages"); const promiseCompletionSource_1 = require("./util/promiseCompletionSource"); const errors_1 = require("./errors"); const trace_1 = require("./trace"); /** * Extension methods for piping sessions and channels. * * Note this class is not exported from the package. Instead, the piping APIs are exposed via * public methods on the `SshSession` and `SshChannel` classes. See those respective methods * for API documentation. */ class PipeExtensions { static async pipeSession(session, toSession) { if (!session) throw new TypeError('Session is required.'); if (!toSession) throw new TypeError('Target session is required'); const endCompletion = new promiseCompletionSource_1.PromiseCompletionSource(); session.onRequest((e) => { e.responsePromise = PipeExtensions.forwardSessionRequest(e, toSession, e.cancellation); }); toSession.onRequest((e) => { e.responsePromise = PipeExtensions.forwardSessionRequest(e, session, e.cancellation); }); session.onChannelOpening((e) => { if (e.isRemoteRequest) { e.openingPromise = PipeExtensions.forwardChannel(e, toSession, e.cancellation); } }); toSession.onChannelOpening((e) => { if (e.isRemoteRequest) { e.openingPromise = PipeExtensions.forwardChannel(e, session, e.cancellation); } }); session.onClosed((e) => { endCompletion.resolve(PipeExtensions.forwardSessionClose(toSession, e)); }); toSession.onClosed((e) => { endCompletion.resolve(PipeExtensions.forwardSessionClose(session, e)); }); const endPromise = await endCompletion.promise; await endPromise; } static async pipeChannel(channel, toChannel) { if (!channel) throw new TypeError('Channel is required.'); if (!toChannel) throw new TypeError('Target channel is required'); const endCompletion = new promiseCompletionSource_1.PromiseCompletionSource(); let closed = false; channel.onRequest((e) => { e.responsePromise = PipeExtensions.forwardChannelRequest(e, toChannel, e.cancellation); }); toChannel.onRequest((e) => { e.responsePromise = PipeExtensions.forwardChannelRequest(e, channel, e.cancellation); }); channel.onDataReceived((data) => { void PipeExtensions.forwardData(channel, toChannel, data).catch(); }); toChannel.onDataReceived((data) => { void PipeExtensions.forwardData(toChannel, channel, data).catch(); }); channel.onEof(() => { void PipeExtensions.forwardData(channel, toChannel, buffer_1.Buffer.alloc(0)).catch(); }); toChannel.onEof(() => { void PipeExtensions.forwardData(toChannel, channel, buffer_1.Buffer.alloc(0)).catch(); }); channel.onExtendedDataReceived((data) => { void PipeExtensions.forwardExtendedData(channel, toChannel, data.dataTypeCode, data.data).catch(); }); toChannel.onExtendedDataReceived((data) => { void PipeExtensions.forwardExtendedData(toChannel, channel, data.dataTypeCode, data.data).catch(); }); channel.onClosed((e) => { if (!closed) { closed = true; endCompletion.resolve(PipeExtensions.forwardChannelClose(channel, toChannel, e)); } }); toChannel.onClosed((e) => { if (!closed) { closed = true; endCompletion.resolve(PipeExtensions.forwardChannelClose(toChannel, channel, e)); } }); const endTask = await endCompletion.promise; await endTask; } static async forwardSessionRequest(e, toSession, cancellation) { // `SshSession.requestResponse()` always set `wantReply` to `true` internally and waits for a // response, but since the message buffer is cached the updated `wantReply` value is not sent. // Anyway, it's better to forward a no-reply message as another no-reply message, using // `SshSession.request()` instead. if (!e.request.wantReply) { return toSession .request(e.request, cancellation) .then(() => new transportMessages_1.SessionRequestSuccessMessage()); } return toSession.requestResponse(e.request, transportMessages_1.SessionRequestSuccessMessage, transportMessages_1.SessionRequestFailureMessage, cancellation); } static async forwardChannel(e, toSession, cancellation) { var _a; try { const toChannel = await toSession.openChannel(e.request, null, cancellation); void PipeExtensions.pipeChannel(e.channel, toChannel).catch(); return new connectionMessages_1.ChannelOpenConfirmationMessage(); } catch (err) { if (!(err instanceof Error)) throw err; const failureMessage = new connectionMessages_1.ChannelOpenFailureMessage(); if (err instanceof errors_1.SshChannelError) { failureMessage.reasonCode = (_a = err.reason) !== null && _a !== void 0 ? _a : connectionMessages_1.SshChannelOpenFailureReason.connectFailed; } else { failureMessage.reasonCode = connectionMessages_1.SshChannelOpenFailureReason.connectFailed; } failureMessage.description = err.message; return failureMessage; } } static async forwardChannelRequest(e, toChannel, cancellation) { e.request.recipientChannel = toChannel.remoteChannelId; const result = await toChannel.request(e.request, cancellation); return result ? new connectionMessages_1.ChannelSuccessMessage() : new connectionMessages_1.ChannelFailureMessage(); } static async forwardSessionClose(session, e) { var _a; return session.close(e.reason, e.message, (_a = e.error) !== null && _a !== void 0 ? _a : undefined); } static async forwardData(channel, toChannel, data) { // Make a copy of the buffer before sending because SshChannel.send() is an async operation // (it may need to wait for the window to open), while the buffer will be re-used for the // next message as sson as this task yields. const buffer = buffer_1.Buffer.alloc(data.length); data.copy(buffer); const promise = toChannel.send(buffer, vscode_jsonrpc_1.CancellationToken.None); channel.adjustWindow(buffer.length); return promise; } static async forwardExtendedData(channel, toChannel, dataTypeCode, data) { // Make a copy of the buffer before sending because SshChannel.send() is an async operation // (it may need to wait for the window to open), while the buffer will be re-used for the // next message as sson as this task yields. const buffer = buffer_1.Buffer.alloc(data.length); data.copy(buffer); const promise = toChannel.sendExtendedData(dataTypeCode, buffer, vscode_jsonrpc_1.CancellationToken.None); channel.adjustWindow(buffer.length); return promise; } static async forwardChannelClose(fromChannel, toChannel, e) { const message = `Piping channel closure.\n` + `Source: ${fromChannel.session} ${fromChannel}\n` + `Destination: ${toChannel.session} ${toChannel}\n`; toChannel.session.trace(trace_1.TraceLevel.Verbose, trace_1.SshTraceEventIds.channelClosed, message); if (e.error) { toChannel.close(e.error); return Promise.resolve(); } else if (e.exitSignal) { return toChannel.close(e.exitSignal, e.errorMessage); } else if (typeof e.exitStatus === 'number') { return toChannel.close(e.exitStatus); } else { return toChannel.close(); } } } exports.PipeExtensions = PipeExtensions; //# sourceMappingURL=pipeExtensions.js.map