Files
nice-app-web/src/utils/tokenManager.ts
focp212@naver.com b6d608a6fa test
2025-10-14 16:01:06 +09:00

189 lines
5.3 KiB
TypeScript

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<Response> {
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<void> {
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();