@opensumi/ide-connection 2.21.8-rc-1670567192.0 → 2.21.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -6
- package/src/browser/index.ts +0 -1
- package/src/browser/ws-channel-handler.ts +0 -124
- package/src/common/connect.ts +0 -199
- package/src/common/index.ts +0 -6
- package/src/common/message.ts +0 -93
- package/src/common/proxy.ts +0 -303
- package/src/common/rpcProtocol.ts +0 -377
- package/src/common/utils.ts +0 -44
- package/src/common/ws-channel.ts +0 -144
- package/src/index.ts +0 -1
- package/src/node/common-channel-handler.ts +0 -227
- package/src/node/connect.ts +0 -11
- package/src/node/index.ts +0 -7
- package/src/node/ws.ts +0 -109
package/src/common/proxy.ts
DELETED
|
@@ -1,303 +0,0 @@
|
|
|
1
|
-
import { ApplicationError, uuid } from '@opensumi/ide-core-common';
|
|
2
|
-
import type { MessageConnection } from '@opensumi/vscode-jsonrpc/lib/common/connection';
|
|
3
|
-
|
|
4
|
-
import { MessageType, ResponseStatus, ICapturedMessage, getCapturer } from './utils';
|
|
5
|
-
|
|
6
|
-
export abstract class RPCService<T = any> {
|
|
7
|
-
rpcClient?: T[];
|
|
8
|
-
rpcRegistered?: boolean;
|
|
9
|
-
register?(): () => Promise<T>;
|
|
10
|
-
get client() {
|
|
11
|
-
return this.rpcClient ? this.rpcClient[0] : undefined;
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export const NOTREGISTERMETHOD = '$$NOTREGISTERMETHOD';
|
|
16
|
-
|
|
17
|
-
export class ProxyClient {
|
|
18
|
-
public proxy: any;
|
|
19
|
-
public reservedWords: string[];
|
|
20
|
-
|
|
21
|
-
constructor(proxy: any, reservedWords = ['then']) {
|
|
22
|
-
this.proxy = proxy;
|
|
23
|
-
this.reservedWords = reservedWords;
|
|
24
|
-
}
|
|
25
|
-
public getClient() {
|
|
26
|
-
return new Proxy(
|
|
27
|
-
{},
|
|
28
|
-
{
|
|
29
|
-
get: (target, prop: string | symbol) => {
|
|
30
|
-
if (this.reservedWords.includes(prop as string) || typeof prop === 'symbol') {
|
|
31
|
-
return Promise.resolve();
|
|
32
|
-
} else {
|
|
33
|
-
return this.proxy[prop];
|
|
34
|
-
}
|
|
35
|
-
},
|
|
36
|
-
},
|
|
37
|
-
);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
interface IRPCResult {
|
|
42
|
-
error?: ApplicationError<number, any>;
|
|
43
|
-
data: any;
|
|
44
|
-
}
|
|
45
|
-
export class RPCProxy {
|
|
46
|
-
private connectionPromise: Promise<MessageConnection>;
|
|
47
|
-
private connectionPromiseResolve: (connection: MessageConnection) => void;
|
|
48
|
-
private connection: MessageConnection;
|
|
49
|
-
private proxyService: any = {};
|
|
50
|
-
private logger: any;
|
|
51
|
-
// capture messages for opensumi devtools
|
|
52
|
-
private capture(message: ICapturedMessage): void {
|
|
53
|
-
const capturer = getCapturer();
|
|
54
|
-
if (capturer !== undefined) {
|
|
55
|
-
capturer(message);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
constructor(public target?: RPCService, logger?: any) {
|
|
60
|
-
this.waitForConnection();
|
|
61
|
-
this.logger = logger || console;
|
|
62
|
-
}
|
|
63
|
-
public listenService(service) {
|
|
64
|
-
if (this.connection) {
|
|
65
|
-
const proxyService = this.proxyService;
|
|
66
|
-
this.bindOnRequest(service, (service, prop) => {
|
|
67
|
-
proxyService[prop] = service[prop].bind(service);
|
|
68
|
-
});
|
|
69
|
-
} else {
|
|
70
|
-
const target = this.target || {};
|
|
71
|
-
const methods = this.getServiceMethod(service);
|
|
72
|
-
methods.forEach((method) => {
|
|
73
|
-
target[method] = service[method].bind(service);
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
public listen(connection: MessageConnection) {
|
|
79
|
-
this.connection = connection;
|
|
80
|
-
|
|
81
|
-
if (this.target) {
|
|
82
|
-
this.listenService(this.target);
|
|
83
|
-
}
|
|
84
|
-
this.connectionPromiseResolve(connection);
|
|
85
|
-
connection.listen();
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
public createProxy(): any {
|
|
89
|
-
const proxy = new Proxy(this, this);
|
|
90
|
-
|
|
91
|
-
const proxyClient = new ProxyClient(proxy);
|
|
92
|
-
return proxyClient.getClient();
|
|
93
|
-
}
|
|
94
|
-
public get(target: any, p: PropertyKey) {
|
|
95
|
-
const prop = p.toString();
|
|
96
|
-
|
|
97
|
-
return (...args: any[]) =>
|
|
98
|
-
this.connectionPromise.then((connection) => {
|
|
99
|
-
connection = this.connection || connection;
|
|
100
|
-
return new Promise((resolve, reject) => {
|
|
101
|
-
try {
|
|
102
|
-
let isSingleArray = false;
|
|
103
|
-
if (args.length === 1 && Array.isArray(args[0])) {
|
|
104
|
-
isSingleArray = true;
|
|
105
|
-
}
|
|
106
|
-
// 调用方法为 on 开头时,作为单项通知
|
|
107
|
-
if (prop.startsWith('on')) {
|
|
108
|
-
if (isSingleArray) {
|
|
109
|
-
connection.sendNotification(prop, [...args]);
|
|
110
|
-
this.capture({ type: MessageType.SendNotification, serviceMethod: prop, arguments: args });
|
|
111
|
-
} else {
|
|
112
|
-
connection.sendNotification(prop, ...args);
|
|
113
|
-
this.capture({ type: MessageType.SendNotification, serviceMethod: prop, arguments: args });
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
resolve(null);
|
|
117
|
-
} else {
|
|
118
|
-
let requestResult: Promise<any>;
|
|
119
|
-
// generate a unique requestId to associate request and requestResult
|
|
120
|
-
const requestId = uuid();
|
|
121
|
-
|
|
122
|
-
if (isSingleArray) {
|
|
123
|
-
requestResult = connection.sendRequest(prop, [...args]) as Promise<any>;
|
|
124
|
-
this.capture({ type: MessageType.SendRequest, requestId, serviceMethod: prop, arguments: args });
|
|
125
|
-
} else {
|
|
126
|
-
requestResult = connection.sendRequest(prop, ...args) as Promise<any>;
|
|
127
|
-
this.capture({ type: MessageType.SendRequest, requestId, serviceMethod: prop, arguments: args });
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
requestResult
|
|
131
|
-
.catch((err) => {
|
|
132
|
-
reject(err);
|
|
133
|
-
})
|
|
134
|
-
.then((result: IRPCResult) => {
|
|
135
|
-
if (result.error) {
|
|
136
|
-
const error = new Error(result.data.message);
|
|
137
|
-
if (result.data.stack) {
|
|
138
|
-
error.stack = result.data.stack;
|
|
139
|
-
}
|
|
140
|
-
if (result.error.code && result.error.data) {
|
|
141
|
-
// 经过通信,applicationError 实例的构造类信息丢失了,使用 fromJson 恢复
|
|
142
|
-
const applicationError = ApplicationError.fromJson(result.error.code, result.error.data);
|
|
143
|
-
error.cause = applicationError;
|
|
144
|
-
}
|
|
145
|
-
this.capture({
|
|
146
|
-
type: MessageType.RequestResult,
|
|
147
|
-
status: ResponseStatus.Fail,
|
|
148
|
-
requestId,
|
|
149
|
-
serviceMethod: prop,
|
|
150
|
-
error: result.data,
|
|
151
|
-
});
|
|
152
|
-
reject(error);
|
|
153
|
-
} else {
|
|
154
|
-
this.capture({
|
|
155
|
-
type: MessageType.RequestResult,
|
|
156
|
-
status: ResponseStatus.Success,
|
|
157
|
-
requestId,
|
|
158
|
-
serviceMethod: prop,
|
|
159
|
-
data: result.data,
|
|
160
|
-
});
|
|
161
|
-
resolve(result.data);
|
|
162
|
-
}
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
} catch (e) {}
|
|
166
|
-
});
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
private getServiceMethod(service): string[] {
|
|
170
|
-
let props: any[] = [];
|
|
171
|
-
|
|
172
|
-
if (/^\s*class/.test(service.constructor.toString())) {
|
|
173
|
-
let obj = service;
|
|
174
|
-
do {
|
|
175
|
-
props = props.concat(Object.getOwnPropertyNames(obj));
|
|
176
|
-
} while ((obj = Object.getPrototypeOf(obj)));
|
|
177
|
-
props = props.sort().filter((e, i, arr) => e !== arr[i + 1] && typeof service[e] === 'function');
|
|
178
|
-
} else {
|
|
179
|
-
for (const prop in service) {
|
|
180
|
-
if (service[prop] && typeof service[prop] === 'function') {
|
|
181
|
-
props.push(prop);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
return props;
|
|
187
|
-
}
|
|
188
|
-
private bindOnRequest(service, cb?) {
|
|
189
|
-
if (this.connection) {
|
|
190
|
-
const connection = this.connection;
|
|
191
|
-
|
|
192
|
-
const methods = this.getServiceMethod(service);
|
|
193
|
-
methods.forEach((method) => {
|
|
194
|
-
if (method.startsWith('on')) {
|
|
195
|
-
connection.onNotification(method, (...args) => {
|
|
196
|
-
this.onNotification(method, ...args);
|
|
197
|
-
this.capture({ type: MessageType.OnNotification, serviceMethod: method, arguments: args });
|
|
198
|
-
});
|
|
199
|
-
} else {
|
|
200
|
-
connection.onRequest(method, (...args) => {
|
|
201
|
-
const requestId = uuid();
|
|
202
|
-
const result = this.onRequest(method, ...args);
|
|
203
|
-
this.capture({ type: MessageType.OnRequest, requestId, serviceMethod: method, arguments: args });
|
|
204
|
-
|
|
205
|
-
result
|
|
206
|
-
.then((result) => {
|
|
207
|
-
this.capture({
|
|
208
|
-
type: MessageType.OnRequestResult,
|
|
209
|
-
status: ResponseStatus.Success,
|
|
210
|
-
requestId,
|
|
211
|
-
serviceMethod: method,
|
|
212
|
-
data: result.data,
|
|
213
|
-
});
|
|
214
|
-
})
|
|
215
|
-
.catch((err) => {
|
|
216
|
-
this.capture({
|
|
217
|
-
type: MessageType.OnRequestResult,
|
|
218
|
-
status: ResponseStatus.Fail,
|
|
219
|
-
requestId,
|
|
220
|
-
serviceMethod: method,
|
|
221
|
-
error: err.data,
|
|
222
|
-
});
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
return result;
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
if (cb) {
|
|
230
|
-
cb(service, method);
|
|
231
|
-
}
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
connection.onRequest((method) => {
|
|
235
|
-
if (!this.proxyService[method]) {
|
|
236
|
-
const requestId = uuid();
|
|
237
|
-
this.capture({ type: MessageType.OnRequest, requestId, serviceMethod: method });
|
|
238
|
-
const result = {
|
|
239
|
-
data: NOTREGISTERMETHOD,
|
|
240
|
-
};
|
|
241
|
-
this.capture({
|
|
242
|
-
type: MessageType.OnRequestResult,
|
|
243
|
-
status: ResponseStatus.Fail,
|
|
244
|
-
requestId,
|
|
245
|
-
serviceMethod: method,
|
|
246
|
-
error: result.data,
|
|
247
|
-
});
|
|
248
|
-
return result;
|
|
249
|
-
}
|
|
250
|
-
});
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
private waitForConnection() {
|
|
255
|
-
this.connectionPromise = new Promise((resolve) => {
|
|
256
|
-
this.connectionPromiseResolve = resolve;
|
|
257
|
-
});
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* 对于纯数组参数的情况,收到请求/通知后做展开操作
|
|
262
|
-
* 因为在通信层会为每个 rpc 调用添加一个 CancellationToken 参数
|
|
263
|
-
* 如果参数本身是数组, 在方法中如果使用 spread 运算符获取参数(...args),则会出现 [...args, MutableToken] 这种情况
|
|
264
|
-
* 所以发送请求时将这类参数统一再用数组包了一层,形如 [[...args]], 参考 {@link RPCProxy.get get} 方法
|
|
265
|
-
* 此时接收到的数组类参数固定长度为 2,且最后一项一定是 MutableToken
|
|
266
|
-
* @param args
|
|
267
|
-
* @returns args
|
|
268
|
-
*/
|
|
269
|
-
private serializeArguments(args: any[]): any[] {
|
|
270
|
-
const maybeCancellationToken = args[args.length - 1];
|
|
271
|
-
if (args.length === 2 && Array.isArray(args[0]) && maybeCancellationToken.hasOwnProperty('_isCancelled')) {
|
|
272
|
-
return [...args[0], maybeCancellationToken];
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
return args;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
private async onRequest(prop: PropertyKey, ...args: any[]) {
|
|
279
|
-
try {
|
|
280
|
-
const result = await this.proxyService[prop](...this.serializeArguments(args));
|
|
281
|
-
|
|
282
|
-
return {
|
|
283
|
-
data: result,
|
|
284
|
-
};
|
|
285
|
-
} catch (e) {
|
|
286
|
-
return {
|
|
287
|
-
error: e,
|
|
288
|
-
data: {
|
|
289
|
-
message: e.message,
|
|
290
|
-
stack: e.stack,
|
|
291
|
-
},
|
|
292
|
-
};
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
private onNotification(prop: PropertyKey, ...args: any[]) {
|
|
297
|
-
try {
|
|
298
|
-
this.proxyService[prop](...this.serializeArguments(args));
|
|
299
|
-
} catch (e) {
|
|
300
|
-
this.logger.warn('notification', e);
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
}
|
|
@@ -1,377 +0,0 @@
|
|
|
1
|
-
import { CancellationToken, CancellationTokenSource, Deferred, Event, Uri } from '@opensumi/ide-core-common';
|
|
2
|
-
// Uri: vscode 中的 uri
|
|
3
|
-
// URI: 在 vscode 中的 uri 基础上包装了一些基础方法
|
|
4
|
-
|
|
5
|
-
export enum RPCProtocolEnv {
|
|
6
|
-
MAIN,
|
|
7
|
-
EXT,
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export interface SerializedError {
|
|
11
|
-
readonly $isError: true;
|
|
12
|
-
readonly name: string;
|
|
13
|
-
readonly message: string;
|
|
14
|
-
readonly stack: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function transformErrorForSerialization(error: Error): SerializedError;
|
|
18
|
-
export function transformErrorForSerialization(error: any): any;
|
|
19
|
-
export function transformErrorForSerialization(error: any): any {
|
|
20
|
-
if (error instanceof Error) {
|
|
21
|
-
const { name, message } = error;
|
|
22
|
-
const stack: string = (error as any).stacktrace || (error as any).stack;
|
|
23
|
-
return {
|
|
24
|
-
$isError: true,
|
|
25
|
-
name,
|
|
26
|
-
message,
|
|
27
|
-
stack,
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return error;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export interface IProxyIdentifier {
|
|
35
|
-
serviceId: string;
|
|
36
|
-
countId: number;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export class ProxyIdentifier<T = any> {
|
|
40
|
-
public static count = 0;
|
|
41
|
-
|
|
42
|
-
public readonly serviceId: string;
|
|
43
|
-
public readonly countId: number;
|
|
44
|
-
constructor(serviceId: string) {
|
|
45
|
-
this.serviceId = serviceId;
|
|
46
|
-
this.countId = ++ProxyIdentifier.count;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export function createExtHostContextProxyIdentifier<T>(serviceId: string): ProxyIdentifier<T> {
|
|
51
|
-
const identifier = new ProxyIdentifier<T>(serviceId);
|
|
52
|
-
return identifier;
|
|
53
|
-
}
|
|
54
|
-
export function createMainContextProxyIdentifier<T>(identifier: string): ProxyIdentifier<T> {
|
|
55
|
-
const result = new ProxyIdentifier<T>(identifier);
|
|
56
|
-
return result;
|
|
57
|
-
}
|
|
58
|
-
export interface IMessagePassingProtocol {
|
|
59
|
-
send(msg): void;
|
|
60
|
-
onMessage: Event<string>;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const enum MessageType {
|
|
64
|
-
Request = 1,
|
|
65
|
-
Reply = 2,
|
|
66
|
-
ReplyErr = 3,
|
|
67
|
-
Cancel = 4,
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
interface RequestMessage {
|
|
71
|
-
type: MessageType.Request;
|
|
72
|
-
id: string;
|
|
73
|
-
proxyId: string;
|
|
74
|
-
method: string;
|
|
75
|
-
args: any[];
|
|
76
|
-
}
|
|
77
|
-
interface CancelMessage {
|
|
78
|
-
type: MessageType.Cancel;
|
|
79
|
-
id: string;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
interface ReplyMessage {
|
|
83
|
-
type: MessageType.Reply;
|
|
84
|
-
id: string;
|
|
85
|
-
res: any;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
interface ErrorMessage {
|
|
89
|
-
type: MessageType.Cancel;
|
|
90
|
-
id: string;
|
|
91
|
-
res: SerializedError;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export namespace ObjectTransfer {
|
|
95
|
-
export function replacer(key: string | undefined, value: any) {
|
|
96
|
-
if (value) {
|
|
97
|
-
if (value.$mid === 1) {
|
|
98
|
-
const uri = Uri.revive(value);
|
|
99
|
-
return {
|
|
100
|
-
$type: 'VSCODE_URI',
|
|
101
|
-
data: uri.toString(),
|
|
102
|
-
};
|
|
103
|
-
} else if (value instanceof Uint8Array || value instanceof Uint32Array || value instanceof Uint16Array) {
|
|
104
|
-
return {
|
|
105
|
-
$type: 'Buffer',
|
|
106
|
-
data: Array.from(value),
|
|
107
|
-
};
|
|
108
|
-
} else if (value instanceof ArrayBuffer) {
|
|
109
|
-
return {
|
|
110
|
-
$type: 'Buffer',
|
|
111
|
-
data: Array.from(new Uint8Array(value)),
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return value;
|
|
117
|
-
}
|
|
118
|
-
export function reviver(key: string | undefined, value: any) {
|
|
119
|
-
if (value && value.$type !== undefined && value.data !== undefined) {
|
|
120
|
-
if (value.$type === 'VSCODE_URI') {
|
|
121
|
-
return Uri.parse(value.data);
|
|
122
|
-
}
|
|
123
|
-
if (value.$type === 'Buffer') {
|
|
124
|
-
return Uint8Array.from(value.data);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
return value;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
export class MessageIO {
|
|
132
|
-
public static cancel(req: string, messageToSendHostId?: string): string {
|
|
133
|
-
return `{"type":${MessageType.Cancel},"id":"${req}"}`;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
public static serializeRequest(callId: string, rpcId: string, method: string, args: any[]): string {
|
|
137
|
-
args = args.map((arg) => {
|
|
138
|
-
if (arg instanceof Error) {
|
|
139
|
-
// 处理 Error 类型的参数
|
|
140
|
-
// eslint-disable-next-line prefer-rest-params
|
|
141
|
-
const array = Array.prototype.slice.call(arguments) as any[];
|
|
142
|
-
array[0] = arg.stack;
|
|
143
|
-
return array.join('\n');
|
|
144
|
-
}
|
|
145
|
-
return arg;
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
return `{"type": ${
|
|
149
|
-
MessageType.Request
|
|
150
|
-
}, "id": "${callId}", "proxyId": "${rpcId}", "method": "${method}", "args": ${JSON.stringify(
|
|
151
|
-
args,
|
|
152
|
-
ObjectTransfer.replacer,
|
|
153
|
-
)}}`;
|
|
154
|
-
}
|
|
155
|
-
public static serializeReplyOK(callId: string, res: any, logger?: any): string {
|
|
156
|
-
if (typeof res === 'undefined') {
|
|
157
|
-
return `{"type": ${MessageType.Reply}, "id": "${callId}"}`;
|
|
158
|
-
} else {
|
|
159
|
-
try {
|
|
160
|
-
return `{"type": ${MessageType.Reply}, "id": "${callId}", "res": ${JSON.stringify(
|
|
161
|
-
res,
|
|
162
|
-
ObjectTransfer.replacer,
|
|
163
|
-
)}}`;
|
|
164
|
-
} catch (e) {
|
|
165
|
-
if (logger) {
|
|
166
|
-
logger.warn('res', res);
|
|
167
|
-
}
|
|
168
|
-
return `{"type": ${MessageType.Reply}, "id": "${callId}", "res": {}}`;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
public static serializeReplyError(callId: string, error: Error, logger?: any): string {
|
|
173
|
-
try {
|
|
174
|
-
return `{"type": ${MessageType.ReplyErr}, "id": "${callId}", "res": ${JSON.stringify(
|
|
175
|
-
transformErrorForSerialization(error),
|
|
176
|
-
ObjectTransfer.replacer,
|
|
177
|
-
)}}`;
|
|
178
|
-
} catch (e) {
|
|
179
|
-
if (logger) {
|
|
180
|
-
logger.error('error', error);
|
|
181
|
-
}
|
|
182
|
-
return `{"type": ${MessageType.ReplyErr}, "id": "${callId}", "res": {}}`;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
export const IRPCProtocol = Symbol('IRPCProtocol');
|
|
188
|
-
export interface IRPCProtocol {
|
|
189
|
-
getProxy<T>(proxyId: ProxyIdentifier<T>): T;
|
|
190
|
-
set<T>(identifier: ProxyIdentifier<T>, instance: T): T;
|
|
191
|
-
get<T>(identifier: ProxyIdentifier<T>): T;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
function canceled(): Error {
|
|
195
|
-
const error = new Error('Canceled');
|
|
196
|
-
error.name = error.message;
|
|
197
|
-
return error;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
export class RPCProtocol implements IRPCProtocol {
|
|
201
|
-
private readonly _protocol: IMessagePassingProtocol;
|
|
202
|
-
private readonly _locals: Map<string, any>;
|
|
203
|
-
private readonly _proxies: Map<string, any>;
|
|
204
|
-
private readonly _cancellationTokenSources: Map<string, CancellationTokenSource>;
|
|
205
|
-
private _lastMessageId: number;
|
|
206
|
-
private _pendingRPCReplies: Map<string, Deferred<any>>;
|
|
207
|
-
private logger;
|
|
208
|
-
|
|
209
|
-
constructor(connection: IMessagePassingProtocol, logger?: any) {
|
|
210
|
-
this._protocol = connection;
|
|
211
|
-
this._locals = new Map();
|
|
212
|
-
this._proxies = new Map();
|
|
213
|
-
this._pendingRPCReplies = new Map();
|
|
214
|
-
this._cancellationTokenSources = new Map();
|
|
215
|
-
|
|
216
|
-
this._lastMessageId = 0;
|
|
217
|
-
this.logger = logger || console;
|
|
218
|
-
this._protocol.onMessage((msg) => this._receiveOneMessage(msg));
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
public set<T>(identifier: ProxyIdentifier<T>, instance: any) {
|
|
222
|
-
this._locals.set(identifier.serviceId, instance);
|
|
223
|
-
return instance;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
public get<T>(identifier: ProxyIdentifier<T>) {
|
|
227
|
-
return this._locals.get(identifier.serviceId);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
public getProxy<T>(proxyId: ProxyIdentifier<T>) {
|
|
231
|
-
if (!this._proxies.has(proxyId.serviceId)) {
|
|
232
|
-
this._proxies.set(proxyId.serviceId, this._createProxy(proxyId.serviceId));
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
return this._proxies.get(proxyId.serviceId);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
private _createProxy(rpcId: string) {
|
|
239
|
-
const handler = {
|
|
240
|
-
get: (target: any, name: string) => {
|
|
241
|
-
if (typeof name === 'symbol') {
|
|
242
|
-
return null;
|
|
243
|
-
}
|
|
244
|
-
if (!target[name] && name.charCodeAt(0) === 36) {
|
|
245
|
-
target[name] = (...myArgs: any[]) => this._remoteCall(rpcId, name, myArgs);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
return target[name];
|
|
249
|
-
},
|
|
250
|
-
};
|
|
251
|
-
|
|
252
|
-
return new Proxy(Object.create(null), handler);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
private _remoteCall(rpcId: string, methodName: string, args: any[]): Promise<any> {
|
|
256
|
-
const cancellationToken: CancellationToken | undefined =
|
|
257
|
-
args.length && CancellationToken.isCancellationToken(args[args.length - 1]) ? args.pop() : undefined;
|
|
258
|
-
if (cancellationToken && cancellationToken.isCancellationRequested) {
|
|
259
|
-
return Promise.reject(canceled());
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
const callId = String(++this._lastMessageId);
|
|
263
|
-
const result = new Deferred();
|
|
264
|
-
|
|
265
|
-
if (cancellationToken) {
|
|
266
|
-
args.push('add.cancellation.token');
|
|
267
|
-
cancellationToken.onCancellationRequested(() => this._protocol.send(MessageIO.cancel(callId)));
|
|
268
|
-
}
|
|
269
|
-
this._pendingRPCReplies.set(callId, result);
|
|
270
|
-
const msg = MessageIO.serializeRequest(callId, rpcId, methodName, args);
|
|
271
|
-
|
|
272
|
-
this._protocol.send(msg);
|
|
273
|
-
return result.promise;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
private _receiveOneMessage(rawmsg: string): void {
|
|
277
|
-
const msg = JSON.parse(rawmsg, ObjectTransfer.reviver);
|
|
278
|
-
switch (msg.type) {
|
|
279
|
-
case MessageType.Request:
|
|
280
|
-
this._receiveRequest(msg);
|
|
281
|
-
break;
|
|
282
|
-
case MessageType.Reply:
|
|
283
|
-
this._receiveReply(msg);
|
|
284
|
-
break;
|
|
285
|
-
case MessageType.Cancel:
|
|
286
|
-
this._receiveCancel(msg);
|
|
287
|
-
break;
|
|
288
|
-
case MessageType.ReplyErr:
|
|
289
|
-
this._receiveError(msg);
|
|
290
|
-
break;
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
private _receiveCancel(msg: CancelMessage) {
|
|
294
|
-
const cancellationTokenSource = this._cancellationTokenSources.get(msg.id);
|
|
295
|
-
if (cancellationTokenSource) {
|
|
296
|
-
cancellationTokenSource.cancel();
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
private _receiveError(msg: ErrorMessage) {
|
|
301
|
-
const callId = msg.id;
|
|
302
|
-
if (!this._pendingRPCReplies.has(callId)) {
|
|
303
|
-
return;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
const pendingReply = this._pendingRPCReplies.get(callId) as Deferred<any>;
|
|
307
|
-
this._pendingRPCReplies.delete(callId);
|
|
308
|
-
|
|
309
|
-
let err: any;
|
|
310
|
-
if (msg.res) {
|
|
311
|
-
if (msg.res.$isError) {
|
|
312
|
-
err = new Error();
|
|
313
|
-
err.name = msg.res.name;
|
|
314
|
-
err.message = msg.res.message;
|
|
315
|
-
err.stack = msg.res.stack;
|
|
316
|
-
} else {
|
|
317
|
-
err = msg.res;
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
pendingReply.reject(err);
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
private _receiveRequest(msg: RequestMessage): void {
|
|
324
|
-
const callId = msg.id;
|
|
325
|
-
const rpcId = msg.proxyId;
|
|
326
|
-
const method = msg.method;
|
|
327
|
-
const args = msg.args.map((arg) => (arg === null ? undefined : arg));
|
|
328
|
-
|
|
329
|
-
const addToken = args.length && args[args.length - 1] === 'add.cancellation.token' ? args.pop() : false;
|
|
330
|
-
if (addToken) {
|
|
331
|
-
const tokenSource = new CancellationTokenSource();
|
|
332
|
-
this._cancellationTokenSources.set(callId, tokenSource);
|
|
333
|
-
args.push(tokenSource.token);
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
const promise = this._invokeHandler(rpcId, method, args);
|
|
337
|
-
promise
|
|
338
|
-
.then((r) => {
|
|
339
|
-
this._protocol.send(MessageIO.serializeReplyOK(callId, r));
|
|
340
|
-
this._cancellationTokenSources.delete(callId);
|
|
341
|
-
})
|
|
342
|
-
.catch((err) => {
|
|
343
|
-
this._protocol.send(MessageIO.serializeReplyError(callId, err));
|
|
344
|
-
this._cancellationTokenSources.delete(callId);
|
|
345
|
-
});
|
|
346
|
-
}
|
|
347
|
-
private _invokeHandler(rpcId: string, methodName: string, args: any[]) {
|
|
348
|
-
try {
|
|
349
|
-
return this._doInvokeHandler(rpcId, methodName, args);
|
|
350
|
-
} catch (err) {
|
|
351
|
-
return Promise.reject(err);
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
private async _doInvokeHandler(rpcId: string, methodName: string, args: any[]): Promise<any> {
|
|
355
|
-
const actor = this._locals.get(rpcId);
|
|
356
|
-
if (!actor) {
|
|
357
|
-
throw new Error('Unknown actor ' + rpcId);
|
|
358
|
-
}
|
|
359
|
-
const method = await actor[methodName];
|
|
360
|
-
if (typeof method !== 'function') {
|
|
361
|
-
throw new Error('Unknown method ' + methodName + ' on actor ' + rpcId);
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
return method.apply(actor, args);
|
|
365
|
-
}
|
|
366
|
-
private _receiveReply(msg: ReplyMessage) {
|
|
367
|
-
const callId = msg.id;
|
|
368
|
-
if (!this._pendingRPCReplies.has(callId)) {
|
|
369
|
-
return;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
const pendingReply = this._pendingRPCReplies.get(callId) as Deferred<any>;
|
|
373
|
-
this._pendingRPCReplies.delete(callId);
|
|
374
|
-
|
|
375
|
-
pendingReply.resolve(msg.res);
|
|
376
|
-
}
|
|
377
|
-
}
|