fix(boot): keep active-profile recovery on one redirect path (#5001)

This commit is contained in:
Rod Boev authored and nesquena-hermes committed 2026-06-26 22:09:09 +00:00
1 parent 44dacc5cf5
commit 4d727c2e24
3 files changed
+100 -13

No files matched your search

+24 -1
View File
@@ -2261,7 +2261,30 @@ window._applyTitlebarProfileVisibility=_applyTitlebarProfileVisibility;
api(_checkUrl,{method:_testUpdates?'GET':'POST',body:_testUpdates?undefined:JSON.stringify({force:false})}).then(d=>{if(!_testUpdates)sessionStorage.setItem('hermes-update-checked','1');if((d.webui&&d.webui.behind>0)||(d.agent&&d.agent.behind>0))_showUpdateBanner(d);}).catch(()=>{});
}
async function _resolveActiveProfileBootstrapState({
loadActiveProfile = () => api('/api/profile/active'),
loadActiveProfile = async () => {
const rel = 'api/profile/active';
const url = new URL(rel, document.baseURI || location.href);
const res = await fetch(url.href, {
credentials: 'include',
headers: {'Content-Type': 'application/json'},
});
if (res.status === 401) return undefined;
if (!res.ok) {
const text = await res.text();
let message = text;
try {
const j = JSON.parse(text);
message = j.error || j.message || text;
} catch (_) {}
const err = new Error(message);
err.status = res.status;
err.statusText = res.statusText;
err.body = text;
throw err;
}
const ct = res.headers.get('content-type') || '';
return ct.includes('application/json') ? await res.json() : await res.text();
},
getNextUrl = () => window.location.pathname + window.location.search,
redirectToLogin = (nextUrl) => {
window.location.href = 'login?next=' + encodeURIComponent(nextUrl);
+74 -10
View File
@@ -91,6 +91,30 @@ function makeAttempt(attempt) {
};
}
function makeFetchResponse(attempt) {
const status = attempt.status === undefined ? 200 : attempt.status;
const payload = Object.prototype.hasOwnProperty.call(attempt, 'payload')
? attempt.payload
: {name: 'default', is_default: true};
const textBody = Object.prototype.hasOwnProperty.call(attempt, 'text')
? attempt.text
: JSON.stringify(payload);
return {
ok: status >= 200 && status < 300,
status,
statusText: attempt.statusText || '',
headers: {
get(name) {
return String(name).toLowerCase() === 'content-type'
? attempt.contentType || 'application/json'
: '';
},
},
json: async () => payload,
text: async () => textBody,
};
}
eval(extractFunction(bootSrc, '_resolveActiveProfileBootstrapState'));
const bootActiveProfileBlock = extractBlock(
bootSrc,
@@ -120,15 +144,52 @@ return (async () => {
const storageHistory = [];
for (const attempt of attempts) {
const state = await _resolveActiveProfileBootstrapState({
const nextUrl = attempt.nextUrl || '/';
const originalFetch = globalThis.fetch;
const originalWindow = globalThis.window;
const originalLocation = globalThis.location;
const originalDocument = globalThis.document;
let state;
try {
if (scenario.useDefaultLoader) {
const location = {
href: `http://example.test${nextUrl}`,
pathname: nextUrl,
search: '',
};
globalThis.fetch = async () => makeFetchResponse(attempt);
globalThis.location = location;
globalThis.document = {baseURI: 'http://example.test/'};
globalThis.window = {location};
state = await _resolveActiveProfileBootstrapState({
markerStorage: storage,
markerKey,
getNextUrl: () => nextUrl,
redirectToLogin: (value) => {
const href = `login?next=${encodeURIComponent(value)}`;
redirectUrls.push(href);
location.href = href;
},
});
} else {
state = await _resolveActiveProfileBootstrapState({
loadActiveProfile: makeAttempt(attempt),
markerStorage: storage,
markerKey,
getNextUrl: () => attempt.nextUrl || '/',
redirectToLogin: (nextUrl) => {
redirectUrls.push(`login?next=${encodeURIComponent(nextUrl)}`);
getNextUrl: () => nextUrl,
redirectToLogin: (value) => {
redirectUrls.push(`login?next=${encodeURIComponent(value)}`);
},
});
}
} finally {
globalThis.fetch = originalFetch;
globalThis.window = originalWindow;
globalThis.location = originalLocation;
globalThis.document = originalDocument;
}
const bootState = {};
let applyBotNameCalls = 0;
const bootResult = await runBootActiveProfileBlock(
@@ -194,9 +255,10 @@ def test_active_profile_boot_recovery_is_one_shot_and_bounded(driver_path):
driver_path,
{
"markerKey": "test-5001-active-profile-recovery",
"useDefaultLoader": True,
"attempts": [
{"type": "undefined", "nextUrl": "/"},
{"type": "undefined", "nextUrl": "/"},
{"status": 401, "payload": {"error": "Authentication required"}, "nextUrl": "/"},
{"status": 401, "payload": {"error": "Authentication required"}, "nextUrl": "/"},
],
},
)
@@ -255,8 +317,9 @@ def test_active_profile_boot_non_401_errors_fallback_without_redirect(driver_pat
driver_path,
{
"markerKey": "test-5001-active-profile-recovery-non-401",
"useDefaultLoader": True,
"attempts": [
{"type": "error", "status": 500, "nextUrl": "/"},
{"status": 500, "payload": {"error": "server failure"}, "nextUrl": "/"},
],
},
)
@@ -278,8 +341,9 @@ def test_active_profile_boot_invalid_payload_falls_back_without_redirect(driver_
driver_path,
{
"markerKey": "test-5001-active-profile-recovery-invalid-payload",
"useDefaultLoader": True,
"attempts": [
{"type": "return", "value": {"is_default": False}, "nextUrl": "/"},
{"status": 200, "payload": {"is_default": False}, "nextUrl": "/"},
],
},
)
@@ -301,10 +365,10 @@ def test_active_profile_success_path_applies_boot_state_and_continues(driver_pat
driver_path,
{
"markerKey": "test-5001-active-profile-recovery-success",
"simulateBootContinue": True,
"useDefaultLoader": True,
"attempts": [
{
"type": "success",
"status": 200,
"payload": {"name": "team-profile", "is_default": False},
}
],
+2 -2
View File
@@ -53,8 +53,8 @@ class TestComposerPlaceholderProfile:
def test_boot_applies_placeholder_after_active_profile_loads(self):
"""Boot must set the composer placeholder after S.activeProfile is known."""
src = _src("boot.js")
fetch_idx = src.find("api('/api/profile/active')")
assert fetch_idx >= 0, "boot.js should fetch the active profile during boot"
fetch_idx = src.find("const activeProfileState = await _resolveActiveProfileBootstrapState();")
assert fetch_idx >= 0, "boot.js should resolve the active profile during boot"
label_idx = src.find("const profileLabel=$('profileChipLabel');", fetch_idx)
assert label_idx >= 0, "profile chip sync should follow active profile fetch"
assert "applyBotName();" in src[fetch_idx:label_idx], (