Skip to content

Commit 14c590d

Browse files
authored
fix(realtime): Set default heartbeat interval to 25s (#667)
* fix * fix(realtime): Set default heartbeat interval to 25s
1 parent 660f709 commit 14c590d

File tree

6 files changed

+72
-29
lines changed

6 files changed

+72
-29
lines changed

Sources/Realtime/RealtimeClientV2.swift

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ import Helpers
1616
public typealias JSONObject = Helpers.JSONObject
1717

1818
/// Factory function for returning a new WebSocket connection.
19-
typealias WebSocketTransport = @Sendable () async throws -> any WebSocket
19+
typealias WebSocketTransport = @Sendable (_ url: URL, _ headers: [String: String]) async throws ->
20+
any WebSocket
2021

2122
public final class RealtimeClientV2: Sendable {
2223
struct MutableState {
@@ -93,14 +94,11 @@ public final class RealtimeClientV2: Sendable {
9394
self.init(
9495
url: url,
9596
options: options,
96-
wsTransport: {
97+
wsTransport: { url, headers in
9798
let configuration = URLSessionConfiguration.default
98-
configuration.httpAdditionalHeaders = options.headers.dictionary
99+
configuration.httpAdditionalHeaders = headers
99100
return try await URLSessionWebSocket.connect(
100-
to: Self.realtimeWebSocketURL(
101-
baseURL: Self.realtimeBaseURL(url: url),
102-
apikey: options.apikey
103-
),
101+
to: url,
104102
configuration: configuration
105103
)
106104
},
@@ -172,7 +170,14 @@ public final class RealtimeClientV2: Sendable {
172170
status = .connecting
173171

174172
do {
175-
let conn = try await wsTransport()
173+
let conn = try await wsTransport(
174+
Self.realtimeWebSocketURL(
175+
baseURL: Self.realtimeBaseURL(url: url),
176+
apikey: options.apikey,
177+
logLevel: options.logLevel
178+
),
179+
options.headers.dictionary
180+
)
176181
mutableState.withValue { $0.conn = conn }
177182
onConnected(reconnect: reconnect)
178183
} catch {
@@ -528,7 +533,7 @@ public final class RealtimeClientV2: Sendable {
528533
return url
529534
}
530535

531-
static func realtimeWebSocketURL(baseURL: URL, apikey: String?) -> URL {
536+
static func realtimeWebSocketURL(baseURL: URL, apikey: String?, logLevel: LogLevel?) -> URL {
532537
guard var components = URLComponents(url: baseURL, resolvingAgainstBaseURL: false)
533538
else {
534539
return baseURL
@@ -540,6 +545,10 @@ public final class RealtimeClientV2: Sendable {
540545
}
541546
components.queryItems!.append(URLQueryItem(name: "vsn", value: "1.0.0"))
542547

548+
if let logLevel {
549+
components.queryItems!.append(URLQueryItem(name: "log_level", value: logLevel.rawValue))
550+
}
551+
543552
components.path.append("/websocket")
544553
components.path = components.path.replacingOccurrences(of: "//", with: "/")
545554

Sources/Realtime/Types.swift

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,14 @@ public struct RealtimeClientOptions: Sendable {
2121
var timeoutInterval: TimeInterval
2222
var disconnectOnSessionLoss: Bool
2323
var connectOnSubscribe: Bool
24+
25+
/// Sets the log level for Realtime
26+
var logLevel: LogLevel?
2427
var fetch: (@Sendable (_ request: URLRequest) async throws -> (Data, URLResponse))?
2528
package var accessToken: (@Sendable () async throws -> String?)?
2629
package var logger: (any SupabaseLogger)?
2730

28-
public static let defaultHeartbeatInterval: TimeInterval = 15
31+
public static let defaultHeartbeatInterval: TimeInterval = 25
2932
public static let defaultReconnectDelay: TimeInterval = 7
3033
public static let defaultTimeoutInterval: TimeInterval = 10
3134
public static let defaultDisconnectOnSessionLoss = true
@@ -38,6 +41,7 @@ public struct RealtimeClientOptions: Sendable {
3841
timeoutInterval: TimeInterval = Self.defaultTimeoutInterval,
3942
disconnectOnSessionLoss: Bool = Self.defaultDisconnectOnSessionLoss,
4043
connectOnSubscribe: Bool = Self.defaultConnectOnSubscribe,
44+
logLevel: LogLevel? = nil,
4145
fetch: (@Sendable (_ request: URLRequest) async throws -> (Data, URLResponse))? = nil,
4246
accessToken: (@Sendable () async throws -> String?)? = nil,
4347
logger: (any SupabaseLogger)? = nil
@@ -48,6 +52,7 @@ public struct RealtimeClientOptions: Sendable {
4852
self.timeoutInterval = timeoutInterval
4953
self.disconnectOnSessionLoss = disconnectOnSessionLoss
5054
self.connectOnSubscribe = connectOnSubscribe
55+
self.logLevel = logLevel
5156
self.fetch = fetch
5257
self.accessToken = accessToken
5358
self.logger = logger
@@ -84,3 +89,8 @@ public enum RealtimeClientStatus: Sendable, CustomStringConvertible {
8489
extension HTTPField.Name {
8590
static let apiKey = Self("apiKey")!
8691
}
92+
93+
/// Log level for Realtime.
94+
public enum LogLevel: String, Sendable {
95+
case info, warn, error
96+
}

Sources/TestHelpers/MockExtensions.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import InlineSnapshotTesting
1212
extension Mock {
1313
package func snapshotRequest(
1414
message: @autoclosure () -> String = "",
15-
record isRecording: Bool? = nil,
15+
record isRecording: SnapshotTestingConfiguration.Record? = nil,
1616
timeout: TimeInterval = 5,
1717
syntaxDescriptor: InlineSnapshotSyntaxDescriptor = InlineSnapshotSyntaxDescriptor(),
1818
matches expected: (() -> String)? = nil,

Supabase.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 14 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Tests/RealtimeTests/RealtimeTests.swift

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import XCTest
1414

1515
@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
1616
final class RealtimeTests: XCTestCase {
17-
let url = URL(string: "https://localhost:54321/realtime/v1")!
17+
let url = URL(string: "http://localhost:54321/realtime/v1")!
1818
let apiKey = "anon.api.key"
1919

2020
override func invokeTest() {
@@ -49,7 +49,7 @@ final class RealtimeTests: XCTestCase {
4949
"custom.access.token"
5050
}
5151
),
52-
wsTransport: { self.client },
52+
wsTransport: { _, _ in self.client },
5353
http: http
5454
)
5555
}
@@ -60,6 +60,30 @@ final class RealtimeTests: XCTestCase {
6060
super.tearDown()
6161
}
6262

63+
func test_transport() async {
64+
let client = RealtimeClientV2(
65+
url: url,
66+
options: RealtimeClientOptions(
67+
headers: ["apikey": apiKey],
68+
logLevel: .warn,
69+
accessToken: {
70+
"custom.access.token"
71+
}
72+
),
73+
wsTransport: { url, headers in
74+
assertInlineSnapshot(of: url, as: .description) {
75+
"""
76+
ws://localhost:54321/realtime/v1/websocket?apikey=anon.api.key&vsn=1.0.0&log_level=warn
77+
"""
78+
}
79+
return FakeWebSocket.fakes().0
80+
},
81+
http: http
82+
)
83+
84+
await client.connect()
85+
}
86+
6387
func testBehavior() async throws {
6488
let channel = sut.channel("public:messages")
6589
var subscriptions: Set<ObservationToken> = []
@@ -352,7 +376,7 @@ final class RealtimeTests: XCTestCase {
352376
let request = await http.receivedRequests.last
353377
assertInlineSnapshot(of: request?.urlRequest, as: .raw(pretty: true)) {
354378
"""
355-
POST https://localhost:54321/realtime/v1/api/broadcast
379+
POST http://localhost:54321/realtime/v1/api/broadcast
356380
Authorization: Bearer custom.access.token
357381
Content-Type: application/json
358382
apiKey: anon.api.key

Tests/RealtimeTests/_PushTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ final class _PushTests: XCTestCase {
3434
options: RealtimeClientOptions(
3535
headers: ["apiKey": "apikey"]
3636
),
37-
wsTransport: { client },
37+
wsTransport: { _, _ in client },
3838
http: HTTPClientMock()
3939
)
4040
}

0 commit comments

Comments
 (0)