@pipsend/sdk 0.1.0 → 0.1.2

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/dist/index.mjs CHANGED
@@ -7,11 +7,30 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
7
7
 
8
8
  // src/types/index.ts
9
9
  var PipsendError = class extends Error {
10
- constructor(message, code, statusCode) {
10
+ constructor(message, code, statusCode, errors, response) {
11
11
  super(message);
12
12
  this.code = code;
13
13
  this.statusCode = statusCode;
14
14
  this.name = "PipsendError";
15
+ this.errors = errors;
16
+ this.response = response;
17
+ }
18
+ /**
19
+ * Get formatted error message with validation details
20
+ */
21
+ getDetailedMessage() {
22
+ if (!this.errors || this.errors.length === 0) {
23
+ return this.message;
24
+ }
25
+ const errorDetails = this.errors.map((err) => ` - ${err.field}: ${err.message}`).join("\n");
26
+ return `${this.message}
27
+ ${errorDetails}`;
28
+ }
29
+ /**
30
+ * Check if this is a validation error
31
+ */
32
+ isValidationError() {
33
+ return this.statusCode === 422 || this.code === "VALIDATION_ERROR";
15
34
  }
16
35
  };
17
36
  var AuthenticationError = class extends PipsendError {
@@ -66,17 +85,35 @@ var AuthManager = class {
66
85
  }
67
86
  /**
68
87
  * Performs authentication with the server
69
- * TODO: Implement real API call when available
70
88
  */
71
89
  async authenticate() {
90
+ if (!this.config.login || !this.config.password) {
91
+ throw new AuthenticationError(
92
+ "Login and password are required for authentication. Please provide them in the config."
93
+ );
94
+ }
72
95
  try {
73
- const mockResponse = {
74
- token: "mock_token_" + Date.now(),
75
- expiresIn: 180 * 60
76
- // 180 minutes in seconds
77
- };
78
- this.setTokenInfo(mockResponse);
79
- console.log("\u2705 Authentication successful (mock)");
96
+ const response = await fetch(`${this.config.server}/api/v1/auth/login`, {
97
+ method: "POST",
98
+ headers: { "Content-Type": "application/json" },
99
+ body: JSON.stringify({
100
+ login: this.config.login,
101
+ password: this.config.password
102
+ })
103
+ });
104
+ if (!response.ok) {
105
+ const errorData = await response.json().catch(() => ({}));
106
+ throw new AuthenticationError(
107
+ errorData.message || `Authentication failed: ${response.status} ${response.statusText}`
108
+ );
109
+ }
110
+ const data = await response.json();
111
+ this.setTokenInfo(data);
112
+ console.log("\u2705 Authentication successful");
113
+ console.log(` User: ${data.user.first_name} ${data.user.last_name} (${data.user.email})`);
114
+ console.log(` Login: ${data.user.login}`);
115
+ console.log(` Balance: $${data.user.balance.toFixed(2)}`);
116
+ console.log(` Logged with: ${data.logged_with}`);
80
117
  } catch (error) {
81
118
  if (error instanceof AuthenticationError) {
82
119
  throw error;
@@ -87,11 +124,45 @@ var AuthManager = class {
87
124
  }
88
125
  }
89
126
  /**
90
- * Forces a re-authentication
127
+ * Refreshes the access token using the refresh token
91
128
  */
92
129
  async refreshToken() {
93
- this.tokenInfo = void 0;
94
- await this.authenticate();
130
+ if (!this.tokenInfo?.refreshToken) {
131
+ console.log("\u26A0\uFE0F No refresh token available, performing full authentication");
132
+ this.tokenInfo = void 0;
133
+ await this.authenticate();
134
+ return;
135
+ }
136
+ try {
137
+ const response = await fetch(`${this.config.server}/api/v1/auth/refresh`, {
138
+ method: "POST",
139
+ headers: { "Content-Type": "application/json" },
140
+ body: JSON.stringify({
141
+ refresh_token: this.tokenInfo.refreshToken
142
+ })
143
+ });
144
+ if (!response.ok) {
145
+ console.log("\u26A0\uFE0F Refresh token failed, performing full authentication");
146
+ this.tokenInfo = void 0;
147
+ await this.authenticate();
148
+ return;
149
+ }
150
+ const data = await response.json();
151
+ const now = /* @__PURE__ */ new Date();
152
+ const expiresAt = new Date(now.getTime() + data.expires_in * 1e3);
153
+ this.tokenInfo = {
154
+ token: data.access_token,
155
+ refreshToken: this.tokenInfo.refreshToken,
156
+ // Keep the same refresh token
157
+ issuedAt: now,
158
+ expiresAt
159
+ };
160
+ console.log("\u2705 Token refreshed successfully");
161
+ } catch (error) {
162
+ console.error("\u274C Error refreshing token:", error);
163
+ this.tokenInfo = void 0;
164
+ await this.authenticate();
165
+ }
95
166
  }
96
167
  /**
97
168
  * Clears the current token
@@ -113,9 +184,10 @@ var AuthManager = class {
113
184
  */
114
185
  setTokenInfo(response) {
115
186
  const now = /* @__PURE__ */ new Date();
116
- const expiresAt = new Date(now.getTime() + response.expiresIn * 1e3);
187
+ const expiresAt = new Date(now.getTime() + response.expires_in * 1e3);
117
188
  this.tokenInfo = {
118
- token: response.token,
189
+ token: response.access_token,
190
+ refreshToken: response.refresh_token,
119
191
  issuedAt: now,
120
192
  expiresAt
121
193
  };
@@ -146,8 +218,9 @@ var WebSocketManager = class {
146
218
  console.log("\u26A0\uFE0F WebSocket reconnection in progress");
147
219
  return;
148
220
  }
149
- const wsUrl = this.buildWebSocketUrl();
150
221
  const token = await this.authManager.ensureAuthenticated();
222
+ const baseWsUrl = this.buildWebSocketUrl();
223
+ const wsUrl = `${baseWsUrl}${baseWsUrl.includes("?") ? "&" : "?"}token=${encodeURIComponent(token)}`;
151
224
  return new Promise((resolve, reject) => {
152
225
  try {
153
226
  const WS = this.getWebSocketConstructor();
@@ -157,11 +230,6 @@ var WebSocketManager = class {
157
230
  this.isConnected = true;
158
231
  this.isReconnecting = false;
159
232
  this.reconnectAttempts = 0;
160
- this.send({
161
- type: "auth",
162
- payload: { token },
163
- timestamp: Date.now()
164
- });
165
233
  this.emit("connected", { timestamp: Date.now() });
166
234
  if (this.subscribedChannels.length > 0) {
167
235
  console.log("\u{1F504} Resubscribing to channels:", this.subscribedChannels);
@@ -204,24 +272,22 @@ var WebSocketManager = class {
204
272
  });
205
273
  }
206
274
  /**
207
- * Subscribes to channels (symbols, events, etc.)
208
- * TODO: Adjust message format according to real WebSocket API
275
+ * Subscribes to WebSocket channels
209
276
  */
210
277
  subscribe(channels) {
211
278
  if (!this.isConnected) {
212
279
  throw new WebSocketError("WebSocket not connected. Call connect() first.");
213
280
  }
214
281
  this.subscribedChannels = [.../* @__PURE__ */ new Set([...this.subscribedChannels, ...channels])];
215
- this.send({
216
- type: "subscribe",
217
- payload: { channels },
218
- timestamp: Date.now()
219
- });
282
+ const message = {
283
+ action: "subscribe",
284
+ channels
285
+ };
286
+ this.sendAction(message);
220
287
  console.log("\u{1F4E1} Subscribed to channels:", channels);
221
288
  }
222
289
  /**
223
- * Unsubscribes from channels
224
- * TODO: Adjust message format according to real WebSocket API
290
+ * Unsubscribes from WebSocket channels
225
291
  */
226
292
  unsubscribe(channels) {
227
293
  if (!this.isConnected) {
@@ -231,11 +297,11 @@ var WebSocketManager = class {
231
297
  this.subscribedChannels = this.subscribedChannels.filter(
232
298
  (ch) => !channels.includes(ch)
233
299
  );
234
- this.send({
235
- type: "unsubscribe",
236
- payload: { channels },
237
- timestamp: Date.now()
238
- });
300
+ const message = {
301
+ action: "unsubscribe",
302
+ channels
303
+ };
304
+ this.sendAction(message);
239
305
  console.log("\u{1F4E1} Unsubscribed from channels:", channels);
240
306
  }
241
307
  /**
@@ -292,7 +358,7 @@ var WebSocketManager = class {
292
358
  try {
293
359
  const url = new URL(this.config.server);
294
360
  url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
295
- const wsPath = this.config.websocket?.path || "/ws";
361
+ const wsPath = this.config.websocket?.path || "/api/v1/ws";
296
362
  url.pathname = wsPath;
297
363
  return url.toString();
298
364
  } catch (error) {
@@ -315,11 +381,11 @@ var WebSocketManager = class {
315
381
  }
316
382
  }
317
383
  /**
318
- * Sends a message through WebSocket
384
+ * Sends an action message through WebSocket
319
385
  */
320
- send(message) {
386
+ sendAction(message) {
321
387
  if (!this.ws || !this.isConnected) {
322
- console.warn("\u26A0\uFE0F WebSocket not ready, message not sent:", message.type);
388
+ console.warn("\u26A0\uFE0F WebSocket not ready, message not sent:", message.action);
323
389
  return;
324
390
  }
325
391
  try {
@@ -330,21 +396,99 @@ var WebSocketManager = class {
330
396
  }
331
397
  }
332
398
  /**
333
- * Handles incoming WebSocket messages
334
- * TODO: Adjust message handling according to real WebSocket API
399
+ * Sends a ping message (for heartbeat)
335
400
  */
336
- handleMessage(data) {
337
- if (data.type === "authenticated") {
338
- console.log("\u2705 WebSocket authenticated");
339
- this.emit("authenticated", data.payload || {});
401
+ sendPing() {
402
+ if (!this.ws || !this.isConnected) {
340
403
  return;
341
404
  }
342
- if (data.type === "pong") {
343
- return;
405
+ try {
406
+ this.ws.send(JSON.stringify({ action: "ping" }));
407
+ } catch (error) {
408
+ console.error("\u274C Failed to send ping:", error);
344
409
  }
345
- const eventType = data.type;
346
- this.emit(eventType, data.payload || data);
347
- this.emit("*", data);
410
+ }
411
+ /**
412
+ * Handles incoming WebSocket messages
413
+ */
414
+ handleMessage(message) {
415
+ switch (message.op) {
416
+ case "connected":
417
+ this.handleConnected(message);
418
+ break;
419
+ case "subscription_success":
420
+ this.handleSubscriptionSuccess(message);
421
+ break;
422
+ case "unsubscription_success":
423
+ this.handleUnsubscriptionSuccess(message);
424
+ break;
425
+ case "push":
426
+ this.handlePushEvent(message);
427
+ break;
428
+ case "error":
429
+ this.handleError(message);
430
+ break;
431
+ default:
432
+ console.warn(message.op);
433
+ }
434
+ this.emit("*", message);
435
+ }
436
+ /**
437
+ * Handles connected message from server
438
+ */
439
+ handleConnected(message) {
440
+ console.log("\u2705 WebSocket connected:", message.data.message);
441
+ console.log(" Available channels:", message.data.available_channels);
442
+ this.emit("connected", message.data);
443
+ }
444
+ /**
445
+ * Handles subscription success
446
+ */
447
+ handleSubscriptionSuccess(message) {
448
+ console.log("\u2705 Subscription successful:", message.data.subscribed);
449
+ this.emit("subscription_success", message.data);
450
+ }
451
+ /**
452
+ * Handles unsubscription success
453
+ */
454
+ handleUnsubscriptionSuccess(message) {
455
+ console.log("\u2705 Unsubscription successful:", message.data.unsubscribed);
456
+ this.emit("unsubscription_success", message.data);
457
+ }
458
+ /**
459
+ * Handles push events (actual data events)
460
+ */
461
+ handlePushEvent(message) {
462
+ const eventData = message.data;
463
+ const eventType = eventData.type;
464
+ switch (eventType) {
465
+ case "position.opened":
466
+ this.emit("position:opened", eventData);
467
+ break;
468
+ case "position.closed":
469
+ case "position.partially_closed":
470
+ this.emit("position:closed", eventData);
471
+ break;
472
+ case "position.updated":
473
+ this.emit("position:updated", eventData);
474
+ break;
475
+ default:
476
+ if (message.topic?.startsWith("balance:")) {
477
+ this.emit("balance:updated", eventData);
478
+ } else {
479
+ console.warn("Unknown event type:", eventType);
480
+ }
481
+ }
482
+ }
483
+ /**
484
+ * Handles error messages from server
485
+ */
486
+ handleError(message) {
487
+ console.error("\u274C WebSocket error:", message.data.message);
488
+ if (message.data.error) {
489
+ console.error(" Error details:", message.data.error);
490
+ }
491
+ this.emit("error", message.data);
348
492
  }
349
493
  /**
350
494
  * Emits an event to all registered listeners
@@ -395,10 +539,7 @@ var WebSocketManager = class {
395
539
  const interval = this.config.websocket?.heartbeatInterval || 3e4;
396
540
  this.heartbeatTimer = setInterval(() => {
397
541
  if (this.isConnected) {
398
- this.send({
399
- type: "ping",
400
- timestamp: Date.now()
401
- });
542
+ this.sendPing();
402
543
  }
403
544
  }, interval);
404
545
  }
@@ -415,28 +556,59 @@ var WebSocketManager = class {
415
556
 
416
557
  // src/api/http-client.ts
417
558
  var HttpClient = class {
418
- constructor(baseUrl) {
559
+ constructor(baseUrl, authManager) {
419
560
  this.baseUrl = baseUrl;
561
+ this.authManager = authManager;
420
562
  }
421
563
  /**
422
564
  * Makes an HTTP request
423
565
  */
424
- async request(endpoint, options = {}) {
566
+ async request(endpoint, options = {}, retryOn401 = true) {
425
567
  const url = `${this.baseUrl}${endpoint}`;
426
568
  try {
569
+ const headers = {
570
+ "Content-Type": "application/json",
571
+ ...options.headers || {}
572
+ };
573
+ if (this.authManager) {
574
+ const token = await this.authManager.ensureAuthenticated();
575
+ headers["Authorization"] = `Bearer ${token}`;
576
+ }
427
577
  const response = await fetch(url, {
428
578
  ...options,
429
- headers: {
430
- "Content-Type": "application/json",
431
- ...options.headers
432
- }
579
+ headers
433
580
  });
581
+ if (response.status === 401 && this.authManager && retryOn401) {
582
+ console.log("\u26A0\uFE0F Received 401, attempting to refresh token...");
583
+ await this.authManager.refreshToken();
584
+ return this.request(endpoint, options, false);
585
+ }
434
586
  if (!response.ok) {
435
587
  const errorText = await response.text();
588
+ let errorMessage = `HTTP ${response.status}: ${errorText || response.statusText}`;
589
+ let errorCode = "HTTP_ERROR";
590
+ let validationErrors;
591
+ let errorResponse;
592
+ try {
593
+ const errorJson = JSON.parse(errorText);
594
+ errorResponse = errorJson;
595
+ if (errorJson.message) {
596
+ errorMessage = errorJson.message;
597
+ }
598
+ if (errorJson.status_code) {
599
+ errorCode = errorJson.status_code;
600
+ }
601
+ if (errorJson.errors && Array.isArray(errorJson.errors)) {
602
+ validationErrors = errorJson.errors;
603
+ }
604
+ } catch {
605
+ }
436
606
  throw new PipsendError(
437
- `HTTP ${response.status}: ${errorText || response.statusText}`,
438
- "HTTP_ERROR",
439
- response.status
607
+ errorMessage,
608
+ errorCode,
609
+ response.status,
610
+ validationErrors,
611
+ errorResponse
440
612
  );
441
613
  }
442
614
  const text = await response.text();
@@ -500,6 +672,12 @@ var AccountsAPI = class {
500
672
  constructor(http) {
501
673
  this.http = http;
502
674
  }
675
+ /**
676
+ * Create a new trading account
677
+ */
678
+ async create(request) {
679
+ return this.http.post("/api/v1/accounts", request);
680
+ }
503
681
  /**
504
682
  * List all accounts with optional filters and pagination
505
683
  */
@@ -512,6 +690,12 @@ var AccountsAPI = class {
512
690
  async getLogins() {
513
691
  return this.http.get("/api/v1/accounts/logins");
514
692
  }
693
+ /**
694
+ * Get account status/metrics (balance, equity, credit, margin)
695
+ */
696
+ async getStatus(login) {
697
+ return this.http.get(`/api/v1/accounts/${login}/status`);
698
+ }
515
699
  /**
516
700
  * Get account statistics with optional filters
517
701
  */
@@ -542,6 +726,20 @@ var AccountsAPI = class {
542
726
  async unarchive(login) {
543
727
  return this.http.put(`/api/v1/accounts/${login}/unarchive`);
544
728
  }
729
+ /**
730
+ * Update account information (partial update)
731
+ * All fields are optional, only send the ones you want to update
732
+ */
733
+ async update(login, request) {
734
+ return this.http.put(`/api/v1/accounts/${login}`, request);
735
+ }
736
+ /**
737
+ * Adjust balance or credit for an account
738
+ * The adjustment is relative: amount is added or subtracted from current value
739
+ */
740
+ async balance(login, request) {
741
+ return this.http.put(`/api/v1/accounts/${login}/adjust`, request);
742
+ }
545
743
  };
546
744
 
547
745
  // src/api/trading-groups.ts
@@ -838,9 +1036,9 @@ var PipsendClient = class {
838
1036
  return this.wsManager?.isWebSocketConnected() ?? false;
839
1037
  },
840
1038
  /**
841
- * Subscribes to symbols or channels
1039
+ * Subscribes to WebSocket channels
842
1040
  * @example
843
- * client.stream.subscribe(['EURUSD', 'GBPUSD']);
1041
+ * client.stream.subscribe(['positions:new', 'positions:closed']);
844
1042
  */
845
1043
  subscribe: (channels) => {
846
1044
  if (!this.wsManager) {
@@ -849,7 +1047,7 @@ var PipsendClient = class {
849
1047
  this.wsManager.subscribe(channels);
850
1048
  },
851
1049
  /**
852
- * Unsubscribes from symbols or channels
1050
+ * Unsubscribes from WebSocket channels
853
1051
  */
854
1052
  unsubscribe: (channels) => {
855
1053
  if (!this.wsManager) {
@@ -864,40 +1062,81 @@ var PipsendClient = class {
864
1062
  return this.wsManager?.getSubscribedChannels() ?? [];
865
1063
  },
866
1064
  /**
867
- * Listens to real-time price updates
1065
+ * Listens to position opened events
1066
+ */
1067
+ onPositionOpened: (callback) => {
1068
+ if (!this.wsManager) {
1069
+ throw new WebSocketError("WebSocket not enabled");
1070
+ }
1071
+ this.wsManager.on("position:opened", callback);
1072
+ },
1073
+ /**
1074
+ * Listens to position closed events (includes partial closes)
1075
+ */
1076
+ onPositionClosed: (callback) => {
1077
+ if (!this.wsManager) {
1078
+ throw new WebSocketError("WebSocket not enabled");
1079
+ }
1080
+ this.wsManager.on("position:closed", callback);
1081
+ },
1082
+ /**
1083
+ * Listens to position updated events (throttled to max 1 per 2 seconds)
1084
+ */
1085
+ onPositionUpdated: (callback) => {
1086
+ if (!this.wsManager) {
1087
+ throw new WebSocketError("WebSocket not enabled");
1088
+ }
1089
+ this.wsManager.on("position:updated", callback);
1090
+ },
1091
+ /**
1092
+ * Listens to balance updated events
1093
+ */
1094
+ onBalanceUpdated: (callback) => {
1095
+ if (!this.wsManager) {
1096
+ throw new WebSocketError("WebSocket not enabled");
1097
+ }
1098
+ this.wsManager.on("balance:updated", callback);
1099
+ },
1100
+ // Legacy methods (deprecated but kept for compatibility)
1101
+ /**
1102
+ * @deprecated Use onPositionUpdated instead
868
1103
  */
869
1104
  onPriceUpdate: (callback) => {
1105
+ console.warn("\u26A0\uFE0F onPriceUpdate is deprecated. Use onPositionUpdated instead.");
870
1106
  if (!this.wsManager) {
871
1107
  throw new WebSocketError("WebSocket not enabled");
872
1108
  }
873
- this.wsManager.on("price", callback);
1109
+ this.wsManager.on("position:updated", callback);
874
1110
  },
875
1111
  /**
876
- * Listens to real-time order updates
1112
+ * @deprecated Use onPositionOpened/onPositionClosed instead
877
1113
  */
878
1114
  onOrderUpdate: (callback) => {
1115
+ console.warn("\u26A0\uFE0F onOrderUpdate is deprecated. Use onPositionOpened/onPositionClosed instead.");
879
1116
  if (!this.wsManager) {
880
1117
  throw new WebSocketError("WebSocket not enabled");
881
1118
  }
882
- this.wsManager.on("order", callback);
1119
+ this.wsManager.on("position:opened", callback);
883
1120
  },
884
1121
  /**
885
- * Listens to real-time position updates
1122
+ * @deprecated Use onPositionUpdated instead
886
1123
  */
887
1124
  onPositionUpdate: (callback) => {
1125
+ console.warn("\u26A0\uFE0F onPositionUpdate is deprecated. Use onPositionUpdated instead.");
888
1126
  if (!this.wsManager) {
889
1127
  throw new WebSocketError("WebSocket not enabled");
890
1128
  }
891
- this.wsManager.on("position", callback);
1129
+ this.wsManager.on("position:updated", callback);
892
1130
  },
893
1131
  /**
894
- * Listens to real-time balance updates
1132
+ * @deprecated Use onBalanceUpdated instead
895
1133
  */
896
1134
  onBalanceUpdate: (callback) => {
1135
+ console.warn("\u26A0\uFE0F onBalanceUpdate is deprecated. Use onBalanceUpdated instead.");
897
1136
  if (!this.wsManager) {
898
1137
  throw new WebSocketError("WebSocket not enabled");
899
1138
  }
900
- this.wsManager.on("balance", callback);
1139
+ this.wsManager.on("balance:updated", callback);
901
1140
  },
902
1141
  /**
903
1142
  * Listens to connection events
@@ -941,7 +1180,7 @@ var PipsendClient = class {
941
1180
  timezone: config.timezone ?? "UTC"
942
1181
  };
943
1182
  this.authManager = new AuthManager(this.config);
944
- this.http = new HttpClient(this.config.server);
1183
+ this.http = new HttpClient(this.config.server, this.authManager);
945
1184
  this.accounts = new AccountsAPI(this.http);
946
1185
  this.tradingGroups = new TradingGroupsAPI(this.http);
947
1186
  this.marketData = new MarketDataAPI(this.http);