@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/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # @cloudpss/ubrpc
2
+
3
+ Rpc server/client build on websocket and ubjson.
package/dist/auth.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ /// <reference types="ws" />
2
+ import type WebSocket from 'isomorphic-ws';
3
+ import type { ConnectionID, RpcMetadata } from './types/payload.js';
4
+ /** 接受认证消息 */
5
+ export declare function waitAuth(socket: WebSocket, seq?: number, id?: ConnectionID): Promise<[number, ConnectionID, RpcMetadata]>;
package/dist/auth.js ADDED
@@ -0,0 +1,52 @@
1
+ import { decodePayload, deserializeError } from './utils/serialize.js';
2
+ import { VERSION } from './version.js';
3
+ const AUTH_TIMEOUT = 5000;
4
+ /** 接受认证消息 */
5
+ export function waitAuth(socket, seq, id) {
6
+ return new Promise((resolve, reject) => {
7
+ const isResponse = seq != null && id != null;
8
+ const timeout = setTimeout(() => {
9
+ reject(new Error(`Failed to authenticate, no ${isResponse ? 'response' : 'request'} in ${AUTH_TIMEOUT}ms`));
10
+ finalize();
11
+ }, AUTH_TIMEOUT);
12
+ const handleMessage = (ev) => {
13
+ try {
14
+ const payload = decodePayload(ev.data);
15
+ if (!payload || payload.type !== 'auth') {
16
+ if (isResponse)
17
+ return reject(new Error(`Failed to authenticate, bad response from server`));
18
+ else
19
+ return reject(new Error(`Failed to authenticate, bad request from client`));
20
+ }
21
+ if (payload.version !== VERSION) {
22
+ return reject(new Error(`Failed to authenticate, version not match, expected ${VERSION}, got ${payload.version}`));
23
+ }
24
+ if (isResponse) {
25
+ if (payload.seq !== seq) {
26
+ return reject(new Error(`Failed to authenticate, bad response seq from server`));
27
+ }
28
+ if (payload.id !== id) {
29
+ return reject(new Error(`Failed to authenticate, bad response id from server`));
30
+ }
31
+ }
32
+ if (payload.error) {
33
+ return reject(deserializeError(payload.error));
34
+ }
35
+ const metadata = typeof payload.metadata != 'object' ? {} : payload.metadata ?? {};
36
+ resolve([payload.seq, payload.id, metadata]);
37
+ }
38
+ catch (ex) {
39
+ reject(ex);
40
+ }
41
+ finally {
42
+ finalize();
43
+ }
44
+ };
45
+ const finalize = () => {
46
+ clearTimeout(timeout);
47
+ socket.removeListener('message', handleMessage);
48
+ };
49
+ socket.addEventListener('message', handleMessage, { once: true });
50
+ });
51
+ }
52
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACvE,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,MAAM,YAAY,GAAG,IAAI,CAAC;AAE1B,aAAa;AACb,MAAM,UAAU,QAAQ,CACpB,MAAiB,EACjB,GAAY,EACZ,EAAiB;IAEjB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACnC,MAAM,UAAU,GAAG,GAAG,IAAI,IAAI,IAAI,EAAE,IAAI,IAAI,CAAC;QAC7C,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE;YAC5B,MAAM,CAAC,IAAI,KAAK,CAAC,8BAA8B,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,OAAO,YAAY,IAAI,CAAC,CAAC,CAAC;YAC5G,QAAQ,EAAE,CAAC;QACf,CAAC,EAAE,YAAY,CAAC,CAAC;QACjB,MAAM,aAAa,GAAG,CAAC,EAA0B,EAAQ,EAAE;YACvD,IAAI;gBACA,MAAM,OAAO,GAAG,aAAa,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;gBACvC,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM,EAAE;oBACrC,IAAI,UAAU;wBAAE,OAAO,MAAM,CAAC,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAC,CAAC;;wBACxF,OAAO,MAAM,CAAC,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAC,CAAC;iBACpF;gBACD,IAAI,OAAO,CAAC,OAAO,KAAK,OAAO,EAAE;oBAC7B,OAAO,MAAM,CACT,IAAI,KAAK,CACL,uDAAuD,OAAO,SAAS,OAAO,CAAC,OAAO,EAAE,CAC3F,CACJ,CAAC;iBACL;gBACD,IAAI,UAAU,EAAE;oBACZ,IAAI,OAAO,CAAC,GAAG,KAAK,GAAG,EAAE;wBACrB,OAAO,MAAM,CAAC,IAAI,KAAK,CAAC,sDAAsD,CAAC,CAAC,CAAC;qBACpF;oBACD,IAAI,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE;wBACnB,OAAO,MAAM,CAAC,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAC,CAAC;qBACnF;iBACJ;gBACD,IAAI,OAAO,CAAC,KAAK,EAAE;oBACf,OAAO,MAAM,CAAC,gBAAgB,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;iBAClD;gBACD,MAAM,QAAQ,GAAG,OAAO,OAAO,CAAC,QAAQ,IAAI,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,IAAI,EAAE,CAAC;gBACnF,OAAO,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC,CAAC;aAChD;YAAC,OAAO,EAAE,EAAE;gBACT,MAAM,CAAC,EAAE,CAAC,CAAC;aACd;oBAAS;gBACN,QAAQ,EAAE,CAAC;aACd;QACL,CAAC,CAAC;QACF,MAAM,QAAQ,GAAG,GAAS,EAAE;YACxB,YAAY,CAAC,OAAO,CAAC,CAAC;YACtB,MAAM,CAAC,cAAc,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;QACpD,CAAC,CAAC;QACF,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,aAAa,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IACtE,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -0,0 +1,14 @@
1
+ /// <reference types="ws" />
2
+ import WebSocket from 'isomorphic-ws';
3
+ import { RpcSocket } from './socket.js';
4
+ import type { RpcMetadata } from './types/payload.js';
5
+ import type { RpcObject } from './types/utils.js';
6
+ /** 由 WS Client 建立的 RPC 连接 */
7
+ export declare class RpcClientSocket<TRemote extends {}, TLocal extends {}> extends RpcSocket<TRemote, TLocal> {
8
+ constructor(url: string | URL, metadata?: RpcMetadata, local?: RpcObject<TLocal>);
9
+ readonly url: string;
10
+ /** @inheritdoc */
11
+ protected authSocket(): Promise<RpcMetadata>;
12
+ /** @inheritdoc */
13
+ protected onClose(ev: WebSocket.CloseEvent): void;
14
+ }
package/dist/client.js ADDED
@@ -0,0 +1,39 @@
1
+ import WebSocket from 'isomorphic-ws';
2
+ import { v4 } from 'uuid';
3
+ import { waitAuth } from './auth.js';
4
+ import { RpcSocket } from './socket.js';
5
+ import { send } from './utils/messaging.js';
6
+ import { VERSION } from './version.js';
7
+ /** 由 WS Client 建立的 RPC 连接 */
8
+ export class RpcClientSocket extends RpcSocket {
9
+ constructor(url, metadata, local) {
10
+ super(v4(), local);
11
+ this._localMetadata = metadata;
12
+ this.url = typeof url == 'string' ? url : url.href;
13
+ this.seq = 2;
14
+ this.socket = new WebSocket(this.url);
15
+ }
16
+ /** @inheritdoc */
17
+ async authSocket() {
18
+ const socket = this.socket;
19
+ const seq = this.nextSeq();
20
+ send(socket, 'auth', {
21
+ seq,
22
+ id: this.id,
23
+ version: VERSION,
24
+ metadata: this.localMetadata ?? {},
25
+ });
26
+ const [, , metadata] = await waitAuth(socket, seq, this.id);
27
+ return metadata;
28
+ }
29
+ /** @inheritdoc */
30
+ onClose(ev) {
31
+ super.onClose(ev);
32
+ if (ev.code === 1000)
33
+ return;
34
+ setTimeout(() => {
35
+ this.socket = new WebSocket(this.url);
36
+ }, 1000);
37
+ }
38
+ }
39
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,eAAe,CAAC;AACtC,OAAO,EAAE,EAAE,EAAE,MAAM,MAAM,CAAC;AAC1B,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAGxC,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,6BAA6B;AAC7B,MAAM,OAAO,eAAuD,SAAQ,SAA0B;IAClG,YAAY,GAAiB,EAAE,QAAsB,EAAE,KAAyB;QAC5E,KAAK,CAAC,EAAE,EAAkB,EAAE,KAAK,CAAC,CAAC;QACnC,IAAI,CAAC,cAAc,GAAG,QAAQ,CAAC;QAC/B,IAAI,CAAC,GAAG,GAAG,OAAO,GAAG,IAAI,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC;QACnD,IAAI,CAAC,GAAG,GAAG,CAAC,CAAC;QACb,IAAI,CAAC,MAAM,GAAG,IAAI,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC1C,CAAC;IAED,kBAAkB;IACC,KAAK,CAAC,UAAU;QAC/B,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAC3B,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;QAC3B,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE;YACjB,GAAG;YACH,EAAE,EAAE,IAAI,CAAC,EAAE;YACX,OAAO,EAAE,OAAO;YAChB,QAAQ,EAAE,IAAI,CAAC,aAAa,IAAI,EAAE;SACrC,CAAC,CAAC;QACH,MAAM,CAAC,EAAE,AAAD,EAAG,QAAQ,CAAC,GAAG,MAAM,QAAQ,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;QAC5D,OAAO,QAAQ,CAAC;IACpB,CAAC;IACD,kBAAkB;IACC,OAAO,CAAC,EAAwB;QAC/C,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAClB,IAAI,EAAE,CAAC,IAAI,KAAK,IAAI;YAAE,OAAO;QAE7B,UAAU,CAAC,GAAG,EAAE;YACZ,IAAI,CAAC,MAAM,GAAG,IAAI,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC1C,CAAC,EAAE,IAAI,CAAC,CAAC;IACb,CAAC;CACJ"}
@@ -0,0 +1,3 @@
1
+ export { RpcClientSocket } from './client.js';
2
+ export { RpcSocket } from './socket.js';
3
+ export { RpcServer, RpcServerSocket } from './server.js';
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { RpcClientSocket } from './client.js';
2
+ export { RpcSocket } from './socket.js';
3
+ export { RpcServer, RpcServerSocket } from './server.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,44 @@
1
+ /// <reference types="ws" />
2
+ import type WebSocket from 'isomorphic-ws';
3
+ import { RpcSocket } from './socket.js';
4
+ import type { ConnectionID, RpcMetadata } from './types/payload.js';
5
+ import type { RpcObject } from './types/utils.js';
6
+ declare const kOnClose: unique symbol;
7
+ declare const kMetadata: unique symbol;
8
+ declare const kReplaceSocket: unique symbol;
9
+ /** 认证过的 socket */
10
+ type WebSocketWithMetadata = WebSocket & {
11
+ [kMetadata]: RpcMetadata;
12
+ };
13
+ /** 由 WS Server 建立的 RPC 连接组 */
14
+ export declare class RpcServer<TRemote extends {}, TLocal extends {}> {
15
+ /** 认证客户端 */
16
+ readonly authenticator: (metadata: RpcMetadata) => RpcMetadata | Promise<RpcMetadata>;
17
+ constructor(local?: RpcObject<TLocal> | ((socket: RpcServerSocket<TRemote, TLocal>) => RpcObject<TLocal>),
18
+ /** 认证客户端 */
19
+ authenticator?: (metadata: RpcMetadata) => RpcMetadata | Promise<RpcMetadata>);
20
+ /** 用于响应调用的本地对象 */
21
+ readonly local?: (socket: RpcServerSocket<TRemote, TLocal>) => RpcObject<TLocal>;
22
+ readonly sockets: Map<string, RpcServerSocket<TRemote, TLocal>>;
23
+ /** WebSocket 连接后调用此方法建立 RPC 连接 */
24
+ connect(socket: WebSocket): Promise<RpcServerSocket<TRemote, TLocal>>;
25
+ /** 认证客户端 */
26
+ protected authSocket(socket: WebSocket): Promise<[ConnectionID, RpcMetadata]>;
27
+ private readonly disconnectingSockets;
28
+ /** WebSocket 断开时调用 */
29
+ [kOnClose](socket: RpcServerSocket<TRemote, TLocal>, ev: WebSocket.CloseEvent): void;
30
+ }
31
+ /** 由 WS Server 建立的 RPC 连接 */
32
+ export declare class RpcServerSocket<TRemote extends object, TLocal extends object> extends RpcSocket<TRemote, TLocal> {
33
+ readonly server: RpcServer<TRemote, TLocal>;
34
+ constructor(id: ConnectionID, server: RpcServer<TRemote, TLocal>, socket: WebSocketWithMetadata);
35
+ /** @inheritdoc */
36
+ protected authSocket(): Promise<RpcMetadata>;
37
+ /** @inheritdoc */
38
+ protected get local(): RpcObject<TLocal> | undefined;
39
+ /** @inheritdoc */
40
+ protected onClose(ev: WebSocket.CloseEvent): void;
41
+ /** 替换 socket */
42
+ [kReplaceSocket](newSocket: WebSocketWithMetadata): void;
43
+ }
44
+ export {};
package/dist/server.js ADDED
@@ -0,0 +1,119 @@
1
+ import { waitAuth } from './auth.js';
2
+ import { RpcSocket } from './socket.js';
3
+ import { send } from './utils/messaging.js';
4
+ import { serializeError } from './utils/serialize.js';
5
+ import { VERSION } from './version.js';
6
+ const kOnClose = Symbol('kOnClose');
7
+ const kMetadata = Symbol('kMetadata');
8
+ const kReplaceSocket = Symbol('kReplaceSocket');
9
+ /** 由 WS Server 建立的 RPC 连接组 */
10
+ export class RpcServer {
11
+ constructor(local,
12
+ /** 认证客户端 */
13
+ authenticator = () => ({})) {
14
+ this.authenticator = authenticator;
15
+ this.sockets = new Map();
16
+ this.disconnectingSockets = new Map();
17
+ if (local == null) {
18
+ this.local = undefined;
19
+ }
20
+ else if (typeof local == 'function') {
21
+ this.local = local;
22
+ }
23
+ else {
24
+ this.local = () => local;
25
+ }
26
+ }
27
+ /** WebSocket 连接后调用此方法建立 RPC 连接 */
28
+ async connect(socket) {
29
+ const s = socket;
30
+ const [id, metadata] = await this.authSocket(socket);
31
+ s[kMetadata] = metadata;
32
+ const exist = this.sockets.get(id);
33
+ if (!exist) {
34
+ const client = new RpcServerSocket(id, this, s);
35
+ this.sockets.set(id, client);
36
+ return client;
37
+ }
38
+ else {
39
+ const tid = this.disconnectingSockets.get(id);
40
+ if (tid != null) {
41
+ this.disconnectingSockets.delete(id);
42
+ clearTimeout(tid);
43
+ }
44
+ exist[kReplaceSocket](s);
45
+ return exist;
46
+ }
47
+ }
48
+ /** 认证客户端 */
49
+ async authSocket(socket) {
50
+ let seq, id, remoteMetadata;
51
+ try {
52
+ [seq, id, remoteMetadata] = await waitAuth(socket);
53
+ const localMetadata = await this.authenticator(remoteMetadata);
54
+ send(socket, 'auth', {
55
+ seq,
56
+ id,
57
+ version: VERSION,
58
+ metadata: localMetadata,
59
+ });
60
+ return [id, remoteMetadata];
61
+ }
62
+ catch (ex) {
63
+ if (seq != null && id != null) {
64
+ send(socket, 'auth', {
65
+ seq,
66
+ id,
67
+ version: VERSION,
68
+ metadata: {},
69
+ error: serializeError(ex),
70
+ });
71
+ }
72
+ else {
73
+ send(socket, 'error', {
74
+ seq: 1,
75
+ error: serializeError(ex),
76
+ });
77
+ }
78
+ socket.close(3401, 'Auth error');
79
+ throw ex;
80
+ }
81
+ }
82
+ /** WebSocket 断开时调用 */
83
+ [kOnClose](socket, ev) {
84
+ const delay = ev.code === 1000 ? 0 : 5000;
85
+ const tid = setTimeout(() => {
86
+ socket.destroy();
87
+ this.sockets.delete(socket.id);
88
+ this.disconnectingSockets.delete(socket.id);
89
+ }, delay);
90
+ this.disconnectingSockets.set(socket.id, tid);
91
+ }
92
+ }
93
+ /** 由 WS Server 建立的 RPC 连接 */
94
+ export class RpcServerSocket extends RpcSocket {
95
+ constructor(id, server, socket) {
96
+ super(id);
97
+ this.server = server;
98
+ this.seq = 1;
99
+ this.socket = socket;
100
+ }
101
+ /** @inheritdoc */
102
+ authSocket() {
103
+ return Promise.resolve(this.socket[kMetadata]);
104
+ }
105
+ /** @inheritdoc */
106
+ get local() {
107
+ return this.server.local?.(this);
108
+ }
109
+ /** @inheritdoc */
110
+ onClose(ev) {
111
+ super.onClose(ev);
112
+ this.server[kOnClose](this, ev);
113
+ }
114
+ /** 替换 socket */
115
+ [kReplaceSocket](newSocket) {
116
+ this.socket = newSocket;
117
+ }
118
+ }
119
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,65 @@
1
+ /// <reference types="ws" />
2
+ import type WebSocket from 'isomorphic-ws';
3
+ import { Observable } from 'rxjs';
4
+ import type { ConnectionID, RpcCallPayload, RpcMetadata, RpcNotifyPayload, RpcPayload, RpcSubscribePayload } from './types/payload.js';
5
+ import type { Methods, Notifications, RpcObject, RpcParameters, RpcReturns, Subjects } from './types/utils.js';
6
+ /** RPC 连接 */
7
+ export declare abstract class RpcSocket<TRemote extends {}, TLocal extends {}> {
8
+ readonly id: ConnectionID;
9
+ constructor(id: ConnectionID, local?: RpcObject<TLocal>);
10
+ protected _localMetadata?: RpcMetadata;
11
+ protected _remoteMetadata?: RpcMetadata;
12
+ /** 本地认证信息 */
13
+ get localMetadata(): RpcMetadata | undefined;
14
+ /** 远程认证信息 */
15
+ get remoteMetadata(): RpcMetadata | undefined;
16
+ /** 连接是否已认证 */
17
+ get authenticated(): boolean;
18
+ private readonly __local?;
19
+ /** 用于响应调用的本地对象 */
20
+ protected get local(): RpcObject<TLocal> | undefined;
21
+ private __socket?;
22
+ /** 作为底层传输的 WebSocket */
23
+ get socket(): WebSocket;
24
+ protected set socket(value: WebSocket);
25
+ private __readyCallbacks;
26
+ protected ready: Promise<void>;
27
+ private readonly __handlers;
28
+ /** 初始化 WebSocket */
29
+ protected initSocket(oldValue: WebSocket | undefined, newValue: WebSocket): Promise<void>;
30
+ /** 认证 WebSocket */
31
+ protected abstract authSocket(): Promise<RpcMetadata>;
32
+ /** 响应 WebSocket error */
33
+ protected onError(_ev: WebSocket.ErrorEvent): void;
34
+ /** 响应 WebSocket open */
35
+ protected onOpen(_ev: WebSocket.Event): void;
36
+ /** 响应 WebSocket close */
37
+ protected onClose(ev: WebSocket.CloseEvent): void;
38
+ /** 响应 WebSocket message */
39
+ protected onMessage(ev: WebSocket.MessageEvent): void;
40
+ /** 响应 Rpc 消息 */
41
+ protected onPayload(payload: RpcPayload): boolean;
42
+ /** 调用本地方法 */
43
+ protected localCall(payload: RpcCallPayload | RpcNotifyPayload): Promise<void>;
44
+ private readonly localSubscription;
45
+ /** 调用本地方法 */
46
+ protected localSubscribe(payload: RpcSubscribePayload): Promise<void>;
47
+ /** 发送数据 */
48
+ protected sendPayload<T extends RpcPayload['type']>(type: T, info: Omit<RpcPayload & {
49
+ type: T;
50
+ }, 'type'>): Promise<void>;
51
+ /** 序列号 */
52
+ protected seq: number;
53
+ /** 获取下一个序列号 */
54
+ protected nextSeq(): number;
55
+ private readonly pendingCalls;
56
+ /** 调用远程方法 */
57
+ call<TMethod extends Methods<TRemote>>(method: TMethod, ...args: RpcParameters<TRemote[TMethod]>): Promise<RpcReturns<TRemote[TMethod]>>;
58
+ /** 调用远程方法,放弃返回值 */
59
+ notify<TNotification extends Notifications<TRemote>>(method: TNotification, ...args: RpcParameters<TRemote[TNotification]>): void;
60
+ private readonly pendingSubscriptions;
61
+ /** 调用远程订阅 */
62
+ subscribe<TSubject extends Subjects<TRemote>>(method: TSubject, ...args: RpcParameters<TRemote[TSubject]>): Observable<RpcReturns<TRemote[TSubject]>>;
63
+ /** 结束 */
64
+ destroy(): void;
65
+ }
package/dist/socket.js ADDED
@@ -0,0 +1,276 @@
1
+ import { from, Observable } from 'rxjs';
2
+ import { send } from './utils/messaging.js';
3
+ import { decodePayload, deserializeError, serializeError } from './utils/serialize.js';
4
+ import { ready } from './utils/websocket.js';
5
+ /** RPC 连接 */
6
+ export class RpcSocket {
7
+ constructor(id, local) {
8
+ this.id = id;
9
+ this.__handlers = Object.freeze({
10
+ open: (ev) => this.onOpen(ev),
11
+ close: (ev) => this.onClose(ev),
12
+ error: (ev) => this.onError(ev),
13
+ message: (ev) => this.onMessage(ev),
14
+ });
15
+ this.localSubscription = new Map();
16
+ /** 序列号 */
17
+ this.seq = 0;
18
+ this.pendingCalls = new Map();
19
+ this.pendingSubscriptions = new Map();
20
+ this.__local = local;
21
+ this.ready = new Promise((...args) => (this.__readyCallbacks = args));
22
+ }
23
+ /** 本地认证信息 */
24
+ get localMetadata() {
25
+ return this._localMetadata;
26
+ }
27
+ /** 远程认证信息 */
28
+ get remoteMetadata() {
29
+ return this._remoteMetadata;
30
+ }
31
+ /** 连接是否已认证 */
32
+ get authenticated() {
33
+ return this._remoteMetadata != null;
34
+ }
35
+ /** 用于响应调用的本地对象 */
36
+ get local() {
37
+ return this.__local;
38
+ }
39
+ /** 作为底层传输的 WebSocket */
40
+ get socket() {
41
+ if (!this.__socket)
42
+ throw new Error(`Socket not initialized`);
43
+ return this.__socket;
44
+ }
45
+ set socket(value) {
46
+ if (this.__socket === value)
47
+ return;
48
+ this.__socket = value;
49
+ void this.initSocket(this.__socket, value).then(...this.__readyCallbacks);
50
+ }
51
+ /** 初始化 WebSocket */
52
+ async initSocket(oldValue, newValue) {
53
+ try {
54
+ if (oldValue) {
55
+ oldValue.removeEventListener('open', this.__handlers.open);
56
+ oldValue.removeEventListener('close', this.__handlers.close);
57
+ oldValue.removeEventListener('error', this.__handlers.error);
58
+ oldValue.removeEventListener('message', this.__handlers.message);
59
+ }
60
+ await ready(newValue);
61
+ const info = await this.authSocket();
62
+ if (this.__socket === newValue) {
63
+ newValue.addEventListener('open', this.__handlers.open);
64
+ newValue.addEventListener('close', this.__handlers.close);
65
+ newValue.addEventListener('error', this.__handlers.error);
66
+ newValue.addEventListener('message', this.__handlers.message);
67
+ this._remoteMetadata = info ?? {};
68
+ }
69
+ }
70
+ catch (ex) {
71
+ this._remoteMetadata = undefined;
72
+ throw ex;
73
+ }
74
+ }
75
+ /** 响应 WebSocket error */
76
+ onError(_ev) {
77
+ this._remoteMetadata = undefined;
78
+ }
79
+ /** 响应 WebSocket open */
80
+ onOpen(_ev) {
81
+ //
82
+ }
83
+ /** 响应 WebSocket close */
84
+ onClose(ev) {
85
+ this._remoteMetadata = undefined;
86
+ if (ev.code === 1000) {
87
+ this.destroy();
88
+ }
89
+ else {
90
+ this.ready = new Promise((...args) => (this.__readyCallbacks = args));
91
+ }
92
+ }
93
+ /** 响应 WebSocket message */
94
+ onMessage(ev) {
95
+ const payload = decodePayload(ev.data);
96
+ let error;
97
+ if (payload) {
98
+ const handled = this.onPayload(payload);
99
+ if (!handled)
100
+ error = [payload.seq, `Unrecognized message, not handled.`];
101
+ }
102
+ else {
103
+ error = [this.nextSeq(), `Invalid message, unknown format.`];
104
+ }
105
+ if (error) {
106
+ void this.sendPayload('error', {
107
+ seq: error[0],
108
+ error: serializeError(new SyntaxError(error[1])),
109
+ });
110
+ }
111
+ }
112
+ /** 响应 Rpc 消息 */
113
+ onPayload(payload) {
114
+ switch (payload.type) {
115
+ case 'call':
116
+ case 'notify':
117
+ void this.localCall(payload);
118
+ return true;
119
+ case 'return': {
120
+ const pending = this.pendingCalls.get(payload.seq);
121
+ // 即使不存在等待的请求,也认为响应是有效的
122
+ if (!pending)
123
+ return true;
124
+ this.pendingCalls.delete(payload.seq);
125
+ if (payload.error) {
126
+ pending[1](deserializeError(payload.error));
127
+ }
128
+ else {
129
+ pending[0](payload.result);
130
+ }
131
+ return true;
132
+ }
133
+ case 'subscribe':
134
+ void this.localSubscribe(payload);
135
+ return true;
136
+ case 'unsubscribe': {
137
+ const subscription = this.localSubscription.get(payload.seq);
138
+ // 即使不存在对应订阅,也认为响应是有效的
139
+ if (!subscription)
140
+ return true;
141
+ subscription.unsubscribe();
142
+ this.localSubscription.delete(payload.seq);
143
+ return true;
144
+ }
145
+ case 'publish': {
146
+ const subscriber = this.pendingSubscriptions.get(payload.seq);
147
+ // 即使不存在对应订阅,也认为响应是有效的
148
+ if (!subscriber) {
149
+ void this.sendPayload('unsubscribe', { seq: payload.seq });
150
+ return true;
151
+ }
152
+ if (payload.error) {
153
+ subscriber.error(deserializeError(payload.error));
154
+ }
155
+ else if (payload.complete) {
156
+ subscriber.complete();
157
+ }
158
+ else {
159
+ subscriber.next(payload.next);
160
+ }
161
+ return true;
162
+ }
163
+ case 'error':
164
+ return true;
165
+ default:
166
+ return false;
167
+ }
168
+ }
169
+ /** 调用本地方法 */
170
+ async localCall(payload) {
171
+ const noReturn = payload.type === 'notify';
172
+ const method = this.local ? this.local[payload.method] : undefined;
173
+ const seq = payload.seq;
174
+ if (typeof method != 'function') {
175
+ if (noReturn)
176
+ return;
177
+ return this.sendPayload('return', {
178
+ seq,
179
+ error: serializeError(new TypeError(`${payload.method} is not a function`)),
180
+ });
181
+ }
182
+ try {
183
+ const result = await Reflect.apply(method, this.local, payload.args);
184
+ if (noReturn)
185
+ return;
186
+ return this.sendPayload('return', { seq, result });
187
+ }
188
+ catch (ex) {
189
+ if (noReturn)
190
+ return;
191
+ return this.sendPayload('return', { seq, error: serializeError(ex) });
192
+ }
193
+ }
194
+ /** 调用本地方法 */
195
+ async localSubscribe(payload) {
196
+ const method = this.local ? this.local[payload.method] : undefined;
197
+ const seq = payload.seq;
198
+ if (typeof method != 'function') {
199
+ return this.sendPayload('publish', {
200
+ seq,
201
+ error: serializeError(new TypeError(`${payload.method} is not a function`)),
202
+ });
203
+ }
204
+ try {
205
+ const result = from((await Reflect.apply(method, this.local, payload.args)));
206
+ const subscription = result.subscribe({
207
+ next: (value) => {
208
+ void this.sendPayload('publish', { seq, next: value });
209
+ },
210
+ error: (err) => {
211
+ this.localSubscription.delete(payload.seq);
212
+ void this.sendPayload('publish', { seq, error: serializeError(err) });
213
+ },
214
+ complete: () => {
215
+ this.localSubscription.delete(payload.seq);
216
+ void this.sendPayload('publish', { seq, complete: true });
217
+ },
218
+ });
219
+ this.localSubscription.set(seq, subscription);
220
+ }
221
+ catch (ex) {
222
+ return this.sendPayload('publish', { seq, error: serializeError(ex) });
223
+ }
224
+ }
225
+ /** 发送数据 */
226
+ async sendPayload(type, info) {
227
+ await this.ready;
228
+ send(this.socket, type, info);
229
+ }
230
+ /** 获取下一个序列号 */
231
+ nextSeq() {
232
+ const seq = this.seq;
233
+ this.seq += 2;
234
+ return seq;
235
+ }
236
+ /** 调用远程方法 */
237
+ call(method, ...args) {
238
+ return new Promise((resolve, reject) => {
239
+ const seq = this.nextSeq();
240
+ void this.sendPayload('call', { seq, method, args });
241
+ this.pendingCalls.set(seq, [resolve, reject]);
242
+ });
243
+ }
244
+ /** 调用远程方法,放弃返回值 */
245
+ notify(method, ...args) {
246
+ const seq = this.nextSeq();
247
+ void this.sendPayload('notify', { seq, method, args });
248
+ }
249
+ /** 调用远程订阅 */
250
+ subscribe(method, ...args) {
251
+ return new Observable((subscriber) => {
252
+ const seq = this.nextSeq();
253
+ void this.sendPayload('subscribe', { seq, method, args });
254
+ this.pendingSubscriptions.set(seq, subscriber);
255
+ return () => {
256
+ this.pendingSubscriptions.delete(seq);
257
+ void this.sendPayload('unsubscribe', { seq });
258
+ };
259
+ });
260
+ }
261
+ /** 结束 */
262
+ destroy() {
263
+ if (this.__socket &&
264
+ (this.__socket.readyState === this.__socket.CONNECTING || this.__socket.readyState === this.__socket.OPEN)) {
265
+ this.__socket.close(1000);
266
+ this.__socket = undefined;
267
+ }
268
+ this.localSubscription.forEach((s) => s.unsubscribe());
269
+ this.localSubscription.clear();
270
+ this.pendingCalls.forEach(([, reject]) => reject(new Error(`RPC Socket closed.`)));
271
+ this.pendingCalls.clear();
272
+ this.pendingSubscriptions.forEach((s) => s.error(new Error(`RPC Socket closed.`)));
273
+ this.pendingSubscriptions.clear();
274
+ }
275
+ }
276
+ //# sourceMappingURL=socket.js.map