- Print
- DarkLight
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()
}/>