liquid_analytics 0.2.2
liquid_analytics: ^0.2.2 copied to clipboard
Vendor-neutral analytics for Flutter. Emit one typed event and fan it out to any set of providers through a middleware pipeline and a built-in consent gate.
Changelog #
0.2.2 #
Fixes from a review of how the library handles personal data. No type signatures change; two behaviour changes are called out below.
-
Fixed: an unreadable delivery queue could permanently break startup.
QueuedEntry.fromJsonasserted every field and resolved both enums by name, and nothing between it andLiquid.init()caught the throw. A write truncated by a process kill or a full disk — or a queue persisted by a build with aConsentCategorythis one no longer has — therefore failed initialization on every launch, permanently, since nothing ever rewrites the bad blob.JsonDeliveryStorenow skips entries it cannot decode and treats an undecodable blob as an empty queue, and the delivery engine starts empty rather than propagating a failure from a host-supplied store. -
Fixed: revoking consent stopped delivery but not retention. Queued entries have been re-checked at drain time since 0.1.1, so nothing was ever sent on a stale grant — but a queue that never drained again kept them, on disk wherever a
DeliveryStoreis configured. Consent changes now evict matching entries immediately and write through the persistence debounce, because the point of a withdrawal is to stop retaining the data. -
Fixed: an entry inside its retry backoff survived a revocation. The drain returned not-yet-due entries to the queue before consulting consent, so a revoked event could wait out its backoff — up to
maxBackoff, five minutes by default — before being dropped. Consent is now judged ahead of the retry schedule. -
Behaviour change:
PiiRedactoralso scrubscontext. No bundled sink forwards context, so nothing was reaching a provider, but context is serialized into the delivery queue — an unredacted value was written to whatever store the app configured, and a custom sink that forwarded context would have leaked silently.Migration: none required; expect context values under a redacted key to be replaced.
-
Behaviour change:
DebugSinkis silent in release builds. Messages carry whatever properties and traits the caller attached, and platform logs are readable outside the app, so a debug sink left in a shipped build leaked data the consent gate never saw. The newenabledflag defaults to!kReleaseMode, following the precedentRecordingSinkset withpostDevToolsEvents.Migration: pass
DebugSink(enabled: true)if you were relying on it logging in release. -
JsonDeliveryStorenow documents what it actually stores — the full message, in plain text, until the queue drains — with an atomic write-and-rename example and a note to keep the file out of automatic backups.
0.2.1 #
Delivery-engine hardening. No public API changes; one new optional option.
- Fixed: events enqueued during a flush could be silently dropped. The drain worked from a snapshot and then rebuilt the queue from it, so anything enqueued while it awaited a slow sink was discarded. Later arrivals are now re-attached. This could lose events on any sink slow enough to overlap an enqueue, and is the most significant fix in this release.
- Fixed:
flush()no longer no-ops when called concurrently. It previously returned an already-completed future, soawait liquid.flush()could return before anything was flushed — including on the app-backgrounding path. A call made mid-flush now schedules exactly one follow-up pass and every such caller shares it. - Removed the 250 ms polling timer. The engine polled every flush policy
four times a second for the life of the process.
FlushPolicy.shouldFlushis now a notifying setter, so policies wake the engine the moment a flush is due and an idle client costs nothing. Custom policies that assignshouldFlushdirectly keep working unchanged. - Queue writes are debounced.
DeliveryStore.savereplaces the whole queue, so persisting per enqueue was O(n) per event and O(n²) across a burst — exactly the outage the queue exists to survive. Tune with the newDeliveryOptions.persistDebounce(default 200 ms;Duration.zerorestores write-through). The queue is always written on flush and on dispose. - Fixed: events emitted before
init()are no longer discarded. Theassertguarding this is stripped in release builds, andstart()then replaced the queue with the stored one. Loaded entries are now merged ahead of anything already queued.
0.2.0 #
Two breaking behaviour changes. Both make the library fail safe rather than fail open; neither changes a type signature.
-
BREAKING —
ConsentPolicy.mapis now an explicit allowlist. Previously a category with no entry in the map was delivered to every capable sink. Now a non-empty map means a category with no entry reaches no sink, so forgetting to map a category withholds data instead of broadcasting it. An entirely empty map still means "routing is not configured" and every capable sink stays eligible, soConsentPolicy.allowAlland the default policy behave as before. Debug builds log a one-time warning per unmapped category.Migration: if you pass a non-empty
map, add an entry for every category you emit. -
BREAKING —
PiiRedactornow recurses. Redacted keys were matched only at the top level ofpropertiesandtraits, so{'user': {'email': ...}}reached every destination that forwards nested structures (PostHog, Mixpanel, Amplitude, Segment). Keys are now matched at any depth, including inside maps held in lists.Migration: none required, but expect more values to be redacted. Iterables are normalized to lists, which is what the provider mappers already did.
0.1.1 #
Two correctness fixes in the consent gate and the event pipeline. Both are behaviour changes; neither changes the public API.
- Consent is now re-checked when the delivery queue drains. Previously
consent was evaluated only when an event was emitted and when it was
enqueued. An event that was queued (batch mode, or awaiting retry after a
sink failure) while consent was granted would still be delivered even if the
user revoked consent before the flush — and because the queue is persisted,
that could happen on a later launch. Queued entries whose category has since
been denied, or which no longer route to the sink under
ConsentPolicy.map, are now dropped; entries whose category has returned to undecided are held rather than sent. - Event order is now preserved when middleware is asynchronous.
LiquidMiddleware.applyreturns aFutureOr, but the client did not wait for one message to finish the pipeline before starting the next, so any middleware that awaited (device info, disk, network) let later events overtake earlier ones and reach sinks out of order. The pipeline is now serialized, which also fixes ordering of the consent buffer replay. A slow sink now delays the events behind it — useDeliveryOptions.batchModefor HTTP-heavy destinations. Liquid.flush()now waits for in-flight pipeline work before draining, so events emitted immediately before the call are included.- A middleware that throws no longer stops subsequent events from being processed.
- Add an
example/covering typed events, consent gating and fan-out.
0.1.0 #
- Initial release of
liquid_analytics. - Typed events, middleware pipeline, consent gate with buffer/replay.
- Offline delivery queue with exponential backoff and optional persistence.
- Batch mode with count/timer flush policies; lifecycle flush.