@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.
- package/CHANGELOG.md +19 -0
- package/README.md +145 -9
- package/android/build.gradle +54 -0
- package/android/src/main/AndroidManifest.xml +14 -0
- package/android/src/main/java/com/datalyr/reactnative/DatalyrNativeModule.java +423 -0
- package/android/src/main/java/com/datalyr/reactnative/DatalyrPackage.java +30 -0
- package/android/src/main/java/com/datalyr/reactnative/DatalyrPlayInstallReferrerModule.java +229 -0
- package/datalyr-react-native.podspec +2 -2
- package/ios/DatalyrSKAdNetwork.m +400 -1
- package/ios/PrivacyInfo.xcprivacy +48 -0
- package/lib/ConversionValueEncoder.d.ts +13 -1
- package/lib/ConversionValueEncoder.js +57 -23
- package/lib/datalyr-sdk.d.ts +31 -2
- package/lib/datalyr-sdk.js +138 -30
- package/lib/index.d.ts +5 -1
- package/lib/index.js +4 -1
- package/lib/integrations/index.d.ts +3 -1
- package/lib/integrations/index.js +2 -1
- package/lib/integrations/meta-integration.d.ts +1 -0
- package/lib/integrations/meta-integration.js +4 -3
- package/lib/integrations/play-install-referrer.d.ts +78 -0
- package/lib/integrations/play-install-referrer.js +166 -0
- package/lib/integrations/tiktok-integration.d.ts +1 -0
- package/lib/integrations/tiktok-integration.js +4 -3
- package/lib/journey.d.ts +106 -0
- package/lib/journey.js +258 -0
- package/lib/native/DatalyrNativeBridge.d.ts +42 -3
- package/lib/native/DatalyrNativeBridge.js +63 -9
- package/lib/native/SKAdNetworkBridge.d.ts +142 -0
- package/lib/native/SKAdNetworkBridge.js +328 -0
- package/lib/network-status.d.ts +84 -0
- package/lib/network-status.js +281 -0
- package/lib/types.d.ts +51 -0
- package/lib/utils.d.ts +6 -1
- package/lib/utils.js +52 -2
- package/package.json +13 -4
- package/src/ConversionValueEncoder.ts +67 -26
- package/src/datalyr-sdk-expo.ts +55 -6
- package/src/datalyr-sdk.ts +161 -38
- package/src/expo.ts +4 -0
- package/src/index.ts +7 -1
- package/src/integrations/index.ts +3 -1
- package/src/integrations/meta-integration.ts +4 -3
- package/src/integrations/play-install-referrer.ts +218 -0
- package/src/integrations/tiktok-integration.ts +4 -3
- package/src/journey.ts +338 -0
- package/src/native/DatalyrNativeBridge.ts +99 -13
- package/src/native/SKAdNetworkBridge.ts +481 -2
- package/src/network-status.ts +312 -0
- package/src/types.ts +74 -6
- 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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
707
|
+
### Debug Logging
|
|
572
708
|
|
|
573
|
-
|
|
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
|
+
}
|