@@ -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
@@ -67,41 +68,70 @@ class ClerkExpoModule(reactContext: ReactApplicationContext) :
6768 try {
6869 publishableKey = pubKey
6970
70- // If the JS SDK has a bearer token, write it to the native SDK's
71- // SharedPreferences so both SDKs share the same Clerk API client.
72- if (! bearerToken.isNullOrEmpty()) {
73- reactApplicationContext.getSharedPreferences(" clerk_preferences" , Context .MODE_PRIVATE )
74- .edit()
75- .putString(" DEVICE_TOKEN" , bearerToken)
76- .apply ()
77- debugLog(TAG , " configure - wrote JS bearer token to native SharedPreferences" )
78- }
79-
80- Clerk .initialize(reactApplicationContext, pubKey)
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+ }
8180
82- // Wait for initialization to complete with timeout
83- try {
84- withTimeout(10_000L ) {
85- Clerk .isInitialized.first { it }
81+ Clerk .initialize(reactApplicationContext, pubKey)
82+
83+ // Wait for initialization to complete with timeout
84+ try {
85+ withTimeout(10_000L ) {
86+ Clerk .isInitialized.first { it }
87+ }
88+ // If a bearer token was provided, wait for the session to hydrate
89+ // so callers that immediately call getSession() see the session.
90+ if (! bearerToken.isNullOrEmpty()) {
91+ withTimeout(5_000L ) {
92+ Clerk .sessionFlow.first { it != null }
93+ }
94+ }
95+ } catch (e: TimeoutCancellationException ) {
96+ val initError = Clerk .initializationError.value
97+ val message = if (initError != null ) {
98+ " Clerk initialization timed out: ${initError.message} "
99+ } else {
100+ " Clerk initialization timed out after 10 seconds"
101+ }
102+ promise.reject(" E_TIMEOUT" , message)
103+ return @launch
86104 }
87- } catch (e: TimeoutCancellationException ) {
88- val initError = Clerk .initializationError.value
89- val message = if (initError != null ) {
90- " Clerk initialization timed out: ${initError.message} "
105+
106+ // Check for initialization errors
107+ val error = Clerk .initializationError.value
108+ if (error != null ) {
109+ promise.reject(" E_INIT_FAILED" , " Failed to initialize Clerk SDK: ${error.message} " )
91110 } else {
92- " Clerk initialization timed out after 10 seconds "
111+ promise.resolve( null )
93112 }
94- promise.reject(" E_TIMEOUT" , message)
95113 return @launch
96114 }
97115
98- // Check for initialization errors
99- val error = Clerk .initializationError.value
100- if (error != null ) {
101- promise.reject(" E_INIT_FAILED" , " Failed to initialize Clerk SDK: ${error.message} " )
102- } else {
103- promise.resolve(null )
116+ // Already initialized — use the public SDK API to update
117+ // the device token and trigger a client/environment refresh.
118+ if (! bearerToken.isNullOrEmpty()) {
119+ val result = Clerk .updateDeviceToken(bearerToken)
120+ if (result is ClerkResult .Failure ) {
121+ debugLog(TAG , " configure - updateDeviceToken failed: ${result.error} " )
122+ }
123+
124+ // Wait for session to appear with the new token (up to 5s)
125+ try {
126+ withTimeout(5_000L ) {
127+ Clerk .sessionFlow.first { it != null }
128+ }
129+ } catch (_: TimeoutCancellationException ) {
130+ debugLog(TAG , " configure - session did not appear after token update" )
131+ }
104132 }
133+
134+ promise.resolve(null )
105135 } catch (e: Exception ) {
106136 promise.reject(" E_INIT_FAILED" , " Failed to initialize Clerk SDK: ${e.message} " , e)
107137 }
@@ -174,15 +204,15 @@ class ClerkExpoModule(reactContext: ReactApplicationContext) :
174204 @ReactMethod
175205 override fun getSession (promise : Promise ) {
176206 if (! Clerk .isInitialized.value) {
177- promise.reject(" E_NOT_INITIALIZED" , " Clerk SDK is not initialized. Call configure() first." )
207+ // Return null when not initialized (matches iOS behavior)
208+ // so callers can proceed to call configure() with a bearer token.
209+ promise.resolve(null )
178210 return
179211 }
180212
181213 val session = Clerk .session
182214 val user = Clerk .user
183215
184- debugLog(TAG , " getSession - hasSession: ${session != null } , hasUser: ${user != null } " )
185-
186216 val result = WritableNativeMap ()
187217
188218 session?.let {
@@ -217,7 +247,6 @@ class ClerkExpoModule(reactContext: ReactApplicationContext) :
217247 try {
218248 val prefs = reactApplicationContext.getSharedPreferences(" clerk_preferences" , Context .MODE_PRIVATE )
219249 val deviceToken = prefs.getString(" DEVICE_TOKEN" , null )
220- debugLog(TAG , " getClientToken - deviceToken: ${if (deviceToken != null ) " found" else " null" } " )
221250 promise.resolve(deviceToken)
222251 } catch (e: Exception ) {
223252 debugLog(TAG , " getClientToken failed: ${e.message} " )
@@ -230,7 +259,13 @@ class ClerkExpoModule(reactContext: ReactApplicationContext) :
230259 @ReactMethod
231260 override fun signOut (promise : Promise ) {
232261 if (! Clerk .isInitialized.value) {
233- promise.reject(" E_NOT_INITIALIZED" , " Clerk SDK is not initialized. Call configure() first." )
262+ // Clear DEVICE_TOKEN from SharedPreferences even when not initialized,
263+ // so the next Clerk.initialize() doesn't boot with a stale client token.
264+ reactApplicationContext.getSharedPreferences(" clerk_preferences" , Context .MODE_PRIVATE )
265+ .edit()
266+ .remove(" DEVICE_TOKEN" )
267+ .apply ()
268+ promise.resolve(null )
234269 return
235270 }
236271
@@ -258,17 +293,13 @@ class ClerkExpoModule(reactContext: ReactApplicationContext) :
258293 }
259294
260295 private fun handleAuthResult (resultCode : Int , data : Intent ? ) {
261- debugLog(TAG , " handleAuthResult - resultCode: $resultCode " )
262-
263296 val promise = pendingAuthPromise ? : return
264297 pendingAuthPromise = null
265298
266299 if (resultCode == Activity .RESULT_OK ) {
267300 val session = Clerk .session
268301 val user = Clerk .user
269302
270- debugLog(TAG , " handleAuthResult - hasSession: ${session != null } , hasUser: ${user != null } " )
271-
272303 val result = WritableNativeMap ()
273304
274305 // Top-level sessionId for JS SDK compatibility (matches iOS response format)
@@ -296,7 +327,6 @@ class ClerkExpoModule(reactContext: ReactApplicationContext) :
296327
297328 promise.resolve(result)
298329 } else {
299- debugLog(TAG , " handleAuthResult - user cancelled" )
300330 val result = WritableNativeMap ()
301331 result.putBoolean(" cancelled" , true )
302332 promise.resolve(result)
0 commit comments