From 5ad583946c3c59daaa3f8a34b1db1bb6204fb8a1 Mon Sep 17 00:00:00 2001 From: Guilherme Souza Date: Thu, 13 Feb 2025 11:22:08 -0300 Subject: [PATCH 1/4] fix --- Sources/TestHelpers/MockExtensions.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/TestHelpers/MockExtensions.swift b/Sources/TestHelpers/MockExtensions.swift index 8b585d319..3f8ac8fb7 100644 --- a/Sources/TestHelpers/MockExtensions.swift +++ b/Sources/TestHelpers/MockExtensions.swift @@ -12,7 +12,7 @@ import InlineSnapshotTesting extension Mock { package func snapshotRequest( message: @autoclosure () -> String = "", - record isRecording: Bool? = nil, + record isRecording: SnapshotTestingConfiguration.Record? = nil, timeout: TimeInterval = 5, syntaxDescriptor: InlineSnapshotSyntaxDescriptor = InlineSnapshotSyntaxDescriptor(), matches expected: (() -> String)? = nil, From eddd6ea4026c836773750ebb399770405320bc91 Mon Sep 17 00:00:00 2001 From: Guilherme Souza Date: Tue, 18 Feb 2025 09:21:17 -0300 Subject: [PATCH 2/4] fix(realtime): Set default heartbeat interval to 25s --- Sources/Realtime/Types.swift | 2 +- .../xcshareddata/swiftpm/Package.resolved | 28 +++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Sources/Realtime/Types.swift b/Sources/Realtime/Types.swift index 184b8cb55..3ad6382e1 100644 --- a/Sources/Realtime/Types.swift +++ b/Sources/Realtime/Types.swift @@ -25,7 +25,7 @@ public struct RealtimeClientOptions: Sendable { package var accessToken: (@Sendable () async throws -> String?)? package var logger: (any SupabaseLogger)? - public static let defaultHeartbeatInterval: TimeInterval = 15 + public static let defaultHeartbeatInterval: TimeInterval = 25 public static let defaultReconnectDelay: TimeInterval = 7 public static let defaultTimeoutInterval: TimeInterval = 10 public static let defaultDisconnectOnSessionLoss = true diff --git a/Supabase.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Supabase.xcworkspace/xcshareddata/swiftpm/Package.resolved index 4a7be1328..d32553d10 100644 --- a/Supabase.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Supabase.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -59,8 +59,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-asn1.git", "state" : { - "revision" : "7faebca1ea4f9aaf0cda1cef7c43aecd2311ddf6", - "version" : "1.3.0" + "revision" : "ae33e5941bb88d88538d0a6b19ca0b01e6c76dcf", + "version" : "1.3.1" } }, { @@ -68,8 +68,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-case-paths", "state" : { - "revision" : "bc92c4b27f9a84bfb498cdbfdf35d5a357e9161f", - "version" : "1.5.6" + "revision" : "19b7263bacb9751f151ec0c93ec816fe1ef67c7b", + "version" : "1.6.1" } }, { @@ -95,8 +95,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-concurrency-extras", "state" : { - "revision" : "163409ef7dae9d960b87f34b51587b6609a76c1f", - "version" : "1.3.0" + "revision" : "82a4ae7170d98d8538ec77238b7eb8e7199ef2e8", + "version" : "1.3.1" } }, { @@ -104,8 +104,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-crypto.git", "state" : { - "revision" : "ff0f781cf7c6a22d52957e50b104f5768b50c779", - "version" : "3.10.0" + "revision" : "45305d32cfb830faebcaa9a7aea66ad342637518", + "version" : "3.11.1" } }, { @@ -131,8 +131,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-identified-collections.git", "state" : { - "revision" : "2f5ab6e091dd032b63dacbda052405756010dc3b", - "version" : "1.1.0" + "revision" : "322d9ffeeba85c9f7c4984b39422ec7cc3c56597", + "version" : "1.1.1" } }, { @@ -140,8 +140,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-snapshot-testing", "state" : { - "revision" : "42a086182681cf661f5c47c9b7dc3931de18c6d7", - "version" : "1.17.6" + "revision" : "b2d4cb30735f4fbc3a01963a9c658336dd21e9ba", + "version" : "1.18.1" } }, { @@ -167,8 +167,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", "state" : { - "revision" : "a3f634d1a409c7979cabc0a71b3f26ffa9fc8af1", - "version" : "1.4.3" + "revision" : "b444594f79844b0d6d76d70fbfb3f7f71728f938", + "version" : "1.5.1" } } ], From 11398b69b623d9ff1c32c274cc8fd998005cc805 Mon Sep 17 00:00:00 2001 From: Guilherme Souza Date: Tue, 18 Feb 2025 09:50:52 -0300 Subject: [PATCH 3/4] add log level to realtime --- Sources/Realtime/RealtimeClientV2.swift | 9 +++++++-- Sources/Realtime/Types.swift | 10 ++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/Sources/Realtime/RealtimeClientV2.swift b/Sources/Realtime/RealtimeClientV2.swift index e82d8fe1e..c65435f63 100644 --- a/Sources/Realtime/RealtimeClientV2.swift +++ b/Sources/Realtime/RealtimeClientV2.swift @@ -99,7 +99,8 @@ public final class RealtimeClientV2: Sendable { return try await URLSessionWebSocket.connect( to: Self.realtimeWebSocketURL( baseURL: Self.realtimeBaseURL(url: url), - apikey: options.apikey + apikey: options.apikey, + logLevel: options.logLevel ), configuration: configuration ) @@ -528,7 +529,7 @@ public final class RealtimeClientV2: Sendable { return url } - static func realtimeWebSocketURL(baseURL: URL, apikey: String?) -> URL { + static func realtimeWebSocketURL(baseURL: URL, apikey: String?, logLevel: LogLevel?) -> URL { guard var components = URLComponents(url: baseURL, resolvingAgainstBaseURL: false) else { return baseURL @@ -540,6 +541,10 @@ public final class RealtimeClientV2: Sendable { } components.queryItems!.append(URLQueryItem(name: "vsn", value: "1.0.0")) + if let logLevel { + components.queryItems!.append(URLQueryItem(name: "log_level", value: logLevel.rawValue)) + } + components.path.append("/websocket") components.path = components.path.replacingOccurrences(of: "//", with: "/") diff --git a/Sources/Realtime/Types.swift b/Sources/Realtime/Types.swift index 3ad6382e1..dd2b19b7f 100644 --- a/Sources/Realtime/Types.swift +++ b/Sources/Realtime/Types.swift @@ -21,6 +21,9 @@ public struct RealtimeClientOptions: Sendable { var timeoutInterval: TimeInterval var disconnectOnSessionLoss: Bool var connectOnSubscribe: Bool + + /// Sets the log level for Realtime + var logLevel: LogLevel? var fetch: (@Sendable (_ request: URLRequest) async throws -> (Data, URLResponse))? package var accessToken: (@Sendable () async throws -> String?)? package var logger: (any SupabaseLogger)? @@ -38,6 +41,7 @@ public struct RealtimeClientOptions: Sendable { timeoutInterval: TimeInterval = Self.defaultTimeoutInterval, disconnectOnSessionLoss: Bool = Self.defaultDisconnectOnSessionLoss, connectOnSubscribe: Bool = Self.defaultConnectOnSubscribe, + logLevel: LogLevel? = nil, fetch: (@Sendable (_ request: URLRequest) async throws -> (Data, URLResponse))? = nil, accessToken: (@Sendable () async throws -> String?)? = nil, logger: (any SupabaseLogger)? = nil @@ -48,6 +52,7 @@ public struct RealtimeClientOptions: Sendable { self.timeoutInterval = timeoutInterval self.disconnectOnSessionLoss = disconnectOnSessionLoss self.connectOnSubscribe = connectOnSubscribe + self.logLevel = logLevel self.fetch = fetch self.accessToken = accessToken self.logger = logger @@ -84,3 +89,8 @@ public enum RealtimeClientStatus: Sendable, CustomStringConvertible { extension HTTPField.Name { static let apiKey = Self("apiKey")! } + +/// Log level for Realtime. +public enum LogLevel: String, Sendable { + case info, warn, error +} From 965e51725f235787e39e8b795c5e51aa2c38cd14 Mon Sep 17 00:00:00 2001 From: Guilherme Souza Date: Tue, 18 Feb 2025 11:28:50 -0300 Subject: [PATCH 4/4] add test --- Sources/Realtime/RealtimeClientV2.swift | 22 ++++++++++-------- Tests/RealtimeTests/RealtimeTests.swift | 30 ++++++++++++++++++++++--- Tests/RealtimeTests/_PushTests.swift | 2 +- 3 files changed, 41 insertions(+), 13 deletions(-) diff --git a/Sources/Realtime/RealtimeClientV2.swift b/Sources/Realtime/RealtimeClientV2.swift index c65435f63..90633906c 100644 --- a/Sources/Realtime/RealtimeClientV2.swift +++ b/Sources/Realtime/RealtimeClientV2.swift @@ -16,7 +16,8 @@ import Helpers public typealias JSONObject = Helpers.JSONObject /// Factory function for returning a new WebSocket connection. -typealias WebSocketTransport = @Sendable () async throws -> any WebSocket +typealias WebSocketTransport = @Sendable (_ url: URL, _ headers: [String: String]) async throws -> + any WebSocket public final class RealtimeClientV2: Sendable { struct MutableState { @@ -93,15 +94,11 @@ public final class RealtimeClientV2: Sendable { self.init( url: url, options: options, - wsTransport: { + wsTransport: { url, headers in let configuration = URLSessionConfiguration.default - configuration.httpAdditionalHeaders = options.headers.dictionary + configuration.httpAdditionalHeaders = headers return try await URLSessionWebSocket.connect( - to: Self.realtimeWebSocketURL( - baseURL: Self.realtimeBaseURL(url: url), - apikey: options.apikey, - logLevel: options.logLevel - ), + to: url, configuration: configuration ) }, @@ -173,7 +170,14 @@ public final class RealtimeClientV2: Sendable { status = .connecting do { - let conn = try await wsTransport() + let conn = try await wsTransport( + Self.realtimeWebSocketURL( + baseURL: Self.realtimeBaseURL(url: url), + apikey: options.apikey, + logLevel: options.logLevel + ), + options.headers.dictionary + ) mutableState.withValue { $0.conn = conn } onConnected(reconnect: reconnect) } catch { diff --git a/Tests/RealtimeTests/RealtimeTests.swift b/Tests/RealtimeTests/RealtimeTests.swift index b27acefa8..128fcce12 100644 --- a/Tests/RealtimeTests/RealtimeTests.swift +++ b/Tests/RealtimeTests/RealtimeTests.swift @@ -14,7 +14,7 @@ import XCTest @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) final class RealtimeTests: XCTestCase { - let url = URL(string: "https://localhost:54321/realtime/v1")! + let url = URL(string: "http://localhost:54321/realtime/v1")! let apiKey = "anon.api.key" override func invokeTest() { @@ -49,7 +49,7 @@ final class RealtimeTests: XCTestCase { "custom.access.token" } ), - wsTransport: { self.client }, + wsTransport: { _, _ in self.client }, http: http ) } @@ -60,6 +60,30 @@ final class RealtimeTests: XCTestCase { super.tearDown() } + func test_transport() async { + let client = RealtimeClientV2( + url: url, + options: RealtimeClientOptions( + headers: ["apikey": apiKey], + logLevel: .warn, + accessToken: { + "custom.access.token" + } + ), + wsTransport: { url, headers in + assertInlineSnapshot(of: url, as: .description) { + """ + ws://localhost:54321/realtime/v1/websocket?apikey=anon.api.key&vsn=1.0.0&log_level=warn + """ + } + return FakeWebSocket.fakes().0 + }, + http: http + ) + + await client.connect() + } + func testBehavior() async throws { let channel = sut.channel("public:messages") var subscriptions: Set = [] @@ -352,7 +376,7 @@ final class RealtimeTests: XCTestCase { let request = await http.receivedRequests.last assertInlineSnapshot(of: request?.urlRequest, as: .raw(pretty: true)) { """ - POST https://localhost:54321/realtime/v1/api/broadcast + POST http://localhost:54321/realtime/v1/api/broadcast Authorization: Bearer custom.access.token Content-Type: application/json apiKey: anon.api.key diff --git a/Tests/RealtimeTests/_PushTests.swift b/Tests/RealtimeTests/_PushTests.swift index 638c4631a..61ee79427 100644 --- a/Tests/RealtimeTests/_PushTests.swift +++ b/Tests/RealtimeTests/_PushTests.swift @@ -34,7 +34,7 @@ final class _PushTests: XCTestCase { options: RealtimeClientOptions( headers: ["apiKey": "apikey"] ), - wsTransport: { client }, + wsTransport: { _, _ in client }, http: HTTPClientMock() ) }