@orderly.network/net 1.0.1

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.
@@ -0,0 +1,329 @@
1
+ import { type WebSocketSubject, webSocket } from "rxjs/webSocket";
2
+
3
+ import { WS_URL } from "./contants";
4
+ import { Observable, Observer, Subject, tap } from "rxjs";
5
+ import { messageHandlers } from "@/ws/handler/handler";
6
+
7
+ export type NetworkId = "testnet" | "mainnet";
8
+
9
+ export type WSOptions = {
10
+ url?: string;
11
+ networkId?: NetworkId;
12
+ accountId?: string;
13
+
14
+ onSigntureRequest?: (accountId: string) => Promise<any>;
15
+ };
16
+
17
+ class WebSocketClient {
18
+ // the topic reference count;
19
+ private static __topicRefCountMap: Map<string, number> = new Map();
20
+ private wsSubject: WebSocketSubject<any>;
21
+ private privateWsSubject?: WebSocketSubject<any>;
22
+
23
+ private authenticated: boolean = false;
24
+
25
+ private _pendingPrivateSubscribe: any[] = [];
26
+
27
+ constructor(options: WSOptions) {
28
+ this.wsSubject = this.createSubject(options);
29
+
30
+ if (!!options.accountId) {
31
+ this.privateWsSubject = this.createPrivateSubject(options);
32
+ }
33
+
34
+ this.bindSubscribe();
35
+ }
36
+ private createSubject(options: WSOptions): WebSocketSubject<any> {
37
+ let url;
38
+ if (typeof options.url === "string") {
39
+ url = options.url;
40
+ } else {
41
+ url = WS_URL[options.networkId || "testnet"].public;
42
+ }
43
+
44
+ return webSocket({
45
+ url: `${url}${options.accountId || ""}`,
46
+ openObserver: {
47
+ next: () => {
48
+ console.log("Connection ok");
49
+ },
50
+ },
51
+ closeObserver: {
52
+ next: () => {
53
+ console.log("Connection closed");
54
+ },
55
+ },
56
+ });
57
+ }
58
+
59
+ private createPrivateSubject(options: WSOptions): WebSocketSubject<any> {
60
+ const url = WS_URL[options.networkId || "testnet"].private;
61
+ const ws = webSocket({
62
+ url: `${url}${options.accountId}`,
63
+ openObserver: {
64
+ next: () => {
65
+ console.log("Private connection ok");
66
+ if (this.authenticated || !options.accountId) return;
67
+
68
+ options.onSigntureRequest?.(options.accountId).then((signature) => {
69
+ this.authenticate(options.accountId!, signature);
70
+ });
71
+ },
72
+ },
73
+ closeObserver: {
74
+ next: () => {
75
+ console.log("Private connection closed");
76
+ this.authenticated = false;
77
+ },
78
+ },
79
+ });
80
+ // authenticate
81
+
82
+ return ws;
83
+ }
84
+
85
+ private bindSubscribe() {
86
+ /// 处理ping,auth等消息
87
+
88
+ this.wsSubject.subscribe({
89
+ next: (message) => {
90
+ const handler = messageHandlers.get(message.event);
91
+ if (handler) {
92
+ handler.handle(message, this.send);
93
+ }
94
+ },
95
+ error(err) {
96
+ console.log("WS Error: ", err);
97
+ },
98
+ complete() {
99
+ console.log("WS Connection closed");
100
+ },
101
+ });
102
+
103
+ if (!this.privateWsSubject) return;
104
+
105
+ this.privateWsSubject.subscribe({
106
+ next: (message) => {
107
+ if (message.event === "auth") {
108
+ this.authenticated = true;
109
+ // 认证成功后,发送之前的订阅消息
110
+ this._sendPendingPrivateMessage();
111
+ return;
112
+ }
113
+
114
+ const handler = messageHandlers.get(message.event);
115
+
116
+ if (handler) {
117
+ handler.handle(message, this.privateSend);
118
+ }
119
+ },
120
+ error(err) {
121
+ console.log("WS Error: ", err);
122
+ },
123
+ complete() {
124
+ console.log("WS Connection closed");
125
+ },
126
+ });
127
+ }
128
+
129
+ private authenticate(
130
+ accountId: string,
131
+ message: {
132
+ publicKey: string;
133
+ signature: string;
134
+ timestamp: number;
135
+ }
136
+ ) {
137
+ if (this.authenticated) return;
138
+ if (!this.privateWsSubject) {
139
+ console.error("private ws not connected");
140
+ return;
141
+ }
142
+
143
+ console.log("push auth message:", message);
144
+ this.privateWsSubject?.next({
145
+ id: "auth",
146
+ event: "auth",
147
+ params: {
148
+ orderly_key: message.publicKey,
149
+ sign: message.signature,
150
+ timestamp: message.timestamp,
151
+ },
152
+ });
153
+ // this.wsSubject.next({ type: "authenticate" });
154
+ // this.authenticated = true;
155
+ }
156
+
157
+ send = (message: any) => {
158
+ this.wsSubject.next(message);
159
+ };
160
+
161
+ privateSend = (message: any) => {
162
+ if (!this.privateWsSubject) {
163
+ console.warn("private ws not connected");
164
+ return;
165
+ }
166
+
167
+ this.privateWsSubject.next(message);
168
+ };
169
+
170
+ get isAuthed() {
171
+ return this.authenticated;
172
+ }
173
+
174
+ // observe<T>(topic: string): Observable<T>;
175
+ // observe<T>(topic: string, unsubscribe?: () => any): Observable<T>;
176
+ // observe<T>(
177
+ // params: {
178
+ // event: string;
179
+ // } & Record<string, any>,
180
+ // unsubscribe?: () => any
181
+ // ): Observable<T>;
182
+ observe<T>(
183
+ params: any,
184
+ unsubscribe?: () => any,
185
+ messageFilter?: (value: T) => boolean
186
+ ): Observable<T> {
187
+ return this._observe(false, params, unsubscribe, messageFilter);
188
+ }
189
+
190
+ // privateObserve<T>(topic: string): Observable<T>;
191
+ // privateObserve<T>(topic: string, unsubscribe?: () => any): Observable<T>;
192
+ // privateObserve<T>(
193
+ // params: {
194
+ // event: string;
195
+ // } & Record<string, any>,
196
+ // unsubscribe?: () => any
197
+ // ): Observable<T>;
198
+ privateObserve<T>(
199
+ params: any,
200
+ unsubscribe?: () => any,
201
+ messageFilter?: (value: T) => boolean
202
+ ): Observable<T> {
203
+ return this._observe(true, params, unsubscribe, messageFilter);
204
+ }
205
+
206
+ private _observe<T>(
207
+ isPrivate: boolean,
208
+ params: any,
209
+ unsubscribe?: () => any,
210
+ messageFilter?: (value: T) => boolean
211
+ ) {
212
+ if (isPrivate && !this.privateWsSubject) {
213
+ throw new Error("private ws not connected");
214
+ }
215
+
216
+ const subject = isPrivate ? this.privateWsSubject : this.wsSubject;
217
+ const sendFunc = isPrivate ? this.privateSend : this.send;
218
+
219
+ const [subscribeMessage, unsubscribeMessage, filter, messageFormatter] =
220
+ this.generateMessage(params, unsubscribe, messageFilter);
221
+
222
+ return new Observable((observer: Observer<T>) => {
223
+ // 如果是private ws, 但是没有连接,就先缓存起来,等连接上了再订阅
224
+ if (isPrivate && !this.authenticated) {
225
+ this._pendingPrivateSubscribe.push(params);
226
+ return;
227
+ }
228
+
229
+ try {
230
+ //TODO: add ref count, only send subscribe message when ref count is 0
231
+ // 如果已经订阅过了,就不再发送订阅消息
232
+ const refCount =
233
+ WebSocketClient.__topicRefCountMap.get(subscribeMessage.topic) || 0;
234
+ if (refCount === 0) {
235
+ // WS.__topicRefCountMap.set(subscribeMessage.topic, WS.__topicRefCountMap.get(subscribeMessage.topic) + 1);
236
+ // this.send(subscribeMessage);
237
+ sendFunc(subscribeMessage);
238
+ WebSocketClient.__topicRefCountMap.set(
239
+ subscribeMessage.topic,
240
+ refCount + 1
241
+ );
242
+ }
243
+ } catch (err) {
244
+ observer.error(err);
245
+ }
246
+
247
+ const subscription = subject!.subscribe({
248
+ next: (x) => {
249
+ try {
250
+ if (filter(x)) {
251
+ observer.next(messageFormatter(x));
252
+ }
253
+ } catch (err) {
254
+ observer.error(err);
255
+ }
256
+ },
257
+ error: (err) => observer.error(err),
258
+ complete: () => observer.complete(),
259
+ });
260
+
261
+ return () => {
262
+ try {
263
+ // console.log("******* unsubscribe", unsubscribeMessage);
264
+ const refCount =
265
+ WebSocketClient.__topicRefCountMap.get(subscribeMessage.topic) || 0;
266
+ if (refCount > 1) {
267
+ WebSocketClient.__topicRefCountMap.set(
268
+ subscribeMessage.topic,
269
+ refCount - 1
270
+ );
271
+ return;
272
+ }
273
+ if (!!unsubscribeMessage) {
274
+ this.send(unsubscribeMessage);
275
+ }
276
+ WebSocketClient.__topicRefCountMap.delete(subscribeMessage.topic);
277
+ } catch (err) {
278
+ observer.error(err);
279
+ }
280
+ subscription.unsubscribe();
281
+ };
282
+ });
283
+ }
284
+
285
+ private generateMessage(
286
+ params: any,
287
+ unsubscribe?: () => any,
288
+ messageFilter?: (value: any) => boolean
289
+ ): [
290
+ Record<string, any>,
291
+ Record<string, any>,
292
+ (message: any) => boolean,
293
+ (message: any) => any
294
+ ] {
295
+ let subscribeMessage: Record<string, any>,
296
+ unsubscribeMessage: Record<string, any>;
297
+ let filter: (message: any) => boolean,
298
+ messageFormatter: (message: any) => any = (message: any) => message.data;
299
+
300
+ if (typeof params === "string") {
301
+ subscribeMessage = { event: "subscribe", topic: params };
302
+ unsubscribeMessage = { event: "unsubscribe", topic: params };
303
+ filter = (message: any) => message.topic === params;
304
+ } else {
305
+ subscribeMessage = params;
306
+ unsubscribeMessage =
307
+ typeof unsubscribe === "function" ? unsubscribe() : unsubscribe;
308
+ filter = messageFilter || ((message: any) => true);
309
+ }
310
+
311
+ return [subscribeMessage, unsubscribeMessage, filter, messageFormatter];
312
+ }
313
+
314
+ private _sendPendingPrivateMessage() {
315
+ if (this._pendingPrivateSubscribe.length === 0) return;
316
+ this._pendingPrivateSubscribe.forEach((params) => {
317
+ this.privateObserve(params).subscribe();
318
+ });
319
+ this._pendingPrivateSubscribe = [];
320
+ }
321
+
322
+ // 取消所有订阅
323
+ desotry() {
324
+ this.wsSubject.unsubscribe();
325
+ this.privateWsSubject?.unsubscribe();
326
+ }
327
+ }
328
+
329
+ export default WebSocketClient;
package/tsconfig.json ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "extends": "tsconfig/base.json",
3
+ "compilerOptions": {
4
+
5
+ "paths": {
6
+ "@/*": [
7
+ "./src/*"
8
+ ]
9
+ }
10
+ },
11
+ "include": ["**/*.ts", "**/*.tsx"],
12
+ "exclude": ["node_modules"]
13
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,11 @@
1
+ import { defineConfig } from "tsup";
2
+
3
+ export default defineConfig({
4
+ entry: ["src/index.ts"],
5
+ format: ["cjs", "esm"],
6
+ target: "es6",
7
+ splitting: false,
8
+ sourcemap: true,
9
+ clean: true,
10
+ dts: true,
11
+ });