import type { BatchPayload } from "../content/market/batch-payload"; import { isAuthResponseMessage } from "./auth-messages"; import { DEFAULT_BATCH_SUBMIT_BASE_URL } from "./batch-submit-config"; interface FetchResponseLike { json(): Promise; ok: boolean; status: number; } type FetchLike = ( input: string, init?: RequestInit ) => Promise; type GetAccessTokenLike = () => Promise; type SendMessageLike = (message: unknown) => Promise; export function createBatchSubmitClient(options: { baseUrl?: string; fetchImpl?: FetchLike; getAccessToken?: GetAccessTokenLike; sendMessage: SendMessageLike; }) { const baseUrl = options.baseUrl ?? DEFAULT_BATCH_SUBMIT_BASE_URL; const fetchImpl = options.fetchImpl ?? fetch; const getAccessToken = options.getAccessToken ?? (() => readAccessToken(options.sendMessage)); return { async submitBatch(payload: BatchPayload) { const token = await getAccessToken(); const response = await fetchImpl( buildBatchSubmitUrl(baseUrl), { body: JSON.stringify(payload), headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" }, method: "POST" } ); if (response.status === 401 || response.status === 403) { throw new Error("batch submit unauthorized"); } if (!response.ok) { throw new Error(`batch submit failed: ${response.status}`); } return readBatchSubmitResponse(await response.json()); } }; } export function buildBatchSubmitUrl(baseUrl: string): string { return new URL("/api/v1/batch-status/batches", baseUrl).toString(); } async function readAccessToken(sendMessage: SendMessageLike): Promise { const response = await sendMessage({ type: "auth:get-access-token" }); if ( !isAuthResponseMessage(response) || !response.ok || response.type !== "auth:token" || !response.value.accessToken.trim() ) { throw new Error("batch submit token unavailable"); } return response.value.accessToken; } function readBatchSubmitResponse(payload: unknown): unknown { if (!isRecord(payload)) { throw new Error("batch submit response is invalid"); } if (payload.success !== true) { const message = typeof payload.msg === "string" && payload.msg.trim() ? payload.msg : "batch submit failed"; throw new Error(message); } return "data" in payload ? payload.data : payload; } function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null; }