Skip to content

[2.x] fix(realtime): only force-reconnect on iOS visibilitychange#4662

Merged
imorland merged 1 commit into
2.xfrom
im/realtime-ios-only-force-reconnect
May 13, 2026
Merged

[2.x] fix(realtime): only force-reconnect on iOS visibilitychange#4662
imorland merged 1 commit into
2.xfrom
im/realtime-ios-only-force-reconnect

Conversation

@imorland
Copy link
Copy Markdown
Member

Summary

#4654 added a visibilitychange listener that constructs a fresh Pusher instance and refreshes the visible discussion list whenever the tab has been hidden for more than 5 seconds. That workaround was needed on iOS Safari, where backgrounding silently drops the WebSocket without firing close. On every other platform the WebSocket survives tab backgrounding fine — but the listener still fired unconditionally, so on desktop Firefox/Chrome (and Android) every "switch to another tab, come back" cycle triggered an unnecessary GET /api/discussions request and a full IndexPage refresh.

Fix

Gate the visibilitychange-triggered forceReconnect() on isIOS(). The pageshow(persisted=true) path stays unconditional — bfcache restore only happens on browsers that bfcached the page, and at that point the WebSocket was definitely torn down regardless of platform.

A new isIOS() utility is added rather than reusing the existing isSafariMobile() core helper, because isSafariMobile() excludes iOS Chrome (CriOS) and iOS Firefox (FxiOS) — both of which use WebKit on iOS and share the same backgrounding pathology this workaround was written for. isIOS() also detects iPadOS 13+ via the MacIntel + maxTouchPoints > 1 quirk (iPadOS reports navigator.platform === 'MacIntel' but desktop Macs have maxTouchPoints === 0).

Test plan

Regression from #4654.

PR #4654 added a `visibilitychange` listener that forces a fresh
Pusher instance and refreshes the visible discussion list whenever
the tab has been hidden for >5s. That was needed on iOS Safari,
where backgrounding the page silently drops the WebSocket without
firing `close`. On every other platform the WebSocket survives tab
backgrounding fine — but the visibilitychange handler still fired,
causing an unnecessary `GET /api/discussions` request and full list
re-render every time the user switched away and back.

Gate the visibilitychange-triggered `forceReconnect()` on `isIOS()`
so the workaround only runs on the platform that actually needs it.
The `pageshow(persisted=true)` path stays unconditional — bfcache
restoration only fires on browsers that bfcached the page, and the
WebSocket was definitely torn down by then regardless of platform.

`isIOS()` is broader than the existing `isSafariMobile()` core
utility because all iOS browsers use WebKit and share the same
backgrounding pathology — iOS Chrome (`CriOS`) and iOS Firefox
(`FxiOS`) are excluded by `isSafariMobile()` but still need this
workaround.

Regression from #4654.
@imorland imorland requested a review from a team as a code owner May 13, 2026 21:36
@imorland imorland added this to the 2.0.0-rc.2 milestone May 13, 2026
@imorland imorland merged commit 1932e3e into 2.x May 13, 2026
26 of 37 checks passed
@imorland imorland deleted the im/realtime-ios-only-force-reconnect branch May 13, 2026 22:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant