Skip to content

Commit 2acc50c

Browse files
authored
refactor(realtime): drop actor from RealtimeChannel and RealtimeClient (#412)
* refactor(realtime): drop actor from RealtimeChannel and RealtimeClient * remove await
1 parent 40112d2 commit 2acc50c

File tree

8 files changed

+201
-122
lines changed

8 files changed

+201
-122
lines changed

Sources/Auth/AuthClient.swift

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,9 @@ public final class AuthClient: Sendable {
106106
/// - Parameters:
107107
/// - email: User's email address.
108108
/// - password: Password for the user.
109-
/// - data: User's metadata.
109+
/// - data: Custom data object to store additional user metadata.
110+
/// - redirectTo: The redirect URL embedded in the email link, defaults to ``Configuration/redirectToURL`` if not provided.
111+
/// - captchaToken: Optional captcha token for securing this endpoint.
110112
@discardableResult
111113
public func signUp(
112114
email: String,
@@ -145,7 +147,8 @@ public final class AuthClient: Sendable {
145147
/// - Parameters:
146148
/// - phone: User's phone number with international prefix.
147149
/// - password: Password for the user.
148-
/// - data: User's metadata.
150+
/// - data: Custom data object to store additional user metadata.
151+
/// - captchaToken: Optional captcha token for securing this endpoint.
149152
@discardableResult
150153
public func signUp(
151154
phone: String,
@@ -184,6 +187,10 @@ public final class AuthClient: Sendable {
184187
}
185188

186189
/// Log in an existing user with an email and password.
190+
/// - Parameters:
191+
/// - email: User's email address.
192+
/// - password: User's password.
193+
/// - captchaToken: Optional captcha token for securing this endpoint.
187194
@discardableResult
188195
public func signIn(
189196
email: String,
@@ -207,6 +214,10 @@ public final class AuthClient: Sendable {
207214
}
208215

209216
/// Log in an existing user with a phone and password.
217+
/// - Parameters:
218+
/// - email: User's phone number.
219+
/// - password: User's password.
220+
/// - captchaToken: Optional captcha token for securing this endpoint.
210221
@discardableResult
211222
public func signIn(
212223
phone: String,
@@ -333,8 +344,7 @@ public final class AuthClient: Sendable {
333344
/// - data: User's metadata.
334345
/// - captchaToken: Captcha verification token.
335346
///
336-
/// - Note: You need to configure a WhatsApp sender on Twillo if you are using phone sign in with
337-
/// the `whatsapp` channel.
347+
/// - Note: You need to configure a WhatsApp sender on Twillo if you are using phone sign in with the `whatsapp` channel.
338348
public func signInWithOTP(
339349
phone: String,
340350
channel: MessagingChannel = .sms,
@@ -362,8 +372,7 @@ public final class AuthClient: Sendable {
362372
/// Attempts a single-sign on using an enterprise Identity Provider.
363373
/// - Parameters:
364374
/// - domain: The email domain to use for signing in.
365-
/// - redirectTo: The URL to redirect the user to after they sign in with the third-party
366-
/// provider.
375+
/// - redirectTo: The URL to redirect the user to after they sign in with the third-party provider.
367376
/// - captchaToken: The captcha token to be used for captcha verification.
368377
/// - Returns: A URL that you can use to initiate the provider's authentication flow.
369378
public func signInWithSSO(

Sources/Realtime/V2/PushV2.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@ actor PushV2 {
2020
}
2121

2222
func send() async -> PushStatus {
23-
await channel?.socket?.push(message)
23+
await channel?.socket.push(message)
2424

2525
if channel?.config.broadcast.acknowledgeBroadcasts == true {
2626
do {
27-
return try await withTimeout(interval: channel?.socket?.options.timeoutInterval ?? 10) {
27+
return try await withTimeout(interval: channel?.socket.options().timeoutInterval ?? 10) {
2828
await withCheckedContinuation {
2929
self.receivedContinuation = $0
3030
}

Sources/Realtime/V2/RealtimeChannelV2.swift

Lines changed: 67 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,34 @@ public struct RealtimeChannelConfig: Sendable {
1414
public var presence: PresenceJoinConfig
1515
}
1616

17-
public actor RealtimeChannelV2 {
17+
struct Socket: Sendable {
18+
var status: @Sendable () -> RealtimeClientV2.Status
19+
var options: @Sendable () -> RealtimeClientOptions
20+
var accessToken: @Sendable () -> String?
21+
var makeRef: @Sendable () -> Int
22+
23+
var connect: @Sendable () async -> Void
24+
var addChannel: @Sendable (_ channel: RealtimeChannelV2) -> Void
25+
var removeChannel: @Sendable (_ channel: RealtimeChannelV2) async -> Void
26+
var push: @Sendable (_ message: RealtimeMessageV2) async -> Void
27+
}
28+
29+
extension Socket {
30+
init(client: RealtimeClientV2) {
31+
self.init(
32+
status: { [weak client] in client?.status ?? .disconnected },
33+
options: { [weak client] in client?.options ?? .init() },
34+
accessToken: { [weak client] in client?.mutableState.accessToken },
35+
makeRef: { [weak client] in client?.makeRef() ?? 0 },
36+
connect: { [weak client] in await client?.connect() },
37+
addChannel: { [weak client] in client?.addChannel($0) },
38+
removeChannel: { [weak client] in await client?.removeChannel($0) },
39+
push: { [weak client] in await client?.push($0) }
40+
)
41+
}
42+
}
43+
44+
public final class RealtimeChannelV2: Sendable {
1845
public typealias Subscription = ObservationToken
1946

2047
public enum Status: Sendable {
@@ -24,24 +51,22 @@ public actor RealtimeChannelV2 {
2451
case unsubscribing
2552
}
2653

27-
weak var socket: RealtimeClientV2? {
28-
didSet {
29-
assert(oldValue == nil, "socket should not be modified once set")
30-
}
54+
struct MutableState {
55+
var clientChanges: [PostgresJoinConfig] = []
56+
var joinRef: String?
57+
var pushes: [String: PushV2] = [:]
3158
}
3259

60+
private let mutableState = LockIsolated(MutableState())
61+
3362
let topic: String
3463
let config: RealtimeChannelConfig
3564
let logger: (any SupabaseLogger)?
65+
let socket: Socket
3666

3767
private let callbackManager = CallbackManager()
38-
3968
private let statusEventEmitter = EventEmitter<Status>(initialEvent: .unsubscribed)
4069

41-
private var clientChanges: [PostgresJoinConfig] = []
42-
private var joinRef: String?
43-
private var pushes: [String: PushV2] = [:]
44-
4570
public private(set) var status: Status {
4671
get { statusEventEmitter.lastEvent }
4772
set { statusEventEmitter.emit(newValue) }
@@ -54,13 +79,13 @@ public actor RealtimeChannelV2 {
5479
init(
5580
topic: String,
5681
config: RealtimeChannelConfig,
57-
socket: RealtimeClientV2,
82+
socket: Socket,
5883
logger: (any SupabaseLogger)?
5984
) {
60-
self.socket = socket
6185
self.topic = topic
6286
self.config = config
6387
self.logger = logger
88+
self.socket = socket
6489
}
6590

6691
deinit {
@@ -69,32 +94,33 @@ public actor RealtimeChannelV2 {
6994

7095
/// Subscribes to the channel
7196
public func subscribe() async {
72-
if await socket?.status != .connected {
73-
if socket?.options.connectOnSubscribe != true {
97+
if socket.status() != .connected {
98+
if socket.options().connectOnSubscribe != true {
7499
fatalError(
75100
"You can't subscribe to a channel while the realtime client is not connected. Did you forget to call `realtime.connect()`?"
76101
)
77102
}
78-
await socket?.connect()
103+
await socket.connect()
79104
}
80105

81-
await socket?.addChannel(self)
106+
socket.addChannel(self)
82107

83108
status = .subscribing
84109
logger?.debug("subscribing to channel \(topic)")
85110

86111
let joinConfig = RealtimeJoinConfig(
87112
broadcast: config.broadcast,
88113
presence: config.presence,
89-
postgresChanges: clientChanges
114+
postgresChanges: mutableState.clientChanges
90115
)
91116

92-
let payload = await RealtimeJoinPayload(
117+
let payload = RealtimeJoinPayload(
93118
config: joinConfig,
94-
accessToken: socket?.accessToken
119+
accessToken: socket.accessToken()
95120
)
96121

97-
joinRef = await socket?.makeRef().description
122+
let joinRef = socket.makeRef().description
123+
mutableState.withValue { $0.joinRef = joinRef }
98124

99125
logger?.debug("subscribing to channel with body: \(joinConfig)")
100126

@@ -109,7 +135,7 @@ public actor RealtimeChannelV2 {
109135
)
110136

111137
do {
112-
try await withTimeout(interval: socket?.options.timeoutInterval ?? 10) { [self] in
138+
try await withTimeout(interval: socket.options().timeoutInterval) { [self] in
113139
_ = await statusChange.first { @Sendable in $0 == .subscribed }
114140
}
115141
} catch {
@@ -128,8 +154,8 @@ public actor RealtimeChannelV2 {
128154

129155
await push(
130156
RealtimeMessageV2(
131-
joinRef: joinRef,
132-
ref: socket?.makeRef().description,
157+
joinRef: mutableState.joinRef,
158+
ref: socket.makeRef().description,
133159
topic: topic,
134160
event: ChannelEvent.leave,
135161
payload: [:]
@@ -141,8 +167,8 @@ public actor RealtimeChannelV2 {
141167
logger?.debug("Updating auth token for channel \(topic)")
142168
await push(
143169
RealtimeMessageV2(
144-
joinRef: joinRef,
145-
ref: socket?.makeRef().description,
170+
joinRef: mutableState.joinRef,
171+
ref: socket.makeRef().description,
146172
topic: topic,
147173
event: ChannelEvent.accessToken,
148174
payload: ["access_token": .string(jwt)]
@@ -162,8 +188,8 @@ public actor RealtimeChannelV2 {
162188

163189
await push(
164190
RealtimeMessageV2(
165-
joinRef: joinRef,
166-
ref: socket?.makeRef().description,
191+
joinRef: mutableState.joinRef,
192+
ref: socket.makeRef().description,
167193
topic: topic,
168194
event: ChannelEvent.broadcast,
169195
payload: [
@@ -187,8 +213,8 @@ public actor RealtimeChannelV2 {
187213

188214
await push(
189215
RealtimeMessageV2(
190-
joinRef: joinRef,
191-
ref: socket?.makeRef().description,
216+
joinRef: mutableState.joinRef,
217+
ref: socket.makeRef().description,
192218
topic: topic,
193219
event: ChannelEvent.presence,
194220
payload: [
@@ -203,8 +229,8 @@ public actor RealtimeChannelV2 {
203229
public func untrack() async {
204230
await push(
205231
RealtimeMessageV2(
206-
joinRef: joinRef,
207-
ref: socket?.makeRef().description,
232+
joinRef: mutableState.joinRef,
233+
ref: socket.makeRef().description,
208234
topic: topic,
209235
event: ChannelEvent.presence,
210236
payload: [
@@ -329,7 +355,7 @@ public actor RealtimeChannelV2 {
329355
Task { [weak self] in
330356
guard let self else { return }
331357

332-
await socket?.removeChannel(self)
358+
await socket.removeChannel(self)
333359
logger?.debug("Unsubscribed from channel \(message.topic)")
334360
}
335361

@@ -439,7 +465,9 @@ public actor RealtimeChannelV2 {
439465
filter: filter
440466
)
441467

442-
clientChanges.append(config)
468+
mutableState.withValue {
469+
$0.clientChanges.append(config)
470+
}
443471

444472
let id = callbackManager.addPostgresCallback(filter: config, callback: callback)
445473
return Subscription { [weak callbackManager, logger] in
@@ -464,14 +492,18 @@ public actor RealtimeChannelV2 {
464492
private func push(_ message: RealtimeMessageV2) async -> PushStatus {
465493
let push = PushV2(channel: self, message: message)
466494
if let ref = message.ref {
467-
pushes[ref] = push
495+
mutableState.withValue {
496+
$0.pushes[ref] = push
497+
}
468498
}
469499
return await push.send()
470500
}
471501

472502
private func didReceiveReply(ref: String, status: String) {
473503
Task {
474-
let push = pushes.removeValue(forKey: ref)
504+
let push = mutableState.withValue {
505+
$0.pushes.removeValue(forKey: ref)
506+
}
475507
await push?.didReceive(status: PushStatus(rawValue: status) ?? .ok)
476508
}
477509
}

0 commit comments

Comments
 (0)