@datalyr/react-native 1.2.1 → 1.3.1

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.
Files changed (51) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +145 -9
  3. package/android/build.gradle +54 -0
  4. package/android/src/main/AndroidManifest.xml +14 -0
  5. package/android/src/main/java/com/datalyr/reactnative/DatalyrNativeModule.java +423 -0
  6. package/android/src/main/java/com/datalyr/reactnative/DatalyrPackage.java +30 -0
  7. package/android/src/main/java/com/datalyr/reactnative/DatalyrPlayInstallReferrerModule.java +229 -0
  8. package/datalyr-react-native.podspec +2 -2
  9. package/ios/DatalyrSKAdNetwork.m +400 -1
  10. package/ios/PrivacyInfo.xcprivacy +48 -0
  11. package/lib/ConversionValueEncoder.d.ts +13 -1
  12. package/lib/ConversionValueEncoder.js +57 -23
  13. package/lib/datalyr-sdk.d.ts +31 -2
  14. package/lib/datalyr-sdk.js +138 -30
  15. package/lib/index.d.ts +5 -1
  16. package/lib/index.js +4 -1
  17. package/lib/integrations/index.d.ts +3 -1
  18. package/lib/integrations/index.js +2 -1
  19. package/lib/integrations/meta-integration.d.ts +1 -0
  20. package/lib/integrations/meta-integration.js +4 -3
  21. package/lib/integrations/play-install-referrer.d.ts +78 -0
  22. package/lib/integrations/play-install-referrer.js +166 -0
  23. package/lib/integrations/tiktok-integration.d.ts +1 -0
  24. package/lib/integrations/tiktok-integration.js +4 -3
  25. package/lib/journey.d.ts +106 -0
  26. package/lib/journey.js +258 -0
  27. package/lib/native/DatalyrNativeBridge.d.ts +42 -3
  28. package/lib/native/DatalyrNativeBridge.js +63 -9
  29. package/lib/native/SKAdNetworkBridge.d.ts +142 -0
  30. package/lib/native/SKAdNetworkBridge.js +328 -0
  31. package/lib/network-status.d.ts +84 -0
  32. package/lib/network-status.js +281 -0
  33. package/lib/types.d.ts +51 -0
  34. package/lib/utils.d.ts +6 -1
  35. package/lib/utils.js +52 -2
  36. package/package.json +13 -4
  37. package/src/ConversionValueEncoder.ts +67 -26
  38. package/src/datalyr-sdk-expo.ts +55 -6
  39. package/src/datalyr-sdk.ts +161 -38
  40. package/src/expo.ts +4 -0
  41. package/src/index.ts +7 -1
  42. package/src/integrations/index.ts +3 -1
  43. package/src/integrations/meta-integration.ts +4 -3
  44. package/src/integrations/play-install-referrer.ts +218 -0
  45. package/src/integrations/tiktok-integration.ts +4 -3
  46. package/src/journey.ts +338 -0
  47. package/src/native/DatalyrNativeBridge.ts +99 -13
  48. package/src/native/SKAdNetworkBridge.ts +481 -2
  49. package/src/network-status.ts +312 -0
  50. package/src/types.ts +74 -6
  51. package/src/utils.ts +62 -6
package/CHANGELOG.md CHANGED
@@ -5,6 +5,25 @@ All notable changes to the Datalyr React Native SDK will be documented in this f
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.3.1] - 2026-01
9
+
10
+ ### Added
11
+ - **iOS 18.4+ Features** - Geo-level postbacks, development postbacks, overlapping windows
12
+ - **Privacy Manifest** (`ios/PrivacyInfo.xcprivacy`) - Required for App Store compliance
13
+ - **Network Status Detection** - Automatic online/offline handling with queue sync
14
+ - `SKAdNetworkBridge` iOS 18.4+ methods:
15
+ - `isGeoPostbackAvailable()` - Check for geo-level postback support
16
+ - `setPostbackEnvironment()` - Configure sandbox/production mode
17
+ - `getEnhancedAttributionInfo()` - Full feature matrix by iOS version
18
+ - `updatePostbackWithWindow()` - Overlapping window support
19
+ - `enableDevelopmentMode()` / `disableDevelopmentMode()` - Convenience methods
20
+ - Migration guides from AppsFlyer and Adjust
21
+ - Comprehensive troubleshooting section in README
22
+
23
+ ### Changed
24
+ - Parallel SDK initialization for faster startup
25
+ - Enhanced TypeScript types for iOS 18.4+ responses
26
+
8
27
  ## [1.2.1] - 2025-01
9
28
 
10
29
  ### Added
package/README.md CHANGED
@@ -553,24 +553,160 @@ import {
553
553
 
554
554
  ---
555
555
 
556
+ ## Migrating from AppsFlyer / Adjust
557
+
558
+ Datalyr provides similar functionality with a simpler integration.
559
+
560
+ ### From AppsFlyer
561
+
562
+ ```typescript
563
+ // BEFORE: AppsFlyer
564
+ import appsFlyer from 'react-native-appsflyer';
565
+ appsFlyer.logEvent('af_purchase', { af_revenue: 99.99, af_currency: 'USD' });
566
+
567
+ // AFTER: Datalyr
568
+ import { Datalyr } from '@datalyr/react-native';
569
+ await Datalyr.trackPurchase(99.99, 'USD', 'product_id');
570
+ ```
571
+
572
+ ### From Adjust
573
+
574
+ ```typescript
575
+ // BEFORE: Adjust
576
+ import { Adjust, AdjustEvent } from 'react-native-adjust';
577
+ const event = new AdjustEvent('abc123');
578
+ event.setRevenue(99.99, 'USD');
579
+ Adjust.trackEvent(event);
580
+
581
+ // AFTER: Datalyr
582
+ import { Datalyr } from '@datalyr/react-native';
583
+ await Datalyr.trackPurchase(99.99, 'USD');
584
+ ```
585
+
586
+ ### Event Mapping
587
+
588
+ | AppsFlyer | Adjust | Datalyr |
589
+ |-----------|--------|---------|
590
+ | `af_purchase` | `PURCHASE` | `trackPurchase()` |
591
+ | `af_add_to_cart` | `ADD_TO_CART` | `trackAddToCart()` |
592
+ | `af_initiated_checkout` | `INITIATE_CHECKOUT` | `trackInitiateCheckout()` |
593
+ | `af_complete_registration` | `COMPLETE_REGISTRATION` | `trackCompleteRegistration()` |
594
+ | `af_content_view` | `VIEW_CONTENT` | `trackViewContent()` |
595
+ | `af_subscribe` | `SUBSCRIBE` | `trackSubscription()` |
596
+
597
+ ### Migration Checklist
598
+
599
+ - [ ] Remove old SDK: `npm uninstall react-native-appsflyer`
600
+ - [ ] Install Datalyr: `npm install @datalyr/react-native`
601
+ - [ ] Run `cd ios && pod install`
602
+ - [ ] Replace initialization and event tracking code
603
+ - [ ] Verify events in Datalyr dashboard
604
+
605
+ ---
606
+
556
607
  ## Troubleshooting
557
608
 
558
- ### Events not appearing
609
+ ### Events Not Appearing
610
+
611
+ **1. Check SDK Status**
612
+ ```typescript
613
+ const status = Datalyr.getStatus();
614
+ console.log('Initialized:', status.initialized);
615
+ console.log('Queue size:', status.queueStats.queueSize);
616
+ console.log('Online:', status.queueStats.isOnline);
617
+ ```
618
+
619
+ **2. Enable Debug Mode**
620
+ ```typescript
621
+ await Datalyr.initialize({
622
+ apiKey: 'dk_your_api_key',
623
+ debug: true,
624
+ });
625
+ ```
626
+
627
+ **3. Force Flush**
628
+ ```typescript
629
+ await Datalyr.flush();
630
+ ```
631
+
632
+ **4. Verify API Key** - Should start with `dk_`
559
633
 
560
- 1. Check API key starts with `dk_`
561
- 2. Enable `debug: true`
562
- 3. Check `Datalyr.getStatus()` for queue info
563
- 4. Verify network connectivity
634
+ ### iOS Build Errors
564
635
 
565
- ### iOS build errors
636
+ ```bash
637
+ cd ios
638
+ pod deintegrate
639
+ pod cache clean --all
640
+ pod install
641
+ ```
566
642
 
643
+ **Clean Reset**
567
644
  ```bash
568
- cd ios && pod deintegrate && pod install
645
+ rm -rf node_modules ios/Pods ios/Podfile.lock
646
+ npm install && cd ios && pod install
647
+ ```
648
+
649
+ ### Android Build Errors
650
+
651
+ ```bash
652
+ cd android && ./gradlew clean
653
+ npx react-native run-android
654
+ ```
655
+
656
+ ### Meta SDK Not Working
657
+
658
+ Verify Info.plist:
659
+ ```xml
660
+ <key>FacebookAppID</key>
661
+ <string>YOUR_APP_ID</string>
662
+ <key>FacebookClientToken</key>
663
+ <string>YOUR_CLIENT_TOKEN</string>
664
+ ```
665
+
666
+ Check status: `Datalyr.getPlatformIntegrationStatus()`
667
+
668
+ ### TikTok SDK Not Working
669
+
670
+ ```typescript
671
+ await Datalyr.initialize({
672
+ apiKey: 'dk_your_api_key',
673
+ tiktok: {
674
+ appId: 'your_app_id',
675
+ tiktokAppId: '7123456789012345',
676
+ },
677
+ });
678
+ ```
679
+
680
+ ### SKAdNetwork Not Updating
681
+
682
+ 1. iOS 14.0+ required (16.1+ for SKAN 4.0)
683
+ 2. Set `skadTemplate` in config
684
+ 3. Use `trackWithSKAdNetwork()` instead of `track()`
685
+
686
+ ### Attribution Not Captured
687
+
688
+ ```typescript
689
+ await Datalyr.initialize({
690
+ apiKey: 'dk_your_api_key',
691
+ enableAttribution: true,
692
+ });
693
+
694
+ // Check data
695
+ const attribution = Datalyr.getAttributionData();
696
+ ```
697
+
698
+ ### App Tracking Transparency (iOS 14.5+)
699
+
700
+ ```typescript
701
+ import { requestTrackingPermissionsAsync } from 'expo-tracking-transparency';
702
+
703
+ const { status } = await requestTrackingPermissionsAsync();
704
+ await Datalyr.updateTrackingAuthorization(status === 'granted');
569
705
  ```
570
706
 
571
- ### Meta/TikTok not working
707
+ ### Debug Logging
572
708
 
573
- Verify Info.plist contains required keys (see Installation).
709
+ Look for `[Datalyr]` prefixed messages in console.
574
710
 
575
711
  ---
576
712
 
@@ -0,0 +1,54 @@
1
+ buildscript {
2
+ repositories {
3
+ google()
4
+ mavenCentral()
5
+ }
6
+ dependencies {
7
+ classpath 'com.android.tools.build:gradle:8.2.0'
8
+ }
9
+ }
10
+
11
+ apply plugin: 'com.android.library'
12
+
13
+ def safeExtGet(prop, fallback) {
14
+ rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
15
+ }
16
+
17
+ android {
18
+ namespace "com.datalyr.reactnative"
19
+ compileSdkVersion safeExtGet('compileSdkVersion', 35)
20
+
21
+ defaultConfig {
22
+ minSdkVersion safeExtGet('minSdkVersion', 21)
23
+ targetSdkVersion safeExtGet('targetSdkVersion', 35)
24
+ }
25
+
26
+ compileOptions {
27
+ sourceCompatibility JavaVersion.VERSION_17
28
+ targetCompatibility JavaVersion.VERSION_17
29
+ }
30
+
31
+ lint {
32
+ abortOnError false
33
+ }
34
+ }
35
+
36
+ repositories {
37
+ google()
38
+ mavenCentral()
39
+ maven { url "https://artifact.bytedance.com/repository/pangle" } // TikTok SDK
40
+ }
41
+
42
+ dependencies {
43
+ // React Native
44
+ implementation "com.facebook.react:react-native:+"
45
+
46
+ // Meta (Facebook) SDK - Updated Jan 2026
47
+ implementation 'com.facebook.android:facebook-android-sdk:18.1.3'
48
+
49
+ // TikTok Business SDK - Updated Jan 2026
50
+ implementation 'com.tiktok.business.sdk:tiktok-business-android-sdk:1.6.0'
51
+
52
+ // Google Play Install Referrer
53
+ implementation 'com.android.installreferrer:installreferrer:2.2'
54
+ }
@@ -0,0 +1,14 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
3
+ package="com.datalyr.reactnative">
4
+
5
+ <!-- Required for network requests -->
6
+ <uses-permission android:name="android.permission.INTERNET" />
7
+
8
+ <!-- Required for Google Play Install Referrer -->
9
+ <uses-permission android:name="com.google.android.finsky.permission.BIND_GET_INSTALL_REFERRER_SERVICE" />
10
+
11
+ <!-- Optional: For device info -->
12
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
13
+
14
+ </manifest>
@@ -0,0 +1,423 @@
1
+ package com.datalyr.reactnative;
2
+
3
+ import android.os.Bundle;
4
+ import android.util.Log;
5
+
6
+ import com.facebook.react.bridge.Arguments;
7
+ import com.facebook.react.bridge.Promise;
8
+ import com.facebook.react.bridge.ReactApplicationContext;
9
+ import com.facebook.react.bridge.ReactContextBaseJavaModule;
10
+ import com.facebook.react.bridge.ReactMethod;
11
+ import com.facebook.react.bridge.ReadableMap;
12
+ import com.facebook.react.bridge.ReadableMapKeySetIterator;
13
+ import com.facebook.react.bridge.WritableMap;
14
+
15
+ // Meta (Facebook) SDK imports
16
+ import com.facebook.FacebookSdk;
17
+ import com.facebook.appevents.AppEventsLogger;
18
+ import com.facebook.appevents.AppEventsConstants;
19
+ import com.facebook.appevents.UserDataStore;
20
+ import com.facebook.bolts.AppLinks;
21
+ import android.net.Uri;
22
+
23
+ // TikTok SDK imports
24
+ import com.tiktok.TikTokBusinessSdk;
25
+ import com.tiktok.TikTokBusinessSdk.TTConfig;
26
+ import com.tiktok.appevents.TikTokAppEvent;
27
+ import com.tiktok.appevents.TikTokAppEventLogger;
28
+
29
+ import java.math.BigDecimal;
30
+ import java.util.Currency;
31
+ import java.util.HashMap;
32
+ import java.util.Map;
33
+
34
+ /**
35
+ * Datalyr Native Module for Android
36
+ * Provides Meta (Facebook) and TikTok SDK integrations for React Native
37
+ */
38
+ public class DatalyrNativeModule extends ReactContextBaseJavaModule {
39
+ private static final String TAG = "DatalyrNative";
40
+ private static final String MODULE_NAME = "DatalyrNative";
41
+
42
+ private final ReactApplicationContext reactContext;
43
+ private AppEventsLogger metaLogger;
44
+ private boolean metaInitialized = false;
45
+ private boolean tiktokInitialized = false;
46
+
47
+ public DatalyrNativeModule(ReactApplicationContext context) {
48
+ super(context);
49
+ this.reactContext = context;
50
+ }
51
+
52
+ @Override
53
+ public String getName() {
54
+ return MODULE_NAME;
55
+ }
56
+
57
+ // ============================================================================
58
+ // Meta (Facebook) SDK Methods
59
+ // ============================================================================
60
+
61
+ @ReactMethod
62
+ public void initializeMetaSDK(String appId, String clientToken, boolean advertiserTrackingEnabled, Promise promise) {
63
+ try {
64
+ // Initialize Facebook SDK
65
+ FacebookSdk.setApplicationId(appId);
66
+ if (clientToken != null && !clientToken.isEmpty()) {
67
+ FacebookSdk.setClientToken(clientToken);
68
+ }
69
+ FacebookSdk.setAdvertiserIDCollectionEnabled(advertiserTrackingEnabled);
70
+ FacebookSdk.setAutoLogAppEventsEnabled(true);
71
+ FacebookSdk.sdkInitialize(reactContext.getApplicationContext());
72
+
73
+ // Create logger instance
74
+ metaLogger = AppEventsLogger.newLogger(reactContext.getApplicationContext());
75
+ metaInitialized = true;
76
+
77
+ Log.d(TAG, "Meta SDK initialized with App ID: " + appId);
78
+ promise.resolve(true);
79
+ } catch (Exception e) {
80
+ Log.e(TAG, "Failed to initialize Meta SDK", e);
81
+ promise.reject("meta_init_error", "Failed to initialize Meta SDK: " + e.getMessage());
82
+ }
83
+ }
84
+
85
+ @ReactMethod
86
+ public void fetchDeferredAppLink(Promise promise) {
87
+ if (!metaInitialized) {
88
+ promise.resolve(null);
89
+ return;
90
+ }
91
+
92
+ try {
93
+ AppLinks.getTargetUrlFromInboundIntent(reactContext.getApplicationContext(), reactContext.getCurrentActivity().getIntent())
94
+ .continueWith(task -> {
95
+ if (task.getError() != null) {
96
+ Log.d(TAG, "Deferred app link error: " + task.getError().getMessage());
97
+ promise.resolve(null);
98
+ return null;
99
+ }
100
+
101
+ Uri targetUrl = task.getResult();
102
+ if (targetUrl != null) {
103
+ promise.resolve(targetUrl.toString());
104
+ } else {
105
+ promise.resolve(null);
106
+ }
107
+ return null;
108
+ });
109
+ } catch (Exception e) {
110
+ Log.d(TAG, "Deferred app link not available");
111
+ promise.resolve(null);
112
+ }
113
+ }
114
+
115
+ @ReactMethod
116
+ public void logMetaEvent(String eventName, Double valueToSum, ReadableMap parameters, Promise promise) {
117
+ if (!metaInitialized || metaLogger == null) {
118
+ promise.resolve(false);
119
+ return;
120
+ }
121
+
122
+ try {
123
+ Bundle params = readableMapToBundle(parameters);
124
+
125
+ if (valueToSum != null) {
126
+ metaLogger.logEvent(eventName, valueToSum, params);
127
+ } else if (params.isEmpty()) {
128
+ metaLogger.logEvent(eventName);
129
+ } else {
130
+ metaLogger.logEvent(eventName, params);
131
+ }
132
+
133
+ Log.d(TAG, "Meta event logged: " + eventName);
134
+ promise.resolve(true);
135
+ } catch (Exception e) {
136
+ Log.e(TAG, "Failed to log Meta event", e);
137
+ promise.reject("meta_event_error", "Failed to log Meta event: " + e.getMessage());
138
+ }
139
+ }
140
+
141
+ @ReactMethod
142
+ public void logMetaPurchase(double amount, String currency, ReadableMap parameters, Promise promise) {
143
+ if (!metaInitialized || metaLogger == null) {
144
+ promise.resolve(false);
145
+ return;
146
+ }
147
+
148
+ try {
149
+ Bundle params = readableMapToBundle(parameters);
150
+ BigDecimal purchaseAmount = BigDecimal.valueOf(amount);
151
+ Currency currencyObj = Currency.getInstance(currency);
152
+
153
+ metaLogger.logPurchase(purchaseAmount, currencyObj, params);
154
+
155
+ Log.d(TAG, "Meta purchase logged: " + amount + " " + currency);
156
+ promise.resolve(true);
157
+ } catch (Exception e) {
158
+ Log.e(TAG, "Failed to log Meta purchase", e);
159
+ promise.reject("meta_purchase_error", "Failed to log Meta purchase: " + e.getMessage());
160
+ }
161
+ }
162
+
163
+ @ReactMethod
164
+ public void setMetaUserData(ReadableMap userData, Promise promise) {
165
+ if (!metaInitialized) {
166
+ promise.resolve(false);
167
+ return;
168
+ }
169
+
170
+ try {
171
+ // Set user data for Advanced Matching
172
+ Bundle userDataBundle = new Bundle();
173
+
174
+ if (userData.hasKey("email")) {
175
+ userDataBundle.putString("em", userData.getString("email"));
176
+ }
177
+ if (userData.hasKey("firstName")) {
178
+ userDataBundle.putString("fn", userData.getString("firstName"));
179
+ }
180
+ if (userData.hasKey("lastName")) {
181
+ userDataBundle.putString("ln", userData.getString("lastName"));
182
+ }
183
+ if (userData.hasKey("phone")) {
184
+ userDataBundle.putString("ph", userData.getString("phone"));
185
+ }
186
+ if (userData.hasKey("dateOfBirth")) {
187
+ userDataBundle.putString("db", userData.getString("dateOfBirth"));
188
+ }
189
+ if (userData.hasKey("gender")) {
190
+ userDataBundle.putString("ge", userData.getString("gender"));
191
+ }
192
+ if (userData.hasKey("city")) {
193
+ userDataBundle.putString("ct", userData.getString("city"));
194
+ }
195
+ if (userData.hasKey("state")) {
196
+ userDataBundle.putString("st", userData.getString("state"));
197
+ }
198
+ if (userData.hasKey("zip")) {
199
+ userDataBundle.putString("zp", userData.getString("zip"));
200
+ }
201
+ if (userData.hasKey("country")) {
202
+ userDataBundle.putString("country", userData.getString("country"));
203
+ }
204
+
205
+ AppEventsLogger.setUserData(
206
+ userData.hasKey("email") ? userData.getString("email") : null,
207
+ userData.hasKey("firstName") ? userData.getString("firstName") : null,
208
+ userData.hasKey("lastName") ? userData.getString("lastName") : null,
209
+ userData.hasKey("phone") ? userData.getString("phone") : null,
210
+ userData.hasKey("dateOfBirth") ? userData.getString("dateOfBirth") : null,
211
+ userData.hasKey("gender") ? userData.getString("gender") : null,
212
+ userData.hasKey("city") ? userData.getString("city") : null,
213
+ userData.hasKey("state") ? userData.getString("state") : null,
214
+ userData.hasKey("zip") ? userData.getString("zip") : null,
215
+ userData.hasKey("country") ? userData.getString("country") : null
216
+ );
217
+
218
+ Log.d(TAG, "Meta user data set for Advanced Matching");
219
+ promise.resolve(true);
220
+ } catch (Exception e) {
221
+ Log.e(TAG, "Failed to set Meta user data", e);
222
+ promise.reject("meta_userdata_error", "Failed to set Meta user data: " + e.getMessage());
223
+ }
224
+ }
225
+
226
+ @ReactMethod
227
+ public void clearMetaUserData(Promise promise) {
228
+ if (!metaInitialized) {
229
+ promise.resolve(false);
230
+ return;
231
+ }
232
+
233
+ try {
234
+ AppEventsLogger.clearUserData();
235
+ Log.d(TAG, "Meta user data cleared");
236
+ promise.resolve(true);
237
+ } catch (Exception e) {
238
+ Log.e(TAG, "Failed to clear Meta user data", e);
239
+ promise.reject("meta_clear_error", "Failed to clear Meta user data: " + e.getMessage());
240
+ }
241
+ }
242
+
243
+ @ReactMethod
244
+ public void updateMetaTrackingAuthorization(boolean enabled, Promise promise) {
245
+ try {
246
+ FacebookSdk.setAdvertiserIDCollectionEnabled(enabled);
247
+ Log.d(TAG, "Meta tracking authorization updated: " + enabled);
248
+ promise.resolve(true);
249
+ } catch (Exception e) {
250
+ Log.e(TAG, "Failed to update Meta tracking authorization", e);
251
+ promise.reject("meta_tracking_error", "Failed to update Meta tracking: " + e.getMessage());
252
+ }
253
+ }
254
+
255
+ // ============================================================================
256
+ // TikTok SDK Methods
257
+ // ============================================================================
258
+
259
+ @ReactMethod
260
+ public void initializeTikTokSDK(String appId, String tiktokAppId, String accessToken, boolean debug, Promise promise) {
261
+ try {
262
+ TTConfig config = new TTConfig(reactContext.getApplicationContext())
263
+ .setAppId(appId)
264
+ .setTTAppId(tiktokAppId);
265
+
266
+ if (accessToken != null && !accessToken.isEmpty()) {
267
+ config.setAccessToken(accessToken);
268
+ }
269
+
270
+ if (debug) {
271
+ config.openDebugMode();
272
+ }
273
+
274
+ TikTokBusinessSdk.initializeSdk(config);
275
+ tiktokInitialized = true;
276
+
277
+ Log.d(TAG, "TikTok SDK initialized with App ID: " + tiktokAppId);
278
+ promise.resolve(true);
279
+ } catch (Exception e) {
280
+ Log.e(TAG, "Failed to initialize TikTok SDK", e);
281
+ promise.reject("tiktok_init_error", "Failed to initialize TikTok SDK: " + e.getMessage());
282
+ }
283
+ }
284
+
285
+ @ReactMethod
286
+ public void trackTikTokEvent(String eventName, String eventId, ReadableMap properties, Promise promise) {
287
+ if (!tiktokInitialized) {
288
+ promise.resolve(false);
289
+ return;
290
+ }
291
+
292
+ try {
293
+ TikTokAppEvent event;
294
+ if (eventId != null && !eventId.isEmpty()) {
295
+ event = new TikTokAppEvent(eventName).setEventId(eventId);
296
+ } else {
297
+ event = new TikTokAppEvent(eventName);
298
+ }
299
+
300
+ // Add properties to the event
301
+ if (properties != null) {
302
+ ReadableMapKeySetIterator iterator = properties.keySetIterator();
303
+ while (iterator.hasNextKey()) {
304
+ String key = iterator.nextKey();
305
+ switch (properties.getType(key)) {
306
+ case String:
307
+ event.addProperty(key, properties.getString(key));
308
+ break;
309
+ case Number:
310
+ event.addProperty(key, properties.getDouble(key));
311
+ break;
312
+ case Boolean:
313
+ event.addProperty(key, properties.getBoolean(key));
314
+ break;
315
+ default:
316
+ break;
317
+ }
318
+ }
319
+ }
320
+
321
+ TikTokBusinessSdk.trackTTEvent(event);
322
+
323
+ Log.d(TAG, "TikTok event logged: " + eventName);
324
+ promise.resolve(true);
325
+ } catch (Exception e) {
326
+ Log.e(TAG, "Failed to log TikTok event", e);
327
+ promise.reject("tiktok_event_error", "Failed to log TikTok event: " + e.getMessage());
328
+ }
329
+ }
330
+
331
+ @ReactMethod
332
+ public void identifyTikTokUser(String externalId, String externalUserName, String phoneNumber, String email, Promise promise) {
333
+ if (!tiktokInitialized) {
334
+ promise.resolve(false);
335
+ return;
336
+ }
337
+
338
+ try {
339
+ TikTokBusinessSdk.identify(
340
+ externalId != null && !externalId.isEmpty() ? externalId : null,
341
+ externalUserName != null && !externalUserName.isEmpty() ? externalUserName : null,
342
+ phoneNumber != null && !phoneNumber.isEmpty() ? phoneNumber : null,
343
+ email != null && !email.isEmpty() ? email : null
344
+ );
345
+
346
+ Log.d(TAG, "TikTok user identified");
347
+ promise.resolve(true);
348
+ } catch (Exception e) {
349
+ Log.e(TAG, "Failed to identify TikTok user", e);
350
+ promise.reject("tiktok_identify_error", "Failed to identify TikTok user: " + e.getMessage());
351
+ }
352
+ }
353
+
354
+ @ReactMethod
355
+ public void logoutTikTok(Promise promise) {
356
+ if (!tiktokInitialized) {
357
+ promise.resolve(false);
358
+ return;
359
+ }
360
+
361
+ try {
362
+ TikTokBusinessSdk.logout();
363
+ Log.d(TAG, "TikTok user logged out");
364
+ promise.resolve(true);
365
+ } catch (Exception e) {
366
+ Log.e(TAG, "Failed to logout TikTok user", e);
367
+ promise.reject("tiktok_logout_error", "Failed to logout TikTok user: " + e.getMessage());
368
+ }
369
+ }
370
+
371
+ @ReactMethod
372
+ public void updateTikTokTrackingAuthorization(boolean enabled, Promise promise) {
373
+ // TikTok SDK handles this automatically on Android
374
+ // No explicit method needed
375
+ Log.d(TAG, "TikTok tracking authorization update requested: " + enabled);
376
+ promise.resolve(true);
377
+ }
378
+
379
+ // ============================================================================
380
+ // SDK Availability
381
+ // ============================================================================
382
+
383
+ @ReactMethod
384
+ public void getSDKAvailability(Promise promise) {
385
+ WritableMap result = Arguments.createMap();
386
+ result.putBoolean("meta", true);
387
+ result.putBoolean("tiktok", true);
388
+ result.putBoolean("playInstallReferrer", true);
389
+ // Apple Search Ads is iOS only
390
+ result.putBoolean("appleSearchAds", false);
391
+ promise.resolve(result);
392
+ }
393
+
394
+ // ============================================================================
395
+ // Helper Methods
396
+ // ============================================================================
397
+
398
+ private Bundle readableMapToBundle(ReadableMap map) {
399
+ Bundle bundle = new Bundle();
400
+ if (map == null) {
401
+ return bundle;
402
+ }
403
+
404
+ ReadableMapKeySetIterator iterator = map.keySetIterator();
405
+ while (iterator.hasNextKey()) {
406
+ String key = iterator.nextKey();
407
+ switch (map.getType(key)) {
408
+ case String:
409
+ bundle.putString(key, map.getString(key));
410
+ break;
411
+ case Number:
412
+ bundle.putDouble(key, map.getDouble(key));
413
+ break;
414
+ case Boolean:
415
+ bundle.putBoolean(key, map.getBoolean(key));
416
+ break;
417
+ default:
418
+ break;
419
+ }
420
+ }
421
+ return bundle;
422
+ }
423
+ }