@opensumi/ide-connection 2.21.13 → 2.22.0
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/lib/common/connect.d.ts +2 -2
- package/lib/common/connect.d.ts.map +1 -1
- package/lib/common/proxy.js +6 -6
- package/lib/common/proxy.js.map +1 -1
- package/lib/common/rpcProtocol.js +11 -11
- package/lib/common/rpcProtocol.js.map +1 -1
- package/lib/common/ws-channel.d.ts +2 -2
- package/lib/common/ws-channel.d.ts.map +1 -1
- package/lib/node/common-channel-handler.d.ts.map +1 -1
- package/lib/node/common-channel-handler.js +5 -7
- package/lib/node/common-channel-handler.js.map +1 -1
- package/package.json +9 -7
- package/src/browser/index.ts +1 -0
- package/src/browser/ws-channel-handler.ts +124 -0
- package/src/common/connect.ts +199 -0
- package/src/common/index.ts +6 -0
- package/src/common/message.ts +93 -0
- package/src/common/proxy.ts +303 -0
- package/src/common/rpcProtocol.ts +377 -0
- package/src/common/utils.ts +44 -0
- package/src/common/ws-channel.ts +144 -0
- package/src/index.ts +1 -0
- package/src/node/common-channel-handler.ts +224 -0
- package/src/node/connect.ts +11 -0
- package/src/node/index.ts +7 -0
- package/src/node/ws.ts +109 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { MessageConnection } from '@opensumi/vscode-jsonrpc/lib/common/connection';
|
|
2
|
+
|
|
3
|
+
import { RPCProxy, NOTREGISTERMETHOD } from './proxy';
|
|
4
|
+
|
|
5
|
+
export type RPCServiceMethod = (...args: any[]) => any;
|
|
6
|
+
export type ServiceProxy = any;
|
|
7
|
+
|
|
8
|
+
export enum ServiceType {
|
|
9
|
+
Service,
|
|
10
|
+
Stub,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class RPCServiceStub {
|
|
14
|
+
constructor(private serviceName: string, private center, private type: ServiceType) {
|
|
15
|
+
if (this.type === ServiceType.Service) {
|
|
16
|
+
this.center.registerService(serviceName, this.type);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async ready() {
|
|
21
|
+
return this.center.when();
|
|
22
|
+
}
|
|
23
|
+
getNotificationName(name: string) {
|
|
24
|
+
return `on:${this.serviceName}:${name}`;
|
|
25
|
+
}
|
|
26
|
+
getRequestName(name: string) {
|
|
27
|
+
return `${this.serviceName}:${name}`;
|
|
28
|
+
}
|
|
29
|
+
// 服务方
|
|
30
|
+
on(name: string, method: RPCServiceMethod) {
|
|
31
|
+
this.onRequest(name, method);
|
|
32
|
+
}
|
|
33
|
+
getServiceMethod(service): string[] {
|
|
34
|
+
let props: any[] = [];
|
|
35
|
+
|
|
36
|
+
if (/^\s*class/.test(service.constructor.toString())) {
|
|
37
|
+
let obj = service;
|
|
38
|
+
do {
|
|
39
|
+
props = props.concat(Object.getOwnPropertyNames(obj));
|
|
40
|
+
} while ((obj = Object.getPrototypeOf(obj)));
|
|
41
|
+
props = props.sort().filter((e, i, arr) => e !== arr[i + 1] && typeof service[e] === 'function');
|
|
42
|
+
} else {
|
|
43
|
+
for (const prop in service) {
|
|
44
|
+
if (service[prop] && typeof service[prop] === 'function') {
|
|
45
|
+
props.push(prop);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return props;
|
|
51
|
+
}
|
|
52
|
+
onRequestService(service: any) {
|
|
53
|
+
const methods = this.getServiceMethod(service);
|
|
54
|
+
for (const method of methods) {
|
|
55
|
+
this.onRequest(method, service[method].bind(service));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
onRequest(name: string, method: RPCServiceMethod) {
|
|
60
|
+
this.center.onRequest(this.getMethodName(name), method);
|
|
61
|
+
}
|
|
62
|
+
broadcast(name: string, ...args) {
|
|
63
|
+
return this.center.broadcast(this.getMethodName(name), ...args);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
getMethodName(name: string) {
|
|
67
|
+
return name.startsWith('on') ? this.getNotificationName(name) : this.getRequestName(name);
|
|
68
|
+
}
|
|
69
|
+
getProxy = <T>() =>
|
|
70
|
+
new Proxy<RPCServiceStub & T>(this as any, {
|
|
71
|
+
// 调用方
|
|
72
|
+
get: (target, prop: string) => {
|
|
73
|
+
if (!target[prop]) {
|
|
74
|
+
if (typeof prop === 'symbol') {
|
|
75
|
+
return Promise.resolve();
|
|
76
|
+
} else {
|
|
77
|
+
return (...args) =>
|
|
78
|
+
this.ready().then(() => {
|
|
79
|
+
const name = this.getMethodName(prop);
|
|
80
|
+
return Promise.all(this.center.serviceProxy.map((proxy) => proxy[name](...args)))
|
|
81
|
+
.then((result) => result.filter((res) => res !== NOTREGISTERMETHOD))
|
|
82
|
+
.then((result) => (result.length === 1 ? result[0] : result));
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
} else {
|
|
86
|
+
return target[prop];
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function initRPCService<T = void>(center: RPCServiceCenter) {
|
|
93
|
+
return {
|
|
94
|
+
createRPCService: (name: string, service?: any) => {
|
|
95
|
+
const proxy = new RPCServiceStub(name, center, ServiceType.Service).getProxy<T>();
|
|
96
|
+
if (service) {
|
|
97
|
+
proxy.onRequestService(service);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return proxy;
|
|
101
|
+
},
|
|
102
|
+
getRPCService: (name: string) => new RPCServiceStub(name, center, ServiceType.Stub).getProxy<T>(),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
interface IBench {
|
|
107
|
+
registerService: (service: string) => void;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface RPCMessageConnection extends MessageConnection {
|
|
111
|
+
uid?: string;
|
|
112
|
+
writer?: any;
|
|
113
|
+
reader?: any;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function createRPCService<T = void>(name: string, center: RPCServiceCenter): any {
|
|
117
|
+
return new RPCServiceStub(name, center, ServiceType.Service).getProxy<T>();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function getRPCService<T = void>(name: string, center: RPCServiceCenter): any {
|
|
121
|
+
return new RPCServiceStub(name, center, ServiceType.Stub).getProxy<T>();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export class RPCServiceCenter {
|
|
125
|
+
public uid: string;
|
|
126
|
+
public rpcProxy: RPCProxy[] = [];
|
|
127
|
+
public serviceProxy: ServiceProxy[] = [];
|
|
128
|
+
private connection: Array<MessageConnection> = [];
|
|
129
|
+
private serviceMethodMap = { client: undefined };
|
|
130
|
+
|
|
131
|
+
private createService: string[] = [];
|
|
132
|
+
private getService: string[] = [];
|
|
133
|
+
|
|
134
|
+
private connectionPromise: Promise<void>;
|
|
135
|
+
private connectionPromiseResolve: () => void;
|
|
136
|
+
private logger;
|
|
137
|
+
|
|
138
|
+
constructor(private bench?: IBench, logger?: any) {
|
|
139
|
+
this.uid = 'RPCServiceCenter:' + process.pid;
|
|
140
|
+
this.connectionPromise = new Promise((resolve) => {
|
|
141
|
+
this.connectionPromiseResolve = resolve;
|
|
142
|
+
});
|
|
143
|
+
this.logger = logger || console;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
registerService(serviceName: string, type: ServiceType): void {
|
|
147
|
+
if (type === ServiceType.Service) {
|
|
148
|
+
this.createService.push(serviceName);
|
|
149
|
+
if (this.bench) {
|
|
150
|
+
this.bench.registerService(serviceName);
|
|
151
|
+
}
|
|
152
|
+
} else if (type === ServiceType.Stub) {
|
|
153
|
+
this.getService.push(serviceName);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
when() {
|
|
158
|
+
return this.connectionPromise;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
setConnection(connection: MessageConnection) {
|
|
162
|
+
if (!this.connection.length) {
|
|
163
|
+
this.connectionPromiseResolve();
|
|
164
|
+
}
|
|
165
|
+
this.connection.push(connection);
|
|
166
|
+
|
|
167
|
+
const rpcProxy = new RPCProxy(this.serviceMethodMap, this.logger);
|
|
168
|
+
rpcProxy.listen(connection);
|
|
169
|
+
this.rpcProxy.push(rpcProxy);
|
|
170
|
+
|
|
171
|
+
const serviceProxy = rpcProxy.createProxy();
|
|
172
|
+
this.serviceProxy.push(serviceProxy);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
removeConnection(connection: MessageConnection) {
|
|
176
|
+
const removeIndex = this.connection.indexOf(connection);
|
|
177
|
+
if (removeIndex !== -1) {
|
|
178
|
+
this.connection.splice(removeIndex, 1);
|
|
179
|
+
this.rpcProxy.splice(removeIndex, 1);
|
|
180
|
+
this.serviceProxy.splice(removeIndex, 1);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return removeIndex !== -1;
|
|
184
|
+
}
|
|
185
|
+
onRequest(name, method: RPCServiceMethod) {
|
|
186
|
+
if (!this.connection.length) {
|
|
187
|
+
this.serviceMethodMap[name] = method;
|
|
188
|
+
} else {
|
|
189
|
+
this.rpcProxy.forEach((proxy) => {
|
|
190
|
+
proxy.listenService({ [name]: method });
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
broadcast(name, ...args): Promise<any> {
|
|
195
|
+
return Promise.all(this.serviceProxy.map((proxy) => proxy[name](...args)) as Promise<any>[])
|
|
196
|
+
.then((result) => result.filter((res) => res !== NOTREGISTERMETHOD))
|
|
197
|
+
.then((result) => (result.length === 1 ? result[0] : result));
|
|
198
|
+
}
|
|
199
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AbstractMessageReader,
|
|
3
|
+
AbstractMessageWriter,
|
|
4
|
+
createMessageConnection,
|
|
5
|
+
} from '@opensumi/vscode-jsonrpc/lib/common/api';
|
|
6
|
+
import { Disposable } from '@opensumi/vscode-jsonrpc/lib/common/disposable';
|
|
7
|
+
import { MessageReader, DataCallback } from '@opensumi/vscode-jsonrpc/lib/common/messageReader';
|
|
8
|
+
import { MessageWriter } from '@opensumi/vscode-jsonrpc/lib/common/messageWriter';
|
|
9
|
+
/**
|
|
10
|
+
* FIXME: 由于 `createMessageConnection` 方法隐式依赖了 `@opensumi/vscode-jsonrpc/lib/browser/main` 或 `@opensumi/vscode-jsonrpc/lib/node/main`
|
|
11
|
+
* 的 `RIL.install()` 初始化代码,而 `browser/main` 中仅支持浏览器使用,
|
|
12
|
+
* 故需要保证提前引入或执行一次 `@opensumi/vscode-jsonrpc/lib/node/main` 代码才能保证逻辑正常执行
|
|
13
|
+
*/
|
|
14
|
+
import '@opensumi/vscode-jsonrpc/lib/node/main';
|
|
15
|
+
|
|
16
|
+
export class WebSocketMessageReader extends AbstractMessageReader implements MessageReader {
|
|
17
|
+
protected state: 'initial' | 'listening' | 'closed' = 'initial';
|
|
18
|
+
protected callback: DataCallback | undefined;
|
|
19
|
+
protected events: { message?: any; error?: any }[] = [];
|
|
20
|
+
|
|
21
|
+
constructor(protected readonly socket) {
|
|
22
|
+
super();
|
|
23
|
+
if (this.socket.onMessage) {
|
|
24
|
+
this.socket.onMessage((message) => {
|
|
25
|
+
this.readMessage(message);
|
|
26
|
+
});
|
|
27
|
+
} else if (this.socket.onmessage) {
|
|
28
|
+
this.socket.onmessage = (message) => {
|
|
29
|
+
this.readMessage(message);
|
|
30
|
+
};
|
|
31
|
+
} else if (this.socket.on) {
|
|
32
|
+
this.socket.on('message', (message) => {
|
|
33
|
+
this.readMessage(message);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public listen(callback: DataCallback): Disposable {
|
|
39
|
+
if (this.state === 'initial') {
|
|
40
|
+
this.state = 'listening';
|
|
41
|
+
this.callback = callback;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
while (this.events.length !== 0) {
|
|
45
|
+
const event = this.events.pop()!;
|
|
46
|
+
if (event.message) {
|
|
47
|
+
this.readMessage(event.message);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return Disposable.create(() => {
|
|
52
|
+
this.state = 'closed';
|
|
53
|
+
this.callback = undefined;
|
|
54
|
+
this.events = [];
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
protected readMessage(message) {
|
|
59
|
+
if (this.state === 'initial') {
|
|
60
|
+
this.events.splice(0, 0, { message });
|
|
61
|
+
} else if (this.state === 'listening') {
|
|
62
|
+
const data = JSON.parse(message);
|
|
63
|
+
this.callback!(data);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export class WebSocketMessageWriter extends AbstractMessageWriter implements MessageWriter {
|
|
69
|
+
constructor(protected readonly socket) {
|
|
70
|
+
super();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
write(msg): Promise<void> {
|
|
74
|
+
try {
|
|
75
|
+
const content = JSON.stringify(msg);
|
|
76
|
+
this.socket.send(content);
|
|
77
|
+
return Promise.resolve();
|
|
78
|
+
} catch (e) {
|
|
79
|
+
return Promise.reject(e);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
public end(): void {}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* 给服务端的 WebSocket 及 Browser 端的 WebSocket 实例共用的方法
|
|
88
|
+
* @param socket
|
|
89
|
+
* @returns
|
|
90
|
+
*/
|
|
91
|
+
export function createWebSocketConnection(socket: any) {
|
|
92
|
+
return createMessageConnection(new WebSocketMessageReader(socket), new WebSocketMessageWriter(socket));
|
|
93
|
+
}
|
|
@@ -0,0 +1,303 @@
|
|
|
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
|
+
}
|