Skip to content

Conversation

@laevandus
Copy link
Contributor

@laevandus laevandus commented Oct 24, 2025

🔗 Issue Links

Related: IOS-1205

🎯 Goal

Required changes for StreamChat migration for adopting web-socket client and remaining types

🛠 Implementation

  • EventNotificationCenter is now a protocol with DefaultEventNotificationCenter. This is required for chat to inject its own version (one which writes to DB)
  • WebSocketClient changes for accommodate chat's requirements (e.g. using URLRequest and setting nil when creating the client)
  • Various changes for making things public

☑️ Contributor Checklist

  • I have signed the Stream CLA (required)
  • This change should be manually QAed
  • Changelog is updated with client-facing changes
  • Changelog is updated with new localization keys
  • New code is covered by unit tests
  • Documentation has been updated in the docs-content repo

@laevandus laevandus requested a review from a team as a code owner October 24, 2025 07:37
@laevandus laevandus marked this pull request as draft October 24, 2025 07:37
@github-actions
Copy link

1 Message
📖 Skipping Danger since the Pull Request is classed as Draft/Work In Progress

Generated by 🚫 Danger

@laevandus laevandus force-pushed the chat-web-socket-migration branch 2 times, most recently from fe498fe to af22b77 Compare October 27, 2025 09:25
@laevandus laevandus force-pushed the chat-web-socket-migration branch from af22b77 to a2d531b Compare October 27, 2025 13:49
Comment on lines +38 to 40
public var isInvalidTokenError: Bool {
ClosedRange.tokenInvalidErrorCodes ~= code || code == StreamErrorCode.accessKeyInvalid
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From Chat's implementation

/// - Returns: Gzip-compressed `Data` instance.
/// - Throws: `GzipError`
public func gzipped() throws -> Data {
func gzipped() throws -> Data {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to be public anymore (previous PR did that)

/// Basically, it's a wrapper over legacy monitor based on `Reachability` (iOS 11 only)
/// and default monitor based on `Network`.`NWPathMonitor` (iOS 12+).
public final class InternetConnection: @unchecked Sendable {
open class InternetConnection: @unchecked Sendable {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test tools need to subclass it on the chat side

import Foundation

public protocol StreamTimer {
public protocol TimerScheduling {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Naming conflict: did not know there was StreamTimer in the UIKit module and Timer was conflicting with Foundation.


@MainActor
public func beginTask(expirationHandler: (@Sendable () -> Void)?) -> Bool {
endTask()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From Chat

Comment on lines +9 to +12
public protocol ConnectionRecoveryHandler: ConnectionStateDelegate, Sendable {
func start()
func stop()
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chat needs this to support some flows

keepConnectionAliveInBackground: keepConnectionAliveInBackground,
reconnectionPolicies: [
WebSocketAutomaticReconnectionPolicy(webSocketClient),
InternetAvailabilityReconnectionPolicy(internetConnection),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is interesting, I struggled with reconnection when building and running demo app and as soon as I dropped this, things started to work. I spent a day on this, so not planning to spend more time on this. Chat's recovery handler also did not check for internet connection (makes sense since the monitor is not 100% accurate and URLRequest can trigger connection establishment even when the monitor says there is no connection. This is also what Apple suggest (can't remember which WWDC on top of my head))

Comment on lines 124 to 134
Task { @MainActor in
StreamConcurrency.onMain {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Task creates delay whereas onMain does not. Which can be important when scheduling background tasks (really small time window for scheduling one)

let isWaitingForReconnect = webSocketConnectionState.isAutomaticReconnectionEnabled || source.serverError?
.isInvalidTokenError == true

let isWaitingForReconnect = webSocketConnectionState.isAutomaticReconnectionEnabled
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From chat

case userInitiated

/// The connection timed out while trying to connect.
case timeout(from: WebSocketConnectionState)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chat needs this state for special reconnection flows

public weak var connectionStateDelegate: ConnectionStateDelegate?

public var connectURL: URL
public var connectRequest: URLRequest?
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chat used endpoint type which in the end was turned into URLRequest. Since WS engine needs URLRequest anyway so it makes sense to change this around. Also Chat sets this just before connecting URL, therefore nil is needed.

Comment on lines +103 to +105
public func initialize() {
connectionState = .initialized
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chat needs this for its special reconnection flows

}

if let date = self?.iso8601formatter.date(from: dateString) {
if let date = self?.iso8601formatter.dateWithMicroseconds(from: dateString) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very important for chat

@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
25.9% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

@laevandus laevandus closed this Oct 29, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants