import { authChallenge } from '@/shared/api' import type { EnvelopeNoData, PowProofRequest } from '@/shared/api' import { t } from '@/shared/i18n' import { AuthError, errorMessage } from './error' import { solve } from './pow' export type ApiResponse = { status: number; data: EnvelopeNoData; headers: Headers } export async function request( promise: Promise, ): Promise['data']['data']>> { let response: R try { response = await promise } catch (error) { if (error instanceof DOMException && error.name === 'AbortError') throw error if (error instanceof DOMException && error.name === 'TimeoutError') throw new AuthError('timeout', t('error.timeout')) throw new AuthError('connection', t('error.connection')) } if (response.status !== 200) { const body = response.data as Partial | null const code = body?.error_code || `http_${response.status}` const header = response.headers.get('Retry-After') const retry = header ? /^\d+$/.test(header) ? Number(header) : Math.ceil((Date.parse(header) - Date.now()) / 1000) : 0 const fromMessage = body?.message?.match(/retry after (\d+)s/) throw new AuthError( code, errorMessage(code) || t(response.status >= 500 ? 'error.server' : 'error.unexpected'), Math.max(0, retry || Number(fromMessage?.[1]) || 0), ) } if (response.data?.code !== 200 || response.data.data == null) throw new AuthError('response', t('error.unexpected')) return response.data.data as NonNullable['data']['data']> } export function requestInit(signal: AbortSignal): RequestInit & { signal: AbortSignal } { return { credentials: 'include', signal: AbortSignal.any([signal, AbortSignal.timeout(90_000)]) } } let proofQueue = Promise.resolve() export async function secured( payload: string, action: (proof: PowProofRequest, init: RequestInit) => Promise, signal: AbortSignal, ): Promise { const previous = proofQueue let release = () => {} proofQueue = new Promise((resolve) => { release = resolve }) await previous try { signal.throwIfAborted() const init = requestInit(signal) const challenge = await request(authChallenge(init)) const proof = await solve(challenge, payload, init.signal) return await action(proof, init) } finally { release() } }