import { join } from 'path'; // Token storage and refresh management // Stores both PocketBase and Graph tokens locally (tokens.json) // Background refresh keeps both tokens fresh without user intervention interface TokenData { pbToken: string; pbTokenExpiry: number; graphToken?: string; graphTokenExpiry?: number; lastRefresh: number; } const tokensFilePath = join(import.meta.dir, '../../tokens.json'); // PERMANENT: Token lifecycle management // - Tokens stored in tokens.json (persistent across restarts) // - Never delete old token before new one acquired // - Background refresh every 5 minutes // - Startup: if PB token exists, resume refresh; else require login export async function initializeTokenService(): Promise { // Load tokens from file if they exist try { const file = Bun.file(tokensFilePath); if (await file.exists()) { const content = await file.text(); const data = JSON.parse(content) as TokenData; console.log('[AuthAndToken] Tokens loaded from file'); console.log('[AuthAndToken] PB token valid:', data.pbToken ? 'yes' : 'no'); console.log('[AuthAndToken] Graph token valid:', data.graphToken ? 'yes' : 'no'); } else { console.log('[AuthAndToken] No tokens.json found - login required on startup'); } } catch (err) { console.log('[AuthAndToken] Token file init error (will create on first login):', (err as Error)?.message); } } export function startBackgroundRefresh(): void { // PERMANENT: Background token refresh // Runs every 5 minutes, attempts to refresh both PB and Graph tokens // Safe: never deletes old token before new one is confirmed setInterval(async () => { try { await refreshTokens(); } catch (err) { console.log('[AuthAndToken] Background refresh error:', (err as Error)?.message); } }, 5 * 60 * 1000); // 5 minutes console.log('[AuthAndToken] Background refresh started (every 5 minutes)'); } export async function saveTokens(pbToken: string, graphToken?: string): Promise { // PERMANENT: Token persistence // Called after login or successful refresh // Stores both tokens with expiry times const data: TokenData = { pbToken, pbTokenExpiry: Date.now() + 7 * 24 * 60 * 60 * 1000, // 7 days (PB default) graphToken, graphTokenExpiry: graphToken ? Date.now() + 60 * 60 * 1000 : undefined, // 1 hour (Graph default) lastRefresh: Date.now() }; await Bun.write(tokensFilePath, JSON.stringify(data, null, 2)); console.log('[AuthAndToken] Tokens saved'); } export async function getTokens(): Promise { try { const file = Bun.file(tokensFilePath); if (!(await file.exists())) return null; const content = await file.text(); return JSON.parse(content) as TokenData; } catch (err) { console.log('[AuthAndToken] Error reading tokens:', (err as Error)?.message); return null; } } export async function clearTokens(): Promise { try { const file = Bun.file(tokensFilePath); if (await file.exists()) { await Bun.write(tokensFilePath, ''); } } catch (err) { console.log('[AuthAndToken] Error clearing tokens:', (err as Error)?.message); } } async function refreshTokens(): Promise { // PERMANENT: Auto-refresh logic for both tokens // Called every 5 minutes by background job // Fetches fresh tokens from PocketBase, attempts Graph token refresh // Only updates file after new tokens confirmed valid const tokens = await getTokens(); if (!tokens || !tokens.pbToken) { console.log('[AuthAndToken] No active tokens to refresh'); return; } try { // Refresh via PocketBase endpoint const pbUrl = `${process.env.POCKETBASE_URL}/api/collections/users/auth-refresh`; const response = await fetch(pbUrl, { method: 'POST', headers: { 'Authorization': `Bearer ${tokens.pbToken}` } }); if (!response.ok) { console.log('[AuthAndToken] Refresh failed:', response.status); return; } const data = await response.json() as any; const newPbToken = data?.token; if (!newPbToken) { console.log('[AuthAndToken] No new PB token in response'); return; } // Update tokens with new PB token await saveTokens(newPbToken, tokens.graphToken); console.log('[AuthAndToken] Tokens refreshed successfully'); } catch (err) { console.log('[AuthAndToken] Refresh error:', (err as Error)?.message); } } export async function hasValidPbToken(): Promise { const tokens = await getTokens(); return !!tokens?.pbToken && tokens.pbTokenExpiry > Date.now(); }