@@ -5,6 +5,7 @@ import android.content.Context
55import android.content.Intent
66import android.util.Log
77import com.clerk.api.Clerk
8+ import com.clerk.api.network.serialization.ClerkResult
89import com.facebook.react.bridge.ActivityEventListener
910import com.facebook.react.bridge.Promise
1011import com.facebook.react.bridge.ReactApplicationContext
@@ -14,7 +15,6 @@ import com.facebook.react.bridge.WritableNativeMap
1415import kotlinx.coroutines.CoroutineScope
1516import kotlinx.coroutines.Dispatchers
1617import kotlinx.coroutines.TimeoutCancellationException
17- import kotlinx.coroutines.flow.MutableStateFlow
1818import kotlinx.coroutines.flow.first
1919import kotlinx.coroutines.launch
2020import kotlinx.coroutines.withTimeout
@@ -68,114 +68,69 @@ class ClerkExpoModule(reactContext: ReactApplicationContext) :
6868 try {
6969 publishableKey = pubKey
7070
71- // If the JS SDK has a bearer token, write it to the native SDK's
72- // SharedPreferences so both SDKs share the same Clerk API client.
73- if (! bearerToken.isNullOrEmpty()) {
74- reactApplicationContext.getSharedPreferences(" clerk_preferences" , Context .MODE_PRIVATE )
75- .edit()
76- .putString(" DEVICE_TOKEN" , bearerToken)
77- .apply ()
78- }
71+ if (! Clerk .isInitialized.value) {
72+ // First-time initialization — write the bearer token to SharedPreferences
73+ // before initializing so the SDK boots with the correct client.
74+ if (! bearerToken.isNullOrEmpty()) {
75+ reactApplicationContext.getSharedPreferences(" clerk_preferences" , Context .MODE_PRIVATE )
76+ .edit()
77+ .putString(" DEVICE_TOKEN" , bearerToken)
78+ .apply ()
79+ }
7980
80- if (Clerk .isInitialized.value) {
81- // Already initialized — force a client refresh so the SDK
82- // picks up the new device token from SharedPreferences.
83- forceClientRefresh()
81+ Clerk .initialize(reactApplicationContext, pubKey)
8482
85- // Wait for session to appear with the new token (up to 5s)
83+ // Wait for initialization to complete with timeout
8684 try {
87- withTimeout(5_000L ) {
88- Clerk .sessionFlow .first { it != null }
85+ withTimeout(10_000L ) {
86+ Clerk .isInitialized .first { it }
8987 }
90- } catch (_: TimeoutCancellationException ) {
91- debugLog(TAG , " configure - session did not appear after force refresh" )
88+ } catch (e: TimeoutCancellationException ) {
89+ val initError = Clerk .initializationError.value
90+ val message = if (initError != null ) {
91+ " Clerk initialization timed out: ${initError.message} "
92+ } else {
93+ " Clerk initialization timed out after 10 seconds"
94+ }
95+ promise.reject(" E_TIMEOUT" , message)
96+ return @launch
9297 }
9398
94- promise.resolve(null )
99+ // Check for initialization errors
100+ val error = Clerk .initializationError.value
101+ if (error != null ) {
102+ promise.reject(" E_INIT_FAILED" , " Failed to initialize Clerk SDK: ${error.message} " )
103+ } else {
104+ promise.resolve(null )
105+ }
95106 return @launch
96107 }
97108
98- // First-time initialization
99- Clerk .initialize(reactApplicationContext, pubKey)
100-
101- // Wait for initialization to complete with timeout
102- try {
103- withTimeout(10_000L ) {
104- Clerk .isInitialized.first { it }
109+ // Already initialized — use the public SDK API to update
110+ // the device token and trigger a client/environment refresh.
111+ if (! bearerToken.isNullOrEmpty()) {
112+ val result = Clerk .updateDeviceToken(bearerToken)
113+ if (result is ClerkResult .Failure ) {
114+ debugLog(TAG , " configure - updateDeviceToken failed: ${result.error} " )
105115 }
106- } catch (e: TimeoutCancellationException ) {
107- val initError = Clerk .initializationError.value
108- val message = if (initError != null ) {
109- " Clerk initialization timed out: ${initError.message} "
110- } else {
111- " Clerk initialization timed out after 10 seconds"
116+
117+ // Wait for session to appear with the new token (up to 5s)
118+ try {
119+ withTimeout(5_000L ) {
120+ Clerk .sessionFlow.first { it != null }
121+ }
122+ } catch (_: TimeoutCancellationException ) {
123+ debugLog(TAG , " configure - session did not appear after token update" )
112124 }
113- promise.reject(" E_TIMEOUT" , message)
114- return @launch
115125 }
116126
117- // Check for initialization errors
118- val error = Clerk .initializationError.value
119- if (error != null ) {
120- promise.reject(" E_INIT_FAILED" , " Failed to initialize Clerk SDK: ${error.message} " )
121- } else {
122- promise.resolve(null )
123- }
127+ promise.resolve(null )
124128 } catch (e: Exception ) {
125129 promise.reject(" E_INIT_FAILED" , " Failed to initialize Clerk SDK: ${e.message} " , e)
126130 }
127131 }
128132 }
129133
130- /* *
131- * Forces the Clerk SDK to re-fetch client/environment data from the API.
132- *
133- * This is needed when a new device token has been written to SharedPreferences
134- * but the SDK was already initialized (so Clerk.initialize() is a no-op).
135- *
136- * Uses reflection to find the ConfigurationManager instance by type (field name
137- * may vary across SDK versions), then sets _isInitialized to false so
138- * reinitialize() proceeds with a fresh client/environment fetch.
139- */
140- private fun forceClientRefresh () {
141- try {
142- // Find the ConfigurationManager field by type since the name may differ
143- val clerkClass = Clerk ::class .java
144- var configManager: Any? = null
145-
146- for (field in clerkClass.declaredFields) {
147- field.isAccessible = true
148- val fieldValue = field.get(Clerk )
149- if (fieldValue != null && fieldValue.javaClass.name.contains(" ConfigurationManager" )) {
150- configManager = fieldValue
151- break
152- }
153- }
154-
155- if (configManager == null ) {
156- debugLog(TAG , " forceClientRefresh - ConfigurationManager not found" )
157- return
158- }
159-
160- // Find _isInitialized field (MutableStateFlow<Boolean>) in ConfigurationManager
161- // and set it to false so reinitialize() will proceed
162- for (field in configManager.javaClass.declaredFields) {
163- field.isAccessible = true
164- val fieldValue = field.get(configManager)
165- if (fieldValue is MutableStateFlow <* > && fieldValue.value is Boolean && fieldValue.value == true ) {
166- @Suppress(" UNCHECKED_CAST" )
167- (fieldValue as MutableStateFlow <Boolean >).value = false
168- Clerk .reinitialize()
169- return
170- }
171- }
172-
173- debugLog(TAG , " forceClientRefresh - _isInitialized flow not found" )
174- } catch (e: Exception ) {
175- debugLog(TAG , " forceClientRefresh failed: ${e.message} " )
176- }
177- }
178-
179134 // MARK: - presentAuth
180135
181136 @ReactMethod
0 commit comments