Skip to content

Commit 811e222

Browse files
authored
fix: race condition when accessing SupabaseClient (#386)
1 parent d61365a commit 811e222

File tree

2 files changed

+98
-48
lines changed

2 files changed

+98
-48
lines changed

Sources/Supabase/Deprecated.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//
2+
// Deprecated.swift
3+
//
4+
//
5+
// Created by Guilherme Souza on 15/05/24.
6+
//
7+
8+
import Foundation
9+
10+
extension SupabaseClient {
11+
/// Database client for Supabase.
12+
@available(
13+
*,
14+
deprecated,
15+
message: "Direct access to database is deprecated, please use one of the available methods such as, SupabaseClient.from(_:), SupabaseClient.rpc(_:params:), or SupabaseClient.schema(_:)."
16+
)
17+
public var database: PostgrestClient {
18+
rest
19+
}
20+
21+
/// Realtime client for Supabase
22+
@available(*, deprecated, message: "Use realtimeV2")
23+
public var realtime: RealtimeClient {
24+
_realtime.value
25+
}
26+
}

Sources/Supabase/SupabaseClient.swift

Lines changed: 72 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public typealias SupabaseLogMessage = _Helpers.SupabaseLogMessage
1818
let version = _Helpers.version
1919

2020
/// Supabase Client.
21-
public final class SupabaseClient: @unchecked Sendable {
21+
public final class SupabaseClient: Sendable {
2222
let options: SupabaseClientOptions
2323
let supabaseURL: URL
2424
let supabaseKey: String
@@ -30,53 +30,74 @@ public final class SupabaseClient: @unchecked Sendable {
3030
/// by access policies.
3131
public let auth: AuthClient
3232

33-
/// Database client for Supabase.
34-
@available(
35-
*,
36-
deprecated,
37-
message: "Direct access to database is deprecated, please use one of the available methods such as, SupabaseClient.from(_:), SupabaseClient.rpc(_:params:), or SupabaseClient.schema(_:)."
38-
)
39-
public var database: PostgrestClient {
40-
rest
41-
}
33+
var rest: PostgrestClient {
34+
mutableState.withValue {
35+
if $0.rest == nil {
36+
$0.rest = PostgrestClient(
37+
url: databaseURL,
38+
schema: options.db.schema,
39+
headers: defaultHeaders.dictionary,
40+
logger: options.global.logger,
41+
fetch: fetchWithAuth,
42+
encoder: options.db.encoder,
43+
decoder: options.db.decoder
44+
)
45+
}
4246

43-
private lazy var rest = PostgrestClient(
44-
url: databaseURL,
45-
schema: options.db.schema,
46-
headers: defaultHeaders.dictionary,
47-
logger: options.global.logger,
48-
fetch: fetchWithAuth,
49-
encoder: options.db.encoder,
50-
decoder: options.db.decoder
51-
)
47+
return $0.rest!
48+
}
49+
}
5250

5351
/// Supabase Storage allows you to manage user-generated content, such as photos or videos.
54-
public private(set) lazy var storage = SupabaseStorageClient(
55-
configuration: StorageClientConfiguration(
56-
url: storageURL,
57-
headers: defaultHeaders.dictionary,
58-
session: StorageHTTPSession(fetch: fetchWithAuth, upload: uploadWithAuth),
59-
logger: options.global.logger
60-
)
61-
)
52+
public var storage: SupabaseStorageClient {
53+
mutableState.withValue {
54+
if $0.storage == nil {
55+
$0.storage = SupabaseStorageClient(
56+
configuration: StorageClientConfiguration(
57+
url: storageURL,
58+
headers: defaultHeaders.dictionary,
59+
session: StorageHTTPSession(fetch: fetchWithAuth, upload: uploadWithAuth),
60+
logger: options.global.logger
61+
)
62+
)
63+
}
6264

63-
/// Realtime client for Supabase
64-
public let realtime: RealtimeClient
65+
return $0.storage!
66+
}
67+
}
68+
69+
let _realtime: UncheckedSendable<RealtimeClient>
6570

6671
/// Realtime client for Supabase
6772
public let realtimeV2: RealtimeClientV2
6873

6974
/// Supabase Functions allows you to deploy and invoke edge functions.
70-
public private(set) lazy var functions = FunctionsClient(
71-
url: functionsURL,
72-
headers: defaultHeaders.dictionary,
73-
region: options.functions.region,
74-
logger: options.global.logger,
75-
fetch: fetchWithAuth
76-
)
75+
public var functions: FunctionsClient {
76+
mutableState.withValue {
77+
if $0.functions == nil {
78+
$0.functions = FunctionsClient(
79+
url: functionsURL,
80+
headers: defaultHeaders.dictionary,
81+
region: options.functions.region,
82+
logger: options.global.logger,
83+
fetch: fetchWithAuth
84+
)
85+
}
86+
87+
return $0.functions!
88+
}
89+
}
7790

7891
let defaultHeaders: HTTPHeaders
79-
private let listenForAuthEventsTask = LockIsolated(Task<Void, Never>?.none)
92+
93+
struct MutableState {
94+
var listenForAuthEventsTask: Task<Void, Never>?
95+
var storage: SupabaseStorageClient?
96+
var rest: PostgrestClient?
97+
var functions: FunctionsClient?
98+
}
99+
100+
private let mutableState = LockIsolated(MutableState())
80101

81102
private var session: URLSession {
82103
options.global.session
@@ -138,10 +159,12 @@ public final class SupabaseClient: @unchecked Sendable {
138159
}
139160
)
140161

141-
realtime = RealtimeClient(
142-
supabaseURL.appendingPathComponent("/realtime/v1").absoluteString,
143-
headers: defaultHeaders.dictionary,
144-
params: defaultHeaders.dictionary
162+
_realtime = UncheckedSendable(
163+
RealtimeClient(
164+
supabaseURL.appendingPathComponent("/realtime/v1").absoluteString,
165+
headers: defaultHeaders.dictionary,
166+
params: defaultHeaders.dictionary
167+
)
145168
)
146169

147170
var realtimeOptions = options.realtime
@@ -234,7 +257,7 @@ public final class SupabaseClient: @unchecked Sendable {
234257
}
235258

236259
deinit {
237-
listenForAuthEventsTask.value?.cancel()
260+
mutableState.listenForAuthEventsTask?.cancel()
238261
}
239262

240263
@Sendable
@@ -259,13 +282,14 @@ public final class SupabaseClient: @unchecked Sendable {
259282
}
260283

261284
private func listenForAuthEvents() {
262-
listenForAuthEventsTask.setValue(
263-
Task {
264-
for await (event, session) in auth.authStateChanges {
265-
await handleTokenChanged(event: event, session: session)
266-
}
285+
let task = Task {
286+
for await (event, session) in auth.authStateChanges {
287+
await handleTokenChanged(event: event, session: session)
267288
}
268-
)
289+
}
290+
mutableState.withValue {
291+
$0.listenForAuthEventsTask = task
292+
}
269293
}
270294

271295
private func handleTokenChanged(event: AuthChangeEvent, session: Session?) async {

0 commit comments

Comments
 (0)