import type { DecodedToken, UserData } from '../types'; /** * Decode a JWT token payload */ export function decodeToken(token: string): DecodedToken | null { try { const parts = token.split('.'); if (parts.length !== 3) { return null; } const base64Url = parts[1]; const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/'); // Add padding if needed const padding = base64.length % 4; const paddedBase64 = padding ? base64 + '='.repeat(4 - padding) : base64; // Decode base64 - atob is available in browsers, Node.js 16+, and React Native const payload: DecodedToken = JSON.parse(atob(paddedBase64)); return payload; } catch (error) { console.error('Error decoding JWT token:', error); return null; } } /** * Check if a token is valid locally (not expired) */ export function isTokenValidLocally(token: string, bufferSeconds = 10): boolean { try { const payload = decodeToken(token); if (!payload || !payload.exp) { return false; } const bufferTime = bufferSeconds * 1000; const expiryTime = payload.exp * 1000; const currentTime = Date.now(); return currentTime < expiryTime - bufferTime; } catch (error) { console.debug('Error validating token locally:', error); return false; } } /** * Check if a token is expired */ export function isTokenExpired(token: string): boolean { return !isTokenValidLocally(token, 0); } /** * Extract user data from a JWT token */ export function getUserFromToken(token: string, storedEmail?: string): UserData | null { try { const payload = decodeToken(token); if (!payload) { return null; } // Get email from various sources let email = payload.email || ''; if (!email && payload.user_metadata?.email) { email = payload.user_metadata.email; } if (!email && storedEmail) { email = storedEmail; } return { id: payload.sub, email: email || 'user@example.com', role: payload.role || 'user', }; } catch (error) { console.error('Error extracting user from token:', error); return null; } } /** * Get token expiration time in milliseconds */ export function getTokenExpirationTime(token: string): number | null { const payload = decodeToken(token); if (!payload || !payload.exp) { return null; } return payload.exp * 1000; } /** * Get time until token expiration in milliseconds */ export function getTimeUntilExpiration(token: string): number { const expirationTime = getTokenExpirationTime(token); if (!expirationTime) { return 0; } return Math.max(0, expirationTime - Date.now()); } /** * Check if user is B2B based on JWT claims */ export function isB2BUser(token: string): boolean { const payload = decodeToken(token); if (!payload) { return false; } // Handle different types for is_b2b return payload.is_b2b === true || payload.is_b2b === 'true' || payload.is_b2b === 1; } /** * Get B2B information from JWT claims */ export function getB2BInfo(token: string): { disableRevenueCat: boolean; organizationId?: string; plan?: string; role?: string; } | null { const payload = decodeToken(token); if (!payload?.app_settings?.b2b) { return null; } const b2bSettings = payload.app_settings.b2b; return { disableRevenueCat: !!b2bSettings.disableRevenueCat, organizationId: b2bSettings.organizationId, plan: b2bSettings.plan, role: b2bSettings.role, }; } /** * Check if RevenueCat should be disabled for this token */ export function shouldDisableRevenueCat(token: string): boolean { const b2bInfo = getB2BInfo(token); return b2bInfo?.disableRevenueCat ?? false; } /** * Get app settings from JWT claims */ export function getAppSettings(token: string): Record | null { const payload = decodeToken(token); return payload?.app_settings || null; }