| export type ScopeTokenProvider = { |
| getAccessToken: (scope: string) => Promise<string>; |
| }; |
|
|
| 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<Response> { |
| 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<Response> => |
| 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 { |
| |
| } |
| } |
|
|
| return firstAttempt; |
| } |
|
|