hedgequantx 1.8.48 → 2.3.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/README.md +7 -6
- package/bin/cli.js +13 -7
- package/dist/algo/copy-engine.js +3 -0
- package/dist/algo/copy-engine.jsc +0 -0
- package/dist/algo/engine.js +3 -0
- package/dist/algo/engine.jsc +0 -0
- package/dist/algo/market-data-rithmic.js +3 -0
- package/dist/algo/market-data-rithmic.jsc +0 -0
- package/dist/algo/market-data.js +3 -0
- package/dist/algo/market-data.jsc +0 -0
- package/dist/algo/rithmic/connection.js +3 -0
- package/dist/algo/rithmic/connection.jsc +0 -0
- package/dist/algo/rithmic/constants.js +3 -0
- package/dist/algo/rithmic/constants.jsc +0 -0
- package/dist/algo/rithmic/index.js +3 -0
- package/dist/algo/rithmic/index.jsc +0 -0
- package/dist/algo/rithmic/market-data.js +3 -0
- package/dist/algo/rithmic/market-data.jsc +0 -0
- package/dist/algo/rithmic/pnl.js +3 -0
- package/dist/algo/rithmic/pnl.jsc +0 -0
- package/dist/algo/rithmic/pool.js +3 -0
- package/dist/algo/rithmic/pool.jsc +0 -0
- package/dist/algo/rithmic/trading.js +3 -0
- package/dist/algo/rithmic/trading.jsc +0 -0
- package/dist/algo/rithmic-decoder.js +3 -0
- package/dist/algo/rithmic-decoder.jsc +0 -0
- package/dist/algo/strategies/ultra-scalping-v2.js +3 -0
- package/dist/algo/strategies/ultra-scalping-v2.jsc +0 -0
- package/dist/algo/strategies/ultra-scalping.js +3 -0
- package/dist/algo/strategies/ultra-scalping.jsc +0 -0
- package/dist/algo/trading-api-rithmic.js +3 -0
- package/dist/algo/trading-api-rithmic.jsc +0 -0
- package/dist/algo/trading-api.js +3 -0
- package/dist/algo/trading-api.jsc +0 -0
- package/dist/algo/utils/smart-logger.js +3 -0
- package/dist/algo/utils/smart-logger.jsc +0 -0
- package/dist/algo/utils/smart-logs.js +3 -0
- package/dist/algo/utils/smart-logs.jsc +0 -0
- package/package.json +33 -10
- package/protos/rithmic/account_pnl_position_update.proto +59 -0
- package/protos/rithmic/base.proto +7 -0
- package/protos/rithmic/best_bid_offer.proto +39 -0
- package/protos/rithmic/exchange_order_notification.proto +140 -0
- package/protos/rithmic/instrument_pnl_position_update.proto +50 -0
- package/protos/rithmic/last_trade.proto +53 -0
- package/protos/rithmic/request_account_list.proto +20 -0
- package/protos/rithmic/request_cancel_all_orders.proto +15 -0
- package/protos/rithmic/request_front_month_contract.proto +10 -0
- package/protos/rithmic/request_heartbeat.proto +13 -0
- package/protos/rithmic/request_login.proto +28 -0
- package/protos/rithmic/request_login_info.proto +10 -0
- package/protos/rithmic/request_logout.proto +10 -0
- package/protos/rithmic/request_market_data_update.proto +42 -0
- package/protos/rithmic/request_new_order.proto +84 -0
- package/protos/rithmic/request_pnl_position_snapshot.proto +14 -0
- package/protos/rithmic/request_pnl_position_updates.proto +20 -0
- package/protos/rithmic/request_product_codes.proto +9 -0
- package/protos/rithmic/request_rithmic_system_info.proto +8 -0
- package/protos/rithmic/request_show_order_history.proto +16 -0
- package/protos/rithmic/request_show_order_history_dates.proto +10 -0
- package/protos/rithmic/request_show_order_history_summary.proto +14 -0
- package/protos/rithmic/request_show_orders.proto +14 -0
- package/protos/rithmic/request_subscribe_for_order_updates.proto +14 -0
- package/protos/rithmic/request_tick_bar_replay.proto +48 -0
- package/protos/rithmic/request_trade_routes.proto +11 -0
- package/protos/rithmic/response_account_list.proto +18 -0
- package/protos/rithmic/response_front_month_contract.proto +13 -0
- package/protos/rithmic/response_heartbeat.proto +14 -0
- package/protos/rithmic/response_login.proto +18 -0
- package/protos/rithmic/response_login_info.proto +24 -0
- package/protos/rithmic/response_logout.proto +11 -0
- package/protos/rithmic/response_market_data_update.proto +9 -0
- package/protos/rithmic/response_new_order.proto +18 -0
- package/protos/rithmic/response_pnl_position_snapshot.proto +11 -0
- package/protos/rithmic/response_pnl_position_updates.proto +11 -0
- package/protos/rithmic/response_product_codes.proto +12 -0
- package/protos/rithmic/response_rithmic_system_info.proto +12 -0
- package/protos/rithmic/response_show_order_history.proto +11 -0
- package/protos/rithmic/response_show_order_history_dates.proto +13 -0
- package/protos/rithmic/response_show_order_history_summary.proto +11 -0
- package/protos/rithmic/response_show_orders.proto +11 -0
- package/protos/rithmic/response_subscribe_for_order_updates.proto +11 -0
- package/protos/rithmic/response_tick_bar_replay.proto +40 -0
- package/protos/rithmic/response_trade_routes.proto +19 -0
- package/protos/rithmic/rithmic_order_notification.proto +124 -0
- package/src/app.js +136 -89
- package/src/config/index.js +27 -8
- package/src/config/settings.js +155 -0
- package/src/pages/accounts.js +2 -3
- package/src/pages/algo/copy-trading.js +293 -200
- package/src/pages/algo/one-account.js +1 -1
- package/src/security/encryption.js +81 -46
- package/src/security/index.js +12 -8
- package/src/security/rateLimit.js +68 -65
- package/src/security/validation.js +93 -79
- package/src/services/hqx-server.js +538 -206
- package/src/services/projectx/index.js +327 -204
- package/src/services/rithmic/index.js +288 -285
- package/src/services/session.js +184 -114
- package/src/services/tradovate/index.js +286 -297
- package/src/ui/index.js +53 -1
- package/src/utils/http.js +236 -0
- package/src/utils/index.js +11 -2
- package/src/utils/logger.js +64 -33
- package/src/utils/prompts.js +79 -71
package/src/services/session.js
CHANGED
|
@@ -6,16 +6,18 @@
|
|
|
6
6
|
const fs = require('fs');
|
|
7
7
|
const path = require('path');
|
|
8
8
|
const os = require('os');
|
|
9
|
-
const
|
|
10
|
-
const {
|
|
11
|
-
const {
|
|
12
|
-
const {
|
|
9
|
+
const crypto = require('crypto');
|
|
10
|
+
const { encrypt, decrypt, maskSensitive, secureWipe } = require('../security/encryption');
|
|
11
|
+
const { SECURITY } = require('../config/settings');
|
|
12
|
+
const { logger } = require('../utils/logger');
|
|
13
13
|
|
|
14
|
-
const
|
|
15
|
-
|
|
14
|
+
const log = logger.scope('Session');
|
|
15
|
+
|
|
16
|
+
const SESSION_DIR = path.join(os.homedir(), SECURITY.SESSION_DIR);
|
|
17
|
+
const SESSION_FILE = path.join(SESSION_DIR, SECURITY.SESSION_FILE);
|
|
16
18
|
|
|
17
19
|
/**
|
|
18
|
-
* Secure session storage with AES-256 encryption
|
|
20
|
+
* Secure session storage with AES-256-GCM encryption
|
|
19
21
|
*/
|
|
20
22
|
const storage = {
|
|
21
23
|
/**
|
|
@@ -24,22 +26,27 @@ const storage = {
|
|
|
24
26
|
*/
|
|
25
27
|
_ensureDir() {
|
|
26
28
|
if (!fs.existsSync(SESSION_DIR)) {
|
|
27
|
-
fs.mkdirSync(SESSION_DIR, { recursive: true, mode:
|
|
29
|
+
fs.mkdirSync(SESSION_DIR, { recursive: true, mode: SECURITY.DIR_PERMISSIONS });
|
|
30
|
+
log.debug('Created session directory');
|
|
28
31
|
}
|
|
29
32
|
},
|
|
30
33
|
|
|
31
34
|
/**
|
|
32
35
|
* Saves sessions with encryption
|
|
33
36
|
* @param {Array} sessions - Sessions to save
|
|
37
|
+
* @returns {boolean} Success status
|
|
34
38
|
*/
|
|
35
39
|
save(sessions) {
|
|
36
40
|
try {
|
|
37
41
|
this._ensureDir();
|
|
38
42
|
const data = JSON.stringify(sessions);
|
|
39
43
|
const encrypted = encrypt(data);
|
|
40
|
-
fs.writeFileSync(SESSION_FILE, encrypted, { mode:
|
|
41
|
-
|
|
42
|
-
|
|
44
|
+
fs.writeFileSync(SESSION_FILE, encrypted, { mode: SECURITY.FILE_PERMISSIONS });
|
|
45
|
+
log.debug('Session saved', { count: sessions.length });
|
|
46
|
+
return true;
|
|
47
|
+
} catch (err) {
|
|
48
|
+
log.error('Failed to save session', { error: err.message });
|
|
49
|
+
return false;
|
|
43
50
|
}
|
|
44
51
|
},
|
|
45
52
|
|
|
@@ -49,34 +56,59 @@ const storage = {
|
|
|
49
56
|
*/
|
|
50
57
|
load() {
|
|
51
58
|
try {
|
|
52
|
-
if (fs.existsSync(SESSION_FILE)) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
59
|
+
if (!fs.existsSync(SESSION_FILE)) {
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const encrypted = fs.readFileSync(SESSION_FILE, 'utf8');
|
|
64
|
+
const decrypted = decrypt(encrypted);
|
|
65
|
+
|
|
66
|
+
if (!decrypted) {
|
|
67
|
+
log.warn('Session decryption failed - clearing');
|
|
68
|
+
this.clear();
|
|
69
|
+
return [];
|
|
58
70
|
}
|
|
59
|
-
|
|
60
|
-
|
|
71
|
+
|
|
72
|
+
const sessions = JSON.parse(decrypted);
|
|
73
|
+
log.debug('Session loaded', { count: sessions.length });
|
|
74
|
+
return sessions;
|
|
75
|
+
} catch (err) {
|
|
76
|
+
log.error('Failed to load session', { error: err.message });
|
|
61
77
|
this.clear();
|
|
78
|
+
return [];
|
|
62
79
|
}
|
|
63
|
-
return [];
|
|
64
80
|
},
|
|
65
81
|
|
|
66
82
|
/**
|
|
67
83
|
* Securely clears session data
|
|
84
|
+
* @returns {boolean} Success status
|
|
68
85
|
*/
|
|
69
86
|
clear() {
|
|
70
87
|
try {
|
|
71
88
|
if (fs.existsSync(SESSION_FILE)) {
|
|
72
89
|
// Overwrite with random data before deleting
|
|
73
90
|
const size = fs.statSync(SESSION_FILE).size;
|
|
74
|
-
|
|
91
|
+
if (size > 0) {
|
|
92
|
+
fs.writeFileSync(SESSION_FILE, crypto.randomBytes(size));
|
|
93
|
+
}
|
|
75
94
|
fs.unlinkSync(SESSION_FILE);
|
|
95
|
+
log.debug('Session cleared securely');
|
|
76
96
|
}
|
|
77
|
-
|
|
78
|
-
|
|
97
|
+
return true;
|
|
98
|
+
} catch (err) {
|
|
99
|
+
log.error('Failed to clear session', { error: err.message });
|
|
100
|
+
return false;
|
|
79
101
|
}
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// Lazy load services to avoid circular dependencies
|
|
106
|
+
let ProjectXService, RithmicService, TradovateService;
|
|
107
|
+
const loadServices = () => {
|
|
108
|
+
if (!ProjectXService) {
|
|
109
|
+
({ ProjectXService } = require('./projectx'));
|
|
110
|
+
({ RithmicService } = require('./rithmic'));
|
|
111
|
+
({ TradovateService } = require('./tradovate'));
|
|
80
112
|
}
|
|
81
113
|
};
|
|
82
114
|
|
|
@@ -84,12 +116,12 @@ const storage = {
|
|
|
84
116
|
* Multi-connection manager with secure token handling
|
|
85
117
|
*/
|
|
86
118
|
const connections = {
|
|
87
|
-
/** @type {Array
|
|
119
|
+
/** @type {Array<{type: string, service: Object, propfirm: string, propfirmKey: string, token: string, connectedAt: Date}>} */
|
|
88
120
|
services: [],
|
|
89
121
|
|
|
90
122
|
/**
|
|
91
123
|
* Adds a new connection
|
|
92
|
-
* @param {string} type - Connection type (projectx, rithmic,
|
|
124
|
+
* @param {string} type - Connection type (projectx, rithmic, tradovate)
|
|
93
125
|
* @param {Object} service - Service instance
|
|
94
126
|
* @param {string} [propfirm] - PropFirm name
|
|
95
127
|
* @param {string} [token] - Auth token
|
|
@@ -99,10 +131,12 @@ const connections = {
|
|
|
99
131
|
type,
|
|
100
132
|
service,
|
|
101
133
|
propfirm,
|
|
134
|
+
propfirmKey: service.propfirmKey,
|
|
102
135
|
token: token || service.token,
|
|
103
|
-
connectedAt: new Date()
|
|
136
|
+
connectedAt: new Date(),
|
|
104
137
|
});
|
|
105
138
|
this.saveToStorage();
|
|
139
|
+
log.info('Connection added', { type, propfirm: propfirm || type });
|
|
106
140
|
},
|
|
107
141
|
|
|
108
142
|
/**
|
|
@@ -119,12 +153,12 @@ const connections = {
|
|
|
119
153
|
if (conn.type === 'projectx') {
|
|
120
154
|
session.token = conn.service.token || conn.token;
|
|
121
155
|
} else if (conn.type === 'rithmic' || conn.type === 'tradovate') {
|
|
122
|
-
// Save encrypted credentials for reconnection
|
|
123
156
|
session.credentials = conn.service.credentials;
|
|
124
157
|
}
|
|
125
158
|
|
|
126
159
|
return session;
|
|
127
160
|
});
|
|
161
|
+
|
|
128
162
|
storage.save(sessions);
|
|
129
163
|
},
|
|
130
164
|
|
|
@@ -133,84 +167,110 @@ const connections = {
|
|
|
133
167
|
* @returns {Promise<boolean>} True if sessions were restored
|
|
134
168
|
*/
|
|
135
169
|
async restoreFromStorage() {
|
|
170
|
+
loadServices();
|
|
136
171
|
const sessions = storage.load();
|
|
137
172
|
|
|
173
|
+
if (!sessions.length) {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
log.info('Restoring sessions', { count: sessions.length });
|
|
178
|
+
|
|
138
179
|
for (const session of sessions) {
|
|
139
180
|
try {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
service.token = session.token;
|
|
144
|
-
|
|
145
|
-
// Validate token is still valid
|
|
146
|
-
const userResult = await service.getUser();
|
|
147
|
-
if (userResult.success) {
|
|
148
|
-
this.services.push({
|
|
149
|
-
type: session.type,
|
|
150
|
-
service,
|
|
151
|
-
propfirm: session.propfirm,
|
|
152
|
-
propfirmKey: propfirmKey,
|
|
153
|
-
token: session.token,
|
|
154
|
-
connectedAt: new Date()
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
} else if (session.type === 'rithmic' && session.credentials) {
|
|
158
|
-
const propfirmKey = session.propfirmKey || 'apex_rithmic';
|
|
159
|
-
const service = new RithmicService(propfirmKey);
|
|
160
|
-
|
|
161
|
-
// Try to reconnect
|
|
162
|
-
const result = await service.login(session.credentials.username, session.credentials.password);
|
|
163
|
-
if (result.success) {
|
|
164
|
-
this.services.push({
|
|
165
|
-
type: session.type,
|
|
166
|
-
service,
|
|
167
|
-
propfirm: session.propfirm,
|
|
168
|
-
propfirmKey: propfirmKey,
|
|
169
|
-
connectedAt: new Date()
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
} else if (session.type === 'tradovate' && session.credentials) {
|
|
173
|
-
const propfirmKey = session.propfirmKey || 'tradovate';
|
|
174
|
-
const service = new TradovateService(propfirmKey);
|
|
175
|
-
|
|
176
|
-
// Try to reconnect
|
|
177
|
-
const result = await service.login(session.credentials.username, session.credentials.password);
|
|
178
|
-
if (result.success) {
|
|
179
|
-
this.services.push({
|
|
180
|
-
type: session.type,
|
|
181
|
-
service,
|
|
182
|
-
propfirm: session.propfirm,
|
|
183
|
-
propfirmKey: propfirmKey,
|
|
184
|
-
connectedAt: new Date()
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
} catch (e) {
|
|
189
|
-
// Invalid session - skip
|
|
181
|
+
await this._restoreSession(session);
|
|
182
|
+
} catch (err) {
|
|
183
|
+
log.warn('Failed to restore session', { type: session.type, error: err.message });
|
|
190
184
|
}
|
|
191
185
|
}
|
|
192
186
|
|
|
193
187
|
return this.services.length > 0;
|
|
194
188
|
},
|
|
195
189
|
|
|
190
|
+
/**
|
|
191
|
+
* Restores a single session
|
|
192
|
+
* @private
|
|
193
|
+
*/
|
|
194
|
+
async _restoreSession(session) {
|
|
195
|
+
const { type, propfirm, propfirmKey } = session;
|
|
196
|
+
|
|
197
|
+
if (type === 'projectx' && session.token) {
|
|
198
|
+
const service = new ProjectXService(propfirmKey || 'topstep');
|
|
199
|
+
service.token = session.token;
|
|
200
|
+
|
|
201
|
+
const userResult = await service.getUser();
|
|
202
|
+
if (userResult.success) {
|
|
203
|
+
this.services.push({
|
|
204
|
+
type,
|
|
205
|
+
service,
|
|
206
|
+
propfirm,
|
|
207
|
+
propfirmKey,
|
|
208
|
+
token: session.token,
|
|
209
|
+
connectedAt: new Date(),
|
|
210
|
+
});
|
|
211
|
+
log.debug('ProjectX session restored');
|
|
212
|
+
}
|
|
213
|
+
} else if (type === 'rithmic' && session.credentials) {
|
|
214
|
+
const service = new RithmicService(propfirmKey || 'apex_rithmic');
|
|
215
|
+
const result = await service.login(session.credentials.username, session.credentials.password);
|
|
216
|
+
|
|
217
|
+
if (result.success) {
|
|
218
|
+
this.services.push({
|
|
219
|
+
type,
|
|
220
|
+
service,
|
|
221
|
+
propfirm,
|
|
222
|
+
propfirmKey,
|
|
223
|
+
connectedAt: new Date(),
|
|
224
|
+
});
|
|
225
|
+
log.debug('Rithmic session restored');
|
|
226
|
+
}
|
|
227
|
+
} else if (type === 'tradovate' && session.credentials) {
|
|
228
|
+
const service = new TradovateService(propfirmKey || 'tradovate');
|
|
229
|
+
const result = await service.login(session.credentials.username, session.credentials.password);
|
|
230
|
+
|
|
231
|
+
if (result.success) {
|
|
232
|
+
this.services.push({
|
|
233
|
+
type,
|
|
234
|
+
service,
|
|
235
|
+
propfirm,
|
|
236
|
+
propfirmKey,
|
|
237
|
+
connectedAt: new Date(),
|
|
238
|
+
});
|
|
239
|
+
log.debug('Tradovate session restored');
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
|
|
196
244
|
/**
|
|
197
245
|
* Removes a connection by index
|
|
198
246
|
* @param {number} index - Connection index
|
|
199
247
|
*/
|
|
200
248
|
remove(index) {
|
|
201
|
-
if (index
|
|
202
|
-
|
|
203
|
-
|
|
249
|
+
if (index < 0 || index >= this.services.length) return;
|
|
250
|
+
|
|
251
|
+
const conn = this.services[index];
|
|
252
|
+
|
|
253
|
+
if (conn.service?.logout) {
|
|
254
|
+
try {
|
|
204
255
|
conn.service.logout();
|
|
256
|
+
} catch (err) {
|
|
257
|
+
log.warn('Logout failed', { error: err.message });
|
|
205
258
|
}
|
|
206
|
-
this.services.splice(index, 1);
|
|
207
|
-
this.saveToStorage();
|
|
208
259
|
}
|
|
260
|
+
|
|
261
|
+
// Clear credentials from memory
|
|
262
|
+
if (conn.service?.credentials) {
|
|
263
|
+
conn.service.credentials = null;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
this.services.splice(index, 1);
|
|
267
|
+
this.saveToStorage();
|
|
268
|
+
log.info('Connection removed', { type: conn.type });
|
|
209
269
|
},
|
|
210
270
|
|
|
211
271
|
/**
|
|
212
272
|
* Gets all connections
|
|
213
|
-
* @returns {Array}
|
|
273
|
+
* @returns {Array}
|
|
214
274
|
*/
|
|
215
275
|
getAll() {
|
|
216
276
|
return this.services;
|
|
@@ -219,7 +279,7 @@ const connections = {
|
|
|
219
279
|
/**
|
|
220
280
|
* Gets connections by type
|
|
221
281
|
* @param {string} type - Connection type
|
|
222
|
-
* @returns {Array}
|
|
282
|
+
* @returns {Array}
|
|
223
283
|
*/
|
|
224
284
|
getByType(type) {
|
|
225
285
|
return this.services.filter(c => c.type === type);
|
|
@@ -227,7 +287,7 @@ const connections = {
|
|
|
227
287
|
|
|
228
288
|
/**
|
|
229
289
|
* Gets connection count
|
|
230
|
-
* @returns {number}
|
|
290
|
+
* @returns {number}
|
|
231
291
|
*/
|
|
232
292
|
count() {
|
|
233
293
|
return this.services.length;
|
|
@@ -235,7 +295,7 @@ const connections = {
|
|
|
235
295
|
|
|
236
296
|
/**
|
|
237
297
|
* Gets all accounts from all connections
|
|
238
|
-
* @returns {Promise<Array>}
|
|
298
|
+
* @returns {Promise<Array>}
|
|
239
299
|
*/
|
|
240
300
|
async getAllAccounts() {
|
|
241
301
|
const allAccounts = [];
|
|
@@ -243,18 +303,19 @@ const connections = {
|
|
|
243
303
|
for (const conn of this.services) {
|
|
244
304
|
try {
|
|
245
305
|
const result = await conn.service.getTradingAccounts();
|
|
306
|
+
|
|
246
307
|
if (result.success && result.accounts) {
|
|
247
|
-
result.accounts
|
|
308
|
+
for (const account of result.accounts) {
|
|
248
309
|
allAccounts.push({
|
|
249
310
|
...account,
|
|
250
311
|
connectionType: conn.type,
|
|
251
312
|
propfirm: conn.propfirm || conn.type,
|
|
252
|
-
service: conn.service
|
|
313
|
+
service: conn.service,
|
|
253
314
|
});
|
|
254
|
-
}
|
|
315
|
+
}
|
|
255
316
|
}
|
|
256
|
-
} catch (
|
|
257
|
-
|
|
317
|
+
} catch (err) {
|
|
318
|
+
log.warn('Failed to get accounts', { type: conn.type, error: err.message });
|
|
258
319
|
}
|
|
259
320
|
}
|
|
260
321
|
|
|
@@ -263,31 +324,27 @@ const connections = {
|
|
|
263
324
|
|
|
264
325
|
/**
|
|
265
326
|
* Gets the service for a specific account
|
|
266
|
-
* @param {string|number} accountId - Account ID
|
|
267
|
-
* @returns {Object|null}
|
|
327
|
+
* @param {string|number} accountId - Account ID
|
|
328
|
+
* @returns {Object|null}
|
|
268
329
|
*/
|
|
269
330
|
getServiceForAccount(accountId) {
|
|
270
331
|
for (const conn of this.services) {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
}
|
|
281
|
-
} catch (e) {
|
|
282
|
-
// Skip
|
|
283
|
-
}
|
|
332
|
+
if (!conn.service?.accounts) continue;
|
|
333
|
+
|
|
334
|
+
const found = conn.service.accounts.find(acc =>
|
|
335
|
+
acc.accountId == accountId ||
|
|
336
|
+
acc.rithmicAccountId == accountId ||
|
|
337
|
+
acc.accountName == accountId
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
if (found) return conn.service;
|
|
284
341
|
}
|
|
285
342
|
return null;
|
|
286
343
|
},
|
|
287
344
|
|
|
288
345
|
/**
|
|
289
346
|
* Checks if any connection is active
|
|
290
|
-
* @returns {boolean}
|
|
347
|
+
* @returns {boolean}
|
|
291
348
|
*/
|
|
292
349
|
isConnected() {
|
|
293
350
|
return this.services.length > 0;
|
|
@@ -297,27 +354,40 @@ const connections = {
|
|
|
297
354
|
* Disconnects all connections and clears sessions
|
|
298
355
|
*/
|
|
299
356
|
disconnectAll() {
|
|
300
|
-
this.services
|
|
301
|
-
|
|
302
|
-
conn.service
|
|
357
|
+
for (const conn of this.services) {
|
|
358
|
+
try {
|
|
359
|
+
if (conn.service?.logout) {
|
|
360
|
+
conn.service.logout();
|
|
361
|
+
}
|
|
362
|
+
if (conn.service?.disconnect) {
|
|
363
|
+
conn.service.disconnect();
|
|
364
|
+
}
|
|
365
|
+
// Clear credentials
|
|
366
|
+
if (conn.service?.credentials) {
|
|
367
|
+
conn.service.credentials = null;
|
|
368
|
+
}
|
|
369
|
+
} catch (err) {
|
|
370
|
+
log.warn('Disconnect failed', { type: conn.type, error: err.message });
|
|
303
371
|
}
|
|
304
|
-
}
|
|
372
|
+
}
|
|
373
|
+
|
|
305
374
|
this.services = [];
|
|
306
375
|
storage.clear();
|
|
376
|
+
log.info('All connections disconnected');
|
|
307
377
|
},
|
|
308
378
|
|
|
309
379
|
/**
|
|
310
380
|
* Gets masked connection info for logging
|
|
311
|
-
* @returns {Array}
|
|
381
|
+
* @returns {Array}
|
|
312
382
|
*/
|
|
313
383
|
getInfo() {
|
|
314
384
|
return this.services.map(conn => ({
|
|
315
385
|
type: conn.type,
|
|
316
386
|
propfirm: conn.propfirm,
|
|
317
387
|
token: maskSensitive(conn.token),
|
|
318
|
-
connectedAt: conn.connectedAt
|
|
388
|
+
connectedAt: conn.connectedAt,
|
|
319
389
|
}));
|
|
320
|
-
}
|
|
390
|
+
},
|
|
321
391
|
};
|
|
322
392
|
|
|
323
393
|
module.exports = { storage, connections };
|