@datalyr/react-native 1.3.1 → 1.4.0

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.
@@ -19,7 +19,7 @@ Pod::Spec.new do |s|
19
19
  s.source_files = "ios/**/*.{h,m,swift}"
20
20
  s.swift_version = "5.0"
21
21
 
22
- s.dependency "React-Core"
22
+ s.dependency "ExpoModulesCore"
23
23
  s.dependency "FBSDKCoreKit", "~> 18.0"
24
24
  s.dependency "TikTokBusinessSDK", "~> 1.6"
25
25
 
@@ -0,0 +1,6 @@
1
+ {
2
+ "platforms": ["ios"],
3
+ "ios": {
4
+ "modules": ["DatalyrNativeModule", "DatalyrSKAdNetworkModule"]
5
+ }
6
+ }
@@ -0,0 +1,221 @@
1
+ import ExpoModulesCore
2
+ import FBSDKCoreKit
3
+ import TikTokBusinessSDK
4
+ import AdServices
5
+
6
+ public class DatalyrNativeModule: Module {
7
+ public func definition() -> ModuleDefinition {
8
+ Name("DatalyrNative")
9
+
10
+ // MARK: - Meta (Facebook) SDK Methods
11
+
12
+ AsyncFunction("initializeMetaSDK") { (appId: String, clientToken: String?, advertiserTrackingEnabled: Bool, promise: Promise) in
13
+ DispatchQueue.main.async {
14
+ Settings.shared.appID = appId
15
+
16
+ if let token = clientToken, !token.isEmpty {
17
+ Settings.shared.clientToken = token
18
+ }
19
+
20
+ Settings.shared.isAdvertiserTrackingEnabled = advertiserTrackingEnabled
21
+ Settings.shared.isAdvertiserIDCollectionEnabled = advertiserTrackingEnabled
22
+
23
+ ApplicationDelegate.shared.application(
24
+ UIApplication.shared,
25
+ didFinishLaunchingWithOptions: nil
26
+ )
27
+
28
+ promise.resolve(true)
29
+ }
30
+ }
31
+
32
+ AsyncFunction("fetchDeferredAppLink") { (promise: Promise) in
33
+ AppLinkUtility.fetchDeferredAppLink { url, error in
34
+ if error != nil {
35
+ promise.resolve(nil)
36
+ return
37
+ }
38
+
39
+ if let url = url {
40
+ promise.resolve(url.absoluteString)
41
+ } else {
42
+ promise.resolve(nil)
43
+ }
44
+ }
45
+ }
46
+
47
+ AsyncFunction("logMetaEvent") { (eventName: String, valueToSum: Double?, parameters: [String: Any]?, promise: Promise) in
48
+ var params: [AppEvents.ParameterName: Any] = [:]
49
+
50
+ if let dict = parameters {
51
+ for (key, value) in dict {
52
+ params[AppEvents.ParameterName(key)] = value
53
+ }
54
+ }
55
+
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
+ }
63
+
64
+ promise.resolve(true)
65
+ }
66
+
67
+ AsyncFunction("logMetaPurchase") { (amount: Double, currency: String, parameters: [String: Any]?, promise: Promise) in
68
+ var params: [AppEvents.ParameterName: Any] = [:]
69
+
70
+ if let dict = parameters {
71
+ for (key, value) in dict {
72
+ params[AppEvents.ParameterName(key)] = value
73
+ }
74
+ }
75
+
76
+ AppEvents.shared.logPurchase(amount: amount, currency: currency, parameters: params)
77
+ promise.resolve(true)
78
+ }
79
+
80
+ 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)
91
+
92
+ promise.resolve(true)
93
+ }
94
+
95
+ AsyncFunction("clearMetaUserData") { (promise: Promise) in
96
+ AppEvents.shared.clearUserData()
97
+ promise.resolve(true)
98
+ }
99
+
100
+ AsyncFunction("updateMetaTrackingAuthorization") { (enabled: Bool, promise: Promise) in
101
+ Settings.shared.isAdvertiserTrackingEnabled = enabled
102
+ Settings.shared.isAdvertiserIDCollectionEnabled = enabled
103
+ promise.resolve(true)
104
+ }
105
+
106
+ // MARK: - TikTok SDK Methods
107
+
108
+ AsyncFunction("initializeTikTokSDK") { (appId: String, tiktokAppId: String, accessToken: String?, debug: Bool, promise: Promise) in
109
+ DispatchQueue.main.async {
110
+ let config = TikTokConfig(appId: appId, tiktokAppId: tiktokAppId)
111
+
112
+ if let token = accessToken, !token.isEmpty {
113
+ config?.accessToken = token
114
+ }
115
+
116
+ if debug {
117
+ config?.setLogLevel(.debug)
118
+ }
119
+
120
+ if let validConfig = config {
121
+ TikTokBusiness.initializeSdk(validConfig)
122
+ promise.resolve(true)
123
+ } else {
124
+ promise.reject("tiktok_init_error", "Failed to create TikTok config")
125
+ }
126
+ }
127
+ }
128
+
129
+ AsyncFunction("trackTikTokEvent") { (eventName: String, eventId: String?, properties: [String: Any]?, promise: Promise) in
130
+ let event: TikTokBaseEvent
131
+
132
+ if let eid = eventId, !eid.isEmpty {
133
+ event = TikTokBaseEvent(eventName: eventName, eventId: eid)
134
+ } else {
135
+ event = TikTokBaseEvent(eventName: eventName)
136
+ }
137
+
138
+ if let dict = properties {
139
+ for (key, value) in dict {
140
+ event.addProperty(withKey: key, value: value)
141
+ }
142
+ }
143
+
144
+ TikTokBusiness.trackTTEvent(event)
145
+ promise.resolve(true)
146
+ }
147
+
148
+ AsyncFunction("identifyTikTokUser") { (externalId: String, externalUserName: String, phoneNumber: String, email: String, promise: Promise) in
149
+ TikTokBusiness.identify(
150
+ withExternalID: externalId.isEmpty ? nil : externalId,
151
+ externalUserName: externalUserName.isEmpty ? nil : externalUserName,
152
+ phoneNumber: phoneNumber.isEmpty ? nil : phoneNumber,
153
+ email: email.isEmpty ? nil : email
154
+ )
155
+ promise.resolve(true)
156
+ }
157
+
158
+ AsyncFunction("logoutTikTok") { (promise: Promise) in
159
+ TikTokBusiness.logout()
160
+ promise.resolve(true)
161
+ }
162
+
163
+ AsyncFunction("updateTikTokTrackingAuthorization") { (enabled: Bool, promise: Promise) in
164
+ // TikTok SDK handles ATT automatically, but we track the change
165
+ promise.resolve(true)
166
+ }
167
+
168
+ // MARK: - SDK Availability Check
169
+
170
+ AsyncFunction("getSDKAvailability") { (promise: Promise) in
171
+ promise.resolve([
172
+ "meta": true,
173
+ "tiktok": true,
174
+ "appleSearchAds": true
175
+ ])
176
+ }
177
+
178
+ // MARK: - Apple Search Ads Attribution
179
+
180
+ AsyncFunction("getAppleSearchAdsAttribution") { (promise: Promise) in
181
+ if #available(iOS 14.3, *) {
182
+ do {
183
+ let token = try AAAttribution.attributionToken()
184
+
185
+ var request = URLRequest(url: URL(string: "https://api-adservices.apple.com/api/v1/")!)
186
+ request.httpMethod = "POST"
187
+ request.setValue("text/plain", forHTTPHeaderField: "Content-Type")
188
+ request.httpBody = token.data(using: .utf8)
189
+
190
+ let task = URLSession.shared.dataTask(with: request) { data, response, error in
191
+ if error != nil {
192
+ promise.resolve(nil)
193
+ return
194
+ }
195
+
196
+ guard let data = data else {
197
+ promise.resolve(nil)
198
+ return
199
+ }
200
+
201
+ do {
202
+ if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] {
203
+ promise.resolve(json)
204
+ } else {
205
+ promise.resolve(nil)
206
+ }
207
+ } catch {
208
+ promise.resolve(nil)
209
+ }
210
+ }
211
+ task.resume()
212
+
213
+ } catch {
214
+ promise.resolve(nil)
215
+ }
216
+ } else {
217
+ promise.resolve(nil)
218
+ }
219
+ }
220
+ }
221
+ }
@@ -0,0 +1,333 @@
1
+ import ExpoModulesCore
2
+ import StoreKit
3
+
4
+ public class DatalyrSKAdNetworkModule: Module {
5
+ public func definition() -> ModuleDefinition {
6
+ Name("DatalyrSKAdNetwork")
7
+
8
+ // SKAN 3.0 - Legacy method for iOS 14.0-16.0
9
+ AsyncFunction("updateConversionValue") { (value: Int, promise: Promise) in
10
+ if #available(iOS 14.0, *) {
11
+ SKAdNetwork.updateConversionValue(value)
12
+ promise.resolve(true)
13
+ } else {
14
+ promise.reject("ios_version_error", "SKAdNetwork requires iOS 14.0+")
15
+ }
16
+ }
17
+
18
+ // SKAN 4.0 / AdAttributionKit - Method for iOS 16.1+ with coarse value and lock window support
19
+ // On iOS 17.4+, this uses AdAttributionKit under the hood
20
+ AsyncFunction("updatePostbackConversionValue") { (fineValue: Int, coarseValue: String, lockWindow: Bool, promise: Promise) in
21
+ guard fineValue >= 0 && fineValue <= 63 else {
22
+ promise.reject("invalid_value", "Conversion value must be between 0 and 63")
23
+ return
24
+ }
25
+
26
+ if #available(iOS 16.1, *) {
27
+ let coarse = Self.mapCoarseValue(coarseValue)
28
+
29
+ SKAdNetwork.updatePostbackConversionValue(fineValue, coarseValue: coarse, lockWindow: lockWindow) { error in
30
+ if let error = error {
31
+ promise.reject("skadnetwork_error", error.localizedDescription)
32
+ } else {
33
+ var framework = "SKAdNetwork"
34
+ if #available(iOS 17.4, *) {
35
+ framework = "AdAttributionKit"
36
+ }
37
+ promise.resolve([
38
+ "success": true,
39
+ "framework": framework,
40
+ "fineValue": fineValue,
41
+ "coarseValue": coarseValue,
42
+ "lockWindow": lockWindow
43
+ ] as [String: Any])
44
+ }
45
+ }
46
+ } else if #available(iOS 14.0, *) {
47
+ // Fallback to SKAN 3.0 for iOS 14.0-16.0
48
+ SKAdNetwork.updateConversionValue(fineValue)
49
+ promise.resolve([
50
+ "success": true,
51
+ "framework": "SKAdNetwork",
52
+ "fineValue": fineValue,
53
+ "coarseValue": "n/a",
54
+ "lockWindow": false
55
+ ] as [String: Any])
56
+ } else {
57
+ promise.reject("ios_version_error", "SKAdNetwork requires iOS 14.0+")
58
+ }
59
+ }
60
+
61
+ // Check if SKAN 4.0 is available (iOS 16.1+)
62
+ AsyncFunction("isSKAN4Available") { (promise: Promise) in
63
+ if #available(iOS 16.1, *) {
64
+ promise.resolve(true)
65
+ } else {
66
+ promise.resolve(false)
67
+ }
68
+ }
69
+
70
+ // Check if AdAttributionKit is available (iOS 17.4+)
71
+ AsyncFunction("isAdAttributionKitAvailable") { (promise: Promise) in
72
+ if #available(iOS 17.4, *) {
73
+ promise.resolve(true)
74
+ } else {
75
+ promise.resolve(false)
76
+ }
77
+ }
78
+
79
+ // Check if overlapping windows are available (iOS 18.4+)
80
+ AsyncFunction("isOverlappingWindowsAvailable") { (promise: Promise) in
81
+ if #available(iOS 18.4, *) {
82
+ promise.resolve(true)
83
+ } else {
84
+ promise.resolve(false)
85
+ }
86
+ }
87
+
88
+ // Register for ad network attribution (supports both AdAttributionKit and SKAdNetwork)
89
+ AsyncFunction("registerForAttribution") { (promise: Promise) in
90
+ if #available(iOS 17.4, *) {
91
+ SKAdNetwork.updatePostbackConversionValue(0, coarseValue: .low, lockWindow: false) { error in
92
+ if let error = error {
93
+ promise.reject("attribution_error", error.localizedDescription)
94
+ } else {
95
+ promise.resolve(["framework": "AdAttributionKit", "registered": true] as [String: Any])
96
+ }
97
+ }
98
+ } else if #available(iOS 14.0, *) {
99
+ SKAdNetwork.registerAppForAdNetworkAttribution()
100
+ promise.resolve(["framework": "SKAdNetwork", "registered": true] as [String: Any])
101
+ } else {
102
+ promise.reject("ios_version_error", "Attribution requires iOS 14.0+")
103
+ }
104
+ }
105
+
106
+ // Get attribution framework info
107
+ AsyncFunction("getAttributionInfo") { (promise: Promise) in
108
+ promise.resolve(Self.buildAttributionInfo())
109
+ }
110
+
111
+ // Update conversion value for re-engagement (AdAttributionKit iOS 17.4+ only)
112
+ AsyncFunction("updateReengagementConversionValue") { (fineValue: Int, coarseValue: String, lockWindow: Bool, promise: Promise) in
113
+ if #available(iOS 17.4, *) {
114
+ guard fineValue >= 0 && fineValue <= 63 else {
115
+ promise.reject("invalid_value", "Conversion value must be between 0 and 63")
116
+ return
117
+ }
118
+
119
+ let coarse = Self.mapCoarseValue(coarseValue)
120
+
121
+ SKAdNetwork.updatePostbackConversionValue(fineValue, coarseValue: coarse, lockWindow: lockWindow) { error in
122
+ if let error = error {
123
+ promise.reject("reengagement_error", error.localizedDescription)
124
+ } else {
125
+ promise.resolve([
126
+ "success": true,
127
+ "type": "reengagement",
128
+ "framework": "AdAttributionKit",
129
+ "fineValue": fineValue,
130
+ "coarseValue": coarseValue,
131
+ "lockWindow": lockWindow
132
+ ] as [String: Any])
133
+ }
134
+ }
135
+ } else {
136
+ promise.reject("unsupported", "Re-engagement attribution requires iOS 17.4+ (AdAttributionKit)")
137
+ }
138
+ }
139
+
140
+ // iOS 18.4+ - Check if geo-level postback data is available
141
+ AsyncFunction("isGeoPostbackAvailable") { (promise: Promise) in
142
+ if #available(iOS 18.4, *) {
143
+ promise.resolve(true)
144
+ } else {
145
+ promise.resolve(false)
146
+ }
147
+ }
148
+
149
+ // iOS 18.4+ - Set postback environment for testing
150
+ AsyncFunction("setPostbackEnvironment") { (environment: String, promise: Promise) in
151
+ if #available(iOS 18.4, *) {
152
+ let isSandbox = environment == "sandbox"
153
+ NSLog("[Datalyr] Postback environment set to: %@ (note: actual sandbox mode is controlled via device Developer Mode)", environment)
154
+ promise.resolve([
155
+ "environment": environment,
156
+ "isSandbox": isSandbox,
157
+ "note": "Enable Developer Mode in iOS Settings for sandbox postbacks"
158
+ ] as [String: Any])
159
+ } else {
160
+ promise.reject("unsupported", "Development postbacks require iOS 18.4+")
161
+ }
162
+ }
163
+
164
+ // iOS 18.4+ - Get enhanced attribution info including geo availability
165
+ AsyncFunction("getEnhancedAttributionInfo") { (promise: Promise) in
166
+ promise.resolve(Self.buildEnhancedAttributionInfo())
167
+ }
168
+
169
+ // iOS 18.4+ - Update postback with overlapping window support
170
+ AsyncFunction("updatePostbackWithWindow") { (fineValue: Int, coarseValue: String, lockWindow: Bool, windowIndex: Int, promise: Promise) in
171
+ guard fineValue >= 0 && fineValue <= 63 else {
172
+ promise.reject("invalid_value", "Conversion value must be between 0 and 63")
173
+ return
174
+ }
175
+
176
+ guard windowIndex >= 0 && windowIndex <= 2 else {
177
+ promise.reject("invalid_window", "Window index must be 0, 1, or 2")
178
+ return
179
+ }
180
+
181
+ if #available(iOS 16.1, *) {
182
+ let coarse = Self.mapCoarseValue(coarseValue)
183
+
184
+ SKAdNetwork.updatePostbackConversionValue(fineValue, coarseValue: coarse, lockWindow: lockWindow) { error in
185
+ if let error = error {
186
+ promise.reject("postback_error", error.localizedDescription)
187
+ } else {
188
+ var framework = "SKAdNetwork"
189
+ var version = "4.0"
190
+ var overlapping = false
191
+
192
+ if #available(iOS 18.4, *) {
193
+ framework = "AdAttributionKit"
194
+ version = "2.0"
195
+ overlapping = true
196
+ } else if #available(iOS 17.4, *) {
197
+ framework = "AdAttributionKit"
198
+ version = "1.0"
199
+ }
200
+
201
+ promise.resolve([
202
+ "success": true,
203
+ "framework": framework,
204
+ "version": version,
205
+ "fineValue": fineValue,
206
+ "coarseValue": coarseValue,
207
+ "lockWindow": lockWindow,
208
+ "windowIndex": windowIndex,
209
+ "overlappingWindows": overlapping,
210
+ ] as [String: Any])
211
+ }
212
+ }
213
+ } else {
214
+ promise.reject("unsupported", "This method requires iOS 16.1+")
215
+ }
216
+ }
217
+ }
218
+
219
+ // MARK: - Helper Methods
220
+
221
+ @available(iOS 16.1, *)
222
+ private static func mapCoarseValue(_ value: String) -> SKAdNetwork.CoarseConversionValue {
223
+ switch value {
224
+ case "high": return .high
225
+ case "medium": return .medium
226
+ default: return .low
227
+ }
228
+ }
229
+
230
+ private static func buildAttributionInfo() -> [String: Any] {
231
+ var info: [String: Any] = [:]
232
+
233
+ if #available(iOS 17.4, *) {
234
+ info["framework"] = "AdAttributionKit"
235
+ info["version"] = "1.0"
236
+ info["reengagement_available"] = true
237
+ info["fine_value_range"] = ["min": 0, "max": 63]
238
+ info["coarse_values"] = ["low", "medium", "high"]
239
+ if #available(iOS 18.4, *) {
240
+ info["overlapping_windows"] = true
241
+ } else {
242
+ info["overlapping_windows"] = false
243
+ }
244
+ } else if #available(iOS 16.1, *) {
245
+ info["framework"] = "SKAdNetwork"
246
+ info["version"] = "4.0"
247
+ info["reengagement_available"] = false
248
+ info["overlapping_windows"] = false
249
+ info["fine_value_range"] = ["min": 0, "max": 63]
250
+ info["coarse_values"] = ["low", "medium", "high"]
251
+ } else if #available(iOS 14.0, *) {
252
+ info["framework"] = "SKAdNetwork"
253
+ info["version"] = "3.0"
254
+ info["reengagement_available"] = false
255
+ info["overlapping_windows"] = false
256
+ info["fine_value_range"] = ["min": 0, "max": 63]
257
+ info["coarse_values"] = [] as [String]
258
+ } else {
259
+ info["framework"] = "none"
260
+ info["version"] = "0"
261
+ info["reengagement_available"] = false
262
+ info["overlapping_windows"] = false
263
+ info["fine_value_range"] = ["min": 0, "max": 0]
264
+ info["coarse_values"] = [] as [String]
265
+ }
266
+
267
+ return info
268
+ }
269
+
270
+ private static func buildEnhancedAttributionInfo() -> [String: Any] {
271
+ if #available(iOS 18.4, *) {
272
+ return [
273
+ "framework": "AdAttributionKit",
274
+ "version": "2.0",
275
+ "reengagement_available": true,
276
+ "overlapping_windows": true,
277
+ "geo_postback_available": true,
278
+ "development_postbacks": true,
279
+ "fine_value_range": ["min": 0, "max": 63],
280
+ "coarse_values": ["low", "medium", "high"],
281
+ "features": ["overlapping_windows", "geo_level_postbacks", "development_postbacks", "reengagement"]
282
+ ] as [String: Any]
283
+ } else if #available(iOS 17.4, *) {
284
+ return [
285
+ "framework": "AdAttributionKit",
286
+ "version": "1.0",
287
+ "reengagement_available": true,
288
+ "overlapping_windows": false,
289
+ "geo_postback_available": false,
290
+ "development_postbacks": false,
291
+ "fine_value_range": ["min": 0, "max": 63],
292
+ "coarse_values": ["low", "medium", "high"],
293
+ "features": ["reengagement"]
294
+ ] as [String: Any]
295
+ } else if #available(iOS 16.1, *) {
296
+ return [
297
+ "framework": "SKAdNetwork",
298
+ "version": "4.0",
299
+ "reengagement_available": false,
300
+ "overlapping_windows": false,
301
+ "geo_postback_available": false,
302
+ "development_postbacks": false,
303
+ "fine_value_range": ["min": 0, "max": 63],
304
+ "coarse_values": ["low", "medium", "high"],
305
+ "features": [] as [String]
306
+ ] as [String: Any]
307
+ } else if #available(iOS 14.0, *) {
308
+ return [
309
+ "framework": "SKAdNetwork",
310
+ "version": "3.0",
311
+ "reengagement_available": false,
312
+ "overlapping_windows": false,
313
+ "geo_postback_available": false,
314
+ "development_postbacks": false,
315
+ "fine_value_range": ["min": 0, "max": 63],
316
+ "coarse_values": [] as [String],
317
+ "features": [] as [String]
318
+ ] as [String: Any]
319
+ } else {
320
+ return [
321
+ "framework": "none",
322
+ "version": "0",
323
+ "reengagement_available": false,
324
+ "overlapping_windows": false,
325
+ "geo_postback_available": false,
326
+ "development_postbacks": false,
327
+ "fine_value_range": ["min": 0, "max": 0],
328
+ "coarse_values": [] as [String],
329
+ "features": [] as [String]
330
+ ] as [String: Any]
331
+ }
332
+ }
333
+ }
@@ -6,12 +6,28 @@
6
6
  * - iOS: Meta SDK, TikTok SDK, Apple Search Ads (AdServices)
7
7
  * - Android: Meta SDK, TikTok SDK, Play Install Referrer
8
8
  */
9
- var _a;
9
+ var _a, _b;
10
10
  import { NativeModules, Platform } from 'react-native';
11
+ import { requireNativeModule } from 'expo-modules-core';
11
12
  // Native modules - available on both iOS and Android
12
- const DatalyrNative = (_a = NativeModules.DatalyrNative) !== null && _a !== void 0 ? _a : null;
13
- // Play Install Referrer - Android only
14
- const DatalyrPlayInstallReferrer = Platform.OS === 'android' ? NativeModules.DatalyrPlayInstallReferrer : null;
13
+ // iOS uses Expo Modules (new arch compatible), Android uses NativeModules (interop layer)
14
+ let DatalyrNative = null;
15
+ if (Platform.OS === 'ios') {
16
+ try {
17
+ DatalyrNative = requireNativeModule('DatalyrNative');
18
+ }
19
+ catch (_c) {
20
+ // Native module not available
21
+ }
22
+ }
23
+ else if (Platform.OS === 'android') {
24
+ DatalyrNative = (_a = NativeModules.DatalyrNative) !== null && _a !== void 0 ? _a : null;
25
+ }
26
+ // Play Install Referrer - Android only (stays on NativeModules)
27
+ let DatalyrPlayInstallReferrer = null;
28
+ if (Platform.OS === 'android') {
29
+ DatalyrPlayInstallReferrer = (_b = NativeModules.DatalyrPlayInstallReferrer) !== null && _b !== void 0 ? _b : null;
30
+ }
15
31
  /**
16
32
  * Check if native module is available
17
33
  */
@@ -1,5 +1,15 @@
1
- import { NativeModules, Platform } from 'react-native';
2
- const { DatalyrSKAdNetwork } = NativeModules;
1
+ import { Platform } from 'react-native';
2
+ import { requireNativeModule } from 'expo-modules-core';
3
+ // SKAdNetwork is iOS-only, use Expo Modules for new arch compatibility
4
+ let DatalyrSKAdNetwork;
5
+ if (Platform.OS === 'ios') {
6
+ try {
7
+ DatalyrSKAdNetwork = requireNativeModule('DatalyrSKAdNetwork');
8
+ }
9
+ catch (_a) {
10
+ // Module not available
11
+ }
12
+ }
3
13
  export class SKAdNetworkBridge {
4
14
  /**
5
15
  * SKAN 3.0 - Update conversion value (0-63)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datalyr/react-native",
3
- "version": "1.3.1",
3
+ "version": "1.4.0",
4
4
  "description": "Datalyr SDK for React Native & Expo - Server-side attribution tracking with bundled Meta and TikTok SDKs for iOS and Android",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -19,7 +19,8 @@
19
19
  "README.md",
20
20
  "CHANGELOG.md",
21
21
  "LICENSE",
22
- "*.podspec"
22
+ "*.podspec",
23
+ "expo-module.config.json"
23
24
  ],
24
25
  "keywords": [
25
26
  "react-native",
@@ -57,10 +58,11 @@
57
58
  "url": "https://github.com/datalyr/react-native.git"
58
59
  },
59
60
  "peerDependencies": {
61
+ "@react-native-community/netinfo": ">=11.0.0",
62
+ "expo-modules-core": ">=2.0.0",
60
63
  "react": ">=18.0.0",
61
64
  "react-native": ">=0.72.0",
62
- "react-native-device-info": ">=12.0.0",
63
- "@react-native-community/netinfo": ">=11.0.0"
65
+ "react-native-device-info": ">=12.0.0"
64
66
  },
65
67
  "dependencies": {
66
68
  "@react-native-async-storage/async-storage": "^2.2.0",
@@ -75,6 +77,7 @@
75
77
  "@typescript-eslint/eslint-plugin": "^8.0.0",
76
78
  "@typescript-eslint/parser": "^8.0.0",
77
79
  "eslint": "^9.0.0",
80
+ "expo-modules-core": "^3.0.29",
78
81
  "jest": "^30.0.0",
79
82
  "typescript": "^5.7.0"
80
83
  },
@@ -90,6 +93,9 @@
90
93
  }
91
94
  },
92
95
  "peerDependenciesMeta": {
96
+ "expo-modules-core": {
97
+ "optional": false
98
+ },
93
99
  "expo": {
94
100
  "optional": true
95
101
  },