Files
auto-trade/features/settings/store/use-kis-runtime-store.ts

264 lines
7.3 KiB
TypeScript
Raw Normal View History

2026-02-06 17:50:35 +09:00
"use client";
2026-02-11 16:31:28 +09:00
import { fetchKisWebSocketApproval } from "@/features/settings/apis/kis-auth.api";
import type { KisTradingEnv } from "@/features/trade/types/trade.types";
2026-02-11 15:27:03 +09:00
import { createJSONStorage, persist } from "zustand/middleware";
import { create } from "zustand";
2026-02-06 17:50:35 +09:00
/**
2026-02-11 16:31:28 +09:00
* @file features/settings/store/use-kis-runtime-store.ts
2026-02-11 15:27:03 +09:00
* @description Stores KIS input, verification, and websocket connection state.
2026-02-11 16:31:28 +09:00
* @see features/trade/hooks/useKisTradeWebSocket.ts
2026-02-06 17:50:35 +09:00
*/
export interface KisRuntimeCredentials {
appKey: string;
appSecret: string;
tradingEnv: KisTradingEnv;
2026-02-10 11:16:39 +09:00
accountNo: string;
2026-02-06 17:50:35 +09:00
}
2026-02-11 15:27:03 +09:00
interface KisWsConnection {
approvalKey: string;
wsUrl: string;
}
2026-02-06 17:50:35 +09:00
interface KisRuntimeStoreState {
kisTradingEnvInput: KisTradingEnv;
kisAppKeyInput: string;
kisAppSecretInput: string;
2026-02-10 11:16:39 +09:00
kisAccountNoInput: string;
2026-02-06 17:50:35 +09:00
verifiedCredentials: KisRuntimeCredentials | null;
isKisVerified: boolean;
isKisProfileVerified: boolean;
verifiedAccountNo: string | null;
2026-02-06 17:50:35 +09:00
tradingEnv: KisTradingEnv;
2026-02-10 11:16:39 +09:00
wsApprovalKey: string | null;
2026-02-11 15:27:03 +09:00
wsUrl: string | null;
2026-02-12 10:24:03 +09:00
_hasHydrated: boolean;
2026-02-06 17:50:35 +09:00
}
interface KisRuntimeStoreActions {
setKisTradingEnvInput: (tradingEnv: KisTradingEnv) => void;
setKisAppKeyInput: (appKey: string) => void;
setKisAppSecretInput: (appSecret: string) => void;
2026-02-10 11:16:39 +09:00
setKisAccountNoInput: (accountNo: string) => void;
setVerifiedKisSession: (
credentials: KisRuntimeCredentials,
tradingEnv: KisTradingEnv,
) => void;
setVerifiedKisProfile: (profile: {
accountNo: string;
}) => void;
invalidateKisProfileVerification: () => void;
2026-02-06 17:50:35 +09:00
invalidateKisVerification: () => void;
clearKisRuntimeSession: (tradingEnv: KisTradingEnv) => void;
2026-02-11 15:27:03 +09:00
getOrFetchWsConnection: () => Promise<KisWsConnection | null>;
2026-02-13 12:17:35 +09:00
clearWsConnectionCache: () => void;
2026-02-12 10:24:03 +09:00
setHasHydrated: (state: boolean) => void;
2026-02-06 17:50:35 +09:00
}
const INITIAL_STATE: KisRuntimeStoreState = {
kisTradingEnvInput: "real",
kisAppKeyInput: "",
kisAppSecretInput: "",
2026-02-10 11:16:39 +09:00
kisAccountNoInput: "",
2026-02-06 17:50:35 +09:00
verifiedCredentials: null,
isKisVerified: false,
isKisProfileVerified: false,
verifiedAccountNo: null,
2026-02-06 17:50:35 +09:00
tradingEnv: "real",
2026-02-10 11:16:39 +09:00
wsApprovalKey: null,
2026-02-11 15:27:03 +09:00
wsUrl: null,
2026-02-12 10:24:03 +09:00
_hasHydrated: false,
2026-02-06 17:50:35 +09:00
};
const RESET_PROFILE_STATE = {
isKisProfileVerified: false,
verifiedAccountNo: null,
} as const;
2026-02-11 15:27:03 +09:00
const RESET_VERIFICATION_STATE = {
verifiedCredentials: null,
isKisVerified: false,
...RESET_PROFILE_STATE,
2026-02-11 15:27:03 +09:00
wsApprovalKey: null,
wsUrl: null,
};
2026-02-10 11:16:39 +09:00
2026-02-11 15:27:03 +09:00
let wsConnectionPromise: Promise<KisWsConnection | null> | null = null;
/**
* @description Runtime store for KIS session.
2026-02-11 16:31:28 +09:00
* @see features/settings/components/KisAuthForm.tsx
2026-02-11 15:27:03 +09:00
*/
2026-02-10 11:16:39 +09:00
export const useKisRuntimeStore = create<
KisRuntimeStoreState & KisRuntimeStoreActions
>()(
2026-02-06 17:50:35 +09:00
persist(
2026-02-10 11:16:39 +09:00
(set, get) => ({
2026-02-06 17:50:35 +09:00
...INITIAL_STATE,
setKisTradingEnvInput: (tradingEnv) =>
set({
kisTradingEnvInput: tradingEnv,
2026-02-11 15:27:03 +09:00
...RESET_VERIFICATION_STATE,
2026-02-06 17:50:35 +09:00
}),
setKisAppKeyInput: (appKey) =>
set({
kisAppKeyInput: appKey,
2026-02-11 15:27:03 +09:00
...RESET_VERIFICATION_STATE,
2026-02-06 17:50:35 +09:00
}),
setKisAppSecretInput: (appSecret) =>
set({
kisAppSecretInput: appSecret,
2026-02-11 15:27:03 +09:00
...RESET_VERIFICATION_STATE,
2026-02-10 11:16:39 +09:00
}),
setKisAccountNoInput: (accountNo) =>
set((state) => ({
2026-02-10 11:16:39 +09:00
kisAccountNoInput: accountNo,
...RESET_PROFILE_STATE,
verifiedCredentials: state.verifiedCredentials
? {
...state.verifiedCredentials,
accountNo: "",
}
: null,
})),
2026-02-06 17:50:35 +09:00
setVerifiedKisSession: (credentials, tradingEnv) =>
set({
verifiedCredentials: credentials,
isKisVerified: true,
tradingEnv,
2026-02-10 11:16:39 +09:00
wsApprovalKey: null,
2026-02-11 15:27:03 +09:00
wsUrl: null,
2026-02-06 17:50:35 +09:00
}),
setVerifiedKisProfile: ({ accountNo }) =>
set((state) => ({
isKisProfileVerified: true,
verifiedAccountNo: accountNo,
verifiedCredentials: state.verifiedCredentials
? {
...state.verifiedCredentials,
accountNo,
}
: state.verifiedCredentials,
wsApprovalKey: null,
wsUrl: null,
})),
invalidateKisProfileVerification: () =>
set((state) => ({
...RESET_PROFILE_STATE,
verifiedCredentials: state.verifiedCredentials
? {
...state.verifiedCredentials,
accountNo: "",
}
: state.verifiedCredentials,
wsApprovalKey: null,
wsUrl: null,
})),
2026-02-06 17:50:35 +09:00
invalidateKisVerification: () =>
set({
2026-02-11 15:27:03 +09:00
...RESET_VERIFICATION_STATE,
2026-02-06 17:50:35 +09:00
}),
clearKisRuntimeSession: (tradingEnv) =>
set({
kisTradingEnvInput: tradingEnv,
kisAppKeyInput: "",
kisAppSecretInput: "",
2026-02-10 11:16:39 +09:00
kisAccountNoInput: "",
2026-02-11 15:27:03 +09:00
...RESET_VERIFICATION_STATE,
2026-02-06 17:50:35 +09:00
tradingEnv,
}),
2026-02-10 11:16:39 +09:00
2026-02-11 15:27:03 +09:00
getOrFetchWsConnection: async () => {
const { wsApprovalKey, wsUrl, verifiedCredentials } = get();
2026-02-10 11:16:39 +09:00
2026-02-11 15:27:03 +09:00
if (wsApprovalKey && wsUrl) {
return { approvalKey: wsApprovalKey, wsUrl };
2026-02-10 11:16:39 +09:00
}
if (!verifiedCredentials) {
return null;
}
2026-02-11 15:27:03 +09:00
if (wsConnectionPromise) {
return wsConnectionPromise;
2026-02-10 11:16:39 +09:00
}
2026-02-11 15:27:03 +09:00
wsConnectionPromise = (async () => {
2026-02-10 11:16:39 +09:00
try {
const data = await fetchKisWebSocketApproval(verifiedCredentials);
2026-02-11 15:27:03 +09:00
if (!data.approvalKey || !data.wsUrl) {
return null;
2026-02-10 11:16:39 +09:00
}
2026-02-11 15:27:03 +09:00
const nextConnection = {
approvalKey: data.approvalKey,
wsUrl: data.wsUrl,
} satisfies KisWsConnection;
set({
wsApprovalKey: nextConnection.approvalKey,
wsUrl: nextConnection.wsUrl,
});
return nextConnection;
2026-02-10 11:16:39 +09:00
} catch (error) {
console.error(error);
return null;
} finally {
2026-02-11 15:27:03 +09:00
wsConnectionPromise = null;
2026-02-10 11:16:39 +09:00
}
})();
2026-02-11 15:27:03 +09:00
return wsConnectionPromise;
2026-02-10 11:16:39 +09:00
},
2026-02-13 12:17:35 +09:00
clearWsConnectionCache: () => {
wsConnectionPromise = null;
set({
wsApprovalKey: null,
wsUrl: null,
});
},
2026-02-12 10:24:03 +09:00
setHasHydrated: (state) => {
set({
_hasHydrated: state,
});
},
2026-02-06 17:50:35 +09:00
}),
{
name: "autotrade-kis-runtime-store",
2026-02-26 09:05:17 +09:00
// 민감정보(appKey/appSecret/accountNo)는 브라우저 세션 범위로만 유지합니다.
storage: createJSONStorage(() => sessionStorage),
2026-02-12 10:24:03 +09:00
onRehydrateStorage: () => (state) => {
state?.setHasHydrated(true);
},
2026-02-06 17:50:35 +09:00
partialize: (state) => ({
2026-02-26 09:05:17 +09:00
// 새로고침 시 인증이 풀리지 않도록, "세션 범위"에서만 인증/입력 상태를 유지합니다.
// 브라우저 종료 시 sessionStorage가 비워지므로 장기 영속(localStorage)은 하지 않습니다.
2026-02-06 17:50:35 +09:00
kisTradingEnvInput: state.kisTradingEnvInput,
kisAppKeyInput: state.kisAppKeyInput,
kisAppSecretInput: state.kisAppSecretInput,
2026-02-10 11:16:39 +09:00
kisAccountNoInput: state.kisAccountNoInput,
2026-02-06 17:50:35 +09:00
verifiedCredentials: state.verifiedCredentials,
isKisVerified: state.isKisVerified,
isKisProfileVerified: state.isKisProfileVerified,
verifiedAccountNo: state.verifiedAccountNo,
2026-02-06 17:50:35 +09:00
tradingEnv: state.tradingEnv,
}),
},
),
);