2025-09-18 16:23:56 +09:00
|
|
|
import createOpenApiClient from "openapi-fetch";
|
2025-09-17 18:43:43 +09:00
|
|
|
import type { paths } from "../__generated__/types";
|
|
|
|
|
|
2025-09-19 12:57:39 +09:00
|
|
|
export class ApiError extends Error {
|
|
|
|
|
constructor(
|
|
|
|
|
message: string,
|
|
|
|
|
public readonly response: Response,
|
|
|
|
|
public readonly body?: unknown
|
|
|
|
|
) {
|
|
|
|
|
super(message);
|
|
|
|
|
this.name = "ApiError";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-18 16:23:56 +09:00
|
|
|
export type ApiClient = ReturnType<typeof createOpenApiClient<paths>>;
|
2025-09-17 18:43:43 +09:00
|
|
|
|
2025-09-18 16:23:56 +09:00
|
|
|
export type AuthHeaderResolver = () => string | undefined;
|
|
|
|
|
|
|
|
|
|
export interface CreateClientOptions {
|
|
|
|
|
getAuthHeader?: AuthHeaderResolver;
|
2025-09-19 12:57:39 +09:00
|
|
|
handleError?: (response: Response) => void | Promise<void>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function defaultHandleError(response: Response) {
|
|
|
|
|
if (response.ok) return;
|
|
|
|
|
|
|
|
|
|
let body: unknown;
|
|
|
|
|
let message = response.statusText || `Request failed with status ${response.status}`;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const cloned = response.clone();
|
|
|
|
|
const contentType = cloned.headers.get("content-type");
|
|
|
|
|
if (contentType?.includes("application/json")) {
|
|
|
|
|
body = await cloned.json();
|
|
|
|
|
if (body && typeof body === "object" && "message" in body && typeof (body as any).message === "string") {
|
|
|
|
|
message = (body as any).message;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
const text = await cloned.text();
|
|
|
|
|
if (text) {
|
|
|
|
|
body = text;
|
|
|
|
|
message = text;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
|
|
|
|
// Ignore body parse errors; fall back to status text
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
throw new ApiError(message, response, body);
|
2025-09-17 18:43:43 +09:00
|
|
|
}
|
|
|
|
|
|
2025-09-18 16:23:56 +09:00
|
|
|
export function createClient(
|
|
|
|
|
baseUrl: string,
|
|
|
|
|
options: CreateClientOptions = {}
|
|
|
|
|
): ApiClient {
|
2025-09-19 12:57:39 +09:00
|
|
|
const client = createOpenApiClient<paths>({ baseUrl });
|
2025-09-18 16:23:56 +09:00
|
|
|
|
2025-09-19 12:57:39 +09:00
|
|
|
const handleError = options.handleError ?? defaultHandleError;
|
2025-09-18 16:00:20 +09:00
|
|
|
|
2025-09-19 12:57:39 +09:00
|
|
|
if (typeof client.use === "function") {
|
2025-09-18 16:23:56 +09:00
|
|
|
const resolveAuthHeader = options.getAuthHeader;
|
|
|
|
|
|
|
|
|
|
client.use({
|
2025-09-19 12:57:39 +09:00
|
|
|
onRequest({ request }) {
|
|
|
|
|
if (!resolveAuthHeader) return;
|
|
|
|
|
if (!request || typeof request.headers?.has !== "function") return;
|
|
|
|
|
if (request.headers.has("Authorization")) return;
|
2025-09-17 18:43:43 +09:00
|
|
|
|
2025-09-18 16:23:56 +09:00
|
|
|
const headerValue = resolveAuthHeader();
|
2025-09-19 12:57:39 +09:00
|
|
|
if (!headerValue) return;
|
2025-09-18 16:23:56 +09:00
|
|
|
|
|
|
|
|
request.headers.set("Authorization", headerValue);
|
|
|
|
|
},
|
2025-09-19 12:57:39 +09:00
|
|
|
async onResponse({ response }) {
|
|
|
|
|
await handleError(response);
|
|
|
|
|
},
|
2025-09-18 16:23:56 +09:00
|
|
|
} as never);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return client;
|
|
|
|
|
}
|
2025-09-19 12:57:39 +09:00
|
|
|
|
|
|
|
|
export type { paths };
|