@debros/network-ts-sdk 0.1.4 → 0.2.5
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 +102 -7
- package/dist/index.d.ts +184 -29
- package/dist/index.js +375 -92
- package/dist/index.js.map +1 -1
- package/package.json +7 -6
- package/src/auth/client.ts +144 -10
- package/src/core/http.ts +80 -11
- package/src/core/ws.ts +87 -80
- package/src/index.ts +15 -9
- package/src/network/client.ts +58 -4
- package/src/pubsub/client.ts +174 -28
package/dist/index.js
CHANGED
|
@@ -27,31 +27,60 @@ var SDKError = class _SDKError extends Error {
|
|
|
27
27
|
var HttpClient = class {
|
|
28
28
|
constructor(config) {
|
|
29
29
|
this.baseURL = config.baseURL.replace(/\/$/, "");
|
|
30
|
-
this.timeout = config.timeout ??
|
|
30
|
+
this.timeout = config.timeout ?? 6e4;
|
|
31
31
|
this.maxRetries = config.maxRetries ?? 3;
|
|
32
32
|
this.retryDelayMs = config.retryDelayMs ?? 1e3;
|
|
33
33
|
this.fetch = config.fetch ?? globalThis.fetch;
|
|
34
34
|
}
|
|
35
35
|
setApiKey(apiKey) {
|
|
36
36
|
this.apiKey = apiKey;
|
|
37
|
-
|
|
37
|
+
if (typeof console !== "undefined") {
|
|
38
|
+
console.log(
|
|
39
|
+
"[HttpClient] API key set:",
|
|
40
|
+
!!apiKey,
|
|
41
|
+
"JWT still present:",
|
|
42
|
+
!!this.jwt
|
|
43
|
+
);
|
|
44
|
+
}
|
|
38
45
|
}
|
|
39
46
|
setJwt(jwt) {
|
|
40
47
|
this.jwt = jwt;
|
|
41
|
-
|
|
48
|
+
if (typeof console !== "undefined") {
|
|
49
|
+
console.log(
|
|
50
|
+
"[HttpClient] JWT set:",
|
|
51
|
+
!!jwt,
|
|
52
|
+
"API key still present:",
|
|
53
|
+
!!this.apiKey
|
|
54
|
+
);
|
|
55
|
+
}
|
|
42
56
|
}
|
|
43
|
-
getAuthHeaders() {
|
|
57
|
+
getAuthHeaders(path) {
|
|
44
58
|
const headers = {};
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
59
|
+
const isDbOperation = path.includes("/v1/rqlite/");
|
|
60
|
+
const isPubSubOperation = path.includes("/v1/pubsub/");
|
|
61
|
+
const isProxyOperation = path.includes("/v1/proxy/");
|
|
62
|
+
if (isDbOperation || isPubSubOperation || isProxyOperation) {
|
|
63
|
+
if (this.apiKey) {
|
|
64
|
+
headers["X-API-Key"] = this.apiKey;
|
|
65
|
+
} else if (this.jwt) {
|
|
66
|
+
headers["Authorization"] = `Bearer ${this.jwt}`;
|
|
67
|
+
}
|
|
68
|
+
} else {
|
|
69
|
+
if (this.jwt) {
|
|
70
|
+
headers["Authorization"] = `Bearer ${this.jwt}`;
|
|
71
|
+
}
|
|
72
|
+
if (this.apiKey) {
|
|
73
|
+
headers["X-API-Key"] = this.apiKey;
|
|
74
|
+
}
|
|
49
75
|
}
|
|
50
76
|
return headers;
|
|
51
77
|
}
|
|
52
78
|
getAuthToken() {
|
|
53
79
|
return this.jwt || this.apiKey;
|
|
54
80
|
}
|
|
81
|
+
getApiKey() {
|
|
82
|
+
return this.apiKey;
|
|
83
|
+
}
|
|
55
84
|
async request(method, path, options = {}) {
|
|
56
85
|
const url = new URL(this.baseURL + path);
|
|
57
86
|
if (options.query) {
|
|
@@ -61,18 +90,33 @@ var HttpClient = class {
|
|
|
61
90
|
}
|
|
62
91
|
const headers = {
|
|
63
92
|
"Content-Type": "application/json",
|
|
64
|
-
...this.getAuthHeaders(),
|
|
93
|
+
...this.getAuthHeaders(path),
|
|
65
94
|
...options.headers
|
|
66
95
|
};
|
|
96
|
+
if (typeof console !== "undefined" && (path.includes("/db/") || path.includes("/query") || path.includes("/auth/") || path.includes("/pubsub/") || path.includes("/proxy/"))) {
|
|
97
|
+
console.log("[HttpClient] Request headers for", path, {
|
|
98
|
+
hasAuth: !!headers["Authorization"],
|
|
99
|
+
hasApiKey: !!headers["X-API-Key"],
|
|
100
|
+
authPrefix: headers["Authorization"] ? headers["Authorization"].substring(0, 20) : "none",
|
|
101
|
+
apiKeyPrefix: headers["X-API-Key"] ? headers["X-API-Key"].substring(0, 20) : "none"
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
const controller = new AbortController();
|
|
105
|
+
const requestTimeout = options.timeout ?? this.timeout;
|
|
106
|
+
const timeoutId = setTimeout(() => controller.abort(), requestTimeout);
|
|
67
107
|
const fetchOptions = {
|
|
68
108
|
method,
|
|
69
109
|
headers,
|
|
70
|
-
signal:
|
|
110
|
+
signal: controller.signal
|
|
71
111
|
};
|
|
72
112
|
if (options.body !== void 0) {
|
|
73
113
|
fetchOptions.body = JSON.stringify(options.body);
|
|
74
114
|
}
|
|
75
|
-
|
|
115
|
+
try {
|
|
116
|
+
return await this.requestWithRetry(url.toString(), fetchOptions);
|
|
117
|
+
} finally {
|
|
118
|
+
clearTimeout(timeoutId);
|
|
119
|
+
}
|
|
76
120
|
}
|
|
77
121
|
async requestWithRetry(url, options, attempt = 0) {
|
|
78
122
|
try {
|
|
@@ -178,13 +222,11 @@ var AuthClient = class {
|
|
|
178
222
|
}
|
|
179
223
|
setApiKey(apiKey) {
|
|
180
224
|
this.currentApiKey = apiKey;
|
|
181
|
-
this.currentJwt = void 0;
|
|
182
225
|
this.httpClient.setApiKey(apiKey);
|
|
183
226
|
this.storage.set("apiKey", apiKey);
|
|
184
227
|
}
|
|
185
228
|
setJwt(jwt) {
|
|
186
229
|
this.currentJwt = jwt;
|
|
187
|
-
this.currentApiKey = void 0;
|
|
188
230
|
this.httpClient.setJwt(jwt);
|
|
189
231
|
this.storage.set("jwt", jwt);
|
|
190
232
|
}
|
|
@@ -207,12 +249,50 @@ var AuthClient = class {
|
|
|
207
249
|
this.setJwt(token);
|
|
208
250
|
return token;
|
|
209
251
|
}
|
|
252
|
+
/**
|
|
253
|
+
* Logout user and clear JWT, but preserve API key
|
|
254
|
+
* Use this for user logout in apps where API key is app-level credential
|
|
255
|
+
*/
|
|
256
|
+
async logoutUser() {
|
|
257
|
+
if (this.currentJwt) {
|
|
258
|
+
try {
|
|
259
|
+
await this.httpClient.post("/v1/auth/logout", { all: true });
|
|
260
|
+
} catch (error) {
|
|
261
|
+
console.warn(
|
|
262
|
+
"Server-side logout failed, continuing with local cleanup:",
|
|
263
|
+
error
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
this.currentJwt = void 0;
|
|
268
|
+
this.httpClient.setJwt(void 0);
|
|
269
|
+
await this.storage.set("jwt", "");
|
|
270
|
+
if (!this.currentApiKey) {
|
|
271
|
+
const storedApiKey = await this.storage.get("apiKey");
|
|
272
|
+
if (storedApiKey) {
|
|
273
|
+
this.currentApiKey = storedApiKey;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
if (this.currentApiKey) {
|
|
277
|
+
this.httpClient.setApiKey(this.currentApiKey);
|
|
278
|
+
console.log("[Auth] API key restored after user logout");
|
|
279
|
+
} else {
|
|
280
|
+
console.warn("[Auth] No API key available after logout");
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Full logout - clears both JWT and API key
|
|
285
|
+
* Use this to completely reset authentication state
|
|
286
|
+
*/
|
|
210
287
|
async logout() {
|
|
211
288
|
if (this.currentJwt) {
|
|
212
289
|
try {
|
|
213
290
|
await this.httpClient.post("/v1/auth/logout", { all: true });
|
|
214
291
|
} catch (error) {
|
|
215
|
-
console.warn(
|
|
292
|
+
console.warn(
|
|
293
|
+
"Server-side logout failed, continuing with local cleanup:",
|
|
294
|
+
error
|
|
295
|
+
);
|
|
216
296
|
}
|
|
217
297
|
}
|
|
218
298
|
this.currentApiKey = void 0;
|
|
@@ -228,6 +308,51 @@ var AuthClient = class {
|
|
|
228
308
|
this.httpClient.setJwt(void 0);
|
|
229
309
|
await this.storage.clear();
|
|
230
310
|
}
|
|
311
|
+
/**
|
|
312
|
+
* Request a challenge nonce for wallet authentication
|
|
313
|
+
*/
|
|
314
|
+
async challenge(params) {
|
|
315
|
+
const response = await this.httpClient.post("/v1/auth/challenge", {
|
|
316
|
+
wallet: params.wallet,
|
|
317
|
+
purpose: params.purpose || "authentication",
|
|
318
|
+
namespace: params.namespace || "default"
|
|
319
|
+
});
|
|
320
|
+
return response;
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Verify wallet signature and get JWT token
|
|
324
|
+
*/
|
|
325
|
+
async verify(params) {
|
|
326
|
+
const response = await this.httpClient.post("/v1/auth/verify", {
|
|
327
|
+
wallet: params.wallet,
|
|
328
|
+
nonce: params.nonce,
|
|
329
|
+
signature: params.signature,
|
|
330
|
+
namespace: params.namespace || "default",
|
|
331
|
+
chain_type: params.chain_type || "ETH"
|
|
332
|
+
});
|
|
333
|
+
this.setJwt(response.access_token);
|
|
334
|
+
if (response.api_key) {
|
|
335
|
+
this.setApiKey(response.api_key);
|
|
336
|
+
}
|
|
337
|
+
if (response.refresh_token) {
|
|
338
|
+
await this.storage.set("refreshToken", response.refresh_token);
|
|
339
|
+
}
|
|
340
|
+
return response;
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Get API key for wallet (creates namespace ownership)
|
|
344
|
+
*/
|
|
345
|
+
async getApiKey(params) {
|
|
346
|
+
const response = await this.httpClient.post("/v1/auth/api-key", {
|
|
347
|
+
wallet: params.wallet,
|
|
348
|
+
nonce: params.nonce,
|
|
349
|
+
signature: params.signature,
|
|
350
|
+
namespace: params.namespace || "default",
|
|
351
|
+
chain_type: params.chain_type || "ETH"
|
|
352
|
+
});
|
|
353
|
+
this.setApiKey(response.api_key);
|
|
354
|
+
return response;
|
|
355
|
+
}
|
|
231
356
|
};
|
|
232
357
|
|
|
233
358
|
// src/db/qb.ts
|
|
@@ -506,33 +631,35 @@ var DBClient = class {
|
|
|
506
631
|
import WebSocket from "isomorphic-ws";
|
|
507
632
|
var WSClient = class {
|
|
508
633
|
constructor(config) {
|
|
509
|
-
this.reconnectAttempts = 0;
|
|
510
634
|
this.messageHandlers = /* @__PURE__ */ new Set();
|
|
511
635
|
this.errorHandlers = /* @__PURE__ */ new Set();
|
|
512
636
|
this.closeHandlers = /* @__PURE__ */ new Set();
|
|
513
|
-
this.
|
|
637
|
+
this.openHandlers = /* @__PURE__ */ new Set();
|
|
638
|
+
this.isClosed = false;
|
|
514
639
|
this.url = config.wsURL;
|
|
515
640
|
this.timeout = config.timeout ?? 3e4;
|
|
516
|
-
this.maxReconnectAttempts = config.maxReconnectAttempts ?? 5;
|
|
517
|
-
this.reconnectDelayMs = config.reconnectDelayMs ?? 1e3;
|
|
518
|
-
this.heartbeatIntervalMs = config.heartbeatIntervalMs ?? 3e4;
|
|
519
|
-
this.authMode = config.authMode ?? "header";
|
|
520
641
|
this.authToken = config.authToken;
|
|
521
642
|
this.WebSocketClass = config.WebSocket ?? WebSocket;
|
|
522
643
|
}
|
|
644
|
+
/**
|
|
645
|
+
* Connect to WebSocket server
|
|
646
|
+
*/
|
|
523
647
|
connect() {
|
|
524
648
|
return new Promise((resolve, reject) => {
|
|
525
649
|
try {
|
|
526
650
|
const wsUrl = this.buildWSUrl();
|
|
527
651
|
this.ws = new this.WebSocketClass(wsUrl);
|
|
652
|
+
this.isClosed = false;
|
|
528
653
|
const timeout = setTimeout(() => {
|
|
529
654
|
this.ws?.close();
|
|
530
|
-
reject(
|
|
655
|
+
reject(
|
|
656
|
+
new SDKError("WebSocket connection timeout", 408, "WS_TIMEOUT")
|
|
657
|
+
);
|
|
531
658
|
}, this.timeout);
|
|
532
659
|
this.ws.addEventListener("open", () => {
|
|
533
660
|
clearTimeout(timeout);
|
|
534
|
-
|
|
535
|
-
this.
|
|
661
|
+
console.log("[WSClient] Connected to", this.url);
|
|
662
|
+
this.openHandlers.forEach((handler) => handler());
|
|
536
663
|
resolve();
|
|
537
664
|
});
|
|
538
665
|
this.ws.addEventListener("message", (event) => {
|
|
@@ -540,29 +667,24 @@ var WSClient = class {
|
|
|
540
667
|
this.messageHandlers.forEach((handler) => handler(msgEvent.data));
|
|
541
668
|
});
|
|
542
669
|
this.ws.addEventListener("error", (event) => {
|
|
670
|
+
console.error("[WSClient] WebSocket error:", event);
|
|
543
671
|
clearTimeout(timeout);
|
|
544
|
-
const error = new SDKError(
|
|
545
|
-
"WebSocket error",
|
|
546
|
-
500,
|
|
547
|
-
"WS_ERROR",
|
|
548
|
-
event
|
|
549
|
-
);
|
|
672
|
+
const error = new SDKError("WebSocket error", 500, "WS_ERROR", event);
|
|
550
673
|
this.errorHandlers.forEach((handler) => handler(error));
|
|
551
674
|
});
|
|
552
675
|
this.ws.addEventListener("close", () => {
|
|
553
676
|
clearTimeout(timeout);
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
this.attemptReconnect();
|
|
557
|
-
} else {
|
|
558
|
-
this.closeHandlers.forEach((handler) => handler());
|
|
559
|
-
}
|
|
677
|
+
console.log("[WSClient] Connection closed");
|
|
678
|
+
this.closeHandlers.forEach((handler) => handler());
|
|
560
679
|
});
|
|
561
680
|
} catch (error) {
|
|
562
681
|
reject(error);
|
|
563
682
|
}
|
|
564
683
|
});
|
|
565
684
|
}
|
|
685
|
+
/**
|
|
686
|
+
* Build WebSocket URL with auth token
|
|
687
|
+
*/
|
|
566
688
|
buildWSUrl() {
|
|
567
689
|
let url = this.url;
|
|
568
690
|
if (this.authToken) {
|
|
@@ -572,85 +694,152 @@ var WSClient = class {
|
|
|
572
694
|
}
|
|
573
695
|
return url;
|
|
574
696
|
}
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
this.ws.send(JSON.stringify({ type: "ping" }));
|
|
579
|
-
}
|
|
580
|
-
}, this.heartbeatIntervalMs);
|
|
581
|
-
}
|
|
582
|
-
stopHeartbeat() {
|
|
583
|
-
if (this.heartbeatInterval) {
|
|
584
|
-
clearInterval(this.heartbeatInterval);
|
|
585
|
-
this.heartbeatInterval = void 0;
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
attemptReconnect() {
|
|
589
|
-
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
|
590
|
-
this.reconnectAttempts++;
|
|
591
|
-
const delayMs = this.reconnectDelayMs * this.reconnectAttempts;
|
|
592
|
-
setTimeout(() => {
|
|
593
|
-
this.connect().catch((error) => {
|
|
594
|
-
this.errorHandlers.forEach((handler) => handler(error));
|
|
595
|
-
});
|
|
596
|
-
}, delayMs);
|
|
597
|
-
} else {
|
|
598
|
-
this.closeHandlers.forEach((handler) => handler());
|
|
599
|
-
}
|
|
600
|
-
}
|
|
697
|
+
/**
|
|
698
|
+
* Register message handler
|
|
699
|
+
*/
|
|
601
700
|
onMessage(handler) {
|
|
602
701
|
this.messageHandlers.add(handler);
|
|
603
702
|
return () => this.messageHandlers.delete(handler);
|
|
604
703
|
}
|
|
704
|
+
/**
|
|
705
|
+
* Unregister message handler
|
|
706
|
+
*/
|
|
707
|
+
offMessage(handler) {
|
|
708
|
+
this.messageHandlers.delete(handler);
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
711
|
+
* Register error handler
|
|
712
|
+
*/
|
|
605
713
|
onError(handler) {
|
|
606
714
|
this.errorHandlers.add(handler);
|
|
607
715
|
return () => this.errorHandlers.delete(handler);
|
|
608
716
|
}
|
|
717
|
+
/**
|
|
718
|
+
* Unregister error handler
|
|
719
|
+
*/
|
|
720
|
+
offError(handler) {
|
|
721
|
+
this.errorHandlers.delete(handler);
|
|
722
|
+
}
|
|
723
|
+
/**
|
|
724
|
+
* Register close handler
|
|
725
|
+
*/
|
|
609
726
|
onClose(handler) {
|
|
610
727
|
this.closeHandlers.add(handler);
|
|
611
728
|
return () => this.closeHandlers.delete(handler);
|
|
612
729
|
}
|
|
730
|
+
/**
|
|
731
|
+
* Unregister close handler
|
|
732
|
+
*/
|
|
733
|
+
offClose(handler) {
|
|
734
|
+
this.closeHandlers.delete(handler);
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* Register open handler
|
|
738
|
+
*/
|
|
739
|
+
onOpen(handler) {
|
|
740
|
+
this.openHandlers.add(handler);
|
|
741
|
+
return () => this.openHandlers.delete(handler);
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* Send data through WebSocket
|
|
745
|
+
*/
|
|
613
746
|
send(data) {
|
|
614
747
|
if (this.ws?.readyState !== WebSocket.OPEN) {
|
|
615
|
-
throw new SDKError(
|
|
616
|
-
"WebSocket is not connected",
|
|
617
|
-
500,
|
|
618
|
-
"WS_NOT_CONNECTED"
|
|
619
|
-
);
|
|
748
|
+
throw new SDKError("WebSocket is not connected", 500, "WS_NOT_CONNECTED");
|
|
620
749
|
}
|
|
621
750
|
this.ws.send(data);
|
|
622
751
|
}
|
|
752
|
+
/**
|
|
753
|
+
* Close WebSocket connection
|
|
754
|
+
*/
|
|
623
755
|
close() {
|
|
624
|
-
this.
|
|
625
|
-
|
|
756
|
+
if (this.isClosed) {
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
this.isClosed = true;
|
|
626
760
|
this.ws?.close();
|
|
627
761
|
}
|
|
762
|
+
/**
|
|
763
|
+
* Check if WebSocket is connected
|
|
764
|
+
*/
|
|
628
765
|
isConnected() {
|
|
629
|
-
return this.ws?.readyState === WebSocket.OPEN;
|
|
766
|
+
return !this.isClosed && this.ws?.readyState === WebSocket.OPEN;
|
|
630
767
|
}
|
|
768
|
+
/**
|
|
769
|
+
* Update auth token
|
|
770
|
+
*/
|
|
631
771
|
setAuthToken(token) {
|
|
632
772
|
this.authToken = token;
|
|
633
773
|
}
|
|
634
774
|
};
|
|
635
775
|
|
|
636
776
|
// src/pubsub/client.ts
|
|
777
|
+
function base64Encode(str) {
|
|
778
|
+
if (typeof Buffer !== "undefined") {
|
|
779
|
+
return Buffer.from(str).toString("base64");
|
|
780
|
+
} else if (typeof btoa !== "undefined") {
|
|
781
|
+
return btoa(
|
|
782
|
+
encodeURIComponent(str).replace(
|
|
783
|
+
/%([0-9A-F]{2})/g,
|
|
784
|
+
(match, p1) => String.fromCharCode(parseInt(p1, 16))
|
|
785
|
+
)
|
|
786
|
+
);
|
|
787
|
+
}
|
|
788
|
+
throw new Error("No base64 encoding method available");
|
|
789
|
+
}
|
|
790
|
+
function base64EncodeBytes(bytes) {
|
|
791
|
+
if (typeof Buffer !== "undefined") {
|
|
792
|
+
return Buffer.from(bytes).toString("base64");
|
|
793
|
+
} else if (typeof btoa !== "undefined") {
|
|
794
|
+
let binary = "";
|
|
795
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
796
|
+
binary += String.fromCharCode(bytes[i]);
|
|
797
|
+
}
|
|
798
|
+
return btoa(binary);
|
|
799
|
+
}
|
|
800
|
+
throw new Error("No base64 encoding method available");
|
|
801
|
+
}
|
|
802
|
+
function base64Decode(b64) {
|
|
803
|
+
if (typeof Buffer !== "undefined") {
|
|
804
|
+
return Buffer.from(b64, "base64").toString("utf-8");
|
|
805
|
+
} else if (typeof atob !== "undefined") {
|
|
806
|
+
const binary = atob(b64);
|
|
807
|
+
const bytes = new Uint8Array(binary.length);
|
|
808
|
+
for (let i = 0; i < binary.length; i++) {
|
|
809
|
+
bytes[i] = binary.charCodeAt(i);
|
|
810
|
+
}
|
|
811
|
+
return new TextDecoder().decode(bytes);
|
|
812
|
+
}
|
|
813
|
+
throw new Error("No base64 decoding method available");
|
|
814
|
+
}
|
|
637
815
|
var PubSubClient = class {
|
|
638
816
|
constructor(httpClient, wsConfig = {}) {
|
|
639
817
|
this.httpClient = httpClient;
|
|
640
818
|
this.wsConfig = wsConfig;
|
|
641
819
|
}
|
|
642
820
|
/**
|
|
643
|
-
* Publish a message to a topic
|
|
821
|
+
* Publish a message to a topic via HTTP
|
|
644
822
|
*/
|
|
645
823
|
async publish(topic, data) {
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
824
|
+
let dataBase64;
|
|
825
|
+
if (typeof data === "string") {
|
|
826
|
+
dataBase64 = base64Encode(data);
|
|
827
|
+
} else {
|
|
828
|
+
dataBase64 = base64EncodeBytes(data);
|
|
829
|
+
}
|
|
830
|
+
await this.httpClient.post(
|
|
831
|
+
"/v1/pubsub/publish",
|
|
832
|
+
{
|
|
833
|
+
topic,
|
|
834
|
+
data_base64: dataBase64
|
|
835
|
+
},
|
|
836
|
+
{
|
|
837
|
+
timeout: 3e4
|
|
838
|
+
}
|
|
839
|
+
);
|
|
651
840
|
}
|
|
652
841
|
/**
|
|
653
|
-
* List active topics in the current namespace
|
|
842
|
+
* List active topics in the current namespace
|
|
654
843
|
*/
|
|
655
844
|
async topics() {
|
|
656
845
|
const response = await this.httpClient.get(
|
|
@@ -659,18 +848,20 @@ var PubSubClient = class {
|
|
|
659
848
|
return response.topics || [];
|
|
660
849
|
}
|
|
661
850
|
/**
|
|
662
|
-
* Subscribe to a topic via WebSocket
|
|
663
|
-
*
|
|
851
|
+
* Subscribe to a topic via WebSocket
|
|
852
|
+
* Creates one WebSocket connection per topic
|
|
664
853
|
*/
|
|
665
854
|
async subscribe(topic, handlers = {}) {
|
|
666
|
-
const wsUrl = new URL(this.wsConfig.wsURL || "ws://
|
|
855
|
+
const wsUrl = new URL(this.wsConfig.wsURL || "ws://127.0.0.1:6001");
|
|
667
856
|
wsUrl.pathname = "/v1/pubsub/ws";
|
|
668
857
|
wsUrl.searchParams.set("topic", topic);
|
|
858
|
+
const authToken = this.httpClient.getApiKey() ?? this.httpClient.getToken();
|
|
669
859
|
const wsClient = new WSClient({
|
|
670
860
|
...this.wsConfig,
|
|
671
861
|
wsURL: wsUrl.toString(),
|
|
672
|
-
authToken
|
|
862
|
+
authToken
|
|
673
863
|
});
|
|
864
|
+
await wsClient.connect();
|
|
674
865
|
const subscription = new Subscription(wsClient, topic);
|
|
675
866
|
if (handlers.onMessage) {
|
|
676
867
|
subscription.onMessage(handlers.onMessage);
|
|
@@ -681,7 +872,6 @@ var PubSubClient = class {
|
|
|
681
872
|
if (handlers.onClose) {
|
|
682
873
|
subscription.onClose(handlers.onClose);
|
|
683
874
|
}
|
|
684
|
-
await wsClient.connect();
|
|
685
875
|
return subscription;
|
|
686
876
|
}
|
|
687
877
|
};
|
|
@@ -690,46 +880,105 @@ var Subscription = class {
|
|
|
690
880
|
this.messageHandlers = /* @__PURE__ */ new Set();
|
|
691
881
|
this.errorHandlers = /* @__PURE__ */ new Set();
|
|
692
882
|
this.closeHandlers = /* @__PURE__ */ new Set();
|
|
883
|
+
this.isClosed = false;
|
|
884
|
+
this.wsMessageHandler = null;
|
|
885
|
+
this.wsErrorHandler = null;
|
|
886
|
+
this.wsCloseHandler = null;
|
|
693
887
|
this.wsClient = wsClient;
|
|
694
888
|
this.topic = topic;
|
|
695
|
-
this.
|
|
889
|
+
this.wsMessageHandler = (data) => {
|
|
696
890
|
try {
|
|
891
|
+
const envelope = JSON.parse(data);
|
|
892
|
+
if (!envelope || typeof envelope !== "object") {
|
|
893
|
+
throw new Error("Invalid envelope: not an object");
|
|
894
|
+
}
|
|
895
|
+
if (!envelope.data || typeof envelope.data !== "string") {
|
|
896
|
+
throw new Error("Invalid envelope: missing or invalid data field");
|
|
897
|
+
}
|
|
898
|
+
if (!envelope.topic || typeof envelope.topic !== "string") {
|
|
899
|
+
throw new Error("Invalid envelope: missing or invalid topic field");
|
|
900
|
+
}
|
|
901
|
+
if (typeof envelope.timestamp !== "number") {
|
|
902
|
+
throw new Error(
|
|
903
|
+
"Invalid envelope: missing or invalid timestamp field"
|
|
904
|
+
);
|
|
905
|
+
}
|
|
906
|
+
const messageData = base64Decode(envelope.data);
|
|
697
907
|
const message = {
|
|
698
|
-
topic:
|
|
699
|
-
data,
|
|
700
|
-
timestamp:
|
|
908
|
+
topic: envelope.topic,
|
|
909
|
+
data: messageData,
|
|
910
|
+
timestamp: envelope.timestamp
|
|
701
911
|
};
|
|
912
|
+
console.log("[Subscription] Received message on topic:", this.topic);
|
|
702
913
|
this.messageHandlers.forEach((handler) => handler(message));
|
|
703
914
|
} catch (error) {
|
|
915
|
+
console.error("[Subscription] Error processing message:", error);
|
|
704
916
|
this.errorHandlers.forEach(
|
|
705
917
|
(handler) => handler(error instanceof Error ? error : new Error(String(error)))
|
|
706
918
|
);
|
|
707
919
|
}
|
|
708
|
-
}
|
|
709
|
-
this.wsClient.
|
|
920
|
+
};
|
|
921
|
+
this.wsClient.onMessage(this.wsMessageHandler);
|
|
922
|
+
this.wsErrorHandler = (error) => {
|
|
710
923
|
this.errorHandlers.forEach((handler) => handler(error));
|
|
711
|
-
}
|
|
712
|
-
this.wsClient.
|
|
924
|
+
};
|
|
925
|
+
this.wsClient.onError(this.wsErrorHandler);
|
|
926
|
+
this.wsCloseHandler = () => {
|
|
713
927
|
this.closeHandlers.forEach((handler) => handler());
|
|
714
|
-
}
|
|
928
|
+
};
|
|
929
|
+
this.wsClient.onClose(this.wsCloseHandler);
|
|
715
930
|
}
|
|
931
|
+
/**
|
|
932
|
+
* Register message handler
|
|
933
|
+
*/
|
|
716
934
|
onMessage(handler) {
|
|
717
935
|
this.messageHandlers.add(handler);
|
|
718
936
|
return () => this.messageHandlers.delete(handler);
|
|
719
937
|
}
|
|
938
|
+
/**
|
|
939
|
+
* Register error handler
|
|
940
|
+
*/
|
|
720
941
|
onError(handler) {
|
|
721
942
|
this.errorHandlers.add(handler);
|
|
722
943
|
return () => this.errorHandlers.delete(handler);
|
|
723
944
|
}
|
|
945
|
+
/**
|
|
946
|
+
* Register close handler
|
|
947
|
+
*/
|
|
724
948
|
onClose(handler) {
|
|
725
949
|
this.closeHandlers.add(handler);
|
|
726
950
|
return () => this.closeHandlers.delete(handler);
|
|
727
951
|
}
|
|
952
|
+
/**
|
|
953
|
+
* Close subscription and underlying WebSocket
|
|
954
|
+
*/
|
|
728
955
|
close() {
|
|
956
|
+
if (this.isClosed) {
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
959
|
+
this.isClosed = true;
|
|
960
|
+
if (this.wsMessageHandler) {
|
|
961
|
+
this.wsClient.offMessage(this.wsMessageHandler);
|
|
962
|
+
this.wsMessageHandler = null;
|
|
963
|
+
}
|
|
964
|
+
if (this.wsErrorHandler) {
|
|
965
|
+
this.wsClient.offError(this.wsErrorHandler);
|
|
966
|
+
this.wsErrorHandler = null;
|
|
967
|
+
}
|
|
968
|
+
if (this.wsCloseHandler) {
|
|
969
|
+
this.wsClient.offClose(this.wsCloseHandler);
|
|
970
|
+
this.wsCloseHandler = null;
|
|
971
|
+
}
|
|
972
|
+
this.messageHandlers.clear();
|
|
973
|
+
this.errorHandlers.clear();
|
|
974
|
+
this.closeHandlers.clear();
|
|
729
975
|
this.wsClient.close();
|
|
730
976
|
}
|
|
977
|
+
/**
|
|
978
|
+
* Check if subscription is active
|
|
979
|
+
*/
|
|
731
980
|
isConnected() {
|
|
732
|
-
return this.wsClient.isConnected();
|
|
981
|
+
return !this.isClosed && this.wsClient.isConnected();
|
|
733
982
|
}
|
|
734
983
|
};
|
|
735
984
|
|
|
@@ -753,7 +1002,9 @@ var NetworkClient = class {
|
|
|
753
1002
|
* Get network status.
|
|
754
1003
|
*/
|
|
755
1004
|
async status() {
|
|
756
|
-
const response = await this.httpClient.get(
|
|
1005
|
+
const response = await this.httpClient.get(
|
|
1006
|
+
"/v1/network/status"
|
|
1007
|
+
);
|
|
757
1008
|
return response;
|
|
758
1009
|
}
|
|
759
1010
|
/**
|
|
@@ -777,6 +1028,38 @@ var NetworkClient = class {
|
|
|
777
1028
|
async disconnect(peerId) {
|
|
778
1029
|
await this.httpClient.post("/v1/network/disconnect", { peer_id: peerId });
|
|
779
1030
|
}
|
|
1031
|
+
/**
|
|
1032
|
+
* Proxy an HTTP request through the Anyone network.
|
|
1033
|
+
* Requires authentication (API key or JWT).
|
|
1034
|
+
*
|
|
1035
|
+
* @param request - The proxy request configuration
|
|
1036
|
+
* @returns The proxied response
|
|
1037
|
+
* @throws {SDKError} If the Anyone proxy is not available or the request fails
|
|
1038
|
+
*
|
|
1039
|
+
* @example
|
|
1040
|
+
* ```ts
|
|
1041
|
+
* const response = await client.network.proxyAnon({
|
|
1042
|
+
* url: 'https://api.example.com/data',
|
|
1043
|
+
* method: 'GET',
|
|
1044
|
+
* headers: {
|
|
1045
|
+
* 'Accept': 'application/json'
|
|
1046
|
+
* }
|
|
1047
|
+
* });
|
|
1048
|
+
*
|
|
1049
|
+
* console.log(response.status_code); // 200
|
|
1050
|
+
* console.log(response.body); // Response data
|
|
1051
|
+
* ```
|
|
1052
|
+
*/
|
|
1053
|
+
async proxyAnon(request) {
|
|
1054
|
+
const response = await this.httpClient.post(
|
|
1055
|
+
"/v1/proxy/anon",
|
|
1056
|
+
request
|
|
1057
|
+
);
|
|
1058
|
+
if (response.error) {
|
|
1059
|
+
throw new Error(`Proxy request failed: ${response.error}`);
|
|
1060
|
+
}
|
|
1061
|
+
return response;
|
|
1062
|
+
}
|
|
780
1063
|
};
|
|
781
1064
|
|
|
782
1065
|
// src/index.ts
|