Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/onboardDebug/node/copilotDebugWorker/rpc.ts
13405 views
1
/*---------------------------------------------------------------------------------------------
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 { Duplex } from 'stream';
7
import type { IDisposable } from '../../../../util/vs/base/common/lifecycle';
8
import { StreamSplitter } from './streamSplitter';
9
10
// JSON-RPC request object
11
interface Request {
12
id: number;
13
method: string;
14
params?: any;
15
}
16
17
// JSON-RPC response object
18
interface Response {
19
id: number;
20
result?: any;
21
error?: {
22
code: number;
23
message: string;
24
data?: any;
25
};
26
}
27
28
export interface ISimpleRPC extends IDisposable {
29
registerMethod(method: string, handler: (params: any) => Promise<any>): void;
30
callMethod(method: string, params?: any): Promise<any>;
31
}
32
33
const terminator = process.platform === 'win32' ? '\r\n' : '\n';
34
35
export class SimpleRPC implements ISimpleRPC {
36
private idCounter: number;
37
private methods = new Map<string, (...params: any[]) => Promise<any>>();
38
private pendingRequests = new Map<number, { resolve: (result: any) => void; reject: (error: Error) => void }>();
39
private didEnd?: boolean;
40
41
public readonly ended: Promise<void>;
42
43
constructor(private readonly stream: Duplex) {
44
this.idCounter = 0;
45
this.stream.pipe(new StreamSplitter('\n')).on('data', d => this.handleData(d));
46
this.ended = new Promise<void>((resolve) => this.stream.on('end', () => {
47
this.didEnd = true;
48
resolve();
49
}));
50
}
51
52
public registerMethod(method: string, handler: (params: any) => Promise<any> | any) {
53
this.methods.set(method, handler);
54
}
55
56
public async callMethod(method: string, params?: any): Promise<any> {
57
const id = this.idCounter++;
58
const request: Request = { id, method, params, };
59
const promise = new Promise<any>((resolve, reject) => {
60
this.pendingRequests.set(id, { resolve, reject });
61
});
62
this.stream.write(JSON.stringify(request) + terminator);
63
return Promise.race([promise, this.ended]);
64
}
65
66
public dispose() {
67
this.didEnd = true;
68
this.stream.end();
69
for (const { reject } of this.pendingRequests.values()) {
70
reject(new Error('RPC connection closed'));
71
}
72
this.pendingRequests.clear();
73
}
74
75
private async handleData(data: Buffer) {
76
// -1 to remove trailing split match
77
const incoming: Response | Request = JSON.parse(data.toString());
78
79
if (!('method' in incoming)) {
80
const { id, result, error } = incoming;
81
const handler = this.pendingRequests.get(id);
82
this.pendingRequests.delete(id);
83
if (error !== undefined) {
84
handler?.reject(new Error(error.message));
85
} else {
86
handler?.resolve(result);
87
}
88
} else {
89
const { id, method, params } = incoming;
90
const response: Response = { id };
91
92
try {
93
if (this.methods.has(method)) {
94
const result = await this.methods.get(method)!(params);
95
response.result = result;
96
} else {
97
throw new Error(`Method not found: ${method}`);
98
}
99
} catch (error) {
100
response.error = {
101
code: -1,
102
message: String(error.stack || error),
103
};
104
}
105
106
if (!this.didEnd) {
107
this.stream.write(JSON.stringify(response) + terminator);
108
}
109
}
110
}
111
}
112
113