PrivacyAwareAnalyticsSampler class
Decorator over a real AnalyticsProvider (FEAT-83) combining 3 privacy/ volume controls, each independently audit-countable via auditSnapshot:
- Consent gate — same default-deny rule ConsentGatedAnalyticsProvider
already enforces (no ConsentStateService registered, or
ConsentCategory.analyticsnot ConsentStatus.granted, drops the event). Duplicated here (not composed via that decorator) so this class stays a single drop-in registration point with one combined audit trail —analyticsProvider.dart's own contract makes the check itself trivial (1 line), so the duplication costs nothing real. - Deterministic sampling —
sessionSeed(defaults to AppSessionTracker.current'ssessionId) + the event name are hashed via fnv1aHash into a stable bucket, so the SAME event name within the SAME session always gets the SAME keep/drop decision. This matters for funnel counts: a per-call coin flip would let event #1 of a repeating action survive while event #2 in the same session silently vanishes, corrupting any per-session aggregate. defaultSamplingRate applies unless samplingRateOverrides names that event specifically. - PII redaction, before anything else downstream — when registry
is supplied, every event is validated through it (same
SdkEventSchemaRegistry FEAT-77 already ships) BEFORE the rate-limit
check and BEFORE reaching AnalyticsProvider itself — a
pii-marked field, or an outright schema rejection, never reaches the rate limiter's counters or the real provider. - Rate limit / backpressure — a fixed-window counter
(maxEventsPerWindow per windowSize); once the window's budget is
spent, further events in that window are dropped immediately
(
AnalyticsDropReason.rateLimited) rather than buffered — gameplay code calling logEvent never blocks or awaits anything, and an unbounded buffer that never gets flushed (e.g. offline) can never grow — dropping IS the backpressure, not a queue with its own lifecycle to manage.
A throwing inner provider is caught and forwarded to CrashReporter.maybe (same contract SchemaValidatedAnalyticsProvider already uses) — never crashes gameplay code.
- Implemented types
Constructors
-
PrivacyAwareAnalyticsSampler(AnalyticsProvider _inner, {SdkEventSchemaRegistry? registry, double defaultSamplingRate = 1.0, Map<
String, double> samplingRateOverrides = const {}, int maxEventsPerWindow = 20, Duration windowSize = const Duration(seconds: 1), String sessionSeed()?, int nowMs()?})
Properties
- auditSnapshot → AnalyticsSamplingAudit
-
Cumulative forwarded/dropped-by-reason counts since construction.
no setter
- defaultSamplingRate → double
-
Sampling rate for an event name not listed in samplingRateOverrides.
1.0(default) never drops for sampling;0.0always does.final - hashCode → int
-
The hash code for this object.
no setterinherited
- maxEventsPerWindow → int
-
Max events forwarded within one windowSize window before further
events in that same window are dropped.
final
- registry → SdkEventSchemaRegistry?
-
Optional PII/shape gate — omit to pass every param through unredacted
(a consumer relying purely on this sampler's consent/sampling/rate
controls without a schema registry).
final
- runtimeType → Type
-
A representation of the runtime type of the object.
no setterinherited
-
samplingRateOverrides
→ Map<
String, double> -
Per-event-name sampling rate, takes precedence over defaultSamplingRate.
final
- windowSize → Duration
-
final
Methods
-
logEvent(
String name, [Map< String, Object?> ? params]) → void -
override
-
noSuchMethod(
Invocation invocation) → dynamic -
Invoked when a nonexistent method or property is accessed.
inherited
-
toString(
) → String -
A string representation of this object.
inherited
Operators
-
operator ==(
Object other) → bool -
The equality operator.
inherited