hedgequantx 2.9.182 → 2.9.183
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/package.json +1 -1
- package/src/app.js +8 -21
- package/src/menus/connect.js +10 -22
- package/src/services/session.js +75 -164
- package/src/services/rithmic-broker/client.js +0 -321
- package/src/services/rithmic-broker/daemon-reconnect.js +0 -353
- package/src/services/rithmic-broker/daemon.js +0 -469
- package/src/services/rithmic-broker/index.js +0 -46
- package/src/services/rithmic-broker/manager.js +0 -264
|
@@ -1,264 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* RithmicBroker Manager
|
|
3
|
-
*
|
|
4
|
-
* Start/stop/status functions for the RithmicBroker daemon.
|
|
5
|
-
* Similar pattern to cliproxy/manager.js
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
'use strict';
|
|
9
|
-
|
|
10
|
-
const { spawn } = require('child_process');
|
|
11
|
-
const path = require('path');
|
|
12
|
-
const fs = require('fs');
|
|
13
|
-
const http = require('http');
|
|
14
|
-
const WebSocket = require('ws');
|
|
15
|
-
|
|
16
|
-
const { BROKER_PORT, BROKER_DIR, PID_FILE, LOG_FILE } = require('./daemon');
|
|
17
|
-
|
|
18
|
-
// Path to daemon script
|
|
19
|
-
const DAEMON_SCRIPT = path.join(__dirname, 'daemon.js');
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Check if daemon is running
|
|
23
|
-
* @returns {Promise<{running: boolean, pid: number|null}>}
|
|
24
|
-
*/
|
|
25
|
-
const isRunning = async () => {
|
|
26
|
-
// Check PID file first
|
|
27
|
-
if (fs.existsSync(PID_FILE)) {
|
|
28
|
-
try {
|
|
29
|
-
const pid = parseInt(fs.readFileSync(PID_FILE, 'utf8').trim(), 10);
|
|
30
|
-
process.kill(pid, 0); // Test if process exists
|
|
31
|
-
return { running: true, pid };
|
|
32
|
-
} catch (e) {
|
|
33
|
-
// Process doesn't exist, clean up stale PID file
|
|
34
|
-
try { fs.unlinkSync(PID_FILE); } catch (e2) { /* ignore */ }
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// Try connecting to WebSocket
|
|
39
|
-
return new Promise((resolve) => {
|
|
40
|
-
const ws = new WebSocket(`ws://127.0.0.1:${BROKER_PORT}`);
|
|
41
|
-
const timeout = setTimeout(() => {
|
|
42
|
-
ws.terminate();
|
|
43
|
-
resolve({ running: false, pid: null });
|
|
44
|
-
}, 2000);
|
|
45
|
-
|
|
46
|
-
ws.on('open', () => {
|
|
47
|
-
clearTimeout(timeout);
|
|
48
|
-
ws.close();
|
|
49
|
-
resolve({ running: true, pid: null });
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
ws.on('error', () => {
|
|
53
|
-
clearTimeout(timeout);
|
|
54
|
-
resolve({ running: false, pid: null });
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Start the daemon
|
|
61
|
-
* @returns {Promise<{success: boolean, error: string|null, pid: number|null}>}
|
|
62
|
-
*/
|
|
63
|
-
const start = async () => {
|
|
64
|
-
const status = await isRunning();
|
|
65
|
-
if (status.running) {
|
|
66
|
-
return { success: true, error: null, pid: status.pid, alreadyRunning: true };
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Ensure directory exists
|
|
70
|
-
if (!fs.existsSync(BROKER_DIR)) {
|
|
71
|
-
fs.mkdirSync(BROKER_DIR, { recursive: true });
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
try {
|
|
75
|
-
// Open log file for daemon output
|
|
76
|
-
const logFd = fs.openSync(LOG_FILE, 'a');
|
|
77
|
-
|
|
78
|
-
// Spawn detached daemon process
|
|
79
|
-
const child = spawn(process.execPath, [DAEMON_SCRIPT], {
|
|
80
|
-
detached: true,
|
|
81
|
-
stdio: ['ignore', logFd, logFd],
|
|
82
|
-
cwd: BROKER_DIR,
|
|
83
|
-
env: { ...process.env, HQX_BROKER_DAEMON: '1' },
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
child.unref();
|
|
87
|
-
fs.closeSync(logFd);
|
|
88
|
-
|
|
89
|
-
// Wait for daemon to start (poll every 500ms, max 5s)
|
|
90
|
-
let attempts = 0;
|
|
91
|
-
const maxAttempts = 10;
|
|
92
|
-
let runStatus = { running: false, pid: null };
|
|
93
|
-
|
|
94
|
-
while (attempts < maxAttempts) {
|
|
95
|
-
await new Promise(r => setTimeout(r, 500));
|
|
96
|
-
runStatus = await isRunning();
|
|
97
|
-
if (runStatus.running) {
|
|
98
|
-
return { success: true, error: null, pid: runStatus.pid || child.pid };
|
|
99
|
-
}
|
|
100
|
-
attempts++;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Read log for error details
|
|
104
|
-
let errorDetail = 'Daemon failed to start';
|
|
105
|
-
if (fs.existsSync(LOG_FILE)) {
|
|
106
|
-
const logContent = fs.readFileSync(LOG_FILE, 'utf8');
|
|
107
|
-
const lines = logContent.split('\n').filter(l => l.trim());
|
|
108
|
-
const lastLines = lines.slice(-5).join(' | ');
|
|
109
|
-
if (lastLines) errorDetail += ` - Log: ${lastLines}`;
|
|
110
|
-
}
|
|
111
|
-
return { success: false, error: errorDetail, pid: null };
|
|
112
|
-
} catch (error) {
|
|
113
|
-
return { success: false, error: error.message, pid: null };
|
|
114
|
-
}
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Stop the daemon
|
|
119
|
-
* @returns {Promise<{success: boolean, error: string|null}>}
|
|
120
|
-
*/
|
|
121
|
-
const stop = async () => {
|
|
122
|
-
const status = await isRunning();
|
|
123
|
-
if (!status.running) {
|
|
124
|
-
return { success: true, error: null };
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
try {
|
|
128
|
-
// Try graceful shutdown via WebSocket
|
|
129
|
-
const ws = new WebSocket(`ws://127.0.0.1:${BROKER_PORT}`);
|
|
130
|
-
|
|
131
|
-
await new Promise((resolve, reject) => {
|
|
132
|
-
const timeout = setTimeout(() => {
|
|
133
|
-
ws.terminate();
|
|
134
|
-
reject(new Error('Shutdown timeout'));
|
|
135
|
-
}, 5000);
|
|
136
|
-
|
|
137
|
-
ws.on('open', () => {
|
|
138
|
-
ws.send(JSON.stringify({ type: 'logout', payload: {}, requestId: 'shutdown' }));
|
|
139
|
-
setTimeout(() => {
|
|
140
|
-
clearTimeout(timeout);
|
|
141
|
-
ws.close();
|
|
142
|
-
resolve();
|
|
143
|
-
}, 1000);
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
ws.on('error', () => {
|
|
147
|
-
clearTimeout(timeout);
|
|
148
|
-
reject(new Error('Connection failed'));
|
|
149
|
-
});
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
// Wait for process to exit
|
|
153
|
-
await new Promise(r => setTimeout(r, 1000));
|
|
154
|
-
|
|
155
|
-
// Verify stopped
|
|
156
|
-
const newStatus = await isRunning();
|
|
157
|
-
if (!newStatus.running) {
|
|
158
|
-
return { success: true, error: null };
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// Force kill if still running
|
|
162
|
-
if (status.pid) {
|
|
163
|
-
try {
|
|
164
|
-
process.kill(status.pid, 'SIGKILL');
|
|
165
|
-
} catch (e) { /* ignore */ }
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Clean up PID file
|
|
169
|
-
if (fs.existsSync(PID_FILE)) {
|
|
170
|
-
try { fs.unlinkSync(PID_FILE); } catch (e) { /* ignore */ }
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
return { success: true, error: null };
|
|
174
|
-
} catch (error) {
|
|
175
|
-
// Force kill via PID
|
|
176
|
-
if (status.pid) {
|
|
177
|
-
try {
|
|
178
|
-
process.kill(status.pid, 'SIGKILL');
|
|
179
|
-
if (fs.existsSync(PID_FILE)) fs.unlinkSync(PID_FILE);
|
|
180
|
-
return { success: true, error: null };
|
|
181
|
-
} catch (e) { /* ignore */ }
|
|
182
|
-
}
|
|
183
|
-
return { success: false, error: error.message };
|
|
184
|
-
}
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Get daemon status
|
|
189
|
-
* @returns {Promise<Object>}
|
|
190
|
-
*/
|
|
191
|
-
const getStatus = async () => {
|
|
192
|
-
const status = await isRunning();
|
|
193
|
-
|
|
194
|
-
if (!status.running) {
|
|
195
|
-
return { running: false, pid: null, connections: [], uptime: 0 };
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// Get detailed status from daemon
|
|
199
|
-
return new Promise((resolve) => {
|
|
200
|
-
const ws = new WebSocket(`ws://127.0.0.1:${BROKER_PORT}`);
|
|
201
|
-
const timeout = setTimeout(() => {
|
|
202
|
-
ws.terminate();
|
|
203
|
-
resolve({ running: true, pid: status.pid, connections: [], uptime: 0 });
|
|
204
|
-
}, 3000);
|
|
205
|
-
|
|
206
|
-
ws.on('open', () => {
|
|
207
|
-
ws.send(JSON.stringify({ type: 'status', requestId: 'status' }));
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
ws.on('message', (data) => {
|
|
211
|
-
clearTimeout(timeout);
|
|
212
|
-
try {
|
|
213
|
-
const msg = JSON.parse(data.toString());
|
|
214
|
-
if (msg.type === 'status') {
|
|
215
|
-
ws.close();
|
|
216
|
-
resolve(msg.payload);
|
|
217
|
-
}
|
|
218
|
-
} catch (e) {
|
|
219
|
-
ws.close();
|
|
220
|
-
resolve({ running: true, pid: status.pid, connections: [], uptime: 0 });
|
|
221
|
-
}
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
ws.on('error', () => {
|
|
225
|
-
clearTimeout(timeout);
|
|
226
|
-
resolve({ running: true, pid: status.pid, connections: [], uptime: 0 });
|
|
227
|
-
});
|
|
228
|
-
});
|
|
229
|
-
};
|
|
230
|
-
|
|
231
|
-
/**
|
|
232
|
-
* Ensure daemon is running (start if not)
|
|
233
|
-
* @returns {Promise<{success: boolean, error: string|null}>}
|
|
234
|
-
*/
|
|
235
|
-
const ensureRunning = async () => {
|
|
236
|
-
const status = await isRunning();
|
|
237
|
-
if (status.running) {
|
|
238
|
-
return { success: true, error: null };
|
|
239
|
-
}
|
|
240
|
-
return start();
|
|
241
|
-
};
|
|
242
|
-
|
|
243
|
-
/**
|
|
244
|
-
* Restart the daemon
|
|
245
|
-
* @returns {Promise<{success: boolean, error: string|null}>}
|
|
246
|
-
*/
|
|
247
|
-
const restart = async () => {
|
|
248
|
-
await stop();
|
|
249
|
-
await new Promise(r => setTimeout(r, 1000));
|
|
250
|
-
return start();
|
|
251
|
-
};
|
|
252
|
-
|
|
253
|
-
module.exports = {
|
|
254
|
-
isRunning,
|
|
255
|
-
start,
|
|
256
|
-
stop,
|
|
257
|
-
getStatus,
|
|
258
|
-
ensureRunning,
|
|
259
|
-
restart,
|
|
260
|
-
BROKER_PORT,
|
|
261
|
-
BROKER_DIR,
|
|
262
|
-
PID_FILE,
|
|
263
|
-
LOG_FILE,
|
|
264
|
-
};
|