Skip to content

Commit a8815e6

Browse files
feat(audience): add ImmutableAudience singleton + GDPR (SDK-147)
Adds the ImmutableAudience static entry point that ties DiskStore, EventQueue, HttpTransport, ConsentStore, and Identity together into the public SDK surface for track/identify/alias plus the GDPR lifecycle (SetConsent, Reset, DeleteData). Public API: Init, Track (IEvent + string overloads), Identify, Alias, Reset, DeleteData, SetConsent, FlushAsync, Shutdown. Behavioural guarantees: - Concurrency: all public methods are thread-safe. Track is the allocation-light hot path - it enqueues a dictionary and returns; serialisation runs on the drain thread. - Consent state machine: None drops everything; Anonymous discards userId from track messages and drops identify/alias; Full sends everything. Downgrades rewrite the on-disk queue. - Never-throw: public methods catch and route every exception to the OnError callback, including background drain failures and shutdown flush errors. - Shutdown hygiene: final synchronous drain, drain-thread join with timeout, HttpTransport disposal, idempotent (safe to call from application exit + Unity quit). - Persistence round-trip: identity, consent, and queued events all survive process restart via atomic write-temp-then-move. Code changes: - ImmutableAudience.cs: 549-line rewrite of the stub (Init wiring, public API surface, consent state machine, backend sync loop). - AudienceConfig.cs: adds OnError, PersistentDataPath, PackageVersion, ShutdownFlushTimeoutMs, HttpHandler (test seam). - ConsentLevel.cs: ConsentLevelExtensions.ToWireString for the tracking-consent backend audit payload. - Transport/EventQueue.cs: swaps ConcurrentQueue<string> for ConcurrentQueue<Dictionary<string,object>> so Json.Serialize runs on the drain thread; adds _drainLock to serialise drain against PurgeAll / ApplyAnonymousDowngrade so a TryDequeue'd event cannot hit disk after consent revocation wiped it; EnqueueChecked closes the same window for new Track calls that race SetConsent(None). See _drainLock comment for full race. - Transport/DiskStore.cs: DeleteAll and ApplyAnonymousDowngrade for the queued-event side of consent revocation. The latter strips userId from queued track messages via JsonReader -> Json.Serialize round-trip (numeric-precision caveat documented in-file - exact for realistic Purchase.Value amounts). Tests: - ImmutableAudienceTests.cs: 737 lines covering Init/Shutdown lifecycle, the Track/Identify/Alias/Reset/DeleteData/FlushAsync public surface, the consent state machine transitions, the never-throw contract, and the privacy invariants. - ConsentSyncTests.cs: backend consent audit round-trip. - DeleteDataTests.cs: queue-purge + backend-call + Identity cache clearing interaction. - Transport/DiskStoreTests.cs, Transport/EventQueueTests.cs: coverage for the new GDPR ops. The preceding four peels (remove duplicate IdentityTests, mirror Runtime layout onto Tests, SSOT constants + scaffolding, ConsentStore) were carved out of this commit to keep the singleton diff focused on the parts that only make sense together.
1 parent 1720e85 commit a8815e6

10 files changed

Lines changed: 1915 additions & 29 deletions

File tree

src/Packages/Audience/Runtime/AudienceConfig.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using System;
2+
13
namespace Immutable.Audience
24
{
35
// Configuration passed to ImmutableAudience.Init.
@@ -20,5 +22,20 @@ public class AudienceConfig
2022

2123
// Flush as soon as this many events are queued.
2224
public int FlushSize { get; set; } = Constants.DefaultFlushSize;
25+
26+
// Optional error callback.
27+
public Action<AudienceError> OnError { get; set; }
28+
29+
// Directory the SDK uses for identity, consent, and queued events.
30+
public string PersistentDataPath { get; set; }
31+
32+
// Library version sent on every message.
33+
public string PackageVersion { get; set; } = Constants.LibraryVersion;
34+
35+
// Maximum time Shutdown waits for the final flush.
36+
public int ShutdownFlushTimeoutMs { get; set; } = 2_000;
37+
38+
// Test seam for HttpTransport; not part of the public API.
39+
internal System.Net.Http.HttpMessageHandler HttpHandler { get; set; }
2340
}
2441
}

src/Packages/Audience/Runtime/ConsentLevel.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,18 @@ public enum ConsentLevel
1010
// Full tracking, including identity.
1111
Full
1212
}
13+
14+
internal static class ConsentLevelExtensions
15+
{
16+
// Throws on unknown casts rather than emitting null: a null value
17+
// would poison the backend consent log.
18+
internal static string ToWireString(this ConsentLevel level) => level switch
19+
{
20+
ConsentLevel.None => "none",
21+
ConsentLevel.Anonymous => "anonymous",
22+
ConsentLevel.Full => "full",
23+
_ => throw new System.ArgumentOutOfRangeException(
24+
nameof(level), level, "Unhandled ConsentLevel"),
25+
};
26+
}
1327
}

0 commit comments

Comments
 (0)