From f40081093784fa1dcdaea3e7bdc69984a58697ae Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 21 Sep 2026 11:06:51 +0100 Subject: [PATCH 1/2] refactor: fix gaps in sdk login state communication --- .../ticketmasterignite/AccountsSDKModule.kt | 18 --- src/IgniteProvider.tsx | 137 ++++++++++++------ 2 files changed, 93 insertions(+), 62 deletions(-) diff --git a/android/src/main/java/com/ticketmasterignite/AccountsSDKModule.kt b/android/src/main/java/com/ticketmasterignite/AccountsSDKModule.kt index ae75e0a..f318112 100644 --- a/android/src/main/java/com/ticketmasterignite/AccountsSDKModule.kt +++ b/android/src/main/java/com/ticketmasterignite/AccountsSDKModule.kt @@ -157,15 +157,6 @@ class AccountsSDKModule(reactContext: ReactApplicationContext) : NativeAccountsS CoroutineScope(Dispatchers.IO).launch { try { authentication.logout() - - val loggedOutParams: WritableMap = Arguments.createMap() - loggedOutParams.putString("accountsSdkLoggedOut", "accountsSdkLoggedOut") - GlobalEventEmitter.sendEvent("igniteAnalytics", loggedOutParams) - - val logoutCompletedParams: WritableMap = Arguments.createMap() - logoutCompletedParams.putString("accountsSdkLogoutCompleted", "accountsSdkLogoutCompleted") - GlobalEventEmitter.sendEvent("igniteAnalytics", logoutCompletedParams) - promise.resolve(true) } catch (e: Exception) { promise.reject("Accounts SDK Logout Error", e) @@ -183,15 +174,6 @@ class AccountsSDKModule(reactContext: ReactApplicationContext) : NativeAccountsS CoroutineScope(Dispatchers.IO).launch { try { authentication.logout() - - val loggedOutParams: WritableMap = Arguments.createMap() - loggedOutParams.putString("accountsSdkLoggedOut", "accountsSdkLoggedOut") - GlobalEventEmitter.sendEvent("igniteAnalytics", loggedOutParams) - - val logoutCompletedParams: WritableMap = Arguments.createMap() - logoutCompletedParams.putString("accountsSdkLogoutCompleted", "accountsSdkLogoutCompleted") - GlobalEventEmitter.sendEvent("igniteAnalytics", logoutCompletedParams) - promise.resolve(true) } catch (e: Exception) { promise.reject("Accounts SDK LogoutAll Error", e) diff --git a/src/IgniteProvider.tsx b/src/IgniteProvider.tsx index 09c22f2..98edacf 100644 --- a/src/IgniteProvider.tsx +++ b/src/IgniteProvider.tsx @@ -1,4 +1,10 @@ -import React, { createContext, useCallback, useEffect, useState } from 'react'; +import React, { + createContext, + useCallback, + useEffect, + useRef, + useState, +} from 'react'; import { NativeModules, NativeEventEmitter, @@ -175,7 +181,7 @@ export const IgniteProvider: React.FC = ({ memberInfo: null, }); - const setAccountDetails = useCallback(async () => { + const setAuthenticationState = useCallback(async () => { let isLoggedInResult = false; let memberInfoResult = null; try { @@ -228,11 +234,11 @@ export const IgniteProvider: React.FC = ({ ); } try { - autoUpdate && (await setAccountDetails()); + autoUpdate && (await setAuthenticationState()); } catch (e) { throw e; } - }, [autoUpdate, enableLogs, setAccountDetails]); + }, [autoUpdate, enableLogs, setAuthenticationState]); const setNativeConfigValues = useCallback(() => { NativeConfig.setConfig('apiKey', apiKey); @@ -363,44 +369,89 @@ export const IgniteProvider: React.FC = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + const handleIgniteAnalyticsEvent = async ( + result: IgniteAnalytics + ): Promise => { + if (result && analytics) analytics(result); + + const checkoutEnded = result.purchaseSdkDidEndCheckoutFor; + const userLoggedInViaTicketsSdk = result.ticketsSdkDidViewEvents; + // The user can logout via the Ticket SDK Modal UI so the below event checks state when the user returns back to RN screens + const ticketsSdkModalDismissed = result.ticketsSdkModalDidDismiss; + const iosAuthStateChanged = + (result.accountsSdkLoggedIn || result.accountsSdkLoggedOut) && + Platform.OS === 'ios'; + // Sport XR fires accountsSdkLoggedIn whereas Modern Accounts fires accountsSdkLoginAccountCompleted + const androidSdkAuthStateChanged = + (result.accountsSdkLoginAccountCompleted || + result.accountsSdkLoggedIn || + result.accountsSdkLogoutStarted) && + Platform.OS === 'android'; + + // The condition below is a helper to handle SDK-initiated auth state changes. + // When RN triggers auth, isLoggingIn is set to true so isLoggingIn being false will be an SDK-initiated change. + if ( + (checkoutEnded || + userLoggedInViaTicketsSdk || + ticketsSdkModalDismissed || + ((iosAuthStateChanged || androidSdkAuthStateChanged) && + !isLoggingIn)) && + autoUpdate + ) { + try { + if (result.accountsSdkLogoutStarted && Platform.OS === 'android') { + // Promise timeout approach allows outer try/catch to handle errors without needing try/catch inside setTimeout + await new Promise((resolve) => setTimeout(resolve, 1200)); + await setAuthenticationState(); + } else { + await setAuthenticationState(); + } + } catch (e) { + enableLogs && + console.log( + `Accounts SDK auth state update error: ${(e as Error).message}` + ); + } + } + if (result.ticketsSdkVenueConcessionsOrderFor) { + venueConcessionsModule?.orderButtonCallback( + result.ticketsSdkVenueConcessionsOrderFor + ); + } + if (result.ticketsSdkVenueConcessionsWalletFor) { + venueConcessionsModule?.walletButtonCallback( + result.ticketsSdkVenueConcessionsWalletFor + ); + } + if (result.ticketsSdkCustomModuleButton1) { + button1?.callback(result.ticketsSdkCustomModuleButton1); + } + if (result.ticketsSdkCustomModuleButton2) { + button2?.callback(result.ticketsSdkCustomModuleButton2); + } + if (result.ticketsSdkCustomModuleButton3) { + button3?.callback(result.ticketsSdkCustomModuleButton3); + } + }; + + const igniteAnalyticsHandlerRef = useRef< + (result: IgniteAnalytics) => Promise + >(handleIgniteAnalyticsEvent); + + // Rewritten on every render so the listener below, which is registered once, + // always invokes a handler holding current props and state + useEffect(() => { + igniteAnalyticsHandlerRef.current = handleIgniteAnalyticsEvent; + }); + useEffect(() => { const igniteEventEmitter = new NativeEventEmitter( NativeModules.GlobalEventEmitter ); igniteEventEmitter.addListener( 'igniteAnalytics', - async (result: IgniteAnalytics) => { - if (result && analytics) analytics(result); - if ( - (result.purchaseSdkDidEndCheckoutFor || - result.ticketsSdkDidViewEvents || - result.ticketsSdkModalDidDismiss || - ((result.accountsSdkLoginAccountCompleted || - result.accountsSdkLoggedOut) && - !isLoggingIn)) && - autoUpdate - ) { - await setAccountDetails(); - } - if (result.ticketsSdkVenueConcessionsOrderFor) { - venueConcessionsModule?.orderButtonCallback( - result.ticketsSdkVenueConcessionsOrderFor - ); - } - if (result.ticketsSdkVenueConcessionsWalletFor) { - venueConcessionsModule?.walletButtonCallback( - result.ticketsSdkVenueConcessionsWalletFor - ); - } - if (result.ticketsSdkCustomModuleButton1) { - button1?.callback(result.ticketsSdkCustomModuleButton1); - } - if (result.ticketsSdkCustomModuleButton2) { - button2?.callback(result.ticketsSdkCustomModuleButton2); - } - if (result.ticketsSdkCustomModuleButton3) { - button3?.callback(result.ticketsSdkCustomModuleButton3); - } + (result: IgniteAnalytics) => { + igniteAnalyticsHandlerRef.current(result); } ); @@ -408,8 +459,6 @@ export const IgniteProvider: React.FC = ({ return () => { igniteEventEmitter.removeAllListeners('igniteAnalytics'); }; - // Only create native listener once in an apps lifecycle - // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const login = useCallback( @@ -421,7 +470,7 @@ export const IgniteProvider: React.FC = ({ const result = await NativeAccountsSdk.login(); if (result?.accessToken) { enableLogs && console.log('Accounts SDK login successful'); - !skipUpdate && autoUpdate && (await setAccountDetails()); + !skipUpdate && autoUpdate && (await setAuthenticationState()); //avoid await on callbacks passed to library as there is no guarantee how long they will take to resolve onLogin && onLogin(); } @@ -431,7 +480,7 @@ export const IgniteProvider: React.FC = ({ } !skipUpdate && setIsLoggingIn(false); }, - [autoUpdate, enableLogs, setAccountDetails] + [autoUpdate, enableLogs, setAuthenticationState] ); const logout = useCallback( @@ -441,13 +490,13 @@ export const IgniteProvider: React.FC = ({ try { await NativeAccountsSdk.logout(); enableLogs && console.log('Accounts SDK logout successful'); - !skipUpdate && autoUpdate && (await setAccountDetails()); + !skipUpdate && autoUpdate && (await setAuthenticationState()); onLogout && onLogout(); } catch (e) { throw e; } }, - [autoUpdate, enableLogs, setAccountDetails] + [autoUpdate, enableLogs, setAuthenticationState] ); const logoutAll = useCallback( @@ -457,13 +506,13 @@ export const IgniteProvider: React.FC = ({ try { await NativeAccountsSdk.logoutAll(); enableLogs && console.log('Accounts SDK logoutAll successful'); - !skipUpdate && autoUpdate && (await setAccountDetails()); + !skipUpdate && autoUpdate && (await setAuthenticationState()); onLogout && onLogout(); } catch (e) { throw e; } }, - [autoUpdate, enableLogs, setAccountDetails] + [autoUpdate, enableLogs, setAuthenticationState] ); const getIsLoggedIn = useCallback(async (): Promise => { From af16a310e68f3cee584804b61927951e323eedcc Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 21 Sep 2026 11:50:46 +0100 Subject: [PATCH 2/2] chore: update comment --- src/IgniteProvider.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/IgniteProvider.tsx b/src/IgniteProvider.tsx index 98edacf..3e56ce3 100644 --- a/src/IgniteProvider.tsx +++ b/src/IgniteProvider.tsx @@ -374,10 +374,11 @@ export const IgniteProvider: React.FC = ({ ): Promise => { if (result && analytics) analytics(result); - const checkoutEnded = result.purchaseSdkDidEndCheckoutFor; - const userLoggedInViaTicketsSdk = result.ticketsSdkDidViewEvents; - // The user can logout via the Ticket SDK Modal UI so the below event checks state when the user returns back to RN screens + // The user can logout via the Tickets SDK Modal UI, so the below event + // tells RN to check the SDK auth state when the user dismisses the modal const ticketsSdkModalDismissed = result.ticketsSdkModalDidDismiss; + const userLoggedInViaTicketsSdk = result.ticketsSdkDidViewEvents; + const checkoutEnded = result.purchaseSdkDidEndCheckoutFor; const iosAuthStateChanged = (result.accountsSdkLoggedIn || result.accountsSdkLoggedOut) && Platform.OS === 'ios';