@cloudpss/ubrpc 0.4.10

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.
Files changed (50) hide show
  1. package/README.md +3 -0
  2. package/dist/auth.d.ts +5 -0
  3. package/dist/auth.js +52 -0
  4. package/dist/auth.js.map +1 -0
  5. package/dist/client.d.ts +14 -0
  6. package/dist/client.js +39 -0
  7. package/dist/client.js.map +1 -0
  8. package/dist/index.d.ts +3 -0
  9. package/dist/index.js +4 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/server.d.ts +44 -0
  12. package/dist/server.js +119 -0
  13. package/dist/server.js.map +1 -0
  14. package/dist/socket.d.ts +65 -0
  15. package/dist/socket.js +276 -0
  16. package/dist/socket.js.map +1 -0
  17. package/dist/types/payload.d.ts +99 -0
  18. package/dist/types/payload.js +2 -0
  19. package/dist/types/payload.js.map +1 -0
  20. package/dist/types/utils.d.ts +32 -0
  21. package/dist/types/utils.js +22 -0
  22. package/dist/types/utils.js.map +1 -0
  23. package/dist/utils/messaging.d.ts +7 -0
  24. package/dist/utils/messaging.js +9 -0
  25. package/dist/utils/messaging.js.map +1 -0
  26. package/dist/utils/serialize.d.ts +9 -0
  27. package/dist/utils/serialize.js +73 -0
  28. package/dist/utils/serialize.js.map +1 -0
  29. package/dist/utils/websocket.d.ts +4 -0
  30. package/dist/utils/websocket.js +21 -0
  31. package/dist/utils/websocket.js.map +1 -0
  32. package/dist/version.d.ts +1 -0
  33. package/dist/version.js +2 -0
  34. package/dist/version.js.map +1 -0
  35. package/jest.config.js +3 -0
  36. package/package.json +45 -0
  37. package/src/auth.ts +59 -0
  38. package/src/client.ts +42 -0
  39. package/src/index.ts +3 -0
  40. package/src/server.ts +127 -0
  41. package/src/socket.ts +304 -0
  42. package/src/types/payload.ts +115 -0
  43. package/src/types/utils.ts +83 -0
  44. package/src/utils/messaging.ts +17 -0
  45. package/src/utils/serialize.ts +67 -0
  46. package/src/utils/websocket.ts +22 -0
  47. package/src/version.ts +1 -0
  48. package/tests/client.js +20 -0
  49. package/tests/server.js +44 -0
  50. package/yarn-error.log +4598 -0
package/src/socket.ts ADDED
@@ -0,0 +1,304 @@
1
+ import type WebSocket from 'isomorphic-ws';
2
+ import { from, Observable, Subscriber, Subscription } from 'rxjs';
3
+ import type {
4
+ ConnectionID,
5
+ RpcCallPayload,
6
+ RpcMetadata,
7
+ RpcNotifyPayload,
8
+ RpcPayload,
9
+ RpcSubscribePayload,
10
+ } from './types/payload.js';
11
+ import type {
12
+ Methods,
13
+ Notifications,
14
+ ObservableLike,
15
+ RpcObject,
16
+ RpcParameters,
17
+ RpcReturns,
18
+ Subjects,
19
+ } from './types/utils.js';
20
+ import { send } from './utils/messaging.js';
21
+ import { decodePayload, deserializeError, serializeError } from './utils/serialize.js';
22
+ import { ready } from './utils/websocket.js';
23
+
24
+ /** RPC 连接 */
25
+ export abstract class RpcSocket<TRemote extends {}, TLocal extends {}> {
26
+ constructor(readonly id: ConnectionID, local?: RpcObject<TLocal>) {
27
+ this.__local = local;
28
+ this.ready = new Promise((...args) => (this.__readyCallbacks = args));
29
+ }
30
+ protected _localMetadata?: RpcMetadata;
31
+ protected _remoteMetadata?: RpcMetadata;
32
+ /** 本地认证信息 */
33
+ get localMetadata(): RpcMetadata | undefined {
34
+ return this._localMetadata;
35
+ }
36
+ /** 远程认证信息 */
37
+ get remoteMetadata(): RpcMetadata | undefined {
38
+ return this._remoteMetadata;
39
+ }
40
+ /** 连接是否已认证 */
41
+ get authenticated(): boolean {
42
+ return this._remoteMetadata != null;
43
+ }
44
+ private readonly __local?: RpcObject<TLocal>;
45
+ /** 用于响应调用的本地对象 */
46
+ protected get local(): RpcObject<TLocal> | undefined {
47
+ return this.__local;
48
+ }
49
+ private __socket?: WebSocket;
50
+ /** 作为底层传输的 WebSocket */
51
+ get socket(): WebSocket {
52
+ if (!this.__socket) throw new Error(`Socket not initialized`);
53
+ return this.__socket;
54
+ }
55
+ protected set socket(value: WebSocket) {
56
+ if (this.__socket === value) return;
57
+ this.__socket = value;
58
+ void this.initSocket(this.__socket, value).then(...this.__readyCallbacks);
59
+ }
60
+ private __readyCallbacks!: Parameters<ConstructorParameters<typeof Promise<void>>[0]>;
61
+ protected ready: Promise<void>;
62
+ private readonly __handlers = Object.freeze({
63
+ open: (ev: WebSocket.Event) => this.onOpen(ev),
64
+ close: (ev: WebSocket.CloseEvent) => this.onClose(ev),
65
+ error: (ev: WebSocket.ErrorEvent) => this.onError(ev),
66
+ message: (ev: WebSocket.MessageEvent) => this.onMessage(ev),
67
+ } as const);
68
+ /** 初始化 WebSocket */
69
+ protected async initSocket(oldValue: WebSocket | undefined, newValue: WebSocket): Promise<void> {
70
+ try {
71
+ if (oldValue) {
72
+ oldValue.removeEventListener('open', this.__handlers.open);
73
+ oldValue.removeEventListener('close', this.__handlers.close);
74
+ oldValue.removeEventListener('error', this.__handlers.error);
75
+ oldValue.removeEventListener('message', this.__handlers.message);
76
+ }
77
+ await ready(newValue);
78
+ const info = await this.authSocket();
79
+ if (this.__socket === newValue) {
80
+ newValue.addEventListener('open', this.__handlers.open);
81
+ newValue.addEventListener('close', this.__handlers.close);
82
+ newValue.addEventListener('error', this.__handlers.error);
83
+ newValue.addEventListener('message', this.__handlers.message);
84
+ this._remoteMetadata = info ?? {};
85
+ }
86
+ } catch (ex) {
87
+ this._remoteMetadata = undefined;
88
+ throw ex;
89
+ }
90
+ }
91
+ /** 认证 WebSocket */
92
+ protected abstract authSocket(): Promise<RpcMetadata>;
93
+ /** 响应 WebSocket error */
94
+ protected onError(_ev: WebSocket.ErrorEvent): void {
95
+ this._remoteMetadata = undefined;
96
+ }
97
+ /** 响应 WebSocket open */
98
+ protected onOpen(_ev: WebSocket.Event): void {
99
+ //
100
+ }
101
+ /** 响应 WebSocket close */
102
+ protected onClose(ev: WebSocket.CloseEvent): void {
103
+ this._remoteMetadata = undefined;
104
+ if (ev.code === 1000) {
105
+ this.destroy();
106
+ } else {
107
+ this.ready = new Promise((...args) => (this.__readyCallbacks = args));
108
+ }
109
+ }
110
+ /** 响应 WebSocket message */
111
+ protected onMessage(ev: WebSocket.MessageEvent): void {
112
+ const payload = decodePayload(ev.data);
113
+ let error;
114
+ if (payload) {
115
+ const handled = this.onPayload(payload);
116
+ if (!handled) error = [payload.seq, `Unrecognized message, not handled.`] as const;
117
+ } else {
118
+ error = [this.nextSeq(), `Invalid message, unknown format.`] as const;
119
+ }
120
+ if (error) {
121
+ void this.sendPayload('error', {
122
+ seq: error[0],
123
+ error: serializeError(new SyntaxError(error[1])),
124
+ });
125
+ }
126
+ }
127
+
128
+ /** 响应 Rpc 消息 */
129
+ protected onPayload(payload: RpcPayload): boolean {
130
+ switch (payload.type) {
131
+ case 'call':
132
+ case 'notify':
133
+ void this.localCall(payload);
134
+ return true;
135
+ case 'return': {
136
+ const pending = this.pendingCalls.get(payload.seq);
137
+ // 即使不存在等待的请求,也认为响应是有效的
138
+ if (!pending) return true;
139
+ this.pendingCalls.delete(payload.seq);
140
+ if (payload.error) {
141
+ pending[1](deserializeError(payload.error));
142
+ } else {
143
+ pending[0](payload.result);
144
+ }
145
+ return true;
146
+ }
147
+ case 'subscribe':
148
+ void this.localSubscribe(payload);
149
+ return true;
150
+ case 'unsubscribe': {
151
+ const subscription = this.localSubscription.get(payload.seq);
152
+ // 即使不存在对应订阅,也认为响应是有效的
153
+ if (!subscription) return true;
154
+ subscription.unsubscribe();
155
+ this.localSubscription.delete(payload.seq);
156
+ return true;
157
+ }
158
+ case 'publish': {
159
+ const subscriber = this.pendingSubscriptions.get(payload.seq);
160
+ // 即使不存在对应订阅,也认为响应是有效的
161
+ if (!subscriber) {
162
+ void this.sendPayload('unsubscribe', { seq: payload.seq });
163
+ return true;
164
+ }
165
+ if (payload.error) {
166
+ subscriber.error(deserializeError(payload.error));
167
+ } else if (payload.complete) {
168
+ subscriber.complete();
169
+ } else {
170
+ subscriber.next(payload.next);
171
+ }
172
+ return true;
173
+ }
174
+ case 'error':
175
+ return true;
176
+ default:
177
+ return false;
178
+ }
179
+ }
180
+ /** 调用本地方法 */
181
+ protected async localCall(payload: RpcCallPayload | RpcNotifyPayload): Promise<void> {
182
+ const noReturn = payload.type === 'notify';
183
+ const method = this.local ? this.local[payload.method as Methods<TLocal>] : undefined;
184
+ const seq = payload.seq;
185
+ if (typeof method != 'function') {
186
+ if (noReturn) return;
187
+ return this.sendPayload('return', {
188
+ seq,
189
+ error: serializeError(new TypeError(`${payload.method} is not a function`)),
190
+ });
191
+ }
192
+ try {
193
+ const result: unknown = await Reflect.apply(method, this.local, payload.args);
194
+ if (noReturn) return;
195
+ return this.sendPayload('return', { seq, result });
196
+ } catch (ex) {
197
+ if (noReturn) return;
198
+ return this.sendPayload('return', { seq, error: serializeError(ex) });
199
+ }
200
+ }
201
+ private readonly localSubscription = new Map<number, Subscription>();
202
+ /** 调用本地方法 */
203
+ protected async localSubscribe(payload: RpcSubscribePayload): Promise<void> {
204
+ const method = this.local ? this.local[payload.method as Subjects<TLocal>] : undefined;
205
+ const seq = payload.seq;
206
+ if (typeof method != 'function') {
207
+ return this.sendPayload('publish', {
208
+ seq,
209
+ error: serializeError(new TypeError(`${payload.method} is not a function`)),
210
+ });
211
+ }
212
+ try {
213
+ const result = from((await Reflect.apply(method, this.local, payload.args)) as ObservableLike<unknown>);
214
+ const subscription = result.subscribe({
215
+ next: (value) => {
216
+ void this.sendPayload('publish', { seq, next: value });
217
+ },
218
+ error: (err) => {
219
+ this.localSubscription.delete(payload.seq);
220
+ void this.sendPayload('publish', { seq, error: serializeError(err) });
221
+ },
222
+ complete: () => {
223
+ this.localSubscription.delete(payload.seq);
224
+ void this.sendPayload('publish', { seq, complete: true });
225
+ },
226
+ });
227
+ this.localSubscription.set(seq, subscription);
228
+ } catch (ex) {
229
+ return this.sendPayload('publish', { seq, error: serializeError(ex) });
230
+ }
231
+ }
232
+ /** 发送数据 */
233
+ protected async sendPayload<T extends RpcPayload['type']>(
234
+ type: T,
235
+ info: Omit<RpcPayload & { type: T }, 'type'>,
236
+ ): Promise<void> {
237
+ await this.ready;
238
+ send(this.socket, type, info);
239
+ }
240
+ /** 序列号 */
241
+ protected seq = 0;
242
+ /** 获取下一个序列号 */
243
+ protected nextSeq(): number {
244
+ const seq = this.seq;
245
+ this.seq += 2;
246
+ return seq;
247
+ }
248
+ private readonly pendingCalls = new Map<
249
+ number,
250
+ [resolve: (result: unknown) => void, reject: (error: Error) => void]
251
+ >();
252
+ /** 调用远程方法 */
253
+ call<TMethod extends Methods<TRemote>>(
254
+ method: TMethod,
255
+ ...args: RpcParameters<TRemote[TMethod]>
256
+ ): Promise<RpcReturns<TRemote[TMethod]>> {
257
+ return new Promise((resolve, reject) => {
258
+ const seq = this.nextSeq();
259
+ void this.sendPayload('call', { seq, method, args });
260
+ this.pendingCalls.set(seq, [resolve as (result: unknown) => void, reject]);
261
+ });
262
+ }
263
+ /** 调用远程方法,放弃返回值 */
264
+ notify<TNotification extends Notifications<TRemote>>(
265
+ method: TNotification,
266
+ ...args: RpcParameters<TRemote[TNotification]>
267
+ ): void {
268
+ const seq = this.nextSeq();
269
+ void this.sendPayload('notify', { seq, method, args });
270
+ }
271
+
272
+ private readonly pendingSubscriptions = new Map<number, Subscriber<unknown>>();
273
+ /** 调用远程订阅 */
274
+ subscribe<TSubject extends Subjects<TRemote>>(
275
+ method: TSubject,
276
+ ...args: RpcParameters<TRemote[TSubject]>
277
+ ): Observable<RpcReturns<TRemote[TSubject]>> {
278
+ return new Observable((subscriber) => {
279
+ const seq = this.nextSeq();
280
+ void this.sendPayload('subscribe', { seq, method, args });
281
+ this.pendingSubscriptions.set(seq, subscriber);
282
+ return () => {
283
+ this.pendingSubscriptions.delete(seq);
284
+ void this.sendPayload('unsubscribe', { seq });
285
+ };
286
+ });
287
+ }
288
+ /** 结束 */
289
+ destroy(): void {
290
+ if (
291
+ this.__socket &&
292
+ (this.__socket.readyState === this.__socket.CONNECTING || this.__socket.readyState === this.__socket.OPEN)
293
+ ) {
294
+ this.__socket.close(1000);
295
+ this.__socket = undefined;
296
+ }
297
+ this.localSubscription.forEach((s) => s.unsubscribe());
298
+ this.localSubscription.clear();
299
+ this.pendingCalls.forEach(([, reject]) => reject(new Error(`RPC Socket closed.`)));
300
+ this.pendingCalls.clear();
301
+ this.pendingSubscriptions.forEach((s) => s.error(new Error(`RPC Socket closed.`)));
302
+ this.pendingSubscriptions.clear();
303
+ }
304
+ }
@@ -0,0 +1,115 @@
1
+ /** RPC 连接 ID */
2
+ export type ConnectionID = string & { tag: 'ConnectionID' };
3
+ /** 认证信息 */
4
+ export type RpcMetadata = Record<string, unknown>;
5
+
6
+ /** RPC 错误 */
7
+ export interface RpcErrorLike {
8
+ /** 名称 */
9
+ name: string;
10
+ /** 消息 */
11
+ message: string;
12
+ /** 调用 */
13
+ stack?: string;
14
+ /** 引发错误的其他错误 */
15
+ errors?: RpcErrorLike[];
16
+ }
17
+
18
+ /** RPC 调用的通用属性 */
19
+ interface RpcPayloadBase {
20
+ /** 调用类型 */
21
+ type: string;
22
+ /** 序列号 */
23
+ seq: number;
24
+ }
25
+
26
+ /** RPC 认证调用 */
27
+ export interface RpcAuthPayload extends RpcPayloadBase {
28
+ /** @inheritdoc */
29
+ type: 'auth';
30
+ /** 连接 ID */
31
+ id: ConnectionID;
32
+ /** 协议版本 */
33
+ version: number;
34
+ /** 认证信息 */
35
+ metadata: RpcMetadata;
36
+ /** 异常 */
37
+ error?: RpcErrorLike;
38
+ }
39
+
40
+ /** RPC 方法调用 */
41
+ export interface RpcCallPayload extends RpcPayloadBase {
42
+ /** @inheritdoc */
43
+ type: 'call';
44
+ /** 调用的方法 */
45
+ method: string;
46
+ /** 参数 */
47
+ args: unknown[];
48
+ }
49
+
50
+ /** RPC 方法返回 */
51
+ export interface RpcReturnPayload extends RpcPayloadBase {
52
+ /** @inheritdoc */
53
+ type: 'return';
54
+ /** 返回值 */
55
+ result?: unknown;
56
+ /** 异常 */
57
+ error?: RpcErrorLike;
58
+ }
59
+
60
+ /** RPC 通知 */
61
+ export interface RpcNotifyPayload extends RpcPayloadBase {
62
+ /** @inheritdoc */
63
+ type: 'notify';
64
+ /** 调用的方法 */
65
+ method: string;
66
+ /** 参数 */
67
+ args: unknown[];
68
+ }
69
+
70
+ /** RPC 订阅 */
71
+ export interface RpcSubscribePayload extends RpcPayloadBase {
72
+ /** @inheritdoc */
73
+ type: 'subscribe';
74
+ /** 调用的方法 */
75
+ method: string;
76
+ /** 参数 */
77
+ args: unknown[];
78
+ }
79
+
80
+ /** RPC 发布 */
81
+ export interface RpcPublishPayload extends RpcPayloadBase {
82
+ /** @inheritdoc */
83
+ type: 'publish';
84
+ /** 发布的值 */
85
+ next?: unknown;
86
+ /** 异常结束 */
87
+ error?: RpcErrorLike;
88
+ /** 正常结束 */
89
+ complete?: true;
90
+ }
91
+
92
+ /** RPC 取消订阅 */
93
+ export interface RpcUnsubscribePayload extends RpcPayloadBase {
94
+ /** @inheritdoc */
95
+ type: 'unsubscribe';
96
+ }
97
+
98
+ /** RPC 错误 */
99
+ export interface RpcErrorPayload extends RpcPayloadBase {
100
+ /** @inheritdoc */
101
+ type: 'error';
102
+ /** 异常 */
103
+ error: RpcErrorLike;
104
+ }
105
+
106
+ /** RPC 调用 */
107
+ export type RpcPayload =
108
+ | RpcCallPayload
109
+ | RpcReturnPayload
110
+ | RpcNotifyPayload
111
+ | RpcPublishPayload
112
+ | RpcSubscribePayload
113
+ | RpcUnsubscribePayload
114
+ | RpcAuthPayload
115
+ | RpcErrorPayload;
@@ -0,0 +1,83 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import type { InteropObservable, Observable } from 'rxjs';
3
+
4
+ /** 可转换为 Observable 的类型 */
5
+ export type ObservableLike<T> = Observable<T> | InteropObservable<T> | AsyncIterable<T>;
6
+
7
+ /** Returns a boolean for whether the two given types are equal. */
8
+ type IsEqual<A, B> = (<G>() => G extends A ? 1 : 2) extends <G>() => G extends B ? 1 : 2 ? true : false;
9
+
10
+ /** 作为通知输出的方法 */
11
+ type IsNotificationReturn<R> = IsEqual<R, void> extends true
12
+ ? true
13
+ : IsEqual<R, undefined> extends true
14
+ ? true
15
+ : R extends PromiseLike<void> | void
16
+ ? true
17
+ : false;
18
+
19
+ /** 作为 Observable 输出的方法 */
20
+ type ObservableReturn = ObservableLike<any> | PromiseLike<ObservableLike<any>>;
21
+
22
+ /** 提取类型中的方法 */
23
+ export type Methods<T> = {
24
+ [P in keyof T]: T[P] extends (...args: any[]) => infer R ? (R extends ObservableReturn ? never : P) : never;
25
+ }[keyof T] &
26
+ string;
27
+ /** 提取类型中的通知 */
28
+ export type Notifications<T> = {
29
+ [P in keyof T]: T[P] extends (...args: any[]) => infer R
30
+ ? IsNotificationReturn<R> extends true
31
+ ? P
32
+ : never
33
+ : never;
34
+ }[keyof T] &
35
+ string;
36
+ /** 提取类型中的订阅主题 */
37
+ export type Subjects<T> = {
38
+ [P in keyof T]: T[P] extends (...args: any[]) => ObservableReturn ? P : never;
39
+ }[keyof T] &
40
+ string;
41
+
42
+ /** RPC 调用的参数 */
43
+ export type RpcParameters<T> = T extends (...args: infer R) => any ? R : never;
44
+ /** RPC 调用的返回 */
45
+ export type RpcReturns<T> = T extends (...args: any[]) => infer R
46
+ ? Awaited<R> extends ObservableLike<infer U>
47
+ ? U
48
+ : Awaited<R>
49
+ : never;
50
+
51
+ /** RPC 调用函数的实现 */
52
+ type RpcField<T> = T extends (...args: infer P) => infer R
53
+ ? Awaited<R> extends ObservableReturn
54
+ ? (...args: P) => ObservableLike<RpcReturns<T>> | PromiseLike<ObservableLike<RpcReturns<T>>>
55
+ : (...args: P) => RpcReturns<T> | PromiseLike<RpcReturns<T>>
56
+ : never;
57
+
58
+ /** RPC 调用对象的实现 */
59
+ export type RpcObject<T> = {
60
+ [K in Methods<T> | Subjects<T>]: RpcField<T[K]>;
61
+ };
62
+
63
+ // interface A {
64
+ // field: number;
65
+ // methodA(x: number): number | undefined;
66
+ // methodB: (x: string, y: number) => Promise<number>;
67
+ // readonly methodC: () => Iterable<number>;
68
+ // readonly methodD: () => void | Promise<null | undefined>;
69
+ // readonly notificationA: () => undefined;
70
+ // readonly notificationB: () => Promise<void>;
71
+ // notificationC(): void;
72
+ // readonly notificationD: () => void | PromiseLike<undefined>;
73
+ // readonly notificationE: () => void | Promise<void>;
74
+ // subjectA(x: number): Observable<number>;
75
+ // subjectB: (x: string) => Promise<Observable<number>>;
76
+ // subjectC: () => PromiseLike<InteropObservable<number>>;
77
+ // subjectD: () => AsyncGenerator<number>;
78
+ // subjectE: () => PromiseLike<AsyncGenerator<number>>;
79
+ // }
80
+
81
+ // type X = Methods<A>;
82
+ // type Y = Subjects<A>;
83
+ // type Z = Notifications<A>;
@@ -0,0 +1,17 @@
1
+ import type WebSocket from 'isomorphic-ws';
2
+ import { encode } from '@cloudpss/ubjson';
3
+ import type { RpcPayload } from '../types/payload.js';
4
+
5
+ /** 发送消息 */
6
+ export function send<T extends RpcPayload['type']>(
7
+ socket: WebSocket,
8
+ type: T,
9
+ info: Omit<RpcPayload & { type: T }, 'type'>,
10
+ ): void {
11
+ socket.send(
12
+ encode({
13
+ ...info,
14
+ type,
15
+ }),
16
+ );
17
+ }
@@ -0,0 +1,67 @@
1
+ import { decode } from '@cloudpss/ubjson';
2
+ import type WebSocket from 'isomorphic-ws';
3
+ import type { RpcErrorLike, RpcPayload } from '../types/payload.js';
4
+
5
+ /** 反序列化 */
6
+ export function decodePayload(data: WebSocket.Data): RpcPayload | undefined {
7
+ if (!data || typeof data == 'string') return undefined;
8
+ if (Array.isArray(data)) data = Buffer.concat(data);
9
+ try {
10
+ const payload = decode(data) as RpcPayload;
11
+ if (!payload || typeof payload != 'object') return undefined;
12
+ if (typeof payload.type !== 'string') return undefined;
13
+ if (typeof payload.seq !== 'number') return undefined;
14
+ return payload;
15
+ } catch (ex) {
16
+ return undefined;
17
+ }
18
+ }
19
+
20
+ const serializing = Symbol('serializing error');
21
+ /** 序列化错误信息 */
22
+ export function serializeError(error: unknown): RpcErrorLike {
23
+ if (error == null)
24
+ return {
25
+ name: 'Error',
26
+ message: '',
27
+ };
28
+ if (typeof error != 'object')
29
+ return {
30
+ name: 'Error',
31
+ message: String(error),
32
+ };
33
+ try {
34
+ const reenter = !!(error as { [serializing]?: true })[serializing];
35
+ (error as { [serializing]?: true })[serializing] = true;
36
+ const { message, stack, name, errors, ...rest } = error as AggregateError;
37
+ return {
38
+ ...rest,
39
+ name: String(name ?? 'Error'),
40
+ message: String(message ?? ''),
41
+ stack: stack,
42
+ errors: reenter ? undefined : Array.isArray(errors) ? errors.map(serializeError) : errors,
43
+ };
44
+ } finally {
45
+ delete (error as { [serializing]?: true })[serializing];
46
+ }
47
+ }
48
+ /** 反序列化错误信息 */
49
+ export function deserializeError(error: RpcErrorLike): Error {
50
+ if (error == null || typeof error != 'object') return new Error(error ?? '');
51
+ const { message, stack, name, errors, ...rest } = error;
52
+ let ex;
53
+ if (Array.isArray(errors)) {
54
+ ex = new AggregateError(errors.map(deserializeError), message);
55
+ } else {
56
+ ex = new Error(message);
57
+ }
58
+ if (name !== ex?.name)
59
+ Object.defineProperty(ex, 'name', { value: String(name), configurable: true, writable: true });
60
+ Object.defineProperty(ex, 'stack', {
61
+ value: stack ? String(stack) : undefined,
62
+ configurable: true,
63
+ writable: true,
64
+ });
65
+ Object.assign(ex, rest);
66
+ return ex;
67
+ }
@@ -0,0 +1,22 @@
1
+ import type WebSocket from 'isomorphic-ws';
2
+
3
+ /** 等待连接建立 */
4
+ export function ready(socket: WebSocket): Promise<void> {
5
+ switch (socket.readyState) {
6
+ case socket.CONNECTING:
7
+ return new Promise((resolve, reject) => {
8
+ socket.addEventListener('open', () => resolve(), { once: true });
9
+ socket.addEventListener('error', (ev) => reject(ev.error ?? new Error(`Failed to connect`)), {
10
+ once: true,
11
+ });
12
+ });
13
+ case socket.OPEN:
14
+ return Promise.resolve();
15
+ case socket.CLOSING:
16
+ return Promise.reject(new Error(`Socket is closing.`));
17
+ case socket.CLOSED:
18
+ return Promise.reject(new Error(`Socket is closed.`));
19
+ default:
20
+ return Promise.reject(new Error(`Socket is in unknown state.`));
21
+ }
22
+ }
package/src/version.ts ADDED
@@ -0,0 +1 @@
1
+ export const VERSION = 1;
@@ -0,0 +1,20 @@
1
+ /* eslint-disable no-console */
2
+ import { RpcClientSocket } from '@cloudpss/ubrpc';
3
+ import { setTimeout } from 'node:timers/promises';
4
+ import { WebSocket } from 'ws';
5
+
6
+ const rpc = new RpcClientSocket('ws://localhost:8090');
7
+ //await setTimeout(1000);
8
+ const result = await rpc.call('hello', 'world');
9
+ console.log(result);
10
+
11
+ const s = rpc.subscribe('timer', 1000).subscribe({
12
+ next: console.log,
13
+ complete: () => console.log('complete'),
14
+ error: () => console.log('error'),
15
+ });
16
+ await setTimeout(1000);
17
+ s.unsubscribe();
18
+ rpc.socket.close(3600);
19
+
20
+ new WebSocket('ws://localhost:8090');
@@ -0,0 +1,44 @@
1
+ /* eslint-disable no-console */
2
+ import { RpcServer } from '@cloudpss/ubrpc';
3
+ import { interval, take, tap } from 'rxjs';
4
+ import { WebSocketServer } from 'ws';
5
+ const a = {
6
+ greetings: 'Hello, ',
7
+ /**
8
+ *
9
+ * @param {string} name
10
+ * @returns {Promise<string>}
11
+ */
12
+ async hello(name) {
13
+ return `${this.greetings}${name}`;
14
+ },
15
+ timer(i) {
16
+ return interval(i).pipe(
17
+ take(10),
18
+ tap({
19
+ next: console.log,
20
+ complete: () => console.log('complete'),
21
+ error: () => console.log('error'),
22
+ subscribe: () => console.log('subscribe'),
23
+ unsubscribe: () => console.log('unsubscribe'),
24
+ finalize: () => console.log('finalize'),
25
+ }),
26
+ );
27
+ },
28
+ };
29
+ const rpc = new RpcServer(a, (meta) => {
30
+ console.log(meta);
31
+ throw new Error('B');
32
+ });
33
+ const server = new WebSocketServer({
34
+ host: 'localhost',
35
+ port: 8090,
36
+ path: '/',
37
+ });
38
+ server.on('connection', async (socket) => {
39
+ try {
40
+ await rpc.connect(socket);
41
+ } catch (ex) {
42
+ console.log(ex);
43
+ }
44
+ });