Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(auth): add convenience deep link handling methods #397

Merged
merged 3 commits into from
May 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading