"use strict"; /* -------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. * ------------------------------------------------------------------------------------------ */ Object.defineProperty(exports, "__esModule", { value: true }); exports.createMessageConnection = exports.ConnectionOptions = exports.MessageStrategy = exports.CancellationStrategy = exports.CancellationSenderStrategy = exports.CancellationReceiverStrategy = exports.RequestCancellationReceiverStrategy = exports.IdCancellationReceiverStrategy = exports.ConnectionStrategy = exports.ConnectionError = exports.ConnectionErrors = exports.LogTraceNotification = exports.SetTraceNotification = exports.TraceFormat = exports.TraceValues = exports.Trace = exports.NullLogger = exports.ProgressType = exports.ProgressToken = void 0; const ral_1 = require("./ral"); const Is = require("./is"); const messages_1 = require("./messages"); const linkedMap_1 = require("./linkedMap"); const events_1 = require("./events"); const cancellation_1 = require("./cancellation"); var CancelNotification; (function (CancelNotification) { CancelNotification.type = new messages_1.NotificationType('$/cancelRequest'); })(CancelNotification || (CancelNotification = {})); var ProgressToken; (function (ProgressToken) { function is(value) { return typeof value === 'string' || typeof value === 'number'; } ProgressToken.is = is; })(ProgressToken || (exports.ProgressToken = ProgressToken = {})); var ProgressNotification; (function (ProgressNotification) { ProgressNotification.type = new messages_1.NotificationType('$/progress'); })(ProgressNotification || (ProgressNotification = {})); class ProgressType { constructor() { } } exports.ProgressType = ProgressType; var StarRequestHandler; (function (StarRequestHandler) { function is(value) { return Is.func(value); } StarRequestHandler.is = is; })(StarRequestHandler || (StarRequestHandler = {})); exports.NullLogger = Object.freeze({ error: () => { }, warn: () => { }, info: () => { }, log: () => { } }); var Trace; (function (Trace) { Trace[Trace["Off"] = 0] = "Off"; Trace[Trace["Messages"] = 1] = "Messages"; Trace[Trace["Compact"] = 2] = "Compact"; Trace[Trace["Verbose"] = 3] = "Verbose"; })(Trace || (exports.Trace = Trace = {})); var TraceValues; (function (TraceValues) { /** * Turn tracing off. */ TraceValues.Off = 'off'; /** * Trace messages only. */ TraceValues.Messages = 'messages'; /** * Compact message tracing. */ TraceValues.Compact = 'compact'; /** * Verbose message tracing. */ TraceValues.Verbose = 'verbose'; })(TraceValues || (exports.TraceValues = TraceValues = {})); (function (Trace) { function fromString(value) { if (!Is.string(value)) { return Trace.Off; } value = value.toLowerCase(); switch (value) { case 'off': return Trace.Off; case 'messages': return Trace.Messages; case 'compact': return Trace.Compact; case 'verbose': return Trace.Verbose; default: return Trace.Off; } } Trace.fromString = fromString; function toString(value) { switch (value) { case Trace.Off: return 'off'; case Trace.Messages: return 'messages'; case Trace.Compact: return 'compact'; case Trace.Verbose: return 'verbose'; default: return 'off'; } } Trace.toString = toString; })(Trace || (exports.Trace = Trace = {})); var TraceFormat; (function (TraceFormat) { TraceFormat["Text"] = "text"; TraceFormat["JSON"] = "json"; })(TraceFormat || (exports.TraceFormat = TraceFormat = {})); (function (TraceFormat) { function fromString(value) { if (!Is.string(value)) { return TraceFormat.Text; } value = value.toLowerCase(); if (value === 'json') { return TraceFormat.JSON; } else { return TraceFormat.Text; } } TraceFormat.fromString = fromString; })(TraceFormat || (exports.TraceFormat = TraceFormat = {})); var SetTraceNotification; (function (SetTraceNotification) { SetTraceNotification.type = new messages_1.NotificationType('$/setTrace'); })(SetTraceNotification || (exports.SetTraceNotification = SetTraceNotification = {})); var LogTraceNotification; (function (LogTraceNotification) { LogTraceNotification.type = new messages_1.NotificationType('$/logTrace'); })(LogTraceNotification || (exports.LogTraceNotification = LogTraceNotification = {})); var ConnectionErrors; (function (ConnectionErrors) { /** * The connection is closed. */ ConnectionErrors[ConnectionErrors["Closed"] = 1] = "Closed"; /** * The connection got disposed. */ ConnectionErrors[ConnectionErrors["Disposed"] = 2] = "Disposed"; /** * The connection is already in listening mode. */ ConnectionErrors[ConnectionErrors["AlreadyListening"] = 3] = "AlreadyListening"; })(ConnectionErrors || (exports.ConnectionErrors = ConnectionErrors = {})); class ConnectionError extends Error { constructor(code, message) { super(message); this.code = code; Object.setPrototypeOf(this, ConnectionError.prototype); } } exports.ConnectionError = ConnectionError; var ConnectionStrategy; (function (ConnectionStrategy) { function is(value) { const candidate = value; return candidate && Is.func(candidate.cancelUndispatched); } ConnectionStrategy.is = is; })(ConnectionStrategy || (exports.ConnectionStrategy = ConnectionStrategy = {})); var IdCancellationReceiverStrategy; (function (IdCancellationReceiverStrategy) { function is(value) { const candidate = value; return candidate && (candidate.kind === undefined || candidate.kind === 'id') && Is.func(candidate.createCancellationTokenSource) && (candidate.dispose === undefined || Is.func(candidate.dispose)); } IdCancellationReceiverStrategy.is = is; })(IdCancellationReceiverStrategy || (exports.IdCancellationReceiverStrategy = IdCancellationReceiverStrategy = {})); var RequestCancellationReceiverStrategy; (function (RequestCancellationReceiverStrategy) { function is(value) { const candidate = value; return candidate && candidate.kind === 'request' && Is.func(candidate.createCancellationTokenSource) && (candidate.dispose === undefined || Is.func(candidate.dispose)); } RequestCancellationReceiverStrategy.is = is; })(RequestCancellationReceiverStrategy || (exports.RequestCancellationReceiverStrategy = RequestCancellationReceiverStrategy = {})); var CancellationReceiverStrategy; (function (CancellationReceiverStrategy) { CancellationReceiverStrategy.Message = Object.freeze({ createCancellationTokenSource(_) { return new cancellation_1.CancellationTokenSource(); } }); function is(value) { return IdCancellationReceiverStrategy.is(value) || RequestCancellationReceiverStrategy.is(value); } CancellationReceiverStrategy.is = is; })(CancellationReceiverStrategy || (exports.CancellationReceiverStrategy = CancellationReceiverStrategy = {})); var CancellationSenderStrategy; (function (CancellationSenderStrategy) { CancellationSenderStrategy.Message = Object.freeze({ sendCancellation(conn, id) { return conn.sendNotification(CancelNotification.type, { id }); }, cleanup(_) { } }); function is(value) { const candidate = value; return candidate && Is.func(candidate.sendCancellation) && Is.func(candidate.cleanup); } CancellationSenderStrategy.is = is; })(CancellationSenderStrategy || (exports.CancellationSenderStrategy = CancellationSenderStrategy = {})); var CancellationStrategy; (function (CancellationStrategy) { CancellationStrategy.Message = Object.freeze({ receiver: CancellationReceiverStrategy.Message, sender: CancellationSenderStrategy.Message }); function is(value) { const candidate = value; return candidate && CancellationReceiverStrategy.is(candidate.receiver) && CancellationSenderStrategy.is(candidate.sender); } CancellationStrategy.is = is; })(CancellationStrategy || (exports.CancellationStrategy = CancellationStrategy = {})); var MessageStrategy; (function (MessageStrategy) { function is(value) { const candidate = value; return candidate && Is.func(candidate.handleMessage); } MessageStrategy.is = is; })(MessageStrategy || (exports.MessageStrategy = MessageStrategy = {})); var ConnectionOptions; (function (ConnectionOptions) { function is(value) { const candidate = value; return candidate && (CancellationStrategy.is(candidate.cancellationStrategy) || ConnectionStrategy.is(candidate.connectionStrategy) || MessageStrategy.is(candidate.messageStrategy)); } ConnectionOptions.is = is; })(ConnectionOptions || (exports.ConnectionOptions = ConnectionOptions = {})); 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, options) { const logger = _logger !== undefined ? _logger : exports.NullLogger; let sequenceNumber = 0; let notificationSequenceNumber = 0; let unknownResponseSequenceNumber = 0; const version = '2.0'; let starRequestHandler = undefined; const requestHandlers = new Map(); let starNotificationHandler = undefined; const notificationHandlers = new Map(); const progressHandlers = new Map(); let timer; let messageQueue = new linkedMap_1.LinkedMap(); let responsePromises = new Map(); let knownCanceledRequests = new Set(); let requestTokens = new Map(); let trace = Trace.Off; let traceFormat = TraceFormat.Text; let tracer; let state = ConnectionState.New; const errorEmitter = new events_1.Emitter(); const closeEmitter = new events_1.Emitter(); const unhandledNotificationEmitter = new events_1.Emitter(); const unhandledProgressEmitter = new events_1.Emitter(); const disposeEmitter = new events_1.Emitter(); const cancellationStrategy = (options && options.cancellationStrategy) ? options.cancellationStrategy : CancellationStrategy.Message; function createRequestQueueKey(id) { if (id === null) { throw new Error(`Can't send requests with id null since the response can't be correlated.`); } return 'req-' + id.toString(); } function createResponseQueueKey(id) { if (id === null) { return 'res-unknown-' + (++unknownResponseSequenceNumber).toString(); } else { return 'res-' + id.toString(); } } function createNotificationQueueKey() { return 'not-' + (++notificationSequenceNumber).toString(); } function addMessageToQueue(queue, message) { if (messages_1.Message.isRequest(message)) { queue.set(createRequestQueueKey(message.id), message); } else if (messages_1.Message.isResponse(message)) { queue.set(createResponseQueueKey(message.id), message); } else { queue.set(createNotificationQueueKey(), message); } } function cancelUndispatched(_message) { return undefined; } function isListening() { return state === ConnectionState.Listening; } function isClosed() { return state === ConnectionState.Closed; } 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 triggerMessageQueue() { if (timer || messageQueue.size === 0) { return; } timer = (0, ral_1.default)().timer.setImmediate(() => { timer = undefined; processMessageQueue(); }); } function handleMessage(message) { if (messages_1.Message.isRequest(message)) { handleRequest(message); } else if (messages_1.Message.isNotification(message)) { handleNotification(message); } else if (messages_1.Message.isResponse(message)) { handleResponse(message); } else { handleInvalidMessage(message); } } function processMessageQueue() { if (messageQueue.size === 0) { return; } const message = messageQueue.shift(); try { const messageStrategy = options?.messageStrategy; if (MessageStrategy.is(messageStrategy)) { messageStrategy.handleMessage(message, handleMessage); } else { handleMessage(message); } } finally { triggerMessageQueue(); } } const callback = (message) => { try { // We have received a cancellation message. Check if the message is still in the queue // and cancel it if allowed to do so. if (messages_1.Message.isNotification(message) && message.method === CancelNotification.type.method) { const cancelId = message.params.id; const key = createRequestQueueKey(cancelId); const toCancel = messageQueue.get(key); if (messages_1.Message.isRequest(toCancel)) { const strategy = options?.connectionStrategy; const response = (strategy && strategy.cancelUndispatched) ? strategy.cancelUndispatched(toCancel, cancelUndispatched) : cancelUndispatched(toCancel); if (response && (response.error !== undefined || response.result !== undefined)) { messageQueue.delete(key); requestTokens.delete(cancelId); response.id = toCancel.id; traceSendingResponse(response, message.method, Date.now()); messageWriter.write(response).catch(() => logger.error(`Sending response for canceled message failed.`)); return; } } const cancellationToken = requestTokens.get(cancelId); // The request is already running. Cancel the token if (cancellationToken !== undefined) { cancellationToken.cancel(); traceReceivedNotification(message); return; } else { // Remember the cancel but still queue the message to // clean up state in process message. knownCanceledRequests.add(cancelId); } } addMessageToQueue(messageQueue, message); } finally { triggerMessageQueue(); } }; function handleRequest(requestMessage) { if (isDisposed()) { // we return here silently since we fired an event when the // connection got disposed. return; } function reply(resultOrError, method, startTime) { const message = { jsonrpc: version, id: requestMessage.id }; if (resultOrError instanceof messages_1.ResponseError) { message.error = resultOrError.toJson(); } else { message.result = resultOrError === undefined ? null : resultOrError; } traceSendingResponse(message, method, startTime); messageWriter.write(message).catch(() => logger.error(`Sending response failed.`)); } function replyError(error, method, startTime) { const message = { jsonrpc: version, id: requestMessage.id, error: error.toJson() }; traceSendingResponse(message, method, startTime); messageWriter.write(message).catch(() => logger.error(`Sending response failed.`)); } function replySuccess(result, method, startTime) { // 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 (result === undefined) { result = null; } const message = { jsonrpc: version, id: requestMessage.id, result: result }; traceSendingResponse(message, method, startTime); messageWriter.write(message).catch(() => logger.error(`Sending response failed.`)); } traceReceivedRequest(requestMessage); const element = requestHandlers.get(requestMessage.method); let type; let requestHandler; if (element) { type = element.type; requestHandler = element.handler; } const startTime = Date.now(); if (requestHandler || starRequestHandler) { const tokenKey = requestMessage.id ?? String(Date.now()); // const cancellationSource = IdCancellationReceiverStrategy.is(cancellationStrategy.receiver) ? cancellationStrategy.receiver.createCancellationTokenSource(tokenKey) : cancellationStrategy.receiver.createCancellationTokenSource(requestMessage); if (requestMessage.id !== null && knownCanceledRequests.has(requestMessage.id)) { cancellationSource.cancel(); } if (requestMessage.id !== null) { requestTokens.set(tokenKey, cancellationSource); } try { let handlerResult; if (requestHandler) { if (requestMessage.params === undefined) { if (type !== undefined && type.numberOfParams !== 0) { replyError(new messages_1.ResponseError(messages_1.ErrorCodes.InvalidParams, `Request ${requestMessage.method} defines ${type.numberOfParams} params but received none.`), requestMessage.method, startTime); return; } handlerResult = requestHandler(cancellationSource.token); } else if (Array.isArray(requestMessage.params)) { if (type !== undefined && type.parameterStructures === messages_1.ParameterStructures.byName) { replyError(new messages_1.ResponseError(messages_1.ErrorCodes.InvalidParams, `Request ${requestMessage.method} defines parameters by name but received parameters by position`), requestMessage.method, startTime); return; } handlerResult = requestHandler(...requestMessage.params, cancellationSource.token); } else { if (type !== undefined && type.parameterStructures === messages_1.ParameterStructures.byPosition) { replyError(new messages_1.ResponseError(messages_1.ErrorCodes.InvalidParams, `Request ${requestMessage.method} defines parameters by position but received parameters by name`), requestMessage.method, startTime); return; } handlerResult = requestHandler(requestMessage.params, cancellationSource.token); } } else if (starRequestHandler) { handlerResult = starRequestHandler(requestMessage.method, requestMessage.params, cancellationSource.token); } const promise = handlerResult; if (!handlerResult) { requestTokens.delete(tokenKey); replySuccess(handlerResult, requestMessage.method, startTime); } else if (promise.then) { promise.then((resultOrError) => { requestTokens.delete(tokenKey); reply(resultOrError, requestMessage.method, startTime); }, error => { requestTokens.delete(tokenKey); if (error instanceof messages_1.ResponseError) { replyError(error, requestMessage.method, startTime); } else if (error && Is.string(error.message)) { replyError(new messages_1.ResponseError(messages_1.ErrorCodes.InternalError, `Request ${requestMessage.method} failed with message: ${error.message}`), requestMessage.method, startTime); } else { replyError(new messages_1.ResponseError(messages_1.ErrorCodes.InternalError, `Request ${requestMessage.method} failed unexpectedly without providing any details.`), requestMessage.method, startTime); } }); } else { requestTokens.delete(tokenKey); reply(handlerResult, requestMessage.method, startTime); } } catch (error) { requestTokens.delete(tokenKey); if (error instanceof messages_1.ResponseError) { reply(error, requestMessage.method, startTime); } else if (error && Is.string(error.message)) { replyError(new messages_1.ResponseError(messages_1.ErrorCodes.InternalError, `Request ${requestMessage.method} failed with message: ${error.message}`), requestMessage.method, startTime); } else { replyError(new messages_1.ResponseError(messages_1.ErrorCodes.InternalError, `Request ${requestMessage.method} failed unexpectedly without providing any details.`), requestMessage.method, startTime); } } } else { replyError(new messages_1.ResponseError(messages_1.ErrorCodes.MethodNotFound, `Unhandled method ${requestMessage.method}`), requestMessage.method, startTime); } } function handleResponse(responseMessage) { if (isDisposed()) { // See handle request. return; } if (responseMessage.id === null) { if (responseMessage.error) { logger.error(`Received response message without id: Error is: \n${JSON.stringify(responseMessage.error, undefined, 4)}`); } else { logger.error(`Received response message without id. No further error information provided.`); } } else { const key = responseMessage.id; const responsePromise = responsePromises.get(key); traceReceivedResponse(responseMessage, responsePromise); if (responsePromise !== undefined) { responsePromises.delete(key); try { if (responseMessage.error) { const error = responseMessage.error; responsePromise.reject(new messages_1.ResponseError(error.code, error.message, error.data)); } else if (responseMessage.result !== undefined) { 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; } let type = undefined; let notificationHandler; if (message.method === CancelNotification.type.method) { const cancelId = message.params.id; knownCanceledRequests.delete(cancelId); traceReceivedNotification(message); return; } else { const element = notificationHandlers.get(message.method); if (element) { notificationHandler = element.handler; type = element.type; } } if (notificationHandler || starNotificationHandler) { try { traceReceivedNotification(message); if (notificationHandler) { if (message.params === undefined) { if (type !== undefined) { if (type.numberOfParams !== 0 && type.parameterStructures !== messages_1.ParameterStructures.byName) { logger.error(`Notification ${message.method} defines ${type.numberOfParams} params but received none.`); } } notificationHandler(); } else if (Array.isArray(message.params)) { // There are JSON-RPC libraries that send progress message as positional params although // specified as named. So convert them if this is the case. const params = message.params; if (message.method === ProgressNotification.type.method && params.length === 2 && ProgressToken.is(params[0])) { notificationHandler({ token: params[0], value: params[1] }); } else { if (type !== undefined) { if (type.parameterStructures === messages_1.ParameterStructures.byName) { logger.error(`Notification ${message.method} defines parameters by name but received parameters by position`); } if (type.numberOfParams !== message.params.length) { logger.error(`Notification ${message.method} defines ${type.numberOfParams} params but received ${params.length} arguments`); } } notificationHandler(...params); } } else { if (type !== undefined && type.parameterStructures === messages_1.ParameterStructures.byPosition) { logger.error(`Notification ${message.method} defines parameters by position but received parameters by name`); } notificationHandler(message.params); } } else if (starNotificationHandler) { starNotificationHandler(message.method, 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(`Received 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 const responseMessage = message; if (Is.string(responseMessage.id) || Is.number(responseMessage.id)) { const key = responseMessage.id; const responseHandler = responsePromises.get(key); if (responseHandler) { responseHandler.reject(new Error('The received response has neither a result nor an error property.')); } } } function stringifyTrace(params) { if (params === undefined || params === null) { return undefined; } switch (trace) { case Trace.Verbose: return JSON.stringify(params, null, 4); case Trace.Compact: return JSON.stringify(params); default: return undefined; } } function traceSendingRequest(message) { if (trace === Trace.Off || !tracer) { return; } if (traceFormat === TraceFormat.Text) { let data = undefined; if ((trace === Trace.Verbose || trace === Trace.Compact) && message.params) { data = `Params: ${stringifyTrace(message.params)}\n\n`; } tracer.log(`Sending request '${message.method} - (${message.id})'.`, data); } else { logLSPMessage('send-request', message); } } function traceSendingNotification(message) { if (trace === Trace.Off || !tracer) { return; } if (traceFormat === TraceFormat.Text) { let data = undefined; if (trace === Trace.Verbose || trace === Trace.Compact) { if (message.params) { data = `Params: ${stringifyTrace(message.params)}\n\n`; } else { data = 'No parameters provided.\n\n'; } } tracer.log(`Sending notification '${message.method}'.`, data); } else { logLSPMessage('send-notification', message); } } function traceSendingResponse(message, method, startTime) { if (trace === Trace.Off || !tracer) { return; } if (traceFormat === TraceFormat.Text) { let data = undefined; if (trace === Trace.Verbose || trace === Trace.Compact) { if (message.error && message.error.data) { data = `Error data: ${stringifyTrace(message.error.data)}\n\n`; } else { if (message.result) { data = `Result: ${stringifyTrace(message.result)}\n\n`; } else if (message.error === undefined) { data = 'No result returned.\n\n'; } } } tracer.log(`Sending response '${method} - (${message.id})'. Processing request took ${Date.now() - startTime}ms`, data); } else { logLSPMessage('send-response', message); } } function traceReceivedRequest(message) { if (trace === Trace.Off || !tracer) { return; } if (traceFormat === TraceFormat.Text) { let data = undefined; if ((trace === Trace.Verbose || trace === Trace.Compact) && message.params) { data = `Params: ${stringifyTrace(message.params)}\n\n`; } tracer.log(`Received request '${message.method} - (${message.id})'.`, data); } else { logLSPMessage('receive-request', message); } } function traceReceivedNotification(message) { if (trace === Trace.Off || !tracer || message.method === LogTraceNotification.type.method) { return; } if (traceFormat === TraceFormat.Text) { let data = undefined; if (trace === Trace.Verbose || trace === Trace.Compact) { if (message.params) { data = `Params: ${stringifyTrace(message.params)}\n\n`; } else { data = 'No parameters provided.\n\n'; } } tracer.log(`Received notification '${message.method}'.`, data); } else { logLSPMessage('receive-notification', message); } } function traceReceivedResponse(message, responsePromise) { if (trace === Trace.Off || !tracer) { return; } if (traceFormat === TraceFormat.Text) { let data = undefined; if (trace === Trace.Verbose || trace === Trace.Compact) { if (message.error && message.error.data) { data = `Error data: ${stringifyTrace(message.error.data)}\n\n`; } else { if (message.result) { data = `Result: ${stringifyTrace(message.result)}\n\n`; } else if (message.error === undefined) { data = 'No result returned.\n\n'; } } } if (responsePromise) { const error = message.error ? ` Request failed: ${message.error.message} (${message.error.code}).` : ''; tracer.log(`Received response '${responsePromise.method} - (${message.id})' in ${Date.now() - responsePromise.timerStart}ms.${error}`, data); } else { tracer.log(`Received response ${message.id} without active response promise.`, data); } } else { logLSPMessage('receive-response', message); } } function logLSPMessage(type, message) { if (!tracer || trace === Trace.Off) { return; } const lspMessage = { isLSPMessage: true, type, message, timestamp: Date.now() }; tracer.log(lspMessage); } function throwIfClosedOrDisposed() { if (isClosed()) { throw new ConnectionError(ConnectionErrors.Closed, 'Connection is closed.'); } if (isDisposed()) { throw new ConnectionError(ConnectionErrors.Disposed, 'Connection is disposed.'); } } function throwIfListening() { if (isListening()) { throw new ConnectionError(ConnectionErrors.AlreadyListening, 'Connection is already listening'); } } function throwIfNotListening() { if (!isListening()) { throw new Error('Call listen() first.'); } } function undefinedToNull(param) { if (param === undefined) { return null; } else { return param; } } function nullToUndefined(param) { if (param === null) { return undefined; } else { return param; } } function isNamedParam(param) { return param !== undefined && param !== null && !Array.isArray(param) && typeof param === 'object'; } function computeSingleParam(parameterStructures, param) { switch (parameterStructures) { case messages_1.ParameterStructures.auto: if (isNamedParam(param)) { return nullToUndefined(param); } else { return [undefinedToNull(param)]; } case messages_1.ParameterStructures.byName: if (!isNamedParam(param)) { throw new Error(`Received parameters by name but param is not an object literal.`); } return nullToUndefined(param); case messages_1.ParameterStructures.byPosition: return [undefinedToNull(param)]; default: throw new Error(`Unknown parameter structure ${parameterStructures.toString()}`); } } function computeMessageParams(type, params) { let result; const numberOfParams = type.numberOfParams; switch (numberOfParams) { case 0: result = undefined; break; case 1: result = computeSingleParam(type.parameterStructures, params[0]); break; default: result = []; for (let i = 0; i < params.length && i < numberOfParams; i++) { result.push(undefinedToNull(params[i])); } if (params.length < numberOfParams) { for (let i = params.length; i < numberOfParams; i++) { result.push(null); } } break; } return result; } const connection = { sendNotification: (type, ...args) => { throwIfClosedOrDisposed(); let method; let messageParams; if (Is.string(type)) { method = type; const first = args[0]; let paramStart = 0; let parameterStructures = messages_1.ParameterStructures.auto; if (messages_1.ParameterStructures.is(first)) { paramStart = 1; parameterStructures = first; } let paramEnd = args.length; const numberOfParams = paramEnd - paramStart; switch (numberOfParams) { case 0: messageParams = undefined; break; case 1: messageParams = computeSingleParam(parameterStructures, args[paramStart]); break; default: if (parameterStructures === messages_1.ParameterStructures.byName) { throw new Error(`Received ${numberOfParams} parameters for 'by Name' notification parameter structure.`); } messageParams = args.slice(paramStart, paramEnd).map(value => undefinedToNull(value)); break; } } else { const params = args; method = type.method; messageParams = computeMessageParams(type, params); } const notificationMessage = { jsonrpc: version, method: method, params: messageParams }; traceSendingNotification(notificationMessage); return messageWriter.write(notificationMessage).catch((error) => { logger.error(`Sending notification failed.`); throw error; }); }, onNotification: (type, handler) => { throwIfClosedOrDisposed(); let method; if (Is.func(type)) { starNotificationHandler = type; } else if (handler) { if (Is.string(type)) { method = type; notificationHandlers.set(type, { type: undefined, handler }); } else { method = type.method; notificationHandlers.set(type.method, { type, handler }); } } return { dispose: () => { if (method !== undefined) { notificationHandlers.delete(method); } else { starNotificationHandler = undefined; } } }; }, onProgress: (_type, token, handler) => { if (progressHandlers.has(token)) { throw new Error(`Progress handler for token ${token} already registered`); } progressHandlers.set(token, handler); return { dispose: () => { progressHandlers.delete(token); } }; }, sendProgress: (_type, token, value) => { // This should not await but simple return to ensure that we don't have another // async scheduling. Otherwise one send could overtake another send. return connection.sendNotification(ProgressNotification.type, { token, value }); }, onUnhandledProgress: unhandledProgressEmitter.event, sendRequest: (type, ...args) => { throwIfClosedOrDisposed(); throwIfNotListening(); let method; let messageParams; let token = undefined; if (Is.string(type)) { method = type; const first = args[0]; const last = args[args.length - 1]; let paramStart = 0; let parameterStructures = messages_1.ParameterStructures.auto; if (messages_1.ParameterStructures.is(first)) { paramStart = 1; parameterStructures = first; } let paramEnd = args.length; if (cancellation_1.CancellationToken.is(last)) { paramEnd = paramEnd - 1; token = last; } const numberOfParams = paramEnd - paramStart; switch (numberOfParams) { case 0: messageParams = undefined; break; case 1: messageParams = computeSingleParam(parameterStructures, args[paramStart]); break; default: if (parameterStructures === messages_1.ParameterStructures.byName) { throw new Error(`Received ${numberOfParams} parameters for 'by Name' request parameter structure.`); } messageParams = args.slice(paramStart, paramEnd).map(value => undefinedToNull(value)); break; } } else { const params = args; method = type.method; messageParams = computeMessageParams(type, params); const numberOfParams = type.numberOfParams; token = cancellation_1.CancellationToken.is(params[numberOfParams]) ? params[numberOfParams] : undefined; } const id = sequenceNumber++; let disposable; if (token) { disposable = token.onCancellationRequested(() => { const p = cancellationStrategy.sender.sendCancellation(connection, id); if (p === undefined) { logger.log(`Received no promise from cancellation strategy when cancelling id ${id}`); return Promise.resolve(); } else { return p.catch(() => { logger.log(`Sending cancellation messages for id ${id} failed`); }); } }); } const requestMessage = { jsonrpc: version, id: id, method: method, params: messageParams }; traceSendingRequest(requestMessage); if (typeof cancellationStrategy.sender.enableCancellation === 'function') { cancellationStrategy.sender.enableCancellation(requestMessage); } return new Promise(async (resolve, reject) => { const resolveWithCleanup = (r) => { resolve(r); cancellationStrategy.sender.cleanup(id); disposable?.dispose(); }; const rejectWithCleanup = (r) => { reject(r); cancellationStrategy.sender.cleanup(id); disposable?.dispose(); }; const responsePromise = { method: method, timerStart: Date.now(), resolve: resolveWithCleanup, reject: rejectWithCleanup }; try { await messageWriter.write(requestMessage); responsePromises.set(id, responsePromise); } catch (error) { logger.error(`Sending request failed.`); // Writing the message failed. So we need to reject the promise. responsePromise.reject(new messages_1.ResponseError(messages_1.ErrorCodes.MessageWriteError, error.message ? error.message : 'Unknown reason')); throw error; } }); }, onRequest: (type, handler) => { throwIfClosedOrDisposed(); let method = null; if (StarRequestHandler.is(type)) { method = undefined; starRequestHandler = type; } else if (Is.string(type)) { method = null; if (handler !== undefined) { method = type; requestHandlers.set(type, { handler: handler, type: undefined }); } } else { if (handler !== undefined) { method = type.method; requestHandlers.set(type.method, { type, handler }); } } return { dispose: () => { if (method === null) { return; } if (method !== undefined) { requestHandlers.delete(method); } else { starRequestHandler = undefined; } } }; }, hasPendingResponse: () => { return responsePromises.size > 0; }, trace: async (_value, _tracer, sendNotificationOrTraceOptions) => { let _sendNotification = false; let _traceFormat = TraceFormat.Text; if (sendNotificationOrTraceOptions !== undefined) { if (Is.boolean(sendNotificationOrTraceOptions)) { _sendNotification = sendNotificationOrTraceOptions; } else { _sendNotification = sendNotificationOrTraceOptions.sendNotification || false; _traceFormat = sendNotificationOrTraceOptions.traceFormat || TraceFormat.Text; } } trace = _value; traceFormat = _traceFormat; if (trace === Trace.Off) { tracer = undefined; } else { tracer = _tracer; } if (_sendNotification && !isClosed() && !isDisposed()) { await connection.sendNotification(SetTraceNotification.type, { value: Trace.toString(_value) }); } }, onError: errorEmitter.event, onClose: closeEmitter.event, onUnhandledNotification: unhandledNotificationEmitter.event, onDispose: disposeEmitter.event, end: () => { messageWriter.end(); }, dispose: () => { if (isDisposed()) { return; } state = ConnectionState.Disposed; disposeEmitter.fire(undefined); const error = new messages_1.ResponseError(messages_1.ErrorCodes.PendingResponseRejected, 'Pending response rejected since connection got disposed'); for (const promise of responsePromises.values()) { promise.reject(error); } responsePromises = new Map(); requestTokens = new Map(); knownCanceledRequests = new Set(); messageQueue = new linkedMap_1.LinkedMap(); // Test for backwards compatibility if (Is.func(messageWriter.dispose)) { messageWriter.dispose(); } if (Is.func(messageReader.dispose)) { messageReader.dispose(); } }, listen: () => { throwIfClosedOrDisposed(); throwIfListening(); state = ConnectionState.Listening; messageReader.listen(callback); }, inspect: () => { // eslint-disable-next-line no-console (0, ral_1.default)().console.log('inspect'); } }; connection.onNotification(LogTraceNotification.type, (params) => { if (trace === Trace.Off || !tracer) { return; } const verbose = trace === Trace.Verbose || trace === Trace.Compact; tracer.log(params.message, verbose ? params.verbose : undefined); }); connection.onNotification(ProgressNotification.type, (params) => { const handler = progressHandlers.get(params.token); if (handler) { handler(params.value); } else { unhandledProgressEmitter.fire(params); } }); return connection; } exports.createMessageConnection = createMessageConnection;