@pipsend/sdk 0.1.0
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/LICENSE +21 -0
- package/README.md +774 -0
- package/dist/index.d.mts +1194 -0
- package/dist/index.d.ts +1194 -0
- package/dist/index.js +1030 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1005 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +60 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1030 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
AuthenticationError: () => AuthenticationError,
|
|
24
|
+
ConfigurationError: () => ConfigurationError,
|
|
25
|
+
PipsendClient: () => PipsendClient,
|
|
26
|
+
PipsendError: () => PipsendError,
|
|
27
|
+
WebSocketError: () => WebSocketError,
|
|
28
|
+
createClient: () => createClient,
|
|
29
|
+
default: () => index_default
|
|
30
|
+
});
|
|
31
|
+
module.exports = __toCommonJS(index_exports);
|
|
32
|
+
|
|
33
|
+
// src/types/index.ts
|
|
34
|
+
var PipsendError = class extends Error {
|
|
35
|
+
constructor(message, code, statusCode) {
|
|
36
|
+
super(message);
|
|
37
|
+
this.code = code;
|
|
38
|
+
this.statusCode = statusCode;
|
|
39
|
+
this.name = "PipsendError";
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
var AuthenticationError = class extends PipsendError {
|
|
43
|
+
constructor(message) {
|
|
44
|
+
super(message, "AUTH_ERROR", 401);
|
|
45
|
+
this.name = "AuthenticationError";
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
var ConfigurationError = class extends PipsendError {
|
|
49
|
+
constructor(message) {
|
|
50
|
+
super(message, "CONFIG_ERROR");
|
|
51
|
+
this.name = "ConfigurationError";
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
var WebSocketError = class extends PipsendError {
|
|
55
|
+
constructor(message) {
|
|
56
|
+
super(message, "WEBSOCKET_ERROR");
|
|
57
|
+
this.name = "WebSocketError";
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// src/core/auth.ts
|
|
62
|
+
var AuthManager = class {
|
|
63
|
+
constructor(config) {
|
|
64
|
+
this.config = config;
|
|
65
|
+
const marginMinutes = config.refreshMarginMinutes ?? 5;
|
|
66
|
+
this.refreshMarginMs = marginMinutes * 60 * 1e3;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Checks if a valid token is available
|
|
70
|
+
*/
|
|
71
|
+
isAuthenticated() {
|
|
72
|
+
return !!this.tokenInfo && !this.isTokenExpired();
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Gets the current token (if it exists and is valid)
|
|
76
|
+
*/
|
|
77
|
+
getToken() {
|
|
78
|
+
if (this.isAuthenticated()) {
|
|
79
|
+
return this.tokenInfo?.token;
|
|
80
|
+
}
|
|
81
|
+
return void 0;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Ensures a valid token exists, authenticating if necessary
|
|
85
|
+
*/
|
|
86
|
+
async ensureAuthenticated() {
|
|
87
|
+
if (!this.isAuthenticated()) {
|
|
88
|
+
await this.authenticate();
|
|
89
|
+
}
|
|
90
|
+
return this.tokenInfo.token;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Performs authentication with the server
|
|
94
|
+
* TODO: Implement real API call when available
|
|
95
|
+
*/
|
|
96
|
+
async authenticate() {
|
|
97
|
+
try {
|
|
98
|
+
const mockResponse = {
|
|
99
|
+
token: "mock_token_" + Date.now(),
|
|
100
|
+
expiresIn: 180 * 60
|
|
101
|
+
// 180 minutes in seconds
|
|
102
|
+
};
|
|
103
|
+
this.setTokenInfo(mockResponse);
|
|
104
|
+
console.log("\u2705 Authentication successful (mock)");
|
|
105
|
+
} catch (error) {
|
|
106
|
+
if (error instanceof AuthenticationError) {
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
109
|
+
throw new AuthenticationError(
|
|
110
|
+
`Authentication error: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Forces a re-authentication
|
|
116
|
+
*/
|
|
117
|
+
async refreshToken() {
|
|
118
|
+
this.tokenInfo = void 0;
|
|
119
|
+
await this.authenticate();
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Clears the current token
|
|
123
|
+
*/
|
|
124
|
+
clearToken() {
|
|
125
|
+
this.tokenInfo = void 0;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Checks if the token is expired or about to expire
|
|
129
|
+
*/
|
|
130
|
+
isTokenExpired() {
|
|
131
|
+
if (!this.tokenInfo) return true;
|
|
132
|
+
const now = Date.now();
|
|
133
|
+
const expiryWithMargin = this.tokenInfo.expiresAt.getTime() - this.refreshMarginMs;
|
|
134
|
+
return now >= expiryWithMargin;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Stores the received token information
|
|
138
|
+
*/
|
|
139
|
+
setTokenInfo(response) {
|
|
140
|
+
const now = /* @__PURE__ */ new Date();
|
|
141
|
+
const expiresAt = new Date(now.getTime() + response.expiresIn * 1e3);
|
|
142
|
+
this.tokenInfo = {
|
|
143
|
+
token: response.token,
|
|
144
|
+
issuedAt: now,
|
|
145
|
+
expiresAt
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
// src/core/websocket.ts
|
|
151
|
+
var WebSocketManager = class {
|
|
152
|
+
constructor(config, authManager) {
|
|
153
|
+
this.config = config;
|
|
154
|
+
this.authManager = authManager;
|
|
155
|
+
// WebSocket instance (browser or ws package)
|
|
156
|
+
this.reconnectAttempts = 0;
|
|
157
|
+
this.listeners = /* @__PURE__ */ new Map();
|
|
158
|
+
this.isConnected = false;
|
|
159
|
+
this.subscribedChannels = [];
|
|
160
|
+
this.isReconnecting = false;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Connects to the WebSocket server
|
|
164
|
+
*/
|
|
165
|
+
async connect() {
|
|
166
|
+
if (this.isConnected) {
|
|
167
|
+
console.log("\u26A0\uFE0F WebSocket already connected");
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (this.isReconnecting) {
|
|
171
|
+
console.log("\u26A0\uFE0F WebSocket reconnection in progress");
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
const wsUrl = this.buildWebSocketUrl();
|
|
175
|
+
const token = await this.authManager.ensureAuthenticated();
|
|
176
|
+
return new Promise((resolve, reject) => {
|
|
177
|
+
try {
|
|
178
|
+
const WS = this.getWebSocketConstructor();
|
|
179
|
+
this.ws = new WS(wsUrl);
|
|
180
|
+
this.ws.onopen = () => {
|
|
181
|
+
console.log("\u2705 WebSocket connected");
|
|
182
|
+
this.isConnected = true;
|
|
183
|
+
this.isReconnecting = false;
|
|
184
|
+
this.reconnectAttempts = 0;
|
|
185
|
+
this.send({
|
|
186
|
+
type: "auth",
|
|
187
|
+
payload: { token },
|
|
188
|
+
timestamp: Date.now()
|
|
189
|
+
});
|
|
190
|
+
this.emit("connected", { timestamp: Date.now() });
|
|
191
|
+
if (this.subscribedChannels.length > 0) {
|
|
192
|
+
console.log("\u{1F504} Resubscribing to channels:", this.subscribedChannels);
|
|
193
|
+
this.subscribe(this.subscribedChannels);
|
|
194
|
+
}
|
|
195
|
+
this.startHeartbeat();
|
|
196
|
+
resolve();
|
|
197
|
+
};
|
|
198
|
+
this.ws.onmessage = (event) => {
|
|
199
|
+
try {
|
|
200
|
+
const data = JSON.parse(event.data);
|
|
201
|
+
this.handleMessage(data);
|
|
202
|
+
} catch (error) {
|
|
203
|
+
console.error("\u274C Failed to parse WebSocket message:", error);
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
this.ws.onerror = (error) => {
|
|
207
|
+
console.error("\u274C WebSocket error:", error);
|
|
208
|
+
this.emit("error", error);
|
|
209
|
+
if (!this.isConnected) {
|
|
210
|
+
reject(new WebSocketError("Failed to connect to WebSocket server"));
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
this.ws.onclose = (event) => {
|
|
214
|
+
console.log("WebSocket disconnected", event.code, event.reason);
|
|
215
|
+
this.isConnected = false;
|
|
216
|
+
this.stopHeartbeat();
|
|
217
|
+
this.emit("disconnected", {
|
|
218
|
+
code: event.code,
|
|
219
|
+
reason: event.reason,
|
|
220
|
+
timestamp: Date.now()
|
|
221
|
+
});
|
|
222
|
+
this.handleReconnect();
|
|
223
|
+
};
|
|
224
|
+
} catch (error) {
|
|
225
|
+
reject(new WebSocketError(
|
|
226
|
+
`Failed to create WebSocket: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
227
|
+
));
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Subscribes to channels (symbols, events, etc.)
|
|
233
|
+
* TODO: Adjust message format according to real WebSocket API
|
|
234
|
+
*/
|
|
235
|
+
subscribe(channels) {
|
|
236
|
+
if (!this.isConnected) {
|
|
237
|
+
throw new WebSocketError("WebSocket not connected. Call connect() first.");
|
|
238
|
+
}
|
|
239
|
+
this.subscribedChannels = [.../* @__PURE__ */ new Set([...this.subscribedChannels, ...channels])];
|
|
240
|
+
this.send({
|
|
241
|
+
type: "subscribe",
|
|
242
|
+
payload: { channels },
|
|
243
|
+
timestamp: Date.now()
|
|
244
|
+
});
|
|
245
|
+
console.log("\u{1F4E1} Subscribed to channels:", channels);
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Unsubscribes from channels
|
|
249
|
+
* TODO: Adjust message format according to real WebSocket API
|
|
250
|
+
*/
|
|
251
|
+
unsubscribe(channels) {
|
|
252
|
+
if (!this.isConnected) {
|
|
253
|
+
console.warn("WebSocket not connected, cannot unsubscribe");
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
this.subscribedChannels = this.subscribedChannels.filter(
|
|
257
|
+
(ch) => !channels.includes(ch)
|
|
258
|
+
);
|
|
259
|
+
this.send({
|
|
260
|
+
type: "unsubscribe",
|
|
261
|
+
payload: { channels },
|
|
262
|
+
timestamp: Date.now()
|
|
263
|
+
});
|
|
264
|
+
console.log("\u{1F4E1} Unsubscribed from channels:", channels);
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Registers an event listener
|
|
268
|
+
*/
|
|
269
|
+
on(event, callback) {
|
|
270
|
+
if (!this.listeners.has(event)) {
|
|
271
|
+
this.listeners.set(event, /* @__PURE__ */ new Set());
|
|
272
|
+
}
|
|
273
|
+
this.listeners.get(event).add(callback);
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Removes an event listener
|
|
277
|
+
*/
|
|
278
|
+
off(event, callback) {
|
|
279
|
+
if (!callback) {
|
|
280
|
+
this.listeners.delete(event);
|
|
281
|
+
} else {
|
|
282
|
+
this.listeners.get(event)?.delete(callback);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Disconnects from WebSocket
|
|
287
|
+
*/
|
|
288
|
+
disconnect() {
|
|
289
|
+
console.log("\u{1F44B} Disconnecting WebSocket...");
|
|
290
|
+
this.stopHeartbeat();
|
|
291
|
+
if (this.ws) {
|
|
292
|
+
this.ws.close();
|
|
293
|
+
this.ws = void 0;
|
|
294
|
+
}
|
|
295
|
+
this.isConnected = false;
|
|
296
|
+
this.isReconnecting = false;
|
|
297
|
+
this.subscribedChannels = [];
|
|
298
|
+
this.listeners.clear();
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Checks if WebSocket is connected
|
|
302
|
+
*/
|
|
303
|
+
isWebSocketConnected() {
|
|
304
|
+
return this.isConnected;
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Gets list of subscribed channels
|
|
308
|
+
*/
|
|
309
|
+
getSubscribedChannels() {
|
|
310
|
+
return [...this.subscribedChannels];
|
|
311
|
+
}
|
|
312
|
+
// Private methods
|
|
313
|
+
/**
|
|
314
|
+
* Builds WebSocket URL from HTTP(S) server URL
|
|
315
|
+
*/
|
|
316
|
+
buildWebSocketUrl() {
|
|
317
|
+
try {
|
|
318
|
+
const url = new URL(this.config.server);
|
|
319
|
+
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
320
|
+
const wsPath = this.config.websocket?.path || "/ws";
|
|
321
|
+
url.pathname = wsPath;
|
|
322
|
+
return url.toString();
|
|
323
|
+
} catch (error) {
|
|
324
|
+
throw new WebSocketError("Invalid server URL for WebSocket connection");
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Gets WebSocket constructor (browser or Node.js)
|
|
329
|
+
*/
|
|
330
|
+
getWebSocketConstructor() {
|
|
331
|
+
if (typeof WebSocket !== "undefined") {
|
|
332
|
+
return WebSocket;
|
|
333
|
+
}
|
|
334
|
+
try {
|
|
335
|
+
return require("ws");
|
|
336
|
+
} catch {
|
|
337
|
+
throw new WebSocketError(
|
|
338
|
+
'WebSocket not available. Install "ws" package for Node.js: npm install ws'
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Sends a message through WebSocket
|
|
344
|
+
*/
|
|
345
|
+
send(message) {
|
|
346
|
+
if (!this.ws || !this.isConnected) {
|
|
347
|
+
console.warn("\u26A0\uFE0F WebSocket not ready, message not sent:", message.type);
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
try {
|
|
351
|
+
const payload = JSON.stringify(message);
|
|
352
|
+
this.ws.send(payload);
|
|
353
|
+
} catch (error) {
|
|
354
|
+
console.error("\u274C Failed to send WebSocket message:", error);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Handles incoming WebSocket messages
|
|
359
|
+
* TODO: Adjust message handling according to real WebSocket API
|
|
360
|
+
*/
|
|
361
|
+
handleMessage(data) {
|
|
362
|
+
if (data.type === "authenticated") {
|
|
363
|
+
console.log("\u2705 WebSocket authenticated");
|
|
364
|
+
this.emit("authenticated", data.payload || {});
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
if (data.type === "pong") {
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
const eventType = data.type;
|
|
371
|
+
this.emit(eventType, data.payload || data);
|
|
372
|
+
this.emit("*", data);
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Emits an event to all registered listeners
|
|
376
|
+
*/
|
|
377
|
+
emit(event, data) {
|
|
378
|
+
const listeners = this.listeners.get(event);
|
|
379
|
+
if (listeners && listeners.size > 0) {
|
|
380
|
+
listeners.forEach((callback) => {
|
|
381
|
+
try {
|
|
382
|
+
callback(data);
|
|
383
|
+
} catch (error) {
|
|
384
|
+
console.error(`\u274C Error in WebSocket listener for "${event}":`, error);
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Handles reconnection logic with exponential backoff
|
|
391
|
+
*/
|
|
392
|
+
handleReconnect() {
|
|
393
|
+
const autoReconnect = this.config.websocket?.autoReconnect ?? true;
|
|
394
|
+
const maxAttempts = this.config.websocket?.maxReconnectAttempts ?? 5;
|
|
395
|
+
if (!autoReconnect) {
|
|
396
|
+
console.log("Auto-reconnect disabled");
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
if (this.reconnectAttempts >= maxAttempts) {
|
|
400
|
+
console.log(`\u274C Max reconnection attempts (${maxAttempts}) reached. Giving up.`);
|
|
401
|
+
this.emit("error", new WebSocketError("Max reconnection attempts reached"));
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
const delay = Math.min(1e3 * Math.pow(2, this.reconnectAttempts), 3e4);
|
|
405
|
+
console.log(
|
|
406
|
+
`\u{1F504} Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts + 1}/${maxAttempts})...`
|
|
407
|
+
);
|
|
408
|
+
this.isReconnecting = true;
|
|
409
|
+
setTimeout(() => {
|
|
410
|
+
this.reconnectAttempts++;
|
|
411
|
+
this.connect().catch((error) => {
|
|
412
|
+
console.error("\u274C Reconnection failed:", error);
|
|
413
|
+
});
|
|
414
|
+
}, delay);
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Starts heartbeat to keep connection alive
|
|
418
|
+
*/
|
|
419
|
+
startHeartbeat() {
|
|
420
|
+
const interval = this.config.websocket?.heartbeatInterval || 3e4;
|
|
421
|
+
this.heartbeatTimer = setInterval(() => {
|
|
422
|
+
if (this.isConnected) {
|
|
423
|
+
this.send({
|
|
424
|
+
type: "ping",
|
|
425
|
+
timestamp: Date.now()
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
}, interval);
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Stops heartbeat timer
|
|
432
|
+
*/
|
|
433
|
+
stopHeartbeat() {
|
|
434
|
+
if (this.heartbeatTimer) {
|
|
435
|
+
clearInterval(this.heartbeatTimer);
|
|
436
|
+
this.heartbeatTimer = void 0;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
// src/api/http-client.ts
|
|
442
|
+
var HttpClient = class {
|
|
443
|
+
constructor(baseUrl) {
|
|
444
|
+
this.baseUrl = baseUrl;
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Makes an HTTP request
|
|
448
|
+
*/
|
|
449
|
+
async request(endpoint, options = {}) {
|
|
450
|
+
const url = `${this.baseUrl}${endpoint}`;
|
|
451
|
+
try {
|
|
452
|
+
const response = await fetch(url, {
|
|
453
|
+
...options,
|
|
454
|
+
headers: {
|
|
455
|
+
"Content-Type": "application/json",
|
|
456
|
+
...options.headers
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
if (!response.ok) {
|
|
460
|
+
const errorText = await response.text();
|
|
461
|
+
throw new PipsendError(
|
|
462
|
+
`HTTP ${response.status}: ${errorText || response.statusText}`,
|
|
463
|
+
"HTTP_ERROR",
|
|
464
|
+
response.status
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
const text = await response.text();
|
|
468
|
+
if (!text) {
|
|
469
|
+
return {};
|
|
470
|
+
}
|
|
471
|
+
return JSON.parse(text);
|
|
472
|
+
} catch (error) {
|
|
473
|
+
if (error instanceof PipsendError) {
|
|
474
|
+
throw error;
|
|
475
|
+
}
|
|
476
|
+
throw new PipsendError(
|
|
477
|
+
`Request failed: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
478
|
+
"NETWORK_ERROR"
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* GET request
|
|
484
|
+
*/
|
|
485
|
+
async get(endpoint, params) {
|
|
486
|
+
const queryString = params ? this.buildQueryString(params) : "";
|
|
487
|
+
const url = queryString ? `${endpoint}?${queryString}` : endpoint;
|
|
488
|
+
return this.request(url, { method: "GET" });
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* POST request
|
|
492
|
+
*/
|
|
493
|
+
async post(endpoint, body) {
|
|
494
|
+
return this.request(endpoint, {
|
|
495
|
+
method: "POST",
|
|
496
|
+
body: body ? JSON.stringify(body) : void 0
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* PUT request
|
|
501
|
+
*/
|
|
502
|
+
async put(endpoint, body) {
|
|
503
|
+
return this.request(endpoint, {
|
|
504
|
+
method: "PUT",
|
|
505
|
+
body: body ? JSON.stringify(body) : void 0
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* DELETE request
|
|
510
|
+
*/
|
|
511
|
+
async delete(endpoint) {
|
|
512
|
+
return this.request(endpoint, { method: "DELETE" });
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Builds query string from params object
|
|
516
|
+
*/
|
|
517
|
+
buildQueryString(params) {
|
|
518
|
+
const filtered = Object.entries(params).filter(([_, value]) => value !== void 0 && value !== null && value !== "").map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
|
|
519
|
+
return filtered.join("&");
|
|
520
|
+
}
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
// src/api/accounts.ts
|
|
524
|
+
var AccountsAPI = class {
|
|
525
|
+
constructor(http) {
|
|
526
|
+
this.http = http;
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* List all accounts with optional filters and pagination
|
|
530
|
+
*/
|
|
531
|
+
async list(params) {
|
|
532
|
+
return this.http.get("/api/v1/accounts", params);
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Get all account logins
|
|
536
|
+
*/
|
|
537
|
+
async getLogins() {
|
|
538
|
+
return this.http.get("/api/v1/accounts/logins");
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Get account statistics with optional filters
|
|
542
|
+
*/
|
|
543
|
+
async getStatistics(params) {
|
|
544
|
+
return this.http.get("/api/v1/accounts/statistics", params);
|
|
545
|
+
}
|
|
546
|
+
/**
|
|
547
|
+
* Change master password for an account
|
|
548
|
+
*/
|
|
549
|
+
async changeMasterPassword(login, request) {
|
|
550
|
+
return this.http.put(`/api/v1/accounts/${login}/password/master`, request);
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Change investor password for an account
|
|
554
|
+
*/
|
|
555
|
+
async changeInvestorPassword(login, request) {
|
|
556
|
+
return this.http.put(`/api/v1/accounts/${login}/password/investor`, request);
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* Archive an account
|
|
560
|
+
*/
|
|
561
|
+
async archive(login) {
|
|
562
|
+
return this.http.put(`/api/v1/accounts/${login}/archive`);
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* Unarchive an account
|
|
566
|
+
*/
|
|
567
|
+
async unarchive(login) {
|
|
568
|
+
return this.http.put(`/api/v1/accounts/${login}/unarchive`);
|
|
569
|
+
}
|
|
570
|
+
};
|
|
571
|
+
|
|
572
|
+
// src/api/trading-groups.ts
|
|
573
|
+
var TradingGroupsAPI = class {
|
|
574
|
+
constructor(http) {
|
|
575
|
+
this.http = http;
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* List all trading groups with optional filters
|
|
579
|
+
*/
|
|
580
|
+
async list(params) {
|
|
581
|
+
return this.http.get("/api/v1/trading-groups", params);
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* Get trading group by name
|
|
585
|
+
*/
|
|
586
|
+
async getByName(name) {
|
|
587
|
+
return this.http.get(`/api/v1/trading-groups/${encodeURIComponent(name)}`);
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* Get all trading group names
|
|
591
|
+
*/
|
|
592
|
+
async getNames() {
|
|
593
|
+
return this.http.get("/api/v1/trading-groups/names");
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Get trading group statistics
|
|
597
|
+
*/
|
|
598
|
+
async getStatistics(type) {
|
|
599
|
+
return this.http.get("/api/v1/trading-groups/statistics", type ? { type } : void 0);
|
|
600
|
+
}
|
|
601
|
+
};
|
|
602
|
+
|
|
603
|
+
// src/api/market-data.ts
|
|
604
|
+
var MarketDataAPI = class {
|
|
605
|
+
constructor(http) {
|
|
606
|
+
this.http = http;
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Get historical OHLCV candles
|
|
610
|
+
*
|
|
611
|
+
* @param params - Candles parameters (symbol and timeframe are required)
|
|
612
|
+
* @returns Paginated candles or array of candles
|
|
613
|
+
*
|
|
614
|
+
* @example
|
|
615
|
+
* ```ts
|
|
616
|
+
* // Get last 100 candles
|
|
617
|
+
* const candles = await client.marketData.getCandles({
|
|
618
|
+
* symbol: 'BTCUSD',
|
|
619
|
+
* timeframe: '1m',
|
|
620
|
+
* limit: 100,
|
|
621
|
+
* order: 'desc'
|
|
622
|
+
* });
|
|
623
|
+
*
|
|
624
|
+
* // Get candles with time range
|
|
625
|
+
* const candles = await client.marketData.getCandles({
|
|
626
|
+
* symbol: 'BTCUSD,ETHUSD',
|
|
627
|
+
* timeframe: '1h',
|
|
628
|
+
* from: '2025-11-01T00:00:00Z',
|
|
629
|
+
* to: '2025-11-02T00:00:00Z'
|
|
630
|
+
* });
|
|
631
|
+
* ```
|
|
632
|
+
*/
|
|
633
|
+
async getCandles(params) {
|
|
634
|
+
return this.http.get("/api/v1/candles", params);
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* Get available symbols with metadata
|
|
638
|
+
*
|
|
639
|
+
* @param params - Optional filters for symbols
|
|
640
|
+
* @returns Paginated symbols or array of symbols
|
|
641
|
+
*
|
|
642
|
+
* @example
|
|
643
|
+
* ```ts
|
|
644
|
+
* // Get all symbols
|
|
645
|
+
* const symbols = await client.marketData.getSymbols();
|
|
646
|
+
*
|
|
647
|
+
* // Filter by group
|
|
648
|
+
* const cryptoSymbols = await client.marketData.getSymbols({
|
|
649
|
+
* group: 'CRYPTO_MAJOR',
|
|
650
|
+
* has_data: true
|
|
651
|
+
* });
|
|
652
|
+
* ```
|
|
653
|
+
*/
|
|
654
|
+
async getSymbols(params) {
|
|
655
|
+
return this.http.get("/api/v1/symbols", params);
|
|
656
|
+
}
|
|
657
|
+
/**
|
|
658
|
+
* Get symbol groups hierarchy
|
|
659
|
+
*
|
|
660
|
+
* Note: This endpoint never uses pagination
|
|
661
|
+
*
|
|
662
|
+
* @param params - Optional filters for groups
|
|
663
|
+
* @returns Array of groups
|
|
664
|
+
*
|
|
665
|
+
* @example
|
|
666
|
+
* ```ts
|
|
667
|
+
* // Get all groups
|
|
668
|
+
* const groups = await client.marketData.getGroups();
|
|
669
|
+
*
|
|
670
|
+
* // Get only root groups
|
|
671
|
+
* const rootGroups = await client.marketData.getGroups({
|
|
672
|
+
* root_only: true
|
|
673
|
+
* });
|
|
674
|
+
* ```
|
|
675
|
+
*/
|
|
676
|
+
async getGroups(params) {
|
|
677
|
+
return this.http.get("/api/v1/groups", params);
|
|
678
|
+
}
|
|
679
|
+
};
|
|
680
|
+
|
|
681
|
+
// src/api/positions.ts
|
|
682
|
+
var PositionsAPI = class {
|
|
683
|
+
constructor(http) {
|
|
684
|
+
this.http = http;
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* List positions with optional filters and pagination
|
|
688
|
+
*/
|
|
689
|
+
async list(params) {
|
|
690
|
+
return this.http.get("/api/v1/positions", params);
|
|
691
|
+
}
|
|
692
|
+
/**
|
|
693
|
+
* Get position statistics with optional filters and grouping
|
|
694
|
+
*/
|
|
695
|
+
async getStats(params) {
|
|
696
|
+
return this.http.get("/api/v1/positions/stats", params);
|
|
697
|
+
}
|
|
698
|
+
/**
|
|
699
|
+
* Get position totals with dynamic grouping
|
|
700
|
+
*/
|
|
701
|
+
async getTotals(params) {
|
|
702
|
+
return this.http.get("/api/v1/positions/totals", params);
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Update position (modify SL/TP)
|
|
706
|
+
*/
|
|
707
|
+
async update(id, request) {
|
|
708
|
+
return this.http.put(`/api/v1/positions/${id}`, request);
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
711
|
+
* Delete (close) position
|
|
712
|
+
*/
|
|
713
|
+
async delete(id, params) {
|
|
714
|
+
return this.http.delete(`/api/v1/positions/${id}?login=${params.login}`);
|
|
715
|
+
}
|
|
716
|
+
/**
|
|
717
|
+
* Check and recalculate positions
|
|
718
|
+
*/
|
|
719
|
+
async check(request) {
|
|
720
|
+
return this.http.post("/api/v1/positions/check", request);
|
|
721
|
+
}
|
|
722
|
+
};
|
|
723
|
+
|
|
724
|
+
// src/api/orders.ts
|
|
725
|
+
var OrdersAPI = class {
|
|
726
|
+
constructor(http) {
|
|
727
|
+
this.http = http;
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* List orders with optional filters and pagination
|
|
731
|
+
*/
|
|
732
|
+
async list(params) {
|
|
733
|
+
return this.http.get("/api/v1/orders", params);
|
|
734
|
+
}
|
|
735
|
+
/**
|
|
736
|
+
* Get order statistics with optional filters and grouping
|
|
737
|
+
*/
|
|
738
|
+
async getStats(params) {
|
|
739
|
+
return this.http.get("/api/v1/orders/stats", params);
|
|
740
|
+
}
|
|
741
|
+
/**
|
|
742
|
+
* Get order totals with dynamic grouping
|
|
743
|
+
*/
|
|
744
|
+
async getTotals(params) {
|
|
745
|
+
return this.http.get("/api/v1/orders/totals", params);
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* Update order (modify SL/TP)
|
|
749
|
+
*/
|
|
750
|
+
async update(id, request) {
|
|
751
|
+
return this.http.put(`/api/v1/orders/${id}`, request);
|
|
752
|
+
}
|
|
753
|
+
/**
|
|
754
|
+
* Delete (cancel) order
|
|
755
|
+
*/
|
|
756
|
+
async delete(id, params) {
|
|
757
|
+
return this.http.delete(`/api/v1/orders/${id}?login=${params.login}`);
|
|
758
|
+
}
|
|
759
|
+
/**
|
|
760
|
+
* Check and validate pending orders
|
|
761
|
+
*/
|
|
762
|
+
async check(request) {
|
|
763
|
+
return this.http.post("/api/v1/orders/check", request);
|
|
764
|
+
}
|
|
765
|
+
};
|
|
766
|
+
|
|
767
|
+
// src/api/trades.ts
|
|
768
|
+
var TradesAPI = class {
|
|
769
|
+
constructor(http) {
|
|
770
|
+
this.http = http;
|
|
771
|
+
}
|
|
772
|
+
/**
|
|
773
|
+
* Open a new trade (market or pending order)
|
|
774
|
+
*/
|
|
775
|
+
async open(request) {
|
|
776
|
+
return this.http.post("/api/v1/trades/open", request);
|
|
777
|
+
}
|
|
778
|
+
/**
|
|
779
|
+
* Close a trade (full or partial)
|
|
780
|
+
*/
|
|
781
|
+
async close(request) {
|
|
782
|
+
return this.http.post("/api/v1/trades/close", request);
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* Modify trade Stop Loss and/or Take Profit
|
|
786
|
+
*/
|
|
787
|
+
async modify(request) {
|
|
788
|
+
return this.http.put("/api/v1/trades/modify", request);
|
|
789
|
+
}
|
|
790
|
+
/**
|
|
791
|
+
* Check if there's sufficient margin for a trade
|
|
792
|
+
*/
|
|
793
|
+
async checkMargin(request) {
|
|
794
|
+
return this.http.post("/api/v1/trades/check-margin", request);
|
|
795
|
+
}
|
|
796
|
+
/**
|
|
797
|
+
* Calculate potential profit for a trade
|
|
798
|
+
*/
|
|
799
|
+
async calculateProfit(request) {
|
|
800
|
+
return this.http.post("/api/v1/trades/calc-profit", request);
|
|
801
|
+
}
|
|
802
|
+
};
|
|
803
|
+
|
|
804
|
+
// src/core/client.ts
|
|
805
|
+
var PipsendClient = class {
|
|
806
|
+
constructor(config) {
|
|
807
|
+
/**
|
|
808
|
+
* Authentication API
|
|
809
|
+
* TODO: Implement when authentication is available
|
|
810
|
+
*/
|
|
811
|
+
this.auth = {
|
|
812
|
+
/**
|
|
813
|
+
* Forces a token refresh
|
|
814
|
+
*/
|
|
815
|
+
refresh: async () => {
|
|
816
|
+
await this.authManager.refreshToken();
|
|
817
|
+
},
|
|
818
|
+
/**
|
|
819
|
+
* Checks if the client is authenticated
|
|
820
|
+
*/
|
|
821
|
+
isAuthenticated: () => {
|
|
822
|
+
return this.authManager.isAuthenticated();
|
|
823
|
+
},
|
|
824
|
+
/**
|
|
825
|
+
* Gets the current token (if it exists)
|
|
826
|
+
*/
|
|
827
|
+
getToken: () => {
|
|
828
|
+
return this.authManager.getToken();
|
|
829
|
+
},
|
|
830
|
+
/**
|
|
831
|
+
* Logs out by clearing the token
|
|
832
|
+
*/
|
|
833
|
+
logout: () => {
|
|
834
|
+
this.authManager.clearToken();
|
|
835
|
+
}
|
|
836
|
+
};
|
|
837
|
+
/**
|
|
838
|
+
* WebSocket streaming API
|
|
839
|
+
* Provides real-time updates for prices, orders, positions, etc.
|
|
840
|
+
*/
|
|
841
|
+
this.stream = {
|
|
842
|
+
/**
|
|
843
|
+
* Connects to WebSocket server
|
|
844
|
+
*/
|
|
845
|
+
connect: async () => {
|
|
846
|
+
if (!this.wsManager) {
|
|
847
|
+
throw new WebSocketError(
|
|
848
|
+
"WebSocket not enabled. Set websocket.enabled: true in config"
|
|
849
|
+
);
|
|
850
|
+
}
|
|
851
|
+
await this.wsManager.connect();
|
|
852
|
+
},
|
|
853
|
+
/**
|
|
854
|
+
* Disconnects from WebSocket server
|
|
855
|
+
*/
|
|
856
|
+
disconnect: () => {
|
|
857
|
+
this.wsManager?.disconnect();
|
|
858
|
+
},
|
|
859
|
+
/**
|
|
860
|
+
* Checks if WebSocket is connected
|
|
861
|
+
*/
|
|
862
|
+
isConnected: () => {
|
|
863
|
+
return this.wsManager?.isWebSocketConnected() ?? false;
|
|
864
|
+
},
|
|
865
|
+
/**
|
|
866
|
+
* Subscribes to symbols or channels
|
|
867
|
+
* @example
|
|
868
|
+
* client.stream.subscribe(['EURUSD', 'GBPUSD']);
|
|
869
|
+
*/
|
|
870
|
+
subscribe: (channels) => {
|
|
871
|
+
if (!this.wsManager) {
|
|
872
|
+
throw new WebSocketError("WebSocket not enabled");
|
|
873
|
+
}
|
|
874
|
+
this.wsManager.subscribe(channels);
|
|
875
|
+
},
|
|
876
|
+
/**
|
|
877
|
+
* Unsubscribes from symbols or channels
|
|
878
|
+
*/
|
|
879
|
+
unsubscribe: (channels) => {
|
|
880
|
+
if (!this.wsManager) {
|
|
881
|
+
throw new WebSocketError("WebSocket not enabled");
|
|
882
|
+
}
|
|
883
|
+
this.wsManager.unsubscribe(channels);
|
|
884
|
+
},
|
|
885
|
+
/**
|
|
886
|
+
* Gets list of subscribed channels
|
|
887
|
+
*/
|
|
888
|
+
getSubscriptions: () => {
|
|
889
|
+
return this.wsManager?.getSubscribedChannels() ?? [];
|
|
890
|
+
},
|
|
891
|
+
/**
|
|
892
|
+
* Listens to real-time price updates
|
|
893
|
+
*/
|
|
894
|
+
onPriceUpdate: (callback) => {
|
|
895
|
+
if (!this.wsManager) {
|
|
896
|
+
throw new WebSocketError("WebSocket not enabled");
|
|
897
|
+
}
|
|
898
|
+
this.wsManager.on("price", callback);
|
|
899
|
+
},
|
|
900
|
+
/**
|
|
901
|
+
* Listens to real-time order updates
|
|
902
|
+
*/
|
|
903
|
+
onOrderUpdate: (callback) => {
|
|
904
|
+
if (!this.wsManager) {
|
|
905
|
+
throw new WebSocketError("WebSocket not enabled");
|
|
906
|
+
}
|
|
907
|
+
this.wsManager.on("order", callback);
|
|
908
|
+
},
|
|
909
|
+
/**
|
|
910
|
+
* Listens to real-time position updates
|
|
911
|
+
*/
|
|
912
|
+
onPositionUpdate: (callback) => {
|
|
913
|
+
if (!this.wsManager) {
|
|
914
|
+
throw new WebSocketError("WebSocket not enabled");
|
|
915
|
+
}
|
|
916
|
+
this.wsManager.on("position", callback);
|
|
917
|
+
},
|
|
918
|
+
/**
|
|
919
|
+
* Listens to real-time balance updates
|
|
920
|
+
*/
|
|
921
|
+
onBalanceUpdate: (callback) => {
|
|
922
|
+
if (!this.wsManager) {
|
|
923
|
+
throw new WebSocketError("WebSocket not enabled");
|
|
924
|
+
}
|
|
925
|
+
this.wsManager.on("balance", callback);
|
|
926
|
+
},
|
|
927
|
+
/**
|
|
928
|
+
* Listens to connection events
|
|
929
|
+
*/
|
|
930
|
+
onConnected: (callback) => {
|
|
931
|
+
if (!this.wsManager) {
|
|
932
|
+
throw new WebSocketError("WebSocket not enabled");
|
|
933
|
+
}
|
|
934
|
+
this.wsManager.on("connected", callback);
|
|
935
|
+
},
|
|
936
|
+
/**
|
|
937
|
+
* Listens to disconnection events
|
|
938
|
+
*/
|
|
939
|
+
onDisconnected: (callback) => {
|
|
940
|
+
if (!this.wsManager) {
|
|
941
|
+
throw new WebSocketError("WebSocket not enabled");
|
|
942
|
+
}
|
|
943
|
+
this.wsManager.on("disconnected", callback);
|
|
944
|
+
},
|
|
945
|
+
/**
|
|
946
|
+
* Listens to error events
|
|
947
|
+
*/
|
|
948
|
+
onError: (callback) => {
|
|
949
|
+
if (!this.wsManager) {
|
|
950
|
+
throw new WebSocketError("WebSocket not enabled");
|
|
951
|
+
}
|
|
952
|
+
this.wsManager.on("error", callback);
|
|
953
|
+
},
|
|
954
|
+
/**
|
|
955
|
+
* Removes event listener
|
|
956
|
+
*/
|
|
957
|
+
off: (event, callback) => {
|
|
958
|
+
this.wsManager?.off(event, callback);
|
|
959
|
+
}
|
|
960
|
+
};
|
|
961
|
+
this.validateConfig(config);
|
|
962
|
+
this.config = {
|
|
963
|
+
...config,
|
|
964
|
+
autoRefresh: config.autoRefresh ?? true,
|
|
965
|
+
refreshMarginMinutes: config.refreshMarginMinutes ?? 5,
|
|
966
|
+
timezone: config.timezone ?? "UTC"
|
|
967
|
+
};
|
|
968
|
+
this.authManager = new AuthManager(this.config);
|
|
969
|
+
this.http = new HttpClient(this.config.server);
|
|
970
|
+
this.accounts = new AccountsAPI(this.http);
|
|
971
|
+
this.tradingGroups = new TradingGroupsAPI(this.http);
|
|
972
|
+
this.marketData = new MarketDataAPI(this.http);
|
|
973
|
+
this.positions = new PositionsAPI(this.http);
|
|
974
|
+
this.orders = new OrdersAPI(this.http);
|
|
975
|
+
this.trades = new TradesAPI(this.http);
|
|
976
|
+
if (config.websocket?.enabled) {
|
|
977
|
+
this.wsManager = new WebSocketManager(this.config, this.authManager);
|
|
978
|
+
if (config.websocket.autoConnect) {
|
|
979
|
+
this.wsManager.connect().catch((error) => {
|
|
980
|
+
console.error("Failed to auto-connect WebSocket:", error);
|
|
981
|
+
});
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
/**
|
|
986
|
+
* Validates the initial configuration
|
|
987
|
+
*/
|
|
988
|
+
validateConfig(config) {
|
|
989
|
+
if (!config.server) {
|
|
990
|
+
throw new ConfigurationError('The "server" parameter is required');
|
|
991
|
+
}
|
|
992
|
+
try {
|
|
993
|
+
new URL(config.server);
|
|
994
|
+
} catch {
|
|
995
|
+
throw new ConfigurationError('The "server" parameter must be a valid URL');
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
/**
|
|
999
|
+
* Health check
|
|
1000
|
+
*/
|
|
1001
|
+
async ping() {
|
|
1002
|
+
return this.http.get("/ping");
|
|
1003
|
+
}
|
|
1004
|
+
/**
|
|
1005
|
+
* Client information
|
|
1006
|
+
*/
|
|
1007
|
+
getConfig() {
|
|
1008
|
+
return { ...this.config };
|
|
1009
|
+
}
|
|
1010
|
+
};
|
|
1011
|
+
|
|
1012
|
+
// src/index.ts
|
|
1013
|
+
function createClient(config) {
|
|
1014
|
+
return new PipsendClient(config);
|
|
1015
|
+
}
|
|
1016
|
+
var sdk = {
|
|
1017
|
+
createClient,
|
|
1018
|
+
PipsendClient
|
|
1019
|
+
};
|
|
1020
|
+
var index_default = sdk;
|
|
1021
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1022
|
+
0 && (module.exports = {
|
|
1023
|
+
AuthenticationError,
|
|
1024
|
+
ConfigurationError,
|
|
1025
|
+
PipsendClient,
|
|
1026
|
+
PipsendError,
|
|
1027
|
+
WebSocketError,
|
|
1028
|
+
createClient
|
|
1029
|
+
});
|
|
1030
|
+
//# sourceMappingURL=index.js.map
|