diff --git a/src/context/AuthContext.jsx b/src/context/AuthContext.jsx index 5e4f134..e82acfc 100644 --- a/src/context/AuthContext.jsx +++ b/src/context/AuthContext.jsx @@ -20,6 +20,7 @@ import { import axios from "axios"; import { auth, db, signInWithGitHub, signOutUser } from "../lib/firebase"; import { validateUserData } from "../utils/inputValidation"; +import logger from "../utils/logger"; import { userDataCache, listenerOptimizer, @@ -97,7 +98,7 @@ const checkAndUpdateStreak = async (data, docRef) => { }); }); } catch (err) { - console.error("Failed to update streak atomically:", err); + logger.error("Failed to update streak atomically:", err); } }; @@ -110,7 +111,7 @@ export const AuthProvider = ({ children }) => { useEffect(() => { if (!auth) { - console.warn("Firebase auth is not initialized; auth listener skipped."); + logger.warn("Firebase auth is not initialized; auth listener skipped."); return undefined; } @@ -179,7 +180,7 @@ export const AuthProvider = ({ children }) => { } }) .catch((error) => { - console.error("Redirect sign-in resolution failure:", error); + logger.error("Redirect sign-in resolution failure:", error); }); const unsubscribeAuth = onAuthStateChanged(auth, async (currentUser) => { @@ -288,7 +289,7 @@ export const AuthProvider = ({ children }) => { const validation = validateUserData(rawUserData); if (!validation.isValid) { - console.warn("User data validation warnings:", validation.errors); + logger.warn("User data validation warnings:", validation.errors); } const sanitizedUserData = validation.sanitized; @@ -345,7 +346,7 @@ export const AuthProvider = ({ children }) => { return authUser; } catch (error) { - console.error("Login service failure:", error); + logger.error("Login service failure:", error); setLoading(false); throw error; } @@ -363,7 +364,7 @@ export const AuthProvider = ({ children }) => { setIsOnboarding(false); setGhAccessToken(null); } catch (error) { - console.error("Logout failure:", error); + logger.error("Logout failure:", error); } finally { setLoading(false); } @@ -398,7 +399,7 @@ export const AuthProvider = ({ children }) => { inventory: [...(prev.inventory || ["oliver"]), mascotId], })); } catch (err) { - console.error("Failed to purchase mascot:", err); + logger.error("Failed to purchase mascot:", err); throw err; } }; @@ -416,7 +417,7 @@ export const AuthProvider = ({ children }) => { updatedAt: new Date().toISOString(), }); } catch (err) { - console.error("Failed to equip mascot:", err); + logger.error("Failed to equip mascot:", err); throw err; } }; @@ -481,7 +482,7 @@ export const AuthProvider = ({ children }) => { if (sortedLangs.length > 0) primaryLanguage = sortedLangs[0]; } } catch (err) { - console.warn("Stars/Language retrieval warning, defaulting:", err); + logger.warn("Stars/Language retrieval warning, defaulting:", err); } let commits = 0; @@ -493,10 +494,10 @@ export const AuthProvider = ({ children }) => { { headers }, ), ); - if (commitsRateLimit) console.warn(commitsRateLimit); + if (commitsRateLimit) logger.warn(commitsRateLimit); else commits = commitsData?.total_count || 0; } catch (err) { - console.warn("Commits retrieval failed:", err); + logger.warn("Commits retrieval failed:", err); } let prs = 0; @@ -508,10 +509,10 @@ export const AuthProvider = ({ children }) => { { headers }, ), ); - if (prsRateLimit) console.warn(prsRateLimit); + if (prsRateLimit) logger.warn(prsRateLimit); else prs = prsData?.total_count || 0; } catch (err) { - console.warn("PRs retrieval failed:", err); + logger.warn("PRs retrieval failed:", err); } let reviews = 0; @@ -523,10 +524,10 @@ export const AuthProvider = ({ children }) => { { headers }, ), ); - if (reviewsRateLimit) console.warn(reviewsRateLimit); + if (reviewsRateLimit) logger.warn(reviewsRateLimit); else reviews = reviewsData?.total_count || 0; } catch (err) { - console.warn("Reviews retrieval failed:", err); + logger.warn("Reviews retrieval failed:", err); } let githubStreak = 0; @@ -568,7 +569,7 @@ export const AuthProvider = ({ children }) => { } } } catch (err) { - console.warn("GitHub events retrieval failed for streak:", err); + logger.warn("GitHub events retrieval failed for streak:", err); } const gitRankPoints = @@ -588,10 +589,10 @@ export const AuthProvider = ({ children }) => { } catch (error) { const rateLimitMsg = getRateLimitMessage(error); if (rateLimitMsg) { - console.warn("Rate limit hit:", rateLimitMsg); + logger.warn("Rate limit hit:", rateLimitMsg); throw new Error(rateLimitMsg, { cause: error }); } - console.error("Error executing GitHub stats fetcher snapshot:", error); + logger.error("Error executing GitHub stats fetcher snapshot:", error); return { commits: 0, prs: 0, @@ -660,10 +661,10 @@ export const AuthProvider = ({ children }) => { } catch (error) { const rateLimitMsg = getRateLimitMessage(error); if (rateLimitMsg) { - console.warn("Sync blocked by rate limit:", rateLimitMsg); + logger.warn("Sync blocked by rate limit:", rateLimitMsg); throw new Error(rateLimitMsg, { cause: error }); } - console.error("Background GitHub sync failed:", error); + logger.error("Background GitHub sync failed:", error); } }; diff --git a/src/lib/firebase.js b/src/lib/firebase.js index 4799ec6..5ef97e8 100644 --- a/src/lib/firebase.js +++ b/src/lib/firebase.js @@ -14,6 +14,7 @@ import { persistentMultipleTabManager, } from "firebase/firestore"; import { getStorage } from "firebase/storage"; +import logger from "../utils/logger"; const firebaseConfig = { apiKey: import.meta.env.VITE_FIREBASE_API_KEY, @@ -40,7 +41,7 @@ const hasRequiredConfig = requiredConfigKeys.every((key) => ); if (!hasRequiredConfig) { - console.warn( + logger.warn( "Firebase is not configured. Auth, database, analytics, and storage services are disabled for this environment.", ); } @@ -100,7 +101,7 @@ if (app && typeof window !== "undefined") { try { analyticsInstance = getAnalytics(app); } catch (error) { - console.warn("Analytics initialization skipped:", error); + logger.warn("Analytics initialization skipped:", error); } } @@ -142,7 +143,7 @@ export const signInWithGitHub = async (requestRepoScope = false) => { return { user, accessToken, userData, result }; } catch (error) { - console.error("GitHub sign-in error:", error); + logger.error("GitHub sign-in error:", error); throw error; } }; @@ -157,7 +158,7 @@ export const signOutUser = async () => { await signOut(auth); return true; } catch (error) { - console.error("Sign out error:", error); + logger.error("Sign out error:", error); throw error; } }; @@ -174,7 +175,7 @@ export const getCurrentUserToken = async () => { const token = await user.getIdToken(); return token; } catch (error) { - console.error("Error getting user token:", error); + logger.error("Error getting user token:", error); return null; } } @@ -193,7 +194,7 @@ export const refreshUserToken = async () => { const token = await user.getIdToken(true); return token; } catch (error) { - console.error("Error refreshing user token:", error); + logger.error("Error refreshing user token:", error); return null; } } diff --git a/src/utils/logger.js b/src/utils/logger.js new file mode 100644 index 0000000..52b432d --- /dev/null +++ b/src/utils/logger.js @@ -0,0 +1,59 @@ +const logger = { + info: (message, data = {}) => { + if (import.meta.env.DEV) { + console.info(`[INFO] ${message}`, sanitizeData(data)); + } + }, + + error: (message, error, context = {}) => { + if (import.meta.env.DEV) { + console.error(`[ERROR] ${message}`, error, sanitizeData(context)); + } + }, + + warn: (message, data = {}) => { + if (import.meta.env.DEV) { + console.warn(`[WARN] ${message}`, sanitizeData(data)); + } + }, + + debug: (message, data = {}) => { + if (import.meta.env.DEV) { + console.debug(`[DEBUG] ${message}`, sanitizeData(data)); + } + }, +}; + +const sanitizeData = (data) => { + if (!data || typeof data !== 'object') { + return data; + } + + const sensitiveKeys = [ + 'userId', + 'uid', + 'email', + 'password', + 'token', + 'accessToken', + 'refreshToken', + 'sessionId', + 'personalInfo', + 'username', + 'githubUsername', + 'apiKey', + 'secret', + ]; + + const sanitized = { ...data }; + + sensitiveKeys.forEach((key) => { + if (key in sanitized) { + sanitized[key] = '[REDACTED]'; + } + }); + + return sanitized; +}; + +export default logger;