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 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
+ ```
@@ -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 java.net.URL
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
- // Each module class must implement the definition function. The definition consists of components
9
- // that describes the module's functionality and behavior.
10
- // See https://docs.expo.dev/modules/module-api for more details about available components.
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
- // Sets the name of the module that JavaScript code will use to refer to the module. Takes a string as an argument.
13
- // Can be inferred from module's class name, but it's recommended to set it explicitly for clarity.
14
- // The module will be accessible from `requireNativeModule('ExpoTiktokAdsEvents')` in JavaScript.
15
- Name("ExpoTiktokAdsEvents")
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
- // Defines constant property on the module.
18
- Constant("PI") {
19
- Math.PI
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
- // Defines event names that the module can send to JavaScript.
23
- Events("onChange")
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
- // Defines a JavaScript synchronous function that runs the native code on the JavaScript thread.
26
- Function("hello") {
27
- "Hello world! 👋"
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
- // Defines a JavaScript function that always returns a Promise and whose native code
31
- // is by default dispatched on the different thread than the JavaScript runtime runs on.
32
- AsyncFunction("setValueAsync") { value: String ->
33
- // Send an event to JavaScript.
34
- sendEvent("onChange", mapOf(
35
- "value" to value
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
- // Enables the module to be used as a native view. Definition components that are accepted as part of
40
- // the view definition: Prop, Events.
41
- View(ExpoTiktokAdsEventsView::class) {
42
- // Defines a setter for the `url` prop.
43
- Prop("url") { view: ExpoTiktokAdsEventsView, url: URL ->
44
- view.webView.loadUrl(url.toString())
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,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-tiktok-ads-events",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Expo Tiktok SDK Events",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -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
- }