1- import { Auth0Client , type Auth0ClientOptions } from '@auth0/auth0-spa-js' ;
1+ import {
2+ Auth0Client ,
3+ type Auth0ClientOptions ,
4+ type LogoutOptions ,
5+ } from '@auth0/auth0-spa-js' ;
26import type { IAuth0Client , IUsersClient } from '../../../core/interfaces' ;
37import type { WebAuth0Options } from '../../../types/platform-specific' ;
48import { WebWebAuthProvider } from './WebWebAuthProvider' ;
@@ -8,102 +12,116 @@ import {
812 ManagementApiOrchestrator ,
913} from '../../../core/services' ;
1014import { HttpClient } from '../../../core/services/HttpClient' ;
15+ import { AuthError } from '../../../core/models' ;
16+
17+ let spaClient : Auth0Client | null = null ;
18+ let redirectHandled = false ;
1119
1220/**
13- * The concrete implementation of IAuth0Client for the Web platform (React Native Web).
21+ * Factory function to get a singleton instance of Auth0Client.
22+ * This ensures that the client is only created once and reused.
1423 *
15- * This class instantiates the `auth0-spa-js` client and all the necessary
16- * web-specific adapters that use it to fulfill their contracts. It also handles
17- * the initial redirect callback flow.
24+ * @param options - The Auth0ClientOptions to configure the client.
25+ * @returns An instance of Auth0Client.
1826 */
27+ const getSpaClient = ( options : Auth0ClientOptions ) : Auth0Client => {
28+ if ( spaClient ) {
29+ return spaClient ;
30+ }
31+ spaClient = new Auth0Client ( options ) ;
32+ return spaClient ;
33+ } ;
34+
1935export class WebAuth0Client implements IAuth0Client {
2036 readonly webAuth : WebWebAuthProvider ;
2137 readonly credentialsManager : WebCredentialsManager ;
2238 readonly auth : AuthenticationOrchestrator ;
2339
24- private readonly client : Auth0Client ;
2540 private readonly httpClient : HttpClient ;
41+ public readonly client : Auth0Client ;
42+
43+ private logoutInProgress = false ;
2644
2745 constructor ( options : WebAuth0Options ) {
2846 const baseUrl = `https://${ options . domain } ` ;
2947
30- // 1. Create the HttpClient.
3148 this . httpClient = new HttpClient ( {
3249 baseUrl : baseUrl ,
3350 timeout : options . timeout ,
3451 headers : options . headers ,
3552 } ) ;
3653
37- // 2. Instantiate the AuthenticationOrchestrator.
3854 this . auth = new AuthenticationOrchestrator ( {
3955 clientId : options . clientId ,
4056 httpClient : this . httpClient ,
4157 } ) ;
4258
43- const { clientId, domain, ...otherOptions } = options ;
4459 const clientOptions : Auth0ClientOptions = {
45- clientId : clientId ,
46- domain : domain ,
47- cacheLocation : otherOptions . cacheLocation ?? 'memory' ,
48- useRefreshTokens : otherOptions . useRefreshTokens ?? true ,
60+ domain : options . domain ,
61+ clientId : options . clientId ,
62+ cacheLocation : options . cacheLocation ?? 'memory' ,
63+ useRefreshTokens : options . useRefreshTokens ?? true ,
4964 authorizationParams : {
50- // A default redirect_uri is required by spa-js.
51- // This can be overridden in the `authorize` call.
5265 redirect_uri :
5366 typeof window !== 'undefined' ? window . location . origin : '' ,
67+ ...options ,
5468 } ,
55- ...otherOptions , // Pass through any other spa-js compatible options.
5669 } ;
5770
58- this . client = new Auth0Client ( clientOptions ) ;
71+ // Use the singleton factory to get the spa-js client instance.
72+ const client = getSpaClient ( clientOptions ) ;
73+ this . client = client ;
5974
60- // Automatically handle the redirect from Auth0 when the app loads.
61- // This is a fire-and-forget operation. The hooks layer will update the
62- // UI once the user state is resolved.
63- this . handleRedirect ( ) ;
75+ this . handleRedirect ( client ) ;
6476
65- // Instantiate our adapters with the configured spa-js client.
66- this . webAuth = new WebWebAuthProvider ( this . client ) ;
67- this . credentialsManager = new WebCredentialsManager ( this . client ) ;
77+ this . webAuth = new WebWebAuthProvider ( this ) ;
78+ this . credentialsManager = new WebCredentialsManager ( this ) ;
6879 }
6980
70- /**
71- * Creates a client for interacting with the Auth0 Management API's user endpoints.
72- *
73- * @param token An access token with the required permissions for the management operations.
74- * @returns An `IUsersClient` instance configured with the provided token.
75- */
7681 users ( token : string ) : IUsersClient {
77- // Re-use the same HttpClient, but the orchestrator will add its own auth header.
7882 return new ManagementApiOrchestrator ( {
7983 token : token ,
8084 httpClient : this . httpClient ,
8185 } ) ;
8286 }
8387
84- /**
85- * Private method to handle the redirect from Auth0 after a login attempt.
86- * This should only run once when the application loads.
87- */
88- private async handleRedirect ( ) : Promise < void > {
88+ public async logout ( options ?: LogoutOptions ) : Promise < void > {
89+ // If a logout process has already started, do nothing.
90+ if ( this . logoutInProgress ) {
91+ return ;
92+ }
93+ this . logoutInProgress = true ;
94+
95+ try {
96+ await this . client . logout ( options ) ;
97+ } catch ( e : any ) {
98+ // Reset the flag on error so a retry is possible.
99+ this . logoutInProgress = false ;
100+ throw new AuthError (
101+ e . error ?? 'LogoutFailed' ,
102+ e . error_description ?? e . message ,
103+ { json : e }
104+ ) ;
105+ }
106+ }
107+
108+ private async handleRedirect ( client : Auth0Client ) : Promise < void > {
109+ if ( redirectHandled ) {
110+ return ;
111+ }
112+
89113 if (
90114 typeof window !== 'undefined' &&
91115 window . location . search . includes ( 'code=' ) &&
92116 window . location . search . includes ( 'state=' )
93117 ) {
118+ redirectHandled = true ; // Mark as handled to prevent re-running
119+
94120 try {
95- // This method processes the code and state, exchanges them for tokens,
96- // and caches the result.
97- await this . client . handleRedirectCallback ( ) ;
121+ await client . handleRedirectCallback ( ) ;
98122 } catch ( e ) {
99- // Errors during handleRedirectCallback are often informational
100- // (e.g., user is already logged in). We can log them but
101- // shouldn't crash the app. The developer can get the error
102- // state from the useAuth0 hook if needed.
103123 console . error ( 'Error during handleRedirectCallback:' , e ) ;
104124 } finally {
105- // Clean the URL to remove the code and state parameters,
106- // preventing the logic from running again on a page refresh.
107125 window . history . replaceState (
108126 { } ,
109127 document . title ,
0 commit comments