@@ -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,124 +2571,109 @@ export class Clerk implements ClerkInterface {
25702571
25712572 let initializationDegradedCounter = 0 ;
25722573
2573- const wait = ( ms : number ) => new Promise < void > ( resolve => setTimeout ( resolve , ms ) ) ;
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+ ) ;
25742584
2575- const maxRetries = 2 ;
2576- let retries = 0 ;
2577- while ( retries < maxRetries ) {
2578- try {
2579- const initEnvironmentPromise = Environment . getInstance ( )
2580- . fetch ( { touch : shouldTouchEnv } )
2581- . then ( res => this . updateEnvironment ( res ) )
2582- . catch ( ( ) => {
2583- ++ initializationDegradedCounter ;
2584- const environmentSnapshot = SafeLocalStorage . getItem < EnvironmentJSONSnapshot | null > (
2585- CLERK_ENVIRONMENT_STORAGE_ENTRY ,
2586- null ,
2587- ) ;
2585+ if ( environmentSnapshot ) {
2586+ this . updateEnvironment ( new Environment ( environmentSnapshot ) ) ;
2587+ }
2588+ } ) ;
25882589
2589- if ( environmentSnapshot ) {
2590- 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 ;
25912600 }
2592- } ) ;
25932601
2594- const initClient = async ( ) => {
2595- return Client . getOrCreateInstance ( )
2596- . fetch ( )
2597- . then ( res => this . updateClient ( res ) )
2598- . catch ( async e => {
2599- /**
2600- * Only handle non 4xx errors, like 5xx errors and network errors.
2601- */
2602- if ( is4xxError ( e ) ) {
2603- // bubble it up
2604- throw e ;
2605- }
2606-
2607- ++ initializationDegradedCounter ;
2608-
2609- const jwtInCookie = this . #authService?. getSessionCookie ( ) ;
2610- const localClient = createClientFromJwt ( jwtInCookie ) ;
2611-
2612- this . updateClient ( localClient ) ;
2613-
2614- /**
2615- * In most scenarios we want the poller to stop while we are fetching a fresh token during an outage.
2616- * We want to avoid having the below `getToken()` retrying at the same time as the poller.
2617- */
2618- this . #authService?. stopPollingForToken ( ) ;
2619-
2620- // Attempt to grab a fresh token
2621- await this . session
2622- ?. getToken ( { skipCache : true } )
2623- // If the token fetch fails, let Clerk be marked as loaded and leave it up to the poller.
2624- . catch ( ( ) => null )
2625- . finally ( ( ) => {
2626- this . #authService?. startPollingForToken ( ) ;
2627- } ) ;
2628-
2629- // Allows for Clerk to be marked as loaded with the client and session created from the JWT.
2630- return null ;
2631- } ) ;
2632- } ;
2633-
2634- const initComponents = ( ) => {
2635- if ( Clerk . mountComponentRenderer && ! this . #componentControls) {
2636- this . #componentControls = Clerk . mountComponentRenderer (
2637- this ,
2638- this . environment as Environment ,
2639- this . #options,
2640- ) ;
2641- }
2642- } ;
2602+ ++ initializationDegradedCounter ;
26432603
2644- const [ , clientResult ] = await allSettled ( [ initEnvironmentPromise , initClient ( ) ] ) ;
2604+ const jwtInCookie = this . #authService?. getSessionCookie ( ) ;
2605+ const localClient = createClientFromJwt ( jwtInCookie ) ;
26452606
2646- if ( clientResult . status === 'rejected' ) {
2647- const e = clientResult . reason ;
2607+ this . updateClient ( localClient ) ;
26482608
2649- if ( isError ( e , 'requires_captcha' ) ) {
2650- initComponents ( ) ;
2651- await initClient ( ) ;
2652- } else {
2653- throw e ;
2654- }
2655- }
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 ( ) ;
2614+
2615+ await this . session
2616+ ?. getToken ( { skipCache : true } )
2617+ . catch ( ( ) => null )
2618+ . finally ( ( ) => {
2619+ this . #authService?. startPollingForToken ( ) ;
2620+ } ) ;
26562621
2657- this . #authService?. setClientUatCookieForDevelopmentInstances ( ) ;
2622+ return null ;
2623+ } ) ;
2624+ } ;
26582625
2659- if ( await this . #redirectFAPIInitiatedFlow( ) ) {
2660- return ;
2626+ const initComponents = ( ) => {
2627+ if ( Clerk . mountComponentRenderer && ! this . #componentControls) {
2628+ this . #componentControls = Clerk . mountComponentRenderer ( this , this . environment as Environment , this . #options) ;
26612629 }
2630+ } ;
26622631
2663- initComponents ( ) ;
2632+ const [ , clientResult ] = await allSettled ( [ initEnvironmentPromise , initClient ( ) ] ) ;
26642633
2665- break ;
2666- } catch ( err ) {
2667- if ( ! isValidBrowserOnline ( ) ) {
2668- console . warn ( err ) ;
2669- return ;
2634+ if ( clientResult . status === 'rejected' ) {
2635+ const e = clientResult . reason ;
2636+
2637+ if ( isError ( e , 'requires_captcha' ) ) {
2638+ initComponents ( ) ;
2639+ await initClient ( ) ;
2640+ } else {
2641+ throw e ;
26702642 }
2643+ }
26712644
2672- const isDevBrowserUnauthenticated = isError ( err , 'dev_browser_unauthenticated' ) ;
2673- const isNetworkError = isClerkRuntimeError ( err ) && err . code === 'network_error' ;
2645+ this . #authService?. setClientUatCookieForDevelopmentInstances ( ) ;
26742646
2675- if ( ! isDevBrowserUnauthenticated && ! isNetworkError ) {
2676- throw err ;
2677- }
2647+ if ( await this . #redirectFAPIInitiatedFlow ( ) ) {
2648+ return ;
2649+ }
26782650
2679- retries += 1 ;
2651+ initComponents ( ) ;
2652+ } ;
26802653
2681- if ( isDevBrowserUnauthenticated ) {
2682- await this . #authService. handleUnauthenticatedDevBrowser ( ) ;
2683- }
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+ }
26842663
2685- if ( retries >= maxRetries ) {
2686- clerkErrorInitFailed ( err ) ;
2687- }
2664+ const isDevBrowserUnauthenticated = isError ( error as any , 'dev_browser_unauthenticated' ) ;
2665+ const isNetworkError = isClerkRuntimeError ( error ) && error . code === 'network_error' ;
26882666
2689- await wait ( Math . pow ( 2 , retries ) * 1_000 ) ;
2690- }
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 ) ;
26912677 }
26922678
26932679 this . #captchaHeartbeat = new CaptchaHeartbeat ( this ) ;
0 commit comments