import { ACCESS_TOKEN, AUTH_LOGIN_REDIRECT_URL, AUTH_USER_ID, CLAIM_NAME, ENDPOINT, } from "@/shared/config"; import { type AuthPayload, type AuthService, getRemainingMs, } from "@common/auth"; import { clearRefreshTimer, removeToken, setToken, setupRefreshTimer, tokenManager, } from "../lib/token-manager"; function resolveLoginError(message: string | undefined): string { switch (message) { case "Invalid Credentials": return "아이디와 비밀번호가 일치하지 않습니다."; case "User not found": return "존재하지 않는 아이디입니다."; case "User is not active": return "활성화되지 않은 계정입니다."; case "User is not verified": return "이메일 인증이 필요합니다."; default: return message ?? "로그인에 실패했습니다."; } } async function login(payload: AuthPayload): Promise { const response = await fetch(`/api/proxy${ENDPOINT.USER_SERVICE.LOGIN}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), }); const data = await response.json(); if (!response.ok) { throw new Error(resolveLoginError(data?.message)); } const accessToken = data[ACCESS_TOKEN] as string | undefined; const tokenId = data[AUTH_USER_ID] as string | undefined; if (!accessToken || !tokenId) { throw new Error("인증 토큰을 받지 못했습니다."); } setToken(CLAIM_NAME, accessToken); setToken(AUTH_USER_ID, tokenId); const remainingMs = getRemainingMs(accessToken); if (remainingMs > 0) { setupRefreshTimer(silentRefresh, remainingMs); } } async function silentRefresh(): Promise { console.log(`[silentRefresh] Called at ${new Date().toISOString()}`); try { const response = await fetch( `/api/proxy${ENDPOINT.USER_SERVICE.SILENT_REFRESH}`, { method: "PUT" }, ); const data = await response.json(); if (!response.ok) { throw new Error(data?.message ?? "토큰 갱신에 실패했습니다."); } const accessToken = data[ACCESS_TOKEN] as string | undefined; const tokenId = data[AUTH_USER_ID] as string | undefined; if (!accessToken || !tokenId) { throw new Error("갱신된 인증 토큰을 받지 못했습니다."); } setToken(CLAIM_NAME, accessToken); setToken(AUTH_USER_ID, tokenId); const remainingMs = getRemainingMs(accessToken); if (remainingMs > 0) { setupRefreshTimer(silentRefresh, remainingMs); } } catch (error) { console.error( `[silentRefresh] Failed at ${new Date().toISOString()}`, error, ); tokenManager.clearAuthToken(); if (typeof window !== "undefined") { window.dispatchEvent(new CustomEvent("auth:logout")); } // re-throw하지 않음: setupRefreshTimer 콜백의 catch와 이중 처리 방지 } } async function logout(): Promise { try { await fetch("/api/auth/logout", { method: "POST", credentials: "include", }); } catch (error) { console.error("Logout API failed:", error); } tokenManager.clearAuthToken(); removeToken("site-id-key"); removeToken("site-theme-key"); if (typeof window !== "undefined") { window.dispatchEvent(new CustomEvent("auth:logout")); window.location.href = AUTH_LOGIN_REDIRECT_URL; } } export const authService: AuthService = { login, silentRefresh, declineSessionExtend: clearRefreshTimer, logout, };