Skip to content

Conversation

@Devnil434
Copy link

@Devnil434 Devnil434 commented Dec 28, 2025

Summary

This PR adds defensive utilities for localStorage access and JSON parsing and replaces unsafe usages across the frontend.
It prevents runtime crashes caused by null or malformed persisted data and hardens WebSocket message handling where payloads were previously assumed to be valid.


What changed

  • Added storage.ts
    • Safe helpers:
      • getLocalString
      • getLocalJSON
      • setLocalString / setLocalJSON
    • Guards against missing keys and corrupted values.
  • Added safeParse.ts
    • safeParse(raw, fallback) utility to prevent JSON.parse crashes.
  • Replaced direct localStorage.getItem and JSON.parse usages with defensive wrappers.
  • Hardened WebSocket message parsing and other runtime paths that previously assumed valid payloads.

Representative files updated

  • theme-provider.tsx
  • useDebateWS.ts
  • DebateRoom.tsx
  • OnlineDebateRoom.tsx
  • gamificationService.ts

(See commit history / branch diff for full list.)


What this fixes

  • 🚨 Prevents uncaught runtime errors such as:
    TypeError: Cannot read properties of null (reading 'length')
    
    

caused by:

  • JSON.parse(null)

  • Accessing .length on missing localStorage values

  • Improves resilience against:

  • Fresh sessions (empty localStorage)

  • Corrupted persisted state

  • Unexpected or malformed WebSocket payloads

Testing / Verification

  • Start the frontend dev server.
  • Reproduce the previously observed crash — no runtime errors appear in DevTools.
  • Clear persisted state and reload:

localStorage.clear()

→ App loads without crashing.

If backend interaction is required, ensure the backend is running normally.

Notes for reviewers

Please focus review on:

  • frontend/src/utils/storage.ts

  • frontend/src/utils/safeParse.ts

  • Call sites migrated to use these helpers

This PR is intentionally defensive and non-breaking and solve this issue #145.

Follow-up work could:

  • Centralize runtime payload validation

  • Add telemetry/logging for malformed data paths

  • I’m happy to sweep remaining files if you’d like this pattern applied more broadly.

Summary by CodeRabbit

  • Configuration

    • Added production config with server, database, JWT, SMTP, and auth placeholders.
  • New Features

    • Introduced safe storage and JSON parsing utilities for safer persistence and message handling.
  • Bug Fixes

    • Improved resilience against malformed local storage and WebSocket payloads with guarded parsing and cleanup.
  • Refactor / Breaking Changes

    • Renamed evaluation field from pointsEarned to score (public interface change).
  • Documentation

    • Updated README with MongoDB connection and security guidance.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Dec 28, 2025

📝 Walkthrough

Walkthrough

Adds SSR-safe storage helpers and a safe JSON parser; refactors many frontend components, hooks, and services to use those utilities instead of direct localStorage/JSON.parse; and adds production backend config files with placeholder secrets, JWT expiry, and other environment settings.

Changes

Cohort / File(s) Summary
New utilities
frontend/src/utils/safeParse.ts, frontend/src/utils/storage.ts
Add safeParse (generic, fallback-capable JSON parser) and SSR-safe storage helpers: getLocalString/getLocalJSON/setLocalString/setLocalJSON with parse-error handling and corrupt-key removal.
Backend config files
backend/config/config.prod.sample.yml, backend/config/config.prod.yml
Add/modify production config: database.uri placeholder (<YOUR_MONGODB_URI>), gemini.apiKey, jwt.secret and jwt.expiry (1440), SMTP and Google OAuth placeholders and comments.
Auth & token adapters
frontend/src/utils/auth.ts, frontend/src/services/teamDebateService.ts
Switch token getters/setters to use storage helpers (getLocalString/setLocalString) while preserving public signatures.
Theme & context
frontend/src/context/theme-provider.tsx
Replace direct localStorage reads/writes with getLocalString/setLocalString.
Core hooks
frontend/src/hooks/useDebateWS.ts, frontend/src/hooks/useUser.ts
Replace localStorage usage with storage helpers; add safeParse for WS/cached data parsing; initialize spectatorId when missing.
Pages — token/storage migrations
frontend/src/Pages/Admin/AdminDashboard.tsx, frontend/src/Pages/CommunityFeed.tsx, frontend/src/Pages/Leaderboard.tsx
Replace localStorage.getItem with getLocalString/getLocalJSON for token/admin/user retrieval and adjust parsing flows.
Pages — resilient parsing
frontend/src/Pages/DebateRoom.tsx, frontend/src/Pages/OnlineDebateRoom.tsx, frontend/src/Pages/TeamDebateRoom.tsx, frontend/src/Pages/ViewDebate.tsx, frontend/src/Pages/Game.tsx, frontend/src/Pages/StrengthenArgument.tsx
Replace many JSON.parse usages with safeParse or guarded try/catch; add validation, fallback defaults, and corrupt-state cleanup; guard WS handling against malformed payloads.
Components — WS parsing guards
frontend/src/components/ChatRoom.tsx, frontend/src/components/Matchmaking.tsx, frontend/src/components/TeamChatSidebar.tsx
Use safeParse for WebSocket message handling; add early-return guards to avoid runtime exceptions on invalid payloads.
Services — gamification & team debate
frontend/src/services/gamificationService.ts, frontend/src/services/teamDebateService.ts
Harden WS onmessage parsing with safeParse and conditional dispatch; switch token retrieval to storage helpers where applicable.
Misc pages & components
frontend/src/Pages/Admin/AdminDashboard.tsx, other updated files...
Widespread replacements of localStorage / JSON.parse with utilities across many frontend files (see diff for full list).
README
README.md
Clarifies MongoDB config guidance: replace the placeholder with a real URI and avoid committing credentials.

Sequence Diagram(s)

(omitted)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • bhavik-mangla

Poem

🐰 In burrows deep I debugged late at night,

SafeParse tucked the JSON snug and tight,
Tokens stored in hollows, calm and warm,
WebSockets guarded from each little storm,
A hopping rabbit cheers — the app runs bright. 🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title 'fix unsafe error' is vague and does not clearly convey the specific nature or scope of the changes; it uses non-descriptive language. Consider a more descriptive title such as 'Add defensive utilities for localStorage and JSON parsing to prevent runtime crashes' that better summarizes the core intent of adding safe wrappers and hardening error handling.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

🧹 Nitpick comments (8)
frontend/src/utils/storage.ts (1)

12-26: Consider runtime type validation for stronger safety.

The type assertion as T at line 16 provides no runtime guarantee that the parsed JSON matches the expected type T. If the stored data structure has changed or is corrupted, this could lead to runtime errors downstream when code assumes certain properties exist.

For critical data types, consider adding runtime validation (e.g., using Zod or a custom validator) to verify the structure matches T before returning.

That said, the fallback mechanism and corrupt item removal provide good resilience against parse failures.

frontend/src/hooks/useUser.ts (1)

20-29: Prefer getLocalJSON for consistency.

Lines 20-27 use getLocalString followed by manual JSON.parse, which duplicates the logic already provided by getLocalJSON. This is inconsistent with the pattern used in other files (e.g., AdminDashboard.tsx line 369).

Consider refactoring to:

🔎 Proposed refactor
   useEffect(() => {
     if (!user) {
-      const cachedUser = getLocalString(USER_CACHE_KEY);
-      if (cachedUser) {
-        try {
-          const parsedUser = JSON.parse(cachedUser);
-          setUser(parsedUser);
-        } catch (error) {
-          console.error("Failed to parse cached user profile:", error);
-          try { localStorage.removeItem(USER_CACHE_KEY); } catch {}
-        }
-      }
+      const cachedUser = getLocalJSON(USER_CACHE_KEY);
+      if (cachedUser) {
+        setUser(cachedUser);
+      }
     }
   }, [user, setUser]);

This leverages the built-in error handling and corrupt item removal already present in getLocalJSON.

frontend/src/utils/auth.ts (1)

1-1: Clean up unused import.

The import includes setLocalJSON but it's not used anywhere in this file. Consider removing it to keep imports clean.

🔎 Proposed cleanup
-import { setLocalString, getLocalString, setLocalJSON } from './storage';
+import { setLocalString, getLocalString } from './storage';
frontend/src/utils/safeParse.ts (1)

8-8: Consider upgrading console.warn to include the raw value for debugging.

The warning logs the error but not the problematic input, making debugging harder in production.

📝 Suggested improvement
  } catch (err) {
-    console.warn('safeParse failed to parse JSON:', err);
+    console.warn('safeParse failed to parse JSON:', err, 'Raw value:', typeof raw === 'string' ? raw.substring(0, 100) : raw);
    return fallback;
  }
frontend/src/components/ChatRoom.tsx (1)

62-63: Consider using a typed interface instead of any for better type safety.

The safeParse<any> call loses type information. Define a message interface to catch structural errors at compile time:

📝 Suggested improvement
interface ChatRoomMessage {
  type: 'chatMessage' | 'notification' | 'reaction' | 'vote' | 'presence';
  username?: string;
  content?: string;
  timestamp?: number;
  extra?: {
    reaction?: string;
    option?: VoteOption;
  };
  count?: number;
}

// Then use:
const data = safeParse<ChatRoomMessage>(event.data, null);
frontend/src/components/TeamChatSidebar.tsx (1)

62-63: Use a typed interface instead of any for WebSocket messages.

Same concern as in ChatRoom.tsx - using any defeats the purpose of TypeScript's type checking. Consider defining:

📝 Suggested improvement
interface TeamChatMessage {
  type: string;
  messageId?: string;
  userId: string;
  email?: string;
  username?: string;
  content?: string;
  message?: string;
  timestamp?: string;
}

const data = safeParse<TeamChatMessage>(event.data, null);
frontend/src/Pages/OnlineDebateRoom.tsx (1)

727-727: Simplify redundant ternary check.

Since storageKey is always defined from the template literal on line 726, the ternary check is unnecessary.

🔎 Proposed simplification
-        const stored = storageKey ? getLocalString(storageKey) : null;
+        const stored = getLocalString(storageKey);
frontend/src/Pages/DebateRoom.tsx (1)

229-260: Use safeParse for consistency with PR objectives.

While the try-catch block handles JSON parsing errors, using JSON.parse directly (line 232) is inconsistent with the PR's goal of replacing all direct JSON.parse calls with safeParse. This would make the error handling more uniform across the codebase.

🔎 Proposed refactor to use safeParse
   const [state, setState] = useState<DebateState>(() => {
     const savedState = getLocalString(debateKey);
     if (savedState) {
-      try {
-        const parsed = JSON.parse(savedState) as Partial<DebateState> | null;
-        if (parsed && typeof parsed === 'object') {
-          return {
-            messages: parsed.messages ?? [],
-            currentPhase: parsed.currentPhase ?? 0,
-            phaseStep: parsed.phaseStep ?? 0,
-            isBotTurn: parsed.isBotTurn ?? false,
-            userStance: parsed.userStance ?? "",
-            botStance: parsed.botStance ?? "",
-            timer: parsed.timer ?? phases[0].time,
-            isDebateEnded: parsed.isDebateEnded ?? false,
-          } as DebateState;
-        }
-      } catch (err) {
-        console.warn('Failed to parse saved debate state, clearing corrupt value', err);
-        try { localStorage.removeItem(debateKey); } catch {}
-      }
+      const parsed = safeParse<Partial<DebateState>>(savedState, null);
+      if (parsed && typeof parsed === 'object') {
+        return {
+          messages: parsed.messages ?? [],
+          currentPhase: parsed.currentPhase ?? 0,
+          phaseStep: parsed.phaseStep ?? 0,
+          isBotTurn: parsed.isBotTurn ?? false,
+          userStance: parsed.userStance ?? "",
+          botStance: parsed.botStance ?? "",
+          timer: parsed.timer ?? phases[0].time,
+          isDebateEnded: parsed.isDebateEnded ?? false,
+        } as DebateState;
+      } else {
+        console.warn('Failed to parse saved debate state, clearing corrupt value');
+        try { localStorage.removeItem(debateKey); } catch {}
+      }
     }
 
     return {
       messages: [],
       currentPhase: 0,
       phaseStep: 0,
       isBotTurn: false,
       userStance: "",
       botStance: "",
       timer: phases[0].time,
       isDebateEnded: false,
     };
   });
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1f8db72 and 2ecc753.

📒 Files selected for processing (22)
  • backend/config/config.prod.sample.yml
  • backend/config/config.prod.yml
  • frontend/src/Pages/Admin/AdminDashboard.tsx
  • frontend/src/Pages/CommunityFeed.tsx
  • frontend/src/Pages/DebateRoom.tsx
  • frontend/src/Pages/Game.tsx
  • frontend/src/Pages/Leaderboard.tsx
  • frontend/src/Pages/OnlineDebateRoom.tsx
  • frontend/src/Pages/StrengthenArgument.tsx
  • frontend/src/Pages/TeamDebateRoom.tsx
  • frontend/src/Pages/ViewDebate.tsx
  • frontend/src/components/ChatRoom.tsx
  • frontend/src/components/Matchmaking.tsx
  • frontend/src/components/TeamChatSidebar.tsx
  • frontend/src/context/theme-provider.tsx
  • frontend/src/hooks/useDebateWS.ts
  • frontend/src/hooks/useUser.ts
  • frontend/src/services/gamificationService.ts
  • frontend/src/services/teamDebateService.ts
  • frontend/src/utils/auth.ts
  • frontend/src/utils/safeParse.ts
  • frontend/src/utils/storage.ts
🧰 Additional context used
🧬 Code graph analysis (15)
frontend/src/Pages/Admin/AdminDashboard.tsx (2)
frontend/src/utils/storage.ts (2)
  • getLocalString (1-10)
  • getLocalJSON (12-26)
frontend/src/services/adminService.ts (1)
  • Admin (3-8)
frontend/src/hooks/useDebateWS.ts (3)
frontend/src/utils/storage.ts (2)
  • getLocalString (1-10)
  • setLocalString (37-44)
frontend/src/utils/safeParse.ts (1)
  • safeParse (1-11)
backend/internal/debate/events.go (1)
  • Event (9-13)
frontend/src/Pages/TeamDebateRoom.tsx (2)
frontend/src/utils/safeParse.ts (1)
  • safeParse (1-11)
frontend/src/utils/storage.ts (1)
  • getLocalString (1-10)
frontend/src/Pages/Leaderboard.tsx (2)
frontend/src/utils/storage.ts (1)
  • getLocalString (1-10)
frontend/src/services/gamificationService.ts (1)
  • fetchGamificationLeaderboard (41-55)
frontend/src/Pages/StrengthenArgument.tsx (1)
backend/models/coach.go (2)
  • WeakStatement (4-9)
  • Evaluation (18-21)
frontend/src/Pages/DebateRoom.tsx (2)
frontend/src/utils/storage.ts (1)
  • getLocalString (1-10)
frontend/src/utils/safeParse.ts (1)
  • safeParse (1-11)
frontend/src/components/ChatRoom.tsx (1)
frontend/src/utils/safeParse.ts (1)
  • safeParse (1-11)
frontend/src/components/TeamChatSidebar.tsx (1)
frontend/src/utils/safeParse.ts (1)
  • safeParse (1-11)
frontend/src/hooks/useUser.ts (1)
frontend/src/utils/storage.ts (2)
  • getLocalString (1-10)
  • setLocalJSON (28-35)
frontend/src/Pages/CommunityFeed.tsx (1)
frontend/src/utils/storage.ts (1)
  • getLocalString (1-10)
frontend/src/Pages/OnlineDebateRoom.tsx (2)
frontend/src/utils/safeParse.ts (1)
  • safeParse (1-11)
frontend/src/utils/storage.ts (1)
  • getLocalString (1-10)
frontend/src/services/gamificationService.ts (1)
backend/models/gamification.go (1)
  • GamificationEvent (47-55)
frontend/src/components/Matchmaking.tsx (3)
frontend/src/utils/safeParse.ts (1)
  • safeParse (1-11)
backend/websocket/matchmaking.go (1)
  • MatchmakingMessage (48-56)
backend/services/matchmaking.go (1)
  • MatchmakingPool (16-25)
frontend/src/context/theme-provider.tsx (1)
frontend/src/utils/storage.ts (2)
  • getLocalString (1-10)
  • setLocalString (37-44)
frontend/src/services/teamDebateService.ts (2)
frontend/src/utils/auth.ts (1)
  • getAuthToken (7-9)
frontend/src/utils/storage.ts (1)
  • getLocalString (1-10)
🪛 Gitleaks (8.30.0)
backend/config/config.prod.yml

[high] 10-10: Uncovered a GCP API key, which could lead to unauthorized access to Google Cloud services and data breaches.

(gcp-api-key)

backend/config/config.prod.sample.yml

[high] 10-10: Uncovered a GCP API key, which could lead to unauthorized access to Google Cloud services and data breaches.

(gcp-api-key)

🔇 Additional comments (27)
backend/config/config.prod.sample.yml (1)

1-43: Configuration changes are unrelated to PR objectives.

The PR objectives state this PR is about "fixing unsafe error handling for persisted and incoming JSON to prevent runtime crashes" in the frontend, but these backend configuration changes are unrelated. Consider:

  • Moving these config changes to a separate PR
  • Explaining why backend config updates are included in a frontend safety PR
frontend/src/Pages/StrengthenArgument.tsx (1)

128-136: Excellent defensive parsing and validation.

This implementation follows best practices by:

  • Safely handling JSON.parse failures
  • Validating all required fields before use
  • Providing clear error messages

This pattern effectively prevents runtime crashes from malformed data.

frontend/src/utils/storage.ts (3)

1-10: LGTM: Well-designed SSR-safe wrapper.

The function correctly handles SSR environments, localStorage access errors, and null values with appropriate fallback behavior.


28-35: LGTM: Consistent error handling.

The function correctly handles SSR environments and localStorage quota/access errors with appropriate logging.


37-44: LGTM: Consistent pattern with setLocalJSON.

The function provides the same SSR-safety and error handling as the JSON variant.

frontend/src/services/teamDebateService.ts (1)

8-12: LGTM: Consistent adoption of storage utilities.

The migration to getLocalString aligns with the PR's goal of centralizing storage access with SSR-safety and error handling. The empty string fallback maintains the function's contract.

frontend/src/context/theme-provider.tsx (1)

2-2: LGTM: Clean migration to storage utilities.

The theme persistence logic correctly uses the new storage helpers while maintaining the existing behavior of converting the enum value to a string for storage.

Also applies to: 31-31, 59-59

frontend/src/Pages/CommunityFeed.tsx (1)

3-3: LGTM: Consistent token retrieval pattern.

All token accesses now use the safe storage wrapper, maintaining existing null checks and error handling. The migration is thorough and consistent across all three usage sites in the file.

Also applies to: 282-282, 360-360, 406-406

frontend/src/Pages/Admin/AdminDashboard.tsx (2)

21-21: LGTM: Clean migration to typed storage utilities.

The admin data retrieval now uses getLocalJSON<Admin> which handles both parsing and type hinting, eliminating the need for manual JSON.parse calls. The token retrieval uses getLocalString consistently. All existing null checks are preserved.

Also applies to: 368-376


508-513: LGTM: Fallback token retrieval for refresh.

The refresh handler correctly falls back to getLocalString("adminToken") if the component state token is null, ensuring the refresh functionality works even if state hasn't been initialized yet.

frontend/src/hooks/useUser.ts (1)

91-91: LGTM: Correct usage of storage utilities.

Line 91 correctly uses setLocalJSON to persist the user cache, and lines 110-111 properly use getLocalString for token checks in authentication state derivation.

Also applies to: 110-111

frontend/src/utils/auth.ts (2)

3-9: LGTM: Consistent token storage and retrieval.

Both setAuthToken and getAuthToken correctly use the storage utilities, providing SSR-safety and error handling for token operations.


11-15: LGTM: Appropriate error handling for removal.

The manual localStorage.removeItem with try/catch is appropriate since there's no dedicated removal helper in the storage utilities. The silent error swallowing is acceptable for cleanup operations.

frontend/src/components/Matchmaking.tsx (1)

73-82: Good use of typed interfaces with safeParse.

This is an excellent example of using safeParse with proper TypeScript types. The MatchmakingMessage interface provides compile-time safety while safeParse provides runtime safety. The nested parse on line 81 appropriately handles the pool field which can be either a pre-parsed array or a JSON string.

frontend/src/Pages/TeamDebateRoom.tsx (3)

737-741: Excellent use of typed safeParse with proper null handling.

This is a model implementation showing:

  • Typed interface WSMessage for compile-time safety
  • safeParse for runtime safety
  • Proper null check and early return
  • Clear warning message for debugging

This pattern should be adopted in Game.tsx, ViewDebate.tsx, and gamificationService.ts.


1455-1458: Good use of getLocalString for safe storage access.

The migration from localStorage.getItem to getLocalString adds SSR-safety and error handling. The fallback chain (localStorage → speechTranscripts → "No response") provides robust resilience.


1487-1495: Robust error handling for judgment parsing.

The safeParse usage with explicit null checking and user-facing error messages demonstrates good defensive programming. The fallback error message on line 1494 ensures users aren't left in an undefined state.

frontend/src/hooks/useDebateWS.ts (4)

16-17: LGTM! Clean utility imports.

The new storage and parsing utilities are correctly imported and will be used throughout this file to prevent unsafe access patterns.


80-84: LGTM! Safe localStorage access with proper fallback.

The use of getLocalString and setLocalString provides robust error handling for localStorage access, and the UUID generation ensures a valid spectatorId is always available.


105-105: LGTM! Consistent safe storage pattern.

The triple fallback (spectatorHash || getLocalString('spectatorHash') || '') ensures the join message always has a defined value while maintaining the existing behavior.


117-121: LGTM! Robust WebSocket message parsing.

The use of safeParse with a null check and early return prevents crashes from malformed WebSocket messages. The warning log aids debugging without disrupting the connection.

frontend/src/Pages/OnlineDebateRoom.tsx (4)

15-15: LGTM! Required utility import.

The safeParse import is used consistently throughout this file to handle JSON parsing safely.


559-567: LGTM! Safe judgment parsing with proper error handling.

The combination of safeParse with null checking and user-friendly error messaging ensures the app remains stable even when the backend returns malformed judgment data.


662-665: LGTM! Consistent safe parsing pattern.

The null check with warning log maintains consistency with the polling path and allows the caller to handle parse failures gracefully.


1126-1130: LGTM! Robust WebSocket message handling.

The safeParse with type parameter and early return ensures only valid messages are processed, preventing crashes from malformed or unexpected WebSocket data.

frontend/src/Pages/DebateRoom.tsx (2)

2-2: LGTM! Required utility imports.

Both safeParse and getLocalString are correctly imported for use in state initialization and judgment parsing.

Also applies to: 4-4


585-597: LGTM! Multi-layered parsing strategy with good fallback logic.

The judgment parsing uses safeParse with a fallback that attempts to fix common JSON formatting issues before retrying. This provides resilience against slightly malformed responses while maintaining consistency with the PR's safe parsing approach.

@Devnil434
Copy link
Author

please review and merge it @Zahnentferner, @varunchitre15, @saisankargochhayat, @heychirag

@Devnil434 Devnil434 closed this by deleting the head repository Jan 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant