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