import type { KisRuntimeCredentials } from "@/features/dashboard/store/use-kis-runtime-store"; import type { DashboardChartTimeframe, DashboardStockCashOrderRequest, DashboardStockCashOrderResponse, DashboardStockChartResponse, DashboardStockOrderBookResponse, DashboardStockOverviewResponse, DashboardStockSearchResponse, } from "@/features/dashboard/types/dashboard.types"; import { DOMESTIC_KIS_SESSION_OVERRIDE_HEADER, DOMESTIC_KIS_SESSION_OVERRIDE_STORAGE_KEY, parseDomesticKisSession, } from "@/lib/kis/domestic-market-session"; /** * 종목 검색 API 호출 * @param keyword 검색어 */ export async function fetchStockSearch( keyword: string, signal?: AbortSignal, ): Promise { const response = await fetch( `/api/kis/domestic/search?q=${encodeURIComponent(keyword)}`, { cache: "no-store", signal, }, ); const payload = (await response.json()) as | DashboardStockSearchResponse | { error?: string }; if (!response.ok) { throw new Error( "error" in payload ? payload.error : "종목 검색 중 오류가 발생했습니다.", ); } return payload as DashboardStockSearchResponse; } /** * 종목 상세 개요 조회 API 호출 * @param symbol 종목코드 * @param credentials KIS 인증 정보 */ export async function fetchStockOverview( symbol: string, credentials: KisRuntimeCredentials, ): Promise { const response = await fetch( `/api/kis/domestic/overview?symbol=${encodeURIComponent(symbol)}`, { method: "GET", headers: buildKisRequestHeaders(credentials), cache: "no-store", }, ); const payload = (await response.json()) as | DashboardStockOverviewResponse | { error?: string }; if (!response.ok) { throw new Error( "error" in payload ? payload.error : "종목 조회 중 오류가 발생했습니다.", ); } return payload as DashboardStockOverviewResponse; } /** * 종목 호가 조회 API 호출 * @param symbol 종목코드 * @param credentials KIS 인증 정보 */ export async function fetchStockOrderBook( symbol: string, credentials: KisRuntimeCredentials, signal?: AbortSignal, ): Promise { const response = await fetch( `/api/kis/domestic/orderbook?symbol=${encodeURIComponent(symbol)}`, { method: "GET", headers: buildKisRequestHeaders(credentials), cache: "no-store", signal, }, ); const payload = (await response.json()) as | DashboardStockOrderBookResponse | { error?: string }; if (!response.ok) { throw new Error( "error" in payload ? payload.error : "호가 조회 중 오류가 발생했습니다.", ); } return payload as DashboardStockOrderBookResponse; } /** * 종목 차트(분봉/일봉/주봉) 조회 API 호출 */ export async function fetchStockChart( symbol: string, timeframe: DashboardChartTimeframe, credentials: KisRuntimeCredentials, cursor?: string, ): Promise { const query = new URLSearchParams({ symbol, timeframe, }); if (cursor) query.set("cursor", cursor); const response = await fetch(`/api/kis/domestic/chart?${query.toString()}`, { method: "GET", headers: buildKisRequestHeaders(credentials), cache: "no-store", }); const payload = (await response.json()) as | DashboardStockChartResponse | { error?: string }; if (!response.ok) { throw new Error( "error" in payload ? payload.error : "차트 조회 중 오류가 발생했습니다.", ); } return payload as DashboardStockChartResponse; } /** * 주식 현금 주문 API 호출 * @param request 주문 요청 데이터 * @param credentials KIS 인증 정보 */ export async function fetchOrderCash( request: DashboardStockCashOrderRequest, credentials: KisRuntimeCredentials, ): Promise { const response = await fetch("/api/kis/domestic/order-cash", { method: "POST", headers: buildKisRequestHeaders(credentials, { jsonContentType: true }), body: JSON.stringify(request), cache: "no-store", }); const payload = (await response.json()) as DashboardStockCashOrderResponse; if (!response.ok) { throw new Error(payload.message || "주문 전송 중 오류가 발생했습니다."); } return payload; } function buildKisRequestHeaders( credentials: KisRuntimeCredentials, options?: { jsonContentType?: boolean }, ) { const headers: Record = { "x-kis-app-key": credentials.appKey, "x-kis-app-secret": credentials.appSecret, "x-kis-trading-env": credentials.tradingEnv, }; if (options?.jsonContentType) { headers["content-type"] = "application/json"; } const sessionOverride = readSessionOverrideForDev(); if (sessionOverride) { headers[DOMESTIC_KIS_SESSION_OVERRIDE_HEADER] = sessionOverride; } return headers; } function readSessionOverrideForDev() { if (typeof window === "undefined") return null; try { const raw = window.localStorage.getItem( DOMESTIC_KIS_SESSION_OVERRIDE_STORAGE_KEY, ); return parseDomesticKisSession(raw); } catch { return null; } }