@x-oasis/async-call-rpc 0.1.38
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/README.md +48 -0
- package/dist/async-call-rpc.cjs.development.js +1275 -0
- package/dist/async-call-rpc.cjs.development.js.map +1 -0
- package/dist/async-call-rpc.cjs.production.min.js +2 -0
- package/dist/async-call-rpc.cjs.production.min.js.map +1 -0
- package/dist/async-call-rpc.esm.js +1559 -0
- package/dist/async-call-rpc.esm.js.map +1 -0
- package/dist/buffer/BufferFactory.d.ts +12 -0
- package/dist/buffer/DataBuffer.d.ts +2 -0
- package/dist/buffer/MessagePackBuffer.d.ts +10 -0
- package/dist/buffer/ReadBaseBuffer.d.ts +5 -0
- package/dist/buffer/ReadBuffer.d.ts +14 -0
- package/dist/buffer/SerializationFormat.d.ts +16 -0
- package/dist/buffer/WriteBaseBuffer.d.ts +5 -0
- package/dist/buffer/WriteBuffer.d.ts +5 -0
- package/dist/buffer/examples.d.ts +10 -0
- package/dist/buffer/index.d.ts +8 -0
- package/dist/common.d.ts +4 -0
- package/dist/endpoint/ProxyRPCClient.d.ts +12 -0
- package/dist/endpoint/RPCClientHost.d.ts +12 -0
- package/dist/endpoint/RPCService.d.ts +20 -0
- package/dist/endpoint/RPCServiceHost.d.ts +10 -0
- package/dist/error.d.ts +21 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +8 -0
- package/dist/middlewares/buffer.d.ts +18 -0
- package/dist/middlewares/handleDisconnectedRequest.d.ts +7 -0
- package/dist/middlewares/handlePortRequest.d.ts +3 -0
- package/dist/middlewares/handleRequest.d.ts +3 -0
- package/dist/middlewares/handleRequestUtils.d.ts +10 -0
- package/dist/middlewares/handleResponse.d.ts +3 -0
- package/dist/middlewares/index.d.ts +7 -0
- package/dist/middlewares/logger.d.ts +4 -0
- package/dist/middlewares/normalize.d.ts +14 -0
- package/dist/middlewares/prepareRequestData.d.ts +28 -0
- package/dist/middlewares/sendRequest.d.ts +6 -0
- package/dist/middlewares/updateSeqInfo.d.ts +6 -0
- package/dist/middlewares/utils.d.ts +3 -0
- package/dist/protocol/AbstractChannelProtocol.d.ts +55 -0
- package/dist/protocol/MessageChannel.d.ts +18 -0
- package/dist/protocol/WebSocketChannel.d.ts +23 -0
- package/dist/protocol/WorkerChannel.d.ts +11 -0
- package/dist/types/buffer.d.ts +4 -0
- package/dist/types/channel.d.ts +27 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/middleware.d.ts +44 -0
- package/dist/types/proxyChannel.d.ts +3 -0
- package/dist/types/proxyService.d.ts +11 -0
- package/dist/types/rpc.d.ts +25 -0
- package/dist/types/rpcProtocol.d.ts +22 -0
- package/dist/utils/constants.d.ts +5 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/jsonrpc.d.ts +36 -0
- package/package.json +36 -0
- package/src/buffer/ARCHITECTURE.md +298 -0
- package/src/buffer/BufferFactory.ts +124 -0
- package/src/buffer/CHANGELOG.md +207 -0
- package/src/buffer/DataBuffer.ts +1 -0
- package/src/buffer/MessagePackBuffer.ts +79 -0
- package/src/buffer/OPTIMIZATION.md +258 -0
- package/src/buffer/README.md +147 -0
- package/src/buffer/ReadBaseBuffer.ts +20 -0
- package/src/buffer/ReadBuffer.ts +58 -0
- package/src/buffer/SerializationFormat.ts +81 -0
- package/src/buffer/WriteBaseBuffer.ts +20 -0
- package/src/buffer/WriteBuffer.ts +15 -0
- package/src/buffer/examples.ts +242 -0
- package/src/buffer/index.ts +15 -0
- package/src/common.ts +20 -0
- package/src/endpoint/ProxyRPCClient.ts +64 -0
- package/src/endpoint/RPCClientHost.ts +45 -0
- package/src/endpoint/RPCService.ts +54 -0
- package/src/endpoint/RPCServiceHost.ts +18 -0
- package/src/error.ts +98 -0
- package/src/index.ts +16 -0
- package/src/middlewares/buffer.ts +33 -0
- package/src/middlewares/handleDisconnectedRequest.ts +30 -0
- package/src/middlewares/handlePortRequest.ts +141 -0
- package/src/middlewares/handleRequest.ts +128 -0
- package/src/middlewares/handleRequestUtils.ts +43 -0
- package/src/middlewares/handleResponse.ts +36 -0
- package/src/middlewares/index.ts +11 -0
- package/src/middlewares/logger.ts +22 -0
- package/src/middlewares/normalize.ts +167 -0
- package/src/middlewares/prepareRequestData.ts +137 -0
- package/src/middlewares/sendRequest.ts +15 -0
- package/src/middlewares/updateSeqInfo.ts +34 -0
- package/src/middlewares/utils.ts +67 -0
- package/src/protocol/AbstractChannelProtocol.ts +343 -0
- package/src/protocol/MessageChannel.ts +80 -0
- package/src/protocol/WebSocketChannel.ts +179 -0
- package/src/protocol/WorkerChannel.ts +36 -0
- package/src/types/buffer.ts +5 -0
- package/src/types/channel.ts +50 -0
- package/src/types/index.ts +9 -0
- package/src/types/messageChannel.ts +133 -0
- package/src/types/middleware.ts +54 -0
- package/src/types/proxyChannel.ts +3 -0
- package/src/types/proxyService.ts +18 -0
- package/src/types/rpc.ts +61 -0
- package/src/types/rpcProtocol.ts +24 -0
- package/src/utils/constants.ts +17 -0
- package/src/utils/index.ts +5 -0
- package/src/utils/jsonrpc.ts +242 -0
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import { Disposable } from '@x-oasis/disposable';
|
|
2
|
+
import { Event } from '@x-oasis/emitter';
|
|
3
|
+
import { Deferred } from '@x-oasis/deferred';
|
|
4
|
+
import { generateRandomKey } from '@x-oasis/id';
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
SendingProps,
|
|
8
|
+
IMessageChannel,
|
|
9
|
+
ClientMiddleware,
|
|
10
|
+
SenderMiddleware,
|
|
11
|
+
RequestRawSequenceId,
|
|
12
|
+
PendingSendEntry,
|
|
13
|
+
AbstractChannelProtocolProps,
|
|
14
|
+
} from '../types';
|
|
15
|
+
import { runMiddlewares, sendRequest } from '../middlewares';
|
|
16
|
+
import ReadBaseBuffer from '../buffer/ReadBaseBuffer';
|
|
17
|
+
import WriteBaseBuffer from '../buffer/WriteBaseBuffer';
|
|
18
|
+
import { BufferFactory } from '../buffer/BufferFactory';
|
|
19
|
+
import { SerializationFormat } from '../buffer/SerializationFormat';
|
|
20
|
+
import { resumeMiddlewares } from '../middlewares/utils';
|
|
21
|
+
|
|
22
|
+
import { deserialize, serialize } from '../middlewares/buffer';
|
|
23
|
+
import { handleResponse } from '../middlewares/handleResponse';
|
|
24
|
+
import RPCService from '../endpoint/RPCService';
|
|
25
|
+
import { prepareNormalData } from '../middlewares/prepareRequestData';
|
|
26
|
+
import { updateSeqInfo } from '../middlewares/updateSeqInfo';
|
|
27
|
+
import { normalizeMessageChannelRawMessage } from '../middlewares/normalize';
|
|
28
|
+
import { handleRequest } from '../middlewares/handleRequest';
|
|
29
|
+
|
|
30
|
+
abstract class AbstractChannelProtocol
|
|
31
|
+
extends Disposable
|
|
32
|
+
implements IMessageChannel
|
|
33
|
+
{
|
|
34
|
+
private readonly _masterProcessName: string;
|
|
35
|
+
|
|
36
|
+
private _key: string;
|
|
37
|
+
|
|
38
|
+
private _service: RPCService;
|
|
39
|
+
|
|
40
|
+
private readonly _description: string;
|
|
41
|
+
|
|
42
|
+
private _seqId: RequestRawSequenceId = -1;
|
|
43
|
+
|
|
44
|
+
// decoder should comes first !!!!
|
|
45
|
+
protected _onMessageMiddleware: ClientMiddleware[] = [
|
|
46
|
+
normalizeMessageChannelRawMessage,
|
|
47
|
+
deserialize,
|
|
48
|
+
handleRequest,
|
|
49
|
+
handleResponse,
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
private _senderMiddleware: SenderMiddleware[] = [
|
|
53
|
+
prepareNormalData,
|
|
54
|
+
updateSeqInfo,
|
|
55
|
+
serialize,
|
|
56
|
+
sendRequest,
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
private _readBuffer: ReadBaseBuffer | null = null;
|
|
60
|
+
|
|
61
|
+
private _writeBuffer: WriteBaseBuffer | null = null;
|
|
62
|
+
|
|
63
|
+
private _serializationFormat: string;
|
|
64
|
+
|
|
65
|
+
private _isConnected = true;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 如果说channel存在的话,那么就是ongoing request
|
|
69
|
+
*/
|
|
70
|
+
public ongoingRequests: Map<string, Deferred> = new Map();
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* 如果说channel不存在,那么请求就会暂时放到这个里面
|
|
74
|
+
*/
|
|
75
|
+
public pendingSendEntries = new Set<PendingSendEntry>();
|
|
76
|
+
|
|
77
|
+
public requestEvents: Map<string, any> = new Map();
|
|
78
|
+
|
|
79
|
+
private onDidConnectedEvent = new Event({ name: 'on-did-connected' });
|
|
80
|
+
|
|
81
|
+
onDidConnected = this.onDidConnectedEvent.subscribe;
|
|
82
|
+
|
|
83
|
+
private onDidDisconnectedEvent = new Event({ name: 'on-did-disconnect' });
|
|
84
|
+
|
|
85
|
+
onDidDisconnected = this.onDidDisconnectedEvent.subscribe;
|
|
86
|
+
|
|
87
|
+
constructor(props?: AbstractChannelProtocolProps) {
|
|
88
|
+
super();
|
|
89
|
+
const {
|
|
90
|
+
description,
|
|
91
|
+
masterProcessName,
|
|
92
|
+
connected = true,
|
|
93
|
+
serializationFormat = SerializationFormat.JSON,
|
|
94
|
+
readBuffer,
|
|
95
|
+
writeBuffer,
|
|
96
|
+
} = props || {};
|
|
97
|
+
|
|
98
|
+
this._description = description;
|
|
99
|
+
this._isConnected = connected;
|
|
100
|
+
this._masterProcessName = masterProcessName;
|
|
101
|
+
this._serializationFormat = serializationFormat;
|
|
102
|
+
|
|
103
|
+
// 如果提供了自定义 buffer,直接使用
|
|
104
|
+
if (readBuffer) {
|
|
105
|
+
this._readBuffer = readBuffer;
|
|
106
|
+
}
|
|
107
|
+
if (writeBuffer) {
|
|
108
|
+
this._writeBuffer = writeBuffer;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// 需要创建,否则创建'message'监听时,会出现混乱
|
|
112
|
+
this._key = generateRandomKey();
|
|
113
|
+
this.registerDisposable(this.onDidConnected(this.didConnected.bind(this)));
|
|
114
|
+
|
|
115
|
+
this._onMessageMiddleware = this.decorateOnMessageMiddleware(
|
|
116
|
+
this._onMessageMiddleware
|
|
117
|
+
);
|
|
118
|
+
this._senderMiddleware = this.decorateSendMiddleware(
|
|
119
|
+
this._senderMiddleware
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
this.applyOnMessageMiddleware(this._onMessageMiddleware);
|
|
123
|
+
this.applySendMiddleware(this._senderMiddleware);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
get service() {
|
|
127
|
+
return this._service;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
setService(service: RPCService) {
|
|
131
|
+
this._service = service;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
get senderMiddleware() {
|
|
135
|
+
return this._senderMiddleware;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Get or create read buffer instance
|
|
140
|
+
* Uses lazy initialization with caching for performance
|
|
141
|
+
*
|
|
142
|
+
* Priority:
|
|
143
|
+
* 1. Custom buffer provided in constructor
|
|
144
|
+
* 2. Cached instance
|
|
145
|
+
* 3. Create new instance using BufferFactory with configured format
|
|
146
|
+
*
|
|
147
|
+
* Subclasses can override this method to provide custom buffer logic
|
|
148
|
+
*/
|
|
149
|
+
get readBuffer(): ReadBaseBuffer {
|
|
150
|
+
if (this._readBuffer) {
|
|
151
|
+
return this._readBuffer;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Try to create using BufferFactory
|
|
155
|
+
try {
|
|
156
|
+
this._readBuffer = BufferFactory.createReadBuffer(
|
|
157
|
+
this._serializationFormat
|
|
158
|
+
);
|
|
159
|
+
} catch (error) {
|
|
160
|
+
// Fallback to JSON if configured format is not available
|
|
161
|
+
console.warn(
|
|
162
|
+
`[AbstractChannelProtocol] Failed to create read buffer with format "${this._serializationFormat}", falling back to JSON.`,
|
|
163
|
+
error
|
|
164
|
+
);
|
|
165
|
+
this._readBuffer = BufferFactory.createReadBuffer(
|
|
166
|
+
SerializationFormat.JSON
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return this._readBuffer;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Get or create write buffer instance
|
|
175
|
+
* Uses lazy initialization with caching for performance
|
|
176
|
+
*
|
|
177
|
+
* Priority:
|
|
178
|
+
* 1. Custom buffer provided in constructor
|
|
179
|
+
* 2. Cached instance
|
|
180
|
+
* 3. Create new instance using BufferFactory with configured format
|
|
181
|
+
*
|
|
182
|
+
* Subclasses can override this method to provide custom buffer logic
|
|
183
|
+
*/
|
|
184
|
+
get writeBuffer(): WriteBaseBuffer {
|
|
185
|
+
if (this._writeBuffer) {
|
|
186
|
+
return this._writeBuffer;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Try to create using BufferFactory
|
|
190
|
+
try {
|
|
191
|
+
this._writeBuffer = BufferFactory.createWriteBuffer(
|
|
192
|
+
this._serializationFormat
|
|
193
|
+
);
|
|
194
|
+
} catch (error) {
|
|
195
|
+
// Fallback to JSON if configured format is not available
|
|
196
|
+
console.warn(
|
|
197
|
+
`[AbstractChannelProtocol] Failed to create write buffer with format "${this._serializationFormat}", falling back to JSON.`,
|
|
198
|
+
error
|
|
199
|
+
);
|
|
200
|
+
this._writeBuffer = BufferFactory.createWriteBuffer(
|
|
201
|
+
SerializationFormat.JSON
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return this._writeBuffer;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Get the configured serialization format
|
|
210
|
+
*/
|
|
211
|
+
get serializationFormat(): string {
|
|
212
|
+
return this._serializationFormat;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Set serialization format (will recreate buffers on next access)
|
|
217
|
+
* Note: This will clear cached buffers, new instances will be created on next access
|
|
218
|
+
*/
|
|
219
|
+
setSerializationFormat(format: string): void {
|
|
220
|
+
if (this._serializationFormat !== format) {
|
|
221
|
+
this._serializationFormat = format;
|
|
222
|
+
// Clear cached buffers to force recreation with new format
|
|
223
|
+
this._readBuffer = null;
|
|
224
|
+
this._writeBuffer = null;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// start from 1
|
|
229
|
+
get seqId() {
|
|
230
|
+
this._seqId += 1;
|
|
231
|
+
return `${this._key}_${this._seqId}`;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
get description() {
|
|
235
|
+
return this._description;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
get masterProcessName() {
|
|
239
|
+
return this._masterProcessName;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
addPendingSendEntry(entry: PendingSendEntry) {
|
|
243
|
+
this.pendingSendEntries.add(entry);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
*
|
|
248
|
+
* @param middlewares
|
|
249
|
+
* @returns
|
|
250
|
+
*
|
|
251
|
+
* 增加自定义的middleware,需要重写这个方法
|
|
252
|
+
*/
|
|
253
|
+
decorateSendMiddleware(middlewares: SenderMiddleware[]) {
|
|
254
|
+
return middlewares;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
decorateOnMessageMiddleware(middlewares: ClientMiddleware[]) {
|
|
258
|
+
return middlewares;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
applyOnMessageMiddleware(fns: Function | Function[]) {
|
|
262
|
+
const copy = [].concat(fns);
|
|
263
|
+
this._onMessageMiddleware = [];
|
|
264
|
+
copy.forEach((fn) => {
|
|
265
|
+
if (typeof fn === 'function') {
|
|
266
|
+
this._onMessageMiddleware.push(fn(this));
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
applySendMiddleware(fns: Function | Function[]) {
|
|
272
|
+
const copy = [].concat(fns);
|
|
273
|
+
this._senderMiddleware = [];
|
|
274
|
+
|
|
275
|
+
copy.forEach((fn) => {
|
|
276
|
+
if (typeof fn === 'function') {
|
|
277
|
+
this._senderMiddleware.push(fn(this));
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
isConnected() {
|
|
283
|
+
return this._isConnected;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
send(..._args: any[]) {
|
|
287
|
+
throw new Error('send method is not implemented');
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
on(..._args: any[]) {
|
|
291
|
+
throw new Error('onMessage method is not implemented');
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
resumePendingEntry() {
|
|
295
|
+
this.pendingSendEntries.forEach((entry) => {
|
|
296
|
+
this.pendingSendEntries.delete(entry);
|
|
297
|
+
resumeMiddlewares(this.senderMiddleware, entry);
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
didConnected() {
|
|
302
|
+
this.resumePendingEntry();
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
connect() {
|
|
306
|
+
this.onDidConnectedEvent.fire();
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// 已经绑定了channel,这个时候需要直接触发
|
|
310
|
+
activate() {
|
|
311
|
+
this._isConnected = true;
|
|
312
|
+
this.onDidConnectedEvent.fire();
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
disconnect() {
|
|
316
|
+
this._isConnected = false;
|
|
317
|
+
this.onDidDisconnectedEvent.fire();
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
makeRequest(props: SendingProps, transfer?: MessagePort[]): Deferred | void;
|
|
321
|
+
|
|
322
|
+
makeRequest(
|
|
323
|
+
requestPath: string,
|
|
324
|
+
fnName: string,
|
|
325
|
+
...args: any[]
|
|
326
|
+
): Deferred | void;
|
|
327
|
+
|
|
328
|
+
makeRequest(...args: any[]) {
|
|
329
|
+
//
|
|
330
|
+
const { returnValue } = runMiddlewares(this.senderMiddleware, args);
|
|
331
|
+
if (returnValue) return returnValue;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
sendReply(...args: any[]) {
|
|
335
|
+
this.send(...args);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
onMessage(...args: any[]) {
|
|
339
|
+
runMiddlewares(this._onMessageMiddleware, args);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
export default AbstractChannelProtocol;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/MessageChannel
|
|
2
|
+
import {
|
|
3
|
+
IMessageChannel,
|
|
4
|
+
AbstractChannelProtocolProps,
|
|
5
|
+
} from '../types/channel';
|
|
6
|
+
import AbstractChannelProtocol from './AbstractChannelProtocol';
|
|
7
|
+
import { SenderMiddleware, ClientMiddleware } from '../types';
|
|
8
|
+
|
|
9
|
+
export default class RPCMessageChannel
|
|
10
|
+
extends AbstractChannelProtocol
|
|
11
|
+
implements IMessageChannel
|
|
12
|
+
{
|
|
13
|
+
private readonly port: MessagePort;
|
|
14
|
+
private sender: any;
|
|
15
|
+
private targetOrigin: string;
|
|
16
|
+
|
|
17
|
+
constructor(
|
|
18
|
+
options: {
|
|
19
|
+
port: MessagePort;
|
|
20
|
+
sender?: any;
|
|
21
|
+
targetOrigin?: string;
|
|
22
|
+
} & AbstractChannelProtocolProps
|
|
23
|
+
) {
|
|
24
|
+
// Extract channel-specific options and pass the rest to parent
|
|
25
|
+
const {
|
|
26
|
+
port,
|
|
27
|
+
sender = window,
|
|
28
|
+
targetOrigin = '*',
|
|
29
|
+
...protocolOptions
|
|
30
|
+
} = options;
|
|
31
|
+
super(protocolOptions);
|
|
32
|
+
this.port = port;
|
|
33
|
+
this.targetOrigin = targetOrigin;
|
|
34
|
+
this.sender = sender;
|
|
35
|
+
// MessagePort 需要调用 start() 才能开始接收消息(当使用 addEventListener 时)
|
|
36
|
+
if (this.port.start) {
|
|
37
|
+
this.port.start();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
on(listener: (event: MessageEvent) => void): void | (() => void) {
|
|
42
|
+
const f = (ev: MessageEvent): void => {
|
|
43
|
+
listener(ev);
|
|
44
|
+
};
|
|
45
|
+
this.port.addEventListener('message', f);
|
|
46
|
+
return () => this.port.removeEventListener('message', f);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
send(message: any, transfer?: Transferable[]) {
|
|
50
|
+
if (transfer && transfer.length > 0) {
|
|
51
|
+
this.port.postMessage(message, transfer);
|
|
52
|
+
} else {
|
|
53
|
+
this.port.postMessage(message);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Inherits buffer getters from AbstractChannelProtocol
|
|
59
|
+
* Uses BufferFactory with configured serialization format
|
|
60
|
+
*
|
|
61
|
+
* To use a different format, pass serializationFormat in constructor:
|
|
62
|
+
* new RPCMessageChannel({ port, serializationFormat: 'msgpack' })
|
|
63
|
+
*
|
|
64
|
+
* Or override these getters if you need custom buffer logic
|
|
65
|
+
*/
|
|
66
|
+
|
|
67
|
+
decorateSendMiddleware(middlewares: SenderMiddleware[]) {
|
|
68
|
+
return middlewares;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
decorateOnMessageMiddleware(middlewares: ClientMiddleware[]) {
|
|
72
|
+
return middlewares;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
disconnect() {
|
|
76
|
+
if (this.port) {
|
|
77
|
+
this.port.close();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import AbstractChannelProtocol from './AbstractChannelProtocol';
|
|
2
|
+
import {
|
|
3
|
+
SenderMiddleware,
|
|
4
|
+
ClientMiddleware,
|
|
5
|
+
AbstractChannelProtocolProps,
|
|
6
|
+
} from '../types';
|
|
7
|
+
import { normalizeWebSocketRawMessage } from '../middlewares/normalize';
|
|
8
|
+
|
|
9
|
+
export default class WebSocketChannel extends AbstractChannelProtocol {
|
|
10
|
+
private socket: WebSocket;
|
|
11
|
+
readonly name: string;
|
|
12
|
+
private reconnectAttempts: number;
|
|
13
|
+
private maxReconnectAttempts: number;
|
|
14
|
+
private reconnectDelay: number;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param socket Pass the WebSocket instance (client-side) or WebSocket connection (server-side).
|
|
18
|
+
* @param options Configuration options
|
|
19
|
+
*/
|
|
20
|
+
constructor(
|
|
21
|
+
socket: WebSocket,
|
|
22
|
+
options?: {
|
|
23
|
+
name?: string;
|
|
24
|
+
maxReconnectAttempts?: number;
|
|
25
|
+
reconnectDelay?: number;
|
|
26
|
+
connected?: boolean;
|
|
27
|
+
} & AbstractChannelProtocolProps
|
|
28
|
+
) {
|
|
29
|
+
// super() must be the first statement - extract options inline using IIFE
|
|
30
|
+
const opts = options || {};
|
|
31
|
+
const {
|
|
32
|
+
name,
|
|
33
|
+
maxReconnectAttempts,
|
|
34
|
+
reconnectDelay,
|
|
35
|
+
connected,
|
|
36
|
+
...protocolOptions
|
|
37
|
+
} = opts;
|
|
38
|
+
|
|
39
|
+
super({
|
|
40
|
+
connected: connected ?? false,
|
|
41
|
+
...protocolOptions,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Now we can assign to instance properties
|
|
45
|
+
this.socket = socket;
|
|
46
|
+
this.name = name || 'websocket';
|
|
47
|
+
this.reconnectAttempts = 0;
|
|
48
|
+
this.maxReconnectAttempts = maxReconnectAttempts ?? 5;
|
|
49
|
+
this.reconnectDelay = reconnectDelay ?? 1000;
|
|
50
|
+
|
|
51
|
+
// Set up WebSocket event handlers
|
|
52
|
+
this.setupSocketHandlers();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private setupSocketHandlers() {
|
|
56
|
+
this.socket.addEventListener('open', () => {
|
|
57
|
+
// WebSocket 已打开,激活连接
|
|
58
|
+
this.activate();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
this.socket.addEventListener('close', () => {
|
|
62
|
+
// WebSocket 已关闭,断开连接
|
|
63
|
+
super.disconnect();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
this.socket.addEventListener('error', (error) => {
|
|
67
|
+
console.error(`[WebSocketChannel ${this.name}] Error:`, error);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
on(listener: (data: unknown) => void): void | (() => void) {
|
|
72
|
+
// Handle both browser MessageEvent and Node.js ws library format
|
|
73
|
+
const f = (ev: MessageEvent | Buffer | string | any): void => {
|
|
74
|
+
// Debug: log the event structure
|
|
75
|
+
if (ev === undefined || ev === null) {
|
|
76
|
+
console.warn(
|
|
77
|
+
`[WebSocketChannel ${this.name}] Received undefined/null message event`
|
|
78
|
+
);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Pass the raw event/data to listener
|
|
83
|
+
// The normalizeWebSocketRawMessage middleware will handle the conversion
|
|
84
|
+
listener(ev);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// Try addEventListener first (works in both browser and ws library)
|
|
88
|
+
if (typeof this.socket.addEventListener === 'function') {
|
|
89
|
+
this.socket.addEventListener('message', f);
|
|
90
|
+
return () => {
|
|
91
|
+
if (typeof this.socket.removeEventListener === 'function') {
|
|
92
|
+
this.socket.removeEventListener('message', f);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
} else {
|
|
96
|
+
// Fallback to 'on' method for older ws library versions
|
|
97
|
+
// Type assertion for Node.js ws library
|
|
98
|
+
const wsSocket = this.socket as any;
|
|
99
|
+
if (typeof wsSocket.on === 'function') {
|
|
100
|
+
wsSocket.on('message', f);
|
|
101
|
+
return () => {
|
|
102
|
+
if (typeof wsSocket.off === 'function') {
|
|
103
|
+
wsSocket.off('message', f);
|
|
104
|
+
} else if (typeof wsSocket.removeListener === 'function') {
|
|
105
|
+
wsSocket.removeListener('message', f);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// If neither method works, return a no-op cleanup function
|
|
112
|
+
return () => {};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
send(data: unknown): void {
|
|
116
|
+
if (this.socket.readyState === WebSocket.OPEN) {
|
|
117
|
+
// Data should already be serialized (string) by the middleware
|
|
118
|
+
// WebSocket.send() accepts string, ArrayBuffer, or Blob
|
|
119
|
+
if (typeof data === 'string') {
|
|
120
|
+
this.socket.send(data);
|
|
121
|
+
} else if (data instanceof ArrayBuffer || data instanceof Blob) {
|
|
122
|
+
this.socket.send(data);
|
|
123
|
+
} else {
|
|
124
|
+
// Fallback: serialize if not already serialized
|
|
125
|
+
this.socket.send(JSON.stringify(data));
|
|
126
|
+
}
|
|
127
|
+
} else {
|
|
128
|
+
console.warn(
|
|
129
|
+
`[WebSocketChannel ${this.name}] Cannot send: WebSocket is not open. State: ${this.socket.readyState}`
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Inherits buffer getters from AbstractChannelProtocol
|
|
136
|
+
* Uses BufferFactory with configured serialization format
|
|
137
|
+
*
|
|
138
|
+
* To use a different format, pass serializationFormat in constructor:
|
|
139
|
+
* new WebSocketChannel(socket, { serializationFormat: 'msgpack' })
|
|
140
|
+
*
|
|
141
|
+
* Or override these getters if you need custom buffer logic
|
|
142
|
+
*/
|
|
143
|
+
|
|
144
|
+
decorateSendMiddleware(middlewares: SenderMiddleware[]) {
|
|
145
|
+
return middlewares;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
decorateOnMessageMiddleware(middlewares: ClientMiddleware[]) {
|
|
149
|
+
// Replace the first middleware (normalizeMessageChannelRawMessage)
|
|
150
|
+
// with normalizeWebSocketRawMessage to handle both browser and Node.js ws library formats
|
|
151
|
+
// Note: We return the factory function, not the result of calling it
|
|
152
|
+
// because applyOnMessageMiddleware will call fn(this) for each middleware
|
|
153
|
+
if (middlewares.length > 0) {
|
|
154
|
+
return [normalizeWebSocketRawMessage, ...middlewares.slice(1)];
|
|
155
|
+
}
|
|
156
|
+
return middlewares;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
disconnect() {
|
|
160
|
+
if (this.socket) {
|
|
161
|
+
this.socket.close();
|
|
162
|
+
}
|
|
163
|
+
super.disconnect();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Get the current WebSocket ready state
|
|
168
|
+
*/
|
|
169
|
+
get readyState(): number {
|
|
170
|
+
return this.socket.readyState;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Check if WebSocket is open
|
|
175
|
+
*/
|
|
176
|
+
isOpen(): boolean {
|
|
177
|
+
return this.socket.readyState === WebSocket.OPEN;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import AbstractChannelProtocol from './AbstractChannelProtocol';
|
|
2
|
+
import { AbstractChannelProtocolProps } from '../types/channel';
|
|
3
|
+
|
|
4
|
+
export default class WorkerChannel extends AbstractChannelProtocol {
|
|
5
|
+
private worker: any;
|
|
6
|
+
readonly name: string;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param worker Pass the Worker in the main thread.
|
|
10
|
+
* @param options Configuration options including serialization format
|
|
11
|
+
*/
|
|
12
|
+
constructor(
|
|
13
|
+
worker: any,
|
|
14
|
+
options?: {
|
|
15
|
+
name?: string;
|
|
16
|
+
} & AbstractChannelProtocolProps
|
|
17
|
+
) {
|
|
18
|
+
// Extract Worker-specific options and pass the rest to parent
|
|
19
|
+
const { name, ...protocolOptions } = options || {};
|
|
20
|
+
super(protocolOptions);
|
|
21
|
+
this.worker = worker;
|
|
22
|
+
this.name = name || 'worker';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
on(listener: (data: unknown) => void): void | (() => void) {
|
|
26
|
+
const f = (ev: MessageEvent): void => {
|
|
27
|
+
listener(ev);
|
|
28
|
+
};
|
|
29
|
+
this.worker.addEventListener('message', f);
|
|
30
|
+
return () => this.worker.removeEventListener('message', f);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
send(data: unknown): void {
|
|
34
|
+
this.worker.postMessage(data);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export type IMessageChannelOnClose = () => void;
|
|
2
|
+
export type IMessageChannelOnError = () => void;
|
|
3
|
+
export type IMessageChannelOnMessage = (message: any) => void;
|
|
4
|
+
export type IMessageChannelSend = (options: any) => void;
|
|
5
|
+
export type IMessageChannelDisconnect = () => void;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A channel is a bidirectional communications channel
|
|
9
|
+
*/
|
|
10
|
+
export type IMessageChannel = {
|
|
11
|
+
onClose?: IMessageChannelOnClose;
|
|
12
|
+
|
|
13
|
+
onError?: IMessageChannelOnError;
|
|
14
|
+
|
|
15
|
+
onMessage: IMessageChannelOnMessage;
|
|
16
|
+
|
|
17
|
+
send: IMessageChannelSend;
|
|
18
|
+
|
|
19
|
+
disconnect: IMessageChannelDisconnect;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type SendingProps = {
|
|
23
|
+
requestPath: string;
|
|
24
|
+
methodName: string;
|
|
25
|
+
args?: any[];
|
|
26
|
+
isOptionsRequest?: boolean;
|
|
27
|
+
transfer?: MessagePort[];
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Channel protocol configuration options
|
|
32
|
+
*/
|
|
33
|
+
export type AbstractChannelProtocolProps = {
|
|
34
|
+
description?: string;
|
|
35
|
+
masterProcessName?: string;
|
|
36
|
+
connected?: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Serialization format for buffer encoding/decoding
|
|
39
|
+
* @default 'json'
|
|
40
|
+
*/
|
|
41
|
+
serializationFormat?: string;
|
|
42
|
+
/**
|
|
43
|
+
* Custom read buffer instance (overrides serializationFormat)
|
|
44
|
+
*/
|
|
45
|
+
readBuffer?: any;
|
|
46
|
+
/**
|
|
47
|
+
* Custom write buffer instance (overrides serializationFormat)
|
|
48
|
+
*/
|
|
49
|
+
writeBuffer?: any;
|
|
50
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from './channel';
|
|
2
|
+
// export * from './messageChannel';
|
|
3
|
+
export * from './rpc';
|
|
4
|
+
export * from './proxyChannel';
|
|
5
|
+
export * from './proxyService';
|
|
6
|
+
export * from './middleware';
|
|
7
|
+
|
|
8
|
+
export type ClientMiddleware = any;
|
|
9
|
+
export type SenderMiddleware = any;
|