11import { ActionSheetProvider } from "@expo/react-native-action-sheet" ;
22import { Ionicons } from "@expo/vector-icons" ;
3+ import AsyncStorage from "@react-native-async-storage/async-storage" ;
34import {
45 NavigationContainer ,
56 LinkingOptions ,
@@ -127,32 +128,60 @@ export default function AppContent({
127128 const navigationRef = useRef < NavigationContainerRef < TabParamList > > ( null ) ;
128129 const hcb = useClient ( ) ;
129130
130- // Reset Stripe Terminal initialization state on app start
131131 useEffect ( ( ) => {
132132 resetStripeTerminalInitialization ( ) ;
133+ setTokenFetchAttempts ( 0 ) ;
134+ setLastTokenFetch ( 0 ) ;
135+ setCachedToken ( null ) ;
136+ setTokenExpiry ( 0 ) ;
133137 } , [ ] ) ;
134138
135139 const [ lastTokenFetch , setLastTokenFetch ] = useState < number > ( 0 ) ;
136140 const [ tokenFetchAttempts , setTokenFetchAttempts ] = useState < number > ( 0 ) ;
141+ const [ cachedToken , setCachedToken ] = useState < string | null > ( null ) ;
142+ const [ tokenExpiry , setTokenExpiry ] = useState < number > ( 0 ) ;
137143 const TOKEN_FETCH_COOLDOWN = 5000 ;
138144 const MAX_TOKEN_FETCH_ATTEMPTS = 3 ;
145+ const TOKEN_CACHE_DURATION = 10 * 60 * 1000 ; // 10 minutes
139146
140147 const fetchTokenProvider = async ( ) : Promise < string > => {
141148 const now = Date . now ( ) ;
142149
150+ // Return cached token if it's still valid
151+ if ( cachedToken && now < tokenExpiry ) {
152+ console . log ( "Using cached Stripe Terminal connection token" ) ;
153+ return cachedToken ;
154+ }
155+
156+ // Check if we should actually fetch the token
157+ // Only fetch if the user is authenticated and has access token
158+ if ( ! tokens ?. accessToken ) {
159+ console . log ( "No access token available, skipping Stripe Terminal token fetch" ) ;
160+ throw new Error ( "Authentication required for Stripe Terminal connection" ) ;
161+ }
162+
163+ // Check rate limiting
143164 if ( now - lastTokenFetch < TOKEN_FETCH_COOLDOWN ) {
165+ const waitTime = Math . ceil ( ( TOKEN_FETCH_COOLDOWN - ( now - lastTokenFetch ) ) / 1000 ) ;
166+ console . warn ( `Rate limited: Please wait ${ waitTime } seconds before retrying` ) ;
144167 throw new Error (
145- `Rate limited: Please wait ${ Math . ceil ( ( TOKEN_FETCH_COOLDOWN - ( now - lastTokenFetch ) ) / 1000 ) } seconds before retrying` ,
168+ `Rate limited: Please wait ${ waitTime } seconds before retrying` ,
146169 ) ;
147170 }
148171
149172 if ( tokenFetchAttempts >= MAX_TOKEN_FETCH_ATTEMPTS ) {
173+ console . error ( `Maximum token fetch attempts (${ MAX_TOKEN_FETCH_ATTEMPTS } ) exceeded` ) ;
174+ setTimeout ( ( ) => {
175+ setTokenFetchAttempts ( 0 ) ;
176+ setLastTokenFetch ( 0 ) ;
177+ } , 60000 ) ;
150178 throw new Error (
151- `Maximum token fetch attempts (${ MAX_TOKEN_FETCH_ATTEMPTS } ) exceeded. Please restart the app .` ,
179+ `Maximum token fetch attempts (${ MAX_TOKEN_FETCH_ATTEMPTS } ) exceeded. Please wait before retrying .` ,
152180 ) ;
153181 }
154182
155183 try {
184+ console . log ( "Fetching new Stripe Terminal connection token..." ) ;
156185 setLastTokenFetch ( now ) ;
157186 setTokenFetchAttempts ( ( prev ) => prev + 1 ) ;
158187
@@ -164,9 +193,16 @@ export default function AppContent({
164193 } ;
165194 } ;
166195
196+ const newToken = token . terminal_connection_token . secret ;
197+ const newExpiry = now + TOKEN_CACHE_DURATION ;
198+
199+ // Cache the token
200+ setCachedToken ( newToken ) ;
201+ setTokenExpiry ( newExpiry ) ;
167202 setTokenFetchAttempts ( 0 ) ;
168203
169- return token . terminal_connection_token . secret ;
204+ console . log ( "Successfully fetched and cached Stripe Terminal connection token" ) ;
205+ return newToken ;
170206 } catch ( error ) {
171207 console . error ( "Token fetch failed:" , error ) ;
172208
@@ -180,6 +216,7 @@ export default function AppContent({
180216 TOKEN_FETCH_COOLDOWN * Math . pow ( 2 , tokenFetchAttempts ) ,
181217 30000 ,
182218 ) ; // Max 30 seconds
219+ console . warn ( `Rate limited (429). Please wait ${ Math . ceil ( backoffTime / 1000 ) } seconds before retrying.` ) ;
183220 throw new Error (
184221 `Rate limited (429). Please wait ${ Math . ceil ( backoffTime / 1000 ) } seconds before retrying.` ,
185222 ) ;
@@ -229,13 +266,22 @@ export default function AppContent({
229266 setStatusBar ( ) ;
230267 const checkAuth = async ( ) => {
231268 if ( tokens ?. accessToken ) {
232- if ( ( await process . env . EXPO_PUBLIC_APP_VARIANT ) === "development" ) {
233- // bypass auth for development
234- setIsAuthenticated ( true ) ;
235- setAppIsReady ( true ) ;
236- return ;
237- }
269+ // if ((await process.env.EXPO_PUBLIC_APP_VARIANT) === "development") {
270+ // // bypass auth for development
271+ // setIsAuthenticated(true);
272+ // setAppIsReady(true);
273+ // return;
274+ // }
238275 try {
276+ const biometricsRequired = await AsyncStorage . getItem ( "biometrics_required" ) ;
277+
278+ if ( biometricsRequired !== "true" ) {
279+ console . log ( "Biometric authentication not required, bypassing..." ) ;
280+ setIsAuthenticated ( true ) ;
281+ setAppIsReady ( true ) ;
282+ return ;
283+ }
284+
239285 // Check if biometric authentication is available
240286 const hasHardware = await LocalAuthentication . hasHardwareAsync ( ) ;
241287 const isEnrolled = await LocalAuthentication . isEnrolledAsync ( ) ;
@@ -299,6 +345,10 @@ export default function AppContent({
299345
300346 useEffect ( ( ) => {
301347 const handleUpdates = async ( ) => {
348+ if ( process . env . EXPO_PUBLIC_APP_VARIANT === "development" ) {
349+ setFinishedUpdateCheck ( true ) ;
350+ return ;
351+ }
302352 try {
303353 const availableUpdate = await Updates . checkForUpdateAsync ( ) ;
304354
0 commit comments