Files
Krow-workspace/apps/web/src/services/authService.ts
2026-01-29 12:39:45 +05:30

212 lines
6.1 KiB
TypeScript

import {
signInWithEmailAndPassword,
signOut,
onAuthStateChanged,
setPersistence,
browserLocalPersistence,
sendPasswordResetEmail,
confirmPasswordReset,
} from "firebase/auth";
import type { User, AuthError } from "firebase/auth";
import { app} from "../features/auth/firebase"
import { getAuth } from "firebase/auth";
const auth = getAuth(app);
// Token refresh interval tracking
let tokenRefreshInterval: number | null = null;
// Constants for session management
const TOKEN_REFRESH_INTERVAL = 40 * 60 * 1000; // Refresh token every 40 minutes (Firebase ID tokens expire in 1 hour)
/**
* Initialize Firebase Auth persistence
* Ensures user session persists across page refreshes
*/
export const initializeAuthPersistence = async () => {
try {
await setPersistence(auth, browserLocalPersistence);
console.log("Auth persistence initialized with localStorage");
} catch (error) {
console.error("Error setting auth persistence:", error);
}
};
/**
* Refresh the current user's ID token to maintain session validity
* Firebase automatically refreshes tokens, but we can force a refresh to ensure validity
* @returns Promise<boolean> - true if refresh successful, false otherwise
*/
export const refreshUserToken = async (): Promise<boolean> => {
try {
const currentUser = auth.currentUser;
if (currentUser) {
await currentUser.getIdTokenResult(true); // Force refresh
console.log("Token refreshed successfully");
return true;
}
return false;
} catch (error) {
console.error("Error refreshing token:", error);
return false;
}
};
/**
* Start automatic token refresh mechanism
* Refreshes token periodically to prevent unexpected logouts
*/
export const startTokenRefreshTimer = () => {
// Clear any existing interval
if (tokenRefreshInterval) {
clearInterval(tokenRefreshInterval);
}
// Set up auto-refresh interval
tokenRefreshInterval = window.setInterval(async () => {
const currentUser = auth.currentUser;
if (currentUser) {
await refreshUserToken();
} else {
// If no user, stop the refresh timer
stopTokenRefreshTimer();
}
}, TOKEN_REFRESH_INTERVAL);
};
/**
* Stop the automatic token refresh timer
*/
export const stopTokenRefreshTimer = () => {
if (tokenRefreshInterval) {
clearInterval(tokenRefreshInterval);
tokenRefreshInterval = null;
}
};
/**
* Login user with email and password
*/
export const loginWithEmail = async (email: string, password: string) => {
try {
const userCredential = await signInWithEmailAndPassword(auth, email, password);
return {
success: true,
user: userCredential.user,
error: null,
};
} catch (error) {
const authError = error as AuthError;
return {
success: false,
user: null,
error: getAuthErrorMessage(authError.code),
};
}
};
/**
* Sign out the current user
* Clears session data, local storage, and stops token refresh
*/
export const logout = async () => {
try {
// Stop token refresh interval
stopTokenRefreshTimer();
// Clear any session-related data from localStorage
localStorage.removeItem('lastActivityTime');
localStorage.removeItem('sessionStartTime');
// Sign out from Firebase
await signOut(auth);
// Clear any other app-specific session data if needed
sessionStorage.clear();
console.log("User logged out successfully");
return { success: true };
} catch (error) {
console.error("Error during logout:", error);
return { success: false, error: (error as Error).message };
}
};
/**
* Send password reset email
*/
export const sendPasswordReset = async (email: string) => {
try {
await sendPasswordResetEmail(auth, email);
return { success: true };
} catch (error) {
const authError = error as AuthError;
return { success: false, error: getAuthErrorMessage(authError.code) };
}
};
/**
* Reset password with code and new password
* Used after user clicks the link in the reset email
*/
export const resetPassword = async (code: string, newPassword: string) => {
try {
await confirmPasswordReset(auth, code, newPassword);
return { success: true };
} catch (error) {
const authError = error as AuthError;
return { success: false, error: getAuthErrorMessage(authError.code) };
}
};
/**
* Subscribe to auth state changes
* Sets up token refresh timer when user logs in, stops it when logs out
* Returns unsubscribe function
*/
export const subscribeToAuthState = (callback: (user: User | null) => void) => {
return onAuthStateChanged(auth, (user) => {
if (user) {
// User logged in - start token refresh
startTokenRefreshTimer();
// Update last activity time
localStorage.setItem('lastActivityTime', Date.now().toString());
localStorage.setItem('sessionStartTime', Date.now().toString());
} else {
// User logged out - stop token refresh
stopTokenRefreshTimer();
}
callback(user);
});
};
/**
* Get current user synchronously
*/
export const getCurrentUser = () => {
return auth.currentUser;
};
/**
* Convert Firebase error codes to user-friendly messages
*/
const getAuthErrorMessage = (errorCode: string): string => {
const errorMessages: Record<string, string> = {
"auth/invalid-email": "Invalid email address format.",
"auth/user-disabled": "This user account has been disabled.",
"auth/user-not-found": "No account found with this email address.",
"auth/wrong-password": "Invalid email or password.",
"auth/invalid-credential": "Invalid email or password.",
"auth/too-many-requests": "Too many login attempts. Please try again later.",
"auth/operation-not-allowed": "Login is currently disabled. Please try again later.",
"auth/network-request-failed": "Network error. Please check your connection.",
"auth/invalid-action-code": "This password reset link is invalid or has expired.",
"auth/expired-action-code": "This password reset link has expired. Please request a new one.",
"auth/weak-password": "Password is too weak. Please choose a stronger password.",
};
return errorMessages[errorCode] || "An error occurred. Please try again.";
};
export { app };