-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathConnection.swift
More file actions
247 lines (220 loc) · 7.85 KB
/
Connection.swift
File metadata and controls
247 lines (220 loc) · 7.85 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
import Ably
/**
* Represents a connection to Ably.
*/
@MainActor
public protocol Connection: AnyObject, Sendable {
/// The subscription type for connection status change listeners.
associatedtype StatusSubscription: AblyChat.StatusSubscription
/**
* The current status of the connection.
*
* - Returns: The current ``ConnectionStatus`` value
*
* ## Example
*
* ```swift
* import AblyChat
*
* let chatClient: ChatClient // existing ChatClient instance
*
* // Check connection status
* if chatClient.connection.status == .connected {
* print("Connected to Ably")
* } else if chatClient.connection.status == .failed {
* print("Connection failed")
* }
*
* // Use status for conditional logic
* func canAttachToRoom() -> Bool {
* return chatClient.connection.status == .connected
* }
* ```
*/
var status: ConnectionStatus { get }
/**
* The error that caused the connection to enter its current status, if any.
*
* - Returns: ``ErrorInfo`` if an error caused the current status, nil otherwise
*
* ## Example
*
* ```swift
* import AblyChat
*
* let chatClient: ChatClient // existing ChatClient instance
*
* // Check for connection errors
* if let error = chatClient.connection.error {
* print("Connection error: \(error.message)")
* print("Error code: \(error.code)")
* }
* // Monitor for errors during status changes
* chatClient.connection.onStatusChange { change in
* if let error = change.error {
* reportErrorToMonitoring(error)
* }
* }
* ```
*/
var error: ErrorInfo? { get }
/**
* Registers a listener to be notified of connection status changes.
*
* Status changes indicate the connection lifecycle, including connecting,
* connected, disconnected, suspended, and failed states. Use this to monitor
* connection health and handle network issues.
*
* - Parameters:
* - callback: Callback invoked when the connection status changes
*
* - Returns: Subscription object with an `off()` method to unregister
*
* ## Example
*
* ```swift
* import Ably
* import AblyChat
*
* let chatClient: ChatClient // existing ChatClient instance
*
* // Monitor connection status changes
* let subscription = chatClient.connection.onStatusChange { change in
* print("Connection: \(change.previous) -> \(change.current)")
*
* // Handle different connection states
* switch change.current {
* case .connected:
* print("Connected to Ably")
* enableChatFeatures()
* hideConnectionWarning()
*
* case .failed:
* print("Connection failed permanently")
* if let error = change.error {
* print("Failure reason: \(error.message)")
* showErrorMessage("Connection failed: \(error.message)")
* }
* requireManualReconnection()
*
* // Other states: Connecting, Disconnected, Suspended
* }
*
* // Clean up when done
* subscription.off()
* ```
*/
@discardableResult
func onStatusChange(_ callback: @escaping @MainActor (ConnectionStatusChange) -> Void) -> StatusSubscription
}
/// `AsyncSequence` variant of `Connection` status changes.
public extension Connection {
/**
* Subscribes a given listener to a connection status changes.
*
* - Parameters:
* - bufferingPolicy: The ``BufferingPolicy`` for the created subscription.
*
* - Returns: A subscription `AsyncSequence` that can be used to iterate through ``ConnectionStatusChange`` events.
*/
func onStatusChange(bufferingPolicy: BufferingPolicy) -> SubscriptionAsyncSequence<ConnectionStatusChange> {
let subscriptionAsyncSequence = SubscriptionAsyncSequence<ConnectionStatusChange>(bufferingPolicy: bufferingPolicy)
let subscription = onStatusChange { statusChange in
subscriptionAsyncSequence.emit(statusChange)
}
subscriptionAsyncSequence.addTerminationHandler {
Task { @MainActor in
subscription.off()
}
}
return subscriptionAsyncSequence
}
/// Same as calling ``onStatusChange(bufferingPolicy:)`` with ``BufferingPolicy/unbounded``.
func onStatusChange() -> SubscriptionAsyncSequence<ConnectionStatusChange> {
onStatusChange(bufferingPolicy: .unbounded)
}
}
/**
* The different states that the connection can be in through its lifecycle.
*/
public enum ConnectionStatus: Sendable {
// (CHA-CS1a) The INITIALIZED status is a default status when the realtime client is first initialized. This value will only (likely) be seen if the realtime client doesn't have autoconnect turned on.
/**
* A temporary state for when the library is first initialized.
*/
case initialized
// (CHA-CS1b) The CONNECTING status is used when the client is in the process of connecting to Ably servers.
/**
* The library is currently connecting to Ably.
*/
case connecting
// (CHA-CS1c) The CONNECTED status is used when the client connected to Ably servers.
/**
* The library is currently connected to Ably.
*/
case connected
// (CHA-CS1d) The DISCONNECTED status is used when the client is not currently connected to Ably servers. This state may be temporary as the underlying Realtime SDK seeks to reconnect.
/**
* The library is currently disconnected from Ably, but will attempt to reconnect.
*/
case disconnected
// (CHA-CS1e) The SUSPENDED status is used when the client is in an extended state of disconnection, but will attempt to reconnect.
/**
* The library is in an extended state of disconnection, but will attempt to reconnect.
*/
case suspended
// (CHA-CS1f) The FAILED status is used when the client is disconnected from the Ably servers due to some non-retriable failure such as authentication failure. It will not attempt to reconnect.
/**
* The library is currently disconnected from Ably and will not attempt to reconnect.
*/
case failed
internal static func fromRealtimeConnectionState(_ state: ARTRealtimeConnectionState) -> Self {
switch state {
case .initialized:
.initialized
case .connecting:
.connecting
case .connected:
.connected
case .disconnected:
.disconnected
case .suspended:
.suspended
case .failed, .closing, .closed:
.failed
@unknown default:
.failed
}
}
}
/**
* Represents a change in the status of the connection.
*/
public struct ConnectionStatusChange: Sendable {
/**
* The new status of the connection.
*/
public var current: ConnectionStatus
/**
* The previous status of the connection.
*/
public var previous: ConnectionStatus
/**
* An error that provides a reason why the connection has
* entered the new status, if applicable.
*/
public var error: ErrorInfo?
/**
* The time in milliseconds that the client will wait before attempting to reconnect.
*/
public var retryIn: TimeInterval?
/// Memberwise initializer to create a `ConnectionStatusChange`.
///
/// - Note: You should not need to use this initializer when using the Chat SDK. It is exposed only to allow users to create mock versions of the SDK's protocols.
public init(current: ConnectionStatus, previous: ConnectionStatus, error: ErrorInfo? = nil, retryIn: TimeInterval?) {
self.current = current
self.previous = previous
self.error = error
self.retryIn = retryIn
}
}