expo-tiktok-ads-events 0.1.2 → 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
+ ```
package/README.md CHANGED
@@ -10,8 +10,60 @@ npm install expo-tiktok-ads-events
10
10
  yarn add expo-tiktok-ads-events
11
11
  ```
12
12
 
13
+ ### Peer Dependencies
14
+
15
+ ```bash
16
+ npm install expo-tracking-transparency
17
+ # or
18
+ yarn add expo-tracking-transparency
19
+ ```
20
+
13
21
  ## Setup
14
22
 
23
+ ### iOS Configuration
24
+
25
+ #### SKAdNetwork Configuration
26
+
27
+ Add the following to your `app.json` to enable proper attribution for TikTok Ads:
28
+
29
+ ```json
30
+ {
31
+ "expo": {
32
+ "ios": {
33
+ "infoPlist": {
34
+ "SKAdNetworkItems": [
35
+ {"SKAdNetworkIdentifier": "238da6jt44.skadnetwork"},
36
+ {"SKAdNetworkIdentifier": "22mmun2rn5.skadnetwork"},
37
+ // ... add all required SKAdNetwork IDs
38
+ // Full list available in the example app
39
+ ]
40
+ }
41
+ }
42
+ }
43
+ }
44
+ ```
45
+
46
+ > **Note:** The complete list of 150+ SKAdNetwork IDs is available in the [example app.json](./example/app.json). These IDs are required for proper attribution of TikTok Ads campaigns.
47
+
48
+ #### Tracking Permission
49
+
50
+ Add to your `app.json`:
51
+
52
+ ```json
53
+ {
54
+ "expo": {
55
+ "plugins": [
56
+ [
57
+ "expo-tracking-transparency",
58
+ {
59
+ "userTrackingPermission": "This identifier will be used to deliver personalized ads to you."
60
+ }
61
+ ]
62
+ ]
63
+ }
64
+ }
65
+ ```
66
+
15
67
  ### Initialization
16
68
 
17
69
  ```typescript
@@ -24,11 +76,13 @@ const { status } = await requestTrackingPermissionsAsync();
24
76
  // Initialize SDK
25
77
  await TiktokAdsEvents.initializeSdk(
26
78
  'YOUR_ACCESS_TOKEN', // TikTok Ads Manager access token
27
- 'YOUR_APP_ID', // App ID
79
+ 'YOUR_APP_ID', // App ID
28
80
  'YOUR_TIKTOK_APP_ID' // TikTok App ID
29
81
  );
30
82
  ```
31
83
 
84
+ > **Important:** Get your credentials from [TikTok Ads Manager](https://ads.tiktok.com/)
85
+
32
86
  ## Usage
33
87
 
34
88
  ### Standard Events
@@ -198,15 +252,20 @@ export default function App() {
198
252
 
199
253
  ### Common Properties
200
254
 
201
- - `currency` - Currency code (e.g., "USD", "EUR")
255
+ - `currency` - Currency code (e.g., "USD", "EUR", "BRL")
202
256
  - `value` - Monetary value
203
- - `content_type` - Content type
204
- - `content_id` - Content ID
257
+ - `content_type` - Content type (e.g., "product", "subscription")
258
+ - `content_id` - Content ID or SKU
205
259
  - `content_name` - Content name
260
+ - `content_category` - Content category
206
261
  - `quantity` - Quantity
207
262
  - `description` - Description
208
263
  - `query` - Search query
209
- - `status` - Status
264
+ - `status` - Status (e.g., "success", "failed")
265
+ - `level` - Level achieved (for gaming apps)
266
+ - `score` - Score value
267
+ - `success` - Success flag (boolean as string: "true"/"false")
268
+ - `payment_method` - Payment method used
210
269
 
211
270
  ### TypeScript Types
212
271
 
@@ -244,12 +303,16 @@ The SDK is automatically configured with:
244
303
  - ✅ Retention tracking enabled
245
304
  - ✅ SKAdNetwork enabled (iOS)
246
305
  - ✅ Debug mode enabled (development)
306
+ - ✅ Install tracking enabled
307
+ - ✅ Auto tracking disabled (manual control)
247
308
 
248
- ## Compatibility
309
+ ## Requirements
249
310
 
250
311
  - iOS 15.1+
251
312
  - Android (in development)
252
313
  - Expo SDK 54+
314
+ - TikTok Business SDK
315
+ - expo-tracking-transparency (for iOS 14+)
253
316
 
254
317
  ## Troubleshooting
255
318
 
@@ -259,6 +322,8 @@ The SDK is automatically configured with:
259
322
  2. Confirm credentials are correct
260
323
  3. Use test mode to validate events
261
324
  4. Wait up to 24 hours for production events to appear
325
+ 5. Ensure SKAdNetwork IDs are properly configured
326
+ 6. Check that the app is running on a real device (not simulator)
262
327
 
263
328
  ### Empty Anonymous ID
264
329
 
@@ -267,23 +332,31 @@ Anonymous ID is generated after successful initialization. Make sure to call `in
267
332
  ### Initialization Error
268
333
 
269
334
  Check:
270
- - Valid access token
271
- - Correct app IDs
335
+ - Valid access token from TikTok Ads Manager
336
+ - Correct app IDs (both App ID and TikTok App ID)
272
337
  - Internet connection
338
+ - TikTok Business SDK is properly installed via CocoaPods
339
+
340
+ ### SKAdNetwork Attribution Issues
341
+
342
+ - Ensure all required SKAdNetwork IDs are added to `app.json`
343
+ - Run `npx expo prebuild` after adding SKAdNetwork configuration
344
+ - Test on a real device (attribution doesn't work on simulator)
273
345
 
274
346
  ## API Reference
275
347
 
276
348
  ### Methods
277
349
 
278
- #### `initializeSdk(accessToken, appId, tiktokAppId)`
350
+ #### `initializeSdk(accessToken, appId, tiktokAppId, debugMode?)`
279
351
  Initialize the TikTok Business SDK.
280
352
 
281
353
  **Parameters:**
282
354
  - `accessToken` (string): TikTok Ads Manager access token
283
355
  - `appId` (string): Your app ID
284
356
  - `tiktokAppId` (string): TikTok app ID
357
+ - `debugMode` (boolean): Enable debug mode (optional, default: true in development)
285
358
 
286
- **Returns:** Promise<string>
359
+ **Returns:** Promise<string> - "initialization successful" or error message
287
360
 
288
361
  #### `trackTTEvent(eventName, properties?)`
289
362
  Track a standard TikTok event.
@@ -342,9 +415,16 @@ Bruno Verçosa - [Pixel Logic Apps](https://github.com/Pixel-Logic-Apps)
342
415
 
343
416
  Contributions are welcome! Please open an issue or submit a pull request.
344
417
 
345
- ## Links
418
+ ## Resources
346
419
 
420
+ ### Official Documentation
347
421
  - [TikTok for Business](https://business.tiktok.com/)
348
422
  - [TikTok Ads Manager](https://ads.tiktok.com/)
423
+ - [TikTok Events Manager](https://ads.tiktok.com/events_manager/)
349
424
  - [TikTok Events API](https://business-api.tiktok.com/portal/docs)
350
- - [Expo Modules Documentation](https://docs.expo.dev/modules/)
425
+ - [TikTok Business SDK iOS](https://github.com/tiktok/tiktok-business-ios-sdk)
426
+
427
+ ### Related
428
+ - [Expo Modules Documentation](https://docs.expo.dev/modules/)
429
+ - [SKAdNetwork Documentation](https://developer.apple.com/documentation/storekit/skadnetwork)
430
+ - [App Tracking Transparency](https://developer.apple.com/documentation/apptrackingtransparency)
@@ -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.2",
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
- }