first commit
This commit is contained in:
204
docs/observability/monitor/analytics-browser/README.md
Normal file
204
docs/observability/monitor/analytics-browser/README.md
Normal file
@@ -0,0 +1,204 @@
|
||||
# @suaveplan/analytics-browser - Browser-tier client-side product analytics: AnalyticsTracker / BatchTransport / BeaconTransport / SessionManager with consent gate, PII scrubber, head-sampling, durable StorageBackend (memory + Web Storage), Zod-validated events, AbortController + timeoutMs on every fetch, OTel counters + histograms, and a typed EventBus lifecycle bus on @suaveplan/events.
|
||||
|
||||
Browser-tier client-side product analytics — `AnalyticsTracker`, `BatchTransport`, `BeaconTransport`, and `SessionManager` with consent gate, PII scrubber, head sampling, durable storage, Zod-validated events, AbortController + timeoutMs on every fetch, and OpenTelemetry counters / histograms. Lifecycle observability is exposed through the `@suaveplan/events` `EventBus`.
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
## Documentation tree
|
||||
|
||||
- [consent](./src/consent/consent.md) — Implements the `ConsentPolicy` SPI bundled with `@suaveplan/analytics-browser`. The tracker calls `policy.isAllowed(eve…
|
||||
- [consent](./src/consent/consent.md) — Implements the `ConsentPolicy` SPI bundled with `@suaveplan/analytics-browser`. The tracker calls `policy.isAllowed(eve…
|
||||
- [error-codes](./src/error-codes/error-codes.md) — Defines the stable error-code registry and typed error class for `@suaveplan/analytics-browser`. The registry uses `@su…
|
||||
- [error-codes](./src/error-codes/error-codes.md) — Defines the stable error-code registry and typed error class for `@suaveplan/analytics-browser`. The registry uses `@su…
|
||||
- [id](./src/internal/id/id.md) — Internal cryptographically-random ID generator shared by the analytics tracker, the session manager, and any consumer t…
|
||||
- [id](./src/internal/id/id.md) — Internal cryptographically-random ID generator shared by the analytics tracker, the session manager, and any consumer t…
|
||||
- [pii](./src/pii/pii.md) — Implements field-level PII scrubbing for `@suaveplan/analytics-browser`. `scrubPII(event, rules)` takes an `AnalyticsEv…
|
||||
- [pii](./src/pii/pii.md) — Implements field-level PII scrubbing for `@suaveplan/analytics-browser`. `scrubPII(event, rules)` takes an `AnalyticsEv…
|
||||
- [sampling](./src/sampling/sampling.md) — Implements head-based sampling for `@suaveplan/analytics-browser`. The tracker calls `sampler.shouldSample(event)` afte…
|
||||
- [sampling](./src/sampling/sampling.md) — Implements head-based sampling for `@suaveplan/analytics-browser`. The tracker calls `sampler.shouldSample(event)` afte…
|
||||
- [schema](./src/schema/analytics-event-schema.md) — Defines the canonical Zod schema for `AnalyticsEvent` and a `validateAnalyticsEvent` parse helper. The schema lets cons…
|
||||
- [analytics-event-schema](./src/schema/analytics-event-schema.md) — Defines the canonical Zod schema for `AnalyticsEvent` and a `validateAnalyticsEvent` parse helper. The schema lets cons…
|
||||
- [session](./src/session/session.md) — Tracks the lifecycle of a single browser session for `@suaveplan/analytics-browser`. The `SessionManager` owns the sess…
|
||||
- [session](./src/session/session.md) — Tracks the lifecycle of a single browser session for `@suaveplan/analytics-browser`. The `SessionManager` owns the sess…
|
||||
- [storage](./src/storage/storage.md) — Implements the `StorageBackend` SPI bundled with `@suaveplan/analytics-browser`. Two backends ship: `MemoryStorageBacke…
|
||||
- [storage](./src/storage/storage.md) — Implements the `StorageBackend` SPI bundled with `@suaveplan/analytics-browser`. Two backends ship: `MemoryStorageBacke…
|
||||
- [telemetry](./src/telemetry/telemetry.md) — Defines the OpenTelemetry instruments emitted by `@suaveplan/analytics-browser`. Three counters cover the accept / flus…
|
||||
- [telemetry](./src/telemetry/telemetry.md) — Defines the OpenTelemetry instruments emitted by `@suaveplan/analytics-browser`. Three counters cover the accept / flus…
|
||||
- [tracker](./src/tracker/tracker.md) — `AnalyticsTracker` is the central orchestrator of `@suaveplan/analytics-browser`. It accepts user-facing events through…
|
||||
- [tracker](./src/tracker/tracker.md) — `AnalyticsTracker` is the central orchestrator of `@suaveplan/analytics-browser`. It accepts user-facing events through…
|
||||
- [batch](./src/transport/batch.md) — Implements `BatchTransport` — the HTTP transport used by `AnalyticsTracker` for the steady-state, non-unload flush path…
|
||||
- [beacon](./src/transport/beacon.md) — Implements `BeaconTransport` — the page-unload-safe transport used by `AnalyticsTracker.shutdown()` and any consumer wi…
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
bun add @suaveplan/analytics-browser
|
||||
```
|
||||
|
||||
## Quick start
|
||||
|
||||
```ts
|
||||
import { AnalyticsTracker, BatchTransport } from "@suaveplan/analytics-browser";
|
||||
|
||||
const tracker = new AnalyticsTracker(
|
||||
new BatchTransport({ endpoint: "/api/analytics" }),
|
||||
);
|
||||
|
||||
tracker.identify("user-123");
|
||||
tracker.track("clicked", { surface: "header" });
|
||||
await tracker.flush();
|
||||
```
|
||||
|
||||
## Subpath exports
|
||||
|
||||
| Path | Contents |
|
||||
|---|---|
|
||||
| `@suaveplan/analytics-browser` | Everything (single barrel). |
|
||||
| `@suaveplan/analytics-browser/tracker` | `AnalyticsTracker`. |
|
||||
| `@suaveplan/analytics-browser/transport` | `BatchTransport`, `BeaconTransport`. |
|
||||
| `@suaveplan/analytics-browser/session` | `SessionManager`. |
|
||||
| `@suaveplan/analytics-browser/consent` | `AllowAllPolicy`, `DoNotTrackPolicy`. |
|
||||
| `@suaveplan/analytics-browser/pii` | `scrubPII`. |
|
||||
| `@suaveplan/analytics-browser/sampling` | `HeadSampler`. |
|
||||
| `@suaveplan/analytics-browser/storage` | `MemoryStorageBackend`, `WebStorageBackend`. |
|
||||
| `@suaveplan/analytics-browser/schema` | `AnalyticsEventSchema`, `validateAnalyticsEvent`. |
|
||||
| `@suaveplan/analytics-browser/error-codes` | `ANALYTICS_BROWSER_CODES`, `AnalyticsBrowserError`. |
|
||||
| `@suaveplan/analytics-browser/telemetry` | OTel counters / histograms + helpers. |
|
||||
| `@suaveplan/analytics-browser/types` | Type-only exports. |
|
||||
|
||||
## Consent gate
|
||||
|
||||
The tracker calls `consentPolicy.isAllowed(event)` before sampling, scrubbing, or queueing. Two built-ins ship:
|
||||
|
||||
- `AllowAllPolicy` — permits every event (default).
|
||||
- `DoNotTrackPolicy` — honours `navigator.doNotTrack === "1"`.
|
||||
|
||||
```ts
|
||||
import { AnalyticsTracker } from "@suaveplan/analytics-browser/tracker";
|
||||
import { DoNotTrackPolicy } from "@suaveplan/analytics-browser/consent";
|
||||
|
||||
const tracker = new AnalyticsTracker(transport, {
|
||||
consentPolicy: new DoNotTrackPolicy(),
|
||||
});
|
||||
```
|
||||
|
||||
Implement `ConsentPolicy` yourself to honour a cookie banner / IAB TCF / regional toggle.
|
||||
|
||||
## PII scrubber
|
||||
|
||||
```ts
|
||||
import { AnalyticsTracker } from "@suaveplan/analytics-browser/tracker";
|
||||
|
||||
const tracker = new AnalyticsTracker(transport, {
|
||||
scrubRules: [
|
||||
{ field: "email", strategy: "redact" }, // → "[REDACTED]"
|
||||
{ field: "phone", strategy: "hash" }, // → 8-char FNV-1a hex
|
||||
{ field: "card.number", strategy: "drop" },
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
Dotted field paths walk into nested `properties` objects. The FNV-1a fingerprint is deterministic across calls — useful for cross-event correlation without revealing the value.
|
||||
|
||||
## Sampling
|
||||
|
||||
```ts
|
||||
import { HeadSampler } from "@suaveplan/analytics-browser/sampling";
|
||||
|
||||
const sampler = new HeadSampler(0.1, {
|
||||
error_reported: 1, // always keep errors
|
||||
clicked: 1, // always keep clicks
|
||||
});
|
||||
```
|
||||
|
||||
`HeadSampler` clamps rates to `[0, 1]`; `NaN` clamps to `0`. The constructor accepts an injectable `random: () => number` so deterministic tests are trivial.
|
||||
|
||||
## Durable storage
|
||||
|
||||
```ts
|
||||
import { AnalyticsTracker } from "@suaveplan/analytics-browser/tracker";
|
||||
import { WebStorageBackend } from "@suaveplan/analytics-browser/storage";
|
||||
|
||||
const tracker = new AnalyticsTracker(transport, {
|
||||
storage: new WebStorageBackend({
|
||||
storage: localStorage,
|
||||
key: "@my-app/analytics",
|
||||
maxBytes: 1_048_576,
|
||||
}),
|
||||
});
|
||||
|
||||
// Restore events persisted across reload.
|
||||
await tracker.restore();
|
||||
```
|
||||
|
||||
The bundled backends are `MemoryStorageBackend` (default) and `WebStorageBackend`. An IndexedDB adapter will slot into the same SPI once `@suaveplan/storage-browser` lands; the contract (`enqueue` / `dequeueAll` / `clear` / `getDepth`) will not change.
|
||||
|
||||
## Unload-safe flushing
|
||||
|
||||
```ts
|
||||
import { BeaconTransport } from "@suaveplan/analytics-browser/transport";
|
||||
|
||||
const beacon = new BeaconTransport({
|
||||
endpoint: "/api/analytics",
|
||||
fallbackToFetch: true,
|
||||
});
|
||||
|
||||
window.addEventListener("visibilitychange", () => {
|
||||
if (document.visibilityState === "hidden") {
|
||||
void tracker.flush();
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener("pagehide", () => {
|
||||
void tracker.flush();
|
||||
});
|
||||
```
|
||||
|
||||
`BeaconTransport` uses `navigator.sendBeacon` first and optionally falls back to `fetch({ keepalive: true })`. When the host rejects the beacon and fallback is disabled, the transport throws `AnalyticsBrowserError(BEACON_REJECTED)`.
|
||||
|
||||
## Lifecycle event bus
|
||||
|
||||
```ts
|
||||
tracker.events.on("track", ({ event }) => {});
|
||||
tracker.events.on("flush", ({ eventCount, success, latencyMs }) => {});
|
||||
tracker.events.on("drop", ({ reason, event }) => {});
|
||||
tracker.events.on("error", ({ error, context }) => {});
|
||||
tracker.events.on("shutdown", () => {});
|
||||
```
|
||||
|
||||
The bus is a real `@suaveplan/events` `EventBus<AnalyticsEventMap>` — every payload is a single object (not a tuple).
|
||||
|
||||
## OpenTelemetry
|
||||
|
||||
`@suaveplan/analytics-browser` declares five OTel instruments under the meter name `@suaveplan/analytics-browser`:
|
||||
|
||||
| Instrument | Kind | Tags |
|
||||
|---|---|---|
|
||||
| `analytics_browser.events_tracked_total` | counter | `name` |
|
||||
| `analytics_browser.events_flushed_total` | counter | `status` (`success` / `failure`) |
|
||||
| `analytics_browser.events_dropped_total` | counter | `reason` (`consent` / `sampling` / `queue_full` / `shutdown` / `invalid`) |
|
||||
| `analytics_browser.batch_size` | histogram | — |
|
||||
| `analytics_browser.flush_latency_ms` | histogram | — (`ms`) |
|
||||
|
||||
No setup is required — the instruments are no-ops when no `MeterProvider` is registered.
|
||||
|
||||
## Migration from upstream `@suaveplan/analytics-browser@1.0.6`
|
||||
|
||||
The only behavioural change is the lifecycle bus shape. Upstream used `EventEmitter` with tuple payloads:
|
||||
|
||||
```ts
|
||||
// upstream
|
||||
tracker.events.on("track", (event) => {});
|
||||
tracker.events.on("flush", (count, success) => {});
|
||||
```
|
||||
|
||||
uses `EventBus` with single-object payloads:
|
||||
|
||||
```ts
|
||||
// suaveplan
|
||||
tracker.events.on("track", ({ event }) => {});
|
||||
tracker.events.on("flush", ({ eventCount, success, latencyMs }) => {});
|
||||
```
|
||||
|
||||
Constructor signature, public method names, and behavioural semantics of `track` / `identify` / `flush` / `shutdown` are preserved.
|
||||
|
||||
Reference in New Issue
Block a user