diff --git a/src/components/providers/presence-provider.tsx b/src/components/providers/presence-provider.tsx index ef73687..0dce434 100644 --- a/src/components/providers/presence-provider.tsx +++ b/src/components/providers/presence-provider.tsx @@ -44,7 +44,11 @@ export function PresenceProvider({ children }: { children: React.ReactNode }) { const disconnectForIdle = useCallback(() => { if (!isConnectedRef.current) return; isIdleRef.current = true; - socketService.disconnect(); + // Use pause() instead of disconnect() so subscriber handlers + // (useMessaging listeners) survive the reconnect. Otherwise the + // user would have to refresh the page after returning from idle + // to see new messages in real time again. + socketService.pause(); }, []); // Track socket connection state diff --git a/src/services/socket.service.ts b/src/services/socket.service.ts index a2492c1..e1c61c2 100644 --- a/src/services/socket.service.ts +++ b/src/services/socket.service.ts @@ -176,17 +176,18 @@ class SocketService { }); } + /** + * Fully disconnect and clear every subscriber handler. Use ONLY on logout + * — this invalidates every registered listener across the app. + * + * For transient disconnects (tab hidden, user idle), use `pause()` instead + * so React-registered listeners survive the reconnect. + */ disconnect(): void { - if (this.socket) { - this.socket.removeAllListeners(); - this.socket.disconnect(); - this.socket = null; - } + this.pause(); this.currentToken = null; - this.isConnecting = false; - this.connectPromise = null; - this.listenersAttached = false; - // Clear all handler sets to prevent leaks + // Clear all handler sets — only safe on logout because every subscriber + // must re-register after a fresh login. this.messageHandlers.clear(); this.typingStartHandlers.clear(); this.typingStopHandlers.clear(); @@ -199,6 +200,26 @@ class SocketService { this.authErrorHandlers.clear(); } + /** + * Tear down the socket without clearing subscriber handler sets. + * Used for idle/visibility disconnects so that when we reconnect, + * the existing React hooks' handlers automatically resume receiving + * events without needing to re-register. + */ + pause(): void { + if (this.socket) { + this.socket.removeAllListeners(); + this.socket.disconnect(); + this.socket = null; + } + this.isConnecting = false; + this.connectPromise = null; + this.listenersAttached = false; + // Intentionally keep: currentToken, messageHandlers, typingStart/StopHandlers, + // statusHandlers, readHandlers, deliveredHandlers, connectionRequest/ResponseHandlers, + // connectionHandlers, authErrorHandlers + } + isConnected(): boolean { return this.socket?.connected || false; }