-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Spec complete for Connection in line with [1] @ commit bf536c8
[1] - ably/specification#227 Note: CHA-CS5a3 has a typo in the spec. Omitted should be emitted. The typo has been fixed in the in-line spec comment in this commit.
- Loading branch information
1 parent
82cb9ff
commit 7509486
Showing
10 changed files
with
372 additions
and
9 deletions.
There are no files selected for viewing
This file contains 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
This file contains 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
This file contains 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
This file contains 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
This file contains 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
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
import Ably | ||
|
||
internal final class DefaultConnection: Connection { | ||
// (CHA-CS2a) The chat client must expose its current connection status, a single value from the list in CHA-CS1. | ||
internal let status: ConnectionStatus | ||
// (CHA-CS2b) The chat client must expose the latest error, if any, associated with its current status. | ||
internal let error: ARTErrorInfo? | ||
|
||
private let realtime: any RealtimeClientProtocol | ||
private let timerManager = TimerManager() | ||
|
||
internal init(realtime: any RealtimeClientProtocol) { | ||
self.realtime = realtime | ||
// (CHA-CS3) The initial status and error of the connection will be whatever status the realtime client returns whilst the connection status object is constructed. | ||
status = .init(from: realtime.connection.state) | ||
error = realtime.connection.errorReason | ||
} | ||
|
||
// (CHA-CS4d) Clients must be able to register a listener for connection status events and receive such events. | ||
internal func onStatusChange(bufferingPolicy: BufferingPolicy) -> Subscription<ConnectionStatusChange> { | ||
let subscription = Subscription<ConnectionStatusChange>(bufferingPolicy: bufferingPolicy) | ||
|
||
// (CHA-CS5) The chat client must monitor the underlying realtime connection for connection status changes. | ||
realtime.connection.on { [weak self] stateChange in | ||
guard let self else { | ||
return | ||
} | ||
let currentState = ConnectionStatus(from: stateChange.current) | ||
let previousState = ConnectionStatus(from: stateChange.previous) | ||
if currentState == status { | ||
return | ||
} | ||
|
||
// (CHA-CS4a) Connection status update events must contain the newly entered connection status. | ||
// (CHA-CS4b) Connection status update events must contain the previous connection status. | ||
// (CHA-CS4c) Connection status update events must contain the connection error (if any) that pertains to the newly entered connection status. | ||
let statusChange = ConnectionStatusChange( | ||
current: currentState, | ||
previous: previousState, | ||
error: stateChange.reason, | ||
retryIn: stateChange.retryIn | ||
) | ||
|
||
Task { | ||
let isTimerRunning = await timerManager.hasRunningTask() | ||
// (CHA-CS5a) The chat client must suppress transient disconnection events. It is not uncommon for Ably servers to perform connection shedding to balance load, or due to retiring. Clients should not need to concern themselves with transient events. | ||
|
||
// (CHA-CS5a2) If a transient disconnect timer is active and the realtime connection status changes to `DISCONNECTED` or `CONNECTING`, the library must not emit a status change. | ||
if isTimerRunning, currentState == .disconnected || currentState == .connecting { | ||
return | ||
} | ||
|
||
// (CHA-CS5a3) If a transient disconnect timer is active and the realtime connections status changes to `CONNECTED`, `SUSPENDED` or `FAILED`, the library shall cancel the transient disconnect timer. The superseding status change shall be emitted. | ||
if isTimerRunning, currentState == .connected || currentState == .suspended || currentState == .failed { | ||
await timerManager.cancelTimer() | ||
subscription.emit(statusChange) | ||
} | ||
|
||
// (CHA-CS5a1) If the realtime connection status transitions from `CONNECTED` to `DISCONNECTED`, the chat client connection status must not change. A 5 second transient disconnect timer shall be started. | ||
if previousState == .connected, currentState == .disconnected, !isTimerRunning { | ||
await timerManager.setTimer(interval: 5.0) { [timerManager] in | ||
Task { | ||
// (CHA-CS5a4) If a transient disconnect timer expires the library shall emit a connection status change event. This event must contain the current status of of timer expiry, along with the original error that initiated the transient disconnect timer. | ||
await timerManager.cancelTimer() | ||
subscription.emit(statusChange) | ||
} | ||
} | ||
return | ||
} | ||
|
||
if isTimerRunning { | ||
await timerManager.cancelTimer() | ||
} | ||
} | ||
|
||
// (CHA-CS5b) Not withstanding CHA-CS5a. If a connection state event is observed from the underlying realtime library, the client must emit a status change event. The current status of that event shall reflect the status change in the underlying realtime library, along with the accompanying error. | ||
subscription.emit(statusChange) | ||
} | ||
|
||
return subscription | ||
} | ||
} | ||
|
||
internal actor TimerManager { | ||
private var currentTask: Task<Void, Never>? | ||
|
||
internal func setTimer(interval: TimeInterval, handler: @escaping @Sendable () -> Void) { | ||
cancelTimer() | ||
|
||
currentTask = Task { | ||
try? await Task.sleep(nanoseconds: UInt64(interval * 1_000_000_000)) | ||
handler() | ||
} | ||
} | ||
|
||
internal func cancelTimer() { | ||
currentTask?.cancel() | ||
currentTask = nil | ||
} | ||
|
||
internal func hasRunningTask() -> Bool { | ||
currentTask != nil | ||
} | ||
} |
This file contains 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
Oops, something went wrong.