@datalyr/react-native 1.4.7 → 1.4.9

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/README.md CHANGED
@@ -478,13 +478,25 @@ await Datalyr.initialize({
478
478
  await Datalyr.initialize({
479
479
  apiKey: 'dk_your_api_key',
480
480
  tiktok: {
481
- appId: 'your_app_id',
482
- tiktokAppId: '7123456789',
481
+ appId: 'your_app_id', // Events API App ID
482
+ tiktokAppId: '7123456789', // TikTok App ID (Developer Portal)
483
+ accessToken: 'your_access_token', // Events API Access Token
483
484
  enableAppEvents: true,
484
485
  },
485
486
  });
486
487
  ```
487
488
 
489
+ **Where to find your TikTok credentials:**
490
+
491
+ | Credential | Where to get it |
492
+ |------------|----------------|
493
+ | `tiktokAppId` | [TikTok Developer Portal](https://developers.tiktok.com) → Your App → App ID |
494
+ | `appId` | TikTok Business Center → Assets → Events → Your App → App ID |
495
+ | `accessToken` | TikTok Business Center → Assets → Events → Your App → Settings → Access Token |
496
+
497
+ > **Note:** The `accessToken` enables client-side TikTok SDK features (enhanced attribution, real-time event forwarding). Without it, events are still tracked server-side via Datalyr postbacks — you'll see a warning in debug mode.
498
+ ```
499
+
488
500
  ### Apple Search Ads
489
501
 
490
502
  Attribution for users who install from Apple Search Ads (iOS 14.3+). Automatically fetched on initialization.
@@ -667,12 +679,17 @@ Check status: `Datalyr.getPlatformIntegrationStatus()`
667
679
 
668
680
  ### TikTok SDK Not Working
669
681
 
682
+ 1. Make sure you have all three TikTok credentials (see [TikTok setup](#tiktok))
683
+ 2. The `accessToken` is required for client-side SDK — without it, you'll see a warning but server-side tracking still works
684
+ 3. Check status: `Datalyr.getPlatformIntegrationStatus()`
685
+
670
686
  ```typescript
671
687
  await Datalyr.initialize({
672
688
  apiKey: 'dk_your_api_key',
673
689
  tiktok: {
674
690
  appId: 'your_app_id',
675
691
  tiktokAppId: '7123456789012345',
692
+ accessToken: 'your_access_token',
676
693
  },
677
694
  });
678
695
  ```
@@ -51,4 +51,7 @@ dependencies {
51
51
 
52
52
  // Google Play Install Referrer
53
53
  implementation 'com.android.installreferrer:installreferrer:2.2'
54
+
55
+ // Google Advertising ID (GAID) for attribution
56
+ implementation 'com.google.android.gms:play-services-ads-identifier:18.1.0'
54
57
  }
@@ -26,6 +26,9 @@ import com.tiktok.TikTokBusinessSdk.TTConfig;
26
26
  import com.tiktok.appevents.TikTokAppEvent;
27
27
  import com.tiktok.appevents.TikTokAppEventLogger;
28
28
 
29
+ import com.google.android.gms.ads.identifier.AdvertisingIdClient;
30
+ import com.google.android.gms.common.GooglePlayServicesNotAvailableException;
31
+
29
32
  import java.math.BigDecimal;
30
33
  import java.util.Currency;
31
34
  import java.util.HashMap;
@@ -391,6 +394,47 @@ public class DatalyrNativeModule extends ReactContextBaseJavaModule {
391
394
  promise.resolve(result);
392
395
  }
393
396
 
397
+ // ============================================================================
398
+ // Advertiser Info (GAID on Android)
399
+ // ============================================================================
400
+
401
+ @ReactMethod
402
+ public void getAdvertiserInfo(Promise promise) {
403
+ // GAID must be fetched on a background thread
404
+ new Thread(() -> {
405
+ try {
406
+ WritableMap result = Arguments.createMap();
407
+
408
+ // Fetch Google Advertising ID
409
+ try {
410
+ AdvertisingIdClient.Info adInfo = AdvertisingIdClient.getAdvertisingIdInfo(reactContext.getApplicationContext());
411
+ boolean limitAdTracking = adInfo.isLimitAdTrackingEnabled();
412
+ result.putBoolean("advertiser_tracking_enabled", !limitAdTracking);
413
+ result.putInt("att_status", limitAdTracking ? 2 : 3); // 2=denied, 3=authorized
414
+
415
+ if (!limitAdTracking && adInfo.getId() != null) {
416
+ result.putString("gaid", adInfo.getId());
417
+ }
418
+ } catch (GooglePlayServicesNotAvailableException e) {
419
+ // Google Play Services not available (e.g., Huawei devices)
420
+ result.putInt("att_status", 3);
421
+ result.putBoolean("advertiser_tracking_enabled", true);
422
+ Log.d(TAG, "Google Play Services not available for GAID");
423
+ } catch (Exception e) {
424
+ // Fallback — GAID not available but not blocking
425
+ result.putInt("att_status", 3);
426
+ result.putBoolean("advertiser_tracking_enabled", true);
427
+ Log.d(TAG, "GAID not available: " + e.getMessage());
428
+ }
429
+
430
+ promise.resolve(result);
431
+ } catch (Exception e) {
432
+ Log.e(TAG, "Failed to get advertiser info: " + e.getMessage());
433
+ promise.resolve(null);
434
+ }
435
+ }).start();
436
+ }
437
+
394
438
  // ============================================================================
395
439
  // Helper Methods
396
440
  // ============================================================================
@@ -2,15 +2,20 @@ import ExpoModulesCore
2
2
  import FBSDKCoreKit
3
3
  import TikTokBusinessSDK
4
4
  import AdServices
5
+ import AppTrackingTransparency
6
+ import AdSupport
5
7
 
6
8
  public class DatalyrNativeModule: Module {
9
+ private var tiktokInitialized = false
10
+ private var metaInitialized = false
11
+
7
12
  public func definition() -> ModuleDefinition {
8
13
  Name("DatalyrNative")
9
14
 
10
15
  // MARK: - Meta (Facebook) SDK Methods
11
16
 
12
17
  AsyncFunction("initializeMetaSDK") { (appId: String, clientToken: String?, advertiserTrackingEnabled: Bool, promise: Promise) in
13
- DispatchQueue.main.async {
18
+ DispatchQueue.main.async { [weak self] in
14
19
  Settings.shared.appID = appId
15
20
 
16
21
  if let token = clientToken, !token.isEmpty {
@@ -20,145 +25,246 @@ public class DatalyrNativeModule: Module {
20
25
  Settings.shared.isAdvertiserTrackingEnabled = advertiserTrackingEnabled
21
26
  Settings.shared.isAdvertiserIDCollectionEnabled = advertiserTrackingEnabled
22
27
 
23
- ApplicationDelegate.shared.application(
24
- UIApplication.shared,
25
- didFinishLaunchingWithOptions: nil
26
- )
28
+ var initError: NSError?
29
+ let success = DatalyrObjCExceptionCatcher.tryBlock({
30
+ ApplicationDelegate.shared.application(
31
+ UIApplication.shared,
32
+ didFinishLaunchingWithOptions: nil
33
+ )
34
+ }, error: &initError)
27
35
 
28
- promise.resolve(true)
36
+ if success {
37
+ self?.metaInitialized = true
38
+ promise.resolve(true)
39
+ } else {
40
+ let message = initError?.localizedDescription ?? "Unknown ObjC exception during Meta SDK init"
41
+ promise.reject("meta_init_error", message)
42
+ }
29
43
  }
30
44
  }
31
45
 
32
46
  AsyncFunction("fetchDeferredAppLink") { (promise: Promise) in
33
- AppLinkUtility.fetchDeferredAppLink { url, error in
34
- if error != nil {
35
- promise.resolve(nil)
36
- return
37
- }
47
+ DispatchQueue.main.async {
48
+ AppLinkUtility.fetchDeferredAppLink { url, error in
49
+ if error != nil {
50
+ promise.resolve(nil)
51
+ return
52
+ }
38
53
 
39
- if let url = url {
40
- promise.resolve(url.absoluteString)
41
- } else {
42
- promise.resolve(nil)
54
+ if let url = url {
55
+ promise.resolve(url.absoluteString)
56
+ } else {
57
+ promise.resolve(nil)
58
+ }
43
59
  }
44
60
  }
45
61
  }
46
62
 
47
63
  AsyncFunction("logMetaEvent") { (eventName: String, valueToSum: Double?, parameters: [String: Any]?, promise: Promise) in
48
- var params: [AppEvents.ParameterName: Any] = [:]
64
+ guard self.metaInitialized else {
65
+ promise.reject("meta_not_initialized", "Meta SDK not initialized. Call initializeMetaSDK first.")
66
+ return
67
+ }
68
+
69
+ DispatchQueue.main.async {
70
+ var params: [AppEvents.ParameterName: Any] = [:]
49
71
 
50
- if let dict = parameters {
51
- for (key, value) in dict {
52
- params[AppEvents.ParameterName(key)] = value
72
+ if let dict = parameters {
73
+ for (key, value) in dict {
74
+ params[AppEvents.ParameterName(key)] = value
75
+ }
53
76
  }
54
- }
55
77
 
56
- if let value = valueToSum {
57
- AppEvents.shared.logEvent(AppEvents.Name(eventName), valueToSum: value, parameters: params)
58
- } else if params.isEmpty {
59
- AppEvents.shared.logEvent(AppEvents.Name(eventName))
60
- } else {
61
- AppEvents.shared.logEvent(AppEvents.Name(eventName), parameters: params)
62
- }
78
+ var logError: NSError?
79
+ DatalyrObjCExceptionCatcher.tryBlock({
80
+ if let value = valueToSum {
81
+ AppEvents.shared.logEvent(AppEvents.Name(eventName), valueToSum: value, parameters: params)
82
+ } else if params.isEmpty {
83
+ AppEvents.shared.logEvent(AppEvents.Name(eventName))
84
+ } else {
85
+ AppEvents.shared.logEvent(AppEvents.Name(eventName), parameters: params)
86
+ }
87
+ }, error: &logError)
63
88
 
64
- promise.resolve(true)
89
+ if let logError = logError {
90
+ promise.reject("meta_event_error", logError.localizedDescription)
91
+ } else {
92
+ promise.resolve(true)
93
+ }
94
+ }
65
95
  }
66
96
 
67
97
  AsyncFunction("logMetaPurchase") { (amount: Double, currency: String, parameters: [String: Any]?, promise: Promise) in
68
- var params: [AppEvents.ParameterName: Any] = [:]
98
+ guard self.metaInitialized else {
99
+ promise.reject("meta_not_initialized", "Meta SDK not initialized. Call initializeMetaSDK first.")
100
+ return
101
+ }
102
+
103
+ DispatchQueue.main.async {
104
+ var params: [AppEvents.ParameterName: Any] = [:]
69
105
 
70
- if let dict = parameters {
71
- for (key, value) in dict {
72
- params[AppEvents.ParameterName(key)] = value
106
+ if let dict = parameters {
107
+ for (key, value) in dict {
108
+ params[AppEvents.ParameterName(key)] = value
109
+ }
73
110
  }
74
- }
75
111
 
76
- AppEvents.shared.logPurchase(amount: amount, currency: currency, parameters: params)
77
- promise.resolve(true)
112
+ var logError: NSError?
113
+ DatalyrObjCExceptionCatcher.tryBlock({
114
+ AppEvents.shared.logPurchase(amount: amount, currency: currency, parameters: params)
115
+ }, error: &logError)
116
+
117
+ if let logError = logError {
118
+ promise.reject("meta_event_error", logError.localizedDescription)
119
+ } else {
120
+ promise.resolve(true)
121
+ }
122
+ }
78
123
  }
79
124
 
80
125
  AsyncFunction("setMetaUserData") { (userData: [String: Any], promise: Promise) in
81
- AppEvents.shared.setUserData(userData["email"] as? String, forType: .email)
82
- AppEvents.shared.setUserData(userData["firstName"] as? String, forType: .firstName)
83
- AppEvents.shared.setUserData(userData["lastName"] as? String, forType: .lastName)
84
- AppEvents.shared.setUserData(userData["phone"] as? String, forType: .phone)
85
- AppEvents.shared.setUserData(userData["dateOfBirth"] as? String, forType: .dateOfBirth)
86
- AppEvents.shared.setUserData(userData["gender"] as? String, forType: .gender)
87
- AppEvents.shared.setUserData(userData["city"] as? String, forType: .city)
88
- AppEvents.shared.setUserData(userData["state"] as? String, forType: .state)
89
- AppEvents.shared.setUserData(userData["zip"] as? String, forType: .zip)
90
- AppEvents.shared.setUserData(userData["country"] as? String, forType: .country)
126
+ guard self.metaInitialized else {
127
+ promise.reject("meta_not_initialized", "Meta SDK not initialized. Call initializeMetaSDK first.")
128
+ return
129
+ }
91
130
 
92
- promise.resolve(true)
131
+ DispatchQueue.main.async {
132
+ if let email = userData["email"] as? String { AppEvents.shared.setUserData(email, forType: .email) }
133
+ if let firstName = userData["firstName"] as? String { AppEvents.shared.setUserData(firstName, forType: .firstName) }
134
+ if let lastName = userData["lastName"] as? String { AppEvents.shared.setUserData(lastName, forType: .lastName) }
135
+ if let phone = userData["phone"] as? String { AppEvents.shared.setUserData(phone, forType: .phone) }
136
+ if let dateOfBirth = userData["dateOfBirth"] as? String { AppEvents.shared.setUserData(dateOfBirth, forType: .dateOfBirth) }
137
+ if let gender = userData["gender"] as? String { AppEvents.shared.setUserData(gender, forType: .gender) }
138
+ if let city = userData["city"] as? String { AppEvents.shared.setUserData(city, forType: .city) }
139
+ if let state = userData["state"] as? String { AppEvents.shared.setUserData(state, forType: .state) }
140
+ if let zip = userData["zip"] as? String { AppEvents.shared.setUserData(zip, forType: .zip) }
141
+ if let country = userData["country"] as? String { AppEvents.shared.setUserData(country, forType: .country) }
142
+
143
+ promise.resolve(true)
144
+ }
93
145
  }
94
146
 
95
147
  AsyncFunction("clearMetaUserData") { (promise: Promise) in
96
- AppEvents.shared.clearUserData()
97
- promise.resolve(true)
148
+ guard self.metaInitialized else {
149
+ promise.reject("meta_not_initialized", "Meta SDK not initialized. Call initializeMetaSDK first.")
150
+ return
151
+ }
152
+
153
+ DispatchQueue.main.async {
154
+ AppEvents.shared.clearUserData()
155
+ promise.resolve(true)
156
+ }
98
157
  }
99
158
 
100
159
  AsyncFunction("updateMetaTrackingAuthorization") { (enabled: Bool, promise: Promise) in
101
- Settings.shared.isAdvertiserTrackingEnabled = enabled
102
- Settings.shared.isAdvertiserIDCollectionEnabled = enabled
103
- promise.resolve(true)
160
+ guard self.metaInitialized else {
161
+ promise.reject("meta_not_initialized", "Meta SDK not initialized. Call initializeMetaSDK first.")
162
+ return
163
+ }
164
+
165
+ DispatchQueue.main.async {
166
+ Settings.shared.isAdvertiserTrackingEnabled = enabled
167
+ Settings.shared.isAdvertiserIDCollectionEnabled = enabled
168
+ promise.resolve(true)
169
+ }
104
170
  }
105
171
 
106
172
  // MARK: - TikTok SDK Methods
107
173
 
108
174
  AsyncFunction("initializeTikTokSDK") { (appId: String, tiktokAppId: String, accessToken: String?, debug: Bool, promise: Promise) in
109
- DispatchQueue.main.async {
110
- let config: TikTokConfig?
111
- if let token = accessToken, !token.isEmpty {
112
- config = TikTokConfig(accessToken: token, appId: appId, tiktokAppId: tiktokAppId)
113
- } else {
114
- config = TikTokConfig(appId: appId, tiktokAppId: tiktokAppId)
175
+ DispatchQueue.main.async { [weak self] in
176
+ guard let token = accessToken, !token.isEmpty else {
177
+ promise.reject("tiktok_init_error", "TikTok accessToken is required. The deprecated init without accessToken has been removed.")
178
+ return
115
179
  }
116
180
 
181
+ let config = TikTokConfig(accessToken: token, appId: appId, tiktokAppId: tiktokAppId)
182
+
117
183
  if debug {
118
184
  config?.setLogLevel(TikTokLogLevelDebug)
119
185
  }
120
186
 
121
- if let validConfig = config {
187
+ guard let validConfig = config else {
188
+ promise.reject("tiktok_init_error", "Failed to create TikTok config")
189
+ return
190
+ }
191
+
192
+ var initError: NSError?
193
+ let success = DatalyrObjCExceptionCatcher.tryBlock({
122
194
  TikTokBusiness.initializeSdk(validConfig)
195
+ }, error: &initError)
196
+
197
+ if success {
198
+ self?.tiktokInitialized = true
123
199
  promise.resolve(true)
124
200
  } else {
125
- promise.reject("tiktok_init_error", "Failed to create TikTok config")
201
+ let message = initError?.localizedDescription ?? "Unknown ObjC exception during TikTok SDK init"
202
+ promise.reject("tiktok_init_error", message)
126
203
  }
127
204
  }
128
205
  }
129
206
 
130
207
  AsyncFunction("trackTikTokEvent") { (eventName: String, eventId: String?, properties: [String: Any]?, promise: Promise) in
131
- let event: TikTokBaseEvent
132
-
133
- if let eid = eventId, !eid.isEmpty {
134
- event = TikTokBaseEvent(eventName: eventName, eventId: eid)
135
- } else {
136
- event = TikTokBaseEvent(eventName: eventName)
208
+ guard self.tiktokInitialized else {
209
+ promise.reject("tiktok_not_initialized", "TikTok SDK not initialized. Call initializeTikTokSDK first.")
210
+ return
137
211
  }
138
212
 
139
- if let dict = properties {
140
- for (key, value) in dict {
141
- event.addProperty(withKey: key, value: value)
213
+ DispatchQueue.main.async {
214
+ let event: TikTokBaseEvent
215
+
216
+ if let eid = eventId, !eid.isEmpty {
217
+ event = TikTokBaseEvent(eventName: eventName, eventId: eid)
218
+ } else {
219
+ event = TikTokBaseEvent(eventName: eventName)
142
220
  }
143
- }
144
221
 
145
- TikTokBusiness.trackTTEvent(event)
146
- promise.resolve(true)
222
+ if let dict = properties {
223
+ for (key, value) in dict {
224
+ event.addProperty(withKey: key, value: value)
225
+ }
226
+ }
227
+
228
+ var trackError: NSError?
229
+ DatalyrObjCExceptionCatcher.tryBlock({
230
+ TikTokBusiness.trackTTEvent(event)
231
+ }, error: &trackError)
232
+
233
+ if let trackError = trackError {
234
+ promise.reject("tiktok_event_error", trackError.localizedDescription)
235
+ } else {
236
+ promise.resolve(true)
237
+ }
238
+ }
147
239
  }
148
240
 
149
241
  AsyncFunction("identifyTikTokUser") { (externalId: String, externalUserName: String, phoneNumber: String, email: String, promise: Promise) in
150
- TikTokBusiness.identify(
151
- withExternalID: externalId.isEmpty ? nil : externalId,
152
- externalUserName: externalUserName.isEmpty ? nil : externalUserName,
153
- phoneNumber: phoneNumber.isEmpty ? nil : phoneNumber,
154
- email: email.isEmpty ? nil : email
155
- )
156
- promise.resolve(true)
242
+ guard self.tiktokInitialized else {
243
+ promise.reject("tiktok_not_initialized", "TikTok SDK not initialized. Call initializeTikTokSDK first.")
244
+ return
245
+ }
246
+
247
+ DispatchQueue.main.async {
248
+ TikTokBusiness.identify(
249
+ withExternalID: externalId.isEmpty ? nil : externalId,
250
+ externalUserName: externalUserName.isEmpty ? nil : externalUserName,
251
+ phoneNumber: phoneNumber.isEmpty ? nil : phoneNumber,
252
+ email: email.isEmpty ? nil : email
253
+ )
254
+ promise.resolve(true)
255
+ }
157
256
  }
158
257
 
159
258
  AsyncFunction("logoutTikTok") { (promise: Promise) in
160
- TikTokBusiness.logout()
161
- promise.resolve(true)
259
+ guard self.tiktokInitialized else {
260
+ promise.reject("tiktok_not_initialized", "TikTok SDK not initialized. Call initializeTikTokSDK first.")
261
+ return
262
+ }
263
+
264
+ DispatchQueue.main.async {
265
+ TikTokBusiness.logout()
266
+ promise.resolve(true)
267
+ }
162
268
  }
163
269
 
164
270
  AsyncFunction("updateTikTokTrackingAuthorization") { (enabled: Bool, promise: Promise) in
@@ -176,6 +282,44 @@ public class DatalyrNativeModule: Module {
176
282
  ])
177
283
  }
178
284
 
285
+ // MARK: - Advertiser Info (IDFA, IDFV, ATT Status)
286
+
287
+ AsyncFunction("getAdvertiserInfo") { (promise: Promise) in
288
+ var result: [String: Any] = [:]
289
+
290
+ // IDFV is always available
291
+ if let idfv = UIDevice.current.identifierForVendor?.uuidString {
292
+ result["idfv"] = idfv
293
+ }
294
+
295
+ // ATT status
296
+ if #available(iOS 14, *) {
297
+ let status = ATTrackingManager.trackingAuthorizationStatus
298
+ result["att_status"] = status.rawValue
299
+ result["advertiser_tracking_enabled"] = status == .authorized
300
+
301
+ // IDFA only if ATT authorized
302
+ if status == .authorized {
303
+ let idfa = ASIdentifierManager.shared().advertisingIdentifier.uuidString
304
+ let zeroUUID = "00000000-0000-0000-0000-000000000000"
305
+ if idfa != zeroUUID {
306
+ result["idfa"] = idfa
307
+ }
308
+ }
309
+ } else {
310
+ // Pre-iOS 14, tracking allowed by default
311
+ result["att_status"] = 3 // .authorized equivalent
312
+ result["advertiser_tracking_enabled"] = true
313
+ let idfa = ASIdentifierManager.shared().advertisingIdentifier.uuidString
314
+ let zeroUUID = "00000000-0000-0000-0000-000000000000"
315
+ if idfa != zeroUUID {
316
+ result["idfa"] = idfa
317
+ }
318
+ }
319
+
320
+ promise.resolve(result)
321
+ }
322
+
179
323
  // MARK: - Apple Search Ads Attribution
180
324
 
181
325
  AsyncFunction("getAppleSearchAdsAttribution") { (promise: Promise) in
@@ -0,0 +1,14 @@
1
+ #import <Foundation/Foundation.h>
2
+
3
+ NS_ASSUME_NONNULL_BEGIN
4
+
5
+ /// Catches ObjC NSExceptions (from Meta/TikTok SDKs) and converts them to NSErrors.
6
+ /// Swift's do/try/catch cannot catch NSExceptions — they propagate through Hermes
7
+ /// and cause EXC_BAD_ACCESS (SIGSEGV) crashes.
8
+ @interface DatalyrObjCExceptionCatcher : NSObject
9
+
10
+ + (BOOL)tryBlock:(void(NS_NOESCAPE ^)(void))block error:(NSError *_Nullable *_Nullable)error;
11
+
12
+ @end
13
+
14
+ NS_ASSUME_NONNULL_END
@@ -0,0 +1,30 @@
1
+ #import "DatalyrObjCExceptionCatcher.h"
2
+
3
+ @implementation DatalyrObjCExceptionCatcher
4
+
5
+ + (BOOL)tryBlock:(void(NS_NOESCAPE ^)(void))block error:(NSError **)error {
6
+ @try {
7
+ block();
8
+ return YES;
9
+ }
10
+ @catch (NSException *exception) {
11
+ if (error) {
12
+ NSString *description = exception.reason ?: exception.name;
13
+ NSDictionary *userInfo = @{
14
+ NSLocalizedDescriptionKey: description,
15
+ @"ExceptionName": exception.name ?: @"Unknown",
16
+ };
17
+ if (exception.userInfo) {
18
+ NSMutableDictionary *merged = [userInfo mutableCopy];
19
+ [merged addEntriesFromDictionary:exception.userInfo];
20
+ userInfo = merged;
21
+ }
22
+ *error = [NSError errorWithDomain:@"com.datalyr.objc-exception"
23
+ code:-1
24
+ userInfo:userInfo];
25
+ }
26
+ return NO;
27
+ }
28
+ }
29
+
30
+ @end
@@ -9,6 +9,7 @@ export declare class DatalyrSDK {
9
9
  private autoEventsManager;
10
10
  private appStateSubscription;
11
11
  private networkStatusUnsubscribe;
12
+ private cachedAdvertiserInfo;
12
13
  private static conversionEncoder?;
13
14
  private static debugEnabled;
14
15
  constructor();
@@ -8,12 +8,14 @@ import { AutoEventsManager } from './auto-events';
8
8
  import { ConversionValueEncoder, ConversionTemplates } from './ConversionValueEncoder';
9
9
  import { SKAdNetworkBridge } from './native/SKAdNetworkBridge';
10
10
  import { metaIntegration, tiktokIntegration, appleSearchAdsIntegration, playInstallReferrerIntegration } from './integrations';
11
+ import { AdvertiserInfoBridge } from './native/DatalyrNativeBridge';
11
12
  import { networkStatusManager } from './network-status';
12
13
  export class DatalyrSDK {
13
14
  constructor() {
14
15
  this.autoEventsManager = null;
15
16
  this.appStateSubscription = null;
16
17
  this.networkStatusUnsubscribe = null;
18
+ this.cachedAdvertiserInfo = null;
17
19
  // Initialize state with defaults
18
20
  this.state = {
19
21
  initialized: false,
@@ -26,9 +28,11 @@ export class DatalyrSDK {
26
28
  maxRetries: 3,
27
29
  retryDelay: 1000,
28
30
  batchSize: 10,
29
- flushInterval: 10000,
31
+ flushInterval: 30000,
30
32
  maxQueueSize: 100,
31
33
  respectDoNotTrack: true,
34
+ enableAutoEvents: true,
35
+ enableAttribution: true,
32
36
  },
33
37
  visitorId: '',
34
38
  anonymousId: '', // Persistent anonymous identifier
@@ -166,6 +170,13 @@ export class DatalyrSDK {
166
170
  }
167
171
  // Wait for all platform integrations to complete
168
172
  await Promise.all(platformInitPromises);
173
+ // Cache advertiser info (IDFA/GAID, ATT status) once at init to avoid per-event native bridge calls
174
+ try {
175
+ this.cachedAdvertiserInfo = await AdvertiserInfoBridge.getAdvertiserInfo();
176
+ }
177
+ catch (error) {
178
+ errorLog('Failed to cache advertiser info:', error);
179
+ }
169
180
  debugLog('Platform integrations initialized', {
170
181
  meta: metaIntegration.isAvailable(),
171
182
  tiktok: tiktokIntegration.isAvailable(),
@@ -179,7 +190,7 @@ export class DatalyrSDK {
179
190
  const installData = await attributionManager.trackInstall();
180
191
  await this.track('app_install', {
181
192
  platform: Platform.OS === 'ios' || Platform.OS === 'android' ? Platform.OS : 'android',
182
- sdk_version: '1.0.2',
193
+ sdk_version: '1.4.9',
183
194
  ...installData,
184
195
  });
185
196
  }
@@ -760,6 +771,13 @@ export class DatalyrSDK {
760
771
  }
761
772
  metaIntegration.updateTrackingAuthorization(enabled);
762
773
  tiktokIntegration.updateTrackingAuthorization(enabled);
774
+ // Refresh cached advertiser info after ATT status change
775
+ try {
776
+ this.cachedAdvertiserInfo = await AdvertiserInfoBridge.getAdvertiserInfo();
777
+ }
778
+ catch (error) {
779
+ errorLog('Failed to refresh advertiser info:', error);
780
+ }
763
781
  // Track ATT status event
764
782
  await this.track('$att_status', {
765
783
  authorized: enabled,
@@ -835,6 +853,8 @@ export class DatalyrSDK {
835
853
  asa_conversion_type: asaAttribution.conversionType,
836
854
  asa_country_or_region: asaAttribution.countryOrRegion,
837
855
  } : {};
856
+ // Use cached advertiser info (IDFA/GAID, ATT status) — cached at init, refreshed on ATT change
857
+ const advertiserInfo = this.cachedAdvertiserInfo;
838
858
  const payload = {
839
859
  workspaceId: this.state.config.workspaceId || 'mobile_sdk',
840
860
  visitorId: this.state.visitorId,
@@ -852,8 +872,24 @@ export class DatalyrSDK {
852
872
  device_model: deviceInfo.model,
853
873
  app_version: deviceInfo.appVersion,
854
874
  app_build: deviceInfo.buildNumber,
875
+ app_name: deviceInfo.bundleId, // Best available app name
876
+ app_namespace: deviceInfo.bundleId,
877
+ screen_width: deviceInfo.screenWidth,
878
+ screen_height: deviceInfo.screenHeight,
879
+ locale: deviceInfo.locale,
880
+ timezone: deviceInfo.timezone,
881
+ carrier: deviceInfo.carrier,
855
882
  network_type: getNetworkType(),
856
883
  timestamp: Date.now(),
884
+ sdk_version: '1.4.9',
885
+ // Advertiser data (IDFA/GAID, ATT status) for Meta CAPI / TikTok Events API
886
+ ...(advertiserInfo ? {
887
+ idfa: advertiserInfo.idfa,
888
+ idfv: advertiserInfo.idfv,
889
+ gaid: advertiserInfo.gaid,
890
+ att_status: advertiserInfo.att_status,
891
+ advertiser_tracking_enabled: advertiserInfo.advertiser_tracking_enabled,
892
+ } : {}),
857
893
  // Attribution data
858
894
  ...attributionData,
859
895
  // Apple Search Ads attribution
@@ -216,7 +216,7 @@ export const createEventQueue = (httpClient, config) => {
216
216
  const defaultConfig = {
217
217
  maxQueueSize: 100,
218
218
  batchSize: 10,
219
- flushInterval: 10000, // 10 seconds
219
+ flushInterval: 30000, // 30 seconds — matches SDK constructor defaults and docs
220
220
  maxRetryCount: 3,
221
221
  };
222
222
  return new EventQueue(httpClient, { ...defaultConfig, ...config });