@datalyr/react-native 1.3.0 → 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.
- package/CHANGELOG.md +19 -0
- package/README.md +145 -9
- package/datalyr-react-native.podspec +1 -1
- package/expo-module.config.json +6 -0
- package/ios/DatalyrNativeModule.swift +221 -0
- package/ios/DatalyrSKAdNetworkModule.swift +333 -0
- package/ios/PrivacyInfo.xcprivacy +48 -0
- package/lib/datalyr-sdk.d.ts +6 -0
- package/lib/datalyr-sdk.js +84 -27
- package/lib/index.d.ts +3 -1
- package/lib/index.js +3 -1
- package/lib/integrations/play-install-referrer.d.ts +5 -1
- package/lib/integrations/play-install-referrer.js +14 -4
- package/lib/native/DatalyrNativeBridge.js +20 -4
- package/lib/native/SKAdNetworkBridge.d.ts +121 -0
- package/lib/native/SKAdNetworkBridge.js +288 -4
- 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 +12 -2
- package/src/datalyr-sdk.ts +96 -32
- package/src/index.ts +5 -1
- package/src/integrations/play-install-referrer.ts +19 -4
- package/src/native/DatalyrNativeBridge.ts +17 -4
- package/src/native/SKAdNetworkBridge.ts +411 -9
- package/src/network-status.ts +312 -0
- package/src/types.ts +74 -6
- package/src/utils.ts +62 -6
- package/ios/DatalyrNative.m +0 -74
- package/ios/DatalyrNative.swift +0 -332
- package/ios/DatalyrSKAdNetwork.m +0 -77
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<plist version="1.0">
|
|
4
|
+
<dict>
|
|
5
|
+
<key>NSPrivacyTracking</key>
|
|
6
|
+
<false/>
|
|
7
|
+
<key>NSPrivacyTrackingDomains</key>
|
|
8
|
+
<array/>
|
|
9
|
+
<key>NSPrivacyCollectedDataTypes</key>
|
|
10
|
+
<array>
|
|
11
|
+
<dict>
|
|
12
|
+
<key>NSPrivacyCollectedDataType</key>
|
|
13
|
+
<string>NSPrivacyCollectedDataTypeDeviceID</string>
|
|
14
|
+
<key>NSPrivacyCollectedDataTypeLinked</key>
|
|
15
|
+
<false/>
|
|
16
|
+
<key>NSPrivacyCollectedDataTypeTracking</key>
|
|
17
|
+
<false/>
|
|
18
|
+
<key>NSPrivacyCollectedDataTypePurposes</key>
|
|
19
|
+
<array>
|
|
20
|
+
<string>NSPrivacyCollectedDataTypePurposeAnalytics</string>
|
|
21
|
+
</array>
|
|
22
|
+
</dict>
|
|
23
|
+
<dict>
|
|
24
|
+
<key>NSPrivacyCollectedDataType</key>
|
|
25
|
+
<string>NSPrivacyCollectedDataTypeProductInteraction</string>
|
|
26
|
+
<key>NSPrivacyCollectedDataTypeLinked</key>
|
|
27
|
+
<false/>
|
|
28
|
+
<key>NSPrivacyCollectedDataTypeTracking</key>
|
|
29
|
+
<false/>
|
|
30
|
+
<key>NSPrivacyCollectedDataTypePurposes</key>
|
|
31
|
+
<array>
|
|
32
|
+
<string>NSPrivacyCollectedDataTypePurposeAnalytics</string>
|
|
33
|
+
</array>
|
|
34
|
+
</dict>
|
|
35
|
+
</array>
|
|
36
|
+
<key>NSPrivacyAccessedAPITypes</key>
|
|
37
|
+
<array>
|
|
38
|
+
<dict>
|
|
39
|
+
<key>NSPrivacyAccessedAPIType</key>
|
|
40
|
+
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
|
|
41
|
+
<key>NSPrivacyAccessedAPITypeReasons</key>
|
|
42
|
+
<array>
|
|
43
|
+
<string>CA92.1</string>
|
|
44
|
+
</array>
|
|
45
|
+
</dict>
|
|
46
|
+
</array>
|
|
47
|
+
</dict>
|
|
48
|
+
</plist>
|
package/lib/datalyr-sdk.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export declare class DatalyrSDK {
|
|
|
8
8
|
private eventQueue;
|
|
9
9
|
private autoEventsManager;
|
|
10
10
|
private appStateSubscription;
|
|
11
|
+
private networkStatusUnsubscribe;
|
|
11
12
|
private static conversionEncoder?;
|
|
12
13
|
private static debugEnabled;
|
|
13
14
|
constructor();
|
|
@@ -193,6 +194,11 @@ export declare class DatalyrSDK {
|
|
|
193
194
|
* Persist user data to storage
|
|
194
195
|
*/
|
|
195
196
|
private persistUserData;
|
|
197
|
+
/**
|
|
198
|
+
* Initialize network status monitoring
|
|
199
|
+
* Automatically updates event queue when network status changes
|
|
200
|
+
*/
|
|
201
|
+
private initializeNetworkMonitoring;
|
|
196
202
|
/**
|
|
197
203
|
* Set up app state monitoring for lifecycle events (optimized)
|
|
198
204
|
*/
|
package/lib/datalyr-sdk.js
CHANGED
|
@@ -8,10 +8,12 @@ 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 { networkStatusManager } from './network-status';
|
|
11
12
|
export class DatalyrSDK {
|
|
12
13
|
constructor() {
|
|
13
14
|
this.autoEventsManager = null;
|
|
14
15
|
this.appStateSubscription = null;
|
|
16
|
+
this.networkStatusUnsubscribe = null;
|
|
15
17
|
// Initialize state with defaults
|
|
16
18
|
this.state = {
|
|
17
19
|
initialized: false,
|
|
@@ -73,18 +75,20 @@ export class DatalyrSDK {
|
|
|
73
75
|
flushInterval: this.state.config.flushInterval || 30000,
|
|
74
76
|
maxRetryCount: this.state.config.maxRetries || 3,
|
|
75
77
|
});
|
|
76
|
-
//
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
78
|
+
// PARALLEL INITIALIZATION: IDs and core managers
|
|
79
|
+
// Run ID creation and core manager initialization in parallel for faster startup
|
|
80
|
+
const [visitorId, anonymousId, sessionId] = await Promise.all([
|
|
81
|
+
getOrCreateVisitorId(),
|
|
82
|
+
getOrCreateAnonymousId(),
|
|
83
|
+
getOrCreateSessionId(),
|
|
84
|
+
// These run concurrently but don't return values we need to capture
|
|
85
|
+
this.loadPersistedUserData(),
|
|
86
|
+
this.state.config.enableAttribution ? attributionManager.initialize() : Promise.resolve(),
|
|
87
|
+
journeyManager.initialize(),
|
|
88
|
+
]);
|
|
89
|
+
this.state.visitorId = visitorId;
|
|
90
|
+
this.state.anonymousId = anonymousId;
|
|
91
|
+
this.state.sessionId = sessionId;
|
|
88
92
|
// Record initial attribution to journey if this is a new session with attribution
|
|
89
93
|
const initialAttribution = attributionManager.getAttributionData();
|
|
90
94
|
if (initialAttribution.utm_source || initialAttribution.fbclid || initialAttribution.gclid || initialAttribution.lyr) {
|
|
@@ -122,7 +126,7 @@ export class DatalyrSDK {
|
|
|
122
126
|
errorLog('Error setting up app state monitoring (non-blocking):', error);
|
|
123
127
|
}
|
|
124
128
|
}, 50);
|
|
125
|
-
// Initialize SKAdNetwork conversion encoder
|
|
129
|
+
// Initialize SKAdNetwork conversion encoder (synchronous, no await needed)
|
|
126
130
|
if (config.skadTemplate) {
|
|
127
131
|
const template = ConversionTemplates[config.skadTemplate];
|
|
128
132
|
if (template) {
|
|
@@ -134,25 +138,34 @@ export class DatalyrSDK {
|
|
|
134
138
|
}
|
|
135
139
|
}
|
|
136
140
|
}
|
|
137
|
-
//
|
|
141
|
+
// PARALLEL INITIALIZATION: Network monitoring and platform integrations
|
|
142
|
+
// These are independent and can run concurrently for faster startup
|
|
143
|
+
const platformInitPromises = [
|
|
144
|
+
// Network monitoring
|
|
145
|
+
this.initializeNetworkMonitoring(),
|
|
146
|
+
// Apple Search Ads (iOS only)
|
|
147
|
+
appleSearchAdsIntegration.initialize(config.debug),
|
|
148
|
+
// Google Play Install Referrer (Android only)
|
|
149
|
+
playInstallReferrerIntegration.initialize(),
|
|
150
|
+
];
|
|
151
|
+
// Add Meta initialization if configured
|
|
138
152
|
if ((_b = config.meta) === null || _b === void 0 ? void 0 : _b.appId) {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
153
|
+
platformInitPromises.push(metaIntegration.initialize(config.meta, config.debug).then(async () => {
|
|
154
|
+
// After Meta initializes, fetch deferred deep link
|
|
155
|
+
if (config.enableAttribution !== false) {
|
|
156
|
+
const deferredLink = await metaIntegration.fetchDeferredDeepLink();
|
|
157
|
+
if (deferredLink) {
|
|
158
|
+
await this.handleDeferredDeepLink(deferredLink);
|
|
159
|
+
}
|
|
145
160
|
}
|
|
146
|
-
}
|
|
161
|
+
}));
|
|
147
162
|
}
|
|
148
|
-
//
|
|
163
|
+
// Add TikTok initialization if configured
|
|
149
164
|
if (((_c = config.tiktok) === null || _c === void 0 ? void 0 : _c.appId) && ((_d = config.tiktok) === null || _d === void 0 ? void 0 : _d.tiktokAppId)) {
|
|
150
|
-
|
|
165
|
+
platformInitPromises.push(tiktokIntegration.initialize(config.tiktok, config.debug));
|
|
151
166
|
}
|
|
152
|
-
//
|
|
153
|
-
await
|
|
154
|
-
// Initialize Google Play Install Referrer (Android only)
|
|
155
|
-
await playInstallReferrerIntegration.initialize();
|
|
167
|
+
// Wait for all platform integrations to complete
|
|
168
|
+
await Promise.all(platformInitPromises);
|
|
156
169
|
debugLog('Platform integrations initialized', {
|
|
157
170
|
meta: metaIntegration.isAvailable(),
|
|
158
171
|
tiktok: tiktokIntegration.isAvailable(),
|
|
@@ -900,6 +913,41 @@ export class DatalyrSDK {
|
|
|
900
913
|
errorLog('Error persisting user data:', error);
|
|
901
914
|
}
|
|
902
915
|
}
|
|
916
|
+
/**
|
|
917
|
+
* Initialize network status monitoring
|
|
918
|
+
* Automatically updates event queue when network status changes
|
|
919
|
+
*/
|
|
920
|
+
async initializeNetworkMonitoring() {
|
|
921
|
+
try {
|
|
922
|
+
await networkStatusManager.initialize();
|
|
923
|
+
// Update event queue with current network status
|
|
924
|
+
this.state.isOnline = networkStatusManager.isOnline();
|
|
925
|
+
this.eventQueue.setOnlineStatus(this.state.isOnline);
|
|
926
|
+
// Subscribe to network changes
|
|
927
|
+
this.networkStatusUnsubscribe = networkStatusManager.subscribe((state) => {
|
|
928
|
+
const isOnline = state.isConnected && (state.isInternetReachable !== false);
|
|
929
|
+
this.state.isOnline = isOnline;
|
|
930
|
+
this.eventQueue.setOnlineStatus(isOnline);
|
|
931
|
+
// Track network status change event (only if SDK is fully initialized)
|
|
932
|
+
if (this.state.initialized) {
|
|
933
|
+
this.track('$network_status_change', {
|
|
934
|
+
is_online: isOnline,
|
|
935
|
+
network_type: state.type,
|
|
936
|
+
is_internet_reachable: state.isInternetReachable,
|
|
937
|
+
}).catch(() => {
|
|
938
|
+
// Ignore errors for network status events
|
|
939
|
+
});
|
|
940
|
+
}
|
|
941
|
+
});
|
|
942
|
+
debugLog(`Network monitoring initialized, online: ${this.state.isOnline}`);
|
|
943
|
+
}
|
|
944
|
+
catch (error) {
|
|
945
|
+
errorLog('Error initializing network monitoring (non-blocking):', error);
|
|
946
|
+
// Default to online if monitoring fails
|
|
947
|
+
this.state.isOnline = true;
|
|
948
|
+
this.eventQueue.setOnlineStatus(true);
|
|
949
|
+
}
|
|
950
|
+
}
|
|
903
951
|
/**
|
|
904
952
|
* Set up app state monitoring for lifecycle events (optimized)
|
|
905
953
|
*/
|
|
@@ -920,6 +968,8 @@ export class DatalyrSDK {
|
|
|
920
968
|
else if (nextAppState === 'active') {
|
|
921
969
|
// App became active, ensure we have fresh session if needed
|
|
922
970
|
this.refreshSession();
|
|
971
|
+
// Refresh network status when coming back from background
|
|
972
|
+
networkStatusManager.refresh();
|
|
923
973
|
// Notify auto-events manager for session handling
|
|
924
974
|
if (this.autoEventsManager) {
|
|
925
975
|
this.autoEventsManager.handleAppForeground();
|
|
@@ -957,6 +1007,13 @@ export class DatalyrSDK {
|
|
|
957
1007
|
this.appStateSubscription.remove();
|
|
958
1008
|
this.appStateSubscription = null;
|
|
959
1009
|
}
|
|
1010
|
+
// Remove network status listener
|
|
1011
|
+
if (this.networkStatusUnsubscribe) {
|
|
1012
|
+
this.networkStatusUnsubscribe();
|
|
1013
|
+
this.networkStatusUnsubscribe = null;
|
|
1014
|
+
}
|
|
1015
|
+
// Destroy network status manager
|
|
1016
|
+
networkStatusManager.destroy();
|
|
960
1017
|
// Destroy event queue
|
|
961
1018
|
this.eventQueue.destroy();
|
|
962
1019
|
// Reset state
|
package/lib/index.d.ts
CHANGED
|
@@ -12,6 +12,8 @@ export * from './event-queue';
|
|
|
12
12
|
export { DatalyrSDK };
|
|
13
13
|
export { ConversionValueEncoder, ConversionTemplates } from './ConversionValueEncoder';
|
|
14
14
|
export { SKAdNetworkBridge } from './native/SKAdNetworkBridge';
|
|
15
|
-
export { metaIntegration, tiktokIntegration, appleSearchAdsIntegration } from './integrations';
|
|
15
|
+
export { metaIntegration, tiktokIntegration, appleSearchAdsIntegration, playInstallReferrerIntegration } from './integrations';
|
|
16
|
+
export { networkStatusManager } from './network-status';
|
|
17
|
+
export type { NetworkState, NetworkStateListener } from './network-status';
|
|
16
18
|
export type { AppleSearchAdsAttribution } from './native/DatalyrNativeBridge';
|
|
17
19
|
export default DatalyrSDK;
|
package/lib/index.js
CHANGED
|
@@ -20,6 +20,8 @@ export { DatalyrSDK };
|
|
|
20
20
|
export { ConversionValueEncoder, ConversionTemplates } from './ConversionValueEncoder';
|
|
21
21
|
export { SKAdNetworkBridge } from './native/SKAdNetworkBridge';
|
|
22
22
|
// Export platform integrations
|
|
23
|
-
export { metaIntegration, tiktokIntegration, appleSearchAdsIntegration } from './integrations';
|
|
23
|
+
export { metaIntegration, tiktokIntegration, appleSearchAdsIntegration, playInstallReferrerIntegration } from './integrations';
|
|
24
|
+
// Export network status manager
|
|
25
|
+
export { networkStatusManager } from './network-status';
|
|
24
26
|
// Default export for compatibility
|
|
25
27
|
export default DatalyrSDK;
|
|
@@ -19,7 +19,9 @@
|
|
|
19
19
|
* - referrer_url: Full referrer URL from Play Store
|
|
20
20
|
* - referrer_click_timestamp: When the referrer link was clicked
|
|
21
21
|
* - install_begin_timestamp: When the install began
|
|
22
|
-
* - gclid: Google Ads click ID (
|
|
22
|
+
* - gclid: Google Ads click ID (standard)
|
|
23
|
+
* - gbraid: Google Ads privacy-safe click ID (iOS App campaigns)
|
|
24
|
+
* - wbraid: Google Ads privacy-safe click ID (Web-to-App campaigns)
|
|
23
25
|
* - utm_source, utm_medium, utm_campaign, etc.
|
|
24
26
|
*/
|
|
25
27
|
export interface PlayInstallReferrer {
|
|
@@ -28,6 +30,8 @@ export interface PlayInstallReferrer {
|
|
|
28
30
|
installBeginTimestamp: number;
|
|
29
31
|
installCompleteTimestamp?: number;
|
|
30
32
|
gclid?: string;
|
|
33
|
+
gbraid?: string;
|
|
34
|
+
wbraid?: string;
|
|
31
35
|
utmSource?: string;
|
|
32
36
|
utmMedium?: string;
|
|
33
37
|
utmCampaign?: string;
|
|
@@ -19,7 +19,9 @@
|
|
|
19
19
|
* - referrer_url: Full referrer URL from Play Store
|
|
20
20
|
* - referrer_click_timestamp: When the referrer link was clicked
|
|
21
21
|
* - install_begin_timestamp: When the install began
|
|
22
|
-
* - gclid: Google Ads click ID (
|
|
22
|
+
* - gclid: Google Ads click ID (standard)
|
|
23
|
+
* - gbraid: Google Ads privacy-safe click ID (iOS App campaigns)
|
|
24
|
+
* - wbraid: Google Ads privacy-safe click ID (Web-to-App campaigns)
|
|
23
25
|
* - utm_source, utm_medium, utm_campaign, etc.
|
|
24
26
|
*/
|
|
25
27
|
import { Platform, NativeModules } from 'react-native';
|
|
@@ -61,6 +63,8 @@ class PlayInstallReferrerIntegration {
|
|
|
61
63
|
utmSource: this.referrerData.utmSource,
|
|
62
64
|
utmMedium: this.referrerData.utmMedium,
|
|
63
65
|
hasGclid: !!this.referrerData.gclid,
|
|
66
|
+
hasGbraid: !!this.referrerData.gbraid,
|
|
67
|
+
hasWbraid: !!this.referrerData.wbraid,
|
|
64
68
|
});
|
|
65
69
|
}
|
|
66
70
|
}
|
|
@@ -109,11 +113,14 @@ class PlayInstallReferrerIntegration {
|
|
|
109
113
|
params.utmCampaign = searchParams.get('utm_campaign') || undefined;
|
|
110
114
|
params.utmTerm = searchParams.get('utm_term') || undefined;
|
|
111
115
|
params.utmContent = searchParams.get('utm_content') || undefined;
|
|
112
|
-
// Extract click IDs
|
|
116
|
+
// Extract click IDs (gclid, gbraid, wbraid)
|
|
113
117
|
params.gclid = searchParams.get('gclid') || undefined;
|
|
118
|
+
params.gbraid = searchParams.get('gbraid') || undefined;
|
|
119
|
+
params.wbraid = searchParams.get('wbraid') || undefined;
|
|
114
120
|
// Store any additional parameters
|
|
121
|
+
const knownParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'gclid', 'gbraid', 'wbraid'];
|
|
115
122
|
searchParams.forEach((value, key) => {
|
|
116
|
-
if (!
|
|
123
|
+
if (!knownParams.includes(key) && !key.startsWith('utm_')) {
|
|
117
124
|
params[key] = value;
|
|
118
125
|
}
|
|
119
126
|
});
|
|
@@ -141,8 +148,11 @@ class PlayInstallReferrerIntegration {
|
|
|
141
148
|
install_referrer_url: this.referrerData.referrerUrl,
|
|
142
149
|
referrer_click_timestamp: this.referrerData.referrerClickTimestamp,
|
|
143
150
|
install_begin_timestamp: this.referrerData.installBeginTimestamp,
|
|
144
|
-
//
|
|
151
|
+
// Google Ads click IDs (gclid is standard, gbraid/wbraid are privacy-safe alternatives)
|
|
145
152
|
gclid: this.referrerData.gclid,
|
|
153
|
+
gbraid: this.referrerData.gbraid,
|
|
154
|
+
wbraid: this.referrerData.wbraid,
|
|
155
|
+
// UTM parameters
|
|
146
156
|
utm_source: this.referrerData.utmSource,
|
|
147
157
|
utm_medium: this.referrerData.utmMedium,
|
|
148
158
|
utm_campaign: this.referrerData.utmCampaign,
|