Skip to content

perf: optimize Firestore subscriptions and room state updates#856

Open
Devexhhh wants to merge 1 commit into
vijaypatil477:mainfrom
Devexhhh:perf/optimize-room-subscriptions
Open

perf: optimize Firestore subscriptions and room state updates#856
Devexhhh wants to merge 1 commit into
vijaypatil477:mainfrom
Devexhhh:perf/optimize-room-subscriptions

Conversation

@Devexhhh

@Devexhhh Devexhhh commented Jun 26, 2026

Copy link
Copy Markdown

✦ Description

This PR improves the performance and scalability of the useRoom hook by optimizing Firestore subscriptions, reducing redundant state updates, and minimizing unnecessary synchronization during collaborative editing.

As the number of collaborators increases, the existing implementation may trigger excessive Firestore reads, repeated React state updates, and unnecessary component re-renders. These changes improve the efficiency of the current synchronization pipeline while preserving the existing collaboration behavior.

🚀 Changes Made

🔄 Firestore Subscription Optimizations

  • Reduced redundant Firestore snapshot processing.

  • Prevented unnecessary state updates when incoming room data has not changed.

  • Reduced duplicate updates triggered by repeated Firestore snapshots.

  • Improved subscription efficiency during active collaboration.

⚡ Presence & Cursor Optimizations

  • Batched presence and cursor synchronization to reduce Firestore write frequency.

  • Prevented duplicate cursor updates when cursor position remains unchanged.

  • Reduced unnecessary presence updates during continuous editing.

🧠 State Management Improvements

  • Memoized derived room permission values (isHost, isEditor, isReadOnly, etc.) to avoid unnecessary recomputation.

  • Reduced unnecessary React re-renders by avoiding duplicate state updates.

  • Optimized synchronization flow by only updating local state when values actually change.

🛠 Code Quality Improvements

  • Extracted synchronization timing values into reusable constants.

  • Improved cleanup of listeners and timers to avoid unnecessary work.

  • Refactored synchronization logic for improved readability and maintainability.

📈 Benefits

  • Reduced Firestore reads and writes.

  • Reduced unnecessary React renders.

  • Improved responsiveness in collaborative rooms.

  • Lower network overhead during active collaboration.

  • Better scalability for rooms with multiple active participants.

  • Improved maintainability of the synchronization logic.

Scope

This PR focuses on optimizing the existing Firestore subscription model.

It does not introduce architectural changes such as listener multiplexing, WebSocket synchronization, or alternative real-time backends. Instead, it improves the efficiency of the current Firestore-based collaboration system while maintaining backward compatibility.

Fixes #817


⟡ Type of Change

  • Bug fix (non-breaking change which fixes an issue)

  • New feature (non-breaking change which adds functionality)

  • Breaking change (fix or feature that would cause existing functionality to not work as expected)

  • Documentation update (non-breaking change to docs)

  • Code styling/formatting (prettier, eslint, spacing)


✦ Checklist

  • My code follows the style guidelines of this project.

  • I have performed a self-review of my code.

  • I have commented my code, particularly in hard-to-understand areas.

  • My changes generate no new warnings or console errors.

  • I have verified that my changes work correctly on both desktop and mobile viewports.

  • (If applicable) I have run npm run lint and npm run format locally before pushing.


    Additional Notes

    This PR addresses the performance concerns related to multiple Firestore subscriptions by reducing redundant snapshot processing, batching synchronization updates, and minimizing unnecessary React state changes.

    The implemented optimizations improve the scalability of the useRoom hook and reduce UI lag in larger collaborative sessions without altering the existing collaboration workflow or introducing breaking changes.

    Summary by CodeRabbit

    • Bug Fixes
      • Improved room state synchronization to reduce duplicate updates and unnecessary refreshes.
      • Made cursor and presence updates more reliable, helping keep collaborator activity in sync.
      • Improved cleanup when leaving a room or closing the page so stale activity is removed more consistently.
      • Streamlined access-related actions so the current room behavior is more stable.

@vercel

vercel Bot commented Jun 26, 2026

Copy link
Copy Markdown

@Devexhhh is attempting to deploy a commit to the omkh4242g-1671's projects Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions github-actions Bot added gssoc Official GSSoC '26 issue tag type:performance Performance and latency optimizations quality:exceptional Exceptional code quality contribution level:intermediate GSSoC '26 Intermediate difficulty issue gssoc:approved GSSoC '26 Approved issue labels Jun 26, 2026
@github-actions

Copy link
Copy Markdown

Hi @Devexhhh, thanks for contributing to Debugra! 🎉

I have automatically:

  • 👤 Assigned this PR to you.
  • 🏷️ Applied the gssoc:approved label.

Our workflows will now analyze your changes to classify:

  • 📈 PR Difficulty: level:*
  • 🧩 PR Type: type:*
  • 🌟 PR Quality: quality:*

Tip

Ensure your PR description references the issue it resolves (e.g. Closes #123). This allows the bot to inherit any additional labels from that issue!

Happy coding! 🚀

@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

useRoom now memoizes derived roles, debounces cursor and room writes, skips duplicate Firestore-driven updates, batches presence updates, and deletes cursor and activeUsers records during unload and leave flows while removing access-control callbacks.

Changes

useRoom synchronization updates

Layer / File(s) Summary
Role memoization and cursor debounce
src/hooks/useRoom.js
Adds sync-delay constants, memoizes room-derived roles, and debounces local cursor writes while skipping duplicate cursor positions.
Snapshot dedupe and room writes
src/hooks/useRoom.js
Skips duplicate remote cursor and room snapshot updates, and uses ROOM_SYNC_DELAY for local room writes with empty catch handlers.
Presence batching
src/hooks/useRoom.js
Rebuilds presence payloads only when activeFile changes and batches activeUsers writes with PRESENCE_BATCH_DELAY.
Leave cleanup and access callbacks
src/hooks/useRoom.js
Removes the current user from activeUsers, deletes the cursor doc on unload and leave, and replaces legacy access-control callbacks with no-ops.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

Hoppity hop, the room sync glows,
No double cursor trail that shows.
Batched-up paws and tidy writes,
Quiet leaves and cleaner nights. 🐇

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Replacing legacy access-control methods with no-op callbacks is unrelated to the performance issue and may change behavior. Remove or justify the access-control no-op changes in a separate PR unless they are required for #817.
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: optimizing Firestore subscriptions and room state updates.
Description check ✅ Passed The description includes a clear summary, linked issue, change type, checklist, and scope, matching the template well.
Linked Issues check ✅ Passed The PR implements the requested debounce, batching, memoization, and duplicate-update reductions for #817.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

Warning

⚠️ This pull request shows signs of AI-generated slop (defensive_cruft, ai_padded_prose). It has been flagged by CodeRabbit slop detection and should be reviewed carefully.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/hooks/useRoom.js (2)

398-406: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Don’t rely on async Firestore writes in beforeunload.

updateDoc and deleteDoc can be dropped when the page unloads, leaving stale active users/cursors. Use a heartbeat/TTL cleanup, pagehide/visibility handling, or server-side stale-presence pruning instead of treating unload cleanup as reliable.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/useRoom.js` around lines 398 - 406, The unload cleanup in useRoom’s
useEffect relies on async Firestore writes inside handleBeforeUnload, which may
be dropped during page teardown. Move presence cleanup away from beforeunload by
using a heartbeat/TTL approach, pagehide or visibilitychange handling, or
server-side stale presence pruning, and update the logic around
roomData.activeUsers, updateDoc, and deleteDoc so disconnect cleanup does not
depend on the unload event completing.

161-181: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Reset dedupe refs when room state is cleared or switched.

leaveRoom clears roomData, activeUsers, and remoteCursors, but prevRemoteCursorsRef, prevRoomRef, and prevActiveUsersRef keep the old snapshots. Rejoining the same unchanged room can skip the first snapshot and leave the hook in the cleared state until Firestore changes again.

🐛 Proposed fix
   useEffect(() => {
     if (!roomId) {
+      prevRemoteCursorsRef.current = {};
       setRemoteCursors({});
       return;
     }
+    prevRemoteCursorsRef.current = {};
     const cursorsRef = collection(db, 'rooms', roomId, 'cursors');

@@
   useEffect(() => {
-    if (!roomId) return;
+    if (!roomId) {
+      prevRoomRef.current = null;
+      prevActiveUsersRef.current = [];
+      return;
+    }
+    prevRoomRef.current = null;
+    prevActiveUsersRef.current = [];
     const unsub = onSnapshot(doc(db, 'rooms', roomId), (snap) => {

Also applies to: 188-231

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/useRoom.js` around lines 161 - 181, The dedupe refs in useRoom are
not being cleared when the room is left or switched, so leaveRoom can reset
state while prevRemoteCursorsRef, prevRoomRef, and prevActiveUsersRef still hold
stale snapshots. Update leaveRoom and any room-change cleanup logic to reset
those refs alongside roomData, activeUsers, and remoteCursors, so rejoining the
same room will not incorrectly skip the first Firestore snapshot. Make sure the
fix is applied in the useEffect/onSnapshot flow that tracks room, active user,
and cursor state.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/hooks/useRoom.js`:
- Around line 263-266: The update in useRoom is writing a stale copy of
activeUsers back to the room document, which can overwrite other users’ presence
changes. Refactor the activeUsers updates in the timer/unload/leave paths so
they do not rely on roomData snapshots; use a transaction that re-reads the room
immediately before updating activeUsers, or move presence to per-user documents.
Apply the same fix to the related activeUsers write sites in useRoom.
- Around line 416-421: The access-control callbacks in useRoom are currently
silent no-ops, which makes the hook expose functions that appear to succeed
while doing nothing. Update requestAccess, approveAccess, denyAccess,
revokeAccess, takeControl, and releaseControl to either perform the real
access/control actions or fail explicitly with a clear unsupported/error path,
and keep their behavior aligned with the rest of the collaboration workflow
returned by the hook. Ensure the implementation is wired through the existing
useRoom callback definitions rather than leaving the empty useCallback bodies in
place.
- Around line 428-431: The leaveRoom cleanup in useRoom is swallowing errors
with inner .catch(() => {}), so failures never reach the outer try/catch. Remove
the per-call catch handling around the deleteDoc and updateDoc operations, and
let those promises reject normally so leaveRoom can report failure through its
existing error path. Use the cleanup block in useRoom that updates room cursors
and activeUsers to make this change.
- Around line 130-145: The cursor dedupe in useRoom should not rely only on
line/col, because the same position can belong to a different room or user and
then the write is incorrectly skipped. Update the prevCursorRef guard inside the
useEffect in useRoom to include room/user identity (for example roomId and user
id/name) along with cursorPos, and reset or re-evaluate the cached cursor when
those identities change so a fresh cursor document is written after
room/user/editor-state transitions.
- Around line 204-235: The room snapshot listener in useRoom is being recreated
on every local edit because the onSnapshot effect depends on code, language, and
stdinValue. Refactor the listener so it reads the latest values from refs (or
another stable source) instead of effect dependencies, keeping the useEffect
that sets up onSnapshot stable. Update the comparisons in the snapshot callback
to use those refs, and keep roomId, user, and the setter functions as the only
dependencies needed for subscription setup.

---

Outside diff comments:
In `@src/hooks/useRoom.js`:
- Around line 398-406: The unload cleanup in useRoom’s useEffect relies on async
Firestore writes inside handleBeforeUnload, which may be dropped during page
teardown. Move presence cleanup away from beforeunload by using a heartbeat/TTL
approach, pagehide or visibilitychange handling, or server-side stale presence
pruning, and update the logic around roomData.activeUsers, updateDoc, and
deleteDoc so disconnect cleanup does not depend on the unload event completing.
- Around line 161-181: The dedupe refs in useRoom are not being cleared when the
room is left or switched, so leaveRoom can reset state while
prevRemoteCursorsRef, prevRoomRef, and prevActiveUsersRef still hold stale
snapshots. Update leaveRoom and any room-change cleanup logic to reset those
refs alongside roomData, activeUsers, and remoteCursors, so rejoining the same
room will not incorrectly skip the first Firestore snapshot. Make sure the fix
is applied in the useEffect/onSnapshot flow that tracks room, active user, and
cursor state.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c65a53df-e03c-4582-8375-f59c75d5cc1f

📥 Commits

Reviewing files that changed from the base of the PR and between f207660 and 838d2fb.

📒 Files selected for processing (1)
  • src/hooks/useRoom.js

Comment thread src/hooks/useRoom.js
Comment on lines +130 to +145
const prevCursorRef = useRef({ line: null, col: null });

useEffect(() => {
if (!roomId || !user || !isEditor || !cursorPos) return;

// 5. Skip duplicate cursor updates
if (
prevCursorRef.current.line === cursorPos.line &&
prevCursorRef.current.col === cursorPos.col
) {
return;
}

// 4. Batch cursor writes
const timer = setTimeout(() => {
prevCursorRef.current = { line: cursorPos.line, col: cursorPos.col };

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Key cursor dedupe by room and user, not only position.

Line 136 skips writes when the coordinates match the previous write. After leaving/joining another room, switching users, or regaining editor rights at the same cursor position, the new cursor doc may never be written.

🐛 Proposed fix
-  const prevCursorRef = useRef({ line: null, col: null });
+  const prevCursorRef = useRef({ roomId: null, uid: null, line: null, col: null });

@@
     if (
+      prevCursorRef.current.roomId === roomId &&
+      prevCursorRef.current.uid === user.uid &&
       prevCursorRef.current.line === cursorPos.line &&
       prevCursorRef.current.col === cursorPos.col
     ) {
       return;
     }

@@
-      prevCursorRef.current = { line: cursorPos.line, col: cursorPos.col };
+      prevCursorRef.current = {
+        roomId,
+        uid: user.uid,
+        line: cursorPos.line,
+        col: cursorPos.col,
+      };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const prevCursorRef = useRef({ line: null, col: null });
useEffect(() => {
if (!roomId || !user || !isEditor || !cursorPos) return;
// 5. Skip duplicate cursor updates
if (
prevCursorRef.current.line === cursorPos.line &&
prevCursorRef.current.col === cursorPos.col
) {
return;
}
// 4. Batch cursor writes
const timer = setTimeout(() => {
prevCursorRef.current = { line: cursorPos.line, col: cursorPos.col };
const prevCursorRef = useRef({ roomId: null, uid: null, line: null, col: null });
useEffect(() => {
if (!roomId || !user || !isEditor || !cursorPos) return;
// 5. Skip duplicate cursor updates
if (
prevCursorRef.current.roomId === roomId &&
prevCursorRef.current.uid === user.uid &&
prevCursorRef.current.line === cursorPos.line &&
prevCursorRef.current.col === cursorPos.col
) {
return;
}
// 4. Batch cursor writes
const timer = setTimeout(() => {
prevCursorRef.current = {
roomId,
uid: user.uid,
line: cursorPos.line,
col: cursorPos.col,
};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/useRoom.js` around lines 130 - 145, The cursor dedupe in useRoom
should not rely only on line/col, because the same position can belong to a
different room or user and then the write is incorrectly skipped. Update the
prevCursorRef guard inside the useEffect in useRoom to include room/user
identity (for example roomId and user id/name) along with cursorPos, and reset
or re-evaluate the cached cursor when those identities change so a fresh cursor
document is written after room/user/editor-state transitions.

Comment thread src/hooks/useRoom.js
Comment on lines +204 to +235
if (
data.code !== undefined &&
data.code !== code &&
data._lastEditor !== user?.uid
) {
setCode(data.code);
}

if (
data.language !== undefined &&
data.language !== language &&
data._lastEditor !== user?.uid
) {
setLanguage(data.language);
}

if (
data.stdin !== undefined &&
data.stdin !== stdinValue &&
data._lastEditor !== user?.uid
) {
setStdinValue(data.stdin);
}

const incomingUsers = data.activeUsers || [];
if (JSON.stringify(prevActiveUsersRef.current) !== JSON.stringify(incomingUsers)) {
prevActiveUsersRef.current = incomingUsers;
setActiveUsers(incomingUsers);
}
});
return unsub;
}, [roomId, user, setCode, setLanguage, setStdinValue]);
}, [roomId, user, code, language, stdinValue, setCode, setLanguage, setStdinValue]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

Keep the room snapshot listener stable during edits.

The listener reads code, language, and stdinValue, so every local edit tears down and recreates onSnapshot. That reintroduces listener churn and extra initial snapshots on the hot editing path this PR is trying to optimize.

⚡ Proposed fix
+  const latestLocalStateRef = useRef({ code, language, stdinValue });
+
+  useEffect(() => {
+    latestLocalStateRef.current = { code, language, stdinValue };
+  }, [code, language, stdinValue]);
+
@@
       if (
         data.code !== undefined &&
-        data.code !== code &&
+        data.code !== latestLocalStateRef.current.code &&
         data._lastEditor !== user?.uid
       ) {
         setCode(data.code);
       }
@@
         data.language !== undefined &&
-        data.language !== language &&
+        data.language !== latestLocalStateRef.current.language &&
         data._lastEditor !== user?.uid
       ) {
         setLanguage(data.language);
       }
@@
         data.stdin !== undefined &&
-        data.stdin !== stdinValue &&
+        data.stdin !== latestLocalStateRef.current.stdinValue &&
         data._lastEditor !== user?.uid
       ) {
         setStdinValue(data.stdin);
       }
@@
-  }, [roomId, user, code, language, stdinValue, setCode, setLanguage, setStdinValue]);
+  }, [roomId, user?.uid, setCode, setLanguage, setStdinValue]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (
data.code !== undefined &&
data.code !== code &&
data._lastEditor !== user?.uid
) {
setCode(data.code);
}
if (
data.language !== undefined &&
data.language !== language &&
data._lastEditor !== user?.uid
) {
setLanguage(data.language);
}
if (
data.stdin !== undefined &&
data.stdin !== stdinValue &&
data._lastEditor !== user?.uid
) {
setStdinValue(data.stdin);
}
const incomingUsers = data.activeUsers || [];
if (JSON.stringify(prevActiveUsersRef.current) !== JSON.stringify(incomingUsers)) {
prevActiveUsersRef.current = incomingUsers;
setActiveUsers(incomingUsers);
}
});
return unsub;
}, [roomId, user, setCode, setLanguage, setStdinValue]);
}, [roomId, user, code, language, stdinValue, setCode, setLanguage, setStdinValue]);
const latestLocalStateRef = useRef({ code, language, stdinValue });
useEffect(() => {
latestLocalStateRef.current = { code, language, stdinValue };
}, [code, language, stdinValue]);
if (
data.code !== undefined &&
data.code !== latestLocalStateRef.current.code &&
data._lastEditor !== user?.uid
) {
setCode(data.code);
}
if (
data.language !== undefined &&
data.language !== latestLocalStateRef.current.language &&
data._lastEditor !== user?.uid
) {
setLanguage(data.language);
}
if (
data.stdin !== undefined &&
data.stdin !== latestLocalStateRef.current.stdinValue &&
data._lastEditor !== user?.uid
) {
setStdinValue(data.stdin);
}
const incomingUsers = data.activeUsers || [];
if (JSON.stringify(prevActiveUsersRef.current) !== JSON.stringify(incomingUsers)) {
prevActiveUsersRef.current = incomingUsers;
setActiveUsers(incomingUsers);
}
});
return unsub;
}, [roomId, user?.uid, setCode, setLanguage, setStdinValue]);
🧰 Tools
🪛 ast-grep (0.44.0)

[warning] 230-230: Avoid using the initial state variable in setState
Context: setActiveUsers(incomingUsers)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(setstate-same-var)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/useRoom.js` around lines 204 - 235, The room snapshot listener in
useRoom is being recreated on every local edit because the onSnapshot effect
depends on code, language, and stdinValue. Refactor the listener so it reads the
latest values from refs (or another stable source) instead of effect
dependencies, keeping the useEffect that sets up onSnapshot stable. Update the
comparisons in the snapshot callback to use those refs, and keep roomId, user,
and the setter functions as the only dependencies needed for subscription setup.

Comment thread src/hooks/useRoom.js
Comment on lines +263 to +266
const timer = setTimeout(() => {
const newUsers = [...currentUsers];
newUsers[myIndex] = { ...newUsers[myIndex], activeFile: language };
updateDoc(doc(db, 'rooms', roomId), { activeUsers: newUsers }).catch(() => { });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Avoid overwriting activeUsers from stale snapshots.

These paths write the entire activeUsers array after reading it from roomData. With batching/unload/leave, another user can join, leave, or change activeFile before this write lands, and the stale array will clobber their update.

Prefer a per-user presence document or a transaction that re-reads the room immediately before updating activeUsers.

Also applies to: 404-406, 428-431

🧰 Tools
🪛 ast-grep (0.44.0)

[warning] 262-266: Avoid using the initial state variable in setState
Context: setTimeout(() => {
const newUsers = [...currentUsers];
newUsers[myIndex] = { ...newUsers[myIndex], activeFile: language };
updateDoc(doc(db, 'rooms', roomId), { activeUsers: newUsers }).catch(() => { });
}, PRESENCE_BATCH_DELAY)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(setstate-same-var)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/useRoom.js` around lines 263 - 266, The update in useRoom is
writing a stale copy of activeUsers back to the room document, which can
overwrite other users’ presence changes. Refactor the activeUsers updates in the
timer/unload/leave paths so they do not rely on roomData snapshots; use a
transaction that re-reads the room immediately before updating activeUsers, or
move presence to per-user documents. Apply the same fix to the related
activeUsers write sites in useRoom.

Comment thread src/hooks/useRoom.js
Comment on lines +416 to +421
const requestAccess = useCallback(() => { }, []);
const approveAccess = useCallback(() => { }, []);
const denyAccess = useCallback(() => { }, []);
const revokeAccess = useCallback(() => { }, []);
const takeControl = useCallback(() => { }, []);
const releaseControl = useCallback(() => { }, []);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Don’t leave public access-control callbacks as silent no-ops.

The hook still returns requestAccess, approveAccess, denyAccess, revokeAccess, takeControl, and releaseControl, but callers now get successful no-op callbacks. That silently breaks existing access/control UI flows while the PR objective says collaboration workflow is preserved.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/useRoom.js` around lines 416 - 421, The access-control callbacks in
useRoom are currently silent no-ops, which makes the hook expose functions that
appear to succeed while doing nothing. Update requestAccess, approveAccess,
denyAccess, revokeAccess, takeControl, and releaseControl to either perform the
real access/control actions or fail explicitly with a clear unsupported/error
path, and keep their behavior aligned with the rest of the collaboration
workflow returned by the hook. Ensure the implementation is wired through the
existing useRoom callback definitions rather than leaving the empty useCallback
bodies in place.

Comment thread src/hooks/useRoom.js
Comment on lines +428 to +431
await deleteDoc(doc(db, 'rooms', roomId, 'cursors', user.uid)).catch(() => { });
if (roomData) {
const newUsers = (roomData.activeUsers || []).filter((u) => u.uid !== user.uid);
await updateDoc(doc(db, 'rooms', roomId), { activeUsers: newUsers }).catch(() => {});
await updateDoc(doc(db, 'rooms', roomId), { activeUsers: newUsers }).catch(() => { });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Let leave cleanup failures reach the outer catch.

The inner .catch(() => {}) calls make the surrounding try/catch ineffective, so leaveRoom shows success even when cursor deletion or presence cleanup fails.

🐛 Proposed fix
-        await deleteDoc(doc(db, 'rooms', roomId, 'cursors', user.uid)).catch(() => { });
+        await deleteDoc(doc(db, 'rooms', roomId, 'cursors', user.uid));
         if (roomData) {
           const newUsers = (roomData.activeUsers || []).filter((u) => u.uid !== user.uid);
-          await updateDoc(doc(db, 'rooms', roomId), { activeUsers: newUsers }).catch(() => { });
+          await updateDoc(doc(db, 'rooms', roomId), { activeUsers: newUsers });
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
await deleteDoc(doc(db, 'rooms', roomId, 'cursors', user.uid)).catch(() => { });
if (roomData) {
const newUsers = (roomData.activeUsers || []).filter((u) => u.uid !== user.uid);
await updateDoc(doc(db, 'rooms', roomId), { activeUsers: newUsers }).catch(() => {});
await updateDoc(doc(db, 'rooms', roomId), { activeUsers: newUsers }).catch(() => { });
await deleteDoc(doc(db, 'rooms', roomId, 'cursors', user.uid));
if (roomData) {
const newUsers = (roomData.activeUsers || []).filter((u) => u.uid !== user.uid);
await updateDoc(doc(db, 'rooms', roomId), { activeUsers: newUsers });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/useRoom.js` around lines 428 - 431, The leaveRoom cleanup in
useRoom is swallowing errors with inner .catch(() => {}), so failures never
reach the outer try/catch. Remove the per-call catch handling around the
deleteDoc and updateDoc operations, and let those promises reject normally so
leaveRoom can report failure through its existing error path. Use the cleanup
block in useRoom that updates room cursors and activeUsers to make this change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gssoc:approved GSSoC '26 Approved issue gssoc Official GSSoC '26 issue tag level:intermediate GSSoC '26 Intermediate difficulty issue quality:exceptional Exceptional code quality contribution type:performance Performance and latency optimizations

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Optimize Firebase Room Subscription Performance (useRoom Hook)

1 participant