11// ClerkExpoModule - Native module for Clerk integration
22// This module provides the configure function, client sync, and native view bridges.
3- // SwiftUI Clerk views are created by the app target through ClerkNativeBridge because
4- // the Clerk SDK (SPM) isn't accessible from the CocoaPods-backed React Native pod.
3+ // SwiftUI Clerk views are created by ClerkNativeBridge through the Clerk iOS SPM dependency.
54
65import UIKit
76import React
87
9- /// Events emitted by the native view wrappers to their React Native host views.
10- public enum ClerkNativeViewEvent : String {
11- /// Emitted by the Expo host view when app-owned dismissible content leaves the window.
12- case dismissed
13- }
14-
15- // Global registry for the app-target native bridge (set by the app target at startup)
16- public var clerkNativeBridge : ClerkNativeBridgeProtocol ? {
17- didSet {
18- if clerkNativeBridge != nil {
19- emitClerkNativeBridgeReady ( )
20- }
21- }
22- }
23-
24- // Protocol that the app target implements to provide Clerk SDK operations and SwiftUI views.
25- public protocol ClerkNativeBridgeProtocol {
26- // Inline rendering — returns UIViewController to preserve SwiftUI lifecycle
27- func makeAuthViewController( mode: String , dismissible: Bool , onEvent: @escaping ( ClerkNativeViewEvent , [ String : Any ] ) -> Void ) -> UIViewController ?
28- func makeUserProfileViewController( dismissible: Bool , onEvent: @escaping ( ClerkNativeViewEvent , [ String : Any ] ) -> Void ) -> UIViewController ?
29- func makeUserButtonViewController( ) -> UIViewController ?
30-
31- // SDK operations
32- func configure( publishableKey: String , bearerToken: String ? ) async throws
33- func getClientToken( ) async -> String ?
34- func syncFromJsClientToken( _ clientToken: String ? , sourceId: String ? , shouldRefreshClient: Bool ) async throws
35- }
36-
37- public protocol ClerkNativeBridgeReadyObserver : AnyObject {
38- func clerkNativeBridgeDidBecomeReady( )
39- }
40-
41- private let clerkNativeBridgeReadyObservers = NSHashTable< AnyObject> . weakObjects( )
42-
43- public func addClerkNativeBridgeReadyObserver( _ observer: ClerkNativeBridgeReadyObserver ) {
44- clerkNativeBridgeReadyObservers. add ( observer)
45- }
46-
47- public func removeClerkNativeBridgeReadyObserver( _ observer: ClerkNativeBridgeReadyObserver ) {
48- clerkNativeBridgeReadyObservers. remove ( observer)
49- }
50-
51- public func emitClerkNativeBridgeReady( ) {
52- let notifyObservers = {
53- for observer in clerkNativeBridgeReadyObservers. allObjects {
54- ( observer as? ClerkNativeBridgeReadyObserver ) ? . clerkNativeBridgeDidBecomeReady ( )
55- }
56- }
57-
58- if Thread . isMainThread {
59- notifyObservers ( )
60- } else {
61- DispatchQueue . main. async ( execute: notifyObservers)
62- }
63- }
64-
658// MARK: - Module
669
6710@objc ( ClerkExpo)
@@ -73,6 +16,9 @@ class ClerkExpoModule: RCTEventEmitter {
7316 override init ( ) {
7417 super. init ( )
7518 ClerkExpoModule . sharedInstance = self
19+ ClerkNativeBridge . setClientChangedEmitter { body in
20+ Self . emitClientChanged ( body)
21+ }
7622 }
7723
7824 @objc override static func requiresMainQueueSetup( ) -> Bool {
@@ -120,14 +66,9 @@ class ClerkExpoModule: RCTEventEmitter {
12066 bearerToken: String ? ,
12167 resolve: @escaping RCTPromiseResolveBlock ,
12268 reject: @escaping RCTPromiseRejectBlock ) {
123- guard let bridge = clerkNativeBridge else {
124- reject ( " E_NOT_INITIALIZED " , " Clerk not initialized. Make sure ClerkNativeBridge is registered. " , nil )
125- return
126- }
127-
12869 Task {
12970 do {
130- try await bridge . configure ( publishableKey: publishableKey, bearerToken: bearerToken)
71+ try await ClerkNativeBridge . shared . configure ( publishableKey: publishableKey, bearerToken: bearerToken)
13172 resolve ( nil )
13273 } catch {
13374 reject ( " E_CONFIGURE_FAILED " , error. localizedDescription, error)
@@ -139,13 +80,8 @@ class ClerkExpoModule: RCTEventEmitter {
13980
14081 @objc func getClientToken( _ resolve: @escaping RCTPromiseResolveBlock ,
14182 reject: @escaping RCTPromiseRejectBlock ) {
142- guard let bridge = clerkNativeBridge else {
143- resolve ( nil )
144- return
145- }
146-
14783 Task {
148- let token = await bridge . getClientToken ( )
84+ let token = await ClerkNativeBridge . shared . getClientToken ( )
14985 resolve ( token)
15086 }
15187 }
@@ -157,18 +93,13 @@ class ClerkExpoModule: RCTEventEmitter {
15793 shouldRefreshClient: Any ? ,
15894 resolve: @escaping RCTPromiseResolveBlock ,
15995 reject: @escaping RCTPromiseRejectBlock ) {
160- guard let bridge = clerkNativeBridge else {
161- resolve ( nil )
162- return
163- }
164-
16596 let normalizedClientToken = clientToken as? String
16697 let normalizedSourceId = sourceId as? String
16798 let defaultShouldRefreshClient = normalizedClientToken? . trimmingCharacters ( in: . whitespacesAndNewlines) . isEmpty ?? true
16899 let normalizedShouldRefreshClient = ( shouldRefreshClient as? Bool ) ?? defaultShouldRefreshClient
169100 Task {
170101 do {
171- try await bridge . syncFromJsClientToken (
102+ try await ClerkNativeBridge . shared . syncFromJsClientToken (
172103 normalizedClientToken,
173104 sourceId: normalizedSourceId,
174105 shouldRefreshClient: normalizedShouldRefreshClient
@@ -181,8 +112,3 @@ class ClerkExpoModule: RCTEventEmitter {
181112 }
182113
183114}
184-
185- /// Requests that ClerkProvider reload the JS client from native client state.
186- public func emitClerkNativeClientChanged( _ body: [ String : Any ] ? = nil ) {
187- ClerkExpoModule . emitClientChanged ( body)
188- }
0 commit comments