From ddc4387a29825e80aee74d310c539be12e2a0914 Mon Sep 17 00:00:00 2001 From: mike <238382448+mike-at-legend@users.noreply.github.com> Date: Mon, 10 Aug 2026 17:21:48 -0400 Subject: [PATCH] Honor receive message completion --- .../Model/Client/NWWebSocket.swift | 17 +++++- .../ReceiveCompletionTests.swift | 54 +++++++++++++++++++ 2 files changed, 69 insertions(+), 2 deletions(-) create mode 100644 Tests/NWWebSocketTests/ReceiveCompletionTests.swift diff --git a/Sources/NWWebSocket/Model/Client/NWWebSocket.swift b/Sources/NWWebSocket/Model/Client/NWWebSocket.swift index dea0466..bdac071 100644 --- a/Sources/NWWebSocket/Model/Client/NWWebSocket.swift +++ b/Sources/NWWebSocket/Model/Client/NWWebSocket.swift @@ -188,7 +188,7 @@ open class NWWebSocket: WebSocketConnection { guard !isListening else { return } isListening = true - connection?.receiveMessage { [weak self] (data, context, _, error) in + connection?.receiveMessage { [weak self] (data, context, isComplete, error) in guard let self = self else { return } @@ -198,7 +198,11 @@ open class NWWebSocket: WebSocketConnection { return } - if let data = data, !data.isEmpty, let context = context { + if Self.shouldDeliverReceivedMessage( + data: data, + context: context, + isComplete: isComplete + ), let data = data, let context = context { self.receiveMessage(data: data, context: context) } @@ -212,6 +216,15 @@ open class NWWebSocket: WebSocketConnection { } } + /// Return `true` only for a complete message that contains data and metadata. + internal static func shouldDeliverReceivedMessage( + data: Data?, + context: NWConnection.ContentContext?, + isComplete: Bool + ) -> Bool { + isComplete && data?.isEmpty == false && context != nil + } + /// Ping the WebSocket periodically. /// - Parameter interval: The `TimeInterval` (in seconds) with which to ping the server. open func ping(interval: TimeInterval) { diff --git a/Tests/NWWebSocketTests/ReceiveCompletionTests.swift b/Tests/NWWebSocketTests/ReceiveCompletionTests.swift new file mode 100644 index 0000000..1d7c9c2 --- /dev/null +++ b/Tests/NWWebSocketTests/ReceiveCompletionTests.swift @@ -0,0 +1,54 @@ +import Foundation +import Network +@testable import NWWebSocket +import XCTest + +final class ReceiveCompletionTests: XCTestCase { + func testCompleteMessageIsDelivered() { + XCTAssertTrue( + NWWebSocket.shouldDeliverReceivedMessage( + data: Data("{}".utf8), + context: textContext, + isComplete: true + ) + ) + } + + func testIncompleteMessageIsNotDelivered() { + XCTAssertFalse( + NWWebSocket.shouldDeliverReceivedMessage( + data: Data("{\"partial\":".utf8), + context: textContext, + isComplete: false + ) + ) + } + + func testEmptyMessageIsNotDelivered() { + XCTAssertFalse( + NWWebSocket.shouldDeliverReceivedMessage( + data: Data(), + context: textContext, + isComplete: true + ) + ) + } + + func testMessageWithoutContextIsNotDelivered() { + XCTAssertFalse( + NWWebSocket.shouldDeliverReceivedMessage( + data: Data("{}".utf8), + context: nil, + isComplete: true + ) + ) + } + + private var textContext: NWConnection.ContentContext { + let metadata = NWProtocolWebSocket.Metadata(opcode: .text) + return NWConnection.ContentContext( + identifier: "test-text-message", + metadata: [metadata] + ) + } +}