fix(realtime_client): detect dropped connections on iOS via WebSocket ping#1451
Merged
Conversation
… ping The native WebSocket was created without a pingInterval, so a silently dropped connection was never detected at the socket layer. On Android the OS surfaces the dead TCP connection promptly (close code 1002), but on iOS writes are buffered instead of surfacing a reset, so no channelError ever propagated and the stream went silently stale. Enable pingInterval on the native WebSocket so it probes the peer and closes the connection when a pong is not received, flowing through the same error and reconnect path Android already uses. This makes disconnect behavior consistent across platforms. Closes #1071
Add a regression test using a raw WebSocket server that completes the handshake but ignores ping frames, asserting the native transport closes the connection when no pong is received. Expose pingInterval as an optional, typedef-compatible parameter on createWebSocketClient so the test can inject a short interval.
Collaborator
|
Just as a sidenote, I detected some similar issues on web with reconnecting and especially channels rejoining behavior being inefficient. Changes are still local, but should be able to create pr this week. |
The capability matrix validator flagged Constants.defaultWebSocketPingInterval as a new public API symbol. The ping interval is an internal default of the native transport, so make it a private constant in websocket_io.dart instead of exposing it on the public Constants class.
grdsdev
approved these changes
Jun 22, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Enable
pingIntervalon the native (dart:io) WebSocket so dropped connections are detected consistently across platforms.Why
Fixes #1071. When the internet drops, Android emits a
RealtimeSubscribeException(close code 1002) but iOS emits nothing, leaving the stream silently stale.Root cause:
createWebSocketClientcreated the native WebSocket without apingInterval, so a silently dropped connection was never detected at the socket layer:onDone/onErrorfires and_triggerChanErrorpropagates achannelError.conn.sink.close(), which does not reliably fireonDoneon a dead iOS socket. So nothing propagated to the channel.How
Set
pingInterval(newConstants.defaultWebSocketPingInterval, 25s, aligned with the existing heartbeat cadence) on the native WebSocket.dart:ionow actively probes the peer and closes the connection (goingAway) when a pong is not received, which flows through the sameonDone → _onConnClose → _triggerChanError → reconnectpath Android already uses.Scope / safety
websocket_web.dart) is untouched, since browsers manage ping/pong internally.RealtimeClientOptions.transportare unaffected.WebSocketTransporttypedef is unchanged.Testing
dart analyzeclean on the changed files.socket_test,mock_test,channel_test, including the existing CHANNEL_ERROR-on-heartbeat-timeout test).No unit test added:
pingIntervalis a property of the underlyingdart:iosocket and is not observable throughIOWebSocketChannelwithout a live connection, and the codebase does not test the default transport (tests inject mocks).