UNPKG

vscode-jsonrpc

Version:
457 lines 19.2 kB
/* -------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. * ------------------------------------------------------------------------------------------ */ 'use strict'; var is = require('./is'); var messages_1 = require('./messages'); exports.ResponseError = messages_1.ResponseError; exports.ErrorCodes = messages_1.ErrorCodes; var messageReader_1 = require('./messageReader'); exports.StreamMessageReader = messageReader_1.StreamMessageReader; exports.IPCMessageReader = messageReader_1.IPCMessageReader; var messageWriter_1 = require('./messageWriter'); exports.StreamMessageWriter = messageWriter_1.StreamMessageWriter; exports.IPCMessageWriter = messageWriter_1.IPCMessageWriter; var events_1 = require('./events'); exports.Event = events_1.Event; exports.Emitter = events_1.Emitter; var cancellation_1 = require('./cancellation'); exports.CancellationTokenSource = cancellation_1.CancellationTokenSource; exports.CancellationToken = cancellation_1.CancellationToken; var CancelNotification; (function (CancelNotification) { CancelNotification.type = { get method() { return '$/cancelRequest'; } }; })(CancelNotification || (CancelNotification = {})); (function (Trace) { Trace[Trace["Off"] = 0] = "Off"; Trace[Trace["Messages"] = 1] = "Messages"; Trace[Trace["Verbose"] = 2] = "Verbose"; })(exports.Trace || (exports.Trace = {})); var Trace = exports.Trace; var Trace; (function (Trace) { function fromString(value) { value = value.toLowerCase(); switch (value) { case 'off': return Trace.Off; case 'messages': return Trace.Messages; case 'verbose': return Trace.Verbose; default: return Trace.Off; } } Trace.fromString = fromString; })(Trace = exports.Trace || (exports.Trace = {})); var ConnectionState; (function (ConnectionState) { ConnectionState[ConnectionState["New"] = 1] = "New"; ConnectionState[ConnectionState["Listening"] = 2] = "Listening"; ConnectionState[ConnectionState["Closed"] = 3] = "Closed"; ConnectionState[ConnectionState["Disposed"] = 4] = "Disposed"; })(ConnectionState || (ConnectionState = {})); function createMessageConnection(messageReader, messageWriter, logger, client) { if (client === void 0) { client = false; } var sequenceNumber = 0; var version = '2.0'; var requestHandlers = Object.create(null); var eventHandlers = Object.create(null); var responsePromises = Object.create(null); var requestTokens = Object.create(null); var trace = Trace.Off; var tracer; var state = ConnectionState.New; var errorEmitter = new events_1.Emitter(); var closeEmitter = new events_1.Emitter(); var unhandledNotificationEmitter = new events_1.Emitter(); var disposeEmitter = new events_1.Emitter(); function isListening() { return state === ConnectionState.Listening; } function isDisposed() { return state === ConnectionState.Disposed; } function closeHandler() { if (state === ConnectionState.New || state === ConnectionState.Listening) { state = ConnectionState.Closed; closeEmitter.fire(undefined); } // If the connection is disposed don't sent close events. } ; function readErrorHandler(error) { errorEmitter.fire([error, undefined, undefined]); } function writeErrorHandler(data) { errorEmitter.fire(data); } messageReader.onClose(closeHandler); messageReader.onError(readErrorHandler); messageWriter.onClose(closeHandler); messageWriter.onError(writeErrorHandler); function handleRequest(requestMessage) { if (isDisposed()) { // we return here silently since we fired an event when the // connection got disposed. return; } function reply(resultOrError) { var message = { jsonrpc: version, id: requestMessage.id }; if (resultOrError instanceof messages_1.ResponseError) { message.error = resultOrError.toJson(); } else { message.result = is.undefined(resultOrError) ? null : resultOrError; } messageWriter.write(message); } function replyError(error) { var message = { jsonrpc: version, id: requestMessage.id, error: error.toJson() }; messageWriter.write(message); } function replySuccess(result) { // The JSON RPC defines that a response must either have a result or an error // So we can't treat undefined as a valid response result. if (is.undefined(result)) { result = null; } var message = { jsonrpc: version, id: requestMessage.id, result: result }; messageWriter.write(message); } var requestHandler = requestHandlers[requestMessage.method]; if (requestHandler) { var cancellationSource = new cancellation_1.CancellationTokenSource(); var tokenKey_1 = String(requestMessage.id); requestTokens[tokenKey_1] = cancellationSource; try { var handlerResult = requestHandler(requestMessage.params, cancellationSource.token); var promise = handlerResult; if (!handlerResult) { delete requestTokens[tokenKey_1]; replySuccess(handlerResult); } else if (promise.then) { promise.then(function (resultOrError) { delete requestTokens[tokenKey_1]; reply(resultOrError); }, function (error) { delete requestTokens[tokenKey_1]; if (error instanceof messages_1.ResponseError) { replyError(error); } else if (error && is.string(error.message)) { replyError(new messages_1.ResponseError(messages_1.ErrorCodes.InternalError, "Request " + requestMessage.method + " failed with message: " + error.message)); } else { replyError(new messages_1.ResponseError(messages_1.ErrorCodes.InternalError, "Request " + requestMessage.method + " failed unexpectedly without providing any details.")); } }); } else { delete requestTokens[tokenKey_1]; reply(handlerResult); } } catch (error) { delete requestTokens[tokenKey_1]; if (error instanceof messages_1.ResponseError) { reply(error); } else if (error && is.string(error.message)) { replyError(new messages_1.ResponseError(messages_1.ErrorCodes.InternalError, "Request " + requestMessage.method + " failed with message: " + error.message)); } else { replyError(new messages_1.ResponseError(messages_1.ErrorCodes.InternalError, "Request " + requestMessage.method + " failed unexpectedly without providing any details.")); } } } else { replyError(new messages_1.ResponseError(messages_1.ErrorCodes.MethodNotFound, "Unhandled method " + requestMessage.method)); } } function handleResponse(responseMessage) { if (isDisposed()) { // See handle request. return; } var key = String(responseMessage.id); var responsePromise = responsePromises[key]; if (trace != Trace.Off && tracer) { traceResponse(responseMessage, responsePromise); } if (responsePromise) { delete responsePromises[key]; try { if (is.defined(responseMessage.error)) { responsePromise.reject(responseMessage.error); } else if (is.defined(responseMessage.result)) { responsePromise.resolve(responseMessage.result); } else { throw new Error('Should never happen.'); } } catch (error) { if (error.message) { logger.error("Response handler '" + responsePromise.method + "' failed with message: " + error.message); } else { logger.error("Response handler '" + responsePromise.method + "' failed unexpectedly."); } } } } function handleNotification(message) { if (isDisposed()) { // See handle request. return; } var eventHandler; if (message.method === CancelNotification.type.method) { eventHandler = function (params) { var id = params.id; var source = requestTokens[String(id)]; if (source) { source.cancel(); } }; } else { eventHandler = eventHandlers[message.method]; } if (eventHandler) { try { if (trace != Trace.Off && tracer) { traceReceivedNotification(message); } eventHandler(message.params); } catch (error) { if (error.message) { logger.error("Notification handler '" + message.method + "' failed with message: " + error.message); } else { logger.error("Notification handler '" + message.method + "' failed unexpectedly."); } } } else { unhandledNotificationEmitter.fire(message); } } function handleInvalidMessage(message) { if (!message) { logger.error('Received empty message.'); return; } logger.error("Recevied message which is neither a response nor a notification message:\n" + JSON.stringify(message, null, 4)); // Test whether we find an id to reject the promise var responseMessage = message; if (is.string(responseMessage.id) || is.number(responseMessage.id)) { var key = String(responseMessage.id); var responseHandler = responsePromises[key]; if (responseHandler) { responseHandler.reject(new Error('The received response has neither a result nor an error property.')); } } } function traceRequest(message) { tracer.log("[" + (new Date().toLocaleTimeString()) + "] Sending request '" + message.method + " - (" + message.id + ")'."); if (trace === Trace.Verbose && message.params) { tracer.log("Params: " + JSON.stringify(message.params, null, 4) + "\n\n"); } } function traceSendNotification(message) { tracer.log("[" + (new Date().toLocaleTimeString()) + "] Sending notification '" + message.method + "'."); if (trace === Trace.Verbose) { if (message.params) { tracer.log("Params: " + JSON.stringify(message.params, null, 4) + "\n\n"); } else { tracer.log('No parameters provided.\n\n'); } } } function traceReceivedNotification(message) { tracer.log("[" + (new Date().toLocaleTimeString()) + "] Received notification '" + message.method + "'."); if (trace === Trace.Verbose) { if (message.params) { tracer.log("Params: " + JSON.stringify(message.params, null, 4) + "\n\n"); } else { tracer.log('No parameters provided.\n\n'); } } } function traceResponse(message, responsePromise) { if (responsePromise) { var error = message.error ? " Request failed: " + message.error.message + " (" + message.error.code + ")." : ''; tracer.log("[" + (new Date().toLocaleTimeString()) + "] Recevied response '" + responsePromise.method + " - (" + message.id + ")' in " + (Date.now() - responsePromise.timerStart) + "ms." + error); } else { tracer.log("[" + (new Date().toLocaleTimeString()) + "] Recevied response " + message.id + " without active response promise."); } if (trace === Trace.Verbose) { if (message.error && message.error.data) { tracer.log("Error data: " + JSON.stringify(message.error.data, null, 4) + "\n\n"); } else { if (message.result) { tracer.log("Result: " + JSON.stringify(message.result, null, 4) + "\n\n"); } else { tracer.log('No result returned.\n\n'); } } } } var callback = function (message) { if (messages_1.isRequestMessage(message)) { handleRequest(message); } else if (messages_1.isReponseMessage(message)) { handleResponse(message); } else if (messages_1.isNotificationMessage(message)) { handleNotification(message); } else { handleInvalidMessage(message); } }; var disposedMessage = 'Connection is disposed'; var connection = { sendNotification: function (type, params) { if (isDisposed()) { throw new Error(disposedMessage); } var notificatioMessage = { jsonrpc: version, method: type.method, params: params }; if (trace != Trace.Off && tracer) { traceSendNotification(notificatioMessage); } messageWriter.write(notificatioMessage); }, onNotification: function (type, handler) { if (isDisposed()) { throw new Error(disposedMessage); } eventHandlers[type.method] = handler; }, sendRequest: function (type, params, token) { if (isDisposed()) { throw new Error(disposedMessage); } var id = sequenceNumber++; var result = new Promise(function (resolve, reject) { var requestMessage = { jsonrpc: version, id: id, method: type.method, params: params }; var responsePromise = { method: type.method, timerStart: Date.now(), resolve: resolve, reject: reject }; if (trace != Trace.Off && tracer) { traceRequest(requestMessage); } try { messageWriter.write(requestMessage); } catch (e) { // Writing the message failed. So we need to reject the promise. responsePromise.reject(new messages_1.ResponseError(messages_1.ErrorCodes.MessageWriteError, e.message ? e.message : 'Unknown reason')); responsePromise = null; } if (responsePromise) { responsePromises[String(id)] = responsePromise; } }); if (token) { token.onCancellationRequested(function (event) { connection.sendNotification(CancelNotification.type, { id: id }); }); } return result; }, onRequest: function (type, handler) { if (isDisposed()) { throw new Error(disposedMessage); } requestHandlers[type.method] = handler; }, trace: function (_value, _tracer) { trace = _value; if (trace === Trace.Off) { tracer = null; } else { tracer = _tracer; } }, onError: errorEmitter.event, onClose: closeEmitter.event, onUnhandledNotification: unhandledNotificationEmitter.event, onDispose: disposeEmitter.event, dispose: function () { if (isDisposed()) { return; } state = ConnectionState.Disposed; disposeEmitter.fire(undefined); var error = new Error('Connection got disposed.'); Object.keys(responsePromises).forEach(function (key) { responsePromises[key].reject(error); }); responsePromises = Object.create(null); requestTokens = Object.create(null); }, listen: function () { if (isDisposed()) { throw new Error(disposedMessage); } if (isListening()) { throw new Error('Conneciton is already listening'); } state = ConnectionState.Listening; messageReader.listen(callback); } }; return connection; } function isMessageReader(value) { return is.defined(value.listen) && is.undefined(value.read); } function isMessageWriter(value) { return is.defined(value.write) && is.undefined(value.end); } function createServerMessageConnection(input, output, logger) { var reader = isMessageReader(input) ? input : new messageReader_1.StreamMessageReader(input); var writer = isMessageWriter(output) ? output : new messageWriter_1.StreamMessageWriter(output); return createMessageConnection(reader, writer, logger); } exports.createServerMessageConnection = createServerMessageConnection; function createClientMessageConnection(input, output, logger) { var reader = isMessageReader(input) ? input : new messageReader_1.StreamMessageReader(input); var writer = isMessageWriter(output) ? output : new messageWriter_1.StreamMessageWriter(output); return createMessageConnection(reader, writer, logger, true); } exports.createClientMessageConnection = createClientMessageConnection; //# sourceMappingURL=main.js.map