@@ -5,7 +5,7 @@
import React from 'react' ;
import { Auth0Provider as BaseAuth0Provider , useAuth0 } from '@auth0/auth0-react' ;
import { useNavigate } from 'react-router-dom' ;
import { apiClient } from '../api/client' ;
import { apiClient , setAuthReady } from '../api/client' ;
interface Auth0ProviderProps {
children : React.ReactNode ;
@@ -18,8 +18,12 @@ export const Auth0Provider: React.FC<Auth0ProviderProps> = ({ children }) => {
const clientId = import . meta . env . VITE_AUTH0_CLIENT_ID ;
const audience = import . meta . env . VITE_AUTH0_AUDIENCE ;
// Basic component loading debug
console . log ( '[Auth0Provider] Component loaded' , { domain , clientId , audience } ) ;
const onRedirectCallback = ( appState ? : { returnTo? : string } ) = > {
console . log ( '[Auth0Provider] Redirect callback triggered' , { appState , returnTo : appState?.returnTo } ) ;
navigate ( appState ? . returnTo || '/dashboard' ) ;
} ;
@@ -28,12 +32,16 @@ export const Auth0Provider: React.FC<Auth0ProviderProps> = ({ children }) => {
domain = { domain }
clientId = { clientId }
authorizationParams = { {
redirect_uri : window.location.hostname === "admin.motovaultpro.com" ? "https://admin.motovaultpro.com/callback" : window . location . origin + "/callback" ,
// Production domain; ensure mobile devices resolve this host during testing
redirect_uri : "https://admin.motovaultpro.com/callback" ,
audience : audience ,
scope : 'openid profile email offline_access' ,
} }
onRedirectCallback = { onRedirectCallback }
// Mobile Safari/ITP: use localstorage + refresh tokens to avoid third‑ party cookie silent auth failures
cacheLocation = "localstorage"
useRefreshTokens = { true }
useRefreshTokensFallback = { true }
>
< TokenInjector > { children } < / TokenInjector >
< / BaseAuth0Provider >
@@ -42,64 +50,139 @@ export const Auth0Provider: React.FC<Auth0ProviderProps> = ({ children }) => {
// Component to inject token into API client with mobile support
const TokenInjector : React.FC < { children : React.ReactNode } > = ( { children } ) = > {
const { getAccessTokenSilently , isAuthenticated } = useAuth0 ( ) ;
const { getAccessTokenSilently , isAuthenticated , isLoading , user } = useAuth0 ( ) ;
const [ retryCount , setRetryCount ] = React . useState ( 0 ) ;
// Helper function to get token with retry logic for mobile devices
const getTokenWithRetry = async ( maxRetries = 3 , delayMs = 500 ) : Promise < string | null > = > {
// Basic component loading debug
console . log ( '[TokenInjector] Component loaded' ) ;
// Debug mobile authentication state
React . useEffect ( ( ) = > {
const isMobile = /Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i . test ( navigator . userAgent ) ;
console . log ( ` [Auth Debug] Mobile: ${ isMobile } , Loading: ${ isLoading } , Authenticated: ${ isAuthenticated } , User: ${ user ? 'present' : 'null' } ` ) ;
} , [ isAuthenticated , isLoading , user ] ) ;
// Helper function to get token with enhanced retry logic for mobile devices
const getTokenWithRetry = async ( maxRetries = 5 , delayMs = 300 ) : Promise < any > = > {
for ( let attempt = 0 ; attempt < maxRetries ; attempt ++ ) {
try {
// P rogressive fallback strategy for mobile compatibility
let tokenOptions ;
// Enhanced p rogressive strategy for mobile compatibility
let tokenOptions : any ;
if ( attempt === 0 ) {
// First attempt: try cache firs t
tokenOptions = { timeoutInSeconds : 15 , cacheMode : 'on' as const } ;
// First attempt: try cache with shorter timeou t
tokenOptions = { timeoutInSeconds : 10 , cacheMode : 'on' as const } ;
} else if ( attempt === 1 ) {
// Second attempt: force refresh
tokenOptions = { timeoutInSeconds : 20 , cacheMode : 'off ' as const } ;
// Second attempt: cache with longer timeout
tokenOptions = { timeoutInSeconds : 20 , cacheMode : 'on ' as const } ;
} else if ( attempt === 2 ) {
// Third attempt: force refresh with reasonable timeout
tokenOptions = { timeoutInSeconds : 15 , cacheMode : 'off' as const } ;
} else if ( attempt === 3 ) {
// Fourth attempt: force refresh with longer timeout
tokenOptions = { timeoutInSeconds : 30 , cacheMode : 'off' as const } ;
} else {
// Final attempt: default behavior with longer timeout
tokenOptions = { timeoutInSeconds : 30 } ;
// Final attempt: default behavior with maximum timeout
tokenOptions = { timeoutInSeconds : 45 } ;
}
const token = await getAccessTokenSilently ( tokenOptions ) ;
console . log ( ` Token acquired successfully on attempt ${ attempt + 1 } ` ) ;
console . log ( ` [Mobile Auth] Token acquired successfully on attempt ${ attempt + 1 } ` , {
cacheMode : tokenOptions.cacheMode ,
timeout : tokenOptions.timeoutInSeconds
} ) ;
return token ;
} catch ( error : any ) {
console . warn ( ` Token acquisition a ttempt ${ attempt + 1 } failed: ` , error . message || error ) ;
// On mobile, Auth0 might need more time - wait and retry
console . warn ( ` [Mobile Auth] A ttempt ${ attempt + 1 } / ${ maxRetries } failed: ` , {
error : error.message || error ,
cacheMode : attempt <= 2 ? 'on' : 'off'
} ) ;
// Mobile-specific: longer delays and more attempts
if ( attempt < maxRetries - 1 ) {
const delay = delayMs * Math . pow ( 2 , attempt ) ; // E xponential backoff
console . log ( ` Waiting ${ delay } ms before retry... ` ) ;
const delay = delayMs * Math . pow ( 1.5 , attempt ) ; // Gentler e xponential backoff
console . log ( ` [Mobile Auth] Waiting ${ Math . round ( delay ) } ms before retry... ` ) ;
await new Promise ( resolve = > setTimeout ( resolve , delay ) ) ;
}
}
}
console . error ( 'All token acquisition attempts failed' ) ;
console . error ( '[Mobile Auth] All token acquisition attempts failed - authentication may be broken ' ) ;
return null ;
} ;
// Force authentication check for devices when user seems logged in but isAuthenticated is false
React . useEffect ( ( ) = > {
const isMobile = /Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i . test ( navigator . userAgent ) ;
// Debug current state
console . log ( '[Auth Debug] State check:' , {
isMobile ,
isLoading ,
isAuthenticated ,
pathname : window.location.pathname ,
userAgent : navigator.userAgent.substring ( 0 , 50 ) + '...'
} ) ;
// Trigger for mobile devices OR any device on protected route without authentication
if ( ! isLoading && ! isAuthenticated && window . location . pathname !== '/' ) {
console . log ( '[Auth Debug] User on protected route but not authenticated, forcing token check...' ) ;
// Aggressive token check
const forceAuthCheck = async ( ) = > {
try {
// Try multiple approaches to get token
const token = await getAccessTokenSilently ( {
cacheMode : 'off' as const ,
timeoutInSeconds : 10
} ) ;
console . log ( '[Auth Debug] Force auth successful, token acquired' ) ;
// Manually add to API client since isAuthenticated might still be false
if ( token ) {
console . log ( '[Auth Debug] Manually adding token to API client' ) ;
// Force add the token to subsequent requests
apiClient . interceptors . request . use ( ( config ) = > {
if ( ! config . headers . Authorization ) {
config . headers . Authorization = ` Bearer ${ token } ` ;
console . log ( '[Auth Debug] Token manually added to request' ) ;
}
return config ;
} ) ;
setAuthReady ( true ) ;
}
} catch ( error : any ) {
console . log ( '[Auth Debug] Force auth failed:' , error . message ) ;
}
} ;
forceAuthCheck ( ) ;
}
} , [ isLoading , isAuthenticated , getAccessTokenSilently ] ) ;
React . useEffect ( ( ) = > {
let interceptorId : number | undefined ;
if ( isAuthenticated ) {
// P re-warm token cache for mobile devices with delay
// Enhanced p re-warm token cache for mobile devices
const initializeToken = async ( ) = > {
// Give Auth0 a moment to fully initialize on mobile
await new Promise ( resolve = > setTimeout ( resolve , 100 ) ) ;
// Give Auth0 more time to fully initialize on mobile devices
const isMobile = /Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i . test ( navigator . userAgent ) ;
const initDelay = isMobile ? 500 : 100 ; // Longer delay for mobile
console . log ( ` [Mobile Auth] Initializing token cache (mobile: ${ isMobile } , delay: ${ initDelay } ms) ` ) ;
await new Promise ( resolve = > setTimeout ( resolve , initDelay ) ) ;
try {
const token = await getTokenWithRetry ( ) ;
if ( token ) {
console . log ( 'Token pre-warming successful' ) ;
console . log ( '[Mobile Auth] Token pre-warming successful' ) ;
setRetryCount ( 0 ) ;
setAuthReady ( true ) ;
} else {
console . error ( 'Failed to acquire token after retries - will retry on API calls' ) ;
console . error ( '[Mobile Auth] Failed to acquire token after retries - will retry on API calls' ) ;
setRetryCount ( prev = > prev + 1 ) ;
}
} catch ( error ) {
console . error ( 'Token initialization failed:' , error ) ;
console . error ( '[Mobile Auth] Token initialization failed:' , error ) ;
setRetryCount ( prev = > prev + 1 ) ;
}
} ;
@@ -112,6 +195,7 @@ const TokenInjector: React.FC<{ children: React.ReactNode }> = ({ children }) =>
const token = await getTokenWithRetry ( ) ;
if ( token ) {
config . headers . Authorization = ` Bearer ${ token } ` ;
setAuthReady ( true ) ;
} else {
console . error ( 'No token available for request to:' , config . url ) ;
// Allow request to proceed - backend will return 401 if needed
@@ -124,6 +208,7 @@ const TokenInjector: React.FC<{ children: React.ReactNode }> = ({ children }) =>
} ) ;
} else {
setRetryCount ( 0 ) ;
setAuthReady ( false ) ;
}
// Cleanup function to remove interceptor
@@ -135,4 +220,4 @@ const TokenInjector: React.FC<{ children: React.ReactNode }> = ({ children }) =>
} , [ isAuthenticated , getAccessTokenSilently , retryCount ] ) ;
return < > { children } < / > ;
} ;
} ;