@@ -136,6 +136,7 @@ import { assertNoLegacyProp } from '../utils/assertNoLegacyProp';
136136import { CLERK_ENVIRONMENT_STORAGE_ENTRY , SafeLocalStorage } from '../utils/localStorage' ;
137137import { memoizeListenerCallback } from '../utils/memoizeStateListenerCallback' ;
138138import { RedirectUrls } from '../utils/redirectUrls' ;
139+ import { withRetry } from '../utils/retry' ;
139140import { AuthCookieService } from './auth/AuthCookieService' ;
140141import { CaptchaHeartbeat } from './auth/CaptchaHeartbeat' ;
141142import { CLERK_SATELLITE_URL , CLERK_SUFFIXED_COOKIES , CLERK_SYNCED , ERROR_CODES } from './constants' ;
@@ -2570,112 +2571,109 @@ export class Clerk implements ClerkInterface {
25702571
25712572 let initializationDegradedCounter = 0 ;
25722573
2573- let retries = 0 ;
2574- while ( retries < 2 ) {
2575- retries ++ ;
2574+ const initializeClerk = async ( ) : Promise < void > => {
2575+ const initEnvironmentPromise = Environment . getInstance ( )
2576+ . fetch ( { touch : shouldTouchEnv } )
2577+ . then ( res => this . updateEnvironment ( res ) )
2578+ . catch ( ( ) => {
2579+ ++ initializationDegradedCounter ;
2580+ const environmentSnapshot = SafeLocalStorage . getItem < EnvironmentJSONSnapshot | null > (
2581+ CLERK_ENVIRONMENT_STORAGE_ENTRY ,
2582+ null ,
2583+ ) ;
25762584
2577- try {
2578- const initEnvironmentPromise = Environment . getInstance ( )
2579- . fetch ( { touch : shouldTouchEnv } )
2580- . then ( res => this . updateEnvironment ( res ) )
2581- . catch ( ( ) => {
2582- ++ initializationDegradedCounter ;
2583- const environmentSnapshot = SafeLocalStorage . getItem < EnvironmentJSONSnapshot | null > (
2584- CLERK_ENVIRONMENT_STORAGE_ENTRY ,
2585- null ,
2586- ) ;
2585+ if ( environmentSnapshot ) {
2586+ this . updateEnvironment ( new Environment ( environmentSnapshot ) ) ;
2587+ }
2588+ } ) ;
25872589
2588- if ( environmentSnapshot ) {
2589- this . updateEnvironment ( new Environment ( environmentSnapshot ) ) ;
2590+ const initClient = async ( ) => {
2591+ return Client . getOrCreateInstance ( )
2592+ . fetch ( )
2593+ . then ( res => this . updateClient ( res ) )
2594+ . catch ( async e => {
2595+ /**
2596+ * Only handle non 4xx errors, like 5xx errors and network errors.
2597+ */
2598+ if ( is4xxError ( e ) ) {
2599+ throw e ;
25902600 }
2591- } ) ;
25922601
2593- const initClient = async ( ) => {
2594- return Client . getOrCreateInstance ( )
2595- . fetch ( )
2596- . then ( res => this . updateClient ( res ) )
2597- . catch ( async e => {
2598- /**
2599- * Only handle non 4xx errors, like 5xx errors and network errors.
2600- */
2601- if ( is4xxError ( e ) ) {
2602- // bubble it up
2603- throw e ;
2604- }
2605-
2606- ++ initializationDegradedCounter ;
2607-
2608- const jwtInCookie = this . #authService?. getSessionCookie ( ) ;
2609- const localClient = createClientFromJwt ( jwtInCookie ) ;
2610-
2611- this . updateClient ( localClient ) ;
2612-
2613- /**
2614- * In most scenarios we want the poller to stop while we are fetching a fresh token during an outage.
2615- * We want to avoid having the below `getToken()` retrying at the same time as the poller.
2616- */
2617- this . #authService?. stopPollingForToken ( ) ;
2618-
2619- // Attempt to grab a fresh token
2620- await this . session
2621- ?. getToken ( { skipCache : true } )
2622- // If the token fetch fails, let Clerk be marked as loaded and leave it up to the poller.
2623- . catch ( ( ) => null )
2624- . finally ( ( ) => {
2625- this . #authService?. startPollingForToken ( ) ;
2626- } ) ;
2627-
2628- // Allows for Clerk to be marked as loaded with the client and session created from the JWT.
2629- return null ;
2630- } ) ;
2631- } ;
2632-
2633- const initComponents = ( ) => {
2634- if ( Clerk . mountComponentRenderer && ! this . #componentControls) {
2635- this . #componentControls = Clerk . mountComponentRenderer (
2636- this ,
2637- this . environment as Environment ,
2638- this . #options,
2639- ) ;
2640- }
2641- } ;
2602+ ++ initializationDegradedCounter ;
26422603
2643- const [ , clientResult ] = await allSettled ( [ initEnvironmentPromise , initClient ( ) ] ) ;
2604+ const jwtInCookie = this . #authService?. getSessionCookie ( ) ;
2605+ const localClient = createClientFromJwt ( jwtInCookie ) ;
26442606
2645- if ( clientResult . status === 'rejected' ) {
2646- const e = clientResult . reason ;
2607+ this . updateClient ( localClient ) ;
26472608
2648- if ( isError ( e , 'requires_captcha' ) ) {
2649- initComponents ( ) ;
2650- await initClient ( ) ;
2651- } else {
2652- throw e ;
2653- }
2654- }
2609+ /**
2610+ * In most scenarios we want the poller to stop while we are fetching a fresh token during an outage.
2611+ * We want to avoid having the below `getToken()` retrying at the same time as the poller.
2612+ */
2613+ this . #authService?. stopPollingForToken ( ) ;
26552614
2656- this . #authService?. setClientUatCookieForDevelopmentInstances ( ) ;
2615+ await this . session
2616+ ?. getToken ( { skipCache : true } )
2617+ . catch ( ( ) => null )
2618+ . finally ( ( ) => {
2619+ this . #authService?. startPollingForToken ( ) ;
2620+ } ) ;
26572621
2658- if ( await this . #redirectFAPIInitiatedFlow( ) ) {
2659- return ;
2622+ return null ;
2623+ } ) ;
2624+ } ;
2625+
2626+ const initComponents = ( ) => {
2627+ if ( Clerk . mountComponentRenderer && ! this . #componentControls) {
2628+ this . #componentControls = Clerk . mountComponentRenderer ( this , this . environment as Environment , this . #options) ;
26602629 }
2630+ } ;
26612631
2662- initComponents ( ) ;
2632+ const [ , clientResult ] = await allSettled ( [ initEnvironmentPromise , initClient ( ) ] ) ;
26632633
2664- break ;
2665- } catch ( err ) {
2666- if ( isError ( err , 'dev_browser_unauthenticated' ) ) {
2667- await this . #authService. handleUnauthenticatedDevBrowser ( ) ;
2668- } else if ( ! isValidBrowserOnline ( ) ) {
2669- console . warn ( err ) ;
2670- return ;
2634+ if ( clientResult . status === 'rejected' ) {
2635+ const e = clientResult . reason ;
2636+
2637+ if ( isError ( e , 'requires_captcha' ) ) {
2638+ initComponents ( ) ;
2639+ await initClient ( ) ;
26712640 } else {
2672- throw err ;
2641+ throw e ;
26732642 }
26742643 }
26752644
2676- if ( retries >= 2 ) {
2677- clerkErrorInitFailed ( ) ;
2645+ this . #authService?. setClientUatCookieForDevelopmentInstances ( ) ;
2646+
2647+ if ( await this . #redirectFAPIInitiatedFlow( ) ) {
2648+ return ;
26782649 }
2650+
2651+ initComponents ( ) ;
2652+ } ;
2653+
2654+ try {
2655+ await withRetry ( initializeClerk , {
2656+ jitter : true ,
2657+ maxAttempts : 2 ,
2658+ shouldRetry : async error => {
2659+ if ( ! isValidBrowserOnline ( ) ) {
2660+ console . warn ( error ) ;
2661+ return false ;
2662+ }
2663+
2664+ const isDevBrowserUnauthenticated = isError ( error as any , 'dev_browser_unauthenticated' ) ;
2665+ const isNetworkError = isClerkRuntimeError ( error ) && error . code === 'network_error' ;
2666+
2667+ if ( isDevBrowserUnauthenticated && this . #authService) {
2668+ await this . #authService. handleUnauthenticatedDevBrowser ( ) ;
2669+ return true ;
2670+ }
2671+
2672+ return isNetworkError ;
2673+ } ,
2674+ } ) ;
2675+ } catch ( err ) {
2676+ clerkErrorInitFailed ( err ) ;
26792677 }
26802678
26812679 this . #captchaHeartbeat = new CaptchaHeartbeat ( this ) ;
0 commit comments