vscode-jsonrpc
Version:
A json rpc implementation over streams
457 lines • 19.2 kB
JavaScript
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
;
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