Skip to content
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

feat(realtime): general Realtime improvements and tests #168

Closed
wants to merge 23 commits into from
Closed
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
157 changes: 90 additions & 67 deletions Examples/RealtimeSample/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,93 +8,69 @@
import Realtime
import SwiftUI

struct ContentView: View {
@State var inserts: [Message] = []
@State var updates: [Message] = []
@State var deletes: [Message] = []

@State var socketStatus: String?
@State var channelStatus: String?

@State var publicSchema: RealtimeChannel?
@MainActor
final class ViewModel: ObservableObject {
@Published var inserts: [Message] = []
@Published var updates: [Message] = []
@Published var deletes: [Message] = []

var body: some View {
List {
Section("INSERTS") {
ForEach(Array(zip(inserts.indices, inserts)), id: \.0) { _, message in
Text(message.stringfiedPayload())
}
}
@Published var socketStatus: String?
@Published var channelStatus: String?

Section("UPDATES") {
ForEach(Array(zip(updates.indices, updates)), id: \.0) { _, message in
Text(message.stringfiedPayload())
}
}

Section("DELETES") {
ForEach(Array(zip(deletes.indices, deletes)), id: \.0) { _, message in
Text(message.stringfiedPayload())
}
}
}
.overlay(alignment: .bottomTrailing) {
VStack(alignment: .leading) {
Toggle(
"Toggle Subscription",
isOn: Binding(get: { publicSchema?.isJoined == true }, set: { _ in toggleSubscription() })
)
Text("Socket: \(socketStatus ?? "")")
Text("Channel: \(channelStatus ?? "")")
}
.padding()
.background(.regularMaterial)
.padding()
}
.onAppear {
createSubscription()
}
}
@Published var publicSchema: RealtimeChannel?

func createSubscription() {
supabase.realtime.connect()

publicSchema = supabase.realtime.channel("public")
.on("postgres_changes", filter: ChannelFilter(event: "INSERT", schema: "public")) {
inserts.append($0)
.on(
"postgres_changes",
filter: ChannelFilter(event: "INSERT", schema: "public")
) { [weak self] message in
self?.inserts.append(message)
}
.on("postgres_changes", filter: ChannelFilter(event: "UPDATE", schema: "public")) {
updates.append($0)
.on(
"postgres_changes",
filter: ChannelFilter(event: "UPDATE", schema: "public")
) { [weak self] message in
self?.updates.append(message)
}
.on("postgres_changes", filter: ChannelFilter(event: "DELETE", schema: "public")) {
deletes.append($0)
.on(
"postgres_changes",
filter: ChannelFilter(event: "DELETE", schema: "public")
) { [weak self] message in
self?.deletes.append(message)
}

publicSchema?.onError { _ in channelStatus = "ERROR" }
publicSchema?.onClose { _ in channelStatus = "Closed gracefully" }
publicSchema?.onError { [weak self] _ in
self?.channelStatus = "ERROR"
}
publicSchema?.onClose { [weak self] _ in
self?.channelStatus = "Closed gracefully"
}
publicSchema?
.subscribe { state, _ in
.subscribe { [weak self] state, _ in
switch state {
case .subscribed:
channelStatus = "OK"
self?.channelStatus = "OK"
case .closed:
channelStatus = "CLOSED"
self?.channelStatus = "CLOSED"
case .timedOut:
channelStatus = "Timed out"
self?.channelStatus = "Timed out"
case .channelError:
channelStatus = "ERROR"
self?.channelStatus = "ERROR"
}
}

supabase.realtime.connect()
supabase.realtime.onOpen {
socketStatus = "OPEN"
supabase.realtime.onOpen { [weak self] in
self?.socketStatus = "OPEN"
}
supabase.realtime.onClose {
socketStatus = "CLOSE"
supabase.realtime.onClose { [weak self] _, _ in
self?.socketStatus = "CLOSE"
}
supabase.realtime.onError { error, _ in
socketStatus = "ERROR: \(error.localizedDescription)"
supabase.realtime.onError { [weak self] error, _ in
self?.socketStatus = "ERROR: \(error.localizedDescription)"
}
}

Expand All @@ -107,12 +83,59 @@ struct ContentView: View {
}
}

struct ContentView: View {
@StateObject var model = ViewModel()

var body: some View {
List {
Section("INSERTS") {
ForEach(Array(zip(model.inserts.indices, model.inserts)), id: \.0) { _, message in
Text(message.stringfiedPayload())
}
}

Section("UPDATES") {
ForEach(Array(zip(model.updates.indices, model.updates)), id: \.0) { _, message in
Text(message.stringfiedPayload())
}
}

Section("DELETES") {
ForEach(Array(zip(model.deletes.indices, model.deletes)), id: \.0) { _, message in
Text(message.stringfiedPayload())
}
}
}
.overlay(alignment: .bottomTrailing) {
VStack(alignment: .leading) {
Toggle(
"Toggle Subscription",
isOn: Binding(
get: { model.publicSchema?.isJoined == true },
set: { _ in
model.toggleSubscription()
}
)
)
Text("Socket: \(model.socketStatus ?? "")")
Text("Channel: \(model.channelStatus ?? "")")
}
.padding()
.background(.regularMaterial)
.padding()
}
.onAppear {
model.createSubscription()
}
}
}

extension Message {
func stringfiedPayload() -> String {
do {
let data = try JSONSerialization.data(
withJSONObject: payload, options: [.prettyPrinted, .sortedKeys]
)
let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
let data = try encoder.encode(payload)
return String(data: data, encoding: .utf8) ?? ""
} catch {
return ""
Expand Down
4 changes: 2 additions & 2 deletions Examples/RealtimeSample/RealtimeSampleApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ struct RealtimeSampleApp: App {

let supabase: SupabaseClient = {
let client = SupabaseClient(
supabaseURL: "https://project-id.supabase.co",
supabaseKey: "anon key"
supabaseURL: "https://nixfbjgqturwbakhnwym.supabase.co",
supabaseKey: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im5peGZiamdxdHVyd2Jha2hud3ltIiwicm9sZSI6ImFub24iLCJpYXQiOjE2NzAzMDE2MzksImV4cCI6MTk4NTg3NzYzOX0.Ct6W75RPlDM37TxrBQurZpZap3kBy0cNkUimxF50HSo"
)
client.realtime.logger = { print($0) }
return client
Expand Down
8 changes: 7 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,13 @@ let package = Package(
"_Helpers",
]
),
.testTarget(name: "RealtimeTests", dependencies: ["Realtime"]),
.testTarget(
name: "RealtimeTests",
dependencies: [
"Realtime",
.product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay"),
]
),
.target(name: "Storage", dependencies: ["_Helpers"]),
.testTarget(name: "StorageTests", dependencies: ["Storage"]),
.target(
Expand Down
8 changes: 2 additions & 6 deletions Sources/Realtime/Defaults.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public enum Defaults {

/// Default maximum amount of time which the system may delay heartbeat events in order to
/// minimize power usage
public static let heartbeatLeeway: DispatchTimeInterval = .milliseconds(10)
public static let heartbeatLeeway: TimeInterval = 10

/// Default reconnect algorithm for the socket
public static let reconnectSteppedBackOff: (Int) -> TimeInterval = { tries in
Expand Down Expand Up @@ -65,15 +65,11 @@ public enum Defaults {
else { return nil }
return json
}

public static let heartbeatQueue: DispatchQueue = .init(
label: "com.phoenix.socket.heartbeat"
)
}

/// Represents the multiple states that a Channel can be in
/// throughout it's lifecycle.
public enum ChannelState: String {
public enum ChannelState: String, Sendable {
case closed
case errored
case joined
Expand Down
102 changes: 0 additions & 102 deletions Sources/Realtime/Delegated.swift

This file was deleted.

19 changes: 19 additions & 0 deletions Sources/Realtime/Dependencies.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// Dependencies.swift
//
//
// Created by Guilherme Souza on 24/11/23.
//

import Foundation

enum Dependencies {
static var makeTimeoutTimer: () -> TimeoutTimerProtocol = {
TimeoutTimer()
}

static var makeHeartbeatTimer: (_ timeInterval: TimeInterval, _ leeway: TimeInterval)
-> HeartbeatTimerProtocol = {
HeartbeatTimer(timeInterval: $0, leeway: $1)
}
}
Loading
Loading