export type ScopeTokenProvider = { getAccessToken: (scope: string) => Promise; }; function isAuthFailureStatus(status: number): boolean { return status === 401 || status === 403; } export async function fetchWithBearerAuthScopeFallback(params: { url: string; scopes: readonly string[]; tokenProvider?: ScopeTokenProvider; fetchFn?: typeof fetch; requestInit?: RequestInit; requireHttps?: boolean; shouldAttachAuth?: (url: string) => boolean; shouldRetry?: (response: Response) => boolean; }): Promise { const fetchFn = params.fetchFn ?? fetch; let parsedUrl: URL; try { parsedUrl = new URL(params.url); } catch { throw new Error(`Invalid URL: ${params.url}`); } if (params.requireHttps === true && parsedUrl.protocol !== "https:") { throw new Error(`URL must use HTTPS: ${params.url}`); } const fetchOnce = (headers?: Headers): Promise => fetchFn(params.url, { ...params.requestInit, ...(headers ? { headers } : {}), }); const firstAttempt = await fetchOnce(); if (firstAttempt.ok) { return firstAttempt; } if (!params.tokenProvider) { return firstAttempt; } const shouldRetry = params.shouldRetry ?? ((response: Response) => isAuthFailureStatus(response.status)); if (!shouldRetry(firstAttempt)) { return firstAttempt; } if (params.shouldAttachAuth && !params.shouldAttachAuth(params.url)) { return firstAttempt; } for (const scope of params.scopes) { try { const token = await params.tokenProvider.getAccessToken(scope); const authHeaders = new Headers(params.requestInit?.headers); authHeaders.set("Authorization", `Bearer ${token}`); const authAttempt = await fetchOnce(authHeaders); if (authAttempt.ok) { return authAttempt; } if (!shouldRetry(authAttempt)) { continue; } } catch { // Ignore token/fetch errors and continue trying remaining scopes. } } return firstAttempt; }