fix(web-probe): login accounts through context request

This commit is contained in:
Codex
2026-06-26 15:14:26 +00:00
parent 7157a40d4c
commit dea3556872
@@ -83,7 +83,7 @@ try {
context = await browser.newContext({ viewport, ...(playwrightProxy === null ? {} : { proxy: playwrightProxy }) });
page = await context.newPage();
attachPassiveListeners(page, "control", pageId);
auth = await runControlCommand({ id: "startup-login", type: "login", createdAt: startedAt, source: "startup" }, async () => authenticate(context, page));
auth = await runControlCommand({ id: "startup-login", type: "login", createdAt: startedAt, source: "startup" }, async () => authenticate(context));
await runControlCommand({ id: "startup-goto", type: "goto", path: targetPath, createdAt: new Date().toISOString(), source: "startup" }, async () => gotoTarget(targetPath));
observerPage = await context.newPage();
attachPassiveListeners(observerPage, "observer", observerPageId);
@@ -340,7 +340,7 @@ async function processCommand(command) {
await writeHeartbeat({ status: "running", activeCommandId });
await appendJsonl(files.control, controlRecord(command, "started", commandInputSummary(command)));
switch (command.type) {
case "login": return authenticate(context, page);
case "login": return authenticate(context);
case "loginAccount": return withObserverSync(await loginAccount(command), "loginAccount");
case "logout": return withObserverSync(await logoutAccount(command), "logout");
case "listSessions": return withObserverSync(await listSessions(command), "listSessions");
@@ -490,87 +490,77 @@ async function runControlCommand(command, fn) {
}
}
async function authenticate(browserContext, authPage) {
async function authenticate(browserContext) {
const loginUrl = new URL("/auth/login", baseUrl).toString();
let activeAuthPage = authPage && !authPage.isClosed() ? authPage : null;
let closeAuthPage = false;
if (!activeAuthPage) {
activeAuthPage = await browserContext.newPage();
closeAuthPage = true;
}
const attempts = [];
const maxAttempts = 5;
const initialDelayMs = 250;
const maxDelayMs = 5000;
try {
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
const retryDelayMs = attempt < maxAttempts ? Math.min(maxDelayMs, initialDelayMs * (2 ** (attempt - 1))) : 0;
const retryLabel = attempt + "/" + maxAttempts;
await writeHeartbeat({ status: terminalStatus, auth: { phase: "api-login", retryAttempt: attempt, retryMaxAttempts: maxAttempts, lastRetryLabel: retryLabel, retryDelayMs: 0, retryExhausted: false, valuesRedacted: true } }).catch(() => {});
try {
const response = await pageAuthLogin(activeAuthPage, loginUrl);
const cookieState = await readAuthCookieState(browserContext);
const retryable = isRetryableAuthStatus(response.status);
const item = {
attempt,
retryAttempt: attempt,
retryMaxAttempts: maxAttempts,
retryLabel,
retryDelayMs: retryable && attempt < maxAttempts ? retryDelayMs : 0,
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
const retryDelayMs = attempt < maxAttempts ? Math.min(maxDelayMs, initialDelayMs * (2 ** (attempt - 1))) : 0;
const retryLabel = attempt + "/" + maxAttempts;
await writeHeartbeat({ status: terminalStatus, auth: { phase: "api-login", retryAttempt: attempt, retryMaxAttempts: maxAttempts, lastRetryLabel: retryLabel, retryDelayMs: 0, retryExhausted: false, valuesRedacted: true } }).catch(() => {});
try {
const response = await pageAuthLogin(browserContext, loginUrl);
const cookieState = await readAuthCookieState(browserContext);
const retryable = isRetryableAuthStatus(response.status);
const item = {
attempt,
retryAttempt: attempt,
retryMaxAttempts: maxAttempts,
retryLabel,
retryDelayMs: retryable && attempt < maxAttempts ? retryDelayMs : 0,
method: "api",
status: response.status,
statusText: response.statusText,
retryable,
cookiePresent: cookieState.cookiePresent,
cookieNames: cookieState.cookieNames,
valuesRedacted: true,
};
attempts.push(item);
await writeHeartbeat({ status: terminalStatus, auth: { phase: "api-login", lastRetryLabel: item.retryLabel, retryAttempt: item.retryAttempt, retryMaxAttempts: item.retryMaxAttempts, retryDelayMs: item.retryDelayMs, lastStatus: item.status, lastStatusText: item.statusText, retryable: item.retryable, cookiePresent: item.cookiePresent, retryExhausted: false, valuesRedacted: true } }).catch(() => {});
if (response.ok && cookieState.cookiePresent) {
return {
ok: true,
method: "api",
loginPath: new URL(loginUrl).pathname,
status: response.status,
statusText: response.statusText,
retryable,
cookiePresent: cookieState.cookiePresent,
cookiePresent: true,
cookieNames: cookieState.cookieNames,
attempts,
retryCount: attempt - 1,
retryMaxAttempts: maxAttempts,
lastRetryLabel: attempt + "/" + maxAttempts,
retryExhausted: false,
retryable: false,
valuesRedacted: true,
};
attempts.push(item);
await writeHeartbeat({ status: terminalStatus, auth: { phase: "api-login", lastRetryLabel: item.retryLabel, retryAttempt: item.retryAttempt, retryMaxAttempts: item.retryMaxAttempts, retryDelayMs: item.retryDelayMs, lastStatus: item.status, lastStatusText: item.statusText, retryable: item.retryable, cookiePresent: item.cookiePresent, retryExhausted: false, valuesRedacted: true } }).catch(() => {});
if (response.ok && cookieState.cookiePresent) {
return {
ok: true,
method: "api",
loginPath: new URL(loginUrl).pathname,
status: response.status,
statusText: response.statusText,
cookiePresent: true,
cookieNames: cookieState.cookieNames,
attempts,
retryCount: attempt - 1,
retryMaxAttempts: maxAttempts,
lastRetryLabel: attempt + "/" + maxAttempts,
retryExhausted: false,
retryable: false,
valuesRedacted: true,
};
}
if (!retryable) break;
} catch (error) {
const retryable = isRetryableAuthError(error);
attempts.push({
attempt,
retryAttempt: attempt,
retryMaxAttempts: maxAttempts,
retryLabel,
retryDelayMs: retryable && attempt < maxAttempts ? retryDelayMs : 0,
method: "api",
status: 0,
statusText: "request-error",
retryable,
error: error && error.message ? truncate(error.message, 500) : truncate(String(error), 500),
cookiePresent: false,
cookieNames: [],
valuesRedacted: true,
});
const item = attempts[attempts.length - 1] || null;
await writeHeartbeat({ status: terminalStatus, auth: { phase: "api-login", lastRetryLabel: item?.retryLabel || retryLabel, retryAttempt: attempt, retryMaxAttempts: maxAttempts, retryDelayMs: item?.retryDelayMs ?? 0, lastStatus: item?.status ?? 0, lastStatusText: item?.statusText ?? "request-error", retryable, cookiePresent: false, retryExhausted: false, lastError: item?.error || null, valuesRedacted: true } }).catch(() => {});
if (!retryable) break;
}
if (attempt < maxAttempts && attempts[attempts.length - 1]?.retryable === true) await sleep(retryDelayMs);
if (!retryable) break;
} catch (error) {
const retryable = isRetryableAuthError(error);
attempts.push({
attempt,
retryAttempt: attempt,
retryMaxAttempts: maxAttempts,
retryLabel,
retryDelayMs: retryable && attempt < maxAttempts ? retryDelayMs : 0,
method: "api",
status: 0,
statusText: "request-error",
retryable,
error: error && error.message ? truncate(error.message, 500) : truncate(String(error), 500),
cookiePresent: false,
cookieNames: [],
valuesRedacted: true,
});
const item = attempts[attempts.length - 1] || null;
await writeHeartbeat({ status: terminalStatus, auth: { phase: "api-login", lastRetryLabel: item?.retryLabel || retryLabel, retryAttempt: attempt, retryMaxAttempts: maxAttempts, retryDelayMs: item?.retryDelayMs ?? 0, lastStatus: item?.status ?? 0, lastStatusText: item?.statusText ?? "request-error", retryable, cookiePresent: false, retryExhausted: false, lastError: item?.error || null, valuesRedacted: true } }).catch(() => {});
if (!retryable) break;
}
} finally {
if (closeAuthPage && activeAuthPage && !activeAuthPage.isClosed()) await activeAuthPage.close().catch(() => {});
if (attempt < maxAttempts && attempts[attempts.length - 1]?.retryable === true) await sleep(retryDelayMs);
}
const cookieState = await readAuthCookieState(browserContext);
const last = attempts[attempts.length - 1] || null;
@@ -598,23 +588,19 @@ async function authenticate(browserContext, authPage) {
throw error;
}
async function pageAuthLogin(authPage, loginUrl, credential = { username, password }) {
if (!authPage) throw new Error("auth page is not ready");
await authPage.goto(new URL("/assets/favicon.svg", baseUrl).toString(), { waitUntil: "domcontentloaded", timeout: 12000 });
return authPage.evaluate(async (input) => {
const response = await fetch(input.loginUrl, {
method: "POST",
headers: { accept: "application/json", "content-type": "application/json" },
body: JSON.stringify({ username: input.username, password: input.password }),
credentials: "include",
});
await response.text().catch(() => "");
return {
ok: response.ok,
status: response.status,
statusText: response.statusText || "",
};
}, { username: credential.username, password: credential.password, loginUrl });
async function pageAuthLogin(browserContext, loginUrl, credential = { username, password }) {
if (!browserContext?.request) throw new Error("auth browser context request is not ready");
const response = await browserContext.request.post(loginUrl, {
data: { username: credential.username, password: credential.password },
headers: { accept: "application/json", "content-type": "application/json" },
timeout: 12000,
});
await response.text().catch(() => "");
return {
ok: response.ok(),
status: response.status(),
statusText: response.statusText() || "",
};
}
async function loginAccount(command) {
@@ -622,7 +608,7 @@ async function loginAccount(command) {
const credential = credentialForAccount(accountId);
const loginUrl = new URL("/auth/login", baseUrl).toString();
const before = await accountSessionSnapshot();
const response = await pageAuthLogin(page, loginUrl, credential);
const response = await pageAuthLogin(context, loginUrl, credential);
const cookieState = await readAuthCookieState(context);
if (!response.ok || !cookieState.cookiePresent) {
const error = new Error("loginAccount failed for accountId=" + accountId + " status=" + response.status + " " + (response.statusText || ""));