Skip to content

Commit 63e9143

Browse files
Pigbibicodex
andcommitted
Harden strategy switch boot fetches with timeout fallback
Co-Authored-By: Codex <noreply@openai.com>
1 parent cb6d9e1 commit 63e9143

2 files changed

Lines changed: 49 additions & 13 deletions

File tree

web/strategy-switch-console/app.js

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@
153153
nasdaq_sp500_smart_dca: { defaultMode: "fixed", defaultBaseInvestmentUsd: "1000" },
154154
ibit_smart_dca: { defaultMode: "fixed", defaultBaseInvestmentUsd: "1000" },
155155
};
156+
const APP_BOOT_TIMEOUT_MS = 15000;
156157
const platformMinReservedCashVariables = {
157158
longbridge: "LONGBRIDGE_MIN_RESERVED_CASH_USD",
158159
ibkr: "IBKR_MIN_RESERVED_CASH_USD",
@@ -286,6 +287,7 @@
286287
bootStrategy: "正在读取策略目录。",
287288
bootSession: "正在验证登录状态。",
288289
bootConfig: "正在读取账号配置和当前状态。",
290+
bootTimeout: "加载超时,已切换到公开预览(登录后可重试)。",
289291
bootPublic: "公开预览已就绪。",
290292
login: "登录",
291293
logout: "退出",
@@ -446,6 +448,7 @@
446448
bootStrategy: "Reading strategy catalog.",
447449
bootSession: "Checking sign-in status.",
448450
bootConfig: "Reading account config and current state.",
451+
bootTimeout: "Loading timed out; switched to public preview. Retry after signing in.",
449452
bootPublic: "Public preview is ready.",
450453
login: "Sign in",
451454
logout: "Sign out",
@@ -664,6 +667,31 @@
664667
}
665668
}
666669

670+
async function fetchWithTimeout(url, init = {}, timeoutMs = APP_BOOT_TIMEOUT_MS) {
671+
const controller = new AbortController();
672+
const timeoutId = window.setTimeout(() => controller.abort(), timeoutMs);
673+
try {
674+
return await fetch(url, { ...init, signal: controller.signal });
675+
} catch (error) {
676+
if (error?.name === "AbortError") {
677+
throw new Error("request timeout");
678+
}
679+
throw error;
680+
} finally {
681+
window.clearTimeout(timeoutId);
682+
}
683+
}
684+
685+
async function requestJson(url, init = {}, timeoutMs = APP_BOOT_TIMEOUT_MS) {
686+
const response = await fetchWithTimeout(url, { ...init, cache: "no-store" }, timeoutMs);
687+
if (!response.ok) throw new Error("request failed");
688+
return response.json();
689+
}
690+
691+
function isRequestTimeoutError(error) {
692+
return String(error?.message || "").toLowerCase() === "request timeout";
693+
}
694+
667695
function optionsFor(platform) {
668696
return state.accountOptions[platform] && state.accountOptions[platform].length
669697
? state.accountOptions[platform]
@@ -2513,9 +2541,7 @@
25132541
state.bootMessageKey = "bootSession";
25142542
render();
25152543
try {
2516-
const response = await fetch("/api/session", { cache: "no-store" });
2517-
if (!response.ok) throw new Error("no backend");
2518-
const session = await response.json();
2544+
const session = await requestJson("/api/session");
25192545
state.auth = {
25202546
available: true,
25212547
allowed: Boolean(session.allowed),
@@ -2538,9 +2564,7 @@
25382564
state.bootMessageKey = "bootStrategy";
25392565
render();
25402566
try {
2541-
const response = await fetch("/api/strategy-profiles", { cache: "no-store" });
2542-
if (!response.ok) throw new Error("no strategy profiles");
2543-
const payload = await response.json();
2567+
const payload = await requestJson("/api/strategy-profiles");
25442568
applyStrategyProfiles(payload.strategyProfiles || []);
25452569
if (payload.platformMeta) platformMeta = payload.platformMeta;
25462570
for (const platform of Object.keys(platformMeta)) syncStrategyForAccount(platform);
@@ -2557,9 +2581,7 @@
25572581
state.bootMessageKey = "bootConfig";
25582582
render();
25592583
try {
2560-
const response = await fetch("/api/config", { cache: "no-store" });
2561-
if (!response.ok) throw new Error("no config");
2562-
const payload = await response.json();
2584+
const payload = await requestJson("/api/config");
25632585
if (payload.accountOptions) {
25642586
applyStrategyProfiles(payload.strategyProfiles || defaultStrategyProfiles);
25652587
state.accountOptions = normalizeAccountOptions(payload.accountOptions);
@@ -2575,9 +2597,14 @@
25752597
state.configSource = "default";
25762598
state.currentStrategies = {};
25772599
}
2578-
} catch {
2600+
} catch (error) {
25792601
state.configSource = "default";
25802602
state.currentStrategies = {};
2603+
if (isRequestTimeoutError(error)) {
2604+
state.bootMessageKey = "bootTimeout";
2605+
} else {
2606+
state.bootMessageKey = "bootPublic";
2607+
}
25812608
} finally {
25822609
state.appReady = true;
25832610
render();
@@ -2876,6 +2903,15 @@
28762903
boot();
28772904

28782905
async function boot() {
2879-
await refreshStrategyProfiles();
2880-
await refreshSession();
2906+
try {
2907+
await refreshStrategyProfiles();
2908+
await refreshSession();
2909+
} catch {
2910+
state.auth = { available: false, allowed: false, admin: false, login: null };
2911+
state.configSource = "default";
2912+
state.currentStrategies = {};
2913+
state.bootMessageKey = "bootTimeout";
2914+
state.appReady = true;
2915+
render();
2916+
}
28812917
}

web/strategy-switch-console/app_js.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)