expo-tiktok-ads-events 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +87 -0
- package/android/build.gradle +9 -0
- package/android/src/main/AndroidManifest.xml +4 -1
- package/android/src/main/java/expo/modules/tiktokadsevents/ExpoTiktokAdsEventsModule.kt +141 -31
- package/package.json +1 -1
- package/android/src/main/java/expo/modules/tiktokadsevents/ExpoTiktokAdsEventsView.kt +0 -30
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
This is an Expo native module that wraps the TikTok Business SDK for both iOS and Android, enabling event tracking for TikTok advertising campaigns in React Native/Expo apps.
|
|
8
|
+
|
|
9
|
+
## Commands
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Build TypeScript
|
|
13
|
+
npm run build
|
|
14
|
+
|
|
15
|
+
# Clean build artifacts
|
|
16
|
+
npm run clean
|
|
17
|
+
|
|
18
|
+
# Lint code
|
|
19
|
+
npm run lint
|
|
20
|
+
|
|
21
|
+
# Run tests
|
|
22
|
+
npm run test
|
|
23
|
+
|
|
24
|
+
# Prepare for publishing
|
|
25
|
+
npm run prepare
|
|
26
|
+
|
|
27
|
+
# Open iOS project in Xcode
|
|
28
|
+
npm run open:ios
|
|
29
|
+
|
|
30
|
+
# Open Android project in Android Studio
|
|
31
|
+
npm run open:android
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Architecture
|
|
35
|
+
|
|
36
|
+
### Native Module Structure
|
|
37
|
+
|
|
38
|
+
The module exports a native module named `TiktokAdsEvents` (not `ExpoTiktokAdsEvents`). Both platforms must use this exact name.
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
src/
|
|
42
|
+
├── index.ts # Public exports
|
|
43
|
+
└── ExpoTiktokAdsEventsModule.ts # TypeScript interface and helpers
|
|
44
|
+
|
|
45
|
+
ios/
|
|
46
|
+
├── ExpoTiktokAdsEvents.podspec # CocoaPods spec (uses TikTokBusinessSDK)
|
|
47
|
+
└── ExpoTiktokAdsEventsModule.swift # iOS implementation
|
|
48
|
+
|
|
49
|
+
android/
|
|
50
|
+
├── build.gradle # Gradle config (uses JitPack SDK)
|
|
51
|
+
└── src/main/java/expo/modules/tiktokadsevents/
|
|
52
|
+
└── ExpoTiktokAdsEventsModule.kt # Android implementation
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Key Implementation Details
|
|
56
|
+
|
|
57
|
+
- **Module Name**: Must be `TiktokAdsEvents` in both `Name()` definitions
|
|
58
|
+
- **iOS SDK**: `TikTokBusinessSDK` via CocoaPods
|
|
59
|
+
- **Android SDK**: `com.github.tiktok:tiktok-business-android-sdk` via JitPack
|
|
60
|
+
- **Event Properties**: Passed as `List<Map<String, Any>>` (Android) or `[[String: Any]]` (iOS)
|
|
61
|
+
|
|
62
|
+
### TypeScript Interface
|
|
63
|
+
|
|
64
|
+
All native methods are async and must match this interface:
|
|
65
|
+
- `initializeSdk(accessToken, appId, tiktokAppId, debugModeEnabled)` → `Promise<boolean>`
|
|
66
|
+
- `trackTTEvent(name, properties?)` → `Promise<string>`
|
|
67
|
+
- `trackCustomEvent(eventName, eventID, properties?)` → `Promise<void>`
|
|
68
|
+
- `identify(externalId, externalUserName?, phoneNumber?, email?)` → `Promise<void>`
|
|
69
|
+
- `getAnonymousID()` → `Promise<string>`
|
|
70
|
+
- `getAccessToken()` → `Promise<string>`
|
|
71
|
+
- `getTestEventCode()` → `Promise<string>`
|
|
72
|
+
|
|
73
|
+
## Testing in Consumer Apps
|
|
74
|
+
|
|
75
|
+
After making changes, consumer apps need:
|
|
76
|
+
```bash
|
|
77
|
+
npx expo prebuild --clean
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
For Android, ensure the app's `android/build.gradle` includes JitPack:
|
|
81
|
+
```groovy
|
|
82
|
+
allprojects {
|
|
83
|
+
repositories {
|
|
84
|
+
maven { url 'https://jitpack.io' }
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
```
|
package/android/build.gradle
CHANGED
|
@@ -9,6 +9,11 @@ applyKotlinExpoModulesCorePlugin()
|
|
|
9
9
|
useCoreDependencies()
|
|
10
10
|
useExpoPublishing()
|
|
11
11
|
|
|
12
|
+
repositories {
|
|
13
|
+
maven { url 'https://jitpack.io' }
|
|
14
|
+
maven { url "https://artifact.bytedance.com/repository/pangle" }
|
|
15
|
+
}
|
|
16
|
+
|
|
12
17
|
// If you want to use the managed Android SDK versions from expo-modules-core, set this to true.
|
|
13
18
|
// The Android SDK versions will be bumped from time to time in SDK releases and may introduce breaking changes in your module code.
|
|
14
19
|
// Most of the time, you may like to manage the Android SDK versions yourself.
|
|
@@ -41,3 +46,7 @@ android {
|
|
|
41
46
|
abortOnError false
|
|
42
47
|
}
|
|
43
48
|
}
|
|
49
|
+
|
|
50
|
+
dependencies {
|
|
51
|
+
implementation 'com.github.tiktok:tiktok-business-android-sdk:1.6.0'
|
|
52
|
+
}
|
|
@@ -1,2 +1,5 @@
|
|
|
1
|
-
<manifest>
|
|
1
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
2
|
+
<uses-permission android:name="android.permission.INTERNET" />
|
|
3
|
+
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
|
4
|
+
<uses-permission android:name="com.google.android.gms.permission.AD_ID" />
|
|
2
5
|
</manifest>
|
|
@@ -1,50 +1,160 @@
|
|
|
1
1
|
package expo.modules.tiktokadsevents
|
|
2
2
|
|
|
3
|
+
import android.util.Log
|
|
3
4
|
import expo.modules.kotlin.modules.Module
|
|
4
5
|
import expo.modules.kotlin.modules.ModuleDefinition
|
|
5
|
-
import
|
|
6
|
+
import expo.modules.kotlin.Promise
|
|
7
|
+
import com.tiktok.TikTokBusinessSdk
|
|
8
|
+
import com.tiktok.TikTokBusinessSdk.TTConfig
|
|
9
|
+
import com.tiktok.TikTokBusinessSdk.LogLevel
|
|
10
|
+
import org.json.JSONObject
|
|
6
11
|
|
|
7
12
|
class ExpoTiktokAdsEventsModule : Module() {
|
|
8
|
-
|
|
9
|
-
//
|
|
10
|
-
|
|
13
|
+
|
|
14
|
+
// Store accessToken since Android SDK doesn't expose it like iOS
|
|
15
|
+
private var storedAccessToken: String = ""
|
|
16
|
+
|
|
17
|
+
private val standardEventMap = mapOf(
|
|
18
|
+
"achieve_level" to "AchieveLevel",
|
|
19
|
+
"add_payment_info" to "AddPaymentInfo",
|
|
20
|
+
"complete_tutorial" to "CompleteTutorial",
|
|
21
|
+
"create_group" to "CreateGroup",
|
|
22
|
+
"create_role" to "CreateRole",
|
|
23
|
+
"generate_lead" to "GenerateLead",
|
|
24
|
+
"in_app_ad_click" to "InAppADClick",
|
|
25
|
+
"in_app_ad_impr" to "InAppADImpr",
|
|
26
|
+
"install_app" to "InstallApp",
|
|
27
|
+
"join_group" to "JoinGroup",
|
|
28
|
+
"launch_app" to "LaunchAPP",
|
|
29
|
+
"loan_application" to "LoanApplication",
|
|
30
|
+
"loan_approval" to "LoanApproval",
|
|
31
|
+
"loan_disbursal" to "LoanDisbursal",
|
|
32
|
+
"login" to "Login",
|
|
33
|
+
"rate" to "Rate",
|
|
34
|
+
"registration" to "Registration",
|
|
35
|
+
"search" to "Search",
|
|
36
|
+
"spend_credits" to "SpendCredits",
|
|
37
|
+
"start_trial" to "StartTrial",
|
|
38
|
+
"subscribe" to "Subscribe",
|
|
39
|
+
"unlock_achievement" to "UnlockAchievement"
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
private fun buildPropertiesJson(properties: List<Map<String, Any>>?): JSONObject? {
|
|
43
|
+
if (properties == null || properties.isEmpty()) return null
|
|
44
|
+
val json = JSONObject()
|
|
45
|
+
properties.forEach { prop ->
|
|
46
|
+
val key = prop["key"] as? String
|
|
47
|
+
val value = prop["value"]
|
|
48
|
+
if (key != null && value != null) {
|
|
49
|
+
json.put(key, value)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return json
|
|
53
|
+
}
|
|
54
|
+
|
|
11
55
|
override fun definition() = ModuleDefinition {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
56
|
+
Name("TiktokAdsEvents")
|
|
57
|
+
|
|
58
|
+
AsyncFunction("initializeSdk") { accessToken: String, appId: String, tiktokAppId: String, debugModeEnabled: Boolean, promise: Promise ->
|
|
59
|
+
try {
|
|
60
|
+
val context = appContext.reactContext ?: throw Exception("React context is null")
|
|
61
|
+
|
|
62
|
+
// Store accessToken for later retrieval
|
|
63
|
+
storedAccessToken = accessToken
|
|
64
|
+
|
|
65
|
+
val configBuilder = TTConfig(context)
|
|
66
|
+
.setAppId(appId)
|
|
67
|
+
.setTTAppId(tiktokAppId)
|
|
68
|
+
|
|
69
|
+
if (debugModeEnabled) {
|
|
70
|
+
configBuilder.setLogLevel(LogLevel.DEBUG)
|
|
71
|
+
configBuilder.openDebugMode()
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
TikTokBusinessSdk.initializeSdk(configBuilder)
|
|
75
|
+
promise.resolve(true)
|
|
76
|
+
} catch (e: Exception) {
|
|
77
|
+
Log.e("TiktokAdsEvents", "Error initializing SDK: ${e.message}", e)
|
|
78
|
+
promise.reject("INIT_ERROR", e.message, e)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
AsyncFunction("getAnonymousID") { promise: Promise ->
|
|
83
|
+
try {
|
|
84
|
+
// Android SDK uses getSessionID() as equivalent to iOS anonymousID
|
|
85
|
+
val sessionId = TikTokBusinessSdk.getSessionID() ?: ""
|
|
86
|
+
promise.resolve(sessionId)
|
|
87
|
+
} catch (e: Exception) {
|
|
88
|
+
promise.reject("ERROR", e.message, e)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
16
91
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
92
|
+
AsyncFunction("getAccessToken") { promise: Promise ->
|
|
93
|
+
try {
|
|
94
|
+
// Return the stored accessToken from initialization
|
|
95
|
+
promise.resolve(storedAccessToken)
|
|
96
|
+
} catch (e: Exception) {
|
|
97
|
+
promise.reject("ERROR", e.message, e)
|
|
98
|
+
}
|
|
20
99
|
}
|
|
21
100
|
|
|
22
|
-
|
|
23
|
-
|
|
101
|
+
AsyncFunction("getTestEventCode") { promise: Promise ->
|
|
102
|
+
try {
|
|
103
|
+
val code = TikTokBusinessSdk.getTestEventCode() ?: ""
|
|
104
|
+
promise.resolve(code)
|
|
105
|
+
} catch (e: Exception) {
|
|
106
|
+
promise.reject("ERROR", e.message, e)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
24
109
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
110
|
+
AsyncFunction("trackCustomEvent") { eventName: String, eventID: String, properties: List<Map<String, Any>>?, promise: Promise ->
|
|
111
|
+
try {
|
|
112
|
+
val propsJson = buildPropertiesJson(properties)
|
|
113
|
+
if (propsJson != null) {
|
|
114
|
+
TikTokBusinessSdk.trackEvent(eventName, propsJson)
|
|
115
|
+
} else {
|
|
116
|
+
TikTokBusinessSdk.trackEvent(eventName)
|
|
117
|
+
}
|
|
118
|
+
TikTokBusinessSdk.flush()
|
|
119
|
+
promise.resolve(null)
|
|
120
|
+
} catch (e: Exception) {
|
|
121
|
+
Log.e("TiktokAdsEvents", "Error tracking custom event: ${e.message}", e)
|
|
122
|
+
promise.reject("TRACK_ERROR", e.message, e)
|
|
123
|
+
}
|
|
28
124
|
}
|
|
29
125
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
126
|
+
AsyncFunction("trackTTEvent") { eventKey: String, properties: List<Map<String, Any>>?, promise: Promise ->
|
|
127
|
+
try {
|
|
128
|
+
val resolved = standardEventMap[eventKey] ?: eventKey
|
|
129
|
+
val propsJson = buildPropertiesJson(properties)
|
|
130
|
+
|
|
131
|
+
if (propsJson != null) {
|
|
132
|
+
TikTokBusinessSdk.trackEvent(resolved, propsJson)
|
|
133
|
+
} else {
|
|
134
|
+
TikTokBusinessSdk.trackEvent(resolved)
|
|
135
|
+
}
|
|
136
|
+
TikTokBusinessSdk.flush()
|
|
137
|
+
|
|
138
|
+
promise.resolve(resolved)
|
|
139
|
+
} catch (e: Exception) {
|
|
140
|
+
Log.e("TiktokAdsEvents", "Error tracking event: ${e.message}", e)
|
|
141
|
+
promise.reject("TRACK_ERROR", e.message, e)
|
|
142
|
+
}
|
|
37
143
|
}
|
|
38
144
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
145
|
+
AsyncFunction("identify") { externalId: String, externalUserName: String?, phoneNumber: String?, email: String?, promise: Promise ->
|
|
146
|
+
try {
|
|
147
|
+
TikTokBusinessSdk.identify(
|
|
148
|
+
externalId,
|
|
149
|
+
externalUserName ?: "",
|
|
150
|
+
phoneNumber ?: "",
|
|
151
|
+
email ?: ""
|
|
152
|
+
)
|
|
153
|
+
promise.resolve(null)
|
|
154
|
+
} catch (e: Exception) {
|
|
155
|
+
Log.e("TiktokAdsEvents", "Error identifying user: ${e.message}", e)
|
|
156
|
+
promise.reject("IDENTIFY_ERROR", e.message, e)
|
|
45
157
|
}
|
|
46
|
-
// Defines an event that the view can send to JavaScript.
|
|
47
|
-
Events("onLoad")
|
|
48
158
|
}
|
|
49
159
|
}
|
|
50
160
|
}
|
package/package.json
CHANGED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
package expo.modules.tiktokadsevents
|
|
2
|
-
|
|
3
|
-
import android.content.Context
|
|
4
|
-
import android.webkit.WebView
|
|
5
|
-
import android.webkit.WebViewClient
|
|
6
|
-
import expo.modules.kotlin.AppContext
|
|
7
|
-
import expo.modules.kotlin.viewevent.EventDispatcher
|
|
8
|
-
import expo.modules.kotlin.views.ExpoView
|
|
9
|
-
|
|
10
|
-
class ExpoTiktokAdsEventsView(context: Context, appContext: AppContext) : ExpoView(context, appContext) {
|
|
11
|
-
// Creates and initializes an event dispatcher for the `onLoad` event.
|
|
12
|
-
// The name of the event is inferred from the value and needs to match the event name defined in the module.
|
|
13
|
-
private val onLoad by EventDispatcher()
|
|
14
|
-
|
|
15
|
-
// Defines a WebView that will be used as the root subview.
|
|
16
|
-
internal val webView = WebView(context).apply {
|
|
17
|
-
layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
|
|
18
|
-
webViewClient = object : WebViewClient() {
|
|
19
|
-
override fun onPageFinished(view: WebView, url: String) {
|
|
20
|
-
// Sends an event to JavaScript. Triggers a callback defined on the view component in JavaScript.
|
|
21
|
-
onLoad(mapOf("url" to url))
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
init {
|
|
27
|
-
// Adds the WebView to the view hierarchy.
|
|
28
|
-
addView(webView)
|
|
29
|
-
}
|
|
30
|
-
}
|