diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9d3a6e27..24b143a3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: matrix: command: [test, ""] platform: [IOS, MAC_CATALYST, MACOS, TVOS, VISIONOS, WATCHOS] - xcode: [15.4, "16.0"] + xcode: ["15.4", "16.0"] exclude: - { platform: VISIONOS } include: diff --git a/Makefile b/Makefile index 4961c1b4..7d9f5b66 100644 --- a/Makefile +++ b/Makefile @@ -81,6 +81,22 @@ test-docs: format: @swift format -i -r --ignore-unparsable-files . + +test-linux: + docker run \ + --rm \ + -v "$(PWD):$(PWD)" \ + -w "$(PWD)" \ + swift:5.10 \ + bash -c 'swift test -c $(CONFIG)' + +build-linux: + docker run \ + --rm \ + -v "$(PWD):$(PWD)" \ + -w "$(PWD)" \ + swift:5.9 \ + bash -c 'swift build -c $(CONFIG)' .PHONY: build-for-library-evolution format xcodebuild test-docs test-integration define udid_for diff --git a/Sources/Auth/AuthClient.swift b/Sources/Auth/AuthClient.swift index 854a7c4b..1cec6346 100644 --- a/Sources/Auth/AuthClient.swift +++ b/Sources/Auth/AuthClient.swift @@ -21,7 +21,9 @@ public final class AuthClient: Sendable { private var api: APIClient { Dependencies[clientID].api } var configuration: AuthClient.Configuration { Dependencies[clientID].configuration } - private var codeVerifierStorage: CodeVerifierStorage { Dependencies[clientID].codeVerifierStorage } + private var codeVerifierStorage: CodeVerifierStorage { + Dependencies[clientID].codeVerifierStorage + } private var date: @Sendable () -> Date { Dependencies[clientID].date } private var sessionManager: SessionManager { Dependencies[clientID].sessionManager } private var eventEmitter: AuthStateChangeEventEmitter { Dependencies[clientID].eventEmitter } @@ -77,10 +79,11 @@ public final class AuthClient: Sendable { sessionManager: .live(clientID: clientID) ) - observeAppLifecycleChanges() + Task { @MainActor in observeAppLifecycleChanges() } } #if canImport(ObjectiveC) + @MainActor private func observeAppLifecycleChanges() { #if canImport(UIKit) #if canImport(WatchKit) @@ -165,14 +168,20 @@ public final class AuthClient: Sendable { /// Listen for auth state changes. /// /// An `.initialSession` is always emitted when this method is called. - public var authStateChanges: AsyncStream<( - event: AuthChangeEvent, - session: Session? - )> { - let (stream, continuation) = AsyncStream<( - event: AuthChangeEvent, - session: Session? - )>.makeStream() + public var authStateChanges: + AsyncStream< + ( + event: AuthChangeEvent, + session: Session? + ) + > + { + let (stream, continuation) = AsyncStream< + ( + event: AuthChangeEvent, + session: Session? + ) + >.makeStream() Task { let handle = await onAuthStateChange { event, session in @@ -209,10 +218,12 @@ public final class AuthClient: Sendable { url: configuration.url.appendingPathComponent("signup"), method: .post, query: [ - (redirectTo ?? configuration.redirectToURL).map { URLQueryItem( - name: "redirect_to", - value: $0.absoluteString - ) }, + (redirectTo ?? configuration.redirectToURL).map { + URLQueryItem( + name: "redirect_to", + value: $0.absoluteString + ) + } ].compactMap { $0 }, body: configuration.encoder.encode( SignUpRequest( @@ -401,10 +412,12 @@ public final class AuthClient: Sendable { url: configuration.url.appendingPathComponent("otp"), method: .post, query: [ - (redirectTo ?? configuration.redirectToURL).map { URLQueryItem( - name: "redirect_to", - value: $0.absoluteString - ) }, + (redirectTo ?? configuration.redirectToURL).map { + URLQueryItem( + name: "redirect_to", + value: $0.absoluteString + ) + } ].compactMap { $0 }, body: configuration.encoder.encode( OTPParams( @@ -524,7 +537,8 @@ public final class AuthClient: Sendable { let codeVerifier = codeVerifierStorage.get() if codeVerifier == nil { - logger?.error("code verifier not found, a code verifier should exist when calling this method.") + logger?.error( + "code verifier not found, a code verifier should exist when calling this method.") } let session: Session = try await api.execute( @@ -878,7 +892,9 @@ public final class AuthClient: Sendable { headers: [.authorization: "Bearer \(accessToken)"] ) ) - } catch let AuthError.api(_, _, _, response) where [404, 403, 401].contains(response.statusCode) { + } catch let AuthError.api(_, _, _, response) + where [404, 403, 401].contains(response.statusCode) + { // ignore 404s since user might not exist anymore // ignore 401s, and 403s since an invalid or expired JWT should sign out the current session. } @@ -898,10 +914,12 @@ public final class AuthClient: Sendable { url: configuration.url.appendingPathComponent("verify"), method: .post, query: [ - (redirectTo ?? configuration.redirectToURL).map { URLQueryItem( - name: "redirect_to", - value: $0.absoluteString - ) }, + (redirectTo ?? configuration.redirectToURL).map { + URLQueryItem( + name: "redirect_to", + value: $0.absoluteString + ) + } ].compactMap { $0 }, body: configuration.encoder.encode( VerifyOTPParams.email( @@ -991,10 +1009,12 @@ public final class AuthClient: Sendable { url: configuration.url.appendingPathComponent("resend"), method: .post, query: [ - (emailRedirectTo ?? configuration.redirectToURL).map { URLQueryItem( - name: "redirect_to", - value: $0.absoluteString - ) }, + (emailRedirectTo ?? configuration.redirectToURL).map { + URLQueryItem( + name: "redirect_to", + value: $0.absoluteString + ) + } ].compactMap { $0 }, body: configuration.encoder.encode( ResendEmailParams( @@ -1078,10 +1098,12 @@ public final class AuthClient: Sendable { url: configuration.url.appendingPathComponent("user"), method: .put, query: [ - (redirectTo ?? configuration.redirectToURL).map { URLQueryItem( - name: "redirect_to", - value: $0.absoluteString - ) }, + (redirectTo ?? configuration.redirectToURL).map { + URLQueryItem( + name: "redirect_to", + value: $0.absoluteString + ) + } ].compactMap { $0 }, body: configuration.encoder.encode(user) ) @@ -1213,10 +1235,12 @@ public final class AuthClient: Sendable { url: configuration.url.appendingPathComponent("recover"), method: .post, query: [ - (redirectTo ?? configuration.redirectToURL).map { URLQueryItem( - name: "redirect_to", - value: $0.absoluteString - ) }, + (redirectTo ?? configuration.redirectToURL).map { + URLQueryItem( + name: "redirect_to", + value: $0.absoluteString + ) + } ].compactMap { $0 }, body: configuration.encoder.encode( RecoverParams( @@ -1300,7 +1324,7 @@ public final class AuthClient: Sendable { } var queryItems: [URLQueryItem] = [ - URLQueryItem(name: "provider", value: provider.rawValue), + URLQueryItem(name: "provider", value: provider.rawValue) ] if let scopes { diff --git a/Sources/Storage/StorageFileApi.swift b/Sources/Storage/StorageFileApi.swift index f98c31b8..42613097 100644 --- a/Sources/Storage/StorageFileApi.swift +++ b/Sources/Storage/StorageFileApi.swift @@ -51,13 +51,8 @@ enum FileUpload { } #if DEBUG - #if compiler(>=6) - // It is safe to mark it as unsafe, since this property is only used in tests for overriding the - // boundary value, instead of using the random one. - nonisolated(unsafe) var testingBoundary: String? - #else - var testingBoundary: String? - #endif + import ConcurrencyExtras + let testingBoundary = LockIsolated(nil) #endif /// Supabase Storage File API @@ -94,7 +89,7 @@ public class StorageFileApi: StorageApi, @unchecked Sendable { headers[.duplex] = options.duplex #if DEBUG - let formData = MultipartFormData(boundary: testingBoundary) + let formData = MultipartFormData(boundary: testingBoundary.value) #else let formData = MultipartFormData() #endif diff --git a/Tests/StorageTests/SupabaseStorageTests.swift b/Tests/StorageTests/SupabaseStorageTests.swift index 1f36c366..5742538d 100644 --- a/Tests/StorageTests/SupabaseStorageTests.swift +++ b/Tests/StorageTests/SupabaseStorageTests.swift @@ -94,7 +94,7 @@ final class SupabaseStorageTests: XCTestCase { #if !os(Linux) func testUploadData() async throws { - testingBoundary = "alamofire.boundary.c21f947c1c7b0c57" + testingBoundary.setValue("alamofire.boundary.c21f947c1c7b0c57") sessionMock.fetch = { request in assertInlineSnapshot(of: request, as: .curl) { @@ -155,7 +155,7 @@ final class SupabaseStorageTests: XCTestCase { } func testUploadFileURL() async throws { - testingBoundary = "alamofire.boundary.c21f947c1c7b0c57" + testingBoundary.setValue("alamofire.boundary.c21f947c1c7b0c57") sessionMock.fetch = { request in assertInlineSnapshot(of: request, as: .curl) {