src/vs/base/common/jsonRpcProtocol.ts
303 LOC · 290 covered · 13 uncovered · 65 ranges · 358 concepts · 26 introducers · 180 tests
File neighbourhood
The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.
Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file
In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.
Graph controls are ready.
Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.
/*---------------------------------------------------------------------------------------------
jsonRpcProtocol.ts ×17
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { DeferredPromise } from './async.js';
import { CancellationToken, CancellationTokenSource } from './cancellation.js';
import { CancellationError } from './errors.js';
import { Disposable, toDisposable } from './lifecycle.js';
import { hasKey } from './types.js';
export type JsonRpcId = string | number;
export interface IJsonRpcError {
code: number;
message: string;
data?: unknown;
}
export interface IJsonRpcRequest {
jsonrpc: '2.0';
id: JsonRpcId;
method: string;
params?: unknown;
}
export interface IJsonRpcNotification {
jsonrpc: '2.0';
method: string;
params?: unknown;
}
export interface IJsonRpcSuccessResponse {
jsonrpc: '2.0';
id: JsonRpcId;
result: unknown;
}
export interface IJsonRpcErrorResponse {
jsonrpc: '2.0';
id?: JsonRpcId;
error: IJsonRpcError;
}
export type JsonRpcMessage = IJsonRpcRequest | IJsonRpcNotification | IJsonRpcSuccessResponse | IJsonRpcErrorResponse;
export type JsonRpcResponse = IJsonRpcSuccessResponse | IJsonRpcErrorResponse;
interface IPendingRequest {
promise: DeferredPromise<unknown>;
cts: CancellationTokenSource;
}
export interface IJsonRpcProtocolHandlers {
handleRequest?(request: IJsonRpcRequest, token: CancellationToken): Promise<unknown> | unknown;
handleNotification?(notification: IJsonRpcNotification): void;
}
export class JsonRpcError extends Error {
constructor(
message: string,
public readonly data?: unknown,
) {
super(message);
}
/**
* Generic JSON-RPC 2.0 protocol helper.
*/
export class JsonRpcProtocol extends Disposable {
private static readonly ParseError = -32700;
private static readonly MethodNotFound = -32601;
private static readonly InternalError = -32603;
private _nextRequestId = 1;
private readonly _pendingRequests = new Map<JsonRpcId, IPendingRequest>();
constructor(
private readonly _handlers: IJsonRpcProtocolHandlers,
) {
super();
}
public sendNotification(notification: Omit<IJsonRpcNotification, 'jsonrpc'>): void {
jsonrpc: '2.0',
...notification,
});
}
public sendRequest<T = unknown>(request: Omit<IJsonRpcRequest, 'jsonrpc' | 'id'>, token: CancellationToken = CancellationToken.None, onCancel?: (id: JsonRpcId) => void): Promise<T> {
return Promise.reject(new CancellationError());
}
const id = this._nextRequestId++;
const promise = new DeferredPromise<unknown>();
const cts = new CancellationTokenSource();
this._pendingRequests.set(id, { promise, cts });
const cancelListener = token.onCancellationRequested(() => {
cts.cancel();
onCancel?.(id);
promise.cancel();
}
this._send({
jsonrpc: '2.0',
id,
...request,
});
return promise.p.finally(() => {
this._pendingRequests.delete(id);
cts.dispose(true);
}
/**
* Handles one or more incoming JSON-RPC messages.
*
* Returns an array of JSON-RPC response objects generated for any incoming
* requests in the message(s). Notifications and responses to our own
* outgoing requests do not produce return values. For batch inputs, the
* returned responses are in the same order as the corresponding requests.
*
* Note: responses are also emitted via the `_send` callback, so callers
* that rely on the return value should not re-send them.
*/
public async handleMessage(message: JsonRpcMessage | JsonRpcMessage[]): Promise<JsonRpcResponse[]> {
for (const single of message) {
const reply = await this._handleMessage(single);
if (reply) {
replies.push(reply);
}
}
return replies;
}
const reply = await this._handleMessage(message);
}
public cancelPendingRequest(id: JsonRpcId): void {
if (request) {
this._pendingRequests.delete(id);
request.cts.cancel();
request.promise.cancel();
request.cts.dispose(true);
}
}
public cancelAllRequests(): void {
pending.cts.cancel();
pending.promise.cancel();
pending.cts.dispose(true);
}
private async _handleMessage(message: JsonRpcMessage): Promise<JsonRpcResponse | undefined> {
}
}
if (isJsonRpcRequest(message)) {
}
if (isJsonRpcNotification(message)) {
this._handlers.handleNotification?.(message);
}
return undefined;
private _handleResult(response: IJsonRpcSuccessResponse): void {
if (request) {
this._pendingRequests.delete(response.id);
request.promise.complete(response.result);
request.cts.dispose(true);
}
}
private _handleError(response: IJsonRpcErrorResponse): void {
return;
}
const request = this._pendingRequests.get(response.id);
if (request) {
this._pendingRequests.delete(response.id);
request.promise.error(new JsonRpcError(response.error.code, response.error.message, response.error.data));
request.cts.dispose(true);
}
}
private async _handleRequest(request: IJsonRpcRequest): Promise<JsonRpcResponse> {
jsonrpc: '2.0',
id: request.id,
error: {
code: JsonRpcProtocol.MethodNotFound,
message: `Method not found: ${request.method}`,
}
};
this._send(response);
return response;
}
const cts = new CancellationTokenSource();
this._register(toDisposable(() => cts.dispose(true)));
try {
const resultOrThenable = this._handlers.handleRequest(request, cts.token);
const result = isThenable(resultOrThenable) ? await resultOrThenable : resultOrThenable;
jsonrpc: '2.0',
id: request.id,
result,
};
this._send(response);
return response;
} catch (error) {
if (error instanceof JsonRpcError) {
jsonrpc: '2.0',
id: request.id,
error: {
code: error.code,
message: error.message,
data: error.data,
}
};
jsonrpc: '2.0',
id: request.id,
error: {
code: JsonRpcProtocol.InternalError,
message: error instanceof Error ? error.message : 'Internal error',
}
};
}
return response;
cts.dispose(true);
}
public override dispose(): void {
super.dispose();
}
public static createParseError(message: string, data?: unknown): IJsonRpcErrorResponse {
return {
jsonrpc: '2.0',
error: {
code: JsonRpcProtocol.ParseError,
message,
data,
}
};
}
export function isJsonRpcRequest(message: JsonRpcMessage): message is IJsonRpcRequest {
return 'method' in message && 'id' in message && (typeof message.id === 'string' || typeof message.id === 'number');
jsonRpcProtocol.ts ×2
}
export function isJsonRpcResponse(message: JsonRpcMessage): message is IJsonRpcSuccessResponse | IJsonRpcErrorResponse {
return hasKey(message, { id: true, result: true }) || hasKey(message, { id: true, error: true });
jsonRpcProtocol.ts ×1
}
export function isJsonRpcNotification(message: JsonRpcMessage): message is IJsonRpcNotification {
return hasKey(message, { method: true }) && !hasKey(message, { id: true });
jsonRpcProtocol.ts ×2
}
return typeof value === 'object' && value !== null && 'then' in value && typeof value.then === 'function';
}