Fix web socket normalClosure on iOS 13

This commit is contained in:
Max Radermacher 2023-01-10 17:26:18 -08:00 committed by GitHub
parent 907b6cb02d
commit 08a7774fc6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -4,6 +4,7 @@
//
import Foundation
import Network
extension WebSocketFactory {
func webSocketPromise(request: URLRequest, queue: DispatchQueue) -> WebSocketPromise? {
@ -149,6 +150,14 @@ final class WebSocketPromise: SSKWebSocketDelegate {
case .waitingForAllResponses(let future):
switch state.socketError {
case .some(WebSocketError.closeError(statusCode: WebSocketError.normalClosure, closeReason: _)):
// On iOS 13, we expect exactly one message. If we notice "!= 1" results on
// well-behaved platforms, surface it as an error since it's probably
// leading to incorrect behavior on iOS 13.
owsAssertDebug(state.receivedMessages.count == 1, "Must be exactly one message.")
let receivedMessages = state.receivedMessages
state.receivedMessages = []
return { future.resolve(receivedMessages) }
case .some(let socketError) where isNormalOS13Closure(state, error: socketError):
let receivedMessages = state.receivedMessages
state.receivedMessages = []
return { future.resolve(receivedMessages) }
@ -162,6 +171,36 @@ final class WebSocketPromise: SSKWebSocketDelegate {
}
}
/// Checks if this is a "normalClosure" on iOS 13.
///
/// On iOS 13, we get back a "Socket is not connected" error instead of a
/// callback to `urlSession(_:webSocketTask:didCloseWith:reason:)` when the
/// web socket is closed normally (ie, with a closeCode of 1000). However,
/// if the web socket is closed *abnormally*, the delegate method is called.
/// As a result, we interpret "Socket is not connected" as a normal teardown
/// on iOS 13 in some scenarios.
///
/// Importantly, this heuristic behaves correctly since we expect exactly
/// one response when calling waitForAllResponses(). If we have a response,
/// it's successful. If we don't, it's a failure. In the future, if we
/// expect an arbitrary number of responses, we'll need a different
/// heuristic (it becomes impossible to know if we're still waiting for
/// additional responses or if we've received them all -- that EOF behavior
/// is exactly what the closeCode provides in the general case).
private func isNormalOS13Closure(_ state: State, error: Error) -> Bool {
guard #unavailable(iOS 14) else {
return false
}
let nsError = error as NSError
guard nsError.domain == kNWErrorDomainPOSIX as String && nsError.code == ENOTCONN else {
return false
}
guard state.receivedMessages.count == 1 else {
return false
}
return true
}
// MARK: - SSKWebSocketDelegate
func websocketDidConnect(socket: SSKWebSocket) {