fix: room expiry edge cases#58
Conversation
There was a problem hiding this comment.
Code Review
This pull request enhances the chat room expiration logic by introducing a hasLeftRef to track leave status, adding a visibility change listener to detect expiration upon tab focus, and refactoring timers to use dynamic expiration values. Additionally, the KV storage TTL for rooms has been reduced to 4 hours. Feedback suggests resetting the hasLeftRef in the cleanup function to ensure the hook remains robust if reused without a full page reload.
| meeting.chat.off("chatUpdate", syncMessages) | ||
| if (hasJoinedRef.current) meeting.leaveRoom() | ||
| if (hasJoinedRef.current && !hasLeftRef.current) meeting.leaveRoom() | ||
| hasJoinedRef.current = false |
There was a problem hiding this comment.
The hasLeftRef should be reset to false in the cleanup function, similar to how hasJoinedRef is reset. While the current implementation uses a hard redirect (window.location.href = "/") which re-initializes the hook state, resetting the ref explicitly ensures that the expiration logic remains correct if the hook is reused across different room sessions without a full page reload (e.g., if the redirect logic is changed in the future or if the hook is used in a context where navigation doesn't trigger a reload).
hasJoinedRef.current = false
hasLeftRef.current = falseThere was a problem hiding this comment.
Pull request overview
Fixes several edge cases in the room expiry flow: prevents double leaveRoom() calls, properly handles cron-initiated disconnects as expiry rather than reconnection attempts, recalibrates timers against the absolute expiry timestamp to handle device sleep and background-tab throttling, and reduces the KV TTL from 30 days to 4 hours to align with the 2-hour room lifetime.
Changes:
- Introduces
hasLeftRefguard plus a shareddoExpireLeave()helper so leave/redirect runs exactly once regardless of trigger (interval, final setTimeout, orroomLeftevent). - Recalibrates the warning and final-expiry timers against
expiresAtRefand adds avisibilitychangehandler so backgrounded/asleep tabs resynctimeLeftand expire correctly. - Reduces
ROOM_KV_TTL_Sto 4 hours so KV auto-purges dead room records and the cron'skv.list()stays small.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| app/src/pages/api/token.ts | Extracts ROOM_KV_TTL_S = 4h and uses it for both KV writes in place of the previous 30-day TTL. |
| app/src/hooks/useChatRoom.ts | Adds hasLeftRef + doExpireLeave(), expiry-aware onRoomLeft, visibility-change recalibration, and absolute-time-based warning/final timers. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Summary
Oracle analysis of the room expiry flow identified several bugs and edge cases.
Fixes
🔴 Bugs
Double
leaveRoom()(#6)Both the
setIntervalcountdown-to-zero path andexpiryFinalRefsetTimeout calledleaveRoom()+ redirect, causing the second call to throw on an already-destroyed meeting object. AddedhasLeftRefguard so the leave executes exactly once regardless of which path fires first.Cron disconnect shows "Reconnecting" instead of expiry message (#5)
When the cron job deletes participants, RTK fires a disconnect event and
onRoomLeftwas unconditionally settingconnectionStatus = "reconnecting", leaving users staring at a spinner with no explanation. Now checksDate.now() >= expiresAtRef.currentinonRoomLeft— if the room has expired, callsdoExpireLeave()and redirects home instead.🟡 Improvements
Warning timer drift (#7A)
expiryTimerRefwas hardcoded to6600 * 1000ms. If the device sleeps and resumes, the setTimeout fires late. Changed toexpiresAt - now - 10minso the delay is always calibrated to the absolute expiry time.Backgrounded tab throttling (#7B)
Browsers throttle
setIntervalin backgrounded tabs to ≥1 minute, causing the countdown to desync. Added avisibilitychangelistener that recalibratestimeLeftfromexpiresAtimmediately when the tab becomes visible again. If the room has already expired, callsdoExpireLeave()directly.KV TTL reduced from 30 days to 4 hours (#4)
Rooms live for 2 hours. Keeping KV records for 30 days was wasteful and caused the cron's
kv.list()to scan through large amounts of dead records. Changed to 4 hours (2h room lifetime + 2h buffer) so KV auto-purges stale records. Cron is kept as defense-in-depth but is no longer the primary cleanup mechanism.