function createAuthTokenRelay({ TOKEN_RELAY_URL, createDebugBody, getXsrfToken, logRequestFailure, logRequestStart, logRequestSuccess, maskToken, readResponseBodyForDebug, refreshXsrfCookie, renderDebugPanel, environment }) { const { fetch } = environment; async function requestTokenRelay({ name = "requestTokenRelay", url = TOKEN_RELAY_URL, includeExpiry = false } = {}) { let refreshResult = null; let refreshAttempted = false; while (true) { const xsrfTokenSource = await getXsrfToken(); if (!xsrfTokenSource.token) { if (refreshAttempted) { const requestEntry = createTokenRelayRequestEntry(xsrfTokenSource, refreshResult, name, url); logRequestFailure(requestEntry, "XSRF token cookie not found after automatic refresh."); throw new Error("XSRF token cookie not found after automatic refresh."); } refreshResult = await refreshXsrfCookie("missing-cookie"); refreshAttempted = true; continue; } const requestEntry = createTokenRelayRequestEntry(xsrfTokenSource, refreshResult, name, url); try { const response = await fetch(url, { method: "GET", credentials: "include", headers: { "x-xsrf-token": xsrfTokenSource.token } }); const responseBody = await readResponseBodyForDebug(response); if (!response.ok) { logRequestSuccess(requestEntry, response, responseBody); if (!refreshAttempted && isInvalidXsrfStatus(response.status)) { refreshResult = await refreshXsrfCookie(`tokenrelay-${response.status}`); refreshAttempted = true; requestEntry.metadata.xsrfRefresh = formatXsrfRefreshResult(refreshResult); renderDebugPanel(); continue; } throw new Error(`Token relay failed with status ${response.status}.`); } let responseData; try { responseData = await response.json(); } catch (error) { logRequestSuccess(requestEntry, response, responseBody); if (!refreshAttempted) { refreshResult = await refreshXsrfCookie("tokenrelay-invalid-response"); refreshAttempted = true; requestEntry.metadata.xsrfRefresh = formatXsrfRefreshResult(refreshResult); renderDebugPanel(); continue; } throw error; } if (!responseData || typeof responseData.access_token !== "string" || !responseData.access_token) { logRequestSuccess(requestEntry, response, responseBody); if (!refreshAttempted) { refreshResult = await refreshXsrfCookie("tokenrelay-missing-access-token"); refreshAttempted = true; requestEntry.metadata.xsrfRefresh = formatXsrfRefreshResult(refreshResult); renderDebugPanel(); continue; } throw new Error("Token relay response does not contain access_token."); } logRequestSuccess(requestEntry, response, createDebugBody(JSON.stringify({ ...responseData, access_token: maskToken(responseData.access_token) }, null, 2))); if (includeExpiry) { const lifetime = Number(responseData.expires_in); return { access_token: responseData.access_token, expiresAt: Number.isFinite(lifetime) && lifetime > 0 ? requestEntry.startedAt.getTime() + lifetime * 1000 : 0 }; } return responseData.access_token; } catch (error) { logRequestFailure(requestEntry, error.message || "Request failed."); throw error; } } } function createTokenRelayRequestEntry(xsrfTokenSource, refreshResult, name = "requestTokenRelay", url = TOKEN_RELAY_URL) { return logRequestStart({ name, method: "GET", url, headers: { "x-xsrf-token": xsrfTokenSource.token ? maskToken(xsrfTokenSource.token) : "(missing)" }, metadata: { xsrfCookieName: xsrfTokenSource.cookieName || "(missing)", xsrfTokenSource: xsrfTokenSource.source, xsrfMatchedCookies: xsrfTokenSource.matchedCookieNames && xsrfTokenSource.matchedCookieNames.length ? xsrfTokenSource.matchedCookieNames.join(", ") : "(none)", xsrfLookupDetails: xsrfTokenSource.lookupDetails && xsrfTokenSource.lookupDetails.length ? xsrfTokenSource.lookupDetails.join(" | ") : "(none)", xsrfTokenError: xsrfTokenSource.error || "", xsrfRefresh: formatXsrfRefreshResult(refreshResult) } }); } function isInvalidXsrfStatus(status) { return status === 400 || status === 401 || status === 403; } function formatXsrfRefreshResult(result) { if (!result) { return "not-requested"; } const outcome = result.refreshed ? "refreshed" : "not-refreshed"; const cookieName = result.cookieName || "(none)"; const duration = Number.isFinite(result.durationMs) ? `${result.durationMs}ms` : "unknown"; return `${result.reason}; ${outcome}; cookie=${cookieName}; duration=${duration}; ${result.error || ""}`.trim(); } return { requestTokenRelay, createTokenRelayRequestEntry, isInvalidXsrfStatus, formatXsrfRefreshResult }; }