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;
|
2026-02-12 17:16:41 +09:00
|
|
|
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;
|
2026-02-12 17:16:41 +09:00
|
|
|
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,
|
2026-02-12 17:16:41 +09:00
|
|
|
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
|
|
|
};
|
|
|
|
|
|
2026-02-12 17:16:41 +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,
|
2026-02-12 17:16:41 +09:00
|
|
|
...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) =>
|
2026-02-12 17:16:41 +09:00
|
|
|
set((state) => ({
|
2026-02-10 11:16:39 +09:00
|
|
|
kisAccountNoInput: accountNo,
|
2026-02-12 17:16:41 +09:00
|
|
|
...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
|
|
|
}),
|
|
|
|
|
|
2026-02-12 17:16:41 +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",
|
|
|
|
|
storage: createJSONStorage(() => localStorage),
|
2026-02-12 10:24:03 +09:00
|
|
|
onRehydrateStorage: () => (state) => {
|
|
|
|
|
state?.setHasHydrated(true);
|
|
|
|
|
},
|
2026-02-06 17:50:35 +09:00
|
|
|
partialize: (state) => ({
|
|
|
|
|
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,
|
2026-02-12 17:16:41 +09:00
|
|
|
isKisProfileVerified: state.isKisProfileVerified,
|
|
|
|
|
verifiedAccountNo: state.verifiedAccountNo,
|
2026-02-06 17:50:35 +09:00
|
|
|
tradingEnv: state.tradingEnv,
|
2026-02-11 15:27:03 +09:00
|
|
|
// wsApprovalKey/wsUrl are kept in memory only (expiration-sensitive).
|
2026-02-06 17:50:35 +09:00
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
);
|