Files
sam-react-prod/src/lib/auth/token-refresh.ts
byeongcheolryu d7f491fa84 refactor: 로딩 스피너 표준화 및 프로젝트 헬스 개선
- LoadingSpinner 컴포넌트 5가지 변형 구현
  - LoadingSpinner (인라인/버튼용)
  - ContentLoadingSpinner (상세/수정 페이지)
  - PageLoadingSpinner (페이지 전환)
  - TableLoadingSpinner (테이블/리스트)
  - ButtonSpinner (버튼 내부)
- 18개+ 페이지 로딩 UI 표준화
  - HR 페이지 (사원, 휴가, 부서, 급여, 근태)
  - 영업 페이지 (견적, 거래처)
  - 게시판, 팝업관리, 품목기준정보
- API 키 보안 개선 (NEXT_PUBLIC_API_KEY → API_KEY)
- Textarea 다크모드 스타일 개선
- DropdownField Radix UI Select 버그 수정 (key prop)
- 프로젝트 헬스 개선 계획서 문서화

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-20 14:33:11 +09:00

97 lines
2.5 KiB
TypeScript

/**
* Token Refresh Utility
*
* Handles automatic token refresh logic for client and server
*/
/**
* Client-side token refresh
* Call this when you receive 401 errors from protected API calls
*/
export async function refreshTokenClient(): Promise<boolean> {
try {
const response = await fetch('/api/auth/refresh', {
method: 'POST',
credentials: 'include', // Include HttpOnly cookies
});
if (!response.ok) {
const data = await response.json();
// If refresh failed and needs re-authentication
if (data.needsReauth) {
console.warn('🔄 Token refresh failed - redirecting to login');
window.location.href = '/login';
return false;
}
return false;
}
console.log('✅ Token refreshed successfully');
return true;
} catch (error) {
console.error('❌ Token refresh error:', error);
return false;
}
}
/**
* Server-side token refresh (for API routes and middleware)
*/
export async function refreshTokenServer(refreshToken: string): Promise<{
success: boolean;
accessToken?: string;
newRefreshToken?: string;
expiresIn?: number;
}> {
try {
const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/v1/refresh`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': `Bearer ${refreshToken}`,
'X-API-KEY': process.env.API_KEY || '',
},
});
if (!response.ok) {
return { success: false };
}
const data = await response.json();
return {
success: true,
accessToken: data.access_token,
newRefreshToken: data.refresh_token,
expiresIn: data.expires_in,
};
} catch (error) {
console.error('Token refresh error:', error);
return { success: false };
}
}
/**
* Check if token is expired or about to expire (within 5 minutes)
* @param expiresAt - Expiration timestamp from backend (e.g., "2025-11-10 15:49:38")
*/
export function shouldRefreshToken(expiresAt: string | null): boolean {
if (!expiresAt) return true; // If no expiration info, assume expired
try {
const expiryTime = new Date(expiresAt).getTime();
const currentTime = Date.now();
const fiveMinutes = 5 * 60 * 1000; // 5 minutes in milliseconds
// Refresh if token expires in less than 5 minutes
return (expiryTime - currentTime) < fiveMinutes;
} catch {
return true; // If parsing fails, assume expired
}
}