스킬 정리 및 리팩토링
This commit is contained in:
82
features/settings/apis/kis-api-utils.ts
Normal file
82
features/settings/apis/kis-api-utils.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import type { KisRuntimeCredentials } from "@/features/settings/store/use-kis-runtime-store";
|
||||
import {
|
||||
DOMESTIC_KIS_SESSION_OVERRIDE_HEADER,
|
||||
DOMESTIC_KIS_SESSION_OVERRIDE_STORAGE_KEY,
|
||||
parseDomesticKisSession,
|
||||
} from "@/lib/kis/domestic-market-session";
|
||||
|
||||
export interface KisApiErrorPayload {
|
||||
ok?: boolean;
|
||||
message?: string;
|
||||
error?: string;
|
||||
errorCode?: string;
|
||||
}
|
||||
|
||||
interface BuildKisRequestHeadersOptions {
|
||||
jsonContentType?: boolean;
|
||||
includeAccountNo?: boolean;
|
||||
includeSessionOverride?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description KIS API 응답에서 사용자 노출용 에러 메시지를 추출합니다.
|
||||
* @see features/trade/apis/kis-stock.api.ts 종목/주문 API 실패 처리
|
||||
* @see features/dashboard/apis/dashboard.api.ts 대시보드 API 실패 처리
|
||||
*/
|
||||
export function resolveKisApiErrorMessage(
|
||||
payload: unknown,
|
||||
fallbackMessage: string,
|
||||
) {
|
||||
if (!payload || typeof payload !== "object") {
|
||||
return fallbackMessage;
|
||||
}
|
||||
|
||||
const response = payload as KisApiErrorPayload;
|
||||
return response.message || response.error || fallbackMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description KIS API 호출용 공통 헤더를 생성합니다.
|
||||
* @see features/dashboard/apis/dashboard.api.ts 잔고/지수/활동 조회 공통 헤더
|
||||
* @see features/trade/apis/kis-stock.api.ts 종목/호가/차트/주문 공통 헤더
|
||||
*/
|
||||
export function buildKisRequestHeaders(
|
||||
credentials: KisRuntimeCredentials,
|
||||
options?: BuildKisRequestHeadersOptions,
|
||||
) {
|
||||
const headers: Record<string, string> = {
|
||||
"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";
|
||||
}
|
||||
|
||||
if (options?.includeAccountNo && credentials.accountNo.trim()) {
|
||||
headers["x-kis-account-no"] = credentials.accountNo.trim();
|
||||
}
|
||||
|
||||
if (options?.includeSessionOverride) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,8 @@
|
||||
import type { KisRuntimeCredentials } from "@/features/settings/store/use-kis-runtime-store";
|
||||
import {
|
||||
resolveKisApiErrorMessage,
|
||||
type KisApiErrorPayload,
|
||||
} from "@/features/settings/apis/kis-api-utils";
|
||||
import type {
|
||||
DashboardKisProfileValidateResponse,
|
||||
DashboardKisRevokeResponse,
|
||||
@@ -25,13 +29,13 @@ async function postKisAuthApi<T extends KisApiBaseResponse>(
|
||||
cache: "no-store",
|
||||
});
|
||||
|
||||
const payload = (await response.json()) as T;
|
||||
const payload = (await response.json()) as T | KisApiErrorPayload;
|
||||
|
||||
if (!response.ok || !payload.ok) {
|
||||
throw new Error(payload.message || fallbackErrorMessage);
|
||||
throw new Error(resolveKisApiErrorMessage(payload, fallbackErrorMessage));
|
||||
}
|
||||
|
||||
return payload;
|
||||
return payload as T;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -240,11 +240,14 @@ export const useKisRuntimeStore = create<
|
||||
}),
|
||||
{
|
||||
name: "autotrade-kis-runtime-store",
|
||||
storage: createJSONStorage(() => localStorage),
|
||||
// 민감정보(appKey/appSecret/accountNo)는 브라우저 세션 범위로만 유지합니다.
|
||||
storage: createJSONStorage(() => sessionStorage),
|
||||
onRehydrateStorage: () => (state) => {
|
||||
state?.setHasHydrated(true);
|
||||
},
|
||||
partialize: (state) => ({
|
||||
// 새로고침 시 인증이 풀리지 않도록, "세션 범위"에서만 인증/입력 상태를 유지합니다.
|
||||
// 브라우저 종료 시 sessionStorage가 비워지므로 장기 영속(localStorage)은 하지 않습니다.
|
||||
kisTradingEnvInput: state.kisTradingEnvInput,
|
||||
kisAppKeyInput: state.kisAppKeyInput,
|
||||
kisAppSecretInput: state.kisAppSecretInput,
|
||||
@@ -254,7 +257,6 @@ export const useKisRuntimeStore = create<
|
||||
isKisProfileVerified: state.isKisProfileVerified,
|
||||
verifiedAccountNo: state.verifiedAccountNo,
|
||||
tradingEnv: state.tradingEnv,
|
||||
// wsApprovalKey/wsUrl are kept in memory only (expiration-sensitive).
|
||||
}),
|
||||
},
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user