o2o-infinith-frontend/src/shared/api/api.ts

57 lines
2.2 KiB
TypeScript

/**
* orval이 생성하는 API 클라이언트의 HTTP 어댑터.
*
* - `kyInstance`: 공통 타임아웃·리트라이·훅(인증 토큰 주입, 401 처리 등 추후 추가) 설정
* - `customFetcher`: orval `httpClient: 'fetch'` 모드의 custom mutator.
* 생성된 코드가 `customFetcher(url, init)`로 호출하면 ky로 요청을 보내고,
* 생성 타입이 기대하는 `{ status, data, headers }` 형태로 응답을 정규화함.
*/
import ky, { type KyInstance } from 'ky'
// import.meta 를 직접 쓰지 않는 이유: orval 이 mutator 를 cjs 로 esbuild 번들하는 과정에서
// `import.meta is not available in cjs` 경고가 떠 SDK 재생성 로그가 시끄럽다.
// 대신 vite.config.ts 의 `define` 으로 빌드 타임 치환되는 글로벌을 사용한다.
const API_BASE_URL = (__VITE_API_BASE_URL__ ?? '').replace(/\/$/, '')
export const kyInstance: KyInstance = ky.create({
timeout: 60_000,
retry: 1,
// orval generated 타입이 4xx/5xx 응답도 data로 받아오므로 throw 비활성
throwHttpErrors: false,
hooks: {
beforeRequest: [
(request) => {
const apiKey = __VITE_API_KEY__;
if (apiKey) request.headers.set('x-api-key', apiKey);
},
// TODO: 인증 토큰 주입
// request => {
// const token = localStorage.getItem('token')
// if (token) request.headers.set('Authorization', `Bearer ${token}`)
// },
],
afterResponse: [
// TODO: 401 처리, 토큰 갱신 등
],
},
})
export const customFetcher = async <T>(
url: string,
init?: RequestInit,
): Promise<T> => {
// orval 이 생성하는 url 은 `/api/...` 상대경로라서, 그대로 두면 dev 서버 origin 으로 감.
// VITE_API_BASE_URL 이 있으면 절대 URL 로 만들어서 백엔드로 직접 요청.
const fullUrl = /^https?:\/\//.test(url) ? url : `${API_BASE_URL}${url}`
const response = await kyInstance(fullUrl, init as Parameters<KyInstance>[1])
let data: unknown = null
try {
data = await response.json()
} catch {
// 빈 응답이거나 JSON 파싱 실패
}
return { status: response.status, data, headers: response.headers } as T
}
export default customFetcher