@advenue/react-native 1.0.1 → 1.1.1
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/android/build.gradle +8 -2
- package/android/src/main/java/expo/modules/advenue/AdvenueAndroidModule.kt +4 -0
- package/android/src/main/kotlin/io/advenue/Advenue.kt +49 -9
- package/android/src/main/kotlin/io/advenue/AdvenueConfig.kt +9 -1
- package/android/src/main/kotlin/io/advenue/DebugLog.kt +51 -0
- package/android/src/main/kotlin/io/advenue/core/Backoff.kt +14 -1
- package/android/src/main/kotlin/io/advenue/core/CommandPipe.kt +28 -2
- package/android/src/main/kotlin/io/advenue/core/Engine.kt +73 -3
- package/android/src/main/kotlin/io/advenue/core/EventQueue.kt +12 -0
- package/android/src/main/kotlin/io/advenue/core/SessionTracker.kt +109 -33
- package/android/src/main/kotlin/io/advenue/platform/ConversionFetcher.kt +24 -10
- package/android/src/main/kotlin/io/advenue/platform/ForegroundTracker.kt +9 -2
- package/android/src/main/kotlin/io/advenue/platform/HttpUrlTransport.kt +21 -8
- package/android/src/main/kotlin/io/advenue/platform/InstallEnrichment.kt +7 -0
- package/android/src/main/kotlin/io/advenue/platform/LifecycleBridge.kt +23 -1
- package/dist/index.cjs +18 -2
- package/dist/index.d.cts +13 -3
- package/dist/index.d.ts +13 -3
- package/dist/index.js +18 -2
- package/ios/AdvenueIosModule.swift +4 -1
- package/ios/vendor/Advenue/Advenue.swift +53 -18
- package/ios/vendor/Advenue/AdvenueConfig.swift +17 -3
- package/ios/vendor/Advenue/AdvenueSetupError.swift +24 -0
- package/ios/vendor/Advenue/DebugLog.swift +46 -0
- package/ios/vendor/AdvenueCore/Backoff.swift +16 -2
- package/ios/vendor/AdvenueCore/Engine.swift +58 -4
- package/ios/vendor/AdvenueCore/EventQueue.swift +12 -0
- package/ios/vendor/AdvenueCore/SessionTracker.swift +89 -32
- package/ios/vendor/AdvenuePlatform/ChallengeFetcher.swift +14 -2
- package/ios/vendor/AdvenuePlatform/ConversionFetcher.swift +14 -3
- package/ios/vendor/AdvenuePlatform/DeviceInfo.swift +19 -0
- package/ios/vendor/AdvenuePlatform/HttpTransport.swift +28 -4
- package/ios/vendor/AdvenuePlatform/InstallEnrichment.swift +4 -0
- package/ios/vendor/AdvenuePlatform/SkanConfigFetcher.swift +14 -2
- package/package.json +18 -9
- package/src/index.ts +11 -1
- package/src/types.ts +13 -3
package/android/build.gradle
CHANGED
|
@@ -8,14 +8,20 @@ plugins {
|
|
|
8
8
|
id 'expo-module-gradle-plugin'
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
// Single source of truth, mirroring AdvenueIos.podspec (`s.version =
|
|
12
|
+
// package['version']`): a hardcoded copy here shipped 0.6.3 while the
|
|
13
|
+
// package was 1.1.0. Inert at runtime — an autolinked Expo module is never
|
|
14
|
+
// published to Maven — but a wrong version misleads every build scan.
|
|
15
|
+
def packageJson = new groovy.json.JsonSlurper().parseText(file('../package.json').text)
|
|
16
|
+
|
|
11
17
|
group = 'expo.modules.advenue'
|
|
12
|
-
version =
|
|
18
|
+
version = packageJson.version
|
|
13
19
|
|
|
14
20
|
android {
|
|
15
21
|
namespace "expo.modules.advenue"
|
|
16
22
|
defaultConfig {
|
|
17
23
|
versionCode 1
|
|
18
|
-
versionName
|
|
24
|
+
versionName packageJson.version
|
|
19
25
|
}
|
|
20
26
|
}
|
|
21
27
|
|
|
@@ -57,6 +57,10 @@ class AdvenueAndroidModule : Module() {
|
|
|
57
57
|
// The JavaScript has sent this since before the inversion; nothing
|
|
58
58
|
// read it, so Meta install-referrer attribution never ran.
|
|
59
59
|
fbAppId = options["fbAppId"] as? String,
|
|
60
|
+
// Logs the start, failures and sent batches to logcat (tag Advenue).
|
|
61
|
+
debug = (options["debug"] as? Boolean) ?: false,
|
|
62
|
+
// Test only: an http:// endpoint is refused without it.
|
|
63
|
+
allowInsecureHttp = (options["allowInsecureHttp"] as? Boolean) ?: false,
|
|
60
64
|
),
|
|
61
65
|
)
|
|
62
66
|
}
|
|
@@ -19,6 +19,8 @@ import io.advenue.core.SystemClock
|
|
|
19
19
|
import io.advenue.core.SystemUuids
|
|
20
20
|
import io.advenue.core.TimerScheduler
|
|
21
21
|
import io.advenue.platform.HttpConversionFetcher
|
|
22
|
+
import io.advenue.platform.INSTALL_HOLD_MARGIN_MS
|
|
23
|
+
import io.advenue.platform.REFERRER_DEADLINE_MS
|
|
22
24
|
import io.advenue.platform.HttpUrlTransport
|
|
23
25
|
import io.advenue.platform.IdentityResolution
|
|
24
26
|
import io.advenue.platform.EnrichmentSources
|
|
@@ -31,6 +33,7 @@ import io.advenue.core.tcfToConsent
|
|
|
31
33
|
import io.advenue.platform.collectDeviceInfo
|
|
32
34
|
import io.advenue.platform.readTcf
|
|
33
35
|
import io.advenue.platform.readAppVersion
|
|
36
|
+
import io.advenue.platform.readLaunchedInForeground
|
|
34
37
|
import io.advenue.platform.collectEnrichment
|
|
35
38
|
import io.advenue.platform.sha256Hex
|
|
36
39
|
import io.advenue.platform.resolveIdentity
|
|
@@ -250,13 +253,19 @@ internal class FacadeState {
|
|
|
250
253
|
*/
|
|
251
254
|
fun start(
|
|
252
255
|
application: Application,
|
|
253
|
-
|
|
256
|
+
requested: AdvenueConfig,
|
|
254
257
|
transport: EventTransport? = null,
|
|
255
258
|
sources: EnrichmentSources? = null,
|
|
259
|
+
launchedInForeground: () -> Boolean = ::readLaunchedInForeground,
|
|
260
|
+
logSink: AdvenueLogSink? = null,
|
|
256
261
|
) {
|
|
257
262
|
// Replace-and-shut-down, never add.
|
|
258
263
|
stop()
|
|
259
264
|
|
|
265
|
+
// `debug`: every swallowed failure goes through onError, so logging there
|
|
266
|
+
// covers all of them — wrapped once, before anything below captures it.
|
|
267
|
+
val (config, log) = withDebugLogging(requested, logSink)
|
|
268
|
+
|
|
260
269
|
// The SDK resolves the app version itself (readAppVersion); a config value
|
|
261
270
|
// is ignored — and said so, rather than looking like it took effect.
|
|
262
271
|
if (config.appVersion != null) {
|
|
@@ -275,12 +284,21 @@ internal class FacadeState {
|
|
|
275
284
|
// and nothing starts, so no event carries an invented identifier. The
|
|
276
285
|
// receiver below is the exit — without it the deferral would trade a
|
|
277
286
|
// phantom id for permanent silence.
|
|
278
|
-
|
|
279
|
-
|
|
287
|
+
// Reported as what it is: an IngestError(0) read as an HTTP failure
|
|
288
|
+
// that never happened, naming no cause.
|
|
289
|
+
config.onError(
|
|
290
|
+
"identity.deferred",
|
|
291
|
+
IllegalStateException(
|
|
292
|
+
"credential-encrypted storage is locked (not unlocked since boot); " +
|
|
293
|
+
"the SDK starts when the user unlocks the device",
|
|
294
|
+
),
|
|
295
|
+
)
|
|
296
|
+
// The unwrapped config: the retry wraps it again on its own start.
|
|
297
|
+
registerUnlockRetry(application, requested, transport, sources)
|
|
280
298
|
return
|
|
281
299
|
}
|
|
282
300
|
|
|
283
|
-
val
|
|
301
|
+
val baseTransport =
|
|
284
302
|
transport
|
|
285
303
|
?: HttpUrlTransport(
|
|
286
304
|
endpoint = config.endpoint,
|
|
@@ -289,8 +307,14 @@ internal class FacadeState {
|
|
|
289
307
|
clock = SystemClock(),
|
|
290
308
|
// Diagnostics only: it runs after the batch is already accepted, so
|
|
291
309
|
// nothing it does can turn a successful ingest into a failure.
|
|
292
|
-
onAccepted = { appId ->
|
|
310
|
+
onAccepted = { appId ->
|
|
311
|
+
// A key pasted from the wrong app is otherwise silent.
|
|
312
|
+
if (resolvedAppId != appId) log("[Advenue] ingesting into app $appId")
|
|
313
|
+
resolvedAppId = appId
|
|
314
|
+
},
|
|
293
315
|
)
|
|
316
|
+
val eventTransport =
|
|
317
|
+
if (config.debug) LoggingTransport(baseTransport, log) else baseTransport
|
|
294
318
|
|
|
295
319
|
val scheduler = TimerScheduler()
|
|
296
320
|
val engine =
|
|
@@ -339,10 +363,19 @@ internal class FacadeState {
|
|
|
339
363
|
pendingDeepLinks = mutableListOf()
|
|
340
364
|
}
|
|
341
365
|
|
|
366
|
+
// Before anything else is recorded: a launch that still owes its install
|
|
367
|
+
// stamps it now and holds every flush until the install is enqueued, so
|
|
368
|
+
// the server never sees this launch's session or events ahead of it. The
|
|
369
|
+
// hold outlives the referrer deadline by a margin and then lapses.
|
|
370
|
+
pipe.submit(Command.BeginInstall(REFERRER_DEADLINE_MS + INSTALL_HOLD_MARGIN_MS))
|
|
371
|
+
|
|
342
372
|
// Replayed in arrival order, before the first session, so a deferred deep
|
|
343
373
|
// link is attributed to the launch it belongs to.
|
|
344
374
|
buffered.forEach { sendDeepLink(it) }
|
|
345
375
|
|
|
376
|
+
// A process woken with no activity (a push, a WorkManager job, a
|
|
377
|
+
// broadcast) is not a session: the first activity start opens it instead.
|
|
378
|
+
val inForeground = launchedInForeground()
|
|
346
379
|
val bridge =
|
|
347
380
|
LifecycleBridge(
|
|
348
381
|
application,
|
|
@@ -353,12 +386,19 @@ internal class FacadeState {
|
|
|
353
386
|
// for hours.
|
|
354
387
|
pipe.submit(Command.Flush)
|
|
355
388
|
},
|
|
389
|
+
// Seeded to match: the Foreground below IS this launch's session, so
|
|
390
|
+
// the launcher activity's start that follows must not open a second
|
|
391
|
+
// one — the engine would read the open session as a killed app and
|
|
392
|
+
// split it with a synthetic session_end.
|
|
393
|
+
seededInForeground = inForeground,
|
|
356
394
|
)
|
|
357
395
|
bridge.start()
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
396
|
+
if (inForeground) pipe.submit(Command.Foreground)
|
|
397
|
+
log(
|
|
398
|
+
"[Advenue] initialized — endpoint ${config.endpoint}, " +
|
|
399
|
+
"sdk ${config.sdkVersion ?: AdvenueVersion.CURRENT}, " +
|
|
400
|
+
if (inForeground) "foreground launch" else "background launch, no session yet",
|
|
401
|
+
)
|
|
362
402
|
|
|
363
403
|
// Enrichment then install, off the caller's thread. initialize() returns
|
|
364
404
|
// synchronously — an SDK that blocks Application.onCreate for five seconds
|
|
@@ -64,6 +64,12 @@ constructor(
|
|
|
64
64
|
* API anahtarı dahil tüm olayları şifresiz taşır.
|
|
65
65
|
*/
|
|
66
66
|
val allowInsecureHttp: Boolean = false,
|
|
67
|
+
/**
|
|
68
|
+
* Development aid: logs the SDK's start, every failure [onError] sees and each
|
|
69
|
+
* accepted batch to logcat (tag `Advenue`). Never logs an identifier or a
|
|
70
|
+
* payload. Leave off in production; route failures through [onError] instead.
|
|
71
|
+
*/
|
|
72
|
+
val debug: Boolean = false,
|
|
67
73
|
) {
|
|
68
74
|
init {
|
|
69
75
|
// B5: şemasız endpoint fail-fast — geçersiz şema transportta değil,
|
|
@@ -125,6 +131,8 @@ constructor(
|
|
|
125
131
|
config = config.copy(allowInsecureHttp = value)
|
|
126
132
|
}
|
|
127
133
|
|
|
134
|
+
public fun debug(value: Boolean): Builder = apply { config = config.copy(debug = value) }
|
|
135
|
+
|
|
128
136
|
public fun build(): AdvenueConfig = config
|
|
129
137
|
}
|
|
130
138
|
}
|
|
@@ -137,5 +145,5 @@ constructor(
|
|
|
137
145
|
* the event.
|
|
138
146
|
*/
|
|
139
147
|
public object AdvenueVersion {
|
|
140
|
-
public const val CURRENT: String = "
|
|
148
|
+
public const val CURRENT: String = "1.1.0"
|
|
141
149
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
package io.advenue
|
|
2
|
+
|
|
3
|
+
import android.util.Log
|
|
4
|
+
import io.advenue.core.ClientEvent
|
|
5
|
+
import io.advenue.core.EventTransport
|
|
6
|
+
|
|
7
|
+
/** Where [AdvenueConfig.debug] writes: logcat in an app, a recorder in tests. */
|
|
8
|
+
internal fun interface AdvenueLogSink {
|
|
9
|
+
fun log(message: String)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Logcat, tag `Advenue`. The lines carry failure contexts, counts and the app
|
|
13
|
+
* id the server resolved — never an identifier or a payload. */
|
|
14
|
+
internal object LogcatSink : AdvenueLogSink {
|
|
15
|
+
override fun log(message: String) {
|
|
16
|
+
Log.i("Advenue", message)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Logs each accepted batch; failures already reach the log through the
|
|
21
|
+
* wrapped `onError` (`flush.transport`, `flush.poison`). */
|
|
22
|
+
internal class LoggingTransport(
|
|
23
|
+
private val inner: EventTransport,
|
|
24
|
+
private val log: (String) -> Unit,
|
|
25
|
+
) : EventTransport {
|
|
26
|
+
override fun send(events: List<ClientEvent>) {
|
|
27
|
+
inner.send(events)
|
|
28
|
+
log("[Advenue] sent ${events.size} event(s)")
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* With `debug` on: the config with `onError` also writing to the log, and the
|
|
34
|
+
* log itself. Off: the config unchanged and a log that discards.
|
|
35
|
+
*/
|
|
36
|
+
internal fun withDebugLogging(
|
|
37
|
+
config: AdvenueConfig,
|
|
38
|
+
sink: AdvenueLogSink?,
|
|
39
|
+
): Pair<AdvenueConfig, (String) -> Unit> {
|
|
40
|
+
if (!config.debug) return config to {}
|
|
41
|
+
val out = sink ?: LogcatSink
|
|
42
|
+
val report = config.onError
|
|
43
|
+
val logged =
|
|
44
|
+
config.copy(
|
|
45
|
+
onError = { context, error ->
|
|
46
|
+
out.log("[Advenue] $context: $error")
|
|
47
|
+
report(context, error)
|
|
48
|
+
},
|
|
49
|
+
)
|
|
50
|
+
return logged to { message -> out.log(message) }
|
|
51
|
+
}
|
|
@@ -5,7 +5,20 @@ package io.advenue.core
|
|
|
5
5
|
* poison payload: retrying a 400 forever would block the queue head, and
|
|
6
6
|
* dropping a 429 would discard events over a throttle.
|
|
7
7
|
*/
|
|
8
|
-
internal class IngestError(
|
|
8
|
+
internal class IngestError(
|
|
9
|
+
public val status: Int,
|
|
10
|
+
/**
|
|
11
|
+
* Set when no response arrived at all — DNS, refused connection, timeout, no
|
|
12
|
+
* network. [status] is then 408 so the backoff retries it, but that 408 was
|
|
13
|
+
* never sent by a server, and logging it as one sends whoever debugs it
|
|
14
|
+
* looking at the wrong end (F-SDK-10).
|
|
15
|
+
*/
|
|
16
|
+
public val networkCause: String? = null,
|
|
17
|
+
) :
|
|
18
|
+
Exception(
|
|
19
|
+
if (networkCause != null) "advenue ingest failed: no response (network error: $networkCause)"
|
|
20
|
+
else "advenue ingest failed: $status",
|
|
21
|
+
) {
|
|
9
22
|
public val isRetryable: Boolean
|
|
10
23
|
get() = status == 408 || status == 429 || status >= 500
|
|
11
24
|
}
|
|
@@ -27,6 +27,9 @@ internal sealed class Command {
|
|
|
27
27
|
|
|
28
28
|
public data class TrackInstall(val properties: Map<String, Any?>?) : Command()
|
|
29
29
|
|
|
30
|
+
/** A launch that still owes its install: hold flushes for at most [holdMs]. */
|
|
31
|
+
internal data class BeginInstall(val holdMs: Long) : Command()
|
|
32
|
+
|
|
30
33
|
internal data class SetConsentData(val consent: Consent?) : Command()
|
|
31
34
|
|
|
32
35
|
internal data class SetPushToken(val token: String?, val provider: String?) : Command()
|
|
@@ -131,23 +134,46 @@ internal class CommandPipe(
|
|
|
131
134
|
)
|
|
132
135
|
is Command.SetAppInstanceId -> engine.setAppInstanceId(command.id)
|
|
133
136
|
is Command.TrackInstall -> engine.trackInstall(command.properties)
|
|
137
|
+
is Command.BeginInstall -> engine.beginInstall(command.holdMs)
|
|
134
138
|
is Command.SetConsentData -> engine.setConsentData(command.consent)
|
|
135
139
|
is Command.SetPushToken -> engine.setPushToken(command.token, command.provider)
|
|
136
140
|
Command.ForgetMe -> engine.forgetMe()
|
|
137
141
|
Command.Foreground -> engine.notifyForeground()
|
|
138
142
|
Command.Background -> engine.notifyBackground()
|
|
139
|
-
Command.Flush ->
|
|
143
|
+
Command.Flush -> {
|
|
144
|
+
val batch = engine.beginFlush()
|
|
145
|
+
if (batch != null) {
|
|
146
|
+
sends.put(Send(batch, single = null))
|
|
147
|
+
} else if (engine.isFlushing()) {
|
|
148
|
+
// Asked for while a batch is on the wire: run it when that send
|
|
149
|
+
// finishes rather than drop it — the events tracked meanwhile would
|
|
150
|
+
// otherwise wait a whole auto-flush interval.
|
|
151
|
+
flushRequested = true
|
|
152
|
+
}
|
|
153
|
+
}
|
|
140
154
|
is Command.BatchOutcome ->
|
|
141
155
|
engine.onBatchOutcome(command.error)?.let { sends.put(Send(listOf(it), single = it)) }
|
|
156
|
+
?: flushIfRequested()
|
|
142
157
|
is Command.SingleOutcome ->
|
|
143
158
|
engine.onSingleOutcome(command.event, command.error)?.let {
|
|
144
159
|
sends.put(Send(listOf(it), single = it))
|
|
145
|
-
}
|
|
160
|
+
} ?: flushIfRequested()
|
|
146
161
|
is Command.RunScheduled -> command.work()
|
|
147
162
|
Command.Shutdown -> Unit
|
|
148
163
|
}
|
|
149
164
|
}
|
|
150
165
|
|
|
166
|
+
/** Core-thread only, like everything [handle] touches. */
|
|
167
|
+
private var flushRequested = false
|
|
168
|
+
|
|
169
|
+
/** The flush a send in flight deferred, once that send is over. After a
|
|
170
|
+
* transient failure the backoff makes this a no-op: the timer retries. */
|
|
171
|
+
private fun flushIfRequested() {
|
|
172
|
+
if (!flushRequested || engine.isFlushing()) return
|
|
173
|
+
flushRequested = false
|
|
174
|
+
engine.beginFlush()?.let { sends.put(Send(it, single = null)) }
|
|
175
|
+
}
|
|
176
|
+
|
|
151
177
|
/**
|
|
152
178
|
* Synchronous, non-blocking and **ordered** — the entire reason the ingress
|
|
153
179
|
* is a queue.
|
|
@@ -80,6 +80,19 @@ internal class AdvenueEngine(
|
|
|
80
80
|
private var consentData: Consent? = readConsentData()
|
|
81
81
|
private var pushToken: String? = null
|
|
82
82
|
private var pushProvider: String? = null
|
|
83
|
+
/**
|
|
84
|
+
* F-SDK-3: when this launch began an install that is not recorded yet. The
|
|
85
|
+
* install is stamped with it — the first open, not the moment the referrer
|
|
86
|
+
* read that preceded it finished.
|
|
87
|
+
*/
|
|
88
|
+
private var installStartedAtMs: Long? = null
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Flushes are held until the install is enqueued, so nothing reaches the
|
|
92
|
+
* server ahead of it. Bounded: past this instant the hold lapses, because a
|
|
93
|
+
* lost enrichment thread must never strand the queue.
|
|
94
|
+
*/
|
|
95
|
+
private var installHoldUntilMs: Long? = null
|
|
83
96
|
private var flushing = false
|
|
84
97
|
private var consecutiveFailures = 0
|
|
85
98
|
private var backoffUntilMs = 0L
|
|
@@ -98,6 +111,9 @@ internal class AdvenueEngine(
|
|
|
98
111
|
// B1: PII scrub (Swift PIIScrub ile aynı sözleşme).
|
|
99
112
|
val scrubbed = if (config.piiScrubEnabled) PiiScrub.scrub(properties) else properties
|
|
100
113
|
queue.enqueue(newEvent(type = type, name = name, properties = scrubbed))
|
|
114
|
+
// An event is proof of life: a relaunch after an OS kill measures the gap
|
|
115
|
+
// from the last one (F-SDK-7).
|
|
116
|
+
sessions.heartbeat()
|
|
101
117
|
return true
|
|
102
118
|
}
|
|
103
119
|
|
|
@@ -202,15 +218,55 @@ internal class AdvenueEngine(
|
|
|
202
218
|
* failure.
|
|
203
219
|
*/
|
|
204
220
|
public fun trackInstall(properties: Map<String, Any?>? = null): Boolean {
|
|
221
|
+
val startedAtMs = installStartedAtMs
|
|
222
|
+
// Every outcome below ends the wait: recorded, refused or already sent,
|
|
223
|
+
// nothing is coming that the held events should still wait for.
|
|
224
|
+
releaseInstallHold()
|
|
205
225
|
if (forgotten || (config.requireConsent && !consent)) return false
|
|
206
226
|
if (store.getString(INSTALL_SENT_KEY) == "1") return false
|
|
207
227
|
// B1: install properties de scrub kapsamındadır.
|
|
208
228
|
val scrubbed = if (config.piiScrubEnabled) PiiScrub.scrub(properties) else properties
|
|
209
|
-
|
|
229
|
+
// At the head: the session and custom events recorded while enrichment ran
|
|
230
|
+
// are already queued, and the install must reach the server before them.
|
|
231
|
+
queue.enqueueFirst(
|
|
232
|
+
newEvent(
|
|
233
|
+
type = "install",
|
|
234
|
+
name = "install",
|
|
235
|
+
properties = scrubbed,
|
|
236
|
+
timestampMs = startedAtMs ?: clock.nowMs(),
|
|
237
|
+
),
|
|
238
|
+
)
|
|
210
239
|
store.setString(INSTALL_SENT_KEY, "1")
|
|
211
240
|
return true
|
|
212
241
|
}
|
|
213
242
|
|
|
243
|
+
/**
|
|
244
|
+
* Marks the start of a launch that still owes its install. Called by the
|
|
245
|
+
* facade BEFORE the launch's foreground, while enrichment (Play referrer,
|
|
246
|
+
* advertising id, attestation) runs. Adjust and AppsFlyer both hold every
|
|
247
|
+
* later package behind the first one; this does the same, for at most
|
|
248
|
+
* [holdMs].
|
|
249
|
+
*/
|
|
250
|
+
public fun beginInstall(holdMs: Long) {
|
|
251
|
+
if (forgotten || store.getString(INSTALL_SENT_KEY) == "1") return
|
|
252
|
+
val now = clock.nowMs()
|
|
253
|
+
installStartedAtMs = now
|
|
254
|
+
installHoldUntilMs = now + holdMs
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** Whether a pending install still holds flushes back. */
|
|
258
|
+
private fun heldForInstall(): Boolean {
|
|
259
|
+
val until = installHoldUntilMs ?: return false
|
|
260
|
+
if (clock.nowMs() < until) return true
|
|
261
|
+
installHoldUntilMs = null
|
|
262
|
+
return false
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
private fun releaseInstallHold() {
|
|
266
|
+
installStartedAtMs = null
|
|
267
|
+
installHoldUntilMs = null
|
|
268
|
+
}
|
|
269
|
+
|
|
214
270
|
/**
|
|
215
271
|
* Erasure, including the install-scoped identifiers.
|
|
216
272
|
*
|
|
@@ -228,6 +284,7 @@ internal class AdvenueEngine(
|
|
|
228
284
|
*/
|
|
229
285
|
public fun forgetMe() {
|
|
230
286
|
forgotten = true
|
|
287
|
+
releaseInstallHold()
|
|
231
288
|
queue.clear()
|
|
232
289
|
sessions.reset()
|
|
233
290
|
consent = false
|
|
@@ -290,9 +347,21 @@ internal class AdvenueEngine(
|
|
|
290
347
|
/** Whether a transport is wired at all. */
|
|
291
348
|
internal fun canFlush(): Boolean = transport != null
|
|
292
349
|
|
|
350
|
+
/** Whether a batch is on the wire (its outcome not yet consumed). */
|
|
351
|
+
internal fun isFlushing(): Boolean = flushing
|
|
352
|
+
|
|
293
353
|
/** Decides what to send. Null means there is nothing to do. */
|
|
294
354
|
internal fun beginFlush(): List<ClientEvent>? {
|
|
295
|
-
|
|
355
|
+
// The auto-flush timer is the foreground tick: it keeps the open
|
|
356
|
+
// sub-session's last activity fresh even when the app records nothing.
|
|
357
|
+
if (!forgotten) sessions.heartbeat()
|
|
358
|
+
if (
|
|
359
|
+
transport == null ||
|
|
360
|
+
flushing ||
|
|
361
|
+
queue.size == 0 ||
|
|
362
|
+
clock.nowMs() < backoffUntilMs ||
|
|
363
|
+
heldForInstall()
|
|
364
|
+
) {
|
|
296
365
|
return null
|
|
297
366
|
}
|
|
298
367
|
// Clamped to the wire's limit, not trusted. The server answers 400 for a
|
|
@@ -408,13 +477,14 @@ internal class AdvenueEngine(
|
|
|
408
477
|
type: String,
|
|
409
478
|
name: String,
|
|
410
479
|
properties: Map<String, Any?>?,
|
|
480
|
+
timestampMs: Long = clock.nowMs(),
|
|
411
481
|
): ClientEvent =
|
|
412
482
|
ClientEvent(
|
|
413
483
|
id = uuid.next(),
|
|
414
484
|
deviceId = config.deviceId,
|
|
415
485
|
type = type,
|
|
416
486
|
name = name,
|
|
417
|
-
timestamp = iso8601(
|
|
487
|
+
timestamp = iso8601(timestampMs),
|
|
418
488
|
platform = config.platform,
|
|
419
489
|
installationId = config.installationId,
|
|
420
490
|
appVersion = config.appVersion,
|
|
@@ -35,6 +35,18 @@ internal class EventQueue(
|
|
|
35
35
|
schedulePersist()
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Puts an event at the HEAD of the queue — the install, which is the first
|
|
40
|
+
* package of an installation and must not be preceded by the session and
|
|
41
|
+
* custom events recorded while enrichment ran. Over the cap, the oldest
|
|
42
|
+
* events behind it are dropped, never the event just placed.
|
|
43
|
+
*/
|
|
44
|
+
public fun enqueueFirst(event: ClientEvent) {
|
|
45
|
+
events.add(0, event)
|
|
46
|
+
while (events.size > maxSize) events.removeAt(1)
|
|
47
|
+
schedulePersist()
|
|
48
|
+
}
|
|
49
|
+
|
|
38
50
|
public fun peek(max: Int): List<ClientEvent> = events.take(max)
|
|
39
51
|
|
|
40
52
|
public fun ack(sent: List<ClientEvent>) {
|