153 lines
4.1 KiB
TypeScript
153 lines
4.1 KiB
TypeScript
import type { KisCredentialInput } from "@/lib/kis/config";
|
|
import { getKisConfig } from "@/lib/kis/config";
|
|
import { getKisAccessToken } from "@/lib/kis/token";
|
|
|
|
/**
|
|
* @file lib/kis/client.ts
|
|
* @description KIS REST 공통 클라이언트(실전/모의 공통)
|
|
*/
|
|
|
|
export interface KisApiEnvelope<TOutput> {
|
|
rt_cd?: string;
|
|
msg_cd?: string;
|
|
msg1?: string;
|
|
output?: TOutput;
|
|
output1?: unknown;
|
|
output2?: unknown;
|
|
}
|
|
|
|
/**
|
|
* KIS GET 호출
|
|
* @param apiPath REST 경로
|
|
* @param trId KIS TR ID
|
|
* @param params 쿼리 파라미터
|
|
* @param credentials 사용자 입력 키(선택)
|
|
* @returns KIS 원본 응답
|
|
* @see lib/kis/domestic.ts getDomesticQuote/getDomesticDailyPrice - 대시보드 시세 데이터 소스
|
|
*/
|
|
export async function kisGet<TOutput>(
|
|
apiPath: string,
|
|
trId: string,
|
|
params: Record<string, string>,
|
|
credentials?: KisCredentialInput,
|
|
): Promise<KisApiEnvelope<TOutput>> {
|
|
const config = getKisConfig(credentials);
|
|
const token = await getKisAccessToken(credentials);
|
|
|
|
const url = new URL(apiPath, config.baseUrl);
|
|
Object.entries(params).forEach(([key, value]) => {
|
|
if (value != null) url.searchParams.set(key, value);
|
|
});
|
|
|
|
const response = await fetch(url.toString(), {
|
|
method: "GET",
|
|
headers: {
|
|
"content-type": "application/json; charset=utf-8",
|
|
authorization: `Bearer ${token}`,
|
|
appkey: config.appKey,
|
|
appsecret: config.appSecret,
|
|
tr_id: trId,
|
|
tr_cont: "",
|
|
custtype: "P",
|
|
},
|
|
cache: "no-store",
|
|
});
|
|
|
|
const rawText = await response.text();
|
|
const payload = tryParseKisEnvelope<TOutput>(rawText);
|
|
|
|
if (!response.ok) {
|
|
const detail = payload.msg1 || rawText.slice(0, 200);
|
|
throw new Error(
|
|
detail
|
|
? `KIS API 요청 실패 (${response.status}): ${detail}`
|
|
: `KIS API 요청 실패 (${response.status})`,
|
|
);
|
|
}
|
|
|
|
if (payload.rt_cd && payload.rt_cd !== "0") {
|
|
const detail = [payload.msg1, payload.msg_cd].filter(Boolean).join(" / ");
|
|
throw new Error(detail || "KIS API 비즈니스 오류가 발생했습니다.");
|
|
}
|
|
|
|
return payload;
|
|
}
|
|
|
|
/**
|
|
* KIS POST 호출 (주문 등)
|
|
* @param apiPath REST 경로
|
|
* @param trId KIS TR ID
|
|
* @param body 요청 본문
|
|
* @param credentials 사용자 입력 키(선택)
|
|
* @returns KIS 원본 응답
|
|
*/
|
|
export async function kisPost<TOutput>(
|
|
apiPath: string,
|
|
trId: string,
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
body: Record<string, any>,
|
|
credentials?: KisCredentialInput,
|
|
): Promise<KisApiEnvelope<TOutput>> {
|
|
const config = getKisConfig(credentials);
|
|
const token = await getKisAccessToken(credentials);
|
|
|
|
const url = new URL(apiPath, config.baseUrl);
|
|
|
|
const response = await fetch(url.toString(), {
|
|
method: "POST",
|
|
headers: {
|
|
"content-type": "application/json; charset=utf-8",
|
|
authorization: `Bearer ${token}`,
|
|
appkey: config.appKey,
|
|
appsecret: config.appSecret,
|
|
tr_id: trId,
|
|
tr_cont: "",
|
|
custtype: "P",
|
|
},
|
|
body: JSON.stringify(body),
|
|
cache: "no-store",
|
|
});
|
|
|
|
const rawText = await response.text();
|
|
const payload = tryParseKisEnvelope<TOutput>(rawText);
|
|
|
|
if (!response.ok) {
|
|
const detail = payload.msg1 || rawText.slice(0, 200);
|
|
throw new Error(
|
|
detail
|
|
? `KIS API 요청 실패 (${response.status}): ${detail}`
|
|
: `KIS API 요청 실패 (${response.status})`,
|
|
);
|
|
}
|
|
|
|
if (payload.rt_cd && payload.rt_cd !== "0") {
|
|
const detail = [payload.msg1, payload.msg_cd].filter(Boolean).join(" / ");
|
|
throw new Error(detail || "KIS API 비즈니스 오류가 발생했습니다.");
|
|
}
|
|
|
|
return payload;
|
|
}
|
|
|
|
/**
|
|
* KIS 응답을 안전하게 JSON으로 파싱합니다.
|
|
* @param rawText fetch 응답 원문
|
|
* @returns KisApiEnvelope
|
|
* @see lib/kis/token.ts tryParseTokenResponse - 토큰 발급 응답 파싱 방식과 동일 패턴
|
|
*/
|
|
function tryParseKisEnvelope<TOutput>(
|
|
rawText: string,
|
|
): KisApiEnvelope<TOutput> {
|
|
try {
|
|
return JSON.parse(rawText) as KisApiEnvelope<TOutput>;
|
|
} catch {
|
|
return {
|
|
msg1: rawText.slice(0, 200),
|
|
};
|
|
}
|
|
}
|
|
|
|
// 하위 호환(alias)
|
|
// 하위 호환(alias)
|
|
export const kisMockGet = kisGet;
|
|
export const kisMockPost = kisPost;
|