@@ -48,6 +48,8 @@ final class ClerkNativeBridge {
4848
4949 private var clientObservationGeneration = 0
5050 private var lastObservedClientState : ClientStateSnapshot ?
51+ private var configurationDepth = 0
52+ private var jsOriginatedClientSyncDepth = 0
5153
5254 private init ( ) { }
5355
@@ -74,6 +76,12 @@ final class ClerkNativeBridge {
7476
7577 @MainActor
7678 func configure( publishableKey: String , bearerToken: String ? = nil ) async throws {
79+ configurationDepth += 1
80+ defer {
81+ lastObservedClientState = Self . clerkConfigured ? Self . clientStateSnapshot ( ) : nil
82+ configurationDepth = max ( 0 , configurationDepth - 1 )
83+ }
84+
7785 loadThemes ( )
7886
7987 if Self . shouldReconfigure ( for: publishableKey) {
@@ -84,16 +92,21 @@ final class ClerkNativeBridge {
8492
8593 let shouldWaitForClient = try await Self . syncTokenState ( bearerToken: bearerToken)
8694 await Self . waitForLoadedClientIfNeeded ( shouldWaitForClient)
87- Self . emitClientChangedIfReceivedToken ( bearerToken)
8895 Self . postConfiguredNotification ( )
8996 return
9097 }
9198
9299 if Self . clerkConfigured {
93100 startClientObserver ( )
94- let shouldWaitForClient = try await Self . syncTokenState ( bearerToken: bearerToken)
95- await Self . waitForLoadedClientIfNeeded ( shouldWaitForClient)
96- Self . emitClientChangedIfReceivedToken ( bearerToken)
101+ let didUpdateDeviceToken = try await Self . syncTokenState ( bearerToken: bearerToken)
102+ if didUpdateDeviceToken {
103+ await Self . waitForLoadedClient ( )
104+ } else if let token = bearerToken? . trimmingCharacters ( in: . whitespacesAndNewlines) , !token. isEmpty {
105+ // A remounted JS runtime can have the same token while native client
106+ // state is stale, so preserve one refresh in that case.
107+ _ = try await Clerk . shared. refreshClient ( )
108+ await Self . waitForLoadedClient ( )
109+ }
97110 return
98111 }
99112
@@ -104,16 +117,9 @@ final class ClerkNativeBridge {
104117
105118 let shouldWaitForClient = try await Self . syncTokenState ( bearerToken: bearerToken)
106119 await Self . waitForLoadedClientIfNeeded ( shouldWaitForClient)
107- Self . emitClientChangedIfReceivedToken ( bearerToken)
108120 Self . postConfiguredNotification ( )
109121 }
110122
111- @MainActor
112- private static func emitClientChangedIfReceivedToken( _ bearerToken: String ? ) {
113- guard let token = bearerToken, !token. trimmingCharacters ( in: . whitespacesAndNewlines) . isEmpty else { return }
114- Self . emitClientChanged ( Self . clientChangedPayload ( changes: . init( client: false , deviceToken: true ) ) )
115- }
116-
117123 @MainActor
118124 private func startClientObserver( reset: Bool = false ) {
119125 guard reset || clientObservationGeneration == 0 else {
@@ -139,13 +145,15 @@ final class ClerkNativeBridge {
139145 let newClientState = Self . clientStateSnapshot ( )
140146 if let previousClientState = self . lastObservedClientState, newClientState != previousClientState {
141147 self . lastObservedClientState = newClientState
142- let payload = Self . clientChangedPayload (
143- changes: . init(
144- client: newClientState. client != previousClientState. client,
145- deviceToken: newClientState. deviceToken != previousClientState. deviceToken
148+ if self . configurationDepth == 0 , self . jsOriginatedClientSyncDepth == 0 {
149+ let payload = Self . clientChangedPayload (
150+ changes: . init(
151+ client: newClientState. client != previousClientState. client,
152+ deviceToken: newClientState. deviceToken != previousClientState. deviceToken
153+ )
146154 )
147- )
148- Self . emitClientChanged ( payload )
155+ Self . emitClientChanged ( payload )
156+ }
149157 }
150158
151159 self . observeClient ( generation: generation)
@@ -180,8 +188,14 @@ final class ClerkNativeBridge {
180188
181189 @MainActor
182190 private static func syncTokenState( bearerToken: String ? ) async throws -> Bool {
183- guard let token = bearerToken, !token. isEmpty else {
184- return Clerk . shared. deviceToken != nil
191+ await waitForLoadedClient ( )
192+
193+ guard let token = bearerToken? . trimmingCharacters ( in: . whitespacesAndNewlines) , !token. isEmpty
194+ else {
195+ return false
196+ }
197+ guard Clerk . shared. deviceToken != token || Clerk . shared. client == nil else {
198+ return false
185199 }
186200 _ = try await Clerk . shared. updateDeviceToken ( token)
187201 return true
@@ -283,15 +297,38 @@ final class ClerkNativeBridge {
283297 guard Self . clerkConfigured else { return }
284298
285299 let previousClientState = Self . clientStateSnapshot ( )
300+ var completedSuccessfully = false
301+ jsOriginatedClientSyncDepth += 1
302+ defer {
303+ let finalClientState = Self . clientStateSnapshot ( )
304+ lastObservedClientState = finalClientState
305+ jsOriginatedClientSyncDepth = max ( 0 , jsOriginatedClientSyncDepth - 1 )
306+
307+ if !completedSuccessfully, finalClientState != previousClientState {
308+ Self . emitClientChanged (
309+ Self . clientChangedPayload (
310+ changes: . init(
311+ client: finalClientState. client != previousClientState. client,
312+ deviceToken: finalClientState. deviceToken != previousClientState. deviceToken
313+ )
314+ )
315+ )
316+ }
317+ }
318+
319+ var refreshedClientWhileUpdatingToken = false
286320
287- if didChangeDeviceToken, let token = deviceToken? . trimmingCharacters ( in: . whitespacesAndNewlines) , !token. isEmpty {
321+ if didChangeDeviceToken,
322+ let token = deviceToken? . trimmingCharacters ( in: . whitespacesAndNewlines) , !token. isEmpty
323+ {
288324 if Clerk . shared. deviceToken != token {
289325 _ = try await Clerk . shared. updateDeviceToken ( token)
290326 await Self . waitForLoadedClient ( )
327+ refreshedClientWhileUpdatingToken = true
291328 }
292329 }
293330
294- if didChangeClient || didChangeDeviceToken {
331+ if !refreshedClientWhileUpdatingToken , didChangeClient || didChangeDeviceToken {
295332 _ = try await Clerk . shared. refreshClient ( )
296333 await Self . waitForLoadedClient ( )
297334 }
@@ -307,6 +344,7 @@ final class ClerkNativeBridge {
307344 )
308345 )
309346 )
347+ completedSuccessfully = true
310348 }
311349
312350 private static func postConfiguredNotification( ) {
0 commit comments