React Native Accounts SDK
  • 12 Oct 2023
  • 8 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("Authentication 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(" - Authentication SDK Configured: \(backendsConfigured.count)")
        resolve("Authentication SDK configuration successful")

      } failure: { error in
        // something went wrong, probably the wrong apiKey+region combination
        print(" - Authentication SDK Configuration Error: \(error.localizedDescription)")
        reject( "Authentication 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) {

    TMAuthentication.shared.logout { 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>")")
      let data = ["memberInfoId": memberInfo.localID, "memberInfoEmail": memberInfo.email]
      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
      guard let id = memberInfo.globalID else {
        resolve(["result": false])
        return
      }
      
      let hasToken = TMAuthentication.shared.hasToken()
      resolve(["result": hasToken])
      
    } failure: { oldMemberInfo, error, backend in
      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) {

    override fun getName() = "AccountsSDKModule"
    private val CODE = 1
    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)
                }
            }
        }

    init {
        reactContext.addActivityEventListener(mActivityEventListener)
    }

    @ReactMethod
    fun login(resultCallback: Callback) {
        mResultCallback = resultCallback
        val intent = TicketsSDKSingleton.getLoginIntent(currentActivity as FragmentActivity)
        currentActivity?.startActivityForResult(intent, CODE)
    }

    @ReactMethod
    fun logout(promise: Promise) {
        TicketsSDKSingleton.getTMAuthentication()?.let {
            runBlocking {
                withContext(context = Dispatchers.IO) {
                    TicketsSDKSingleton.logout(reactApplicationContext)
                }
                promise.resolve(true)
            }
        }
    }

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

                promise.resolve(response)
            }
        } ?: false

    @ReactMethod
    fun getUserDetails(promise: Promise) = TicketsSDKSingleton.getTMAuthentication()?.let {
            runBlocking {
                val archticsAccessToken = async { it.getToken(AuthSource.ARCHTICS) }
                val hostAccessToken = async { it.getToken(AuthSource.HOST) }
                val (resArchticAccessToken, resHostAccessToken) = awaitAll(
                    archticsAccessToken,
                    hostAccessToken
                )

                if (resArchticAccessToken.isNullOrEmpty() && resHostAccessToken.isNullOrEmpty()) {
                    promise.resolve(null)
                } else {
                    promise.resolve(it.fetchUserDetails().getOrNull())
                }
            }
        }


    @ReactMethod
    fun getAccessToken(promise: Promise) = TicketsSDKSingleton.getTMAuthentication()?.let {
        runBlocking {
            val hostAccessToken = async { it.getToken(AuthSource.HOST) }
            val archticsAccessToken = async { it.getToken(AuthSource.ARCHTICS) }
            val (resArchticAccessToken, resHostAccessToken) = awaitAll(
                archticsAccessToken,
                hostAccessToken
            )
            if (!resHostAccessToken.isNullOrEmpty()) {
                promise.resolve(resHostAccessToken)
            } else if (!resArchticAccessToken.isNullOrEmpty()) {
                promise.resolve(resArchticAccessToken)
            } else {
                promise.resolve(null)
            }
        }
    }


    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)
            )
        )

    private suspend fun validateAuthToken(authentication: TMAuthentication): Map<AuthSource, String> {
        val tokenMap = mutableMapOf<AuthSource, String>()
        AuthSource.values().forEach {
            //Validate if there is an active token for the AuthSource, if not it returns null.
            authentication.getToken(it)?.let { token ->
                tokenMap[it] = token
            }
        }
        return tokenMap
    }
}

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