@danielgroen/dxtrade-api 1.0.7 → 1.0.8

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
@@ -1,12 +1,10 @@
1
1
  // src/constants/brokers.ts
2
2
  var BROKER = {
3
- LARK: "https://trade.gooeytrade.com",
3
+ LARKFUNDING: "https://trade.gooeytrade.com",
4
4
  EIGHTCAP: "https://trader.dx-eightcap.com"
5
5
  };
6
6
  function resolveBrokerUrl(broker, customUrls) {
7
- if (customUrls?.[broker]) {
8
- return customUrls[broker];
9
- }
7
+ if (customUrls?.[broker]) return customUrls[broker];
10
8
  const key = broker.toUpperCase();
11
9
  if (BROKER[key]) {
12
10
  return BROKER[key];
@@ -15,6 +13,7 @@ function resolveBrokerUrl(broker, customUrls) {
15
13
  }
16
14
 
17
15
  // src/constants/endpoints.ts
16
+ var websocketQuery = `?X-Atmosphere-tracking-id=0&X-Atmosphere-Framework=2.3.2-javascript&X-Atmosphere-Transport=websocket&X-Atmosphere-TrackMessageSize=true&Content-Type=text/x-gwt-rpc;%20charset=UTF-8&X-atmo-protocol=true&sessionState=dx-new&guest-mode=false`;
18
17
  var endpoints = {
19
18
  login: (base) => `${base}/api/auth/login`,
20
19
  switchAccount: (base, id) => `${base}/api/accounts/switch?accountId=${id}`,
@@ -22,7 +21,7 @@ var endpoints = {
22
21
  instrumentInfo: (base, symbol, tzOffset) => `${base}/api/instruments/info?symbol=${symbol}&timezoneOffset=${tzOffset}&withExDividends=true`,
23
22
  submitOrder: (base) => `${base}/api/orders/single`,
24
23
  assessments: (base) => `${base}/api/assessments`,
25
- websocket: (base) => `wss://${base.split("//")[1]}/client/connector?X-Atmosphere-tracking-id=0&X-Atmosphere-Framework=2.3.2-javascript&X-Atmosphere-Transport=websocket&X-Atmosphere-TrackMessageSize=true&Content-Type=text/x-gwt-rpc;%20charset=UTF-8&X-atmo-protocol=true&sessionState=dx-new&guest-mode=false`
24
+ websocket: (base) => `wss://${base.split("//")[1]}/client/connector` + websocketQuery
26
25
  };
27
26
 
28
27
  // src/constants/enums.ts
@@ -42,6 +41,24 @@ var ACTION = /* @__PURE__ */ ((ACTION2) => {
42
41
  ACTION2["CLOSING"] = "CLOSING";
43
42
  return ACTION2;
44
43
  })(ACTION || {});
44
+ var TIF = /* @__PURE__ */ ((TIF2) => {
45
+ TIF2["GTC"] = "GTC";
46
+ TIF2["DAY"] = "DAY";
47
+ TIF2["GTD"] = "GTD";
48
+ return TIF2;
49
+ })(TIF || {});
50
+ var WS_MESSAGE = /* @__PURE__ */ ((WS_MESSAGE2) => {
51
+ WS_MESSAGE2["ACCOUNTS"] = "ACCOUNTS";
52
+ WS_MESSAGE2["AVAILABLE_WATCHLISTS"] = "AVAILABLE_WATCHLISTS";
53
+ WS_MESSAGE2["MESSAGE"] = "MESSAGE";
54
+ WS_MESSAGE2["ORDERS"] = "ORDERS";
55
+ WS_MESSAGE2["POSITIONS"] = "POSITIONS";
56
+ WS_MESSAGE2["POSITION_CASH_TRANSFERS"] = "POSITION_CASH_TRANSFERS";
57
+ WS_MESSAGE2["PRIVATE_LAYOUT_NAMES"] = "PRIVATE_LAYOUT_NAMES";
58
+ WS_MESSAGE2["SHARED_PROPERTIES_MESSAGE"] = "SHARED_PROPERTIES_MESSAGE";
59
+ WS_MESSAGE2["USER_LOGIN_INFO"] = "USER_LOGIN_INFO";
60
+ return WS_MESSAGE2;
61
+ })(WS_MESSAGE || {});
45
62
 
46
63
  // src/constants/errors.ts
47
64
  var DxtradeError = class extends Error {
@@ -53,28 +70,30 @@ var DxtradeError = class extends Error {
53
70
  }
54
71
  };
55
72
 
56
- // src/client.ts
57
- import crypto from "crypto";
73
+ // src/domains/session/session.ts
74
+ import WebSocket from "ws";
58
75
 
59
76
  // src/utils/cookies.ts
60
- function parseCookies(setCookieHeaders) {
61
- const cookies = {};
62
- for (const cookie of setCookieHeaders) {
63
- const [nameValue] = cookie.split(";");
64
- const eqIndex = nameValue.indexOf("=");
65
- if (eqIndex === -1) continue;
66
- const name = nameValue.slice(0, eqIndex).trim();
67
- const value = nameValue.slice(eqIndex + 1).trim();
68
- cookies[name] = value;
77
+ var Cookies = class {
78
+ static parse(setCookieHeaders) {
79
+ const cookies = {};
80
+ for (const cookie of setCookieHeaders) {
81
+ const [nameValue] = cookie.split(";");
82
+ const eqIndex = nameValue.indexOf("=");
83
+ if (eqIndex === -1) continue;
84
+ const name = nameValue.slice(0, eqIndex).trim();
85
+ const value = nameValue.slice(eqIndex + 1).trim();
86
+ cookies[name] = value;
87
+ }
88
+ return cookies;
69
89
  }
70
- return cookies;
71
- }
72
- function serializeCookies(cookies) {
73
- return Object.entries(cookies).map(([key, value]) => `${key}=${value}`).join("; ");
74
- }
75
- function mergeCookies(existing, incoming) {
76
- return { ...existing, ...incoming };
77
- }
90
+ static serialize(cookies) {
91
+ return Object.entries(cookies).map(([key, value]) => `${key}=${value}`).join("; ");
92
+ }
93
+ static merge(existing, incoming) {
94
+ return { ...existing, ...incoming };
95
+ }
96
+ };
78
97
 
79
98
  // src/utils/headers.ts
80
99
  function baseHeaders() {
@@ -113,7 +132,33 @@ async function retryRequest(config, retries = 3) {
113
132
  }
114
133
 
115
134
  // src/utils/websocket.ts
116
- import WebSocket from "ws";
135
+ import { appendFileSync, writeFileSync } from "fs";
136
+ var DEBUG_LOG = "debug.log";
137
+ function shouldLog(msg, debug) {
138
+ if (!debug) return false;
139
+ if (debug === true || debug === "true") return true;
140
+ if (typeof msg === "string") return false;
141
+ const filters = debug.split(",").map((s) => s.trim().toUpperCase());
142
+ return filters.includes(msg.type);
143
+ }
144
+ function debugLog(msg) {
145
+ appendFileSync(DEBUG_LOG, JSON.stringify(msg) + "\n");
146
+ }
147
+ function clearDebugLog() {
148
+ writeFileSync(DEBUG_LOG, "");
149
+ }
150
+ function parseWsData(data) {
151
+ const raw = data.toString();
152
+ const pipeIndex = raw.indexOf("|");
153
+ if (pipeIndex === -1) return raw;
154
+ try {
155
+ return JSON.parse(raw.slice(pipeIndex + 1));
156
+ } catch {
157
+ return raw;
158
+ }
159
+ }
160
+
161
+ // src/domains/session/session.ts
117
162
  function waitForHandshake(wsUrl, cookieStr, timeout = 3e4, debug = false) {
118
163
  return new Promise((resolve, reject) => {
119
164
  const ws = new WebSocket(wsUrl, { headers: { Cookie: cookieStr } });
@@ -122,9 +167,10 @@ function waitForHandshake(wsUrl, cookieStr, timeout = 3e4, debug = false) {
122
167
  reject(new Error("[dxtrade-api] Handshake timed out"));
123
168
  }, timeout);
124
169
  ws.on("message", (data) => {
125
- const str = data.toString();
126
- if (debug) console.log("[dxtrade-api:ws]", str);
127
- if (str.includes(`"POSITIONS"`)) {
170
+ const msg = parseWsData(data);
171
+ if (shouldLog(msg, debug)) debugLog(msg);
172
+ if (typeof msg === "string") return;
173
+ if (msg.type === "POSITIONS" /* POSITIONS */) {
128
174
  clearTimeout(timer);
129
175
  ws.close();
130
176
  resolve();
@@ -137,338 +183,373 @@ function waitForHandshake(wsUrl, cookieStr, timeout = 3e4, debug = false) {
137
183
  });
138
184
  });
139
185
  }
140
- function waitForOrderUpdate(wsUrl, cookieStr, timeout = 3e4, debug = false) {
141
- return new Promise((resolve, reject) => {
142
- const ws = new WebSocket(wsUrl, { headers: { Cookie: cookieStr } });
186
+ async function login(ctx) {
187
+ try {
188
+ const response = await retryRequest(
189
+ {
190
+ method: "POST",
191
+ url: endpoints.login(ctx.baseUrl),
192
+ data: {
193
+ username: ctx.config.username,
194
+ password: ctx.config.password,
195
+ domain: ctx.config.broker
196
+ },
197
+ headers: { "Content-Type": "application/json" }
198
+ },
199
+ ctx.retries
200
+ );
201
+ if (response.status === 200) {
202
+ const setCookies = response.headers["set-cookie"] ?? [];
203
+ const incoming = Cookies.parse(setCookies);
204
+ ctx.cookies = Cookies.merge(ctx.cookies, incoming);
205
+ ctx.callbacks.onLogin?.();
206
+ } else {
207
+ ctx.throwError("LOGIN_FAILED", `Login failed: ${response.status}`);
208
+ }
209
+ } catch (error) {
210
+ if (error instanceof DxtradeError) throw error;
211
+ const message = error instanceof Error ? error.message : "Unknown error";
212
+ ctx.throwError("LOGIN_ERROR", `Login error: ${message}`);
213
+ }
214
+ }
215
+ async function fetchCsrf(ctx) {
216
+ try {
217
+ const cookieStr = Cookies.serialize(ctx.cookies);
218
+ const response = await retryRequest(
219
+ {
220
+ method: "GET",
221
+ url: ctx.baseUrl,
222
+ headers: { ...cookieOnlyHeaders(cookieStr), Referer: ctx.baseUrl }
223
+ },
224
+ ctx.retries
225
+ );
226
+ const csrfMatch = response.data?.match(/name="csrf" content="([^"]+)"/);
227
+ if (csrfMatch) {
228
+ ctx.csrf = csrfMatch[1];
229
+ } else {
230
+ ctx.throwError("CSRF_NOT_FOUND", "CSRF token not found");
231
+ }
232
+ } catch (error) {
233
+ if (error instanceof DxtradeError) throw error;
234
+ const message = error instanceof Error ? error.message : "Unknown error";
235
+ ctx.throwError("CSRF_ERROR", `CSRF fetch error: ${message}`);
236
+ }
237
+ }
238
+ async function switchAccount(ctx, accountId) {
239
+ ctx.ensureSession();
240
+ try {
241
+ await retryRequest(
242
+ {
243
+ method: "POST",
244
+ url: endpoints.switchAccount(ctx.baseUrl, accountId),
245
+ headers: authHeaders(ctx.csrf, Cookies.serialize(ctx.cookies))
246
+ },
247
+ ctx.retries
248
+ );
249
+ ctx.callbacks.onAccountSwitch?.(accountId);
250
+ } catch (error) {
251
+ if (error instanceof DxtradeError) throw error;
252
+ const message = error instanceof Error ? error.message : "Unknown error";
253
+ ctx.throwError("ACCOUNT_SWITCH_ERROR", `Error switching account: ${message}`);
254
+ }
255
+ }
256
+ async function connect(ctx) {
257
+ await login(ctx);
258
+ await fetchCsrf(ctx);
259
+ if (ctx.debug) clearDebugLog();
260
+ const wsUrl = endpoints.websocket(ctx.baseUrl);
261
+ const cookieStr = Cookies.serialize(ctx.cookies);
262
+ await waitForHandshake(wsUrl, cookieStr, 3e4, ctx.debug);
263
+ if (ctx.config.accountId) {
264
+ await switchAccount(ctx, ctx.config.accountId);
265
+ await waitForHandshake(endpoints.websocket(ctx.baseUrl), Cookies.serialize(ctx.cookies), 3e4, ctx.debug);
266
+ }
267
+ }
268
+
269
+ // src/domains/symbol/symbol.ts
270
+ async function getSymbolSuggestions(ctx, text) {
271
+ ctx.ensureSession();
272
+ try {
273
+ const cookieStr = Cookies.serialize(ctx.cookies);
274
+ const response = await retryRequest(
275
+ {
276
+ method: "GET",
277
+ url: endpoints.suggest(ctx.baseUrl, text),
278
+ headers: { ...baseHeaders(), Cookie: cookieStr }
279
+ },
280
+ ctx.retries
281
+ );
282
+ const suggests = response.data?.suggests;
283
+ if (!suggests?.length) {
284
+ ctx.throwError("NO_SUGGESTIONS", "No symbol suggestions found");
285
+ }
286
+ return suggests;
287
+ } catch (error) {
288
+ if (error instanceof DxtradeError) throw error;
289
+ const message = error instanceof Error ? error.message : "Unknown error";
290
+ ctx.throwError("SUGGEST_ERROR", `Error getting symbol suggestions: ${message}`);
291
+ }
292
+ }
293
+ async function getSymbolInfo(ctx, symbol) {
294
+ ctx.ensureSession();
295
+ try {
296
+ const offsetMinutes = Math.abs((/* @__PURE__ */ new Date()).getTimezoneOffset());
297
+ const cookieStr = Cookies.serialize(ctx.cookies);
298
+ const response = await retryRequest(
299
+ {
300
+ method: "GET",
301
+ url: endpoints.instrumentInfo(ctx.baseUrl, symbol, offsetMinutes),
302
+ headers: { ...baseHeaders(), Cookie: cookieStr }
303
+ },
304
+ ctx.retries
305
+ );
306
+ if (!response.data) {
307
+ ctx.throwError("NO_SYMBOL_INFO", "No symbol info returned");
308
+ }
309
+ return response.data;
310
+ } catch (error) {
311
+ if (error instanceof DxtradeError) throw error;
312
+ const message = error instanceof Error ? error.message : "Unknown error";
313
+ ctx.throwError("SYMBOL_INFO_ERROR", `Error getting symbol info: ${message}`);
314
+ }
315
+ }
316
+
317
+ // src/domains/order/order.ts
318
+ import crypto from "crypto";
319
+ import WebSocket2 from "ws";
320
+ function createOrderListener(wsUrl, cookieStr, timeout = 3e4, debug = false) {
321
+ const ws = new WebSocket2(wsUrl, { headers: { Cookie: cookieStr } });
322
+ let settled = false;
323
+ const ready = new Promise((resolve) => {
324
+ ws.on("open", resolve);
325
+ });
326
+ const promise = new Promise((resolve, reject) => {
143
327
  const timer = setTimeout(() => {
328
+ if (settled) return;
329
+ settled = true;
144
330
  ws.close();
145
331
  reject(new Error("[dxtrade-api] Order update timed out"));
146
332
  }, timeout);
333
+ function done(err, result) {
334
+ if (settled) return;
335
+ settled = true;
336
+ clearTimeout(timer);
337
+ ws.close();
338
+ if (err) reject(err);
339
+ else resolve(result);
340
+ }
147
341
  ws.on("message", (data) => {
148
- const str = data.toString();
149
- if (debug) console.log("[dxtrade-api:ws]", str);
150
- if (!str.includes(`"ORDERS"`)) return;
151
- if (!str.includes(`orderId`)) return;
152
- const json = str.replace(/^.*?\{/, "{");
153
- if (!json.includes("{")) return;
154
- try {
155
- const response = JSON.parse(json);
156
- const body = response.body?.[0];
157
- if (!body) return;
158
- clearTimeout(timer);
159
- ws.close();
342
+ const msg = parseWsData(data);
343
+ if (shouldLog(msg, debug)) debugLog(msg);
344
+ if (typeof msg === "string") return;
345
+ if (msg.type === "MESSAGE" /* MESSAGE */) {
346
+ const messages = msg.body;
347
+ const orderMsg = messages?.findLast?.(
348
+ (m) => m.messageCategory === "TRADE_LOG" && m.messageType === "ORDER" && !m.historyMessage
349
+ );
350
+ if (!orderMsg) return;
351
+ const params = orderMsg.parametersTO;
352
+ if (params.orderStatus === "REJECTED") {
353
+ const reason = params.rejectReason?.key ?? "Unknown reason";
354
+ done(new Error(`[dxtrade-api] Order rejected: ${reason}`));
355
+ } else if (params.orderStatus === "FILLED") {
356
+ done(null, {
357
+ orderId: params.orderKey,
358
+ status: params.orderStatus,
359
+ symbol: params.symbol,
360
+ filledQuantity: params.filledQuantity,
361
+ filledPrice: params.filledPrice
362
+ });
363
+ }
364
+ return;
365
+ }
366
+ if (msg.type === "ORDERS" /* ORDERS */) {
367
+ const body = msg.body?.[0];
368
+ if (!body?.orderId) return;
160
369
  if (body.status === "REJECTED") {
161
- reject(new Error(`[dxtrade-api] Order rejected: ${body.statusDescription ?? "Unknown reason"}`));
162
- } else {
163
- resolve(body);
370
+ done(new Error(`[dxtrade-api] Order rejected: ${body.statusDescription ?? "Unknown reason"}`));
371
+ } else if (body.status === "FILLED") {
372
+ done(null, body);
164
373
  }
165
- } catch {
166
374
  }
167
375
  });
168
376
  ws.on("error", (error) => {
377
+ if (settled) return;
378
+ settled = true;
169
379
  clearTimeout(timer);
170
380
  ws.close();
171
381
  reject(new Error(`[dxtrade-api] WebSocket order listener error: ${error.message}`));
172
382
  });
173
383
  });
384
+ return { promise, ready };
385
+ }
386
+ async function submitOrder(ctx, params) {
387
+ ctx.ensureSession();
388
+ const {
389
+ symbol,
390
+ side,
391
+ quantity,
392
+ orderType,
393
+ orderCode,
394
+ price,
395
+ instrumentId,
396
+ stopLoss,
397
+ takeProfit,
398
+ positionEffect = "OPENING" /* OPENING */,
399
+ positionCode,
400
+ tif = "GTC",
401
+ expireDate,
402
+ metadata
403
+ } = params;
404
+ const info = await getSymbolInfo(ctx, symbol);
405
+ const units = Math.round(quantity * info.lotSize);
406
+ const qty = side === "BUY" /* BUY */ ? units : -units;
407
+ const priceParam = orderType === "STOP" /* STOP */ ? "stopPrice" : "limitPrice";
408
+ const orderData = {
409
+ directExchange: false,
410
+ legs: [
411
+ {
412
+ ...instrumentId != null && { instrumentId },
413
+ ...positionCode != null && { positionCode },
414
+ positionEffect,
415
+ ratioQuantity: 1,
416
+ symbol
417
+ }
418
+ ],
419
+ orderSide: side,
420
+ orderType,
421
+ quantity: qty,
422
+ requestId: orderCode ?? `gwt-uid-931-${crypto.randomUUID()}`,
423
+ timeInForce: tif,
424
+ ...expireDate != null && { expireDate },
425
+ ...metadata != null && { metadata }
426
+ };
427
+ if (price != null && orderType !== "MARKET" /* MARKET */) {
428
+ orderData[priceParam] = price;
429
+ }
430
+ if (stopLoss) {
431
+ orderData.stopLoss = {
432
+ ...stopLoss.offset != null && { fixedOffset: stopLoss.offset },
433
+ ...stopLoss.price != null && { fixedPrice: stopLoss.price },
434
+ priceFixed: stopLoss.price != null,
435
+ orderChainId: 0,
436
+ orderId: 0,
437
+ orderType: "STOP" /* STOP */,
438
+ quantityForProtection: qty,
439
+ removed: false
440
+ };
441
+ }
442
+ if (takeProfit) {
443
+ orderData.takeProfit = {
444
+ ...takeProfit.offset != null && { fixedOffset: takeProfit.offset },
445
+ ...takeProfit.price != null && { fixedPrice: takeProfit.price },
446
+ priceFixed: takeProfit.price != null,
447
+ orderChainId: 0,
448
+ orderId: 0,
449
+ orderType: "LIMIT" /* LIMIT */,
450
+ quantityForProtection: qty,
451
+ removed: false
452
+ };
453
+ }
454
+ try {
455
+ const wsUrl = endpoints.websocket(ctx.baseUrl);
456
+ const cookieStr = Cookies.serialize(ctx.cookies);
457
+ const listener = createOrderListener(wsUrl, cookieStr, 3e4, ctx.debug);
458
+ await listener.ready;
459
+ const response = await retryRequest(
460
+ {
461
+ method: "POST",
462
+ url: endpoints.submitOrder(ctx.baseUrl),
463
+ data: orderData,
464
+ headers: authHeaders(ctx.csrf, Cookies.serialize(ctx.cookies))
465
+ },
466
+ ctx.retries
467
+ );
468
+ ctx.callbacks.onOrderPlaced?.(response.data);
469
+ const orderUpdate = await listener.promise;
470
+ ctx.callbacks.onOrderUpdate?.(orderUpdate);
471
+ return orderUpdate;
472
+ } catch (error) {
473
+ if (error instanceof DxtradeError) throw error;
474
+ const message = error instanceof Error ? error.response?.data?.message ?? error.message : "Unknown error";
475
+ ctx.throwError("ORDER_ERROR", `Error submitting order: ${message}`);
476
+ }
477
+ }
478
+
479
+ // src/domains/assessments/assessments.ts
480
+ async function getAssessments(ctx, params) {
481
+ ctx.ensureSession();
482
+ try {
483
+ const response = await retryRequest(
484
+ {
485
+ method: "POST",
486
+ url: endpoints.assessments(ctx.baseUrl),
487
+ data: {
488
+ from: params.from,
489
+ instrument: params.instrument,
490
+ subtype: params.subtype ?? null,
491
+ to: params.to
492
+ },
493
+ headers: authHeaders(ctx.csrf, Cookies.serialize(ctx.cookies))
494
+ },
495
+ ctx.retries
496
+ );
497
+ return response.data;
498
+ } catch (error) {
499
+ if (error instanceof DxtradeError) throw error;
500
+ const message = error instanceof Error ? error.message : "Unknown error";
501
+ ctx.throwError("ASSESSMENTS_ERROR", `Error fetching assessments: ${message}`);
502
+ }
174
503
  }
175
504
 
176
505
  // src/client.ts
177
506
  var DxtradeClient = class {
178
- config;
179
- callbacks;
180
- cookies = {};
181
- csrf = null;
182
- baseUrl;
183
- retries;
184
- debug;
507
+ _ctx;
185
508
  constructor(config) {
186
- this.config = config;
187
- this.callbacks = config.callbacks ?? {};
188
- this.baseUrl = resolveBrokerUrl(config.broker, config.brokerUrls);
189
- this.retries = config.retries ?? 3;
190
- this.debug = config.debug ?? false;
509
+ const callbacks = config.callbacks ?? {};
510
+ this._ctx = {
511
+ config,
512
+ callbacks,
513
+ cookies: {},
514
+ csrf: null,
515
+ baseUrl: resolveBrokerUrl(config.broker, config.brokerUrls),
516
+ retries: config.retries ?? 3,
517
+ debug: config.debug ?? false,
518
+ ensureSession() {
519
+ if (!this.csrf) {
520
+ throw new DxtradeError("NO_SESSION", "No active session. Call login() and fetchCsrf() or connect() first.");
521
+ }
522
+ },
523
+ throwError(code, message) {
524
+ const error = new DxtradeError(code, message);
525
+ callbacks.onError?.(error);
526
+ throw error;
527
+ }
528
+ };
191
529
  }
192
- // ── Session ─────────────────────────────────────────────────────────────────────────────────────────────────────────
193
530
  async login() {
194
- try {
195
- const response = await retryRequest(
196
- {
197
- method: "POST",
198
- url: endpoints.login(this.baseUrl),
199
- data: {
200
- username: this.config.username,
201
- password: this.config.password,
202
- domain: this.config.broker
203
- },
204
- headers: { "Content-Type": "application/json" }
205
- },
206
- this.retries
207
- );
208
- if (response.status === 200) {
209
- const setCookies = response.headers["set-cookie"] ?? [];
210
- const incoming = parseCookies(setCookies);
211
- this.cookies = mergeCookies(this.cookies, incoming);
212
- this.callbacks.onLogin?.();
213
- } else {
214
- this.throwError("LOGIN_FAILED", `Login failed: ${response.status}`);
215
- }
216
- } catch (error) {
217
- if (error instanceof DxtradeError) throw error;
218
- const message = error instanceof Error ? error.message : "Unknown error";
219
- this.throwError("LOGIN_ERROR", `Login error: ${message}`);
220
- }
531
+ return login(this._ctx);
221
532
  }
222
533
  async fetchCsrf() {
223
- try {
224
- const cookieStr = serializeCookies(this.cookies);
225
- const response = await retryRequest(
226
- {
227
- method: "GET",
228
- url: this.baseUrl,
229
- headers: { ...cookieOnlyHeaders(cookieStr), Referer: this.baseUrl }
230
- },
231
- this.retries
232
- );
233
- const csrfMatch = response.data?.match(/name="csrf" content="([^"]+)"/);
234
- if (csrfMatch) {
235
- this.csrf = csrfMatch[1];
236
- } else {
237
- this.throwError("CSRF_NOT_FOUND", "CSRF token not found");
238
- }
239
- } catch (error) {
240
- if (error instanceof DxtradeError) throw error;
241
- const message = error instanceof Error ? error.message : "Unknown error";
242
- this.throwError("CSRF_ERROR", `CSRF fetch error: ${message}`);
243
- }
534
+ return fetchCsrf(this._ctx);
244
535
  }
245
536
  async switchAccount(accountId) {
246
- this.ensureSession();
247
- try {
248
- await retryRequest(
249
- {
250
- method: "POST",
251
- url: endpoints.switchAccount(this.baseUrl, accountId),
252
- headers: authHeaders(this.csrf, serializeCookies(this.cookies))
253
- },
254
- this.retries
255
- );
256
- this.callbacks.onAccountSwitch?.(accountId);
257
- } catch (error) {
258
- if (error instanceof DxtradeError) throw error;
259
- const message = error instanceof Error ? error.message : "Unknown error";
260
- this.throwError(
261
- "ACCOUNT_SWITCH_ERROR",
262
- `Error switching account: ${message}`
263
- );
264
- }
537
+ return switchAccount(this._ctx, accountId);
538
+ }
539
+ async connect() {
540
+ return connect(this._ctx);
265
541
  }
266
- // ── Market Data ─────────────────────────────────────────────────────────────────────────────────────────────────────
267
542
  async getSymbolSuggestions(text) {
268
- this.ensureSession();
269
- try {
270
- const cookieStr = serializeCookies(this.cookies);
271
- const response = await retryRequest(
272
- {
273
- method: "GET",
274
- url: endpoints.suggest(this.baseUrl, text),
275
- headers: { ...baseHeaders(), Cookie: cookieStr }
276
- },
277
- this.retries
278
- );
279
- const suggests = response.data?.suggests;
280
- if (!suggests?.length) {
281
- this.throwError("NO_SUGGESTIONS", "No symbol suggestions found");
282
- }
283
- return suggests;
284
- } catch (error) {
285
- if (error instanceof DxtradeError) throw error;
286
- const message = error instanceof Error ? error.message : "Unknown error";
287
- this.throwError(
288
- "SUGGEST_ERROR",
289
- `Error getting symbol suggestions: ${message}`
290
- );
291
- }
543
+ return getSymbolSuggestions(this._ctx, text);
292
544
  }
293
545
  async getSymbolInfo(symbol) {
294
- this.ensureSession();
295
- try {
296
- const offsetMinutes = Math.abs((/* @__PURE__ */ new Date()).getTimezoneOffset());
297
- const cookieStr = serializeCookies(this.cookies);
298
- const response = await retryRequest(
299
- {
300
- method: "GET",
301
- url: endpoints.instrumentInfo(this.baseUrl, symbol, offsetMinutes),
302
- headers: { ...baseHeaders(), Cookie: cookieStr }
303
- },
304
- this.retries
305
- );
306
- if (!response.data) {
307
- this.throwError("NO_SYMBOL_INFO", "No symbol info returned");
308
- }
309
- return response.data;
310
- } catch (error) {
311
- if (error instanceof DxtradeError) throw error;
312
- const message = error instanceof Error ? error.message : "Unknown error";
313
- this.throwError(
314
- "SYMBOL_INFO_ERROR",
315
- `Error getting symbol info: ${message}`
316
- );
317
- }
546
+ return getSymbolInfo(this._ctx, symbol);
318
547
  }
319
- // ── Trading ─────────────────────────────────────────────────────────────────────────────────────────────────────────
320
548
  async submitOrder(params) {
321
- this.ensureSession();
322
- const {
323
- symbol,
324
- side,
325
- quantity,
326
- orderType,
327
- price,
328
- instrumentId,
329
- stopLoss,
330
- takeProfit,
331
- positionEffect = "OPENING" /* OPENING */
332
- } = params;
333
- const qty = side === "BUY" /* BUY */ ? Math.abs(quantity) : -Math.abs(quantity);
334
- const priceParam = orderType === "STOP" /* STOP */ ? "stopPrice" : "limitPrice";
335
- const orderData = {
336
- directExchange: false,
337
- legs: [
338
- {
339
- ...instrumentId != null && { instrumentId },
340
- positionEffect,
341
- ratioQuantity: 1,
342
- symbol
343
- }
344
- ],
345
- orderSide: side,
346
- orderType,
347
- quantity: qty,
348
- requestId: `gwt-uid-931-${crypto.randomUUID()}`,
349
- timeInForce: "GTC"
350
- };
351
- if (price != null && orderType !== "MARKET" /* MARKET */) {
352
- orderData[priceParam] = price;
353
- }
354
- if (stopLoss) {
355
- orderData.stopLoss = {
356
- ...stopLoss.offset != null && { fixedOffset: stopLoss.offset },
357
- ...stopLoss.price != null && { fixedPrice: stopLoss.price },
358
- priceFixed: stopLoss.price != null,
359
- orderChainId: 0,
360
- orderId: 0,
361
- orderType: "STOP" /* STOP */,
362
- quantityForProtection: qty,
363
- removed: false
364
- };
365
- }
366
- if (takeProfit) {
367
- orderData.takeProfit = {
368
- ...takeProfit.offset != null && { fixedOffset: takeProfit.offset },
369
- ...takeProfit.price != null && { fixedPrice: takeProfit.price },
370
- priceFixed: takeProfit.price != null,
371
- orderChainId: 0,
372
- orderId: 0,
373
- orderType: "LIMIT" /* LIMIT */,
374
- quantityForProtection: qty,
375
- removed: false
376
- };
377
- }
378
- try {
379
- const response = await retryRequest(
380
- {
381
- method: "POST",
382
- url: endpoints.submitOrder(this.baseUrl),
383
- data: orderData,
384
- headers: authHeaders(this.csrf, serializeCookies(this.cookies))
385
- },
386
- this.retries
387
- );
388
- if (response.status !== 200) {
389
- this.throwError(
390
- "ORDER_SUBMIT_FAILED",
391
- `Order submission failed with status: ${response.status}`
392
- );
393
- }
394
- this.callbacks.onOrderPlaced?.({
395
- status: response.status,
396
- data: response.data
397
- });
398
- const wsUrl = endpoints.websocket(this.baseUrl);
399
- const cookieStr = serializeCookies(this.cookies);
400
- const orderUpdate = await waitForOrderUpdate(
401
- wsUrl,
402
- cookieStr,
403
- 3e4,
404
- this.debug
405
- );
406
- this.callbacks.onOrderUpdate?.(orderUpdate);
407
- return orderUpdate;
408
- } catch (error) {
409
- if (error instanceof DxtradeError) throw error;
410
- const message = error instanceof Error ? error.response?.data?.message ?? error.message : "Unknown error";
411
- this.throwError("ORDER_ERROR", `Error submitting order: ${message}`);
412
- }
549
+ return submitOrder(this._ctx, params);
413
550
  }
414
- // ── Analytics ───────────────────────────────────────────────────────────────────────────────────────────────────────
415
551
  async getAssessments(params) {
416
- this.ensureSession();
417
- try {
418
- const response = await retryRequest(
419
- {
420
- method: "POST",
421
- url: endpoints.assessments(this.baseUrl),
422
- data: {
423
- from: params.from,
424
- instrument: params.instrument,
425
- subtype: params.subtype ?? null,
426
- to: params.to
427
- },
428
- headers: authHeaders(this.csrf, serializeCookies(this.cookies))
429
- },
430
- this.retries
431
- );
432
- return response.data;
433
- } catch (error) {
434
- if (error instanceof DxtradeError) throw error;
435
- const message = error instanceof Error ? error.message : "Unknown error";
436
- this.throwError(
437
- "ASSESSMENTS_ERROR",
438
- `Error fetching assessments: ${message}`
439
- );
440
- }
441
- }
442
- // ── Convenience ─────────────────────────────────────────────────────────────────────────────────────────────────────
443
- async connect() {
444
- await this.login();
445
- await this.fetchCsrf();
446
- const wsUrl = endpoints.websocket(this.baseUrl);
447
- const cookieStr = serializeCookies(this.cookies);
448
- await waitForHandshake(wsUrl, cookieStr, 3e4, this.debug);
449
- if (this.config.accountId) {
450
- await this.switchAccount(this.config.accountId);
451
- await waitForHandshake(
452
- endpoints.websocket(this.baseUrl),
453
- serializeCookies(this.cookies),
454
- 3e4,
455
- this.debug
456
- );
457
- }
458
- }
459
- // ── Internals ───────────────────────────────────────────────────────────────────────────────────────────────────────
460
- ensureSession() {
461
- if (!this.csrf) {
462
- throw new DxtradeError(
463
- "NO_SESSION",
464
- "No active session. Call login() and fetchCsrf() or connect() first."
465
- );
466
- }
467
- }
468
- throwError(code, message) {
469
- const error = new DxtradeError(code, message);
470
- this.callbacks.onError?.(error);
471
- throw error;
552
+ return getAssessments(this._ctx, params);
472
553
  }
473
554
  };
474
555
  export {
@@ -478,6 +559,8 @@ export {
478
559
  DxtradeError,
479
560
  ORDER_TYPE,
480
561
  SIDE,
562
+ TIF,
563
+ WS_MESSAGE,
481
564
  endpoints,
482
565
  resolveBrokerUrl
483
566
  };