@debros/network-ts-sdk 0.1.5 → 0.3.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.
- package/README.md +96 -1
- package/dist/index.d.ts +392 -29
- package/dist/index.js +795 -96
- package/dist/index.js.map +1 -1
- package/package.json +7 -6
- package/src/auth/client.ts +144 -10
- package/src/cache/client.ts +203 -0
- package/src/core/http.ts +248 -13
- package/src/core/ws.ts +87 -80
- package/src/db/repository.ts +6 -2
- package/src/index.ts +44 -10
- package/src/network/client.ts +58 -4
- package/src/pubsub/client.ts +174 -28
- package/src/storage/client.ts +270 -0
package/src/core/ws.ts
CHANGED
|
@@ -4,10 +4,6 @@ import { SDKError } from "../errors";
|
|
|
4
4
|
export interface WSClientConfig {
|
|
5
5
|
wsURL: string;
|
|
6
6
|
timeout?: number;
|
|
7
|
-
maxReconnectAttempts?: number;
|
|
8
|
-
reconnectDelayMs?: number;
|
|
9
|
-
heartbeatIntervalMs?: number;
|
|
10
|
-
authMode?: "header" | "query";
|
|
11
7
|
authToken?: string;
|
|
12
8
|
WebSocket?: typeof WebSocket;
|
|
13
9
|
}
|
|
@@ -15,54 +11,53 @@ export interface WSClientConfig {
|
|
|
15
11
|
export type WSMessageHandler = (data: string) => void;
|
|
16
12
|
export type WSErrorHandler = (error: Error) => void;
|
|
17
13
|
export type WSCloseHandler = () => void;
|
|
14
|
+
export type WSOpenHandler = () => void;
|
|
18
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Simple WebSocket client with minimal abstractions
|
|
18
|
+
* No complex reconnection, no heartbeats - keep it simple
|
|
19
|
+
*/
|
|
19
20
|
export class WSClient {
|
|
20
21
|
private url: string;
|
|
21
22
|
private timeout: number;
|
|
22
|
-
private maxReconnectAttempts: number;
|
|
23
|
-
private reconnectDelayMs: number;
|
|
24
|
-
private heartbeatIntervalMs: number;
|
|
25
|
-
private authMode: "header" | "query";
|
|
26
23
|
private authToken?: string;
|
|
27
24
|
private WebSocketClass: typeof WebSocket;
|
|
28
25
|
|
|
29
26
|
private ws?: WebSocket;
|
|
30
|
-
private reconnectAttempts = 0;
|
|
31
|
-
private heartbeatInterval?: NodeJS.Timeout;
|
|
32
27
|
private messageHandlers: Set<WSMessageHandler> = new Set();
|
|
33
28
|
private errorHandlers: Set<WSErrorHandler> = new Set();
|
|
34
29
|
private closeHandlers: Set<WSCloseHandler> = new Set();
|
|
35
|
-
private
|
|
30
|
+
private openHandlers: Set<WSOpenHandler> = new Set();
|
|
31
|
+
private isClosed = false;
|
|
36
32
|
|
|
37
33
|
constructor(config: WSClientConfig) {
|
|
38
34
|
this.url = config.wsURL;
|
|
39
35
|
this.timeout = config.timeout ?? 30000;
|
|
40
|
-
this.maxReconnectAttempts = config.maxReconnectAttempts ?? 5;
|
|
41
|
-
this.reconnectDelayMs = config.reconnectDelayMs ?? 1000;
|
|
42
|
-
this.heartbeatIntervalMs = config.heartbeatIntervalMs ?? 30000;
|
|
43
|
-
this.authMode = config.authMode ?? "header";
|
|
44
36
|
this.authToken = config.authToken;
|
|
45
37
|
this.WebSocketClass = config.WebSocket ?? WebSocket;
|
|
46
38
|
}
|
|
47
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Connect to WebSocket server
|
|
42
|
+
*/
|
|
48
43
|
connect(): Promise<void> {
|
|
49
44
|
return new Promise((resolve, reject) => {
|
|
50
45
|
try {
|
|
51
46
|
const wsUrl = this.buildWSUrl();
|
|
52
47
|
this.ws = new this.WebSocketClass(wsUrl);
|
|
53
|
-
|
|
54
|
-
// Note: Custom headers via ws library in Node.js are not sent with WebSocket upgrade requests
|
|
55
|
-
// so we rely on query parameters for authentication
|
|
48
|
+
this.isClosed = false;
|
|
56
49
|
|
|
57
50
|
const timeout = setTimeout(() => {
|
|
58
51
|
this.ws?.close();
|
|
59
|
-
reject(
|
|
52
|
+
reject(
|
|
53
|
+
new SDKError("WebSocket connection timeout", 408, "WS_TIMEOUT")
|
|
54
|
+
);
|
|
60
55
|
}, this.timeout);
|
|
61
56
|
|
|
62
57
|
this.ws.addEventListener("open", () => {
|
|
63
58
|
clearTimeout(timeout);
|
|
64
|
-
|
|
65
|
-
this.
|
|
59
|
+
console.log("[WSClient] Connected to", this.url);
|
|
60
|
+
this.openHandlers.forEach((handler) => handler());
|
|
66
61
|
resolve();
|
|
67
62
|
});
|
|
68
63
|
|
|
@@ -72,24 +67,16 @@ export class WSClient {
|
|
|
72
67
|
});
|
|
73
68
|
|
|
74
69
|
this.ws.addEventListener("error", (event: Event) => {
|
|
70
|
+
console.error("[WSClient] WebSocket error:", event);
|
|
75
71
|
clearTimeout(timeout);
|
|
76
|
-
const error = new SDKError(
|
|
77
|
-
"WebSocket error",
|
|
78
|
-
500,
|
|
79
|
-
"WS_ERROR",
|
|
80
|
-
event
|
|
81
|
-
);
|
|
72
|
+
const error = new SDKError("WebSocket error", 500, "WS_ERROR", event);
|
|
82
73
|
this.errorHandlers.forEach((handler) => handler(error));
|
|
83
74
|
});
|
|
84
75
|
|
|
85
76
|
this.ws.addEventListener("close", () => {
|
|
86
77
|
clearTimeout(timeout);
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
this.attemptReconnect();
|
|
90
|
-
} else {
|
|
91
|
-
this.closeHandlers.forEach((handler) => handler());
|
|
92
|
-
}
|
|
78
|
+
console.log("[WSClient] Connection closed");
|
|
79
|
+
this.closeHandlers.forEach((handler) => handler());
|
|
93
80
|
});
|
|
94
81
|
} catch (error) {
|
|
95
82
|
reject(error);
|
|
@@ -97,86 +84,106 @@ export class WSClient {
|
|
|
97
84
|
});
|
|
98
85
|
}
|
|
99
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Build WebSocket URL with auth token
|
|
89
|
+
*/
|
|
100
90
|
private buildWSUrl(): string {
|
|
101
91
|
let url = this.url;
|
|
102
|
-
|
|
103
|
-
// Always append auth token as query parameter for compatibility
|
|
104
|
-
// Works in both Node.js and browser environments
|
|
92
|
+
|
|
105
93
|
if (this.authToken) {
|
|
106
94
|
const separator = url.includes("?") ? "&" : "?";
|
|
107
95
|
const paramName = this.authToken.startsWith("ak_") ? "api_key" : "token";
|
|
108
96
|
url += `${separator}${paramName}=${encodeURIComponent(this.authToken)}`;
|
|
109
97
|
}
|
|
110
|
-
|
|
111
|
-
return url;
|
|
112
|
-
}
|
|
113
98
|
|
|
114
|
-
|
|
115
|
-
this.heartbeatInterval = setInterval(() => {
|
|
116
|
-
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
117
|
-
this.ws.send(JSON.stringify({ type: "ping" }));
|
|
118
|
-
}
|
|
119
|
-
}, this.heartbeatIntervalMs);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
private stopHeartbeat() {
|
|
123
|
-
if (this.heartbeatInterval) {
|
|
124
|
-
clearInterval(this.heartbeatInterval);
|
|
125
|
-
this.heartbeatInterval = undefined;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
private attemptReconnect() {
|
|
130
|
-
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
|
131
|
-
this.reconnectAttempts++;
|
|
132
|
-
const delayMs = this.reconnectDelayMs * this.reconnectAttempts;
|
|
133
|
-
setTimeout(() => {
|
|
134
|
-
this.connect().catch((error) => {
|
|
135
|
-
this.errorHandlers.forEach((handler) => handler(error));
|
|
136
|
-
});
|
|
137
|
-
}, delayMs);
|
|
138
|
-
} else {
|
|
139
|
-
this.closeHandlers.forEach((handler) => handler());
|
|
140
|
-
}
|
|
99
|
+
return url;
|
|
141
100
|
}
|
|
142
101
|
|
|
143
|
-
|
|
102
|
+
/**
|
|
103
|
+
* Register message handler
|
|
104
|
+
*/
|
|
105
|
+
onMessage(handler: WSMessageHandler): () => void {
|
|
144
106
|
this.messageHandlers.add(handler);
|
|
145
107
|
return () => this.messageHandlers.delete(handler);
|
|
146
108
|
}
|
|
147
109
|
|
|
148
|
-
|
|
110
|
+
/**
|
|
111
|
+
* Unregister message handler
|
|
112
|
+
*/
|
|
113
|
+
offMessage(handler: WSMessageHandler): void {
|
|
114
|
+
this.messageHandlers.delete(handler);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Register error handler
|
|
119
|
+
*/
|
|
120
|
+
onError(handler: WSErrorHandler): () => void {
|
|
149
121
|
this.errorHandlers.add(handler);
|
|
150
122
|
return () => this.errorHandlers.delete(handler);
|
|
151
123
|
}
|
|
152
124
|
|
|
153
|
-
|
|
125
|
+
/**
|
|
126
|
+
* Unregister error handler
|
|
127
|
+
*/
|
|
128
|
+
offError(handler: WSErrorHandler): void {
|
|
129
|
+
this.errorHandlers.delete(handler);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Register close handler
|
|
134
|
+
*/
|
|
135
|
+
onClose(handler: WSCloseHandler): () => void {
|
|
154
136
|
this.closeHandlers.add(handler);
|
|
155
137
|
return () => this.closeHandlers.delete(handler);
|
|
156
138
|
}
|
|
157
139
|
|
|
158
|
-
|
|
140
|
+
/**
|
|
141
|
+
* Unregister close handler
|
|
142
|
+
*/
|
|
143
|
+
offClose(handler: WSCloseHandler): void {
|
|
144
|
+
this.closeHandlers.delete(handler);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Register open handler
|
|
149
|
+
*/
|
|
150
|
+
onOpen(handler: WSOpenHandler): () => void {
|
|
151
|
+
this.openHandlers.add(handler);
|
|
152
|
+
return () => this.openHandlers.delete(handler);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Send data through WebSocket
|
|
157
|
+
*/
|
|
158
|
+
send(data: string): void {
|
|
159
159
|
if (this.ws?.readyState !== WebSocket.OPEN) {
|
|
160
|
-
throw new SDKError(
|
|
161
|
-
"WebSocket is not connected",
|
|
162
|
-
500,
|
|
163
|
-
"WS_NOT_CONNECTED"
|
|
164
|
-
);
|
|
160
|
+
throw new SDKError("WebSocket is not connected", 500, "WS_NOT_CONNECTED");
|
|
165
161
|
}
|
|
166
162
|
this.ws.send(data);
|
|
167
163
|
}
|
|
168
164
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
165
|
+
/**
|
|
166
|
+
* Close WebSocket connection
|
|
167
|
+
*/
|
|
168
|
+
close(): void {
|
|
169
|
+
if (this.isClosed) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
this.isClosed = true;
|
|
172
173
|
this.ws?.close();
|
|
173
174
|
}
|
|
174
175
|
|
|
176
|
+
/**
|
|
177
|
+
* Check if WebSocket is connected
|
|
178
|
+
*/
|
|
175
179
|
isConnected(): boolean {
|
|
176
|
-
return this.ws?.readyState === WebSocket.OPEN;
|
|
180
|
+
return !this.isClosed && this.ws?.readyState === WebSocket.OPEN;
|
|
177
181
|
}
|
|
178
182
|
|
|
179
|
-
|
|
183
|
+
/**
|
|
184
|
+
* Update auth token
|
|
185
|
+
*/
|
|
186
|
+
setAuthToken(token?: string): void {
|
|
180
187
|
this.authToken = token;
|
|
181
188
|
}
|
|
182
189
|
}
|
package/src/db/repository.ts
CHANGED
|
@@ -98,7 +98,9 @@ export class Repository<T extends Record<string, any>> {
|
|
|
98
98
|
private buildInsertSql(entity: T): string {
|
|
99
99
|
const columns = Object.keys(entity).filter((k) => entity[k] !== undefined);
|
|
100
100
|
const placeholders = columns.map(() => "?").join(", ");
|
|
101
|
-
return `INSERT INTO ${this.tableName} (${columns.join(
|
|
101
|
+
return `INSERT INTO ${this.tableName} (${columns.join(
|
|
102
|
+
", "
|
|
103
|
+
)}) VALUES (${placeholders})`;
|
|
102
104
|
}
|
|
103
105
|
|
|
104
106
|
private buildInsertArgs(entity: T): any[] {
|
|
@@ -111,7 +113,9 @@ export class Repository<T extends Record<string, any>> {
|
|
|
111
113
|
const columns = Object.keys(entity)
|
|
112
114
|
.filter((k) => entity[k] !== undefined && k !== this.primaryKey)
|
|
113
115
|
.map((k) => `${k} = ?`);
|
|
114
|
-
return `UPDATE ${this.tableName} SET ${columns.join(", ")} WHERE ${
|
|
116
|
+
return `UPDATE ${this.tableName} SET ${columns.join(", ")} WHERE ${
|
|
117
|
+
this.primaryKey
|
|
118
|
+
} = ?`;
|
|
115
119
|
}
|
|
116
120
|
|
|
117
121
|
private buildUpdateArgs(entity: T): any[] {
|
package/src/index.ts
CHANGED
|
@@ -3,8 +3,14 @@ import { AuthClient } from "./auth/client";
|
|
|
3
3
|
import { DBClient } from "./db/client";
|
|
4
4
|
import { PubSubClient } from "./pubsub/client";
|
|
5
5
|
import { NetworkClient } from "./network/client";
|
|
6
|
+
import { CacheClient } from "./cache/client";
|
|
7
|
+
import { StorageClient } from "./storage/client";
|
|
6
8
|
import { WSClientConfig } from "./core/ws";
|
|
7
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
StorageAdapter,
|
|
11
|
+
MemoryStorage,
|
|
12
|
+
LocalStorageAdapter,
|
|
13
|
+
} from "./auth/types";
|
|
8
14
|
|
|
9
15
|
export interface ClientConfig extends Omit<HttpClientConfig, "fetch"> {
|
|
10
16
|
apiKey?: string;
|
|
@@ -19,6 +25,8 @@ export interface Client {
|
|
|
19
25
|
db: DBClient;
|
|
20
26
|
pubsub: PubSubClient;
|
|
21
27
|
network: NetworkClient;
|
|
28
|
+
cache: CacheClient;
|
|
29
|
+
storage: StorageClient;
|
|
22
30
|
}
|
|
23
31
|
|
|
24
32
|
export function createClient(config: ClientConfig): Client {
|
|
@@ -38,8 +46,9 @@ export function createClient(config: ClientConfig): Client {
|
|
|
38
46
|
});
|
|
39
47
|
|
|
40
48
|
// Derive WebSocket URL from baseURL if not explicitly provided
|
|
41
|
-
const wsURL =
|
|
42
|
-
config.
|
|
49
|
+
const wsURL =
|
|
50
|
+
config.wsConfig?.wsURL ??
|
|
51
|
+
config.baseURL.replace(/^http/, "ws").replace(/\/$/, "");
|
|
43
52
|
|
|
44
53
|
const db = new DBClient(httpClient);
|
|
45
54
|
const pubsub = new PubSubClient(httpClient, {
|
|
@@ -47,16 +56,19 @@ export function createClient(config: ClientConfig): Client {
|
|
|
47
56
|
wsURL,
|
|
48
57
|
});
|
|
49
58
|
const network = new NetworkClient(httpClient);
|
|
59
|
+
const cache = new CacheClient(httpClient);
|
|
60
|
+
const storage = new StorageClient(httpClient);
|
|
50
61
|
|
|
51
62
|
return {
|
|
52
63
|
auth,
|
|
53
64
|
db,
|
|
54
65
|
pubsub,
|
|
55
66
|
network,
|
|
67
|
+
cache,
|
|
68
|
+
storage,
|
|
56
69
|
};
|
|
57
70
|
}
|
|
58
71
|
|
|
59
|
-
// Re-exports
|
|
60
72
|
export { HttpClient } from "./core/http";
|
|
61
73
|
export { WSClient } from "./core/ws";
|
|
62
74
|
export { AuthClient } from "./auth/client";
|
|
@@ -65,13 +77,11 @@ export { QueryBuilder } from "./db/qb";
|
|
|
65
77
|
export { Repository } from "./db/repository";
|
|
66
78
|
export { PubSubClient, Subscription } from "./pubsub/client";
|
|
67
79
|
export { NetworkClient } from "./network/client";
|
|
80
|
+
export { CacheClient } from "./cache/client";
|
|
81
|
+
export { StorageClient } from "./storage/client";
|
|
68
82
|
export { SDKError } from "./errors";
|
|
69
83
|
export { MemoryStorage, LocalStorageAdapter } from "./auth/types";
|
|
70
|
-
export type {
|
|
71
|
-
StorageAdapter,
|
|
72
|
-
AuthConfig,
|
|
73
|
-
WhoAmI,
|
|
74
|
-
} from "./auth/types";
|
|
84
|
+
export type { StorageAdapter, AuthConfig, WhoAmI } from "./auth/types";
|
|
75
85
|
export type * from "./db/types";
|
|
76
86
|
export type {
|
|
77
87
|
Message,
|
|
@@ -79,4 +89,28 @@ export type {
|
|
|
79
89
|
ErrorHandler,
|
|
80
90
|
CloseHandler,
|
|
81
91
|
} from "./pubsub/client";
|
|
82
|
-
export type {
|
|
92
|
+
export type {
|
|
93
|
+
PeerInfo,
|
|
94
|
+
NetworkStatus,
|
|
95
|
+
ProxyRequest,
|
|
96
|
+
ProxyResponse,
|
|
97
|
+
} from "./network/client";
|
|
98
|
+
export type {
|
|
99
|
+
CacheGetRequest,
|
|
100
|
+
CacheGetResponse,
|
|
101
|
+
CachePutRequest,
|
|
102
|
+
CachePutResponse,
|
|
103
|
+
CacheDeleteRequest,
|
|
104
|
+
CacheDeleteResponse,
|
|
105
|
+
CacheMultiGetRequest,
|
|
106
|
+
CacheMultiGetResponse,
|
|
107
|
+
CacheScanRequest,
|
|
108
|
+
CacheScanResponse,
|
|
109
|
+
CacheHealthResponse,
|
|
110
|
+
} from "./cache/client";
|
|
111
|
+
export type {
|
|
112
|
+
StorageUploadResponse,
|
|
113
|
+
StoragePinRequest,
|
|
114
|
+
StoragePinResponse,
|
|
115
|
+
StorageStatus,
|
|
116
|
+
} from "./storage/client";
|
package/src/network/client.ts
CHANGED
|
@@ -7,9 +7,25 @@ export interface PeerInfo {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
export interface NetworkStatus {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
node_id: string;
|
|
11
|
+
connected: boolean;
|
|
12
|
+
peer_count: number;
|
|
13
|
+
database_size: number;
|
|
14
|
+
uptime: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ProxyRequest {
|
|
18
|
+
url: string;
|
|
19
|
+
method: string;
|
|
20
|
+
headers?: Record<string, string>;
|
|
21
|
+
body?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ProxyResponse {
|
|
25
|
+
status_code: number;
|
|
26
|
+
headers: Record<string, string>;
|
|
27
|
+
body: string;
|
|
28
|
+
error?: string;
|
|
13
29
|
}
|
|
14
30
|
|
|
15
31
|
export class NetworkClient {
|
|
@@ -35,7 +51,9 @@ export class NetworkClient {
|
|
|
35
51
|
* Get network status.
|
|
36
52
|
*/
|
|
37
53
|
async status(): Promise<NetworkStatus> {
|
|
38
|
-
const response = await this.httpClient.get<NetworkStatus>(
|
|
54
|
+
const response = await this.httpClient.get<NetworkStatus>(
|
|
55
|
+
"/v1/network/status"
|
|
56
|
+
);
|
|
39
57
|
return response;
|
|
40
58
|
}
|
|
41
59
|
|
|
@@ -62,4 +80,40 @@ export class NetworkClient {
|
|
|
62
80
|
async disconnect(peerId: string): Promise<void> {
|
|
63
81
|
await this.httpClient.post("/v1/network/disconnect", { peer_id: peerId });
|
|
64
82
|
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Proxy an HTTP request through the Anyone network.
|
|
86
|
+
* Requires authentication (API key or JWT).
|
|
87
|
+
*
|
|
88
|
+
* @param request - The proxy request configuration
|
|
89
|
+
* @returns The proxied response
|
|
90
|
+
* @throws {SDKError} If the Anyone proxy is not available or the request fails
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```ts
|
|
94
|
+
* const response = await client.network.proxyAnon({
|
|
95
|
+
* url: 'https://api.example.com/data',
|
|
96
|
+
* method: 'GET',
|
|
97
|
+
* headers: {
|
|
98
|
+
* 'Accept': 'application/json'
|
|
99
|
+
* }
|
|
100
|
+
* });
|
|
101
|
+
*
|
|
102
|
+
* console.log(response.status_code); // 200
|
|
103
|
+
* console.log(response.body); // Response data
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
106
|
+
async proxyAnon(request: ProxyRequest): Promise<ProxyResponse> {
|
|
107
|
+
const response = await this.httpClient.post<ProxyResponse>(
|
|
108
|
+
"/v1/proxy/anon",
|
|
109
|
+
request
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
// Check if the response contains an error
|
|
113
|
+
if (response.error) {
|
|
114
|
+
throw new Error(`Proxy request failed: ${response.error}`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return response;
|
|
118
|
+
}
|
|
65
119
|
}
|