Enhancement
To have a reliable Realtime experience, the client should handle connection issues by reconnecting to the socket and resubscribing to channels when it detects that the internet connection has been reestablished.
Use NWPathMonitor on Apple platforms.
Problem
The current Realtime implementation uses passive detection of network issues - it only discovers connectivity loss when WebSocket operations fail or heartbeats timeout (up to 25 seconds delay). This results in:
- Delayed detection: Up to 25 seconds before heartbeat timeout triggers reconnection
- No proactive reconnection: When network is restored, the SDK waits for the next scheduled operation to fail/succeed
- Poor user experience: Users may think the app is stuck when network returns but connection hasn't re-established
Current Behavior
| Event |
Detection Method |
Delay |
| Network lost |
WebSocket error or heartbeat timeout |
0-25 seconds |
| Network restored |
Next reconnection attempt succeeds |
7+ seconds (reconnect delay) |
Desired Behavior
| Event |
Detection Method |
Delay |
| Network lost |
NWPathMonitor callback |
Immediate |
| Network restored |
NWPathMonitor callback |
Immediate reconnect |
Root Cause
The SDK has no active network monitoring. All network-related code is reactive:
Sources/Realtime/WebSocket/URLSessionWebSocket.swift:214-231 - Maps network errors to WebSocket close codes (reactive)
Sources/Realtime/RealtimeClientV2.swift:291-301 - Reconnection only triggers on close/error events
Sources/Realtime/RealtimeClientV2.swift:446-506 - Heartbeat provides health checks but with 25-second intervals
No NWPathMonitor or Network.framework usage exists anywhere in the codebase.
Proposed Solution
Approach: Implement NetworkMonitor using NWPathMonitor
Create a new component that monitors network connectivity and triggers immediate reconnection when connectivity is restored.
Implementation Plan
1. Create NetworkMonitor class
New file: Sources/Realtime/NetworkMonitor.swift
#if canImport(Network)
import Network
@available(iOS 12.0, macOS 10.14, tvOS 12.0, watchOS 5.0, *)
final class NetworkMonitor: Sendable {
enum Status: Sendable {
case satisfied // Network available
case unsatisfied // No network
case requiresConnection // Network requires user action (captive portal)
}
private let monitor: NWPathMonitor
private let queue = DispatchQueue(label: "com.supabase.realtime.networkmonitor")
private let statusSubject = AsyncValueSubject<Status>(.unsatisfied)
var status: Status { statusSubject.value }
var statusChange: AsyncStream<Status> { statusSubject.values }
init(requiredInterfaceType: NWInterface.InterfaceType? = nil) {
if let interfaceType = requiredInterfaceType {
monitor = NWPathMonitor(requiredInterfaceType: interfaceType)
} else {
monitor = NWPathMonitor()
}
monitor.pathUpdateHandler = { [weak self] path in
let newStatus: Status
switch path.status {
case .satisfied:
newStatus = .satisfied
case .unsatisfied:
newStatus = .unsatisfied
case .requiresConnection:
newStatus = .requiresConnection
@unknown default:
newStatus = .unsatisfied
}
self?.statusSubject.yield(newStatus)
}
}
func start() {
monitor.start(queue: queue)
}
func cancel() {
monitor.cancel()
}
}
#endif
2. Add configuration option
In Sources/Realtime/Types.swift, add to RealtimeClientOptions:
/// Whether to monitor network connectivity and trigger immediate reconnection
/// when connectivity is restored. Uses NWPathMonitor on Apple platforms.
/// Default: true on supported Apple platforms, false elsewhere.
public var monitorNetworkConnectivity: Bool = defaultMonitorNetworkConnectivity
#if os(iOS) || os(macOS) || os(tvOS) || os(watchOS) || os(visionOS)
private static let defaultMonitorNetworkConnectivity = true
#else
private static let defaultMonitorNetworkConnectivity = false
#endif
3. Integrate into RealtimeClientV2
In Sources/Realtime/RealtimeClientV2.swift:
// Add to MutableState
#if canImport(Network)
@available(iOS 12.0, macOS 10.14, tvOS 12.0, watchOS 5.0, *)
var networkMonitor: NetworkMonitor?
var networkMonitorTask: Task<Void, Never>?
#endif
// Add to initializer (after other setup)
#if canImport(Network)
if #available(iOS 12.0, macOS 10.14, tvOS 12.0, watchOS 5.0, *),
options.monitorNetworkConnectivity {
setupNetworkMonitoring()
}
#endif
// New method
#if canImport(Network)
@available(iOS 12.0, macOS 10.14, tvOS 12.0, watchOS 5.0, *)
private func setupNetworkMonitoring() {
let monitor = NetworkMonitor()
mutableState.withValue {
$0.networkMonitor = monitor
$0.networkMonitorTask = Task { [weak self] in
var previousStatus: NetworkMonitor.Status?
for await status in monitor.statusChange {
guard let self else { return }
// Trigger reconnection when network transitions to satisfied
if status == .satisfied && previousStatus != .satisfied {
// Only reconnect if we were disconnected
if self.status == .disconnected {
await self.connect()
}
}
previousStatus = status
}
}
}
monitor.start()
}
#endif
// Add cleanup to deinit/disconnect
#if canImport(Network)
if #available(iOS 12.0, macOS 10.14, tvOS 12.0, watchOS 5.0, *) {
mutableState.withValue {
$0.networkMonitorTask?.cancel()
$0.networkMonitor?.cancel()
}
}
#endif
4. Expose network status to apps
Add public API for apps that want to observe network status:
/// Current network connectivity status.
/// Available on iOS 12+, macOS 10.14+, tvOS 12+, watchOS 5+.
#if canImport(Network)
@available(iOS 12.0, macOS 10.14, tvOS 12.0, watchOS 5.0, *)
public var networkStatus: NetworkMonitor.Status? {
mutableState.networkMonitor?.status
}
@available(iOS 12.0, macOS 10.14, tvOS 12.0, watchOS 5.0, *)
public var networkStatusChange: AsyncStream<NetworkMonitor.Status>? {
mutableState.networkMonitor?.statusChange
}
#endif
Files to Change
Implementation Notes
- Platform availability: NWPathMonitor requires iOS 12+, macOS 10.14+, tvOS 12+, watchOS 5+ - use
@available and #if canImport(Network)
- Thread safety: NWPathMonitor callbacks come on custom dispatch queue; use async/await bridge
- Debouncing: Network status can fluctuate rapidly - may need debouncing to avoid reconnect storms
- Integration with SDK-652: This complements background/foreground handling - both can work together
- Captive portals: Handle
.requiresConnection status appropriately (don't attempt reconnect)
Edge Cases
- Network fluctuates rapidly (wifi to cellular transitions) - debounce reconnection attempts
- Captive portal detected - don't attempt reconnection until resolved
- Already connecting when network status changes - don't double-connect
- Monitor started but connection intentionally disconnected by user - respect user intent
- VPN connects/disconnects - should trigger path update
Relationship to SDK-652
| Issue |
Trigger |
Use Case |
| SDK-652 |
App lifecycle (background/foreground) |
iOS suspends WebSocket when backgrounded |
| SDK-653 (this) |
Network connectivity change |
Airplane mode, wifi loss, cellular handoff |
Both issues can share infrastructure (e.g., RealtimeLifecycleManager) but address different scenarios.
Acceptance Criteria
Testing Strategy
Unit Tests
func testNetworkRestoredTriggersReconnect() async {
let client = RealtimeClientV2(...)
await client.connect()
// Simulate network loss (would need mock NetworkMonitor)
// ...
// Simulate network restored
// ...
// Verify reconnection was triggered
XCTAssertEqual(client.status, .connected)
}
func testNoReconnectOnCaptivePortal() async {
// When network status is .requiresConnection
// Should NOT attempt automatic reconnection
}
Manual Testing
- Connect to Realtime channel
- Enable Airplane mode
- Verify connection status changes to disconnected
- Disable Airplane mode
- Verify immediate reconnection (not waiting for heartbeat timeout)
- Verify channel events resume
Network Condition Simulator
Use Xcode's Network Link Conditioner or networksetup CLI to simulate:
- 100% packet loss (network unavailable)
- Restore to normal (network available)
- Verify reconnection timing is immediate vs. ~25 seconds without this feature
Research
Related Issues
- GitHub: #579
- Linear: SDK-652 (Background/foreground handling - complementary)
- Linear: SDK-277 (JS SDK app state handling)
Apple Documentation
Existing Reconnection Infrastructure
The SDK already has robust reconnection mechanics:
RealtimeClientV2.swift:291-301 - reconnect() method
RealtimeClientV2.swift:397-403 - Channel auto-rejoin
RealtimeClientV2.swift:589-626 - Send buffer mechanism
This enhancement just needs to call the existing connect() at the right time.
Current Network Error Handling
Already maps these URLSession errors to WebSocket close codes (URLSessionWebSocket.swift:214-231):
NSURLErrorTimedOut -> 1006
NSURLErrorNetworkConnectionLost -> 1006
NSURLErrorNotConnectedToInternet -> 1006
Generated with Claude Code /issue
Enhancement
To have a reliable Realtime experience, the client should handle connection issues by reconnecting to the socket and resubscribing to channels when it detects that the internet connection has been reestablished.
Use
NWPathMonitoron Apple platforms.Problem
The current Realtime implementation uses passive detection of network issues - it only discovers connectivity loss when WebSocket operations fail or heartbeats timeout (up to 25 seconds delay). This results in:
Current Behavior
Desired Behavior
Root Cause
The SDK has no active network monitoring. All network-related code is reactive:
Sources/Realtime/WebSocket/URLSessionWebSocket.swift:214-231- Maps network errors to WebSocket close codes (reactive)Sources/Realtime/RealtimeClientV2.swift:291-301- Reconnection only triggers on close/error eventsSources/Realtime/RealtimeClientV2.swift:446-506- Heartbeat provides health checks but with 25-second intervalsNo
NWPathMonitoror Network.framework usage exists anywhere in the codebase.Proposed Solution
Approach: Implement
NetworkMonitorusing NWPathMonitorCreate a new component that monitors network connectivity and triggers immediate reconnection when connectivity is restored.
Implementation Plan
1. Create
NetworkMonitorclassNew file:
Sources/Realtime/NetworkMonitor.swift2. Add configuration option
In
Sources/Realtime/Types.swift, add toRealtimeClientOptions:3. Integrate into RealtimeClientV2
In
Sources/Realtime/RealtimeClientV2.swift:4. Expose network status to apps
Add public API for apps that want to observe network status:
Files to Change
Sources/Realtime/NetworkMonitor.swift- NEW - NWPathMonitor wrapperSources/Realtime/Types.swift:15-67- AddmonitorNetworkConnectivityoptionSources/Realtime/RealtimeClientV2.swift:34-51- Add network monitor to MutableStateSources/Realtime/RealtimeClientV2.swift:120-168- Initialize and start network monitoringSources/Realtime/RealtimeClientV2.swift:512-533- Cleanup in disconnect/deinitTests/RealtimeTests/- Add tests for network monitoringImplementation Notes
@availableand#if canImport(Network).requiresConnectionstatus appropriately (don't attempt reconnect)Edge Cases
Relationship to SDK-652
Both issues can share infrastructure (e.g.,
RealtimeLifecycleManager) but address different scenarios.Acceptance Criteria
NWPathMonitorused on supported Apple platforms to detect network changes.requiresConnectionstatus (captive portals) - does not attempt blind reconnectRealtimeClientOptions.monitorNetworkConnectivity = falseTesting Strategy
Unit Tests
Manual Testing
Network Condition Simulator
Use Xcode's Network Link Conditioner or
networksetupCLI to simulate:Research
Related Issues
Apple Documentation
Existing Reconnection Infrastructure
The SDK already has robust reconnection mechanics:
RealtimeClientV2.swift:291-301-reconnect()methodRealtimeClientV2.swift:397-403- Channel auto-rejoinRealtimeClientV2.swift:589-626- Send buffer mechanismThis enhancement just needs to call the existing
connect()at the right time.Current Network Error Handling
Already maps these URLSession errors to WebSocket close codes (
URLSessionWebSocket.swift:214-231):NSURLErrorTimedOut-> 1006NSURLErrorNetworkConnectionLost-> 1006NSURLErrorNotConnectedToInternet-> 1006Generated with Claude Code /issue