Skip to content

Conversation

@laevandus
Copy link
Contributor

@laevandus laevandus commented Oct 31, 2025

🔗 Issue Links

Fixes IOS-1205

🎯 Goal

Supporting changes for making web-socket client to fit into chat and make all of its existing testing flows to work

📝 Summary

Provide bullet points with the most important changes in the codebase.

🛠 Implementation

Provide a detailed description of the implementation and explain your decisions if you find them relevant.

🎨 Showcase

Add relevant screenshots and/or videos/gifs to easily see what this PR changes, if applicable.

Before After
img img

🧪 Manual Testing Notes

Explain how this change can be tested manually, if applicable.

☑️ 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 31, 2025 12:01
public protocol BackgroundTaskScheduler: Sendable {
/// It's your responsibility to finish previously running task.
///
/// Returns: `false` if system forbid background task, `true` otherwise
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Moved scheduler implementation from chat to core since there were additional fixes and the main actor annotations made it too hard to fix tests since using Task {} creates a delay.

case let .disconnected(source):
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.

Also from chat's implementation. isAutomaticReconnectionEnabled already has logic for invalid token

Copy link
Contributor

Choose a reason for hiding this comment

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

hmm, we should check this on video.

Copy link
Contributor

Choose a reason for hiding this comment

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

@ipavlidakis what do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OK, some things are off here.

isAutomaticReconnectionEnabled returns false for isInvalidTokenError, but for some reason it was reversed here.

public var isAutomaticReconnectionEnabled: Bool {
        guard case let .disconnected(source) = self else { return false }
        
        switch source {
        case let .serverInitiated(clientError):
            if let wsEngineError = clientError?.underlyingError as? WebSocketEngineError,
               wsEngineError.code == WebSocketEngineError.stopErrorCode {
                // Don't reconnect on `stop` errors
                return false
            }
            
            if let serverInitiatedError = clientError?.errorPayload {
                if serverInitiatedError.isInvalidTokenError {
                    // Don't reconnect on invalid token errors
                    return false
                }
                
                if serverInitiatedError.isClientError && !serverInitiatedError.isTokenExpiredError {
                    // Don't reconnect on client side errors unless it is an expired token
                    // Expired tokens return 401, so it is considered client error.
                    return false
                }
            }
            
            return true
        case .systemInitiated:
            return true
        case .noPongReceived:
            return true
        case .userInitiated:
            return false
        case .timeout:
            return false
        }
    }

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Looks like reversed to handle expired tokens. Looks messy.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed the issue with using errorPayload within that property, now it works correctly for everyone and there is no need for that OR statement.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

FYI: Tried these changes with Feeds and it seems to work just fine.

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.

Requirement from chat

Comment on lines +133 to +135
if serverInitiatedError.isClientError && !serverInitiatedError.isTokenExpiredError {
// Don't reconnect on client side errors unless it is an expired token
// Expired tokens return 401, so it is considered client error.
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


/// The current state the web socket connection.
@Atomic public private(set) var connectionState: WebSocketConnectionState = .initialized {
@Atomic public internal(set) var connectionState: WebSocketConnectionState = .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.

For tests

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 needs URLRequest, internally URL is turned into URLRequest anyway, so makes sense to do it from the start

Comment on lines +228 to +230
} catch is ClientError.IgnoredEventType {
log.info("Skipping unsupported event type with payload: \(data.debugPrettyPrintedJSON)", subsystems: .webSocket)
return
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

@github-actions
Copy link

github-actions bot commented Nov 3, 2025

1 Warning
⚠️ Big PR

Generated by 🚫 Danger

@sonarqubecloud
Copy link

sonarqubecloud bot commented Nov 4, 2025

Quality Gate Failed Quality Gate failed

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

See analysis details on SonarQube Cloud

@laevandus laevandus merged commit 7f3be03 into chat-json-encoding-decoding Nov 5, 2025
6 of 9 checks passed
@laevandus laevandus deleted the chat-web-socket-client branch November 5, 2025 07:21
laevandus added a commit that referenced this pull request Nov 5, 2025
* Chat json encoding decoding (#33)

* Chat web socket client (#34)
laevandus added a commit that referenced this pull request Nov 5, 2025
* Support error types for chat (#32)

* Chat json encoding decoding (#33)

* Chat web socket client (#34)
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.

3 participants