Skip to content

Commit 0c8ffcc

Browse files
authored
refactor: share request logic between sub-packages (#141)
* SupabaseClient and PostgrestBuilder conform to sendable * Share request logic between sub-packages * Fix tests * Add HTTPClient type to centralize HTTP request execution * Move spi to type declaration * Make StorageApi conform to Sendable * Add Sendable conformance to HTTPClient
1 parent 9377ef5 commit 0c8ffcc

26 files changed

+601
-471
lines changed

.swiftpm/xcode/xcshareddata/xcschemes/Supabase.xcscheme

Lines changed: 7 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<Scheme
33
LastUpgradeVersion = "1500"
4-
version = "1.3">
4+
version = "1.7">
55
<BuildAction
66
parallelizeBuildables = "YES"
77
buildImplicitDependencies = "YES">
@@ -27,78 +27,12 @@
2727
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
2828
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
2929
shouldUseLaunchSchemeArgsEnv = "YES">
30-
<Testables>
31-
<TestableReference
32-
skipped = "NO">
33-
<BuildableReference
34-
BuildableIdentifier = "primary"
35-
BlueprintIdentifier = "SupabaseTests"
36-
BuildableName = "SupabaseTests"
37-
BlueprintName = "SupabaseTests"
38-
ReferencedContainer = "container:">
39-
</BuildableReference>
40-
</TestableReference>
41-
<TestableReference
42-
skipped = "NO">
43-
<BuildableReference
44-
BuildableIdentifier = "primary"
45-
BlueprintIdentifier = "FunctionsTests"
46-
BuildableName = "FunctionsTests"
47-
BlueprintName = "FunctionsTests"
48-
ReferencedContainer = "container:">
49-
</BuildableReference>
50-
</TestableReference>
51-
<TestableReference
52-
skipped = "NO">
53-
<BuildableReference
54-
BuildableIdentifier = "primary"
55-
BlueprintIdentifier = "GoTrueTests"
56-
BuildableName = "GoTrueTests"
57-
BlueprintName = "GoTrueTests"
58-
ReferencedContainer = "container:">
59-
</BuildableReference>
60-
</TestableReference>
61-
<TestableReference
62-
skipped = "NO">
63-
<BuildableReference
64-
BuildableIdentifier = "primary"
65-
BlueprintIdentifier = "PostgRESTIntegrationTests"
66-
BuildableName = "PostgRESTIntegrationTests"
67-
BlueprintName = "PostgRESTIntegrationTests"
68-
ReferencedContainer = "container:">
69-
</BuildableReference>
70-
</TestableReference>
71-
<TestableReference
72-
skipped = "NO">
73-
<BuildableReference
74-
BuildableIdentifier = "primary"
75-
BlueprintIdentifier = "PostgRESTTests"
76-
BuildableName = "PostgRESTTests"
77-
BlueprintName = "PostgRESTTests"
78-
ReferencedContainer = "container:">
79-
</BuildableReference>
80-
</TestableReference>
81-
<TestableReference
82-
skipped = "NO">
83-
<BuildableReference
84-
BuildableIdentifier = "primary"
85-
BlueprintIdentifier = "RealtimeTests"
86-
BuildableName = "RealtimeTests"
87-
BlueprintName = "RealtimeTests"
88-
ReferencedContainer = "container:">
89-
</BuildableReference>
90-
</TestableReference>
91-
<TestableReference
92-
skipped = "NO">
93-
<BuildableReference
94-
BuildableIdentifier = "primary"
95-
BlueprintIdentifier = "StorageTests"
96-
BuildableName = "StorageTests"
97-
BlueprintName = "StorageTests"
98-
ReferencedContainer = "container:">
99-
</BuildableReference>
100-
</TestableReference>
101-
</Testables>
30+
<TestPlans>
31+
<TestPlanReference
32+
reference = "container:Supabase.xctestplan"
33+
default = "YES">
34+
</TestPlanReference>
35+
</TestPlans>
10236
</TestAction>
10337
<LaunchAction
10438
buildConfiguration = "Debug"

Examples/Examples/AuthView.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@ final class AuthController: ObservableObject {
2121
}
2222

2323
func observeAuth() async {
24-
for await event in await supabase.auth.onAuthStateChange() {
24+
for await (event, session) in await supabase.auth.onAuthStateChange() {
2525
guard event == .signedIn || event == .signedOut else {
2626
return
2727
}
2828

29-
session = try? await supabase.auth.session
29+
self.session = session
3030
}
3131
}
3232
}

Sources/GoTrue/GoTrueClient.swift

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ public actor GoTrueClient {
130130
/// - Parameters:
131131
/// - configuration: The client configuration.
132132
public init(configuration: Configuration) {
133-
let api = APIClient()
133+
let api = APIClient(http: HTTPClient(fetchHandler: configuration.fetch))
134134

135135
self.init(
136136
configuration: configuration,
@@ -173,7 +173,10 @@ public actor GoTrueClient {
173173
/// Listen for auth state changes.
174174
///
175175
/// An `.initialSession` is always emitted when this method is called.
176-
public func onAuthStateChange() async -> AsyncStream<(event: AuthChangeEvent, session: Session?)> {
176+
public func onAuthStateChange() async -> AsyncStream<(
177+
event: AuthChangeEvent,
178+
session: Session?
179+
)> {
177180
let (id, stream) = await eventEmitter.attachListener()
178181

179182
Task { [id] in
@@ -201,7 +204,7 @@ public actor GoTrueClient {
201204
return try await _signUp(
202205
request: .init(
203206
path: "/signup",
204-
method: "POST",
207+
method: .post,
205208
query: [
206209
redirectTo.map { URLQueryItem(name: "redirect_to", value: $0.absoluteString) },
207210
].compactMap { $0 },
@@ -234,7 +237,7 @@ public actor GoTrueClient {
234237
try await _signUp(
235238
request: .init(
236239
path: "/signup",
237-
method: "POST",
240+
method: .post,
238241
body: configuration.encoder.encode(
239242
SignUpRequest(
240243
password: password,
@@ -268,7 +271,7 @@ public actor GoTrueClient {
268271
try await _signIn(
269272
request: .init(
270273
path: "/token",
271-
method: "POST",
274+
method: .post,
272275
query: [URLQueryItem(name: "grant_type", value: "password")],
273276
body: configuration.encoder.encode(
274277
UserCredentials(email: email, password: password)
@@ -283,7 +286,7 @@ public actor GoTrueClient {
283286
try await _signIn(
284287
request: .init(
285288
path: "/token",
286-
method: "POST",
289+
method: .post,
287290
query: [URLQueryItem(name: "grant_type", value: "password")],
288291
body: configuration.encoder.encode(
289292
UserCredentials(password: password, phone: phone)
@@ -299,7 +302,7 @@ public actor GoTrueClient {
299302
try await _signIn(
300303
request: .init(
301304
path: "/token",
302-
method: "POST",
305+
method: .post,
303306
query: [URLQueryItem(name: "grant_type", value: "id_token")],
304307
body: configuration.encoder.encode(credentials)
305308
)
@@ -347,7 +350,7 @@ public actor GoTrueClient {
347350
try await api.execute(
348351
.init(
349352
path: "/otp",
350-
method: "POST",
353+
method: .post,
351354
query: [
352355
redirectTo.map { URLQueryItem(name: "redirect_to", value: $0.absoluteString) },
353356
].compactMap { $0 },
@@ -382,7 +385,7 @@ public actor GoTrueClient {
382385
try await api.execute(
383386
.init(
384387
path: "/otp",
385-
method: "POST",
388+
method: .post,
386389
body: configuration.encoder.encode(
387390
OTPParams(
388391
phone: phone,
@@ -404,7 +407,7 @@ public actor GoTrueClient {
404407
let session: Session = try await api.execute(
405408
.init(
406409
path: "/token",
407-
method: "POST",
410+
method: .post,
408411
query: [URLQueryItem(name: "grant_type", value: "pkce")],
409412
body: configuration.encoder.encode(
410413
[
@@ -516,7 +519,7 @@ public actor GoTrueClient {
516519
let user = try await api.execute(
517520
.init(
518521
path: "/user",
519-
method: "GET",
522+
method: .get,
520523
headers: ["Authorization": "\(tokenType) \(accessToken)"]
521524
)
522525
).decoded(as: User.self, decoder: configuration.decoder)
@@ -590,7 +593,7 @@ public actor GoTrueClient {
590593
try await api.authorizedExecute(
591594
.init(
592595
path: "/logout",
593-
method: "POST"
596+
method: .post
594597
)
595598
)
596599
await sessionManager.remove()
@@ -613,7 +616,7 @@ public actor GoTrueClient {
613616
try await _verifyOTP(
614617
request: .init(
615618
path: "/verify",
616-
method: "POST",
619+
method: .post,
617620
query: [
618621
redirectTo.map { URLQueryItem(name: "redirect_to", value: $0.absoluteString) },
619622
].compactMap { $0 },
@@ -641,7 +644,7 @@ public actor GoTrueClient {
641644
try await _verifyOTP(
642645
request: .init(
643646
path: "/verify",
644-
method: "POST",
647+
method: .post,
645648
body: configuration.encoder.encode(
646649
VerifyOTPParams(
647650
phone: phone,
@@ -683,7 +686,7 @@ public actor GoTrueClient {
683686
/// Should be used only when you require the most current user data. For faster results,
684687
/// session.user is recommended.
685688
public func user(jwt: String? = nil) async throws -> User {
686-
var request = Request(path: "/user", method: "GET")
689+
var request = Request(path: "/user", method: .get)
687690

688691
if let jwt {
689692
request.headers["Authorization"] = "Bearer \(jwt)"
@@ -706,7 +709,7 @@ public actor GoTrueClient {
706709

707710
var session = try await sessionManager.session()
708711
let updatedUser = try await api.authorizedExecute(
709-
.init(path: "/user", method: "PUT", body: configuration.encoder.encode(user))
712+
.init(path: "/user", method: .put, body: configuration.encoder.encode(user))
710713
).decoded(as: User.self, decoder: configuration.decoder)
711714
session.user = updatedUser
712715
try await sessionManager.update(session)
@@ -725,7 +728,7 @@ public actor GoTrueClient {
725728
try await api.execute(
726729
.init(
727730
path: "/recover",
728-
method: "POST",
731+
method: .post,
729732
query: [
730733
redirectTo.map { URLQueryItem(name: "redirect_to", value: $0.absoluteString) },
731734
].compactMap { $0 },
@@ -756,7 +759,7 @@ public actor GoTrueClient {
756759
let session = try await api.execute(
757760
.init(
758761
path: "/token",
759-
method: "POST",
762+
method: .post,
760763
query: [URLQueryItem(name: "grant_type", value: "refresh_token")],
761764
body: configuration.encoder.encode(credentials)
762765
)

Sources/GoTrue/GoTrueMFA.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public actor GoTrueMFA {
3333
public func enroll(params: MFAEnrollParams) async throws -> AuthMFAEnrollResponse {
3434
try await api.authorizedExecute(
3535
Request(
36-
path: "/factors", method: "POST",
36+
path: "/factors", method: .post,
3737
body: configuration.encoder.encode(params)
3838
)
3939
)
@@ -46,7 +46,7 @@ public actor GoTrueMFA {
4646
/// - Returns: An authentication response with the challenge information.
4747
public func challenge(params: MFAChallengeParams) async throws -> AuthMFAChallengeResponse {
4848
try await api.authorizedExecute(
49-
Request(path: "/factors/\(params.factorId)/challenge", method: "POST")
49+
Request(path: "/factors/\(params.factorId)/challenge", method: .post)
5050
)
5151
.decoded(decoder: configuration.decoder)
5252
}
@@ -59,7 +59,7 @@ public actor GoTrueMFA {
5959
public func verify(params: MFAVerifyParams) async throws -> AuthMFAVerifyResponse {
6060
let response: AuthMFAVerifyResponse = try await api.authorizedExecute(
6161
Request(
62-
path: "/factors/\(params.factorId)/verify", method: "POST",
62+
path: "/factors/\(params.factorId)/verify", method: .post,
6363
body: configuration.encoder.encode(params)
6464
)
6565
).decoded(decoder: configuration.decoder)
@@ -79,7 +79,7 @@ public actor GoTrueMFA {
7979
@discardableResult
8080
public func unenroll(params: MFAUnenrollParams) async throws -> AuthMFAUnenrollResponse {
8181
try await api.authorizedExecute(
82-
Request(path: "/factors/\(params.factorId)", method: "DELETE")
82+
Request(path: "/factors/\(params.factorId)", method: .delete)
8383
)
8484
.decoded(decoder: configuration.decoder)
8585
}

Sources/GoTrue/Internal/APIClient.swift

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ actor APIClient {
1010
Dependencies.current.value!.sessionManager
1111
}
1212

13+
let http: HTTPClient
14+
15+
init(http: HTTPClient) {
16+
self.http = http
17+
}
18+
1319
@discardableResult
1420
func authorizedExecute(_ request: Request) async throws -> Response {
1521
let session = try await sessionManager.session()
@@ -24,18 +30,17 @@ actor APIClient {
2430
func execute(_ request: Request) async throws -> Response {
2531
var request = request
2632
request.headers.merge(configuration.headers) { r, _ in r }
27-
let urlRequest = try request.urlRequest(withBaseURL: configuration.url)
2833

29-
let (data, response) = try await configuration.fetch(urlRequest)
30-
guard let httpResponse = response as? HTTPURLResponse else {
31-
throw URLError(.badServerResponse)
32-
}
34+
let response = try await http.fetch(request, baseURL: configuration.url)
3335

34-
guard (200 ..< 300).contains(httpResponse.statusCode) else {
35-
let apiError = try configuration.decoder.decode(GoTrueError.APIError.self, from: data)
36+
guard (200 ..< 300).contains(response.statusCode) else {
37+
let apiError = try configuration.decoder.decode(
38+
GoTrueError.APIError.self,
39+
from: response.data
40+
)
3641
throw GoTrueError.api(apiError)
3742
}
3843

39-
return Response(data: data, response: httpResponse)
44+
return response
4045
}
4146
}

Sources/GoTrue/Internal/EventEmitter.swift

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ import Foundation
22
@_spi(Internal) import _Helpers
33

44
struct EventEmitter: Sendable {
5-
var attachListener: @Sendable () async -> (id: UUID, stream: AsyncStream<(event: AuthChangeEvent, session: Session?)>)
5+
var attachListener: @Sendable () async -> (
6+
id: UUID,
7+
stream: AsyncStream<(event: AuthChangeEvent, session: Session?)>
8+
)
69
var emit: @Sendable (_ event: AuthChangeEvent, _ session: Session?, _ id: UUID?) async -> Void
710
}
811

@@ -14,13 +17,15 @@ extension EventEmitter {
1417

1518
extension EventEmitter {
1619
static var live: Self = {
17-
let continuations = ActorIsolated([UUID: AsyncStream<(event: AuthChangeEvent, session: Session?)>.Continuation]())
20+
let continuations =
21+
ActorIsolated([UUID: AsyncStream<(event: AuthChangeEvent, session: Session?)>.Continuation]())
1822

1923
return Self(
2024
attachListener: {
2125
let id = UUID()
2226

23-
let (stream, continuation) = AsyncStream<(event: AuthChangeEvent, session: Session?)>.makeStream()
27+
let (stream, continuation) = AsyncStream<(event: AuthChangeEvent, session: Session?)>
28+
.makeStream()
2429

2530
continuation.onTermination = { [id] _ in
2631
continuations.withValue {

0 commit comments

Comments
 (0)