@cross-deck/web 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +35 -0
- package/README.md +19 -0
- package/dist/index.cjs +435 -29
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +53 -3
- package/dist/index.d.ts +53 -3
- package/dist/index.mjs +435 -29
- package/dist/index.mjs.map +1 -1
- package/dist/react.cjs +435 -29
- package/dist/react.cjs.map +1 -1
- package/dist/react.mjs +435 -29
- package/dist/react.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,41 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@cross-deck/web` will be documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
4
4
|
|
|
5
|
+
## [0.6.0] — 2026-05-10
|
|
6
|
+
|
|
7
|
+
Bank-grade analytics enrichment. Two additive changes that close the gap between Crossdeck's analytics surface and Google Analytics 4 / Google Ads dashboards: identity continuity that survives cleared storage, and first-touch acquisition attribution attached to every event of a session. No public API changes — `Crossdeck.init({...})` callsites do not need to change.
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **Identity continuity — dual-store redundancy.** The SDK now writes `anonymousId` and `crossdeckCustomerId` to BOTH `localStorage` (primary) and a 1st-party `document.cookie` (secondary). On boot it reads both and prefers primary; if primary is empty, it recovers from the cookie and resyncs primary. This protects against ITP localStorage purges, "clear site data" actions, and aggressive privacy extensions — a returning user keeps the same Crossdeck identity instead of becoming a phantom new visitor on dashboards. See `sdks/SDK_TRUTH.md` § "Identity continuity — bank-grade redundancy" for the full contract.
|
|
12
|
+
- **`CookieStorage` adapter** in `storage.ts`. Sets `Path=/`, `Max-Age=63072000` (2y), `SameSite=Lax`, `Secure` (when over HTTPS — omitted on `http://localhost` so dev works without a TLS cert). Encodes/decodes cookie names + values defensively so embedded `;` and `=` survive round-trip.
|
|
13
|
+
- **First-touch acquisition capture in `AutoTracker`.** On every `session.started` the SDK reads `window.location.search` and `document.referrer` and captures `utm_source`, `utm_medium`, `utm_campaign`, `utm_content`, `utm_term`, plus `referrer`. Non-empty values are auto-attached to every subsequent event of that session — matching GA4's session-pinned attribution semantics. SPA route changes mid-session do NOT re-read the URL; a new session (>30 min idle, or explicit `resetSession()`) re-captures off the current URL.
|
|
14
|
+
- **`AutoTracker.currentAcquisition`** getter. Returns the captured-once-per-session acquisition context for inspection / tests / framework bindings. Returns empty strings (not undefined) when there's no active session so callers can spread without conditional logic.
|
|
15
|
+
- **`captureAcquisition()` exported** from `auto-track.ts` for unit testing acquisition extraction in isolation.
|
|
16
|
+
- **18 new tests** (138 total, up from 120):
|
|
17
|
+
- `storage.test.ts` — 6 cases covering `CookieStorage` round-trip, URL-encoding survival, attribute emission (Path / SameSite / Max-Age / Secure on HTTPS, Secure-omitted on HTTP), null on broken cookies, no-op in Node (no `document`).
|
|
18
|
+
- `identity.test.ts` — 6 cases covering the redundancy contract: writes-to-both, recovery from secondary when primary cleared, recovery from primary when secondary cleared, primary-wins-on-conflict, set/reset both, defence-in-depth against a throwing secondary.
|
|
19
|
+
- `auto-track.test.ts` — 5 cases: `captureAcquisition` reads utm_*, returns empty for clean URLs, `currentAcquisition` is session-pinned (SPA navigation does NOT change it mid-session), `resetSession` re-captures off the current URL, returns empty when no session exists.
|
|
20
|
+
|
|
21
|
+
### Server-side enrichment (lands without an SDK upgrade)
|
|
22
|
+
|
|
23
|
+
The 0.6.0 SDK pairs with these backend changes that started populating ClickHouse columns ahead of this release — every existing 0.5.0 install starts seeing them in dashboards immediately:
|
|
24
|
+
|
|
25
|
+
- **Geography** — `events.country` populated from the Cloudflare `CF-IPCountry` header at `/v1/events`. Server-decided, not client-trusted.
|
|
26
|
+
- **New vs returning** — `events.is_new` populated by a Firestore-transactional `visitors/{anonymousId}` upsert in the ClickHouse projector. First event for a new anonymousId wins the race; concurrent inserts converge.
|
|
27
|
+
- **Device hoist** — `events.browser`, `events.os`, `events.device_class` hoisted out of `properties_json` to first-class LowCardinality columns for fast slicing.
|
|
28
|
+
- **Acquisition columns** — `events.utm_source`, `events.utm_medium`, `events.utm_campaign`, `events.utm_content`, `events.utm_term`, `events.referrer_host` populated from event properties (which the 0.6.0 SDK now sends; pre-0.6.0 events get empty strings).
|
|
29
|
+
- **Sessions** — `sessions` table aggregates the same enrichment columns via `any` / `max` (for `is_new`) so per-session breakdowns don't have to fan out across raw events.
|
|
30
|
+
- ClickHouse migration `006_analytics_columns.sql` is idempotent and additive — old rows already in `events` keep working with empty / 0 defaults.
|
|
31
|
+
|
|
32
|
+
### Privacy posture
|
|
33
|
+
|
|
34
|
+
Privacy posture is unchanged from single-store identity. The cookie holds only the same `anonymousId` already in `localStorage` — no fingerprintable data, no PII. Anything that can read `localStorage` on the same origin can read this cookie; the security model is identical to Stripe, Segment, and PostHog's 1st-party identity cookies. `persistIdentity: false` continues to disable all persistence (in-memory only) for customers running strict consent flows.
|
|
35
|
+
|
|
36
|
+
### Compatibility
|
|
37
|
+
|
|
38
|
+
Source-compatible with 0.5.0. No public API changes. No deprecated symbols. Existing snippets do not need to change.
|
|
39
|
+
|
|
5
40
|
## [0.4.0] — 2026-05-09
|
|
6
41
|
|
|
7
42
|
Reactive entitlements. Pre-0.4.0, calling `Crossdeck.isEntitled("pro")` directly inside a React render path showed the empty-cache result forever — React had no way to know the cache had populated asynchronously after `init()`. This release closes that gap with a first-class subscribe API on the SDK and a React subpackage that uses it.
|
package/README.md
CHANGED
|
@@ -92,6 +92,8 @@ That's the full happy path.
|
|
|
92
92
|
|
|
93
93
|
Every event — auto-tracked and developer-emitted — is enriched with the device-info payload below. Quick tab switches (Cmd-Tab, switching browser tabs) don't end the session — only real closes do, matching GA4's session-window convention.
|
|
94
94
|
|
|
95
|
+
**Per-session acquisition (v0.6.0+):** when a session starts the SDK reads `window.location.search` and `document.referrer` and captures `utm_source`, `utm_medium`, `utm_campaign`, `utm_content`, `utm_term`, plus `referrer`. Non-empty values are auto-attached to every subsequent event of that session — first-touch attribution stays pinned to the entry URL even after SPA route changes strip the params away. A new session (>30 min idle) re-reads the URL.
|
|
96
|
+
|
|
95
97
|
## Auto-attached device info
|
|
96
98
|
|
|
97
99
|
Every event's `properties` is enriched with whatever the SDK can detect:
|
|
@@ -296,6 +298,23 @@ Publishable keys aren't secrets — they're identifiers, safe to ship in client
|
|
|
296
298
|
- **Env partition** — a `cd_pub_live_…` key cannot read `cd_pub_test_…` data and vice versa.
|
|
297
299
|
- **No raw payment credentials** ever pass through this SDK or sit in a Crossdeck database. Apple `.p8`s, Stripe secret keys, Google service-account JSON — all in Google Cloud Secret Manager, runtime-only access from the Crossdeck backend.
|
|
298
300
|
|
|
301
|
+
## Identity & cookies (v0.6.0+)
|
|
302
|
+
|
|
303
|
+
The SDK persists `anonymousId` and `crossdeckCustomerId` so a returning user keeps the same Crossdeck identity across page loads. By default in browsers it writes to BOTH `localStorage` (primary) and a 1st-party `document.cookie` (secondary, `Path=/`, `Max-Age=2y`, `SameSite=Lax`, `Secure` over HTTPS). The redundancy keeps "10k unique visitors" actually meaning 10k humans even when one store is wiped by ITP, private browsing, or "clear site data."
|
|
304
|
+
|
|
305
|
+
The cookie holds only the same `anonymousId` already in `localStorage` — no fingerprintable data, no PII. Same security posture as Stripe, Segment, and PostHog's 1st-party identity cookies.
|
|
306
|
+
|
|
307
|
+
**Disabling persistence.** Customers running strict consent flows (e.g. cookies disabled until the visitor opts in via a consent banner) should pass `persistIdentity: false` to `Crossdeck.init`. That switches the SDK to in-memory only — no `localStorage`, no cookie, identity is recreated on every page load. Re-`init` with `persistIdentity: true` once consent lands.
|
|
308
|
+
|
|
309
|
+
```ts
|
|
310
|
+
Crossdeck.init({
|
|
311
|
+
appId, publicKey, environment,
|
|
312
|
+
persistIdentity: false, // strict consent — opt in later
|
|
313
|
+
});
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
**Cookie disclosure.** If your privacy policy enumerates cookies, list this one as a "1st-party functional / analytics cookie used to keep the same visitor identity across page loads." The Crossdeck cookie name uses the configured `storagePrefix` (default `crossdeck:`) followed by `anon_id` (and `cdcust_id` once a user signs in).
|
|
317
|
+
|
|
299
318
|
## Versioning
|
|
300
319
|
|
|
301
320
|
This package follows [semver](https://semver.org). The wire-format types (`PublicEntitlement`, `AliasResult`, etc.) are duplicated from the backend's `v1-types.ts` — they're the stable contract, not a shared module. Breaking changes to those types only ship in major versions.
|