Skip to content

Commit e163268

Browse files
authored
V5: Use JSON encoders and decoders from StreamCore (#3860)
* V5: Use web-socket client from StreamCore (#3861)
1 parent 473c24c commit e163268

File tree

67 files changed

+292
-4451
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+292
-4451
lines changed

DemoApp/Screens/DemoReminderListVC.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -478,7 +478,7 @@ extension DemoReminderListVC: MessageReminderListControllerDelegate, EventsContr
478478
updateRemindersData()
479479
}
480480

481-
func eventsController(_ controller: EventsController, didReceiveEvent event: any StreamChat.Event) {
481+
func eventsController(_ controller: EventsController, didReceiveEvent event: any Event) {
482482
if event is MessageReminderDueEvent {
483483
updateReminderListsWithNewNowDate()
484484
}

DemoApp/Screens/Livestream/DemoLivestreamChatChannelVC.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,7 @@ class DemoLivestreamChatChannelVC: _ViewController,
393393
messageListVC.scrollToBottomButton.content = .init(messages: skippedMessagesAmount, mentions: 0)
394394
}
395395

396-
func eventsController(_ controller: EventsController, didReceiveEvent event: any StreamChat.Event) {
396+
func eventsController(_ controller: EventsController, didReceiveEvent event: any Event) {
397397
if event is NewMessagePendingEvent {
398398
if livestreamChannelController.isPaused {
399399
pauseBannerView.setState(.resuming)

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ let package = Package(
2929
],
3030
dependencies: [
3131
.package(url: "https://github.com/apple/swift-docc-plugin", exact: "1.0.0"),
32-
.package(url: "https://github.com/GetStream/stream-core-swift.git", branch: "chat-errors")
32+
.package(url: "https://github.com/GetStream/stream-core-swift.git", exact: "0.5.0")
3333
],
3434
targets: [
3535
.target(

Sources/StreamChat/APIClient/RequestDecoder.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,9 @@ struct DefaultRequestDecoder: RequestDecoder {
4646
log.debug("URL request response: \(httpResponse), data:\n\(data.debugPrettyPrintedJSON))", subsystems: .httpRequests)
4747

4848
guard httpResponse.statusCode < 300 else {
49-
let serverError: ErrorPayload
49+
let serverError: APIError
5050
do {
51-
serverError = try JSONDecoder.default.decode(ErrorPayload.self, from: data)
51+
serverError = try JSONDecoder.default.decode(APIError.self, from: data)
5252
} catch {
5353
log
5454
.error(
@@ -58,7 +58,7 @@ struct DefaultRequestDecoder: RequestDecoder {
5858
throw ClientError.Unknown("Unknown error. Server response: \(httpResponse).")
5959
}
6060

61-
if serverError.isExpiredTokenError {
61+
if serverError.isTokenExpiredError {
6262
log.info("Request failed because of an expired token.", subsystems: .httpRequests)
6363
throw ClientError.ExpiredToken()
6464
}

Sources/StreamChat/ChatClient+Environment.swift

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,18 @@ extension ChatClient {
2525

2626
var webSocketClientBuilder: (@Sendable (
2727
_ sessionConfiguration: URLSessionConfiguration,
28-
_ requestEncoder: RequestEncoder,
2928
_ eventDecoder: AnyEventDecoder,
30-
_ notificationCenter: EventNotificationCenter
29+
_ notificationCenter: PersistentEventNotificationCenter
3130
) -> WebSocketClient)? = {
32-
WebSocketClient(
31+
let wsEnvironment = WebSocketClient.Environment(eventBatchingPeriod: 0.5)
32+
return WebSocketClient(
3333
sessionConfiguration: $0,
34-
requestEncoder: $1,
35-
eventDecoder: $2,
36-
eventNotificationCenter: $3
34+
eventDecoder: $1,
35+
eventNotificationCenter: $2,
36+
webSocketClientType: .coordinator,
37+
environment: wsEnvironment,
38+
connectRequest: nil,
39+
healthCheckBeforeConnected: true
3740
)
3841
}
3942

@@ -57,7 +60,7 @@ extension ChatClient {
5760

5861
var eventDecoderBuilder: @Sendable () -> EventDecoder = { EventDecoder() }
5962

60-
var notificationCenterBuilder: @Sendable (_ database: DatabaseContainer, _ manualEventHandler: ManualEventHandler?) -> EventNotificationCenter = { EventNotificationCenter(database: $0, manualEventHandler: $1) }
63+
var notificationCenterBuilder: @Sendable (_ database: DatabaseContainer, _ manualEventHandler: ManualEventHandler?) -> PersistentEventNotificationCenter = { PersistentEventNotificationCenter(database: $0, manualEventHandler: $1) }
6164

6265
var internetConnection: @Sendable (_ center: NotificationCenter, _ monitor: InternetConnectionMonitor) -> InternetConnection = {
6366
InternetConnection(notificationCenter: $0, monitor: $1)
@@ -76,16 +79,18 @@ extension ChatClient {
7679
var connectionRepositoryBuilder: @Sendable (
7780
_ isClientInActiveMode: Bool,
7881
_ syncRepository: SyncRepository,
82+
_ webSocketEncoder: RequestEncoder?,
7983
_ webSocketClient: WebSocketClient?,
8084
_ apiClient: APIClient,
8185
_ timerType: TimerScheduling.Type
8286
) -> ConnectionRepository = {
8387
ConnectionRepository(
8488
isClientInActiveMode: $0,
8589
syncRepository: $1,
86-
webSocketClient: $2,
87-
apiClient: $3,
88-
timerType: $4
90+
webSocketEncoder: $2,
91+
webSocketClient: $3,
92+
apiClient: $4,
93+
timerType: $5
8994
)
9095
}
9196

@@ -110,20 +115,18 @@ extension ChatClient {
110115
var connectionRecoveryHandlerBuilder: @Sendable (
111116
_ webSocketClient: WebSocketClient,
112117
_ eventNotificationCenter: EventNotificationCenter,
113-
_ syncRepository: SyncRepository,
114118
_ backgroundTaskScheduler: BackgroundTaskScheduler?,
115119
_ internetConnection: InternetConnection,
116120
_ keepConnectionAliveInBackground: Bool
117121
) -> ConnectionRecoveryHandler = {
118122
DefaultConnectionRecoveryHandler(
119123
webSocketClient: $0,
120124
eventNotificationCenter: $1,
121-
syncRepository: $2,
122-
backgroundTaskScheduler: $3,
123-
internetConnection: $4,
125+
backgroundTaskScheduler: $2,
126+
internetConnection: $3,
124127
reconnectionStrategy: DefaultRetryStrategy(),
125128
reconnectionTimerType: DefaultTimer.self,
126-
keepConnectionAliveInBackground: $5
129+
keepConnectionAliveInBackground: $4
127130
)
128131
}
129132

Sources/StreamChat/ChatClient.swift

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public class ChatClient: @unchecked Sendable {
5252
private(set) var connectionRecoveryHandler: ConnectionRecoveryHandler?
5353

5454
/// The notification center used to send and receive notifications about incoming events.
55-
private(set) var eventNotificationCenter: EventNotificationCenter
55+
private(set) var eventNotificationCenter: PersistentEventNotificationCenter
5656

5757
/// The registry that contains all the attachment payloads associated with their attachment types.
5858
/// For the meantime this is a static property to avoid breaking changes. On v5, this can be changed.
@@ -99,6 +99,7 @@ public class ChatClient: @unchecked Sendable {
9999

100100
/// The `WebSocketClient` instance `Client` uses to communicate with Stream WS servers.
101101
let webSocketClient: WebSocketClient?
102+
let webSocketEncoder: RequestEncoder?
102103

103104
/// The `DatabaseContainer` instance `Client` uses to store and cache data.
104105
let databaseContainer: DatabaseContainer
@@ -184,13 +185,13 @@ public class ChatClient: @unchecked Sendable {
184185
channelListUpdater
185186
)
186187
let webSocketClient = factory.makeWebSocketClient(
187-
requestEncoder: webSocketEncoder,
188188
urlSessionConfiguration: urlSessionConfiguration,
189189
eventNotificationCenter: eventNotificationCenter
190190
)
191191
let connectionRepository = environment.connectionRepositoryBuilder(
192192
config.isClientInActiveMode,
193193
syncRepository,
194+
webSocketEncoder,
194195
webSocketClient,
195196
apiClient,
196197
environment.timerType
@@ -207,6 +208,7 @@ public class ChatClient: @unchecked Sendable {
207208
self.databaseContainer = databaseContainer
208209
self.apiClient = apiClient
209210
self.webSocketClient = webSocketClient
211+
self.webSocketEncoder = webSocketEncoder
210212
self.eventNotificationCenter = eventNotificationCenter
211213
self.offlineRequestsRepository = offlineRequestsRepository
212214
self.connectionRepository = connectionRepository
@@ -268,7 +270,6 @@ public class ChatClient: @unchecked Sendable {
268270
connectionRecoveryHandler = environment.connectionRecoveryHandlerBuilder(
269271
webSocketClient,
270272
eventNotificationCenter,
271-
syncRepository,
272273
environment.backgroundTaskSchedulerBuilder(),
273274
environment.internetConnection(eventNotificationCenter, environment.internetMonitor),
274275
config.staysConnectedInBackground
@@ -718,7 +719,7 @@ extension ChatClient: AuthenticationRepositoryDelegate {
718719
}
719720

720721
extension ChatClient: ConnectionStateDelegate {
721-
func webSocketClient(_ client: WebSocketClient, didUpdateConnectionState state: WebSocketConnectionState) {
722+
public func webSocketClient(_ client: WebSocketClient, didUpdateConnectionState state: WebSocketConnectionState) {
722723
connectionRepository.handleConnectionUpdate(
723724
state: state,
724725
onExpiredToken: { [weak self] in

Sources/StreamChat/ChatClientFactory.swift

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,11 @@ class ChatClientFactory {
6565
}
6666

6767
func makeWebSocketClient(
68-
requestEncoder: RequestEncoder,
6968
urlSessionConfiguration: URLSessionConfiguration,
70-
eventNotificationCenter: EventNotificationCenter
69+
eventNotificationCenter: PersistentEventNotificationCenter
7170
) -> WebSocketClient? {
7271
environment.webSocketClientBuilder?(
7372
urlSessionConfiguration,
74-
requestEncoder,
7573
EventDecoder(),
7674
eventNotificationCenter
7775
)
@@ -114,7 +112,7 @@ class ChatClientFactory {
114112
func makeEventNotificationCenter(
115113
databaseContainer: DatabaseContainer,
116114
currentUserId: @escaping () -> UserId?
117-
) -> EventNotificationCenter {
115+
) -> PersistentEventNotificationCenter {
118116
let center = environment.notificationCenterBuilder(databaseContainer, nil)
119117
let middlewares: [EventMiddleware] = [
120118
EventDataProcessorMiddleware(),

Sources/StreamChat/Config/ChatClientConfig.swift

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -227,21 +227,3 @@ extension ChatClientConfig {
227227
public var latestMessagesLimit = 5
228228
}
229229
}
230-
231-
/// A struct representing an API key of the chat app.
232-
///
233-
/// An API key can be obtained by registering on [our website](https://getstream.io/chat/trial/\).
234-
///
235-
public struct APIKey: Equatable, Sendable {
236-
/// The string representation of the API key
237-
public let apiKeyString: String
238-
239-
/// Creates a new `APIKey` from the provided string. Fails, if the string is empty.
240-
///
241-
/// - Warning: The `apiKeyString` must be a non-empty value, otherwise an assertion failure is raised.
242-
///
243-
public init(_ apiKeyString: String) {
244-
log.assert(apiKeyString.isEmpty == false, "APIKey can't be initialize with an empty string.")
245-
self.apiKeyString = apiKeyString
246-
}
247-
}

Sources/StreamChat/Query/Filter.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -592,14 +592,14 @@ private struct FilterRightSide: Decodable {
592592
self.operator = container.allKeys.first!.stringValue
593593
var value: FilterValue?
594594

595-
if let dateValue = try? container.decode(Date.self, forKey: key) {
596-
value = dateValue
597-
} else if let stringValue = try? container.decode(String.self, forKey: key) {
598-
value = stringValue
599-
} else if let intValue = try? container.decode(Int.self, forKey: key) {
595+
if let intValue = try? container.decode(Int.self, forKey: key) {
600596
value = intValue
601597
} else if let doubleValue = try? container.decode(Double.self, forKey: key) {
602598
value = doubleValue
599+
} else if let dateValue = try? container.decode(Date.self, forKey: key) {
600+
value = dateValue
601+
} else if let stringValue = try? container.decode(String.self, forKey: key) {
602+
value = stringValue
603603
} else if let boolValue = try? container.decode(Bool.self, forKey: key) {
604604
value = boolValue
605605
} else if let stringArray = try? container.decode([String].self, forKey: key) {

Sources/StreamChat/Repositories/ConnectionRepository.swift

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,21 +22,25 @@ class ConnectionRepository: @unchecked Sendable {
2222
set { connectionQueue.async(flags: .barrier) { self._connectionId = newValue }}
2323
}
2424

25+
let webSocketConnectEndpoint = AllocatedUnfairLock<Endpoint<EmptyResponse>?>(nil)
2526
let isClientInActiveMode: Bool
2627
private let syncRepository: SyncRepository
28+
private let webSocketEncoder: RequestEncoder?
2729
private let webSocketClient: WebSocketClient?
2830
private let apiClient: APIClient
2931
private let timerType: TimerScheduling.Type
3032

3133
init(
3234
isClientInActiveMode: Bool,
3335
syncRepository: SyncRepository,
36+
webSocketEncoder: RequestEncoder?,
3437
webSocketClient: WebSocketClient?,
3538
apiClient: APIClient,
3639
timerType: TimerScheduling.Type
3740
) {
3841
self.isClientInActiveMode = isClientInActiveMode
3942
self.syncRepository = syncRepository
43+
self.webSocketEncoder = webSocketEncoder
4044
self.webSocketClient = webSocketClient
4145
self.apiClient = apiClient
4246
self.timerType = timerType
@@ -80,6 +84,7 @@ class ConnectionRepository: @unchecked Sendable {
8084
}
8185
}
8286
}
87+
updateWebSocketConnectURLRequest()
8388
webSocketClient?.connect()
8489
}
8590

@@ -114,29 +119,52 @@ class ConnectionRepository: @unchecked Sendable {
114119

115120
/// Updates the WebSocket endpoint to use the passed token and user information for the connection
116121
func updateWebSocketEndpoint(with token: Token, userInfo: UserInfo?) {
117-
webSocketClient?.connectEndpoint = .webSocketConnect(userInfo: userInfo ?? .init(id: token.userId))
122+
webSocketConnectEndpoint.value = .webSocketConnect(userInfo: userInfo ?? .init(id: token.userId))
118123
}
119-
124+
120125
/// Updates the WebSocket endpoint to use the passed user id
121126
func updateWebSocketEndpoint(with currentUserId: UserId) {
122-
webSocketClient?.connectEndpoint = .webSocketConnect(userInfo: UserInfo(id: currentUserId))
127+
webSocketConnectEndpoint.value = .webSocketConnect(userInfo: UserInfo(id: currentUserId))
128+
}
129+
130+
private func updateWebSocketConnectURLRequest() {
131+
guard let webSocketClient, let webSocketEncoder, let webSocketConnectEndpoint = webSocketConnectEndpoint.value else { return }
132+
let request: URLRequest? = {
133+
do {
134+
return try webSocketEncoder.encodeRequest(for: webSocketConnectEndpoint)
135+
} catch {
136+
log.error(error.localizedDescription, error: error)
137+
return nil
138+
}
139+
}()
140+
guard let request else { return }
141+
webSocketClient.connectRequest = request
123142
}
124143

125144
func handleConnectionUpdate(
126145
state: WebSocketConnectionState,
127146
onExpiredToken: () -> Void
128147
) {
148+
let event = ConnectionStatusUpdated(webSocketConnectionState: state)
149+
if event.connectionStatus != connectionStatus {
150+
// Publish Connection event with the new state
151+
webSocketClient?.publishEvent(event)
152+
}
153+
129154
connectionStatus = .init(webSocketConnectionState: state)
130155

131156
// We should notify waiters if connectionId was obtained (i.e. state is .connected)
132157
// or for .disconnected state except for disconnect caused by an expired token
133158
let shouldNotifyConnectionIdWaiters: Bool
134159
let connectionId: String?
135160
switch state {
136-
case let .connected(connectionId: id):
161+
case let .connected(healthCheckInfo: healthCheckInfo):
137162
shouldNotifyConnectionIdWaiters = true
138-
connectionId = id
139-
case let .disconnected(source) where source.serverError?.isExpiredTokenError == true:
163+
connectionId = healthCheckInfo.connectionId
164+
syncRepository.syncLocalState {
165+
log.info("Local state sync completed", subsystems: .offlineSupport)
166+
}
167+
case let .disconnected(source) where source.serverError?.isTokenExpiredError == true:
140168
onExpiredToken()
141169
shouldNotifyConnectionIdWaiters = false
142170
connectionId = nil
@@ -146,7 +174,7 @@ class ConnectionRepository: @unchecked Sendable {
146174
case .initialized,
147175
.connecting,
148176
.disconnecting,
149-
.waitingForConnectionId:
177+
.authenticating:
150178
shouldNotifyConnectionIdWaiters = false
151179
connectionId = nil
152180
}

0 commit comments

Comments
 (0)