@advenue/react-native 0.8.0 → 1.0.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.
Files changed (94) hide show
  1. package/README.md +8 -7
  2. package/android/src/main/java/expo/modules/advenue/AdvenueAndroidModule.kt +118 -347
  3. package/android/src/main/kotlin/io/advenue/Advenue.kt +549 -0
  4. package/android/src/main/kotlin/io/advenue/AdvenueConfig.kt +114 -0
  5. package/android/src/main/kotlin/io/advenue/core/Backoff.kt +28 -0
  6. package/android/src/main/kotlin/io/advenue/core/ClientEvent.kt +136 -0
  7. package/android/src/main/kotlin/io/advenue/core/CommandPipe.kt +186 -0
  8. package/android/src/main/kotlin/io/advenue/core/Consent.kt +52 -0
  9. package/android/src/main/kotlin/io/advenue/core/Contracts.kt +80 -0
  10. package/android/src/main/kotlin/io/advenue/core/Conversion.kt +69 -0
  11. package/android/src/main/kotlin/io/advenue/core/Engine.kt +414 -0
  12. package/android/src/main/kotlin/io/advenue/core/EventQueue.kt +89 -0
  13. package/android/src/main/kotlin/io/advenue/core/HmacSigner.kt +31 -0
  14. package/android/src/main/kotlin/io/advenue/core/InstallReferrer.kt +146 -0
  15. package/android/src/main/kotlin/io/advenue/core/Json.kt +272 -0
  16. package/android/src/main/kotlin/io/advenue/core/Limits.kt +47 -0
  17. package/android/src/main/kotlin/io/advenue/core/MetaReferrer.kt +80 -0
  18. package/android/src/main/kotlin/io/advenue/core/SessionTracker.kt +189 -0
  19. package/android/src/main/kotlin/io/advenue/core/SystemServices.kt +58 -0
  20. package/android/src/main/kotlin/io/advenue/core/Tcf.kt +48 -0
  21. package/android/src/main/kotlin/io/advenue/core/Time.kt +56 -0
  22. package/android/src/main/kotlin/io/advenue/platform/Collectors.kt +149 -0
  23. package/android/src/main/kotlin/io/advenue/platform/CompositeStore.kt +33 -0
  24. package/android/src/main/kotlin/io/advenue/platform/ConversionFetcher.kt +68 -0
  25. package/android/src/main/kotlin/io/advenue/platform/ForegroundTracker.kt +90 -0
  26. package/android/src/main/kotlin/io/advenue/platform/HttpUrlTransport.kt +101 -0
  27. package/android/src/main/kotlin/io/advenue/platform/Identity.kt +79 -0
  28. package/android/src/main/kotlin/io/advenue/platform/InstallEnrichment.kt +197 -0
  29. package/android/src/main/kotlin/io/advenue/platform/InstallScopedStore.kt +91 -0
  30. package/android/src/main/kotlin/io/advenue/platform/LifecycleBridge.kt +72 -0
  31. package/android/src/main/kotlin/io/advenue/platform/PreferencesStore.kt +32 -0
  32. package/android/src/main/kotlin/io/advenue/plugin/Contracts.kt +67 -0
  33. package/android/src/main/kotlin/io/advenue/plugin/FirebaseAppInstanceIdSource.kt +73 -0
  34. package/android/src/main/kotlin/io/advenue/plugin/PlayAdvertisingIdSource.kt +47 -0
  35. package/android/src/main/kotlin/io/advenue/plugin/PlayInstallReferrerSource.kt +85 -0
  36. package/android/src/main/kotlin/io/advenue/plugin/PlayIntegritySource.kt +86 -0
  37. package/android/src/main/kotlin/io/advenue/plugin/PluginRegistry.kt +61 -0
  38. package/dist/index.cjs +183 -655
  39. package/dist/index.d.cts +199 -331
  40. package/dist/index.d.ts +199 -331
  41. package/dist/index.js +182 -661
  42. package/ios/AdvenueIosModule.swift +144 -432
  43. package/ios/vendor/Advenue/Advenue.swift +596 -0
  44. package/ios/vendor/Advenue/AdvenueConfig.swift +77 -0
  45. package/ios/vendor/AdvenueCore/AdvenueValue.swift +103 -0
  46. package/ios/vendor/AdvenueCore/Attestation.swift +52 -0
  47. package/ios/vendor/AdvenueCore/Backoff.swift +28 -0
  48. package/ios/vendor/AdvenueCore/ClientEvent.swift +154 -0
  49. package/ios/vendor/AdvenueCore/Consent.swift +40 -0
  50. package/ios/vendor/AdvenueCore/Contracts.swift +59 -0
  51. package/ios/vendor/AdvenueCore/Conversion.swift +74 -0
  52. package/ios/vendor/AdvenueCore/ConversionValue.swift +217 -0
  53. package/ios/vendor/AdvenueCore/Engine.swift +587 -0
  54. package/ios/vendor/AdvenueCore/EventQueue.swift +89 -0
  55. package/ios/vendor/AdvenueCore/Limits.swift +41 -0
  56. package/ios/vendor/AdvenueCore/PIIScrub.swift +102 -0
  57. package/ios/vendor/AdvenueCore/SessionTracker.swift +139 -0
  58. package/ios/vendor/AdvenueCore/SkanConfig.swift +76 -0
  59. package/ios/vendor/AdvenueCore/SkanReporter.swift +25 -0
  60. package/ios/vendor/AdvenueCore/SkanState.swift +258 -0
  61. package/ios/vendor/AdvenueCore/Tcf.swift +48 -0
  62. package/ios/vendor/AdvenueCore/Transport.swift +14 -0
  63. package/ios/vendor/AdvenueFirebase/FirebaseAppInstanceId.swift +33 -0
  64. package/ios/vendor/AdvenuePlatform/AdvertisingIdentity.swift +87 -0
  65. package/ios/vendor/AdvenuePlatform/ChallengeFetcher.swift +42 -0
  66. package/ios/vendor/AdvenuePlatform/ConversionFetcher.swift +63 -0
  67. package/ios/vendor/AdvenuePlatform/CryptoKitSigner.swift +19 -0
  68. package/ios/vendor/AdvenuePlatform/DeviceCheckAttestation.swift +91 -0
  69. package/ios/vendor/AdvenuePlatform/DeviceInfo.swift +65 -0
  70. package/ios/vendor/AdvenuePlatform/ForegroundTracker.swift +55 -0
  71. package/ios/vendor/AdvenuePlatform/HttpTransport.swift +96 -0
  72. package/ios/vendor/AdvenuePlatform/Identity.swift +52 -0
  73. package/ios/vendor/AdvenuePlatform/InstallEnrichment.swift +143 -0
  74. package/ios/vendor/AdvenuePlatform/KeychainStore.swift +95 -0
  75. package/ios/vendor/AdvenuePlatform/SearchAdsToken.swift +76 -0
  76. package/ios/vendor/AdvenuePlatform/SkanConfigFetcher.swift +70 -0
  77. package/ios/vendor/AdvenuePlatform/StoreKitSkanReporter.swift +140 -0
  78. package/ios/vendor/AdvenuePlatform/SystemServices.swift +55 -0
  79. package/ios/vendor/AdvenuePlatform/TcfReader.swift +18 -0
  80. package/ios/vendor/AdvenuePlatform/UserDefaultsStore.swift +26 -0
  81. package/package.json +9 -11
  82. package/scripts/check-dist.mjs +17 -0
  83. package/scripts/check-vendored-swift.mjs +148 -0
  84. package/scripts/vendor-natives.mjs +149 -0
  85. package/scripts/vendor-natives.test.mjs +112 -0
  86. package/src/deep-links.ts +19 -1
  87. package/src/index.ts +267 -803
  88. package/src/native-types.ts +74 -171
  89. package/src/native.ts +0 -23
  90. package/src/types.ts +81 -0
  91. package/src/aem.ts +0 -33
  92. package/src/mmkv-storage.ts +0 -21
  93. package/src/native-storage.ts +0 -63
  94. package/src/secure-store.ts +0 -26
@@ -0,0 +1,217 @@
1
+ import Foundation
2
+
3
+ /// SKAdNetwork 4's coarse bucket. Apple sends this instead of the fine value
4
+ /// when a device's install volume is below its crowd-anonymity threshold, so it
5
+ /// is not a fallback — for low-volume campaigns it is the only signal.
6
+ public enum CoarseValue: String, Codable, Sendable, CaseIterable {
7
+ case low
8
+ case medium
9
+ case high
10
+ }
11
+
12
+ /// Maps a fine value to its coarse bucket by Apple's even thirds of 0–63.
13
+ ///
14
+ /// The boundaries are the whole function: an off-by-one silently reassigns a
15
+ /// third of every advertiser's postbacks to the wrong bucket, and nothing
16
+ /// downstream can detect it.
17
+ public func deriveCoarse(_ fineValue: Int) -> CoarseValue {
18
+ if fineValue <= 21 { return .low }
19
+ if fineValue <= 42 { return .medium }
20
+ return .high
21
+ }
22
+
23
+ public struct ConversionValueRule: Codable, Equatable, Sendable {
24
+ public var fineValue: Int
25
+ /// Overrides the derived bucket when the advertiser wants a specific one.
26
+ public var coarseValue: CoarseValue?
27
+ /// Canonical non-negative base-10 micros. A string, not a number, because
28
+ /// money must never go through a Double.
29
+ public var minRevenueMicros: String?
30
+ /// Every named event must have been seen for the rule to match.
31
+ public var events: [String]?
32
+
33
+ public init(
34
+ fineValue: Int, coarseValue: CoarseValue? = nil,
35
+ minRevenueMicros: String? = nil, events: [String]? = nil
36
+ ) {
37
+ self.fineValue = fineValue
38
+ self.coarseValue = coarseValue
39
+ self.minRevenueMicros = minRevenueMicros
40
+ self.events = events
41
+ }
42
+ }
43
+
44
+ public struct ConversionValueConfig: Codable, Equatable, Sendable {
45
+ public var rules: [ConversionValueRule]
46
+ public var defaultCoarse: CoarseValue?
47
+ /// Three-letter uppercase ISO code. Required when any rule has a revenue
48
+ /// threshold, because comparing two amounts in different denominations is
49
+ /// meaningless.
50
+ public var revenueCurrency: String?
51
+
52
+ public init(
53
+ rules: [ConversionValueRule], defaultCoarse: CoarseValue? = nil,
54
+ revenueCurrency: String? = nil
55
+ ) {
56
+ self.rules = rules
57
+ self.defaultCoarse = defaultCoarse
58
+ self.revenueCurrency = revenueCurrency
59
+ }
60
+ }
61
+
62
+ /// What has accumulated on the device for the current measurement window.
63
+ public struct MeasurementState: Equatable, Sendable {
64
+ public var events: [String]
65
+ public var revenueMicros: String?
66
+ public var revenueCurrency: String?
67
+
68
+ public init(
69
+ events: [String] = [], revenueMicros: String? = nil, revenueCurrency: String? = nil
70
+ ) {
71
+ self.events = events
72
+ self.revenueMicros = revenueMicros
73
+ self.revenueCurrency = revenueCurrency
74
+ }
75
+ }
76
+
77
+ public struct ConversionValue: Equatable, Sendable {
78
+ public let fineValue: Int
79
+ public let coarseValue: CoarseValue
80
+ }
81
+
82
+ public enum ConversionConfigError: Error, Equatable, CustomStringConvertible {
83
+ case fineValueOutOfRange(Int)
84
+ case malformedMicros(String)
85
+ case malformedCurrency(String)
86
+ case revenueRuleWithoutCurrency
87
+
88
+ public var description: String {
89
+ switch self {
90
+ case .fineValueOutOfRange(let value):
91
+ return "conversion fineValue must be an integer 0–63, got \(value)"
92
+ case .malformedMicros(let value):
93
+ return "minRevenueMicros must be a canonical non-negative base-10 integer, got \(value)"
94
+ case .malformedCurrency(let value):
95
+ return "revenueCurrency must be a three-letter uppercase ISO currency, got \(value)"
96
+ case .revenueRuleWithoutCurrency:
97
+ return "revenueCurrency is required for revenue rules"
98
+ }
99
+ }
100
+ }
101
+
102
+ /// Config-driven conversion-value engine: the SDK accumulates events and
103
+ /// revenue, this decides what to report, and the platform layer makes the call.
104
+ public struct ConversionValueMapper: Sendable {
105
+ private let rules: [ConversionValueRule]
106
+ private let defaultCoarse: CoarseValue
107
+ private let revenueCurrency: String?
108
+
109
+ /// Validation happens here rather than at `compute` on purpose: a config
110
+ /// mistake should surface when the config is loaded, not silently report fine
111
+ /// value 0 from every device for a week.
112
+ public init(_ config: ConversionValueConfig) throws {
113
+ for rule in config.rules {
114
+ guard (0...63).contains(rule.fineValue) else {
115
+ throw ConversionConfigError.fineValueOutOfRange(rule.fineValue)
116
+ }
117
+ if let micros = rule.minRevenueMicros, !isCanonicalMicros(micros) {
118
+ throw ConversionConfigError.malformedMicros(micros)
119
+ }
120
+ }
121
+ if let currency = config.revenueCurrency, !isIsoCurrency(currency) {
122
+ throw ConversionConfigError.malformedCurrency(currency)
123
+ }
124
+ if config.rules.contains(where: { $0.minRevenueMicros != nil }),
125
+ config.revenueCurrency == nil
126
+ {
127
+ throw ConversionConfigError.revenueRuleWithoutCurrency
128
+ }
129
+
130
+ self.rules = config.rules
131
+ self.defaultCoarse = config.defaultCoarse ?? .low
132
+ self.revenueCurrency = config.revenueCurrency
133
+ }
134
+
135
+ /// The **highest** matching rule wins, not the first or the last: rule order
136
+ /// in a config is an authoring convenience and must not change the outcome.
137
+ public func compute(_ state: MeasurementState) -> ConversionValue {
138
+ var winner: ConversionValueRule?
139
+ for rule in rules where matches(rule, state) {
140
+ if winner == nil || rule.fineValue > winner!.fineValue { winner = rule }
141
+ }
142
+ guard let winner else {
143
+ return ConversionValue(fineValue: 0, coarseValue: defaultCoarse)
144
+ }
145
+ return ConversionValue(
146
+ fineValue: winner.fineValue,
147
+ coarseValue: winner.coarseValue ?? deriveCoarse(winner.fineValue))
148
+ }
149
+
150
+ private func matches(_ rule: ConversionValueRule, _ state: MeasurementState) -> Bool {
151
+ if let threshold = rule.minRevenueMicros {
152
+ guard let accrued = state.revenueMicros, isCanonicalMicros(accrued) else { return false }
153
+ // A denomination mismatch arises legitimately when the app changes its
154
+ // SKAN currency mid-window: persisted device state still holds revenue in
155
+ // the old one. That revenue is unusable for this comparison, so the rule
156
+ // does not match. Throwing here froze the device's conversion value for
157
+ // the rest of the window, because every later evaluation re-threw into
158
+ // the SDK's fire-and-forget catch.
159
+ guard state.revenueCurrency == revenueCurrency else { return false }
160
+ if microsLess(accrued, threshold) { return false }
161
+ }
162
+ if let required = rule.events, !required.allSatisfy(state.events.contains) {
163
+ return false
164
+ }
165
+ return true
166
+ }
167
+ }
168
+
169
+ /// Exact comparison of canonical non-negative base-10 integers, without a
170
+ /// bignum: more digits is larger, and equal digit counts compare
171
+ /// lexicographically. A `Double` would make neighbouring large micro amounts
172
+ /// compare equal and silently misprice a conversion.
173
+ public func microsLess(_ lhs: String, _ rhs: String) -> Bool {
174
+ lhs.count != rhs.count ? lhs.count < rhs.count : lhs < rhs
175
+ }
176
+
177
+ /// Parses a non-negative decimal amount ("9.99") into exact millionths,
178
+ /// returning nil for anything that is not one. Digit-string arithmetic, not
179
+ /// `Double`: at 2^53 a Double can no longer count, and below that it cannot
180
+ /// represent 0.07 — both failures misprice a conversion by real money.
181
+ ///
182
+ /// The grammar matches `decimalToMicros` in `@advenue/skadnetwork`: no leading
183
+ /// zeros, at most six fractional places, no sign and no exponent.
184
+ public func decimalToMicros(_ amount: String) -> String? {
185
+ guard !amount.isEmpty, amount.allSatisfy(\.isASCII) else { return nil }
186
+ let parts = amount.split(separator: ".", omittingEmptySubsequences: false)
187
+ guard parts.count <= 2 else { return nil }
188
+ let whole = String(parts[0])
189
+ let fraction = parts.count == 2 ? String(parts[1]) : ""
190
+
191
+ guard !whole.isEmpty, whole.allSatisfy(\.isNumber) else { return nil }
192
+ guard whole == "0" || whole.first != "0" else { return nil }
193
+ if parts.count == 2 {
194
+ guard !fraction.isEmpty, fraction.count <= 6, fraction.allSatisfy(\.isNumber) else {
195
+ return nil
196
+ }
197
+ }
198
+
199
+ let digits = whole + fraction.padding(toLength: 6, withPad: "0", startingAt: 0)
200
+ // Canonicalise: "0.000001" produces "0000001", and the queue's exact
201
+ // comparison is defined only on values with no leading zeros.
202
+ let trimmed = String(digits.drop(while: { $0 == "0" }))
203
+ return trimmed.isEmpty ? "0" : trimmed
204
+ }
205
+
206
+ /// No leading zeros except "0" itself, digits only — the same shape the server
207
+ /// accepts, so a value that passes here cannot be rejected there.
208
+ func isCanonicalMicros(_ value: String) -> Bool {
209
+ guard !value.isEmpty, value.allSatisfy(\.isASCII), value.allSatisfy({ $0.isNumber }) else {
210
+ return false
211
+ }
212
+ return value == "0" || value.first != "0"
213
+ }
214
+
215
+ func isIsoCurrency(_ value: String) -> Bool {
216
+ value.count == 3 && value.allSatisfy { $0.isUppercase && $0.isASCII && $0.isLetter }
217
+ }