import { JWTPayload, UserInfo } from '@/types'; import { appBridge } from './appBridge'; import { jwtDecoder } from './jwtDecoder'; const ACCESS_TOKEN_KEY = 'nice_access_token'; const REFRESH_TOKEN_KEY = 'nice_refresh_token'; export class TokenManager { private static instance: TokenManager; private constructor() {} static getInstance(): TokenManager { if (!TokenManager.instance) { TokenManager.instance = new TokenManager(); } return TokenManager.instance; } setTokens(accessToken: string, refreshToken: string): void { localStorage.setItem(ACCESS_TOKEN_KEY, accessToken); localStorage.setItem(REFRESH_TOKEN_KEY, refreshToken); this.saveTokensToAppBridge(accessToken, refreshToken); } getAccessToken(): string | null { console.log('getAccessToken') return localStorage.getItem(ACCESS_TOKEN_KEY); } getRefreshToken(): string | null { return localStorage.getItem(REFRESH_TOKEN_KEY); } clearTokens(): void { localStorage.removeItem(ACCESS_TOKEN_KEY); localStorage.removeItem(REFRESH_TOKEN_KEY); } isTokenExpired(token: string): boolean { return jwtDecoder.isExpired(token); } isAccessTokenExpired(): boolean { const token = this.getAccessToken(); if (!token) return true; return this.isTokenExpired(token); } isRefreshTokenExpired(): boolean { const token = this.getRefreshToken(); if (!token) return true; return this.isTokenExpired(token); } decodeToken(token: string): JWTPayload | null { return jwtDecoder.decode(token); } getUserInfoFromToken(token?: string): UserInfo | null { const accessToken = token || this.getAccessToken(); if (!accessToken) return null; const decoded = this.decodeToken(accessToken); if (!decoded) return null; return { id: decoded.userId, email: decoded.email, name: '', // 토큰에서 이름을 가져오지 않으므로 빈 문자열 role: decoded.role, createdAt: '', updatedAt: '' }; } getTokenExpirationTime(token: string): number | null { return jwtDecoder.getExpiration(token); } getTimeUntilExpiration(token: string): number { return jwtDecoder.getTimeUntilExpiration(token); } shouldRefreshToken(): boolean { const accessToken = this.getAccessToken(); if (!accessToken) return false; const timeUntilExpiration = this.getTimeUntilExpiration(accessToken); // 토큰이 5분 이내에 만료되면 갱신 const REFRESH_BUFFER_SECONDS = 5 * 60; // 5 minutes return timeUntilExpiration < REFRESH_BUFFER_SECONDS; } hasValidTokens(): boolean { const accessToken = this.getAccessToken(); const refreshToken = this.getRefreshToken(); if (!accessToken || !refreshToken) return false; // 리프레시 토큰이 유효하면 액세스 토큰은 갱신 가능 return !this.isRefreshTokenExpired(); } async refreshTokens(): Promise<{accessToken: string, refreshToken: string}> { const refreshToken = this.getRefreshToken(); if (!refreshToken) { throw new Error('No refresh token available'); } const response = await fetch('http://3.35.79.250:8090/auth/v1/refresh', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ refreshToken }), }); if (!response.ok) { throw new Error(`Token refresh failed: ${response.status}`); } const data = await response.json(); const newAccessToken = data.accessToken; const newRefreshToken = data.refreshToken; this.setTokens(newAccessToken, newRefreshToken); return { accessToken: newAccessToken, refreshToken: newRefreshToken }; } async makeAuthenticatedRequest(url: string, options: RequestInit = {}): Promise { let accessToken = this.getAccessToken(); if (!accessToken || this.isAccessTokenExpired()) { try { const refreshed = await this.refreshTokens(); accessToken = refreshed.accessToken; } catch (error) { this.clearTokens(); throw new Error('Authentication failed - please log in again'); } } const requestOptions = { ...options, headers: { ...options.headers, 'Authorization': `Bearer ${accessToken}`, }, }; const response = await fetch(url, requestOptions); if (response.status === 401) { try { const refreshed = await this.refreshTokens(); const retryOptions = { ...requestOptions, headers: { ...requestOptions.headers, 'Authorization': `Bearer ${refreshed.accessToken}`, }, }; return await fetch(url, retryOptions); } catch (error) { this.clearTokens(); throw new Error('Authentication failed - please log in again'); } } return response; } private async saveTokensToAppBridge(accessToken: string, refreshToken: string): Promise { try { if (appBridge.isNativeEnvironment()) { await Promise.all([ appBridge.setStorage(ACCESS_TOKEN_KEY, accessToken), appBridge.setStorage(REFRESH_TOKEN_KEY, refreshToken) ]); } } catch (error) { console.error('Failed to save tokens to AppBridge:', error); } } } export const tokenManager = TokenManager.getInstance();