PrivacyAwareAnalyticsSampler class

Decorator over a real AnalyticsProvider (FEAT-83) combining 3 privacy/ volume controls, each independently audit-countable via auditSnapshot:

  1. Consent gate — same default-deny rule ConsentGatedAnalyticsProvider already enforces (no ConsentStateService registered, or ConsentCategory.analytics not 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.
  2. Deterministic sampling — sessionSeed (defaults to AppSessionTracker.current's sessionId) + 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.
  3. 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.
  4. 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.0 always 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