Skip to content

fix(realtime): Set default heartbeat interval to 25s #667

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 18 additions & 9 deletions Sources/Realtime/RealtimeClientV2.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -93,14 +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
),
to: url,
configuration: configuration
)
},
Expand Down Expand Up @@ -172,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 {
Expand Down Expand Up @@ -528,7 +533,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
Expand All @@ -540,6 +545,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: "/")

Expand Down
12 changes: 11 additions & 1 deletion Sources/Realtime/Types.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,14 @@ 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)?

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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
}
2 changes: 1 addition & 1 deletion Sources/TestHelpers/MockExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
28 changes: 14 additions & 14 deletions Supabase.xcworkspace/xcshareddata/swiftpm/Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 27 additions & 3 deletions Tests/RealtimeTests/RealtimeTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -49,7 +49,7 @@ final class RealtimeTests: XCTestCase {
"custom.access.token"
}
),
wsTransport: { self.client },
wsTransport: { _, _ in self.client },
http: http
)
}
Expand All @@ -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<ObservationToken> = []
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion Tests/RealtimeTests/_PushTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ final class _PushTests: XCTestCase {
options: RealtimeClientOptions(
headers: ["apiKey": "apikey"]
),
wsTransport: { client },
wsTransport: { _, _ in client },
http: HTTPClientMock()
)
}
Expand Down
Loading