React Native Accounts SDK
  • 21 Oct 2024
  • 9 Minutes to read
  • Contributors
  • Dark
    Light

React Native Accounts SDK

  • Dark
    Light

Article summary

Accounts SDK

Accounts SDK React Native (iOS)

Create a new .swift file, the name in this guide is "AccountsSDK" but call it whatever you like

If it is the first time making a .swift file, Xcode will prompt you to make a Bridging-Header.h file, press "Create Bridging Header" 


 A  Bridging-Header.h will expose Objective-C public headers to Swift


In [PROJECT-NAME]-Bridging-Header.h file add 

#import <React/RCTBridgeModule.h>

In yourAccountsSDK.swift create a class and extend NSObject like below (you can remove the auto-generated Foundation import):

@objc(AccountsSDK)
class AccountsSDK: NSObject {
    
  
}


Make sure to include @objc(AccountsSDK)  above the class to expose it to an Objective-C implementation file we will make later

import TicketmasterAuthentication which should have been made available after adding the Ticketmaster Frameworks

import TicketmasterAuthentication


Below are the Account SDK methods that React Native will call, you can add the desired methods to your AccountsSDK class and update them to your needs

@objc(AccountsSDK)
class AccountsSDK: NSObject  {

  
  @objc public func configureAccountsSDK(_ resolve: @escaping (String) -> Void, reject: @escaping (_ code: String, _ message: String, _ error: NSError) -> Void) {
      // build a combination of Settings and Branding
      let tmxServiceSettings = TMAuthentication.TMXSettings(apiKey: "[API_KEY]",
                                                            region: .US)
      
      let branding = TMAuthentication.Branding(displayName: "My Team",
                                               backgroundColor: .red,
                                               theme: .light)
      
      let brandedServiceSettings = TMAuthentication.BrandedServiceSettings(tmxSettings: tmxServiceSettings,
                                                                           branding: branding)
      
      // configure TMAuthentication with Settings and Branding
      print("Accounts SDK Configuring...")
      
      TMAuthentication.shared.configure(brandedServiceSettings: brandedServiceSettings) { backendsConfigured in
        // your API key may contain configurations for multiple backend services
        // the details are not needed for most common use-cases
        print(" - Accounts SDK Configured: \(backendsConfigured.count)")
        resolve("Accounts SDK configuration successful")

      } failure: { error in
        // something went wrong, probably the wrong apiKey+region combination
        print(" - Accounts SDK Configuration Error: \(error.localizedDescription)")
        reject( "Accounts SDK Configuration Error:", error.localizedDescription, error as NSError)
      }
  }
  
  
  @objc public func login(_ resolve: @escaping ([String: Any]) -> Void, reject: @escaping (_ code: String, _ message: String, _ error: NSError) -> Void) {
    
    TMAuthentication.shared.login { authToken in
      print("Login Completed")
      print(" - AuthToken: \(authToken.accessToken.prefix(20))...")
      let data = ["accessToken": authToken.accessToken]
      resolve(data)
    } aborted: { oldAuthToken, backend in
      let data = ["accessToken": ""]
      resolve(data)
      print("Login Aborted by User")
    } failure: { oldAuthToken, error, backend in
      print("Login Error: \(error.localizedDescription)")
      reject( "Accounts SDK Login Error", error.localizedDescription, error as NSError)
    }
  }
  
  
  @objc public func logout(_ resolve: @escaping (String) -> Void, reject: @escaping (_ code: String, _ message: String, _ error: NSError) -> Void) {
    // logout of all accounts, not just the accounts in the current configuration
    TMAuthentication.shared.logoutAll {backends in
      resolve("Logout Successful")
      print("Logout Completed")
      print(" - Backends Count: \(backends?.count ?? 0)")
    }
  }
  
  @objc public func refreshToken(_ resolve: @escaping ([String: Any]) -> Void, reject: @escaping (_ code: String, _ message: String, _ error: NSError) -> Void) {
    
    TMAuthentication.shared.validToken { authToken in
      print("Token Refreshed (if needed)")
      print(" - AuthToken: \(authToken.accessToken.prefix(20))...")
      let data = ["accessToken": authToken.accessToken]
      resolve(data)
    } aborted: { oldAuthToken, backend in
      print("Refresh Login Aborted by User")
      let data = ["accessToken": ""]
      resolve(data)
    } failure: { oldAuthToken, error, backend in
      print("Refresh Error: \(error.localizedDescription)")
      reject( "Accounts SDK Refresh Token Error", error.localizedDescription, error as NSError)
    }
  }
  
  @objc public func getMemberInfo(_ resolve: @escaping ([String: Any]) -> Void, reject: @escaping (_ code: String, _ message: String, _ error: NSError) -> Void) {
    
    TMAuthentication.shared.memberInfo { memberInfo in
      print("MemberInfo Completed")
      print(" - UserID: \(memberInfo.localID ?? "<nil>")")
      print(" - Email: \(memberInfo.email ?? "<nil>")")
      print(memberInfo)
      let data = ["globalUserId": memberInfo.globalID, "memberId": memberInfo.localID,  "hmacId": memberInfo.hmacID, "firstName": memberInfo.firstName, "lastName": memberInfo.lastName, "email": memberInfo.email, "phone": memberInfo.phone, "preferredLang": memberInfo.language]
      resolve(data as [String : Any])
    } failure: { oldMemberInfo, error, backend in
      print("MemberInfo Error: \(error.localizedDescription)")
      reject( "Accounts SDK Member Info Error", error.localizedDescription, error as NSError)
    }
  }
  
  @objc public func getToken(_ resolve: @escaping ([String: Any]) -> Void, reject: @escaping (_ code: String, _ message: String, _ error: NSError) -> Void) {
    TMAuthentication.shared.validToken(showLoginIfNeeded: false) { authToken in
      print("Token Retrieved")
      let data = ["accessToken": authToken.accessToken]
      resolve(data)
    } aborted: { oldAuthToken, backend in
      print("Token Retrieval Aborted ")
      let data = ["accessToken": ""]
      resolve(data)
    } failure: { oldAuthToken, error, backend in
      print("Token Retrieval Error: \(error.localizedDescription)")
      reject( "Accounts SDK Token Retrieval Error", error.localizedDescription, error as NSError)
    }
  }
  
  @objc public func isLoggedIn(_ resolve: @escaping ([String: Bool]) -> Void, reject: @escaping (_ code: String, _ message: String, _ error: NSError) -> Void) {
    TMAuthentication.shared.memberInfo { memberInfo in
      let hasToken = TMAuthentication.shared.hasToken()
      resolve(["result": hasToken])
      
    } failure: { oldMemberInfo, error, backend in
      if(TMAuthentication.shared.hasToken()){
        let hasToken = TMAuthentication.shared.hasToken()
        resolve(["result": hasToken])
      } else {
        reject("Accounts SDK Is Logged In Error", error.localizedDescription, error as NSError)
      }
    }
  }
}


Replace [APIKEY] in configureAccountsSDK() with your API key through your developer account


The last step before you can call this method in React Native is to write an implementation file in Objective-C which will register these modules and methods to React Native


Create a .m file called AccountsSDK.m (or whatever appropriate) and past in the below 

#import "React/RCTBridgeModule.h"

@interface RCT_EXTERN_MODULE(AccountsSDK, NSObject)


- (dispatch_queue_t)methodQueue
{
  return dispatch_get_main_queue();
}

+ (BOOL)requiresMainQueueSetup {
  return true;
}

RCT_EXTERN_METHOD(configureAccountsSDK: (RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)

RCT_EXTERN_METHOD(login: (RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)

RCT_EXTERN_METHOD(logout: (RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)

RCT_EXTERN_METHOD(refreshToken: (RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)

RCT_EXTERN_METHOD(getMemberInfo: (RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)

RCT_EXTERN_METHOD(getToken: (RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)

RCT_EXTERN_METHOD(isLoggedIn: (RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)


@end


dispatch_get_main_queue is a callback for secondary threads to inform the main thread that async operations are completed, it is used if your native module is rending any UI or to make sure your UI gets updated when you async actions are completed

requiresMainQueueSetup() Ensure that your native module is run on the main thread which is relevant in case of UI interactions, if not provided this defaults to YES but a warning will come up in React Native as it will be default to NO in the future 


Finally, on the React Native side you can call the native methods in React Native using NativeModules

import { NativeModules } from 'react-native'


Here is examples of Accounts SDK methods being called in React Native:

Note: the NativeModules.AccountsSDK.configureAccountsSDK() method needs to be called every time the app restarts and only needs to be called once each time the app restarts, it will probably work best in an App.tsx or earlier rendered file which is always rendered on app start up

import React, {useEffect} from 'react'
import {View, NativeModules, Text, TouchableOpacity, StyleSheet} from 'react-native'

type AccountsSDKError = {
  message: string
  code: string
}

const Home = () => {

 useEffect(() => {
     onConfigureAccountsSDK();
  }, [])

 const onConfigureAccountsSDK = () => {
   NativeModules.AccountsSDK.configureAccountsSDK()
      .then((result: any) => {
        console.log(result)
      })
      .catch((err: AccountsSDKError) => {
   console.log('Accounts SDK Configuration error:', err.message)
  })
  }

 const onLogin = () => {
    NativeModules.AccountsSDK.login()
      .then((result: any) => {
        console.log('Accounts SDK Login access token:', result)
      })
      .catch((err: AccountsSDKError) => {
        console.log('Accounts SDK Login error:', err.message)
      })
  }

 const onLogout = () => {
    NativeModules.AccountsSDK.logout()
      .then((result: any) => {
        console.log(result)
      })
      .catch(() => {
        console.log('Error on logout')
      })
  }

  const onRefreshToken = () => {
    NativeModules.AccountsSDK.refreshToken()
      .then((result: any) => {
        console.log('Accounts SDK access token:', result)
      })
      .catch((err: AccountsSDKError) => {
        console.log('Accounts SDK Refresh Token error:', err.message)
      })
  }

  const getMemberInfo = () => {
    NativeModules.AccountsSDK.getMemberInfo()
      .then((result: any) => {
        console.log('Member Info:', result)
      })
      .catch((err: AccountsSDKError) => {
        console.log('Member Info error:', err.message)
      })
  }

// Additional 
 const getToken = async () => {
    try {
      // iOS getToken has the exact same Native logic as refreshToken, but will not display the login UI if a user is not logged in
      const result = await NativeModules.AccountsSDK.getToken();
      console.log('Accounts SDK Login access token:', result);
    } catch (err) {
      console.log('Get Token error:', (err as Error).message);
    }
  }

  const isLoggedIn = async () => {
    try {
      const result = await AccountsSDK.isLoggedIn();
      console.log('Is logged in: ', result.result);
    } catch (e: any) {
      !(e as Error).message.includes('User not logged in') &&
        console.log('IsLoggedIn error: ', (e as Error).message);
    }
  }

  return (
    <View style={{flex: 1}}>
      <TouchableOpacity onPress={() => onLogin()} style={styles.button}>
        <Text>Login</Text>
      </TouchableOpacity>
      <TouchableOpacity onPress={() => onLogout()} style={styles.button}>
        <Text>Logout</Text>
      </TouchableOpacity>
      <TouchableOpacity onPress={() => onRefreshToken()} style={styles.button}>
        <Text>Refresh Token </Text>
      </TouchableOpacity>
      <TouchableOpacity onPress={() => getMemberInfo()} style={styles.button}>
        <Text>Get Member Info</Text>
      </TouchableOpacity>
      <TouchableOpacity onPress={() => getToken()} style={styles.button}>
        <Text>Get Token </Text>
      </TouchableOpacity>
      <TouchableOpacity onPress={() => isLoggedIn()} style={styles.button}>
        <Text>isLoggedIn </Text>
      </TouchableOpacity>
    </View>
  )
}

const styles = StyleSheet.create({
  button: {
    backgroundColor: '#07a',
    width: 170,
    height: 30,
    borderRadius: 4,
    alignItems: 'center',
    margin: 4,
    justifyContent: 'center',
  },
})

export default Home

Analytics  

To setup delegates in React Native iOS see:https://ignite.ticketmaster.com/docs/react-native-accounts-information

After setting up Delegates in your Accounts SDK NSObject/RCTEventEmitter class you can follow: https://ignite.ticketmaster.com/docs/analytics  




Accounts SDK React Native (Android)

Create a native module in Android called AccountsSDKModule.kt with the following content:

class AccountsSDKModule(reactContext: ReactApplicationContext) :
  ReactContextBaseJavaModule(reactContext) {

  object IgniteSDKSingleton {
    private var authenticationSDK: TMAuthentication? = null

    fun getAuthenticationSDK(): TMAuthentication? {
      return authenticationSDK
    }

    fun setAuthenticationSDK(authSDK: TMAuthentication) {
      authenticationSDK = authSDK
    }
  }

  private val CODE = 1
  override fun getName() = "AccountsSDK"
  private var mResultCallback: Callback? = null

  private val mActivityEventListener: ActivityEventListener =
    object : BaseActivityEventListener() {
      override fun onActivityResult(
        activity: Activity?,
        requestCode: Int,
        resultCode: Int,
        data: Intent?
      ) {
        if (mResultCallback != null) {
          mResultCallback!!.invoke(resultCode)
          mResultCallback = null
        }
        if (resultCode == Activity.RESULT_CANCELED) {
        }
        if (resultCode == Activity.RESULT_OK) {
        }
      }
    }

  init {
    reactContext.addActivityEventListener(mActivityEventListener)
  }

  @ReactMethod
  fun login(resultCallback: Callback) {
    IgniteSDKSingleton.getAuthenticationSDK()?.let { authentication ->
      runBlocking() {
        mResultCallback = resultCallback
        val currentFragmentActivity = currentActivity as FragmentActivity
        val intent = authentication.getLoginIntent(currentFragmentActivity)
        currentActivity?.startActivityForResult(intent, CODE)
      }
    }
  }

  @ReactMethod
  fun isLoggedIn(promise: Promise) =
    IgniteSDKSingleton.getAuthenticationSDK()?.let { authentication ->
      runBlocking {
        try {
          val response = withContext(context = Dispatchers.IO) {
            AuthSource.values().forEach {
              if (authentication.getToken(it)?.isNotBlank() == true) {
                return@withContext true
              }
            }
            return@withContext false
          }

          promise.resolve(response)
        } catch (e: Exception) {
          promise.reject("Accounts SDK isLoggedIn Error: ", e)
        }
      }
    } ?: false

  @ReactMethod
  fun configureAccountsSDK(promise: Promise) {
    runBlocking(Dispatchers.Main) {
      try {
        val currentFragmentActivity = currentActivity as FragmentActivity
        val authentication = TMAuthentication.Builder()
          .apiKey("")
          .clientName("My Team")
          .colors(createTMAuthenticationColors(android.graphics.Color.parseColor("#026cdf")))
          .environment(TMXDeploymentEnvironment.Production)
          .region(TMXDeploymentRegion.US)
          .forceNewSession(true)
          .build(currentFragmentActivity)
        IgniteSDKSingleton.setAuthenticationSDK(authentication)

        TicketsSDKClient
          .Builder()
          .authenticationSDKClient(authentication)
          .colors(createTicketsColors(android.graphics.Color.parseColor("#026cdf")))
          .build(currentFragmentActivity)
          .apply {
            TicketsSDKSingleton.setTicketsSdkClient(this)
            promise.resolve("Accounts SDK configuration successful")
          }
      } catch (e: Exception) {
        promise.reject("Accounts SDK Configuration Error: ", e)
      }
    }
  }

  @ReactMethod
  fun logout(promise: Promise) {
    runBlocking {
      withContext(context = Dispatchers.IO) {
        try {
          val currentFragmentActivity = currentActivity as FragmentActivity
          val authentication = TMAuthentication.Builder()
            .apiKey("")
            .clientName("My Team")
            .colors(TMAuthentication.ColorTheme())
            .environment(TMXDeploymentEnvironment.Production)
            .region(TMXDeploymentRegion.US)
            .build(currentFragmentActivity)
          authentication.logout(currentFragmentActivity)
          promise.resolve(true)
        } catch (e: Exception) {
          promise.reject("Accounts SDK Logout Error: ", e)
        }
      }
    }
  }

  @ReactMethod
  fun getMemberInfo(promise: Promise) = IgniteSDKSingleton.getAuthenticationSDK()?.let {
    runBlocking {
      try {
        val archticsAccessToken = async { it.getToken(AuthSource.ARCHTICS) }
        val hostAccessToken = async { it.getToken(AuthSource.HOST) }
        val mfxAccessToken = async { it.getToken(AuthSource.MFX) }
        val sportXRAccessToken = async { it.getToken(AuthSource.SPORTXR) }
        val (resArchticsAccessToken, resHostAccessToken, resMfxAccessToken, resSportXRAccessToken) = awaitAll(
          archticsAccessToken,
          hostAccessToken,
          mfxAccessToken,
          sportXRAccessToken
        )

        if (resArchticsAccessToken.isNullOrEmpty() && resHostAccessToken.isNullOrEmpty() && resMfxAccessToken.isNullOrEmpty() && resSportXRAccessToken.isNullOrEmpty()) {
          promise.resolve(null)
        } else {
          val memberInfoJson = Gson().toJson(it.fetchUserDetails().getOrNull())
          promise.resolve(memberInfoJson)
        }
      } catch (e: Exception) {
        promise.reject("Accounts SDK getMemberInfo Error: ", e)
      }
    }
  }

  @ReactMethod
  fun refreshToken(promise: Promise) = IgniteSDKSingleton.getAuthenticationSDK()?.let {
    runBlocking {
      try {
        val hostAccessToken = async { it.getToken(AuthSource.HOST) }
        val archticsAccessToken = async { it.getToken(AuthSource.ARCHTICS) }
        val mfxAccessToken = async { it.getToken(AuthSource.MFX) }
        val sportXRAccessToken = async { it.getToken(AuthSource.SPORTXR) }
        val (resArchticsAccessToken, resHostAccessToken, resMfxAccessToken, resSportXRAccessToken) = awaitAll(
          archticsAccessToken,
          hostAccessToken,
          mfxAccessToken,
          sportXRAccessToken
        )
        val tokenRefreshedParams: WritableMap = Arguments.createMap().apply {
          putString("accountsSdkTokenRefreshed", "accountsSdkTokenRefreshed")
        }
        val combinedTokens: WritableMap = Arguments.createMap()
        if (!resHostAccessToken.isNullOrEmpty()) {
          combinedTokens.putString("hostAccessToken", resHostAccessToken)
        }
        if (!resArchticsAccessToken.isNullOrEmpty()) {
          combinedTokens.putString("archticsAccessToken", resArchticsAccessToken)
        }
        if (!resMfxAccessToken.isNullOrEmpty()) {
          combinedTokens.putString("mfxAccessToken", resMfxAccessToken)
        }
        if (!resSportXRAccessToken.isNullOrEmpty()) {
          combinedTokens.putString("sportXRAccessToken", resSportXRAccessToken)
        }
        GlobalEventEmitter.sendEvent("igniteAnalytics", tokenRefreshedParams)
        promise.resolve(combinedTokens)
      } catch (e: Exception) {
        promise.reject("Accounts SDK refreshToken Error: ", e)
      }
    }
  }

  private fun createTicketsColors(color: Int): TicketsColors =
    TicketsColors(
      lightColors(
        primary = Color(color),
        primaryVariant = Color(color),
        secondary = Color(color)
      ),
      darkColors(
        primary = Color(color),
        primaryVariant = Color(color),
        secondary = Color(color)
      )
    )

  @SuppressLint("ConflictingOnColor")
  private fun createTMAuthenticationColors(color: Int): TMAuthentication.ColorTheme =
    TMAuthentication.ColorTheme(
      // The Color class is part of the Compose library
      lightColors(
        primary = Color(color),
        primaryVariant = Color(color),
        secondary = Color(color),
        onPrimary = Color.White // Color used for text and icons displayed on top of the primary color.
      ),
      darkColors(
        primary = Color(color),
        primaryVariant = Color(color),
        secondary = Color(color),
        onPrimary = Color.White // Color used for text and icons displayed on top of the primary color.
      )
    )
}

On the React Native side you can call the native methods in React Native using NativeModules

import { NativeModules } from 'react-native'

Most native methods returns a Promise use callbacks or async/await to get a return value.

<Button onPress=async () => {
    const accessToken = await NativeModules.getAccessToken() 
}/>

Was this article helpful?

What's Next