developing #10

Merged
GHMiranda merged 64 commits from developing into master 2026-01-25 00:37:11 +00:00
1 changed files with 31 additions and 0 deletions
Showing only changes of commit c28b9ac565 - Show all commits

View File

@ -44,6 +44,7 @@ const RECONNECT_BASE_DELAY_MS = 1000
const RECONNECT_MAX_DELAY_MS = 30_000 const RECONNECT_MAX_DELAY_MS = 30_000
const INACTIVITY_TIMEOUT_MS = 5 * 60_000 // 5 minutes of inactivity before pausing const INACTIVITY_TIMEOUT_MS = 5 * 60_000 // 5 minutes of inactivity before pausing
const CONNECTION_TIMEOUT_MS = 15_000 // 15s timeout for initial connection const CONNECTION_TIMEOUT_MS = 15_000 // 15s timeout for initial connection
const STALE_THRESHOLD_MS = 60_000 // 60s of inactivity = force fresh reconnect on return
export class RealtimeConnection { export class RealtimeConnection {
private channel: RealtimeChannel | null = null private channel: RealtimeChannel | null = null
@ -58,6 +59,7 @@ export class RealtimeConnection {
private callbacks: RealtimeCallbacks private callbacks: RealtimeCallbacks
private isDestroyed = false private isDestroyed = false
private isPaused = false private isPaused = false
private lastActivityTime = Date.now()
private supabase = createClient() private supabase = createClient()
constructor(projectId: string, userId: string, displayName: string, callbacks: RealtimeCallbacks) { constructor(projectId: string, userId: string, displayName: string, callbacks: RealtimeCallbacks) {
@ -70,6 +72,7 @@ export class RealtimeConnection {
async connect(): Promise<void> { async connect(): Promise<void> {
if (this.isDestroyed) return if (this.isDestroyed) return
this.isPaused = false this.isPaused = false
this.lastActivityTime = Date.now()
this.callbacks.onConnectionStateChange('connecting') this.callbacks.onConnectionStateChange('connecting')
this.resetInactivityTimer() this.resetInactivityTimer()
this.clearConnectionTimer() this.clearConnectionTimer()
@ -244,15 +247,43 @@ export class RealtimeConnection {
/** /**
* Notify that user activity has occurred, resetting the inactivity timer. * Notify that user activity has occurred, resetting the inactivity timer.
* If the connection was paused due to inactivity, it will resume. * If the connection was paused due to inactivity, it will resume.
* If the connection was idle for a significant period, force a fresh reconnect
* to ensure the WebSocket isn't stale.
*/ */
notifyActivity(): void { notifyActivity(): void {
if (this.isDestroyed) return if (this.isDestroyed) return
const now = Date.now()
const idleDuration = now - this.lastActivityTime
this.lastActivityTime = now
this.resetInactivityTimer() this.resetInactivityTimer()
if (this.isPaused) { if (this.isPaused) {
this.resume() this.resume()
} else if (idleDuration > STALE_THRESHOLD_MS && this.channel) {
// Connection may appear alive but WebSocket could be stale after idle.
// Force a fresh reconnect to ensure broadcasts actually work.
this.forceReconnect()
} }
} }
/**
* Force a fresh reconnect by tearing down the current channel and reconnecting.
*/
private async forceReconnect(): Promise<void> {
if (this.isDestroyed || this.isPaused) return
this.stopHeartbeat()
this.clearReconnectTimer()
this.clearConnectionTimer()
if (this.channel) {
this.supabase.removeChannel(this.channel)
this.channel = null
}
this.reconnectAttempts = 0
await this.connect()
}
getIsPaused(): boolean { getIsPaused(): boolean {
return this.isPaused return this.isPaused
} }