Skip to content

Commit

Permalink
feat(auth): add convenience deep link handling methods (#397)
Browse files Browse the repository at this point in the history
* feat(auth): automatically load session from deep link

* feat: add handle method

* add handle url method to SupabaseClient
  • Loading branch information
grdsdev authored May 27, 2024
1 parent 77fb7e6 commit db7a094
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 15 deletions.
15 changes: 14 additions & 1 deletion Examples/Examples/Contants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,18 @@
import Foundation

enum Constants {
static let redirectToURL = URL(string: "com.supabase.swift-examples://")!
static let redirectToURL = URL(scheme: "com.supabase.swift-examples")!
}

extension URL {
init?(scheme: String) {
var components = URLComponents()
components.scheme = scheme

guard let url = components.url else {
return nil
}

self = url
}
}
33 changes: 33 additions & 0 deletions Examples/Examples/ExamplesApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,41 @@ import GoogleSignIn
import Supabase
import SwiftUI

class AppDelegate: UIResponder, UIApplicationDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
if let url = launchOptions?[.url] as? URL {
supabase.handle(url)
}
return true
}

func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
supabase.handle(url)
return true
}

func application(_: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options _: UIScene.ConnectionOptions) -> UISceneConfiguration {
let configuration = UISceneConfiguration(name: nil, sessionRole: connectingSceneSession.role)
configuration.delegateClass = SceneDelegate.self
return configuration
}
}

class SceneDelegate: UIResponder, UISceneDelegate {
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
guard let url = URLContexts.first?.url else { return }

supabase.handle(url)
}
}

@main
struct ExamplesApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

var body: some Scene {
WindowGroup {
RootView()
Expand Down
18 changes: 5 additions & 13 deletions Examples/Examples/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,20 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>GIDClientID</key>
<string>YOUR_IOS_CLIENT_ID</string>
<key>GIDServerClientID</key>
<string>YOUR_SERVER_CLIENT_ID</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLSchemes</key>
<array>
<string>com.supabase.swift-examples</string>
</array>
</dict>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLSchemes</key>
<array>
<string>YOUR_DOT_REVERSED_IOS_CLIENT_ID</string>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
</array>
</dict>
</array>
<key>GIDClientID</key>
<string>YOUR_IOS_CLIENT_ID</string>
<key>GIDServerClientID</key>
<string>YOUR_SERVER_CLIENT_ID</string>
</dict>
</plist>
63 changes: 62 additions & 1 deletion Sources/Auth/AuthClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public final class AuthClient: Sendable {
private var date: @Sendable () -> Date { Current.date }
private var sessionManager: SessionManager { Current.sessionManager }
private var eventEmitter: AuthStateChangeEventEmitter { Current.eventEmitter }
private var logger: (any SupabaseLogger)? { Current.logger }
private var logger: (any SupabaseLogger)? { Current.configuration.logger }
private var storage: any AuthLocalStorage { Current.configuration.localStorage }

/// Returns the session, refreshing it if necessary.
Expand Down Expand Up @@ -596,6 +596,67 @@ public final class AuthClient: Sendable {
}
#endif

/// Handles an incoming URL received by the app.
///
/// ## Usage example:
///
/// ### UIKit app lifecycle
///
/// In your `AppDelegate.swift`:
///
/// ```swift
/// public func application(
/// _ application: UIApplication,
/// didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
/// ) -> Bool {
/// if let url = launchOptions?[.url] as? URL {
/// supabase.auth.handle(url)
/// }
///
/// return true
/// }
///
/// func application(
/// _ app: UIApplication,
/// open url: URL,
/// options: [UIApplication.OpenURLOptionsKey: Any]
/// ) -> Bool {
/// supabase.auth.handle(url)
/// return true
/// }
/// ```
///
/// ### UIKit app lifecycle with scenes
///
/// In your `SceneDelegate.swift`:
///
/// ```swift
/// func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
/// guard let url = URLContexts.first?.url else { return }
/// supabase.auth.handle(url)
/// }
/// ```
///
/// ### SwiftUI app lifecycle
///
/// In your `AppDelegate.swift`:
///
/// ```swift
/// SomeView()
/// .onOpenURL { url in
/// supabase.auth.handle(url)
/// }
/// ```
public func handle(_ url: URL) {
Task {
do {
try await session(from: url)
} catch {
logger?.error("Failure loading session from url '\(url)' error: \(error)")
}
}
}

/// Gets the session data from a OAuth2 callback URL.
@discardableResult
public func session(from url: URL) async throws -> Session {
Expand Down
55 changes: 55 additions & 0 deletions Sources/Supabase/SupabaseClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,61 @@ public final class SupabaseClient: Sendable {
await realtimeV2.removeAllChannels()
}

/// Handles an incoming URL received by the app.
///
/// ## Usage example:
///
/// ### UIKit app lifecycle
///
/// In your `AppDelegate.swift`:
///
/// ```swift
/// public func application(
/// _ application: UIApplication,
/// didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
/// ) -> Bool {
/// if let url = launchOptions?[.url] as? URL {
/// supabase.handle(url)
/// }
///
/// return true
/// }
///
/// func application(
/// _ app: UIApplication,
/// open url: URL,
/// options: [UIApplication.OpenURLOptionsKey: Any]
/// ) -> Bool {
/// supabase.handle(url)
/// return true
/// }
/// ```
///
/// ### UIKit app lifecycle with scenes
///
/// In your `SceneDelegate.swift`:
///
/// ```swift
/// func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
/// guard let url = URLContexts.first?.url else { return }
/// supabase.handle(url)
/// }
/// ```
///
/// ### SwiftUI app lifecycle
///
/// In your `AppDelegate.swift`:
///
/// ```swift
/// SomeView()
/// .onOpenURL { url in
/// supabase.handle(url)
/// }
/// ```
public func handle(_ url: URL) {
auth.handle(url)
}

deinit {
mutableState.listenForAuthEventsTask?.cancel()
}
Expand Down

0 comments on commit db7a094

Please sign in to comment.