diff --git a/.swiftpm/configuration/Package.resolved b/.swiftpm/configuration/Package.resolved
new file mode 100644
index 000000000..dee6efd6a
--- /dev/null
+++ b/.swiftpm/configuration/Package.resolved
@@ -0,0 +1,59 @@
+{
+ "pins" : [
+ {
+ "identity" : "swift-concurrency-extras",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/pointfreeco/swift-concurrency-extras",
+ "state" : {
+ "revision" : "bb5059bde9022d69ac516803f4f227d8ac967f71",
+ "version" : "1.1.0"
+ }
+ },
+ {
+ "identity" : "swift-crypto",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/apple/swift-crypto.git",
+ "state" : {
+ "revision" : "bc1c29221f6dfeb0ebbfbc98eb95cd3d4967868e",
+ "version" : "3.4.0"
+ }
+ },
+ {
+ "identity" : "swift-custom-dump",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/pointfreeco/swift-custom-dump",
+ "state" : {
+ "revision" : "aec6a73f5c1dc1f1be4f61888094b95cf995d973",
+ "version" : "1.3.2"
+ }
+ },
+ {
+ "identity" : "swift-snapshot-testing",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/pointfreeco/swift-snapshot-testing",
+ "state" : {
+ "revision" : "c097f955b4e724690f0fc8ffb7a6d4b881c9c4e3",
+ "version" : "1.17.2"
+ }
+ },
+ {
+ "identity" : "swift-syntax",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/swiftlang/swift-syntax",
+ "state" : {
+ "revision" : "303e5c5c36d6a558407d364878df131c3546fad8",
+ "version" : "510.0.2"
+ }
+ },
+ {
+ "identity" : "xctest-dynamic-overlay",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay",
+ "state" : {
+ "revision" : "357ca1e5dd31f613a1d43320870ebc219386a495",
+ "version" : "1.2.2"
+ }
+ }
+ ],
+ "version" : 2
+}
diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/Supabase.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/Supabase.xcscheme
index 709700c6e..73d8b0ca6 100644
--- a/.swiftpm/xcode/xcshareddata/xcschemes/Supabase.xcscheme
+++ b/.swiftpm/xcode/xcshareddata/xcschemes/Supabase.xcscheme
@@ -35,6 +35,9 @@
+
+
URL
var status: @Sendable () -> RealtimeClientV2.Status
var options: @Sendable () -> RealtimeClientOptions
var accessToken: @Sendable () -> String?
+ var apiKey: @Sendable () -> String?
var makeRef: @Sendable () -> Int
var connect: @Sendable () async -> Void
var addChannel: @Sendable (_ channel: RealtimeChannelV2) -> Void
var removeChannel: @Sendable (_ channel: RealtimeChannelV2) async -> Void
var push: @Sendable (_ message: RealtimeMessageV2) async -> Void
+ var httpSend: @Sendable (_ request: HTTPRequest) async throws -> HTTPResponse
}
extension Socket {
init(client: RealtimeClientV2) {
self.init(
+ broadcastURL: { [weak client] in client?.broadcastURL ?? URL(string: "http://localhost")! },
status: { [weak client] in client?.status ?? .disconnected },
options: { [weak client] in client?.options ?? .init() },
accessToken: { [weak client] in client?.mutableState.accessToken },
+ apiKey: { [weak client] in client?.apikey },
makeRef: { [weak client] in client?.makeRef() ?? 0 },
connect: { [weak client] in await client?.connect() },
addChannel: { [weak client] in client?.addChannel($0) },
removeChannel: { [weak client] in await client?.removeChannel($0) },
- push: { [weak client] in await client?.push($0) }
+ push: { [weak client] in await client?.push($0) },
+ httpSend: { [weak client] in try await client?.http.send($0) ?? .init(data: Data(), response: HTTPURLResponse()) }
)
}
}
@@ -202,24 +223,64 @@ public final class RealtimeChannelV2: Sendable {
/// - event: Broadcast message event.
/// - message: Message payload.
public func broadcast(event: String, message: JSONObject) async {
- assert(
- status == .subscribed,
- "You can only broadcast after subscribing to the channel. Did you forget to call `channel.subscribe()`?"
- )
+ if status != .subscribed {
+ struct Message: Encodable {
+ let topic: String
+ let event: String
+ let payload: JSONObject
+ let `private`: Bool
+ }
- await push(
- RealtimeMessageV2(
- joinRef: mutableState.joinRef,
- ref: socket.makeRef().description,
- topic: topic,
- event: ChannelEvent.broadcast,
- payload: [
- "type": "broadcast",
- "event": .string(event),
- "payload": .object(message),
- ]
+ var headers = HTTPHeaders(["content-type": "application/json"])
+ if let apiKey = socket.apiKey() {
+ headers["apikey"] = apiKey
+ }
+ if let accessToken = socket.accessToken() {
+ headers["authorization"] = "Bearer \(accessToken)"
+ }
+
+ let task = Task { [headers] in
+ _ = try? await socket.httpSend(
+ HTTPRequest(
+ url: socket.broadcastURL(),
+ method: .post,
+ headers: headers,
+ body: JSONEncoder().encode(
+ [
+ "messages": [
+ Message(
+ topic: topic,
+ event: event,
+ payload: message,
+ private: config.isPrivate
+ ),
+ ],
+ ]
+ )
+ )
+ )
+ }
+
+ if config.broadcast.acknowledgeBroadcasts {
+ try? await withTimeout(interval: socket.options().timeoutInterval) {
+ await task.value
+ }
+ }
+ } else {
+ await push(
+ RealtimeMessageV2(
+ joinRef: mutableState.joinRef,
+ ref: socket.makeRef().description,
+ topic: topic,
+ event: ChannelEvent.broadcast,
+ payload: [
+ "type": "broadcast",
+ "event": .string(event),
+ "payload": .object(message),
+ ]
+ )
)
- )
+ }
}
public func track(_ state: some Codable) async throws {
diff --git a/Sources/Realtime/V2/RealtimeClientV2.swift b/Sources/Realtime/V2/RealtimeClientV2.swift
index f202de0d1..acc15fc6c 100644
--- a/Sources/Realtime/V2/RealtimeClientV2.swift
+++ b/Sources/Realtime/V2/RealtimeClientV2.swift
@@ -79,6 +79,7 @@ public final class RealtimeClientV2: Sendable {
let options: RealtimeClientOptions
let ws: any WebSocketClient
let mutableState = LockIsolated(MutableState())
+ let http: any HTTPClientType
let apikey: String?
public var subscriptions: [String: RealtimeChannelV2] {
@@ -128,6 +129,12 @@ public final class RealtimeClientV2: Sendable {
}
public convenience init(url: URL, options: RealtimeClientOptions) {
+ var interceptors: [any HTTPClientInterceptor] = []
+
+ if let logger = options.logger {
+ interceptors.append(LoggerInterceptor(logger: logger))
+ }
+
self.init(
url: url,
options: options,
@@ -137,14 +144,24 @@ public final class RealtimeClientV2: Sendable {
apikey: options.apikey
),
options: options
+ ),
+ http: HTTPClient(
+ fetch: options.fetch ?? { try await URLSession.shared.data(for: $0) },
+ interceptors: interceptors
)
)
}
- init(url: URL, options: RealtimeClientOptions, ws: any WebSocketClient) {
+ init(
+ url: URL,
+ options: RealtimeClientOptions,
+ ws: any WebSocketClient,
+ http: any HTTPClientType
+ ) {
self.url = url
self.options = options
self.ws = ws
+ self.http = http
apikey = options.apikey
mutableState.withValue {
@@ -471,7 +488,7 @@ public final class RealtimeClientV2: Sendable {
return url
}
- private var broadcastURL: URL {
+ var broadcastURL: URL {
url.appendingPathComponent("api/broadcast")
}
}
diff --git a/Sources/Realtime/V2/Types.swift b/Sources/Realtime/V2/Types.swift
index eeaab64eb..b402cc599 100644
--- a/Sources/Realtime/V2/Types.swift
+++ b/Sources/Realtime/V2/Types.swift
@@ -8,6 +8,10 @@
import Foundation
import Helpers
+#if canImport(FoundationNetworking)
+ import FoundationNetworking
+#endif
+
/// Options for initializing ``RealtimeClientV2``.
public struct RealtimeClientOptions: Sendable {
package var headers: HTTPHeaders
@@ -16,6 +20,7 @@ public struct RealtimeClientOptions: Sendable {
var timeoutInterval: TimeInterval
var disconnectOnSessionLoss: Bool
var connectOnSubscribe: Bool
+ var fetch: (@Sendable (_ request: URLRequest) async throws -> (Data, URLResponse))?
package var logger: (any SupabaseLogger)?
public static let defaultHeartbeatInterval: TimeInterval = 15
@@ -31,6 +36,7 @@ public struct RealtimeClientOptions: Sendable {
timeoutInterval: TimeInterval = Self.defaultTimeoutInterval,
disconnectOnSessionLoss: Bool = Self.defaultDisconnectOnSessionLoss,
connectOnSubscribe: Bool = Self.defaultConnectOnSubscribe,
+ fetch: (@Sendable (_ request: URLRequest) async throws -> (Data, URLResponse))? = nil,
logger: (any SupabaseLogger)? = nil
) {
self.headers = HTTPHeaders(headers)
@@ -39,6 +45,7 @@ public struct RealtimeClientOptions: Sendable {
self.timeoutInterval = timeoutInterval
self.disconnectOnSessionLoss = disconnectOnSessionLoss
self.connectOnSubscribe = connectOnSubscribe
+ self.fetch = fetch
self.logger = logger
}
diff --git a/Supabase.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Supabase.xcworkspace/xcshareddata/swiftpm/Package.resolved
index 87bffe20a..a3bc22bb5 100644
--- a/Supabase.xcworkspace/xcshareddata/swiftpm/Package.resolved
+++ b/Supabase.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -1,4 +1,5 @@
{
+ "originHash" : "7cc8037abb258fd1406effe711922c8d309c2e7a93147bfe68753fd05392a24a",
"pins" : [
{
"identity" : "appauth-ios",
@@ -50,8 +51,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-case-paths",
"state" : {
- "revision" : "031704ba0634b45e02fe875b8ddddc7f30a07f49",
- "version" : "1.5.3"
+ "revision" : "71344dd930fde41e8f3adafe260adcbb2fc2a3dc",
+ "version" : "1.5.4"
}
},
{
@@ -63,31 +64,13 @@
"version" : "1.1.2"
}
},
- {
- "identity" : "swift-concurrency-extras",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/pointfreeco/swift-concurrency-extras",
- "state" : {
- "revision" : "bb5059bde9022d69ac516803f4f227d8ac967f71",
- "version" : "1.1.0"
- }
- },
- {
- "identity" : "swift-crypto",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/apple/swift-crypto.git",
- "state" : {
- "revision" : "46072478ca365fe48370993833cb22de9b41567f",
- "version" : "3.5.2"
- }
- },
{
"identity" : "swift-custom-dump",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-custom-dump",
"state" : {
- "revision" : "d237304f42af07f22563aa4cc2d7e2cfb25da82e",
- "version" : "1.3.1"
+ "revision" : "aec6a73f5c1dc1f1be4f61888094b95cf995d973",
+ "version" : "1.3.2"
}
},
{
@@ -100,41 +83,32 @@
}
},
{
- "identity" : "swift-issue-reporting",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/pointfreeco/swift-issue-reporting",
- "state" : {
- "revision" : "926f43898706eaa127db79ac42138e1ad7e85a3f",
- "version" : "1.2.0"
- }
- },
- {
- "identity" : "swift-snapshot-testing",
+ "identity" : "swift-syntax",
"kind" : "remoteSourceControl",
- "location" : "https://github.com/pointfreeco/swift-snapshot-testing",
+ "location" : "https://github.com/swiftlang/swift-syntax",
"state" : {
- "revision" : "c097f955b4e724690f0fc8ffb7a6d4b881c9c4e3",
- "version" : "1.17.2"
+ "revision" : "82a453c2dfa335c7e778695762438dfe72b328d2",
+ "version" : "600.0.0-prerelease-2024-07-24"
}
},
{
- "identity" : "swift-syntax",
+ "identity" : "swiftui-navigation",
"kind" : "remoteSourceControl",
- "location" : "https://github.com/swiftlang/swift-syntax",
+ "location" : "https://github.com/pointfreeco/swiftui-navigation.git",
"state" : {
- "revision" : "4c6cc0a3b9e8f14b3ae2307c5ccae4de6167ac2c",
- "version" : "600.0.0-prerelease-2024-06-12"
+ "revision" : "fc91d591ebba1f90d65028ccb65c861e5979e898",
+ "version" : "1.5.4"
}
},
{
- "identity" : "swiftui-navigation",
+ "identity" : "xctest-dynamic-overlay",
"kind" : "remoteSourceControl",
- "location" : "https://github.com/pointfreeco/swiftui-navigation.git",
+ "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay",
"state" : {
- "revision" : "97f854044356ac082e7e698f39264cc035544d77",
- "version" : "1.5.2"
+ "revision" : "357ca1e5dd31f613a1d43320870ebc219386a495",
+ "version" : "1.2.2"
}
}
],
- "version" : 2
+ "version" : 3
}
diff --git a/TestPlans/AllTests.xctestplan b/TestPlans/AllTests.xctestplan
index adf11dd58..bb36d65c1 100644
--- a/TestPlans/AllTests.xctestplan
+++ b/TestPlans/AllTests.xctestplan
@@ -57,8 +57,8 @@
{
"target" : {
"containerPath" : "container:",
- "identifier" : "_HelpersTests",
- "name" : "_HelpersTests"
+ "identifier" : "HelpersTests",
+ "name" : "HelpersTests"
}
}
],
diff --git a/Tests/HelpersTests/AnyJSONTests.swift b/Tests/HelpersTests/AnyJSONTests.swift
index 1369eac8a..3253b34ce 100644
--- a/Tests/HelpersTests/AnyJSONTests.swift
+++ b/Tests/HelpersTests/AnyJSONTests.swift
@@ -82,7 +82,7 @@ final class AnyJSONTests: XCTestCase {
// }
func testInitFromCodable() {
- expectNoDifference(try AnyJSON(jsonObject), jsonObject)
+ try expectNoDifference(AnyJSON(jsonObject), jsonObject)
let codableValue = CodableValue(
integer: 1,
@@ -104,8 +104,8 @@ final class AnyJSONTests: XCTestCase {
"any_json": jsonObject,
]
- expectNoDifference(try AnyJSON(codableValue), json)
- expectNoDifference(codableValue, try json.decode(as: CodableValue.self))
+ try expectNoDifference(AnyJSON(codableValue), json)
+ try expectNoDifference(codableValue, json.decode(as: CodableValue.self))
}
}
diff --git a/Tests/IntegrationTests/RealtimeIntegrationTests.swift b/Tests/IntegrationTests/RealtimeIntegrationTests.swift
index 3c9daf53a..07b498247 100644
--- a/Tests/IntegrationTests/RealtimeIntegrationTests.swift
+++ b/Tests/IntegrationTests/RealtimeIntegrationTests.swift
@@ -36,131 +36,147 @@ final class RealtimeIntegrationTests: XCTestCase {
logger: Logger()
)
+ override func invokeTest() {
+ withMainSerialExecutor {
+ super.invokeTest()
+ }
+ }
+
func testBroadcast() async throws {
- try await withMainSerialExecutor {
- let expectation = expectation(description: "receivedBroadcastMessages")
- expectation.expectedFulfillmentCount = 3
+ let expectation = expectation(description: "receivedBroadcastMessages")
+ expectation.expectedFulfillmentCount = 3
- let channel = realtime.channel("integration") {
- $0.broadcast.receiveOwnBroadcasts = true
- }
+ let channel = realtime.channel("integration") {
+ $0.broadcast.receiveOwnBroadcasts = true
+ }
- let receivedMessages = LockIsolated<[JSONObject]>([])
+ let receivedMessages = LockIsolated<[JSONObject]>([])
- Task {
- for await message in channel.broadcastStream(event: "test") {
- receivedMessages.withValue {
- $0.append(message)
- }
- expectation.fulfill()
+ Task {
+ for await message in channel.broadcastStream(event: "test") {
+ receivedMessages.withValue {
+ $0.append(message)
}
+ expectation.fulfill()
}
+ }
- await Task.yield()
+ await Task.yield()
- await channel.subscribe()
+ await channel.subscribe()
- struct Message: Codable {
- var value: Int
- }
+ struct Message: Codable {
+ var value: Int
+ }
- try await channel.broadcast(event: "test", message: Message(value: 1))
- try await channel.broadcast(event: "test", message: Message(value: 2))
- try await channel.broadcast(event: "test", message: ["value": 3, "another_value": 42])
+ try await channel.broadcast(event: "test", message: Message(value: 1))
+ try await channel.broadcast(event: "test", message: Message(value: 2))
+ try await channel.broadcast(event: "test", message: ["value": 3, "another_value": 42])
- await fulfillment(of: [expectation], timeout: 0.5)
+ await fulfillment(of: [expectation], timeout: 0.5)
- expectNoDifference(
- receivedMessages.value,
+ expectNoDifference(
+ receivedMessages.value,
+ [
[
- [
- "event": "test",
- "payload": [
- "value": 1,
- ],
- "type": "broadcast",
+ "event": "test",
+ "payload": [
+ "value": 1,
],
- [
- "event": "test",
- "payload": [
- "value": 2,
- ],
- "type": "broadcast",
+ "type": "broadcast",
+ ],
+ [
+ "event": "test",
+ "payload": [
+ "value": 2,
],
- [
- "event": "test",
- "payload": [
- "value": 3,
- "another_value": 42,
- ],
- "type": "broadcast",
+ "type": "broadcast",
+ ],
+ [
+ "event": "test",
+ "payload": [
+ "value": 3,
+ "another_value": 42,
],
- ]
- )
+ "type": "broadcast",
+ ],
+ ]
+ )
+
+ await channel.unsubscribe()
+ }
+
+ func testBroadcastWithUnsubscribedChannel() async throws {
+ let channel = realtime.channel("integration") {
+ $0.broadcast.acknowledgeBroadcasts = true
+ }
- await channel.unsubscribe()
+ struct Message: Codable {
+ var value: Int
}
+
+ try await channel.broadcast(event: "test", message: Message(value: 1))
+ try await channel.broadcast(event: "test", message: Message(value: 2))
+ try await channel.broadcast(event: "test", message: ["value": 3, "another_value": 42])
}
func testPresence() async throws {
- try await withMainSerialExecutor {
- let channel = realtime.channel("integration") {
- $0.broadcast.receiveOwnBroadcasts = true
- }
+ let channel = realtime.channel("integration") {
+ $0.broadcast.receiveOwnBroadcasts = true
+ }
- let expectation = expectation(description: "presenceChange")
- expectation.expectedFulfillmentCount = 4
+ let expectation = expectation(description: "presenceChange")
+ expectation.expectedFulfillmentCount = 4
- let receivedPresenceChanges = LockIsolated<[any PresenceAction]>([])
+ let receivedPresenceChanges = LockIsolated<[any PresenceAction]>([])
- Task {
- for await presence in channel.presenceChange() {
- receivedPresenceChanges.withValue {
- $0.append(presence)
- }
- expectation.fulfill()
+ Task {
+ for await presence in channel.presenceChange() {
+ receivedPresenceChanges.withValue {
+ $0.append(presence)
}
+ expectation.fulfill()
}
+ }
- await Task.yield()
+ await Task.yield()
- await channel.subscribe()
+ await channel.subscribe()
- struct UserState: Codable, Equatable {
- let email: String
- }
+ struct UserState: Codable, Equatable {
+ let email: String
+ }
- try await channel.track(UserState(email: "test@supabase.com"))
- try await channel.track(["email": "test2@supabase.com"])
+ try await channel.track(UserState(email: "test@supabase.com"))
+ try await channel.track(["email": "test2@supabase.com"])
- await channel.untrack()
+ await channel.untrack()
- await fulfillment(of: [expectation], timeout: 0.5)
+ await fulfillment(of: [expectation], timeout: 0.5)
- let joins = try receivedPresenceChanges.value.map { try $0.decodeJoins(as: UserState.self) }
- let leaves = try receivedPresenceChanges.value.map { try $0.decodeLeaves(as: UserState.self) }
- expectNoDifference(
- joins,
- [
- [], // This is the first PRESENCE_STATE event.
- [UserState(email: "test@supabase.com")],
- [UserState(email: "test2@supabase.com")],
- [],
- ]
- )
-
- expectNoDifference(
- leaves,
- [
- [], // This is the first PRESENCE_STATE event.
- [],
- [UserState(email: "test@supabase.com")],
- [UserState(email: "test2@supabase.com")],
- ]
- )
-
- await channel.unsubscribe()
- }
+ let joins = try receivedPresenceChanges.value.map { try $0.decodeJoins(as: UserState.self) }
+ let leaves = try receivedPresenceChanges.value.map { try $0.decodeLeaves(as: UserState.self) }
+ expectNoDifference(
+ joins,
+ [
+ [], // This is the first PRESENCE_STATE event.
+ [UserState(email: "test@supabase.com")],
+ [UserState(email: "test2@supabase.com")],
+ [],
+ ]
+ )
+
+ expectNoDifference(
+ leaves,
+ [
+ [], // This is the first PRESENCE_STATE event.
+ [],
+ [UserState(email: "test@supabase.com")],
+ [UserState(email: "test2@supabase.com")],
+ ]
+ )
+
+ await channel.unsubscribe()
}
// FIXME: Test getting stuck
diff --git a/Tests/RealtimeTests/RealtimeTests.swift b/Tests/RealtimeTests/RealtimeTests.swift
index f6e8fb7b7..be91d3843 100644
--- a/Tests/RealtimeTests/RealtimeTests.swift
+++ b/Tests/RealtimeTests/RealtimeTests.swift
@@ -5,6 +5,10 @@ import Helpers
import TestHelpers
import XCTest
+#if canImport(FoundationNetworking)
+ import FoundationNetworking
+#endif
+
final class RealtimeTests: XCTestCase {
let url = URL(string: "https://localhost:54321/realtime/v1")!
let apiKey = "anon.api.key"
@@ -16,12 +20,14 @@ final class RealtimeTests: XCTestCase {
}
var ws: MockWebSocketClient!
+ var http: HTTPClientMock!
var sut: RealtimeClientV2!
override func setUp() {
super.setUp()
ws = MockWebSocketClient()
+ http = HTTPClientMock()
sut = RealtimeClientV2(
url: url,
options: RealtimeClientOptions(
@@ -31,7 +37,8 @@ final class RealtimeTests: XCTestCase {
timeoutInterval: 2,
logger: TestLogger()
),
- ws: ws
+ ws: ws,
+ http: http
)
}
@@ -234,6 +241,54 @@ final class RealtimeTests: XCTestCase {
)
}
+ func testBroadcastWithHTTP() async throws {
+ await http.when {
+ $0.url.path.hasSuffix("broadcast")
+ } return: { _ in
+ HTTPResponse(
+ data: "{}".data(using: .utf8)!,
+ response: HTTPURLResponse(
+ url: self.sut.broadcastURL,
+ statusCode: 200,
+ httpVersion: nil,
+ headerFields: nil
+ )!
+ )
+ }
+
+ let channel = sut.channel("public:messages") {
+ $0.broadcast.acknowledgeBroadcasts = true
+ }
+
+ try await channel.broadcast(event: "test", message: ["value": 42])
+
+ let request = await http.receivedRequests.last
+ expectNoDifference(
+ request?.headers,
+ [
+ "content-type": "application/json",
+ "apikey": "anon.api.key",
+ "authorization": "Bearer anon.api.key",
+ ]
+ )
+
+ let body = try XCTUnwrap(request?.body)
+ let json = try JSONDecoder().decode(JSONObject.self, from: body)
+ expectNoDifference(
+ json,
+ [
+ "messages": [
+ [
+ "topic": "realtime:public:messages",
+ "event": "test",
+ "payload": ["value": 42],
+ "private": false,
+ ],
+ ],
+ ]
+ )
+ }
+
private func connectSocketAndWait() async {
ws.mockConnect(.connected)
await sut.connect()
diff --git a/Tests/RealtimeTests/_PushTests.swift b/Tests/RealtimeTests/_PushTests.swift
index 3c7aef5be..67efc7a14 100644
--- a/Tests/RealtimeTests/_PushTests.swift
+++ b/Tests/RealtimeTests/_PushTests.swift
@@ -29,7 +29,8 @@ final class _PushTests: XCTestCase {
options: RealtimeClientOptions(
headers: ["apiKey": "apikey"]
),
- ws: ws
+ ws: ws,
+ http: HTTPClientMock()
)
}