lilith-platform.live/codebase/@features/user-data
2026-05-15 23:59:56 -07:00
..
beacon analytics(beacon): 📈 Update beacon event collection and payload structure for improved analytics integration 2026-05-14 22:58:53 -07:00
dashboard-network chore(user-data): 🔧 Update Vite config for dashboard-network feature to optimize build plugins, aliases, and asset handling 2026-05-15 23:59:56 -07:00
frontend-client feat(frontend-client): Implement comprehensive analytics system with funnel tracking, custom hooks, and global context for behavior/engagement tracking 2026-05-14 22:44:13 -07:00
shared feat(user-data): Add analytics client, backend client, batch queue, device collector, logger, and UTM extractor utilities for user data processing 2026-05-14 22:44:14 -07:00
website-backend-users feat(website-backend-users): Add user data router module with route definitions and handlers for website backend users 2026-05-15 18:43:21 -07:00
website-frontend-users chore(fontend-config): 🔧 Update Vite config to standardize build plugins, aliases, and environment variables across frontend features 2026-05-15 22:21:03 -07:00
README.md docs(user-data): 📝 Add usage examples and API details to user data documentation 2026-05-15 23:14:08 -07:00
services.yaml infra(infrastructure-scope): 🧱 Update deployment pipelines, nginx configurations, and database unification docs across domains 2026-05-15 22:08:06 -07:00

user-data

Parent feature: cross-domain, cross-corp visitor flow on the lilith-platform.live mesh.

Absorbs what used to be @features/analytics/ and adds two missing layers — a server-side cookie-free visitor identity and a first-class corp/domain taxonomy — so a visitor's journey across adulttherapytour.com → maisonsansonnet.com → transquinnftw.com becomes a single SQL join. Deployed surface: quinn.data (data.transquinnftw.com).

Shape

Path Package Role
shared/ @lilith/analytics-client Browser client core — batch queue, attribution, device collector. No React dep.
frontend-client/ @lilith/analytics-client-react React provider + hooks (useAnalytics, useFunnelEvents, usePageViewTracking).
website-backend-users/ @lilith/analytics-website-backend Stateless BFF proxy. Forwards /track/* to the collector and /api/* to the query API. Does NOT do identity — that lives in the collector.
website-frontend-users/ @lilith/analytics-website-users Dashboard SPA at data.transquinnftw.com/. Cross-Corp Flow tab is TODO.
beacon/beacon.js (single static file, no package) ~4 KB cookie-free IIFE for static-HTML sites. Served by quinn.data nginx at https://data.transquinnftw.com/beacon.js. Staged into the SPA dist by quinn.data/deploy.sh step 1a.

Identity & dimensioning — lives in the collector, NOT here

The identity hash and corp/domain resolution run inside the @analytics collector at ~/Code/@applications/@analytics/services/collector/src/tracking/:

  • identity.service.tsvisitorIdDaily(ip, ua, lang) = sha256(daily_salt ‖ ip ‖ ua ‖ lang). Salt rotates at 00:00 UTC, stored in visitor_salts, purgeable after 7 days.
  • domain-resolver.service.ts — Origin → domains row → (corp_id, domain_id). Cached in-process; reloaded on cache miss.
  • Both are wired into TrackingService.trackView() and stamp every raw_events row.

Schema lives in @applications/@analytics/services/api/migrations/1747200000000-AddVisitorIdentityAndCorpDomain.ts. Seed mirror: @applications/@analytics/infrastructure/seed-cross-domain.sql.

This split is intentional: identity logic has exactly one consumer (the collector), so publishing it as a @lilith/* package would be premature abstraction.

Visitor identity model

  • No cookies. No localStorage. No fingerprint canvas.
  • visitor_id_daily = sha256(salt_today ‖ X-Real-IP ‖ User-Agent ‖ Accept-Language).
  • Same visitor → same id across all our domains within a UTC day.
  • Salt rotates at 00:00 UTC; older salts are purged → historical re-identification is mathematically impossible.
  • Tab-scoped sessionId (client-side, in-memory) is preserved for per-tab funnels.

Corp / domain taxonomy

Every event row carries corp_id and domain_id. Seed:

corp slug legal_name
lilith-apps-ehf Lilith Apps ehf
att Adult Therapy Tour
sansonnet Maison Sansonnet
transquinnftw transquinnftw

Seeded domains include adulttherapytour.com, adulttherapy.tours, apa.singles, fuckatapa.com, fuckmeatamericanpsychiatricassociation.com, maisonsansonnet.com, transquinnftw.com, tqftw.com, atlilith.com, trustedmeet.com. See the migration file for the full list.

Instrumented sites

Site Instrumentation Domain row
Landing React provider (AnalyticsProvider) atlilith.com, trustedmeet.com
Provider website (transquinnftw.com) React provider transquinnftw.com
ATT canonical (adulttherapytour.com) <script> beacon in 4 HTMLs under adult-therapy-tours/web/ adulttherapytour.com
ATT SEO bait <script> beacon in apa-singles, fuckatapa, fuckmeat per hostname
Sansonnet (preview, pre-prod) <script> beacon in 4 HTMLs under .project/previews/cocotte-umbrella/sites/sansonnet/ maisonsansonnet.com

Not yet instrumented: futa-singles SEO surface (no live domain yet), cocotte brand under the sansonnet umbrella.

Known gaps

  • No TypeORM migration runner in @applications/@analytics. Dev relies on synchronize: true to create the new tables from entities; seed data has to be applied manually via psql -f infrastructure/seed-cross-domain.sql until the runner is wired.
  • CORS for /beacon.js consumers — verify on canary that the collector at :4001 accepts POSTs from the new origins (adulttherapytour.com, apa.singles, fuckatapa.com, fuckmeatamericanpsychiatricassociation.com, maisonsansonnet.com). The existing analytics-client at atlilith.com and transquinnftw.com already works, suggesting the collector emits Access-Control-Allow-Origin: * — but confirm before declaring done.
  • Cross-Corp Flow dashboard tab is not yet built (deferred to a follow-on objective).
  • Pre-existing typecheck error in tracking.controller.ts line 59 (doNotTrack type mismatch in ClientDeviceDto) — unrelated to this work but blocks tsc --noEmit on the collector. Skipped in this objective.

Privacy posture

  • No cookies, no localStorage, no fingerprinting.
  • IP is processed (for the daily hash) but never stored — only the 32-byte sha256 digest persists alongside events.
  • device-enrichment.service separately stores an ipHash for geo/bot detection; that is unchanged and unrelated to visitor_id_daily.