@jango-blockchained/hoox-shared 1.0.9 → 1.1.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 +17 -0
- package/dist/analytics.d.ts +23 -1
- package/dist/analytics.d.ts.map +1 -1
- package/dist/analytics.js +14 -3
- package/dist/api-client.d.ts +12 -2
- package/dist/api-client.d.ts.map +1 -1
- package/dist/api-client.js +74 -7
- package/dist/colors.d.ts +63 -24
- package/dist/colors.d.ts.map +1 -1
- package/dist/colors.js +60 -19
- package/dist/config.d.ts +34 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +61 -6
- package/dist/cron-handler.d.ts +2 -2
- package/dist/cron-handler.d.ts.map +1 -1
- package/dist/d1/index.js +1 -0
- package/dist/d1/repository.js +297 -0
- package/dist/d1/schemas.js +81 -0
- package/dist/exchanges/base-exchange-client.js +120 -0
- package/dist/exchanges/types.js +1 -0
- package/dist/index.d.ts +21 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +580 -60
- package/dist/middleware/auth.d.ts +42 -6
- package/dist/middleware/auth.d.ts.map +1 -1
- package/dist/middleware/auth.js +151 -0
- package/dist/middleware/cors.d.ts +28 -2
- package/dist/middleware/cors.d.ts.map +1 -1
- package/dist/middleware/cors.js +77 -0
- package/dist/middleware/index.d.ts +2 -2
- package/dist/middleware/index.d.ts.map +1 -1
- package/dist/middleware/index.js +192 -107
- package/dist/middleware/logger.js +202 -0
- package/dist/middleware/rate-limit.d.ts.map +1 -1
- package/dist/middleware/rate-limit.js +181 -0
- package/dist/middleware/security-headers.js +58 -0
- package/dist/middleware/validate.js +56 -0
- package/dist/operator-transport.d.ts +61 -0
- package/dist/operator-transport.d.ts.map +1 -0
- package/dist/operator-transport.js +87 -0
- package/dist/path-utils.d.ts +59 -1
- package/dist/path-utils.d.ts.map +1 -1
- package/dist/path-utils.js +89 -2
- package/dist/queue-handler.d.ts +3 -3
- package/dist/queue-handler.d.ts.map +1 -1
- package/dist/router.d.ts +2 -2
- package/dist/router.d.ts.map +1 -1
- package/dist/schemas/index.js +7 -4
- package/dist/schemas/registry.js +387 -0
- package/dist/schemas/types.js +1 -0
- package/dist/schemas/validators.d.ts.map +1 -1
- package/dist/schemas/validators.js +290 -0
- package/dist/service-bindings.d.ts +65 -0
- package/dist/service-bindings.d.ts.map +1 -1
- package/dist/service-bindings.js +216 -1
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +97 -2
- package/dist/sse.d.ts +12 -3
- package/dist/sse.d.ts.map +1 -1
- package/dist/sse.js +70 -7
- package/dist/stores/config-store.d.ts.map +1 -1
- package/dist/stores/config-store.js +129 -6
- package/dist/stores/service-store.d.ts.map +1 -1
- package/dist/stores/service-store.js +96 -25
- package/dist/types.d.ts +66 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +11 -0
- package/dist/wizard/engine.js +501 -0
- package/dist/wizard/index.js +2 -1
- package/dist/wizard/persistence.js +31 -0
- package/dist/wizard/presets.js +199 -0
- package/dist/wizard/provisioner.js +1 -0
- package/dist/wizard/types.js +1 -0
- package/package.json +38 -12
package/dist/index.js
CHANGED
|
@@ -15,6 +15,69 @@ var __export = (target, all) => {
|
|
|
15
15
|
};
|
|
16
16
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
17
17
|
|
|
18
|
+
// src/operator-transport.ts
|
|
19
|
+
function stripTrailingSlashes(url) {
|
|
20
|
+
return url.replace(/\/+$/, "");
|
|
21
|
+
}
|
|
22
|
+
function parseTransport(raw) {
|
|
23
|
+
if (!raw)
|
|
24
|
+
return null;
|
|
25
|
+
const v = raw.trim().toLowerCase();
|
|
26
|
+
if (VALID_TRANSPORTS.has(v)) {
|
|
27
|
+
return v;
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
function resolveOperatorTransportProfile(env = process.env, options = {}) {
|
|
32
|
+
const accessClientId = env.CF_ACCESS_CLIENT_ID?.trim() ?? "";
|
|
33
|
+
const accessClientSecret = env.CF_ACCESS_CLIENT_SECRET?.trim() ?? "";
|
|
34
|
+
const hasAccess = Boolean(accessClientId && accessClientSecret);
|
|
35
|
+
const explicit = parseTransport(env.HOOX_TRANSPORT) ?? parseTransport(options.configTransport);
|
|
36
|
+
const transport = explicit ?? (hasAccess ? "access" : "public");
|
|
37
|
+
const apiFromEnv = env.HOOX_API_URL?.trim();
|
|
38
|
+
const apiFromConfig = options.configApiUrl?.trim();
|
|
39
|
+
const tokenFromEnv = env.HOOX_API_TOKEN?.trim();
|
|
40
|
+
const tokenFromConfig = options.configApiToken?.trim();
|
|
41
|
+
return {
|
|
42
|
+
transport,
|
|
43
|
+
apiBase: stripTrailingSlashes(apiFromEnv || apiFromConfig || DEFAULT_API_BASE),
|
|
44
|
+
bearerToken: tokenFromEnv || tokenFromConfig || "",
|
|
45
|
+
accessClientId,
|
|
46
|
+
accessClientSecret
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function buildOperatorAuthHeaders(profile) {
|
|
50
|
+
const headers = {};
|
|
51
|
+
if (profile.bearerToken) {
|
|
52
|
+
headers.Authorization = `Bearer ${profile.bearerToken}`;
|
|
53
|
+
}
|
|
54
|
+
const useAccessHeaders = profile.transport === "access" || Boolean(profile.accessClientId) && Boolean(profile.accessClientSecret);
|
|
55
|
+
if (useAccessHeaders && profile.accessClientId && profile.accessClientSecret) {
|
|
56
|
+
headers["CF-Access-Client-Id"] = profile.accessClientId;
|
|
57
|
+
headers["CF-Access-Client-Secret"] = profile.accessClientSecret;
|
|
58
|
+
}
|
|
59
|
+
return headers;
|
|
60
|
+
}
|
|
61
|
+
function hasOperatorClientCredentials(profile) {
|
|
62
|
+
if (profile.bearerToken)
|
|
63
|
+
return true;
|
|
64
|
+
return Boolean(profile.accessClientId && profile.accessClientSecret);
|
|
65
|
+
}
|
|
66
|
+
function operatorUrl(profile, path) {
|
|
67
|
+
const base = profile.apiBase.replace(/\/+$/, "");
|
|
68
|
+
const p = path.startsWith("/") ? path : `/${path}`;
|
|
69
|
+
return `${base}${p}`;
|
|
70
|
+
}
|
|
71
|
+
var DEFAULT_API_BASE = "http://localhost:8787", VALID_TRANSPORTS;
|
|
72
|
+
var init_operator_transport = __esm(() => {
|
|
73
|
+
VALID_TRANSPORTS = new Set([
|
|
74
|
+
"public",
|
|
75
|
+
"access",
|
|
76
|
+
"mtls",
|
|
77
|
+
"tunnel"
|
|
78
|
+
]);
|
|
79
|
+
});
|
|
80
|
+
|
|
18
81
|
// src/api-client.ts
|
|
19
82
|
var exports_api_client = {};
|
|
20
83
|
__export(exports_api_client, {
|
|
@@ -40,20 +103,25 @@ function isNetworkError(error) {
|
|
|
40
103
|
function sleep(ms) {
|
|
41
104
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
42
105
|
}
|
|
106
|
+
function activeProfile(override) {
|
|
107
|
+
return override ?? resolveOperatorTransportProfile();
|
|
108
|
+
}
|
|
43
109
|
async function hooxFetch(path, options) {
|
|
110
|
+
const profile = options?.transport ?? (options?.transportEnv ? resolveOperatorTransportProfile(options.transportEnv) : activeProfile());
|
|
111
|
+
const { transport: _t, transportEnv: _e, ...fetchInit } = options ?? {};
|
|
44
112
|
let lastError;
|
|
45
113
|
for (let attempt = 0;attempt <= MAX_RETRIES; attempt++) {
|
|
46
114
|
const controller = new AbortController;
|
|
47
115
|
const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
48
116
|
try {
|
|
49
|
-
const url =
|
|
117
|
+
const url = operatorUrl(profile, path);
|
|
50
118
|
const headers = {
|
|
51
119
|
"Content-Type": "application/json",
|
|
52
|
-
...
|
|
53
|
-
...
|
|
120
|
+
...buildOperatorAuthHeaders(profile),
|
|
121
|
+
...fetchInit.headers
|
|
54
122
|
};
|
|
55
123
|
const response = await fetch(url, {
|
|
56
|
-
...
|
|
124
|
+
...fetchInit,
|
|
57
125
|
headers,
|
|
58
126
|
signal: controller.signal
|
|
59
127
|
});
|
|
@@ -101,10 +169,9 @@ async function hooxFetch(path, options) {
|
|
|
101
169
|
retryable: false
|
|
102
170
|
});
|
|
103
171
|
}
|
|
104
|
-
var
|
|
172
|
+
var FETCH_TIMEOUT_MS = 5000, MAX_RETRIES = 3, BASE_RETRY_DELAY_MS = 1000, MAX_RETRY_DELAY_MS = 16000, WorkerAPIError;
|
|
105
173
|
var init_api_client = __esm(() => {
|
|
106
|
-
|
|
107
|
-
API_TOKEN = process.env.HOOX_API_TOKEN || "";
|
|
174
|
+
init_operator_transport();
|
|
108
175
|
WorkerAPIError = class WorkerAPIError extends Error {
|
|
109
176
|
status;
|
|
110
177
|
retryable;
|
|
@@ -122,17 +189,18 @@ var exports_sse = {};
|
|
|
122
189
|
__export(exports_sse, {
|
|
123
190
|
subscribeSSE: () => subscribeSSE
|
|
124
191
|
});
|
|
125
|
-
function subscribeSSE(path, callback, onStatus) {
|
|
192
|
+
function subscribeSSE(path, callback, onStatus, options) {
|
|
126
193
|
let aborted = false;
|
|
194
|
+
const profile = options?.transport ?? (options?.transportEnv ? resolveOperatorTransportProfile(options.transportEnv) : resolveOperatorTransportProfile());
|
|
127
195
|
const run = async () => {
|
|
128
196
|
let attempt = 0;
|
|
129
197
|
while (!aborted && attempt < MAX_RECONNECT_ATTEMPTS) {
|
|
130
198
|
try {
|
|
131
|
-
const url =
|
|
199
|
+
const url = operatorUrl(profile, path);
|
|
132
200
|
const response = await fetch(url, {
|
|
133
201
|
headers: {
|
|
134
202
|
Accept: "text/event-stream",
|
|
135
|
-
...
|
|
203
|
+
...buildOperatorAuthHeaders(profile)
|
|
136
204
|
}
|
|
137
205
|
});
|
|
138
206
|
if (!response.ok || !response.body) {
|
|
@@ -161,7 +229,7 @@ function subscribeSSE(path, callback, onStatus) {
|
|
|
161
229
|
}
|
|
162
230
|
}
|
|
163
231
|
}
|
|
164
|
-
} catch
|
|
232
|
+
} catch {
|
|
165
233
|
if (aborted)
|
|
166
234
|
break;
|
|
167
235
|
onStatus?.("reconnecting");
|
|
@@ -181,21 +249,31 @@ function subscribeSSE(path, callback, onStatus) {
|
|
|
181
249
|
}
|
|
182
250
|
};
|
|
183
251
|
}
|
|
184
|
-
var
|
|
252
|
+
var RECONNECT_BASE_MS = 1000, RECONNECT_MAX_MS = 16000, MAX_RECONNECT_ATTEMPTS = 10;
|
|
185
253
|
var init_sse = __esm(() => {
|
|
186
|
-
|
|
187
|
-
API_TOKEN2 = process.env.HOOX_API_TOKEN || "";
|
|
254
|
+
init_operator_transport();
|
|
188
255
|
});
|
|
189
256
|
|
|
190
257
|
// src/analytics.ts
|
|
191
|
-
async function trackAnalytics(env, endpoint, body) {
|
|
258
|
+
async function trackAnalytics(env, endpoint, body, options) {
|
|
192
259
|
if (!env.ANALYTICS_SERVICE)
|
|
193
260
|
return;
|
|
194
261
|
try {
|
|
262
|
+
const payload = {
|
|
263
|
+
...body,
|
|
264
|
+
...options?.indexes ? { indexes: [...options.indexes] } : {}
|
|
265
|
+
};
|
|
266
|
+
const headers = {
|
|
267
|
+
"Content-Type": "application/json"
|
|
268
|
+
};
|
|
269
|
+
const internalKey = env.INTERNAL_KEY_BINDING;
|
|
270
|
+
if (internalKey) {
|
|
271
|
+
headers["X-Internal-Auth-Key"] = internalKey;
|
|
272
|
+
}
|
|
195
273
|
await env.ANALYTICS_SERVICE.fetch(new Request(`http://localhost${endpoint}`, {
|
|
196
274
|
method: "POST",
|
|
197
|
-
headers
|
|
198
|
-
body: JSON.stringify(
|
|
275
|
+
headers,
|
|
276
|
+
body: JSON.stringify(payload)
|
|
199
277
|
}));
|
|
200
278
|
} catch (e) {
|
|
201
279
|
console.error("Analytics tracking failed:", e);
|
|
@@ -204,35 +282,81 @@ async function trackAnalytics(env, endpoint, body) {
|
|
|
204
282
|
|
|
205
283
|
// src/colors.ts
|
|
206
284
|
var Colors = {
|
|
207
|
-
background: "#
|
|
208
|
-
foreground: "#
|
|
209
|
-
card: "#
|
|
210
|
-
border: "#
|
|
211
|
-
muted: "#
|
|
212
|
-
"muted-foreground": "#
|
|
213
|
-
dim: "#
|
|
214
|
-
accent: "#
|
|
215
|
-
"accent-dim": "#
|
|
216
|
-
success: "#
|
|
217
|
-
warning: "#
|
|
218
|
-
error: "#
|
|
219
|
-
info: "#
|
|
220
|
-
text: "#
|
|
221
|
-
"text-muted": "#
|
|
222
|
-
panel: "#
|
|
223
|
-
divider: "#
|
|
224
|
-
highlight: "#
|
|
285
|
+
background: "#050508",
|
|
286
|
+
foreground: "#E8E8F0",
|
|
287
|
+
card: "#0A0A0F",
|
|
288
|
+
border: "#232330",
|
|
289
|
+
muted: "#8B8B9E",
|
|
290
|
+
"muted-foreground": "#6E6E84",
|
|
291
|
+
dim: "#3C3C50",
|
|
292
|
+
accent: "#818CF8",
|
|
293
|
+
"accent-dim": "#6366F1",
|
|
294
|
+
success: "#34D399",
|
|
295
|
+
warning: "#FBBF24",
|
|
296
|
+
error: "#FB7185",
|
|
297
|
+
info: "#38BDF8",
|
|
298
|
+
text: "#E8E8F0",
|
|
299
|
+
"text-muted": "#8B8B9E",
|
|
300
|
+
panel: "#0A0A0F",
|
|
301
|
+
divider: "#232330",
|
|
302
|
+
highlight: "#22D3EE",
|
|
303
|
+
backdrop: "#000000"
|
|
304
|
+
};
|
|
305
|
+
var CoolBracketPalette = [
|
|
306
|
+
"#22D3EE",
|
|
307
|
+
"#38BDF8",
|
|
308
|
+
"#60A5FA",
|
|
309
|
+
"#818CF8",
|
|
310
|
+
"#A78BFA",
|
|
311
|
+
"#C084FC",
|
|
312
|
+
"#E879F9",
|
|
313
|
+
"#A78BFA",
|
|
314
|
+
"#818CF8",
|
|
315
|
+
"#38BDF8"
|
|
316
|
+
];
|
|
317
|
+
var ConnectionStatusColor = {
|
|
318
|
+
connected: Colors.success,
|
|
319
|
+
polling: Colors.highlight,
|
|
320
|
+
reconnecting: Colors.warning,
|
|
321
|
+
offline: Colors.error
|
|
322
|
+
};
|
|
323
|
+
var WorkerStatusColor = {
|
|
324
|
+
operational: Colors.success,
|
|
325
|
+
degraded: Colors.warning,
|
|
326
|
+
down: Colors.error
|
|
327
|
+
};
|
|
328
|
+
var LogLevelColor = {
|
|
329
|
+
error: Colors.error,
|
|
330
|
+
warn: Colors.warning,
|
|
331
|
+
info: Colors.foreground,
|
|
332
|
+
debug: Colors.muted
|
|
333
|
+
};
|
|
334
|
+
var AlertSeverityColor = {
|
|
335
|
+
info: Colors.info,
|
|
336
|
+
warning: Colors.warning,
|
|
337
|
+
error: Colors.error,
|
|
338
|
+
critical: Colors.error
|
|
225
339
|
};
|
|
226
340
|
|
|
227
341
|
// src/config.ts
|
|
228
|
-
import {
|
|
342
|
+
import {
|
|
343
|
+
readFileSync,
|
|
344
|
+
writeFileSync,
|
|
345
|
+
existsSync,
|
|
346
|
+
mkdirSync,
|
|
347
|
+
chmodSync,
|
|
348
|
+
statSync
|
|
349
|
+
} from "fs";
|
|
229
350
|
import { join } from "path";
|
|
230
351
|
import { homedir } from "os";
|
|
231
352
|
var HOX_DIR = join(homedir(), ".hoox");
|
|
232
353
|
var CONFIG_PATH = join(HOX_DIR, "config.json");
|
|
354
|
+
var HOOX_DIR_MODE = 448;
|
|
355
|
+
var HOOX_CONFIG_FILE_MODE = 384;
|
|
233
356
|
var DEFAULT_CONFIG = {
|
|
234
357
|
apiUrl: "http://localhost:8787",
|
|
235
358
|
apiToken: "",
|
|
359
|
+
transport: "public",
|
|
236
360
|
refreshIntervalMs: 500,
|
|
237
361
|
theme: "dark",
|
|
238
362
|
activeExchanges: ["binance", "bybit", "mexc"],
|
|
@@ -265,12 +389,41 @@ function readConfigSync() {
|
|
|
265
389
|
async function readConfig() {
|
|
266
390
|
return readConfigSync();
|
|
267
391
|
}
|
|
392
|
+
function ensureHooxDirSecure(dir = HOX_DIR) {
|
|
393
|
+
if (!existsSync(dir)) {
|
|
394
|
+
mkdirSync(dir, { recursive: true, mode: HOOX_DIR_MODE });
|
|
395
|
+
}
|
|
396
|
+
try {
|
|
397
|
+
chmodSync(dir, HOOX_DIR_MODE);
|
|
398
|
+
} catch {}
|
|
399
|
+
}
|
|
400
|
+
function writeSecureFileSync(filePath, contents, mode = HOOX_CONFIG_FILE_MODE) {
|
|
401
|
+
writeFileSync(filePath, contents, { encoding: "utf-8", mode });
|
|
402
|
+
try {
|
|
403
|
+
chmodSync(filePath, mode);
|
|
404
|
+
} catch {}
|
|
405
|
+
}
|
|
406
|
+
function isUnixModeGroupOrWorldAccessible(mode) {
|
|
407
|
+
return (mode & 63) !== 0;
|
|
408
|
+
}
|
|
409
|
+
function isConfigWorldOrGroupReadable(filePath = CONFIG_PATH) {
|
|
410
|
+
try {
|
|
411
|
+
if (!existsSync(filePath))
|
|
412
|
+
return false;
|
|
413
|
+
return isUnixModeGroupOrWorldAccessible(statSync(filePath).mode & 511);
|
|
414
|
+
} catch {
|
|
415
|
+
return false;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
function formatConfigPermissionWarning(filePath = CONFIG_PATH) {
|
|
419
|
+
if (!isConfigWorldOrGroupReadable(filePath))
|
|
420
|
+
return null;
|
|
421
|
+
return `Warning: ${filePath} is group/world-accessible. ` + `Run: chmod 600 ${filePath} (and chmod 700 on the parent dir). ` + `This file may contain apiToken / operator secrets.`;
|
|
422
|
+
}
|
|
268
423
|
function writeConfigSync(config) {
|
|
269
424
|
try {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
}
|
|
273
|
-
writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), "utf-8");
|
|
425
|
+
ensureHooxDirSecure(HOX_DIR);
|
|
426
|
+
writeSecureFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), HOOX_CONFIG_FILE_MODE);
|
|
274
427
|
} catch (err) {
|
|
275
428
|
console.error("Failed to write config:", err);
|
|
276
429
|
}
|
|
@@ -278,6 +431,12 @@ function writeConfigSync(config) {
|
|
|
278
431
|
async function writeConfig(config) {
|
|
279
432
|
writeConfigSync(config);
|
|
280
433
|
}
|
|
434
|
+
var VALID_TRANSPORTS2 = new Set([
|
|
435
|
+
"public",
|
|
436
|
+
"access",
|
|
437
|
+
"mtls",
|
|
438
|
+
"tunnel"
|
|
439
|
+
]);
|
|
281
440
|
function validateConfig(config) {
|
|
282
441
|
const errors = [];
|
|
283
442
|
if (config.apiUrl && !config.apiUrl.startsWith("http")) {
|
|
@@ -289,6 +448,9 @@ function validateConfig(config) {
|
|
|
289
448
|
if (config.theme && !["dark", "light"].includes(config.theme)) {
|
|
290
449
|
errors.push('theme must be "dark" or "light"');
|
|
291
450
|
}
|
|
451
|
+
if (config.transport !== undefined && !VALID_TRANSPORTS2.has(config.transport)) {
|
|
452
|
+
errors.push('transport must be "public" | "access" | "mtls" | "tunnel"');
|
|
453
|
+
}
|
|
292
454
|
return errors;
|
|
293
455
|
}
|
|
294
456
|
|
|
@@ -598,6 +760,7 @@ class D1Repository {
|
|
|
598
760
|
}
|
|
599
761
|
}
|
|
600
762
|
}
|
|
763
|
+
|
|
601
764
|
// src/d1/schemas.ts
|
|
602
765
|
import { z } from "zod";
|
|
603
766
|
var TradeRecordSchema = z.object({
|
|
@@ -1048,6 +1211,16 @@ var WebhookPayloadSchema = z2.object({
|
|
|
1048
1211
|
orderType: z2.string().optional(),
|
|
1049
1212
|
leverage: z2.number().int().positive().optional()
|
|
1050
1213
|
}).strict();
|
|
1214
|
+
var TradeQueueMessageSchema = z2.object({
|
|
1215
|
+
requestId: z2.string().min(1).max(128),
|
|
1216
|
+
exchange: z2.string().min(1).max(64),
|
|
1217
|
+
action: TradeActionSchema,
|
|
1218
|
+
symbol: z2.string().min(1).max(32),
|
|
1219
|
+
quantity: z2.number().positive().finite(),
|
|
1220
|
+
price: z2.number().positive().finite().optional(),
|
|
1221
|
+
leverage: z2.number().int().positive().max(125).optional(),
|
|
1222
|
+
queuedAt: z2.string().min(1).max(64)
|
|
1223
|
+
}).strict();
|
|
1051
1224
|
var TradeSignalSchema = z2.object({
|
|
1052
1225
|
id: z2.number().int().positive().optional(),
|
|
1053
1226
|
source: z2.string().min(1),
|
|
@@ -1189,7 +1362,209 @@ function createQueueHandler(options) {
|
|
|
1189
1362
|
};
|
|
1190
1363
|
}
|
|
1191
1364
|
|
|
1365
|
+
// src/middleware/auth.ts
|
|
1366
|
+
function timingSafeEqual(a, b) {
|
|
1367
|
+
const encoder = new TextEncoder;
|
|
1368
|
+
const aBuf = encoder.encode(a);
|
|
1369
|
+
const bBuf = encoder.encode(b);
|
|
1370
|
+
const len = Math.max(aBuf.length, bBuf.length);
|
|
1371
|
+
let result = aBuf.length === bBuf.length ? 0 : 1;
|
|
1372
|
+
for (let i = 0;i < len; i++) {
|
|
1373
|
+
const av = i < aBuf.length ? aBuf[i] : 0;
|
|
1374
|
+
const bv = i < bBuf.length ? bBuf[i] : 0;
|
|
1375
|
+
result |= av ^ bv;
|
|
1376
|
+
}
|
|
1377
|
+
return result === 0;
|
|
1378
|
+
}
|
|
1379
|
+
function resolveOperatorApiKey(env) {
|
|
1380
|
+
const preferred = env.OPERATOR_API_KEY;
|
|
1381
|
+
if (typeof preferred === "string" && preferred.length > 0) {
|
|
1382
|
+
return preferred;
|
|
1383
|
+
}
|
|
1384
|
+
const legacy = env.INTERNAL_API_KEY;
|
|
1385
|
+
if (typeof legacy === "string" && legacy.length > 0) {
|
|
1386
|
+
return legacy;
|
|
1387
|
+
}
|
|
1388
|
+
return;
|
|
1389
|
+
}
|
|
1390
|
+
async function requireAuth(request, env) {
|
|
1391
|
+
return requireOperatorAuth(request, env);
|
|
1392
|
+
}
|
|
1393
|
+
async function requireOperatorAuth(request, env) {
|
|
1394
|
+
const apiKey = resolveOperatorApiKey(env);
|
|
1395
|
+
if (!apiKey) {
|
|
1396
|
+
return new Response(JSON.stringify({
|
|
1397
|
+
error: "Operator API key not configured",
|
|
1398
|
+
hint: "Set OPERATOR_API_KEY (preferred) or INTERNAL_API_KEY on the Worker"
|
|
1399
|
+
}), { status: 401, headers: { "Content-Type": "application/json" } });
|
|
1400
|
+
}
|
|
1401
|
+
const authHeader = request.headers.get("Authorization");
|
|
1402
|
+
const expectedHeader = `Bearer ${apiKey}`;
|
|
1403
|
+
if (!authHeader || !timingSafeEqual(authHeader, expectedHeader)) {
|
|
1404
|
+
return new Response(JSON.stringify({ error: "Unauthorized" }), {
|
|
1405
|
+
status: 401,
|
|
1406
|
+
headers: { "Content-Type": "application/json" }
|
|
1407
|
+
});
|
|
1408
|
+
}
|
|
1409
|
+
return null;
|
|
1410
|
+
}
|
|
1411
|
+
function createOperatorAuthMiddleware() {
|
|
1412
|
+
return async (request, env, _ctx) => {
|
|
1413
|
+
const denied = await requireOperatorAuth(request, env);
|
|
1414
|
+
if (denied)
|
|
1415
|
+
return denied;
|
|
1416
|
+
return;
|
|
1417
|
+
};
|
|
1418
|
+
}
|
|
1419
|
+
function normalizeKeyNames(keyName) {
|
|
1420
|
+
if (typeof keyName === "string") {
|
|
1421
|
+
return [keyName];
|
|
1422
|
+
}
|
|
1423
|
+
return [...keyName];
|
|
1424
|
+
}
|
|
1425
|
+
function collectInternalAuthKeys(env, keyNames) {
|
|
1426
|
+
const keys = [];
|
|
1427
|
+
for (const name of normalizeKeyNames(keyNames)) {
|
|
1428
|
+
const value = env[name];
|
|
1429
|
+
if (typeof value === "string" && value.length > 0) {
|
|
1430
|
+
keys.push(value);
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
return keys;
|
|
1434
|
+
}
|
|
1435
|
+
function matchesAnyInternalAuthKey(providedKey, expectedKeys) {
|
|
1436
|
+
for (const expected of expectedKeys) {
|
|
1437
|
+
if (timingSafeEqual(providedKey, expected)) {
|
|
1438
|
+
return true;
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
return false;
|
|
1442
|
+
}
|
|
1443
|
+
function internalAuthNotConfiguredResponse(keyNames) {
|
|
1444
|
+
const label = normalizeKeyNames(keyNames).join(" | ");
|
|
1445
|
+
return new Response(JSON.stringify({
|
|
1446
|
+
success: false,
|
|
1447
|
+
error: `Internal auth key(s) not configured: ${label}`
|
|
1448
|
+
}), { status: 401, headers: { "Content-Type": "application/json" } });
|
|
1449
|
+
}
|
|
1450
|
+
function requireInternalAuth(request, env, keyName = "INTERNAL_KEY_BINDING") {
|
|
1451
|
+
const expectedKeys = collectInternalAuthKeys(env, keyName);
|
|
1452
|
+
if (expectedKeys.length === 0) {
|
|
1453
|
+
return internalAuthNotConfiguredResponse(keyName);
|
|
1454
|
+
}
|
|
1455
|
+
const providedKey = request.headers.get("X-Internal-Auth-Key");
|
|
1456
|
+
if (!providedKey || !matchesAnyInternalAuthKey(providedKey, expectedKeys)) {
|
|
1457
|
+
return new Response(JSON.stringify({ success: false, error: "Unauthorized" }), { status: 401, headers: { "Content-Type": "application/json" } });
|
|
1458
|
+
}
|
|
1459
|
+
return null;
|
|
1460
|
+
}
|
|
1461
|
+
function createInternalAuthMiddleware(keyName = "INTERNAL_KEY_BINDING") {
|
|
1462
|
+
return async (request, env, _ctx) => {
|
|
1463
|
+
const expectedKeys = collectInternalAuthKeys(env, keyName);
|
|
1464
|
+
if (expectedKeys.length === 0) {
|
|
1465
|
+
return internalAuthNotConfiguredResponse(keyName);
|
|
1466
|
+
}
|
|
1467
|
+
const providedKey = request.headers.get("X-Internal-Auth-Key");
|
|
1468
|
+
if (!providedKey || !matchesAnyInternalAuthKey(providedKey, expectedKeys)) {
|
|
1469
|
+
return new Response(JSON.stringify({ success: false, error: "Unauthorized" }), { status: 401, headers: { "Content-Type": "application/json" } });
|
|
1470
|
+
}
|
|
1471
|
+
return;
|
|
1472
|
+
};
|
|
1473
|
+
}
|
|
1474
|
+
function checkInternalAuth(request, env, keyName = "INTERNAL_KEY_BINDING") {
|
|
1475
|
+
const expectedKeys = collectInternalAuthKeys(env, keyName);
|
|
1476
|
+
if (expectedKeys.length === 0) {
|
|
1477
|
+
return {
|
|
1478
|
+
authorized: false,
|
|
1479
|
+
error: `${normalizeKeyNames(keyName).join(" | ")} not configured`
|
|
1480
|
+
};
|
|
1481
|
+
}
|
|
1482
|
+
const providedKey = request.headers.get("X-Internal-Auth-Key");
|
|
1483
|
+
if (!providedKey || !matchesAnyInternalAuthKey(providedKey, expectedKeys)) {
|
|
1484
|
+
return { authorized: false, error: "Unauthorized" };
|
|
1485
|
+
}
|
|
1486
|
+
return { authorized: true };
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1192
1489
|
// src/service-bindings.ts
|
|
1490
|
+
var InternalAuthKeyFields = {
|
|
1491
|
+
DEFAULT: "INTERNAL_KEY_BINDING",
|
|
1492
|
+
D1_READ: "D1_READ_KEY_BINDING",
|
|
1493
|
+
D1_WRITE: "D1_WRITE_KEY_BINDING",
|
|
1494
|
+
TRADE_EXECUTE: "TRADE_EXECUTE_KEY_BINDING",
|
|
1495
|
+
TRADE_READ: "TRADE_READ_KEY_BINDING",
|
|
1496
|
+
TELEGRAM_ALERT: "TELEGRAM_INTERNAL_KEY_BINDING",
|
|
1497
|
+
WALLET_EXECUTE: "WALLET_EXECUTE_KEY_BINDING",
|
|
1498
|
+
AGENT: "AGENT_INTERNAL_KEY",
|
|
1499
|
+
D1_INTERNAL_ALIAS: "D1_INTERNAL_KEY",
|
|
1500
|
+
TRADE_INTERNAL_ALIAS: "TRADE_INTERNAL_KEY",
|
|
1501
|
+
TELEGRAM_INTERNAL_ALIAS: "TELEGRAM_INTERNAL_KEY"
|
|
1502
|
+
};
|
|
1503
|
+
var D1_READ_AUTH_KEY_FIELDS = [
|
|
1504
|
+
InternalAuthKeyFields.D1_READ,
|
|
1505
|
+
InternalAuthKeyFields.DEFAULT
|
|
1506
|
+
];
|
|
1507
|
+
var D1_WRITE_AUTH_KEY_FIELDS = [
|
|
1508
|
+
InternalAuthKeyFields.D1_WRITE,
|
|
1509
|
+
InternalAuthKeyFields.DEFAULT
|
|
1510
|
+
];
|
|
1511
|
+
var TRADE_EXECUTE_AUTH_KEY_FIELDS = [
|
|
1512
|
+
InternalAuthKeyFields.TRADE_EXECUTE,
|
|
1513
|
+
InternalAuthKeyFields.DEFAULT,
|
|
1514
|
+
InternalAuthKeyFields.AGENT
|
|
1515
|
+
];
|
|
1516
|
+
var TRADE_READ_AUTH_KEY_FIELDS = [
|
|
1517
|
+
InternalAuthKeyFields.TRADE_READ,
|
|
1518
|
+
InternalAuthKeyFields.DEFAULT
|
|
1519
|
+
];
|
|
1520
|
+
var TELEGRAM_ALERT_AUTH_KEY_FIELDS = [
|
|
1521
|
+
InternalAuthKeyFields.TELEGRAM_ALERT,
|
|
1522
|
+
InternalAuthKeyFields.DEFAULT
|
|
1523
|
+
];
|
|
1524
|
+
var WALLET_EXECUTE_AUTH_KEY_FIELDS = [
|
|
1525
|
+
InternalAuthKeyFields.WALLET_EXECUTE,
|
|
1526
|
+
InternalAuthKeyFields.DEFAULT
|
|
1527
|
+
];
|
|
1528
|
+
var DASHBOARD_D1_READ_AUTH_KEY_FIELDS = [
|
|
1529
|
+
InternalAuthKeyFields.D1_READ,
|
|
1530
|
+
InternalAuthKeyFields.D1_INTERNAL_ALIAS,
|
|
1531
|
+
InternalAuthKeyFields.DEFAULT
|
|
1532
|
+
];
|
|
1533
|
+
var DASHBOARD_TRADE_EXECUTE_AUTH_KEY_FIELDS = [
|
|
1534
|
+
InternalAuthKeyFields.TRADE_EXECUTE,
|
|
1535
|
+
InternalAuthKeyFields.TRADE_INTERNAL_ALIAS,
|
|
1536
|
+
InternalAuthKeyFields.DEFAULT
|
|
1537
|
+
];
|
|
1538
|
+
var DASHBOARD_TELEGRAM_ALERT_AUTH_KEY_FIELDS = [
|
|
1539
|
+
InternalAuthKeyFields.TELEGRAM_ALERT,
|
|
1540
|
+
InternalAuthKeyFields.TELEGRAM_INTERNAL_ALIAS,
|
|
1541
|
+
InternalAuthKeyFields.DEFAULT
|
|
1542
|
+
];
|
|
1543
|
+
function resolveInternalAuthKey(env, keyFields) {
|
|
1544
|
+
return collectInternalAuthKeys(env, keyFields)[0];
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
class ServiceAuthError extends Error {
|
|
1548
|
+
constructor(message) {
|
|
1549
|
+
super(message);
|
|
1550
|
+
this.name = "ServiceAuthError";
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
function authenticatedServiceFetch(binding, env, path, body, options) {
|
|
1554
|
+
const key = options?.internalKey ?? (options?.internalKeyFields ? resolveInternalAuthKey(env, options.internalKeyFields) : env.INTERNAL_KEY_BINDING);
|
|
1555
|
+
if (!key) {
|
|
1556
|
+
const label = options?.internalKeyFields ? Array.isArray(options.internalKeyFields) ? options.internalKeyFields.join(" | ") : options.internalKeyFields : "INTERNAL_KEY_BINDING";
|
|
1557
|
+
return Promise.reject(new ServiceAuthError(`${label} not configured`));
|
|
1558
|
+
}
|
|
1559
|
+
return serviceFetch(binding, path, body, {
|
|
1560
|
+
method: options?.method,
|
|
1561
|
+
timeout: options?.timeout,
|
|
1562
|
+
headers: {
|
|
1563
|
+
...options?.headers,
|
|
1564
|
+
"X-Internal-Auth-Key": key
|
|
1565
|
+
}
|
|
1566
|
+
});
|
|
1567
|
+
}
|
|
1193
1568
|
function serviceFetch(binding, path, body, options) {
|
|
1194
1569
|
const timeout = options?.timeout ?? 30000;
|
|
1195
1570
|
return binding.fetch(`http://internal${path}`, {
|
|
@@ -1302,13 +1677,18 @@ function createMockEnv(overrides) {
|
|
|
1302
1677
|
}
|
|
1303
1678
|
|
|
1304
1679
|
// src/path-utils.ts
|
|
1680
|
+
import { existsSync as existsSync2 } from "fs";
|
|
1305
1681
|
import { homedir as homedir2 } from "os";
|
|
1306
|
-
import { join as join2, resolve } from "path";
|
|
1682
|
+
import { dirname, join as join2, resolve } from "path";
|
|
1307
1683
|
function createHooxPath(path) {
|
|
1308
1684
|
return path;
|
|
1309
1685
|
}
|
|
1310
1686
|
function getHooxHome() {
|
|
1311
1687
|
try {
|
|
1688
|
+
const override = process.env.HOOX_HOME?.trim();
|
|
1689
|
+
if (override) {
|
|
1690
|
+
return createHooxPath(resolve(override));
|
|
1691
|
+
}
|
|
1312
1692
|
const home = homedir2();
|
|
1313
1693
|
if (!home || home.length === 0) {
|
|
1314
1694
|
return createHooxPath(resolve(process.cwd(), ".hoox"));
|
|
@@ -1318,6 +1698,84 @@ function getHooxHome() {
|
|
|
1318
1698
|
return createHooxPath(resolve(process.cwd(), ".hoox"));
|
|
1319
1699
|
}
|
|
1320
1700
|
}
|
|
1701
|
+
function isHooxSetupRoot(dir) {
|
|
1702
|
+
if (!dir)
|
|
1703
|
+
return false;
|
|
1704
|
+
try {
|
|
1705
|
+
const root = resolve(dir);
|
|
1706
|
+
return existsSync2(join2(root, "wrangler.jsonc")) && existsSync2(join2(root, "packages", "cli", "package.json"));
|
|
1707
|
+
} catch {
|
|
1708
|
+
return false;
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
function findHooxSetupRoot(startDir = process.cwd()) {
|
|
1712
|
+
let dir = resolve(startDir);
|
|
1713
|
+
for (;; ) {
|
|
1714
|
+
if (isHooxSetupRoot(dir))
|
|
1715
|
+
return dir;
|
|
1716
|
+
const parent = dirname(dir);
|
|
1717
|
+
if (parent === dir)
|
|
1718
|
+
return null;
|
|
1719
|
+
dir = parent;
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
function resolveHooxRuntimeRoot(options) {
|
|
1723
|
+
const env = options?.env ?? process.env;
|
|
1724
|
+
const cwd = resolve(options?.cwd ?? process.cwd());
|
|
1725
|
+
const globalRepo = getHooxRepoPath();
|
|
1726
|
+
const envRepo = env.HOOX_REPO?.trim();
|
|
1727
|
+
if (envRepo) {
|
|
1728
|
+
const resolved = resolve(envRepo);
|
|
1729
|
+
if (isHooxSetupRoot(resolved)) {
|
|
1730
|
+
return {
|
|
1731
|
+
root: resolved,
|
|
1732
|
+
source: "env",
|
|
1733
|
+
checked: {
|
|
1734
|
+
env: resolved,
|
|
1735
|
+
cwd: findHooxSetupRoot(cwd),
|
|
1736
|
+
global: globalRepo
|
|
1737
|
+
}
|
|
1738
|
+
};
|
|
1739
|
+
}
|
|
1740
|
+
return {
|
|
1741
|
+
root: null,
|
|
1742
|
+
source: "env",
|
|
1743
|
+
checked: {
|
|
1744
|
+
env: resolved,
|
|
1745
|
+
cwd: findHooxSetupRoot(cwd),
|
|
1746
|
+
global: globalRepo
|
|
1747
|
+
}
|
|
1748
|
+
};
|
|
1749
|
+
}
|
|
1750
|
+
const local = findHooxSetupRoot(cwd);
|
|
1751
|
+
if (local) {
|
|
1752
|
+
return {
|
|
1753
|
+
root: local,
|
|
1754
|
+
source: "cwd",
|
|
1755
|
+
checked: { cwd: local, global: globalRepo }
|
|
1756
|
+
};
|
|
1757
|
+
}
|
|
1758
|
+
if (isHooxSetupRoot(globalRepo)) {
|
|
1759
|
+
return {
|
|
1760
|
+
root: globalRepo,
|
|
1761
|
+
source: "global",
|
|
1762
|
+
checked: { cwd: null, global: globalRepo }
|
|
1763
|
+
};
|
|
1764
|
+
}
|
|
1765
|
+
return {
|
|
1766
|
+
root: null,
|
|
1767
|
+
source: "none",
|
|
1768
|
+
checked: { cwd: null, global: globalRepo }
|
|
1769
|
+
};
|
|
1770
|
+
}
|
|
1771
|
+
function getTuiEntryCandidates(runtimeRoot) {
|
|
1772
|
+
const root = resolve(runtimeRoot);
|
|
1773
|
+
return [
|
|
1774
|
+
join2(root, "packages", "tui", "src", "main.tsx"),
|
|
1775
|
+
join2(root, "packages", "tui", "dist", "main.js"),
|
|
1776
|
+
join2(root, "packages", "tui", "src", "main.ts")
|
|
1777
|
+
];
|
|
1778
|
+
}
|
|
1321
1779
|
function resolveHooxPath(relativePath) {
|
|
1322
1780
|
if (!relativePath || typeof relativePath !== "string") {
|
|
1323
1781
|
throw new Error("relativePath must be a non-empty string");
|
|
@@ -1376,6 +1834,17 @@ function getHooxStatePath() {
|
|
|
1376
1834
|
}
|
|
1377
1835
|
|
|
1378
1836
|
// src/session.ts
|
|
1837
|
+
import { z as z3 } from "zod";
|
|
1838
|
+
var SessionStateFileSchema = z3.object({
|
|
1839
|
+
activeView: z3.string().optional(),
|
|
1840
|
+
sidebarExpanded: z3.boolean().optional(),
|
|
1841
|
+
windowSize: z3.object({
|
|
1842
|
+
cols: z3.number().int().positive(),
|
|
1843
|
+
rows: z3.number().int().positive()
|
|
1844
|
+
}).optional(),
|
|
1845
|
+
lastData: z3.number().nonnegative().optional(),
|
|
1846
|
+
savedAt: z3.string().optional()
|
|
1847
|
+
}).strict();
|
|
1379
1848
|
var TUI_STATE_DIR = ".tui-state";
|
|
1380
1849
|
var SESSION_FILE = (() => {
|
|
1381
1850
|
try {
|
|
@@ -1413,7 +1882,8 @@ async function restoreSession() {
|
|
|
1413
1882
|
if (!exists)
|
|
1414
1883
|
return { ...DEFAULT_SESSION };
|
|
1415
1884
|
const raw = await file.text();
|
|
1416
|
-
const
|
|
1885
|
+
const result = SessionStateFileSchema.safeParse(JSON.parse(raw));
|
|
1886
|
+
const parsed = result.success ? result.data : {};
|
|
1417
1887
|
return {
|
|
1418
1888
|
activeView: validateViewId(parsed.activeView) ? parsed.activeView : DEFAULT_SESSION.activeView,
|
|
1419
1889
|
sidebarExpanded: typeof parsed.sidebarExpanded === "boolean" ? parsed.sidebarExpanded : DEFAULT_SESSION.sidebarExpanded,
|
|
@@ -1601,7 +2071,7 @@ var BASE_WORKERS = {
|
|
|
1601
2071
|
path: "workers/d1-worker",
|
|
1602
2072
|
vars: { database_name: "hoox-db" }
|
|
1603
2073
|
},
|
|
1604
|
-
hoox: { enabled: true, path: "workers/hoox", vars: {} },
|
|
2074
|
+
hoox: { enabled: true, path: "workers/hoox-worker", vars: {} },
|
|
1605
2075
|
"agent-worker": { enabled: true, path: "workers/agent-worker", vars: {} },
|
|
1606
2076
|
"analytics-worker": {
|
|
1607
2077
|
enabled: true,
|
|
@@ -1921,6 +2391,7 @@ class WizardEngine {
|
|
|
1921
2391
|
}
|
|
1922
2392
|
}
|
|
1923
2393
|
}
|
|
2394
|
+
|
|
1924
2395
|
// src/wizard/persistence.ts
|
|
1925
2396
|
function serializeState(state) {
|
|
1926
2397
|
return JSON.stringify(state, null, 2);
|
|
@@ -1951,7 +2422,7 @@ function deriveCalledBy(manifests) {
|
|
|
1951
2422
|
var manifests = {
|
|
1952
2423
|
hoox: {
|
|
1953
2424
|
name: "hoox",
|
|
1954
|
-
path: "workers/hoox",
|
|
2425
|
+
path: "workers/hoox-worker",
|
|
1955
2426
|
vars: {
|
|
1956
2427
|
WEBHOOK_API_KEY_BINDING: {
|
|
1957
2428
|
type: "secret",
|
|
@@ -2295,6 +2766,7 @@ var manifests = {
|
|
|
2295
2766
|
var CALLED_BY = deriveCalledBy(manifests);
|
|
2296
2767
|
var WORKER_MANIFESTS = manifests;
|
|
2297
2768
|
var WORKER_NAMES = Object.keys(manifests).sort();
|
|
2769
|
+
|
|
2298
2770
|
// src/schemas/validators.ts
|
|
2299
2771
|
import { parse } from "jsonc-parser";
|
|
2300
2772
|
function validateWranglerJsonc(workerName, manifest, jsoncContent) {
|
|
@@ -2320,7 +2792,8 @@ function validateWranglerJsonc(workerName, manifest, jsoncContent) {
|
|
|
2320
2792
|
});
|
|
2321
2793
|
return errors;
|
|
2322
2794
|
}
|
|
2323
|
-
const
|
|
2795
|
+
const wrangler = parsed;
|
|
2796
|
+
const declaredVars = wrangler.vars ?? {};
|
|
2324
2797
|
for (const [name, def] of Object.entries(manifest.vars)) {
|
|
2325
2798
|
if (!(name in declaredVars)) {
|
|
2326
2799
|
errors.push({
|
|
@@ -2341,7 +2814,7 @@ function validateWranglerJsonc(workerName, manifest, jsoncContent) {
|
|
|
2341
2814
|
});
|
|
2342
2815
|
}
|
|
2343
2816
|
}
|
|
2344
|
-
const declaredServices =
|
|
2817
|
+
const declaredServices = wrangler.services ?? [];
|
|
2345
2818
|
for (const expected of manifest.services) {
|
|
2346
2819
|
const match = declaredServices.find((s) => s.binding === expected.binding && s.service === expected.service);
|
|
2347
2820
|
if (!match) {
|
|
@@ -2397,7 +2870,8 @@ function validateRootSecrets(workerName, manifest, rootJsoncContent) {
|
|
|
2397
2870
|
});
|
|
2398
2871
|
return errors;
|
|
2399
2872
|
}
|
|
2400
|
-
const
|
|
2873
|
+
const rootWrangler = parsed;
|
|
2874
|
+
const rootSecrets = rootWrangler.workers?.[workerName]?.secrets ?? [];
|
|
2401
2875
|
const expectedSecrets = Object.entries(manifest.vars).filter(([_, def]) => def.type === "secret").map(([name]) => name);
|
|
2402
2876
|
for (const expected of expectedSecrets) {
|
|
2403
2877
|
if (!rootSecrets.includes(expected)) {
|
|
@@ -2647,7 +3121,7 @@ var useServiceStore = create2()(immer2((set, get) => ({
|
|
|
2647
3121
|
fetchWorkers: async () => {
|
|
2648
3122
|
const { hooxFetch: hooxFetch2 } = await Promise.resolve().then(() => (init_api_client(), exports_api_client));
|
|
2649
3123
|
try {
|
|
2650
|
-
const data = await hooxFetch2("/workers");
|
|
3124
|
+
const data = await hooxFetch2("/v1/workers");
|
|
2651
3125
|
set((state) => {
|
|
2652
3126
|
state.workers = data;
|
|
2653
3127
|
state.lastUpdated = Date.now();
|
|
@@ -2672,20 +3146,26 @@ var useServiceStore = create2()(immer2((set, get) => ({
|
|
|
2672
3146
|
acknowledged: false
|
|
2673
3147
|
});
|
|
2674
3148
|
set((state) => {
|
|
3149
|
+
state.lastError = msg;
|
|
3150
|
+
state.disconnectedAt = state.disconnectedAt ?? Date.now();
|
|
2675
3151
|
if (state.connectionStatus === "connected" || state.connectionStatus === "polling") {
|
|
2676
3152
|
state.connectionStatus = "reconnecting";
|
|
2677
3153
|
state.retryCount = state.retryCount + 1;
|
|
2678
|
-
state.lastError = msg;
|
|
2679
|
-
state.disconnectedAt = state.disconnectedAt ?? Date.now();
|
|
2680
3154
|
if (state.retryCount >= MAX_RETRIES2) {
|
|
2681
3155
|
state.connectionStatus = "offline";
|
|
2682
3156
|
state.lastError = `Connection lost after ${MAX_RETRIES2} retries`;
|
|
2683
3157
|
} else {
|
|
2684
3158
|
state.reconnectDelay = getBackoffDelay(state.retryCount);
|
|
2685
3159
|
}
|
|
2686
|
-
}
|
|
2687
|
-
|
|
2688
|
-
state.
|
|
3160
|
+
} else if (state.connectionStatus === "reconnecting") {
|
|
3161
|
+
state.retryCount = state.retryCount + 1;
|
|
3162
|
+
if (state.retryCount >= MAX_RETRIES2) {
|
|
3163
|
+
state.connectionStatus = "offline";
|
|
3164
|
+
state.lastError = `Connection lost after ${MAX_RETRIES2} retries`;
|
|
3165
|
+
state.reconnectDelay = 0;
|
|
3166
|
+
} else {
|
|
3167
|
+
state.reconnectDelay = getBackoffDelay(state.retryCount);
|
|
3168
|
+
}
|
|
2689
3169
|
}
|
|
2690
3170
|
});
|
|
2691
3171
|
}
|
|
@@ -2693,7 +3173,7 @@ var useServiceStore = create2()(immer2((set, get) => ({
|
|
|
2693
3173
|
streamTrades: async () => {
|
|
2694
3174
|
const { subscribeSSE: subscribeSSE2 } = await Promise.resolve().then(() => (init_sse(), exports_sse));
|
|
2695
3175
|
try {
|
|
2696
|
-
await subscribeSSE2("/trades/stream", (trade) => {
|
|
3176
|
+
await subscribeSSE2("/v1/trades/stream", (trade) => {
|
|
2697
3177
|
get().pushTrade(trade);
|
|
2698
3178
|
});
|
|
2699
3179
|
} catch (error) {
|
|
@@ -2712,7 +3192,7 @@ var useServiceStore = create2()(immer2((set, get) => ({
|
|
|
2712
3192
|
streamLogs: async () => {
|
|
2713
3193
|
const { subscribeSSE: subscribeSSE2 } = await Promise.resolve().then(() => (init_sse(), exports_sse));
|
|
2714
3194
|
try {
|
|
2715
|
-
await subscribeSSE2("/logs/stream", (log) => {
|
|
3195
|
+
await subscribeSSE2("/v1/logs/stream", (log) => {
|
|
2716
3196
|
get().pushLog(log);
|
|
2717
3197
|
});
|
|
2718
3198
|
} catch (error) {
|
|
@@ -2772,9 +3252,7 @@ var useServiceStore = create2()(immer2((set, get) => ({
|
|
|
2772
3252
|
state.lastErrorDetails = null;
|
|
2773
3253
|
state.reconnectDelay = 0;
|
|
2774
3254
|
state.disconnectedAt = null;
|
|
2775
|
-
|
|
2776
|
-
state.connectionStatus = "connected";
|
|
2777
|
-
}
|
|
3255
|
+
state.connectionStatus = "connected";
|
|
2778
3256
|
}),
|
|
2779
3257
|
handleConnectionFailure: (errorMessage) => set((state) => {
|
|
2780
3258
|
state.lastError = errorMessage;
|
|
@@ -2837,6 +3315,7 @@ var useServiceStore = create2()(immer2((set, get) => ({
|
|
|
2837
3315
|
import { create as create3 } from "zustand";
|
|
2838
3316
|
import { immer as immer3 } from "zustand/middleware/immer";
|
|
2839
3317
|
import { persist, createJSONStorage } from "zustand/middleware";
|
|
3318
|
+
import { chmodSync as chmodSync2, mkdirSync as mkdirSync2, existsSync as existsSync3 } from "fs";
|
|
2840
3319
|
import { homedir as homedir3 } from "os";
|
|
2841
3320
|
import { join as join3 } from "path";
|
|
2842
3321
|
var bunConfigStorage = {
|
|
@@ -2853,11 +3332,17 @@ var bunConfigStorage = {
|
|
|
2853
3332
|
},
|
|
2854
3333
|
setItem: async (_name, value) => {
|
|
2855
3334
|
const dir = join3(homedir3(), ".hoox");
|
|
3335
|
+
if (!existsSync3(dir)) {
|
|
3336
|
+
mkdirSync2(dir, { recursive: true, mode: HOOX_DIR_MODE });
|
|
3337
|
+
}
|
|
2856
3338
|
try {
|
|
2857
|
-
|
|
3339
|
+
chmodSync2(dir, HOOX_DIR_MODE);
|
|
2858
3340
|
} catch {}
|
|
2859
3341
|
const filePath = join3(dir, "config.json");
|
|
2860
3342
|
await Bun.write(filePath, value);
|
|
3343
|
+
try {
|
|
3344
|
+
chmodSync2(filePath, HOOX_CONFIG_FILE_MODE);
|
|
3345
|
+
} catch {}
|
|
2861
3346
|
},
|
|
2862
3347
|
removeItem: async (_name) => {}
|
|
2863
3348
|
};
|
|
@@ -2949,7 +3434,9 @@ var useConfigStore = create3()(persist(immer3((set) => ({
|
|
|
2949
3434
|
// src/index.ts
|
|
2950
3435
|
init_api_client();
|
|
2951
3436
|
init_sse();
|
|
3437
|
+
init_operator_transport();
|
|
2952
3438
|
export {
|
|
3439
|
+
writeSecureFileSync,
|
|
2953
3440
|
writeConfigSync,
|
|
2954
3441
|
writeConfig,
|
|
2955
3442
|
validateWranglerJsonc,
|
|
@@ -2967,15 +3454,24 @@ export {
|
|
|
2967
3454
|
serializeState,
|
|
2968
3455
|
saveSession,
|
|
2969
3456
|
restoreSession,
|
|
3457
|
+
resolveOperatorTransportProfile,
|
|
3458
|
+
resolveInternalAuthKey,
|
|
3459
|
+
resolveHooxRuntimeRoot,
|
|
2970
3460
|
resolveHooxPath,
|
|
2971
3461
|
resolveDependencies,
|
|
2972
3462
|
readConfigSync,
|
|
2973
3463
|
readConfig,
|
|
3464
|
+
operatorUrl,
|
|
2974
3465
|
logKvTimestamp,
|
|
2975
3466
|
isWithinHooxHome,
|
|
3467
|
+
isUnixModeGroupOrWorldAccessible,
|
|
3468
|
+
isHooxSetupRoot,
|
|
3469
|
+
isConfigWorldOrGroupReadable,
|
|
2976
3470
|
hooxFetch,
|
|
2977
3471
|
healthCheck,
|
|
2978
3472
|
headersToObject,
|
|
3473
|
+
hasOperatorClientCredentials,
|
|
3474
|
+
getTuiEntryCandidates,
|
|
2979
3475
|
getRelativeHooxPath,
|
|
2980
3476
|
getHooxWranglerPath,
|
|
2981
3477
|
getHooxStatePath,
|
|
@@ -2998,7 +3494,10 @@ export {
|
|
|
2998
3494
|
formatDuration2 as formatDuration,
|
|
2999
3495
|
formatCurrency,
|
|
3000
3496
|
formatCpu,
|
|
3497
|
+
formatConfigPermissionWarning,
|
|
3001
3498
|
formatCompactCurrency,
|
|
3499
|
+
findHooxSetupRoot,
|
|
3500
|
+
ensureHooxDirSecure,
|
|
3002
3501
|
deserializeState,
|
|
3003
3502
|
createSuccessResponse,
|
|
3004
3503
|
createQueueHandler,
|
|
@@ -3017,6 +3516,9 @@ export {
|
|
|
3017
3516
|
createJsonResponse,
|
|
3018
3517
|
createErrorResponse,
|
|
3019
3518
|
createCronHandler,
|
|
3519
|
+
buildOperatorAuthHeaders,
|
|
3520
|
+
authenticatedServiceFetch,
|
|
3521
|
+
WorkerStatusColor,
|
|
3020
3522
|
WorkerAPIError,
|
|
3021
3523
|
WizardEngine,
|
|
3022
3524
|
WebhookPayloadSchema,
|
|
@@ -3024,23 +3526,41 @@ export {
|
|
|
3024
3526
|
WORKER_MANIFESTS,
|
|
3025
3527
|
WORKER_DEPENDENCIES,
|
|
3026
3528
|
WIZARD_STATE_PATH,
|
|
3529
|
+
WALLET_EXECUTE_AUTH_KEY_FIELDS,
|
|
3027
3530
|
TradeSignalSchema,
|
|
3531
|
+
TradeQueueMessageSchema,
|
|
3028
3532
|
TradeActionSchema,
|
|
3533
|
+
TRADE_READ_AUTH_KEY_FIELDS,
|
|
3534
|
+
TRADE_EXECUTE_AUTH_KEY_FIELDS,
|
|
3029
3535
|
TRADEMARK_NOTICE,
|
|
3030
3536
|
TRADEMARKS,
|
|
3537
|
+
TELEGRAM_ALERT_AUTH_KEY_FIELDS,
|
|
3538
|
+
ServiceAuthError,
|
|
3031
3539
|
PositionSchema,
|
|
3032
3540
|
PRESETS,
|
|
3541
|
+
LogLevelColor,
|
|
3033
3542
|
KVKeys,
|
|
3543
|
+
InternalAuthKeyFields,
|
|
3034
3544
|
INTEGRATIONS,
|
|
3545
|
+
HOOX_DIR_MODE,
|
|
3546
|
+
HOOX_CONFIG_FILE_MODE,
|
|
3035
3547
|
FULL_LEGAL_NOTICE,
|
|
3036
3548
|
ExchangeRouter,
|
|
3037
3549
|
Errors,
|
|
3038
3550
|
DISCLAIMER_HEADER,
|
|
3039
3551
|
DISCLAIMER,
|
|
3552
|
+
DASHBOARD_TRADE_EXECUTE_AUTH_KEY_FIELDS,
|
|
3553
|
+
DASHBOARD_TELEGRAM_ALERT_AUTH_KEY_FIELDS,
|
|
3554
|
+
DASHBOARD_D1_READ_AUTH_KEY_FIELDS,
|
|
3555
|
+
D1_WRITE_AUTH_KEY_FIELDS,
|
|
3556
|
+
D1_READ_AUTH_KEY_FIELDS,
|
|
3040
3557
|
D1Repository,
|
|
3558
|
+
CoolBracketPalette,
|
|
3559
|
+
ConnectionStatusColor,
|
|
3041
3560
|
Colors,
|
|
3042
3561
|
COPYRIGHT,
|
|
3043
3562
|
CALLED_BY,
|
|
3044
3563
|
BaseExchangeClient2 as BaseExchangeClient,
|
|
3045
|
-
BalanceSchema
|
|
3564
|
+
BalanceSchema,
|
|
3565
|
+
AlertSeverityColor
|
|
3046
3566
|
};
|