Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 61 additions & 19 deletions app/src/hooks/useChatRoom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export function useChatRoom(
const expiresAtRef = useRef<number>(0)
const joinedMeetingRef = useRef<typeof meeting | null>(null)
const hasJoinedRef = useRef(false)
const hasLeftRef = useRef(false)
const expiryTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
const expiryFinalRef = useRef<ReturnType<typeof setTimeout> | null>(null)
const countdownRef = useRef<ReturnType<typeof setInterval> | null>(null)
Expand Down Expand Up @@ -208,7 +209,26 @@ export function useChatRoom(
setMessages([...mapped])
}

const doExpireLeave = () => {
if (hasLeftRef.current) return
hasLeftRef.current = true
setExpiryWarning(
"Room session expired. Please re-open the link to rejoin."
)
meeting.leaveRoom().catch(() => {})
if (typeof window !== "undefined") {
window.location.href = "/"
}
}

const onRoomLeft = ({ state }: { state: string }) => {
if (hasLeftRef.current) return
const isExpired =
expiresAtRef.current > 0 && Date.now() >= expiresAtRef.current
if (isExpired) {
doExpireLeave()
return
}
if (state === "disconnected") {
setConnectionStatus("reconnecting")
} else if (state === "failed") {
Expand Down Expand Up @@ -237,31 +257,51 @@ export function useChatRoom(
})
hasJoinedRef.current = true

const joinedAt = Date.now()
const onVisibilityChange = () => {
if (document.visibilityState === "visible" && expiresAtRef.current > 0) {
const remaining = Math.max(
0,
Math.floor((expiresAtRef.current - Date.now()) / 1000)
)
setTimeLeft(remaining)
if (remaining === 0) doExpireLeave()
}
}
document.addEventListener("visibilitychange", onVisibilityChange)

countdownRef.current = setInterval(() => {
const base = expiresAtRef.current || joinedAt + 2 * 60 * 60 * 1000
const base = expiresAtRef.current
if (!base) return
const remaining = Math.max(0, Math.floor((base - Date.now()) / 1000))
setTimeLeft(remaining)
if (remaining === 0 && countdownRef.current) {
clearInterval(countdownRef.current)
if (remaining === 0) {
clearInterval(countdownRef.current!)
countdownRef.current = null
doExpireLeave()
}
}, 1000)

expiryTimerRef.current = setTimeout(() => {
setExpiryWarning(
"This room will expire in 10 minutes. Copy the link and re-open to continue."
)
}, 6600 * 1000)

expiryFinalRef.current = setTimeout(() => {
setExpiryWarning(
"Room session expired. Please re-open the link to rejoin."
)
meeting.leaveRoom().catch(() => {})
if (typeof window !== "undefined") {
window.location.href = "/"
const scheduleWarning = () => {
if (!expiresAtRef.current) return
const warnDelay = expiresAtRef.current - Date.now() - 10 * 60 * 1000
if (warnDelay <= 0) {
setExpiryWarning(
"This room will expire in 10 minutes. Copy the link and re-open to continue."
)
return
}
}, 7200 * 1000)
expiryTimerRef.current = setTimeout(() => {
setExpiryWarning(
"This room will expire in 10 minutes. Copy the link and re-open to continue."
)
}, warnDelay)
}
scheduleWarning()

const finalDelay = expiresAtRef.current
? Math.max(0, expiresAtRef.current - Date.now())
: 7200 * 1000
expiryFinalRef.current = setTimeout(doExpireLeave, finalDelay)

meeting.self.on("roomLeft", onRoomLeft)
meeting.self.on("roomJoined", onRoomJoined)
Expand All @@ -277,6 +317,7 @@ export function useChatRoom(
buildParticipants()

return () => {
document.removeEventListener("visibilitychange", onVisibilityChange)
meeting.self.off("roomLeft", onRoomLeft)
meeting.self.off("roomJoined", onRoomJoined)
;(meeting.meta as any).off("socketConnectionUpdate", onSocketUpdate)
Expand All @@ -287,8 +328,9 @@ export function useChatRoom(
meeting.self.off("screenShareUpdate", buildParticipants)
meeting.participants.joined.off("screenShareUpdate", buildParticipants)
meeting.chat.off("chatUpdate", syncMessages)
if (hasJoinedRef.current) meeting.leaveRoom()
if (hasJoinedRef.current && !hasLeftRef.current) meeting.leaveRoom()
hasJoinedRef.current = false
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

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 = false

hasLeftRef.current = false
if (expiryTimerRef.current) clearTimeout(expiryTimerRef.current)
if (expiryFinalRef.current) clearTimeout(expiryFinalRef.current)
if (countdownRef.current) clearInterval(countdownRef.current)
Expand Down
5 changes: 3 additions & 2 deletions app/src/pages/api/token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const MAX_NAME_LENGTH = 32
const ROOM_MAX_AGE_MS = 2 * 60 * 60 * 1000 // 2 hours
const RATE_LIMIT_WINDOW_S = 60
const RATE_LIMIT_MAX = 20
const ROOM_KV_TTL_S = 4 * 3600

function rtkBase(env: Env) {
return `https://api.cloudflare.com/client/v4/accounts/${env.CF_ACCOUNT_ID}/realtime/kit/${env.RTK_APP_ID}`
Expand Down Expand Up @@ -99,7 +100,7 @@ async function getOrCreateMeeting(
botEnabled: record.botEnabled || enableBot,
}
await env.ROOMS_KV.put(key, JSON.stringify(upgraded), {
expirationTtl: 30 * 24 * 3600,
expirationTtl: ROOM_KV_TTL_S,
})
return {
meetingId: record.meetingId,
Expand Down Expand Up @@ -139,7 +140,7 @@ async function getOrCreateMeeting(
botEnabled: enableBot,
}
await env.ROOMS_KV.put(key, JSON.stringify(record), {
expirationTtl: 30 * 24 * 3600,
expirationTtl: ROOM_KV_TTL_S,
})
return {
meetingId,
Expand Down
Loading