Skip to content

Commit

Permalink
refactor: simplify Auth dependency system (#278)
Browse files Browse the repository at this point in the history
* refactor: simplify Auth dependency system

* Remove unused code from tests

* wip
  • Loading branch information
grdsdev authored Mar 26, 2024
1 parent 363aa00 commit 831a1f7
Show file tree
Hide file tree
Showing 17 changed files with 389 additions and 414 deletions.
10 changes: 4 additions & 6 deletions Sources/Auth/AuthAdmin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,11 @@ import Foundation
@_spi(Internal) import _Helpers

public actor AuthAdmin {
private var configuration: AuthClient.Configuration {
Dependencies.current.value!.configuration
}
@Dependency(\.configuration)
private var configuration: AuthClient.Configuration

private var api: APIClient {
Dependencies.current.value!.api
}
@Dependency(\.api)
private var api: APIClient

/// Delete a user. Requires `service_role` key.
/// - Parameter id: The id of the user you want to delete.
Expand Down
96 changes: 49 additions & 47 deletions Sources/Auth/AuthClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,33 +56,26 @@ public actor AuthClient {
}
}

private var configuration: Configuration {
Dependencies.current.value!.configuration
}
@Dependency(\.configuration)
private var configuration: Configuration

private var api: APIClient {
Dependencies.current.value!.api
}
@Dependency(\.api)
private var api: APIClient

private var sessionManager: any SessionManager {
Dependencies.current.value!.sessionManager
}
@Dependency(\.eventEmitter)
private var eventEmitter: EventEmitter

private var codeVerifierStorage: CodeVerifierStorage {
Dependencies.current.value!.codeVerifierStorage
}
@Dependency(\.sessionManager)
private var sessionManager: SessionManager

private var eventEmitter: any EventEmitter {
Dependencies.current.value!.eventEmitter
}
@Dependency(\.codeVerifierStorage)
private var codeVerifierStorage: CodeVerifierStorage

private var currentDate: @Sendable () -> Date {
Dependencies.current.value!.currentDate
}
@Dependency(\.currentDate)
private var currentDate: @Sendable () -> Date

private var logger: (any SupabaseLogger)? {
Dependencies.current.value!.logger
}
@Dependency(\.logger)
private var logger: (any SupabaseLogger)?

/// Returns the session, refreshing it if necessary.
///
Expand Down Expand Up @@ -141,17 +134,20 @@ public actor AuthClient {
/// - Parameters:
/// - configuration: The client configuration.
public init(configuration: Configuration) {
let api = APIClient.live(http: HTTPClient(
logger: configuration.logger,
fetchHandler: configuration.fetch
))
let api = APIClient.live(
configuration: configuration,
http: HTTPClient(
logger: configuration.logger,
fetchHandler: configuration.fetch
)
)

self.init(
configuration: configuration,
sessionManager: DefaultSessionManager.shared,
sessionManager: .live,
codeVerifierStorage: .live,
api: api,
eventEmitter: DefaultEventEmitter.shared,
eventEmitter: .live,
sessionStorage: .live,
logger: configuration.logger
)
Expand All @@ -160,31 +156,29 @@ public actor AuthClient {
/// This internal initializer is here only for easy injecting mock instances when testing.
init(
configuration: Configuration,
sessionManager: any SessionManager,
sessionManager: SessionManager,
codeVerifierStorage: CodeVerifierStorage,
api: APIClient,
eventEmitter: any EventEmitter,
eventEmitter: EventEmitter,
sessionStorage: SessionStorage,
logger: (any SupabaseLogger)?
) {
mfa = AuthMFA()
admin = AuthAdmin()

Dependencies.current.setValue(
Dependencies(
configuration: configuration,
sessionManager: sessionManager,
api: api,
eventEmitter: eventEmitter,
sessionStorage: sessionStorage,
sessionRefresher: SessionRefresher(
refreshSession: { [weak self] in
try await self?.refreshSession(refreshToken: $0) ?? .empty
}
),
codeVerifierStorage: codeVerifierStorage,
logger: logger
)
Current = Dependencies(
configuration: configuration,
sessionManager: sessionManager,
api: api,
eventEmitter: eventEmitter,
sessionStorage: sessionStorage,
sessionRefresher: SessionRefresher(
refreshSession: { [weak self] in
try await self?.refreshSession(refreshToken: $0) ?? .empty
}
),
codeVerifierStorage: codeVerifierStorage,
logger: logger
)
}

Expand Down Expand Up @@ -310,7 +304,11 @@ public actor AuthClient {

/// Log in an existing user with an email and password.
@discardableResult
public func signIn(email: String, password: String, captchaToken: String? = nil) async throws -> Session {
public func signIn(
email: String,
password: String,
captchaToken: String? = nil
) async throws -> Session {
try await _signIn(
request: .init(
path: "/token",
Expand All @@ -329,7 +327,11 @@ public actor AuthClient {

/// Log in an existing user with a phone and password.
@discardableResult
public func signIn(phone: String, password: String, captchaToken: String? = nil) async throws -> Session {
public func signIn(
phone: String,
password: String,
captchaToken: String? = nil
) async throws -> Session {
try await _signIn(
request: .init(
path: "/token",
Expand Down Expand Up @@ -931,7 +933,7 @@ public actor AuthClient {

private func emitInitialSession(forToken token: ObservationToken) async {
let session = try? await session
eventEmitter.emit(.initialSession, session: session, token: token)
eventEmitter.emit(.initialSession, session, token)
}

private func prepareForPKCE() -> (codeChallenge: String?, codeChallengeMethod: String?) {
Expand Down
20 changes: 8 additions & 12 deletions Sources/Auth/AuthMFA.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,17 @@ import Foundation

/// Contains the full multi-factor authentication API.
public actor AuthMFA {
private var api: APIClient {
Dependencies.current.value!.api
}
@Dependency(\.api)
private var api: APIClient

private var sessionManager: any SessionManager {
Dependencies.current.value!.sessionManager
}
@Dependency(\.configuration)
private var configuration: AuthClient.Configuration

private var configuration: AuthClient.Configuration {
Dependencies.current.value!.configuration
}
@Dependency(\.sessionManager)
private var sessionManager: SessionManager

private var eventEmitter: any EventEmitter {
Dependencies.current.value!.eventEmitter
}
@Dependency(\.eventEmitter)
private var eventEmitter: EventEmitter

/// Starts the enrollment process for a new Multi-Factor Authentication (MFA) factor. This method
/// creates a new `unverified` factor.
Expand Down
15 changes: 8 additions & 7 deletions Sources/Auth/Internal/APIClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,11 @@ struct APIClient: Sendable {
}

extension APIClient {
static func live(http: HTTPClient) -> Self {
var configuration: AuthClient.Configuration {
Dependencies.current.value!.configuration
}

return APIClient(
static func live(
configuration: AuthClient.Configuration,
http: HTTPClient
) -> Self {
APIClient(
execute: { request in
var request = request
request.headers.merge(configuration.headers) { r, _ in r }
Expand Down Expand Up @@ -45,7 +44,9 @@ extension APIClient {
extension APIClient {
@discardableResult
func authorizedExecute(_ request: Request) async throws -> Response {
let session = try await Dependencies.current.value!.sessionManager.session()
@Dependency(\.sessionManager) var sessionManager

let session = try await sessionManager.session()

var request = request
request.headers["Authorization"] = "Bearer \(session.accessToken)"
Expand Down
6 changes: 2 additions & 4 deletions Sources/Auth/Internal/CodeVerifierStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,8 @@ struct CodeVerifierStorage: Sendable {
}

extension CodeVerifierStorage {
static var live: Self = {
var localStorage: any AuthLocalStorage {
Dependencies.current.value!.configuration.localStorage
}
static let live: Self = {
@Dependency(\.configuration.localStorage) var localStorage

let key = "supabase.code-verifier"

Expand Down
35 changes: 31 additions & 4 deletions Sources/Auth/Internal/Dependencies.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,42 @@ import ConcurrencyExtras
import Foundation

struct Dependencies: Sendable {
static let current = LockIsolated(Dependencies?.none)

var configuration: AuthClient.Configuration
var sessionManager: any SessionManager
var sessionManager: SessionManager
var api: APIClient
var eventEmitter: any EventEmitter
var eventEmitter: EventEmitter
var sessionStorage: SessionStorage
var sessionRefresher: SessionRefresher
var codeVerifierStorage: CodeVerifierStorage
var currentDate: @Sendable () -> Date = { Date() }
var logger: (any SupabaseLogger)?
}

private let _Current = LockIsolated<Dependencies?>(nil)
var Current: Dependencies {
get {
guard let instance = _Current.value else {
fatalError("Current should be set before usage.")
}

return instance
}
set {
_Current.withValue { Current in
Current = newValue
}
}
}

@propertyWrapper
struct Dependency<Value> {
var wrappedValue: Value {
Current[keyPath: keyPath]
}

let keyPath: KeyPath<Dependencies, Value>

init(_ keyPath: KeyPath<Dependencies, Value>) {
self.keyPath = keyPath
}
}
73 changes: 33 additions & 40 deletions Sources/Auth/Internal/EventEmitter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,60 +2,53 @@ import ConcurrencyExtras
import Foundation
@_spi(Internal) import _Helpers

protocol EventEmitter: Sendable {
func attachListener(
struct EventEmitter: Sendable {
var attachListener: @Sendable (
_ listener: @escaping AuthStateChangeListener
) -> ObservationToken

func emit(
var emit: @Sendable (
_ event: AuthChangeEvent,
session: Session?,
token: ObservationToken?
)
_ session: Session?,
_ token: ObservationToken?
) -> Void
}

extension EventEmitter {
func emit(
_ event: AuthChangeEvent,
session: Session?
) {
emit(event, session: session, token: nil)
emit(event, session, nil)
}
}

final class DefaultEventEmitter: EventEmitter {
static let shared = DefaultEventEmitter()

private init() {}

let emitter = _Helpers.EventEmitter<(AuthChangeEvent, Session?)?>(
initialEvent: nil,
emitsLastEventWhenAttaching: false
)

func attachListener(
_ listener: @escaping AuthStateChangeListener
) -> ObservationToken {
emitter.attach { event in
guard let event else { return }
listener(event.0, event.1)
}
}

func emit(
_ event: AuthChangeEvent,
session: Session?,
token: ObservationToken? = nil
) {
NotificationCenter.default.post(
name: AuthClient.didChangeAuthStateNotification,
object: nil,
userInfo: [
AuthClient.authChangeEventInfoKey: event,
AuthClient.authChangeSessionInfoKey: session as Any,
]
extension EventEmitter {
static let live: EventEmitter = {
let emitter = _Helpers.EventEmitter<(AuthChangeEvent, Session?)?>(
initialEvent: nil,
emitsLastEventWhenAttaching: false
)

emitter.emit((event, session), to: token)
}
return EventEmitter(
attachListener: { listener in
emitter.attach { event in
guard let event else { return }
listener(event.0, event.1)
}
},
emit: { event, session, token in
NotificationCenter.default.post(
name: AuthClient.didChangeAuthStateNotification,
object: nil,
userInfo: [
AuthClient.authChangeEventInfoKey: event,
AuthClient.authChangeSessionInfoKey: session as Any,
]
)

emitter.emit((event, session), to: token)
}
)
}()
}
Loading

0 comments on commit 831a1f7

Please sign in to comment.