crossmate

A collaborative crossword app for iOS
Log | Files | Refs | LICENSE

commit 72261ba3f7764c20a041a7c64820b972dff046cf
parent 2a54c07c49946496aaf2f657e98d2c5ecd082d09
Author: Michael Camilleri <[email protected]>
Date:   Tue, 26 May 2026 01:50:42 +0900

Ensure protocol in URLs is wss

Diffstat:
MCrossmate/Services/EngagementHost.swift | 13++++++++++---
MTests/Unit/Sync/EngagementCoordinatorTests.swift | 24++++++++++++++++++++++++
2 files changed, 34 insertions(+), 3 deletions(-)

diff --git a/Crossmate/Services/EngagementHost.swift b/Crossmate/Services/EngagementHost.swift @@ -78,12 +78,16 @@ final class EngagementHost: NSObject { } } - private static func socketURL( + static func socketURL( room: EngagementRoomCredentials, authorID: String, - deviceID: String + deviceID: String, + baseURL: URL? = endpointURL ) throws -> URL? { - guard let baseURL = endpointURL else { return nil } + guard let baseURL else { return nil } + guard baseURL.scheme == "ws" || baseURL.scheme == "wss" else { + throw EngagementHostError.invalidEndpoint + } let timestamp = String(Int(Date().timeIntervalSince1970)) let nonce = UUID().uuidString let signaturePayload = EngagementSocketAuthenticator.signaturePayload( @@ -156,6 +160,7 @@ extension EngagementHost: URLSessionWebSocketDelegate { enum EngagementHostError: LocalizedError { case missingEndpoint case missingSocket + case invalidEndpoint case invalidSecret var errorDescription: String? { @@ -164,6 +169,8 @@ enum EngagementHostError: LocalizedError { "CrossmateEngagementSocketURL is not configured." case .missingSocket: "The engagement socket is not connected." + case .invalidEndpoint: + "CrossmateEngagementSocketURL must use ws or wss." case .invalidSecret: "The engagement room secret is invalid." } diff --git a/Tests/Unit/Sync/EngagementCoordinatorTests.swift b/Tests/Unit/Sync/EngagementCoordinatorTests.swift @@ -50,6 +50,30 @@ struct EngagementCoordinatorTests { #expect(!first.isEmpty) } + @Test("socket URL preserves configured WebSocket endpoint") + func socketURL() throws { + let room = EngagementRoomCredentials( + roomID: UUID(uuidString: "23232323-2323-2323-2323-232323232323")!, + secret: Data(repeating: 2, count: 32).base64URLEncodedString(), + createdAt: Date(timeIntervalSince1970: 100), + expiresAt: Date(timeIntervalSince1970: 700) + ) + + let url = try #require(EngagementHost.socketURL( + room: room, + authorID: "alice", + deviceID: "deviceA", + baseURL: URL(string: "wss://example.org") + )) + + #expect(url.scheme == "wss") + #expect(url.host() == "example.org") + #expect(url.path == "/rooms/23232323-2323-2323-2323-232323232323/socket") + #expect(URLComponents(url: url, resolvingAgainstBaseURL: false)? + .queryItems? + .contains(URLQueryItem(name: "authorID", value: "alice")) == true) + } + @Test("debug message envelope round trips") func debugMessageRoundTrip() throws { let sentAt = Date(timeIntervalSince1970: 123)