@advenue/react-native 0.9.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.
- package/README.md +8 -7
- package/android/src/main/java/expo/modules/advenue/AdvenueAndroidModule.kt +118 -368
- package/android/src/main/kotlin/io/advenue/Advenue.kt +549 -0
- package/android/src/main/kotlin/io/advenue/AdvenueConfig.kt +114 -0
- package/android/src/main/kotlin/io/advenue/core/Backoff.kt +28 -0
- package/android/src/main/kotlin/io/advenue/core/ClientEvent.kt +136 -0
- package/android/src/main/kotlin/io/advenue/core/CommandPipe.kt +186 -0
- package/android/src/main/kotlin/io/advenue/core/Consent.kt +52 -0
- package/android/src/main/kotlin/io/advenue/core/Contracts.kt +80 -0
- package/android/src/main/kotlin/io/advenue/core/Conversion.kt +69 -0
- package/android/src/main/kotlin/io/advenue/core/Engine.kt +414 -0
- package/android/src/main/kotlin/io/advenue/core/EventQueue.kt +89 -0
- package/android/src/main/kotlin/io/advenue/core/HmacSigner.kt +31 -0
- package/android/src/main/kotlin/io/advenue/core/InstallReferrer.kt +146 -0
- package/android/src/main/kotlin/io/advenue/core/Json.kt +272 -0
- package/android/src/main/kotlin/io/advenue/core/Limits.kt +47 -0
- package/android/src/main/kotlin/io/advenue/core/MetaReferrer.kt +80 -0
- package/android/src/main/kotlin/io/advenue/core/SessionTracker.kt +189 -0
- package/android/src/main/kotlin/io/advenue/core/SystemServices.kt +58 -0
- package/android/src/main/kotlin/io/advenue/core/Tcf.kt +48 -0
- package/android/src/main/kotlin/io/advenue/core/Time.kt +56 -0
- package/android/src/main/kotlin/io/advenue/platform/Collectors.kt +149 -0
- package/android/src/main/kotlin/io/advenue/platform/CompositeStore.kt +33 -0
- package/android/src/main/kotlin/io/advenue/platform/ConversionFetcher.kt +68 -0
- package/android/src/main/kotlin/io/advenue/platform/ForegroundTracker.kt +90 -0
- package/android/src/main/kotlin/io/advenue/platform/HttpUrlTransport.kt +101 -0
- package/android/src/main/kotlin/io/advenue/platform/Identity.kt +79 -0
- package/android/src/main/kotlin/io/advenue/platform/InstallEnrichment.kt +197 -0
- package/android/src/main/kotlin/io/advenue/platform/InstallScopedStore.kt +91 -0
- package/android/src/main/kotlin/io/advenue/platform/LifecycleBridge.kt +72 -0
- package/android/src/main/kotlin/io/advenue/platform/PreferencesStore.kt +32 -0
- package/android/src/main/kotlin/io/advenue/plugin/Contracts.kt +67 -0
- package/android/src/main/kotlin/io/advenue/plugin/FirebaseAppInstanceIdSource.kt +73 -0
- package/android/src/main/kotlin/io/advenue/plugin/PlayAdvertisingIdSource.kt +47 -0
- package/android/src/main/kotlin/io/advenue/plugin/PlayInstallReferrerSource.kt +85 -0
- package/android/src/main/kotlin/io/advenue/plugin/PlayIntegritySource.kt +86 -0
- package/android/src/main/kotlin/io/advenue/plugin/PluginRegistry.kt +61 -0
- package/dist/index.cjs +183 -675
- package/dist/index.d.cts +199 -336
- package/dist/index.d.ts +199 -336
- package/dist/index.js +182 -681
- package/ios/AdvenueIosModule.swift +144 -446
- package/ios/vendor/Advenue/Advenue.swift +596 -0
- package/ios/vendor/Advenue/AdvenueConfig.swift +77 -0
- package/ios/vendor/AdvenueCore/AdvenueValue.swift +103 -0
- package/ios/vendor/AdvenueCore/Attestation.swift +52 -0
- package/ios/vendor/AdvenueCore/Backoff.swift +28 -0
- package/ios/vendor/AdvenueCore/ClientEvent.swift +154 -0
- package/ios/vendor/AdvenueCore/Consent.swift +40 -0
- package/ios/vendor/AdvenueCore/Contracts.swift +59 -0
- package/ios/vendor/AdvenueCore/Conversion.swift +74 -0
- package/ios/vendor/AdvenueCore/ConversionValue.swift +217 -0
- package/ios/vendor/AdvenueCore/Engine.swift +587 -0
- package/ios/vendor/AdvenueCore/EventQueue.swift +89 -0
- package/ios/vendor/AdvenueCore/Limits.swift +41 -0
- package/ios/vendor/AdvenueCore/PIIScrub.swift +102 -0
- package/ios/vendor/AdvenueCore/SessionTracker.swift +139 -0
- package/ios/vendor/AdvenueCore/SkanConfig.swift +76 -0
- package/ios/vendor/AdvenueCore/SkanReporter.swift +25 -0
- package/ios/vendor/AdvenueCore/SkanState.swift +258 -0
- package/ios/vendor/AdvenueCore/Tcf.swift +48 -0
- package/ios/vendor/AdvenueCore/Transport.swift +14 -0
- package/ios/vendor/AdvenueFirebase/FirebaseAppInstanceId.swift +33 -0
- package/ios/vendor/AdvenuePlatform/AdvertisingIdentity.swift +87 -0
- package/ios/vendor/AdvenuePlatform/ChallengeFetcher.swift +42 -0
- package/ios/vendor/AdvenuePlatform/ConversionFetcher.swift +63 -0
- package/ios/vendor/AdvenuePlatform/CryptoKitSigner.swift +19 -0
- package/ios/vendor/AdvenuePlatform/DeviceCheckAttestation.swift +91 -0
- package/ios/vendor/AdvenuePlatform/DeviceInfo.swift +65 -0
- package/ios/vendor/AdvenuePlatform/ForegroundTracker.swift +55 -0
- package/ios/vendor/AdvenuePlatform/HttpTransport.swift +96 -0
- package/ios/vendor/AdvenuePlatform/Identity.swift +52 -0
- package/ios/vendor/AdvenuePlatform/InstallEnrichment.swift +143 -0
- package/ios/vendor/AdvenuePlatform/KeychainStore.swift +95 -0
- package/ios/vendor/AdvenuePlatform/SearchAdsToken.swift +76 -0
- package/ios/vendor/AdvenuePlatform/SkanConfigFetcher.swift +70 -0
- package/ios/vendor/AdvenuePlatform/StoreKitSkanReporter.swift +140 -0
- package/ios/vendor/AdvenuePlatform/SystemServices.swift +55 -0
- package/ios/vendor/AdvenuePlatform/TcfReader.swift +18 -0
- package/ios/vendor/AdvenuePlatform/UserDefaultsStore.swift +26 -0
- package/package.json +9 -11
- package/scripts/check-dist.mjs +17 -0
- package/scripts/check-vendored-swift.mjs +148 -0
- package/scripts/vendor-natives.mjs +149 -0
- package/scripts/vendor-natives.test.mjs +112 -0
- package/src/deep-links.ts +19 -1
- package/src/index.ts +266 -830
- package/src/native-types.ts +74 -181
- package/src/native.ts +0 -23
- package/src/types.ts +81 -0
- package/src/aem.ts +0 -33
- package/src/mmkv-storage.ts +0 -21
- package/src/native-storage.ts +0 -63
- 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
|
+
}
|