Skip to content
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