디자인 변경
This commit is contained in:
@@ -1,15 +1,15 @@
|
||||
"use client";
|
||||
|
||||
import { create } from "zustand";
|
||||
import { createJSONStorage, persist } from "zustand/middleware";
|
||||
import type { KisTradingEnv } from "@/features/dashboard/types/dashboard.types";
|
||||
import { fetchKisWebSocketApproval } from "@/features/dashboard/apis/kis-auth.api";
|
||||
import type { KisTradingEnv } from "@/features/dashboard/types/dashboard.types";
|
||||
import { createJSONStorage, persist } from "zustand/middleware";
|
||||
import { create } from "zustand";
|
||||
|
||||
/**
|
||||
* @file features/dashboard/store/use-kis-runtime-store.ts
|
||||
* @description KIS 키 입력/검증 상태를 zustand로 관리하고 새로고침 시 복원합니다.
|
||||
* @description Stores KIS input, verification, and websocket connection state.
|
||||
* @see features/dashboard/hooks/useKisTradeWebSocket.ts
|
||||
*/
|
||||
|
||||
export interface KisRuntimeCredentials {
|
||||
appKey: string;
|
||||
appSecret: string;
|
||||
@@ -17,73 +17,37 @@ export interface KisRuntimeCredentials {
|
||||
accountNo: string;
|
||||
}
|
||||
|
||||
interface KisWsConnection {
|
||||
approvalKey: string;
|
||||
wsUrl: string;
|
||||
}
|
||||
|
||||
interface KisRuntimeStoreState {
|
||||
// [State] 입력 폼 상태
|
||||
kisTradingEnvInput: KisTradingEnv;
|
||||
kisAppKeyInput: string;
|
||||
kisAppSecretInput: string;
|
||||
kisAccountNoInput: string;
|
||||
|
||||
// [State] 검증/연동 상태
|
||||
verifiedCredentials: KisRuntimeCredentials | null;
|
||||
isKisVerified: boolean;
|
||||
tradingEnv: KisTradingEnv;
|
||||
|
||||
// [State] 웹소켓 승인키
|
||||
wsApprovalKey: string | null;
|
||||
wsUrl: string | null;
|
||||
}
|
||||
|
||||
interface KisRuntimeStoreActions {
|
||||
/**
|
||||
* 거래 모드 입력값을 변경하고 기존 검증 상태를 무효화합니다.
|
||||
* @param tradingEnv 거래 모드
|
||||
* @see features/dashboard/components/dashboard-main.tsx 거래 모드 버튼 onClick 이벤트
|
||||
*/
|
||||
setKisTradingEnvInput: (tradingEnv: KisTradingEnv) => void;
|
||||
/**
|
||||
* 앱 키 입력값을 변경하고 기존 검증 상태를 무효화합니다.
|
||||
* @param appKey 앱 키
|
||||
* @see features/dashboard/components/dashboard-main.tsx App Key onChange 이벤트
|
||||
*/
|
||||
setKisAppKeyInput: (appKey: string) => void;
|
||||
/**
|
||||
* 앱 시크릿 입력값을 변경하고 기존 검증 상태를 무효화합니다.
|
||||
* @param appSecret 앱 시크릿
|
||||
* @see features/dashboard/components/dashboard-main.tsx App Secret onChange 이벤트
|
||||
*/
|
||||
setKisAppSecretInput: (appSecret: string) => void;
|
||||
/**
|
||||
* 계좌번호 입력값을 변경하고 기존 검증 상태를 무효화합니다.
|
||||
* @param accountNo 계좌번호 (8자리-2자리)
|
||||
*/
|
||||
setKisAccountNoInput: (accountNo: string) => void;
|
||||
/**
|
||||
* 검증 성공 상태를 저장합니다.
|
||||
* @param credentials 검증 완료된 키
|
||||
* @param tradingEnv 현재 연동 모드
|
||||
* @see features/dashboard/components/dashboard-main.tsx handleValidateKis
|
||||
*/
|
||||
setVerifiedKisSession: (
|
||||
credentials: KisRuntimeCredentials,
|
||||
tradingEnv: KisTradingEnv,
|
||||
) => void;
|
||||
/**
|
||||
* 검증 실패 또는 입력 변경 시 검증 상태만 초기화합니다.
|
||||
* @see features/dashboard/components/dashboard-main.tsx handleValidateKis catch
|
||||
*/
|
||||
invalidateKisVerification: () => void;
|
||||
/**
|
||||
* 접근 폐기 시 입력값/검증값을 모두 제거합니다.
|
||||
* @param tradingEnv 표시용 모드
|
||||
* @see features/dashboard/components/dashboard-main.tsx handleRevokeKis
|
||||
*/
|
||||
clearKisRuntimeSession: (tradingEnv: KisTradingEnv) => void;
|
||||
|
||||
/**
|
||||
* 웹소켓 승인키를 가져오거나 없으면 발급받습니다.
|
||||
* @returns approvalKey
|
||||
*/
|
||||
getOrFetchApprovalKey: () => Promise<string | null>;
|
||||
getOrFetchWsConnection: () => Promise<KisWsConnection | null>;
|
||||
}
|
||||
|
||||
const INITIAL_STATE: KisRuntimeStoreState = {
|
||||
@@ -95,11 +59,22 @@ const INITIAL_STATE: KisRuntimeStoreState = {
|
||||
isKisVerified: false,
|
||||
tradingEnv: "real",
|
||||
wsApprovalKey: null,
|
||||
wsUrl: null,
|
||||
};
|
||||
|
||||
// 동시 요청 방지를 위한 모듈 스코프 변수
|
||||
let approvalPromise: Promise<string | null> | null = null;
|
||||
const RESET_VERIFICATION_STATE = {
|
||||
verifiedCredentials: null,
|
||||
isKisVerified: false,
|
||||
wsApprovalKey: null,
|
||||
wsUrl: null,
|
||||
};
|
||||
|
||||
let wsConnectionPromise: Promise<KisWsConnection | null> | null = null;
|
||||
|
||||
/**
|
||||
* @description Runtime store for KIS session.
|
||||
* @see features/dashboard/components/auth/KisAuthForm.tsx
|
||||
*/
|
||||
export const useKisRuntimeStore = create<
|
||||
KisRuntimeStoreState & KisRuntimeStoreActions
|
||||
>()(
|
||||
@@ -110,33 +85,25 @@ export const useKisRuntimeStore = create<
|
||||
setKisTradingEnvInput: (tradingEnv) =>
|
||||
set({
|
||||
kisTradingEnvInput: tradingEnv,
|
||||
verifiedCredentials: null,
|
||||
isKisVerified: false,
|
||||
wsApprovalKey: null,
|
||||
...RESET_VERIFICATION_STATE,
|
||||
}),
|
||||
|
||||
setKisAppKeyInput: (appKey) =>
|
||||
set({
|
||||
kisAppKeyInput: appKey,
|
||||
verifiedCredentials: null,
|
||||
isKisVerified: false,
|
||||
wsApprovalKey: null,
|
||||
...RESET_VERIFICATION_STATE,
|
||||
}),
|
||||
|
||||
setKisAppSecretInput: (appSecret) =>
|
||||
set({
|
||||
kisAppSecretInput: appSecret,
|
||||
verifiedCredentials: null,
|
||||
isKisVerified: false,
|
||||
wsApprovalKey: null,
|
||||
...RESET_VERIFICATION_STATE,
|
||||
}),
|
||||
|
||||
setKisAccountNoInput: (accountNo) =>
|
||||
set({
|
||||
kisAccountNoInput: accountNo,
|
||||
verifiedCredentials: null,
|
||||
isKisVerified: false,
|
||||
wsApprovalKey: null,
|
||||
...RESET_VERIFICATION_STATE,
|
||||
}),
|
||||
|
||||
setVerifiedKisSession: (credentials, tradingEnv) =>
|
||||
@@ -144,15 +111,13 @@ export const useKisRuntimeStore = create<
|
||||
verifiedCredentials: credentials,
|
||||
isKisVerified: true,
|
||||
tradingEnv,
|
||||
// 인증이 바뀌면 승인키도 초기화
|
||||
wsApprovalKey: null,
|
||||
wsUrl: null,
|
||||
}),
|
||||
|
||||
invalidateKisVerification: () =>
|
||||
set({
|
||||
verifiedCredentials: null,
|
||||
isKisVerified: false,
|
||||
wsApprovalKey: null,
|
||||
...RESET_VERIFICATION_STATE,
|
||||
}),
|
||||
|
||||
clearKisRuntimeSession: (tradingEnv) =>
|
||||
@@ -161,48 +126,52 @@ export const useKisRuntimeStore = create<
|
||||
kisAppKeyInput: "",
|
||||
kisAppSecretInput: "",
|
||||
kisAccountNoInput: "",
|
||||
verifiedCredentials: null,
|
||||
isKisVerified: false,
|
||||
...RESET_VERIFICATION_STATE,
|
||||
tradingEnv,
|
||||
wsApprovalKey: null,
|
||||
}),
|
||||
|
||||
getOrFetchApprovalKey: async () => {
|
||||
const { wsApprovalKey, verifiedCredentials } = get();
|
||||
getOrFetchWsConnection: async () => {
|
||||
const { wsApprovalKey, wsUrl, verifiedCredentials } = get();
|
||||
|
||||
// 1. 이미 키가 있으면 반환
|
||||
if (wsApprovalKey) {
|
||||
return wsApprovalKey;
|
||||
if (wsApprovalKey && wsUrl) {
|
||||
return { approvalKey: wsApprovalKey, wsUrl };
|
||||
}
|
||||
|
||||
// 2. 인증 정보가 없으면 실패
|
||||
if (!verifiedCredentials) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 3. 이미 진행 중인 요청이 있다면 해당 Promise 반환 (Deduping)
|
||||
if (approvalPromise) {
|
||||
return approvalPromise;
|
||||
if (wsConnectionPromise) {
|
||||
return wsConnectionPromise;
|
||||
}
|
||||
|
||||
// 4. API 호출
|
||||
approvalPromise = (async () => {
|
||||
wsConnectionPromise = (async () => {
|
||||
try {
|
||||
const data = await fetchKisWebSocketApproval(verifiedCredentials);
|
||||
if (data.approvalKey) {
|
||||
set({ wsApprovalKey: data.approvalKey });
|
||||
return data.approvalKey;
|
||||
if (!data.approvalKey || !data.wsUrl) {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
|
||||
const nextConnection = {
|
||||
approvalKey: data.approvalKey,
|
||||
wsUrl: data.wsUrl,
|
||||
} satisfies KisWsConnection;
|
||||
|
||||
set({
|
||||
wsApprovalKey: nextConnection.approvalKey,
|
||||
wsUrl: nextConnection.wsUrl,
|
||||
});
|
||||
|
||||
return nextConnection;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return null;
|
||||
} finally {
|
||||
approvalPromise = null;
|
||||
wsConnectionPromise = null;
|
||||
}
|
||||
})();
|
||||
|
||||
return approvalPromise;
|
||||
return wsConnectionPromise;
|
||||
},
|
||||
}),
|
||||
{
|
||||
@@ -216,13 +185,7 @@ export const useKisRuntimeStore = create<
|
||||
verifiedCredentials: state.verifiedCredentials,
|
||||
isKisVerified: state.isKisVerified,
|
||||
tradingEnv: state.tradingEnv,
|
||||
// wsApprovalKey도 로컬 스토리지에 저장하여 새로고침 후에도 유지 (선택사항이나 유지하는 게 유리)
|
||||
// 단, 승인키 유효기간 문제가 있을 수 있으나 API 실패 시 재발급 로직을 넣거나,
|
||||
// 현재 로직상 인증 정보가 바뀌면 초기화되므로 저장해도 무방.
|
||||
// 하지만 유효기간 만료 처리가 없으므로 일단 저장하지 않는 게 안전할 수도 있음.
|
||||
// 사용자가 "새로고침"을 하는 빈도보다 "일반적인 사용"이 많으므로 저장하지 않음 (partialize에서 제외)
|
||||
// -> 코드를 보니 여기 포함시키지 않으면 저장이 안 됨.
|
||||
// 유효기간 처리가 없으니 승인키는 메모리에만 유지하도록 함 (새로고침 시 재발급)
|
||||
// wsApprovalKey/wsUrl are kept in memory only (expiration-sensitive).
|
||||
}),
|
||||
},
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user