(deps: BridgeApiDeps)
| 66 | } |
| 67 | |
| 68 | export function createBridgeApiClient(deps: BridgeApiDeps): BridgeApiClient { |
| 69 | function debug(msg: string): void { |
| 70 | deps.onDebug?.(msg) |
| 71 | } |
| 72 | |
| 73 | let consecutiveEmptyPolls = 0 |
| 74 | const EMPTY_POLL_LOG_INTERVAL = 100 |
| 75 | |
| 76 | function getHeaders(accessToken: string): Record<string, string> { |
| 77 | const headers: Record<string, string> = { |
| 78 | Authorization: `Bearer ${accessToken}`, |
| 79 | 'Content-Type': 'application/json', |
| 80 | 'anthropic-version': '2023-06-01', |
| 81 | 'anthropic-beta': BETA_HEADER, |
| 82 | 'x-environment-runner-version': deps.runnerVersion, |
| 83 | } |
| 84 | const deviceToken = deps.getTrustedDeviceToken?.() |
| 85 | if (deviceToken) { |
| 86 | headers['X-Trusted-Device-Token'] = deviceToken |
| 87 | } |
| 88 | return headers |
| 89 | } |
| 90 | |
| 91 | function resolveAuth(): string { |
| 92 | const accessToken = deps.getAccessToken() |
| 93 | if (!accessToken) { |
| 94 | throw new Error(BRIDGE_LOGIN_INSTRUCTION) |
| 95 | } |
| 96 | return accessToken |
| 97 | } |
| 98 | |
| 99 | /** |
| 100 | * Execute an OAuth-authenticated request with a single retry on 401. |
| 101 | * On 401, attempts token refresh via handleOAuth401Error (same pattern as |
| 102 | * withRetry.ts for v1/messages). If refresh succeeds, retries the request |
| 103 | * once with the new token. If refresh fails or the retry also returns 401, |
| 104 | * the 401 response is returned for handleErrorStatus to throw BridgeFatalError. |
| 105 | */ |
| 106 | async function withOAuthRetry<T>( |
| 107 | fn: (accessToken: string) => Promise<{ status: number; data: T }>, |
| 108 | context: string, |
| 109 | ): Promise<{ status: number; data: T }> { |
| 110 | const accessToken = resolveAuth() |
| 111 | const response = await fn(accessToken) |
| 112 | |
| 113 | if (response.status !== 401) { |
| 114 | return response |
| 115 | } |
| 116 | |
| 117 | if (!deps.onAuth401) { |
| 118 | debug(`[bridge:api] ${context}: 401 received, no refresh handler`) |
| 119 | return response |
| 120 | } |
| 121 | |
| 122 | // Attempt token refresh — matches the pattern in withRetry.ts |
| 123 | debug(`[bridge:api] ${context}: 401 received, attempting token refresh`) |
| 124 | const refreshed = await deps.onAuth401(accessToken) |
| 125 | if (refreshed) { |
no outgoing calls
no test coverage detected