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.
@@ -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
- };