Private
Public Access
Send push notifications when the app is backgrounded, not just closed (#31)
Root cause (found earlier): the server only pushes to users it believes are "connected" to a room, but that was a raw WebSocket- connection check with no concept of whether the tab was actually foregrounded -- a backgrounded-but-still-connected tab looked exactly like someone actively watching, so the push got suppressed even though nothing could surface on a hidden page. No backend change needed: the server's presence tracking (and the push-suppression logic built on it) was already correct for "not joined to this room's channel" -- the gap was purely that the client never told it about backgrounding. useChatSocket.ts now tracks document.visibilityState and sends "leave" for every desired room when hidden (without forgetting the app still wants them joined), and "join" again when visible -- reusing the exact join/leave path a real room switch already goes through, no new WS message type or backend logic required. Rejoining also triggers a message-history refetch in ChatPane (keyed off the server's existing "joined" ack), so anything sent while backgrounded gets backfilled instead of silently missing -- as a side effect, this also fixes reconnect-after-a-dropped-connection never backfilling either, which had the same gap. Verified end-to-end: simulated backgrounding in the browser and confirmed via direct Redis inspection that the room's presence hash (what push-suppression actually reads) goes empty on hide and repopulates with a message resync on show. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -23,8 +23,11 @@ export function ChatPane({ room, members, isMobile, onBack, onToggleInfo, infoOp
|
||||
const [wsError, setWsError] = useState<string | null>(null)
|
||||
const [historyUnavailableOffline, setHistoryUnavailableOffline] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
setHistory([])
|
||||
const refreshHistory = useCallback(() => {
|
||||
// live is cleared alongside history, not just on room switch: it's
|
||||
// superseded by this fetch fully replacing history with the current
|
||||
// authoritative list, so anything already in live would otherwise
|
||||
// render twice once history was fetched.
|
||||
setLive([])
|
||||
setWsError(null)
|
||||
setHistoryUnavailableOffline(false)
|
||||
@@ -39,6 +42,15 @@ export function ChatPane({ room, members, isMobile, onBack, onToggleInfo, infoOp
|
||||
})
|
||||
}, [room.id])
|
||||
|
||||
useEffect(() => {
|
||||
// Blanks the previous room's messages immediately, rather than leaving
|
||||
// them on screen until the fetch resolves -- refreshHistory itself
|
||||
// deliberately doesn't do this (a resync on the *same* room shouldn't
|
||||
// flash empty while refetching).
|
||||
setHistory([])
|
||||
refreshHistory()
|
||||
}, [room.id, refreshHistory])
|
||||
|
||||
useEffect(() => {
|
||||
socket.joinRoom(room.id)
|
||||
return () => socket.leaveRoom(room.id)
|
||||
@@ -53,7 +65,16 @@ export function ChatPane({ room, members, isMobile, onBack, onToggleInfo, infoOp
|
||||
// that misattributing one to the wrong room's banner isn't worth
|
||||
// guarding against separately.
|
||||
socket.subscribe((envelope: ServerEnvelope) => {
|
||||
if (envelope.type === 'message' && envelope.room_id === room.id) {
|
||||
if (envelope.type === 'joined' && envelope.room_id === room.id) {
|
||||
// Fires on initial join (a harmless redundant fetch right after
|
||||
// the mount effect's own) and, more importantly, on every
|
||||
// rejoin -- coming back from a backgrounded tab (see
|
||||
// useChatSocket's visibility handling) or reconnecting after a
|
||||
// dropped connection. Either way, messages could have arrived
|
||||
// while this socket wasn't in the room's channel, so resync
|
||||
// instead of trusting whatever's already in state.
|
||||
refreshHistory()
|
||||
} else if (envelope.type === 'message' && envelope.room_id === room.id) {
|
||||
setLive((prev) => [...prev, envelope])
|
||||
} else if (envelope.type === 'message_update' && envelope.room_id === room.id) {
|
||||
setHistory((prev) =>
|
||||
@@ -77,7 +98,7 @@ export function ChatPane({ room, members, isMobile, onBack, onToggleInfo, infoOp
|
||||
setWsError(envelope.detail)
|
||||
}
|
||||
}),
|
||||
[socket, room.id],
|
||||
[socket, room.id, refreshHistory],
|
||||
)
|
||||
|
||||
const connected = socket.connected
|
||||
|
||||
Reference in New Issue
Block a user