From dff369aafdb7dde653ec102d866b08ee14e6eb90 Mon Sep 17 00:00:00 2001 From: kris Date: Sat, 28 Mar 2026 09:13:46 +0800 Subject: [PATCH] harden oneliner session backend recovery --- web/storyforge-web-v4/assets/app.js | 25 ++++++++++++++++--- .../tests/workbench-pages.test.mjs | 18 +++++++++++++ 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/web/storyforge-web-v4/assets/app.js b/web/storyforge-web-v4/assets/app.js index c9508e5..8cc3ee5 100644 --- a/web/storyforge-web-v4/assets/app.js +++ b/web/storyforge-web-v4/assets/app.js @@ -562,6 +562,17 @@ function setLastSeenAt(value) { appState.lastSeenAt = SESSION_STORE.setLastSeenAt(value); } +function normalizeBackendUrlValue(value) { + return String(value || "").trim().replace(/\/$/, ""); +} + +function hasSessionBackendMismatch(expectedBackendUrl = DEFAULT_BACKEND_URL) { + if (!appState.session) return false; + const expected = normalizeBackendUrlValue(expectedBackendUrl); + const current = normalizeBackendUrlValue(appState.session.backendUrl); + return Boolean(expected && current && expected !== current); +} + function compareDateDesc(leftValue, rightValue) { return new Date(rightValue || 0).getTime() - new Date(leftValue || 0).getTime(); } @@ -1139,6 +1150,10 @@ async function loginWithAutoSession(backendUrl = DEFAULT_BACKEND_URL) { async function ensureAutoSession(options = {}) { const backendUrl = options.backendUrl || readAuthForm().backendUrl || DEFAULT_BACKEND_URL; const force = Boolean(options.force); + const backendMismatch = hasSessionBackendMismatch(backendUrl); + if (backendMismatch) { + persistSession(null); + } if (appState.session && !force) { return true; } @@ -1689,10 +1704,11 @@ async function loadPlatformAccount(platform, accountId, requestToken = 0) { async function bootstrap() { renderAll(); - if (!appState.session) { - setBusy(true, "正在自动连接后端..."); + const backendMismatch = hasSessionBackendMismatch(); + if (!appState.session || backendMismatch) { + setBusy(true, backendMismatch ? "正在切换到当前工作区后端..." : "正在自动连接后端..."); try { - await ensureAutoSession(); + await ensureAutoSession({ force: backendMismatch }); } finally { setBusy(false, ""); } @@ -8279,7 +8295,8 @@ document.addEventListener("submit", async (event) => { openOneLinerPanel(); renderAll(); } catch (error) { - alert("OneLiner 调度失败: " + error.message); + presentActionFailure(error, "OneLiner 调度失败"); + openOneLinerPanel(); } finally { setBusy(false, ""); } diff --git a/web/storyforge-web-v4/tests/workbench-pages.test.mjs b/web/storyforge-web-v4/tests/workbench-pages.test.mjs index 8ded76f..8f3902f 100644 --- a/web/storyforge-web-v4/tests/workbench-pages.test.mjs +++ b/web/storyforge-web-v4/tests/workbench-pages.test.mjs @@ -75,3 +75,21 @@ test("ensureAutoSession re-renders immediately when auto-connect starts", () => assert.match(source, /appState\.autoConnectAttempted = true;/); assert.match(source, /renderAll\(\);/); }); + +test("bootstrap does not trust a stored session from a different backend", () => { + const helpers = extractBetween(APP, "function loadStoredSession()", "function compareDateDesc("); + const ensure = extractBetween(APP, "async function ensureAutoSession(options = {})", "async function refreshFromAuthModal()"); + const bootstrap = extractBetween(APP, "async function bootstrap()", "async function markTrackingDigestRead()"); + + assert.match(helpers, /function normalizeBackendUrlValue\(/); + assert.match(helpers, /function hasSessionBackendMismatch\(/); + assert.match(ensure, /const backendMismatch = hasSessionBackendMismatch\(backendUrl\);/); + assert.match(ensure, /persistSession\(null\);/); + assert.match(bootstrap, /const backendMismatch = hasSessionBackendMismatch\(\);/); + assert.match(bootstrap, /await ensureAutoSession\(\{ force: backendMismatch \}\);/); +}); + +test("oneliner submit failures stay inside the app instead of using a browser alert", () => { + assert.doesNotMatch(APP, /alert\("OneLiner 调度失败:/); + assert.match(APP, /presentActionFailure\(error,\s*"OneLiner 调度失败"\)/); +});