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.

1 > /*--------------------------------------------------------------------------------------------- jsonRpcProtocol.ts ×17
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { DeferredPromise } from './async.js';
7 > import { CancellationToken, CancellationTokenSource } from './cancellation.js';
8 > import { CancellationError } from './errors.js';
9 > import { Disposable, toDisposable } from './lifecycle.js';
10 > import { hasKey } from './types.js';
11 >
12 > export type JsonRpcId = string | number;
13 >
14 > export interface IJsonRpcError {
15 > code: number;
16 > message: string;
17 > data?: unknown;
18 > }
19 >
20 > export interface IJsonRpcRequest {
21 > jsonrpc: '2.0';
22 > id: JsonRpcId;
23 > method: string;
24 > params?: unknown;
25 > }
26 >
27 > export interface IJsonRpcNotification {
28 > jsonrpc: '2.0';
29 > method: string;
30 > params?: unknown;
31 > }
32 >
33 > export interface IJsonRpcSuccessResponse {
34 > jsonrpc: '2.0';
35 > id: JsonRpcId;
36 > result: unknown;
37 > }
38 >
39 > export interface IJsonRpcErrorResponse {
40 > jsonrpc: '2.0';
41 > id?: JsonRpcId;
42 > error: IJsonRpcError;
43 > }
44 >
45 > export type JsonRpcMessage = IJsonRpcRequest | IJsonRpcNotification | IJsonRpcSuccessResponse | IJsonRpcErrorResponse;
46 > export type JsonRpcResponse = IJsonRpcSuccessResponse | IJsonRpcErrorResponse;
47 >
48 > interface IPendingRequest {
49 > promise: DeferredPromise<unknown>;
50 > cts: CancellationTokenSource;
51 > }
52 >
53 > export interface IJsonRpcProtocolHandlers {
54 > handleRequest?(request: IJsonRpcRequest, token: CancellationToken): Promise<unknown> | unknown;
55 > handleNotification?(notification: IJsonRpcNotification): void;
56 > }
57 >
58 > export class JsonRpcError extends Error {
59 > constructor(
60 > public readonly code: number, jsonRpcProtocol.ts ×1
61 > message: string,
62 > public readonly data?: unknown,
63 > ) {
64 > super(message);
65 > }
67 >
68 > /**
69 > * Generic JSON-RPC 2.0 protocol helper.
70 > */
71 > export class JsonRpcProtocol extends Disposable {
72 > private static readonly ParseError = -32700;
73 > private static readonly MethodNotFound = -32601;
74 > private static readonly InternalError = -32603;
75 >
76 > private _nextRequestId = 1;
77 > private readonly _pendingRequests = new Map<JsonRpcId, IPendingRequest>();
78 >
79 > constructor(
80 > private readonly _send: (message: JsonRpcMessage) => void, jsonRpcProtocol.ts ×4
81 > private readonly _handlers: IJsonRpcProtocolHandlers,
82 > ) {
83 > super();
84 > }
86 > public sendNotification(notification: Omit<IJsonRpcNotification, 'jsonrpc'>): void {
87 > this._send({ jsonRpcProtocol.ts ×1
88 > jsonrpc: '2.0',
89 > ...notification,
90 > });
91 > }
93 > public sendRequest<T = unknown>(request: Omit<IJsonRpcRequest, 'jsonrpc' | 'id'>, token: CancellationToken = CancellationToken.None, onCancel?: (id: JsonRpcId) => void): Promise<T> {
94 > if (this._store.isDisposed) { jsonRpcProtocol.ts ×4
95 return Promise.reject(new CancellationError());
96 }
98 > const id = this._nextRequestId++;
99 > const promise = new DeferredPromise<unknown>();
100 > const cts = new CancellationTokenSource();
101 > this._pendingRequests.set(id, { promise, cts });
102 >
103 > const cancelListener = token.onCancellationRequested(() => {
104 > if (!promise.isSettled) { jsonRpcProtocol.ts ×2
105 > this._pendingRequests.delete(id); jsonRpcProtocol.ts ×1
106 > cts.cancel();
107 > onCancel?.(id);
108 > promise.cancel();
109 > }
110 > cancelListener.dispose(); jsonRpcProtocol.ts ×2
112 >
113 > this._send({
114 > jsonrpc: '2.0',
115 > id,
116 > ...request,
117 > });
118 >
119 > return promise.p.finally(() => {
120 > cancelListener.dispose(); jsonRpcProtocol.ts ×1
121 > this._pendingRequests.delete(id);
122 > cts.dispose(true);
123 > }) as Promise<T>; jsonRpcProtocol.ts ×4
124 > }
126 > /**
127 > * Handles one or more incoming JSON-RPC messages.
128 > *
129 > * Returns an array of JSON-RPC response objects generated for any incoming
130 > * requests in the message(s). Notifications and responses to our own
131 > * outgoing requests do not produce return values. For batch inputs, the
132 > * returned responses are in the same order as the corresponding requests.
133 > *
134 > * Note: responses are also emitted via the `_send` callback, so callers
135 > * that rely on the return value should not re-send them.
136 > */
137 > public async handleMessage(message: JsonRpcMessage | JsonRpcMessage[]): Promise<JsonRpcResponse[]> {
138 > if (Array.isArray(message)) { jsonRpcProtocol.ts ×4
139 > const replies: JsonRpcResponse[] = []; jsonRpcProtocol.ts ×1
140 > for (const single of message) {
141 > const reply = await this._handleMessage(single);
142 > if (reply) {
143 > replies.push(reply);
144 > }
145 > }
146 > return replies;
147 > }
149 > const reply = await this._handleMessage(message);
150 > return reply ? [reply] : []; jsonRpcProtocol.ts ×4
151 > }
153 > public cancelPendingRequest(id: JsonRpcId): void {
154 > const request = this._pendingRequests.get(id); jsonRpcProtocol.ts ×1
155 > if (request) {
156 > this._pendingRequests.delete(id);
157 > request.cts.cancel();
158 > request.promise.cancel();
159 > request.cts.dispose(true);
160 > }
161 > }
163 > public cancelAllRequests(): void {
164 > for (const [id, pending] of this._pendingRequests) { jsonRpcProtocol.ts ×4
165 > this._pendingRequests.delete(id); jsonRpcProtocol.ts ×1
166 > pending.cts.cancel();
167 > pending.promise.cancel();
168 > pending.cts.dispose(true);
169 > }
172 > private async _handleMessage(message: JsonRpcMessage): Promise<JsonRpcResponse | undefined> {
173 > if (isJsonRpcResponse(message)) { jsonRpcProtocol.ts ×4
174 > if (hasKey(message, { result: true })) { jsonRpcProtocol.ts ×3
175 > this._handleResult(message); jsonRpcProtocol.ts ×2
176 > } else { jsonRpcProtocol.ts ×3
177 > this._handleError(message); jsonRpcProtocol.ts ×3
178 > }
179 > return undefined; jsonRpcProtocol.ts ×3
180 > }
182 > if (isJsonRpcRequest(message)) {
183 > return this._handleRequest(message); jsonRpcProtocol.ts ×4
184 > }
186 > if (isJsonRpcNotification(message)) {
187 > this._handlers.handleNotification?.(message);
188 > }
189 >
190 > return undefined;
193 > private _handleResult(response: IJsonRpcSuccessResponse): void {
194 > const request = this._pendingRequests.get(response.id); jsonRpcProtocol.ts ×2
195 > if (request) {
196 > this._pendingRequests.delete(response.id);
197 > request.promise.complete(response.result);
198 > request.cts.dispose(true);
199 > }
200 > }
202 > private _handleError(response: IJsonRpcErrorResponse): void {
203 > if (response.id === undefined) { jsonRpcProtocol.ts ×3
204 return;
205 }
207 > const request = this._pendingRequests.get(response.id);
208 > if (request) {
209 > this._pendingRequests.delete(response.id);
210 > request.promise.error(new JsonRpcError(response.error.code, response.error.message, response.error.data));
211 > request.cts.dispose(true);
212 > }
213 > }
215 > private async _handleRequest(request: IJsonRpcRequest): Promise<JsonRpcResponse> {
216 > if (!this._handlers.handleRequest) { jsonRpcProtocol.ts ×4
217 > const response: IJsonRpcErrorResponse = { jsonRpcProtocol.ts ×1
218 > jsonrpc: '2.0',
219 > id: request.id,
220 > error: {
221 > code: JsonRpcProtocol.MethodNotFound,
222 > message: `Method not found: ${request.method}`,
223 > }
224 > };
225 > this._send(response);
226 > return response;
227 > }
229 > const cts = new CancellationTokenSource();
230 > this._register(toDisposable(() => cts.dispose(true)));
231 >
232 > try {
233 > const resultOrThenable = this._handlers.handleRequest(request, cts.token);
234 > const result = isThenable(resultOrThenable) ? await resultOrThenable : resultOrThenable;
235 > const response: IJsonRpcSuccessResponse = { jsonRpcProtocol.ts ×4
236 > jsonrpc: '2.0',
237 > id: request.id,
238 > result,
239 > };
240 > this._send(response);
241 > return response;
242 > } catch (error) {
243 > let response: IJsonRpcErrorResponse; jsonRpcProtocol.ts ×3
244 > if (error instanceof JsonRpcError) {
245 > response = { jsonRpcProtocol.ts ×1
246 > jsonrpc: '2.0',
247 > id: request.id,
248 > error: {
249 > code: error.code,
250 > message: error.message,
251 > data: error.data,
252 > }
253 > };
254 > } else { jsonRpcProtocol.ts ×3
255 > response = { jsonRpcProtocol.ts ×1
256 > jsonrpc: '2.0',
257 > id: request.id,
258 > error: {
259 > code: JsonRpcProtocol.InternalError,
260 > message: error instanceof Error ? error.message : 'Internal error',
261 > }
262 > };
263 > }
264 > this._send(response); jsonRpcProtocol.ts ×3
265 > return response;
266 > } finally { jsonRpcProtocol.ts ×2
267 > cts.dispose(true);
268 > }
271 > public override dispose(): void {
272 > this.cancelAllRequests(); jsonRpcProtocol.ts ×4
273 > super.dispose();
274 > }
276 > public static createParseError(message: string, data?: unknown): IJsonRpcErrorResponse {
277 return {
278 jsonrpc: '2.0',
279 error: {
280 code: JsonRpcProtocol.ParseError,
281 message,
282 data,
283 }
284 };
285 }
287 >
288 > export function isJsonRpcRequest(message: JsonRpcMessage): message is IJsonRpcRequest {
289 > return 'method' in message && 'id' in message && (typeof message.id === 'string' || typeof message.id === 'number'); jsonRpcProtocol.ts ×2
290 > }
292 > export function isJsonRpcResponse(message: JsonRpcMessage): message is IJsonRpcSuccessResponse | IJsonRpcErrorResponse {
293 > return hasKey(message, { id: true, result: true }) || hasKey(message, { id: true, error: true }); jsonRpcProtocol.ts ×1
294 > }
296 > export function isJsonRpcNotification(message: JsonRpcMessage): message is IJsonRpcNotification {
297 > return hasKey(message, { method: true }) && !hasKey(message, { id: true }); jsonRpcProtocol.ts ×2
298 > }
300 >
301 > function isThenable<T>(value: T | Promise<T>): value is Promise<T> { jsonRpcProtocol.ts ×1
302 > return typeof value === 'object' && value !== null && 'then' in value && typeof value.then === 'function';
303 > }