diff --git a/Examples/Examples/AnyJSONView.swift b/Examples/Examples/AnyJSONView.swift index 97b7f8a7..8adc2a49 100644 --- a/Examples/Examples/AnyJSONView.swift +++ b/Examples/Examples/AnyJSONView.swift @@ -33,7 +33,6 @@ struct AnyJSONView: View { } } } - case let .object(object): let elements = Array(object).sorted(by: { $0.key < $1.key }) ForEach(elements, id: \.key) { element in diff --git a/Examples/UserManagement/AppView.swift b/Examples/UserManagement/AppView.swift index 17506890..72ed2183 100644 --- a/Examples/UserManagement/AppView.swift +++ b/Examples/UserManagement/AppView.swift @@ -19,7 +19,7 @@ struct AppView: View { } } .task { - for await state in await supabase.auth.authStateChanges { + for await state in supabase.auth.authStateChanges { if [.initialSession, .signedIn, .signedOut].contains(state.event) { isAuthenticated = state.session != nil } diff --git a/Examples/UserManagement/ProfileView.swift b/Examples/UserManagement/ProfileView.swift index 7285239f..15771a51 100644 --- a/Examples/UserManagement/ProfileView.swift +++ b/Examples/UserManagement/ProfileView.swift @@ -101,8 +101,7 @@ struct ProfileView: View { do { let currentUser = try await supabase.auth.session.user - let profile: Profile = try await supabase.database - .from("profiles") + let profile: Profile = try await supabase.from("profiles") .select() .eq("id", value: currentUser.id) .single() @@ -138,7 +137,7 @@ struct ProfileView: View { avatarURL: imageURL ) - try await supabase.database + try await supabase .from("profiles") .update(updatedProfile) .eq("id", value: currentUser.id) diff --git a/Sources/Realtime/V2/CallbackManager.swift b/Sources/Realtime/V2/CallbackManager.swift index 5659aeca..ea567b65 100644 --- a/Sources/Realtime/V2/CallbackManager.swift +++ b/Sources/Realtime/V2/CallbackManager.swift @@ -9,7 +9,7 @@ import ConcurrencyExtras import Foundation import Helpers -final class CallbackManager: @unchecked Sendable { +final class CallbackManager: Sendable { struct MutableState { var id = 0 var serverChanges: [PostgresJoinConfig] = [] diff --git a/Sources/Realtime/V2/RealtimeChannelV2.swift b/Sources/Realtime/V2/RealtimeChannelV2.swift index a6048e44..a8fe9f8b 100644 --- a/Sources/Realtime/V2/RealtimeChannelV2.swift +++ b/Sources/Realtime/V2/RealtimeChannelV2.swift @@ -176,10 +176,18 @@ public final class RealtimeChannelV2: Sendable { ) } + /// Send a broadcast message with `event` and a `Codable` payload. + /// - Parameters: + /// - event: Broadcast message event. + /// - message: Message payload. public func broadcast(event: String, message: some Codable) async throws { try await broadcast(event: event, message: JSONObject(message)) } + /// Send a broadcast message with `event` and a raw `JSON` payload. + /// - Parameters: + /// - event: Broadcast message event. + /// - message: Message payload. public func broadcast(event: String, message: JSONObject) async { assert( status == .subscribed, diff --git a/Sources/Realtime/V2/RealtimeMessageV2.swift b/Sources/Realtime/V2/RealtimeMessageV2.swift index 1bb81c0d..ff45913e 100644 --- a/Sources/Realtime/V2/RealtimeMessageV2.swift +++ b/Sources/Realtime/V2/RealtimeMessageV2.swift @@ -23,9 +23,15 @@ public struct RealtimeMessageV2: Hashable, Codable, Sendable { self.payload = payload } + var status: PushStatus? { + payload["status"] + .flatMap(\.stringValue) + .flatMap(PushStatus.init(rawValue:)) + } + public var eventType: EventType? { switch event { - case ChannelEvent.system where payload["status"]?.stringValue == "ok": .system + case ChannelEvent.system where status == .ok: .system case ChannelEvent.postgresChanges: .postgresChanges case ChannelEvent.broadcast: diff --git a/Sources/Realtime/V2/WebSocketClient.swift b/Sources/Realtime/V2/WebSocketClient.swift index be624006..0044025d 100644 --- a/Sources/Realtime/V2/WebSocketClient.swift +++ b/Sources/Realtime/V2/WebSocketClient.swift @@ -96,6 +96,7 @@ final class WebSocket: NSObject, URLSessionWebSocketDelegate, WebSocketClient, @ case .data: fallthrough + default: throw RealtimeError("Unsupported message type.") } diff --git a/Tests/RealtimeTests/RealtimeMessageV2Tests.swift b/Tests/RealtimeTests/RealtimeMessageV2Tests.swift new file mode 100644 index 00000000..fb29b19b --- /dev/null +++ b/Tests/RealtimeTests/RealtimeMessageV2Tests.swift @@ -0,0 +1,60 @@ +// +// RealtimeMessageV2Tests.swift +// +// +// Created by Guilherme Souza on 26/06/24. +// + +@testable import Realtime +import XCTest + +final class RealtimeMessageV2Tests: XCTestCase { + func testStatus() { + var message = RealtimeMessageV2(joinRef: nil, ref: nil, topic: "heartbeat", event: "event", payload: ["status": "ok"]) + XCTAssertEqual(message.status, .ok) + + message = RealtimeMessageV2(joinRef: nil, ref: nil, topic: "heartbeat", event: "event", payload: ["status": "timeout"]) + XCTAssertEqual(message.status, .timeout) + + message = RealtimeMessageV2(joinRef: nil, ref: nil, topic: "heartbeat", event: "event", payload: ["status": "error"]) + XCTAssertEqual(message.status, .error) + + message = RealtimeMessageV2(joinRef: nil, ref: nil, topic: "heartbeat", event: "event", payload: ["status": "invalid"]) + XCTAssertNil(message.status) + } + + func testEventType() { + let payloadWithTokenExpiredMessage: JSONObject = ["message": "access token has expired"] + let payloadWithStatusOK: JSONObject = ["status": "ok"] + let payloadWithNoStatus: JSONObject = [:] + + let systemEventMessage = RealtimeMessageV2(joinRef: nil, ref: nil, topic: "topic", event: ChannelEvent.system, payload: payloadWithStatusOK) + let postgresChangesEventMessage = RealtimeMessageV2(joinRef: nil, ref: nil, topic: "topic", event: ChannelEvent.postgresChanges, payload: payloadWithNoStatus) + let tokenExpiredEventMessage = RealtimeMessageV2(joinRef: nil, ref: nil, topic: "topic", event: ChannelEvent.system, payload: payloadWithTokenExpiredMessage) + + XCTAssertEqual(systemEventMessage.eventType, .system) + XCTAssertEqual(postgresChangesEventMessage.eventType, .postgresChanges) + XCTAssertEqual(tokenExpiredEventMessage.eventType, .tokenExpired) + + let broadcastEventMessage = RealtimeMessageV2(joinRef: nil, ref: nil, topic: "topic", event: ChannelEvent.broadcast, payload: payloadWithNoStatus) + XCTAssertEqual(broadcastEventMessage.eventType, .broadcast) + + let closeEventMessage = RealtimeMessageV2(joinRef: nil, ref: nil, topic: "topic", event: ChannelEvent.close, payload: payloadWithNoStatus) + XCTAssertEqual(closeEventMessage.eventType, .close) + + let errorEventMessage = RealtimeMessageV2(joinRef: nil, ref: nil, topic: "topic", event: ChannelEvent.error, payload: payloadWithNoStatus) + XCTAssertEqual(errorEventMessage.eventType, .error) + + let presenceDiffEventMessage = RealtimeMessageV2(joinRef: nil, ref: nil, topic: "topic", event: ChannelEvent.presenceDiff, payload: payloadWithNoStatus) + XCTAssertEqual(presenceDiffEventMessage.eventType, .presenceDiff) + + let presenceStateEventMessage = RealtimeMessageV2(joinRef: nil, ref: nil, topic: "topic", event: ChannelEvent.presenceState, payload: payloadWithNoStatus) + XCTAssertEqual(presenceStateEventMessage.eventType, .presenceState) + + let replyEventMessage = RealtimeMessageV2(joinRef: nil, ref: nil, topic: "topic", event: ChannelEvent.reply, payload: payloadWithNoStatus) + XCTAssertEqual(replyEventMessage.eventType, .reply) + + let unknownEventMessage = RealtimeMessageV2(joinRef: nil, ref: nil, topic: "topic", event: "unknown_event", payload: payloadWithNoStatus) + XCTAssertNil(unknownEventMessage.eventType) + } +} diff --git a/Tests/RealtimeTests/RealtimeTests.swift b/Tests/RealtimeTests/RealtimeTests.swift index 11662c74..76e3bdcb 100644 --- a/Tests/RealtimeTests/RealtimeTests.swift +++ b/Tests/RealtimeTests/RealtimeTests.swift @@ -37,7 +37,7 @@ final class RealtimeTests: XCTestCase { override func tearDown() { sut.disconnect() - + super.tearDown() }