export default defineNuxtPlugin(() => { // Only run on client side if (import.meta.server) return let refreshTimer: NodeJS.Timeout | null = null let isRefreshing = false const scheduleTokenRefresh = (expiresAt: number) => { // Clear existing timer if (refreshTimer) { clearTimeout(refreshTimer) refreshTimer = null } // Calculate time until refresh (refresh 2 minutes before expiry) const refreshBuffer = 2 * 60 * 1000 // 2 minutes in milliseconds const timeUntilRefresh = expiresAt - Date.now() - refreshBuffer console.log('[AUTH_REFRESH] Scheduling token refresh in:', Math.max(0, timeUntilRefresh), 'ms') // Only schedule if we have time left if (timeUntilRefresh > 0) { refreshTimer = setTimeout(async () => { if (isRefreshing) return try { isRefreshing = true console.log('[AUTH_REFRESH] Attempting automatic token refresh...') const response = await $fetch<{ success: boolean; expiresAt?: number }>('/api/auth/refresh', { method: 'POST' }) if (response.success && response.expiresAt) { console.log('[AUTH_REFRESH] Token refresh successful, scheduling next refresh') scheduleTokenRefresh(response.expiresAt) } else { console.error('[AUTH_REFRESH] Token refresh failed, redirecting to login') await navigateTo('/login') } } catch (error) { console.error('[AUTH_REFRESH] Token refresh error:', error) // If refresh fails, redirect to login await navigateTo('/login') } finally { isRefreshing = false } }, timeUntilRefresh) } else { // Token already expired or very close to expiry, try immediate refresh setTimeout(async () => { if (isRefreshing) return try { isRefreshing = true console.log('[AUTH_REFRESH] Token expired, attempting immediate refresh...') const response = await $fetch<{ success: boolean; expiresAt?: number }>('/api/auth/refresh', { method: 'POST' }) if (response.success && response.expiresAt) { console.log('[AUTH_REFRESH] Immediate refresh successful') scheduleTokenRefresh(response.expiresAt) } else { console.error('[AUTH_REFRESH] Immediate refresh failed, redirecting to login') await navigateTo('/login') } } catch (error) { console.error('[AUTH_REFRESH] Immediate refresh error:', error) await navigateTo('/login') } finally { isRefreshing = false } }, 100) // Small delay to avoid immediate execution } } const checkAndScheduleRefresh = async () => { try { // Use middleware cache instead of API call const nuxtApp = useNuxtApp() const authState = nuxtApp.payload?.data?.authState const sessionCache = nuxtApp.payload?.data?.['auth:session:cache'] const sessionData = authState || sessionCache if (sessionData?.authenticated) { // Get the session cookie to extract expiry time const sessionCookie = useCookie('nuxt-oidc-auth') if (sessionCookie.value) { try { const parsedSession = typeof sessionCookie.value === 'string' ? JSON.parse(sessionCookie.value) : sessionCookie.value if (parsedSession.expiresAt) { console.log('[AUTH_REFRESH] Found session with expiry:', new Date(parsedSession.expiresAt)) scheduleTokenRefresh(parsedSession.expiresAt) } } catch (parseError) { console.error('[AUTH_REFRESH] Failed to parse session cookie:', parseError) } } } else { console.log('[AUTH_REFRESH] No authenticated session found in cache') } } catch (error) { console.error('[AUTH_REFRESH] Failed to check session:', error) } } // Check authentication status and schedule refresh on plugin initialization onMounted(() => { checkAndScheduleRefresh() }) // Listen for route changes to re-check auth status const router = useRouter() router.afterEach((to) => { // Only check on protected routes if (to.meta.auth !== false) { checkAndScheduleRefresh() } }) // Listen for visibility changes to refresh when tab becomes active if (typeof document !== 'undefined') { document.addEventListener('visibilitychange', () => { if (!document.hidden) { // Tab became visible, check if we need to refresh checkAndScheduleRefresh() } }) } // Clean up timer on plugin destruction onBeforeUnmount(() => { if (refreshTimer) { clearTimeout(refreshTimer) refreshTimer = null } }) })