UNPKG

@microsoft/dev-tunnels-ssh

Version:
130 lines 5.45 kB
"use strict"; // // Copyright (c) Microsoft Corporation. All rights reserved. // Object.defineProperty(exports, "__esModule", { value: true }); exports.SshRpcMessageStream = void 0; const rpc = require("vscode-jsonrpc"); const buffer_1 = require("buffer"); const sshData_1 = require("./io/sshData"); const contentLengthHeaderPrefix = 'Content-Length: '; const headersSeparator = '\r\n\r\n'; class SshRpcMessageReader { constructor(channel) { this.channel = channel; this.errorEmitter = new rpc.Emitter(); this.closeEmitter = new rpc.Emitter(); this.partialMessageEmitter = new rpc.Emitter(); this.callback = null; this.messageBuffer = new sshData_1.SshDataWriter(buffer_1.Buffer.alloc(1024)); this.headersLength = null; this.messageLength = null; this.onError = this.errorEmitter.event; this.onClose = this.closeEmitter.event; this.onPartialMessage = this.partialMessageEmitter.event; this.eventRegistration = this.channel.onDataReceived(this.onDataReceived.bind(this)); this.channel.onClosed((e) => { if (e.error) { this.errorEmitter.fire(e.error); } // Note: we always want to fire a close event to avoid the rpc connection // to be used. After the event any usage of the rpc message connection will // throw an error with this code: ConnectionErrors.Closed this.closeEmitter.fire(); }); } listen(callback) { this.callback = callback; return rpc.Disposable.create(() => { this.callback = null; }); } dispose() { if (this.eventRegistration) { this.eventRegistration.dispose(); } } onDataReceived(data) { this.messageBuffer.write(data); this.channel.adjustWindow(data.length); // In case of recursion, the `data` might have already been a slice of the message buffer, // but it could have been invalidated by expansion during write() above. data = this.messageBuffer.toBuffer(); if (this.messageLength === null) { const headersEnd = data.indexOf(headersSeparator); if (headersEnd < 0) { return; // Wait for more data. } const headers = data.slice(0, headersEnd).toString(); if (!headers.startsWith(contentLengthHeaderPrefix)) { throw new Error(`Message does not start with JSON-RPC headers.\n${headers}`); } this.headersLength = headersEnd + headersSeparator.length; this.messageLength = parseInt(headers.substr(contentLengthHeaderPrefix.length, headersEnd - contentLengthHeaderPrefix.length), 10); } const position = this.messageBuffer.position; const totalLength = this.headersLength + this.messageLength; if (position >= totalLength) { if (this.callback) { const messageJson = data.slice(this.headersLength, totalLength).toString(); let message; try { message = JSON.parse(messageJson); } catch (e) { if (!(e instanceof Error)) throw e; throw new Error(`Failed to parse JSON-RPC message: ${e.message}\n${messageJson}`); } this.callback(message); } this.messageLength = null; this.messageBuffer.position = 0; if (position > totalLength) { // Recursively receive the remaining data, which will cause it // to be copied to the beginning of the buffer; this.onDataReceived(data.slice(totalLength)); } } } } class SshRpcMessageWriter { constructor(channel) { this.channel = channel; this.errorEmitter = new rpc.Emitter(); this.closeEmitter = new rpc.Emitter(); this.onError = this.errorEmitter.event; this.onClose = this.closeEmitter.event; this.channel.onClosed((e) => { if (e.error) { this.errorEmitter.fire([ e.error, (e.errorMessage && { jsonrpc: e.errorMessage }) || undefined, e.exitStatus, ]); } this.closeEmitter.fire(); }); } write(message) { const messageJson = JSON.stringify(message); const messageData = buffer_1.Buffer.from(messageJson); const headerData = buffer_1.Buffer.from(contentLengthHeaderPrefix + messageData.length + headersSeparator); const data = buffer_1.Buffer.alloc(headerData.length + messageData.length); headerData.copy(data, 0); messageData.copy(data, headerData.length); return this.channel.send(data).catch((e) => { this.errorEmitter.fire([e, undefined, undefined]); }); } end() { } dispose() { } } class SshRpcMessageStream { constructor(channel) { this.reader = new SshRpcMessageReader(channel); this.writer = new SshRpcMessageWriter(channel); } } exports.SshRpcMessageStream = SshRpcMessageStream; //# sourceMappingURL=sshRpcMessageStream.js.map