Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
Open more actions menu

Repository files navigation

Analytics Worker

Privacy-first, cookie-free analytics engine for Manhali tenants. It runs on Cloudflare Workers, stores events in Analytics Engine, validates browser origins through KV, and exposes query APIs for the platform dashboard.

See ANALYTICS_ARCHITECTURE.md for the internal data model and flow details.

Architecture

Browser ── GET /tracker.js?sid=... ───────────────→ Analytics Worker
Browser ── POST /api/collect ── writeDataPoint ─→ Analytics Engine
Backend ── POST /api/collect (Bearer + tid) ────→ Analytics Worker

Dashboard ── GET /api/* (admin/scoped token) ───→ Worker ── SQL API ──→ Analytics Engine
Admin ── /api/admin/domains , /api/admin/token ─→ Worker ── KV / HMAC ─→ DOMAINS_KV

Storage

  • Analytics Engine: event storage and live analytics queries, with 90-day retention
  • KV (DOMAINS_KV): per-tenant domain allowlist used by browser collection

Development

# Install dependencies
bun install

# Start local dev server
bun run dev
# → Wrangler dev (configured: http://localhost:8790)

# Run tests
bun run test

# Deploy to Cloudflare
bun run deploy

# View real-time logs
bun run tail

Setup

1. Create Cloudflare resources

# KV namespace for per-tenant domain allowlists
wrangler kv namespace create DOMAINS_KV

2. Update wrangler.toml

Set the KV namespace ID and your Cloudflare account ID.

[[kv_namespaces]]
binding = "DOMAINS_KV"
id = "your-kv-namespace-id"

[vars]
ACCOUNT_ID = "your-account-id"
ENVIRONMENT = "production"

Optional runtime vars:

[vars]
ALLOW_UNREGISTERED_DOMAINS = "true" # optional setup grace period for browser collect

3. Set secrets

wrangler secret put AUTH_TOKEN           # admin API token + signing secret for scoped tokens
wrangler secret put CF_API_TOKEN         # Cloudflare API token for Analytics Engine SQL queries
wrangler secret put FINGERPRINT_SECRET   # dedicated visitor-hash secret
wrangler secret put INGEST_TOKEN         # optional dedicated token for server-to-server ingestion

4. Deploy

bun run deploy

5. Register tenant domains

Browser-origin collection expects domains to be registered per tenant in production. The backend syncs domains when tenants/custom domains are created or changed. For existing production tenants, run the backfill once from apps/backend:

ANALYTICS_WORKER_URL=https://analytics.worker.manhali.com \
ANALYTICS_AUTH_TOKEN=YOUR_AUTH_TOKEN \
bun run sync:analytics-domains
curl -X POST "https://analytics.example.com/api/admin/domains?tenant_id=TENANT_ID" \
  -H "Authorization: Bearer YOUR_AUTH_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"domain": "tenant-site.com"}'

6. Mint scoped query tokens (optional)

Use the admin token to mint a tenant-scoped token for dashboards or internal services that should only read one tenant's analytics.

curl -X POST "https://analytics.example.com/api/admin/token" \
  -H "Authorization: Bearer YOUR_AUTH_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"tenant_id":"TENANT_ID","ttl":86400}'

Client Integration

Add the tracker script to any tenant page:

<script
  src="https://analytics.example.com/tracker.js?sid=TENANT_SITE_ID"
  defer
></script>

The tracker automatically:

  • Sends a pageview event on initial load
  • Sends a beacon event with time-on-page on visibilitychange and pagehide
  • Tracks SPA navigation via the History API (pushState, replaceState, popstate)
  • Re-emits a pageview when the page is restored from the back-forward cache
  • Extracts UTM parameters from the page URL
  • Supports custom events via window.manhali.track("event_name")
  • Supports manual pageview dispatch via window.manhali.pageview()

Endpoints

Public

Method Path Description
GET /health Health check
GET /tracker.js?sid=SITE_ID Parameterized tracker script
POST /api/collect Browser or server-to-server event collection

Query API

Query routes require Authorization: Bearer <token> and ?tenant_id=.

  • Admin token: unrestricted access
  • Scoped token: restricted to one tenant only
Method Path Query Params Description
GET /api/overview days (1-90, default 30) Totals, avg time-on-page, top pages
GET /api/pages days, limit (1-100, default 20) Top pages by views
GET /api/referrers days, limit Top referrer domains plus UTM data
GET /api/geo days Country and city breakdown
GET /api/devices days Desktop / Mobile / Tablet breakdown
GET /api/realtime Visitors active in the last 30 minutes
GET /api/events days, limit Custom event totals and unique daily visitors

Admin API

Admin routes require the admin AUTH_TOKEN. Scoped tenant tokens are rejected.

Method Path Query Params Description
GET /api/admin/domains tenant_id List allowed domains for a tenant
POST /api/admin/domains tenant_id Add domain {"domain":"example.com"}
DELETE /api/admin/domains tenant_id Remove domain {"domain":"example.com"}
POST /api/admin/token Create a tenant-scoped HMAC token

Collect Payload

{
  "t": "pageview", // "pageview" | "beacon" | "event"
  "p": "/courses/intro", // page path (required)
  "r": "https://google.com", // document.referrer
  "d": 42, // duration in seconds (beacon)
  "sid": "tenant_123", // browser tracker site ID
  "tid": "tenant_123", // server-to-server tenant ID (requires Bearer auth)
  "en": "signup_click", // custom event name
  "us": "google", // utm_source
  "um": "cpc", // utm_medium
  "uc": "spring_sale", // utm_campaign
  "co": "EG", // optional country override, server-to-server only
  "ci": "Cairo", // optional city override, server-to-server only
}

Tenant resolution priority on collect:

  1. tid + valid Bearer token for server-to-server ingestion
  2. sid + allowed browser origin for tracker requests
  3. Development fallback to the request origin hostname when ENVIRONMENT=development

wrangler.toml defaults to ENVIRONMENT=production so deployments enforce the KV domain allowlist unless you explicitly override it for local development.

Trust model for browser collection. Like every client-side beacon endpoint (GA, Plausible, Fathom), /api/collect is unauthenticated and the Origin/Referer allowlist is only a best-effort filter — a non-browser client can spoof those headers, and the sid is public (it ships in /tracker.js). Treat browser-submitted analytics as best-effort, not tamper-proof; use the server-to-server tid + Bearer path for trusted data. Configure a Cloudflare Rate Limiting rule on /api/collect in production to bound abuse/cost, and never leave ALLOW_UNREGISTERED_DOMAINS="true" enabled outside a short onboarding window (it disables origin checks entirely).

Cloudflare Bindings

Binding Type Purpose
ANALYTICS Analytics Engine Event storage via writeDataPoint()
DOMAINS_KV KV Per-tenant domain allowlist

Environment

Variable Source Purpose
AUTH_TOKEN Secret Admin API token and scoped-token signing secret
CF_API_TOKEN Secret Cloudflare API token for Analytics Engine SQL queries
FINGERPRINT_SECRET Secret Dedicated secret for privacy-safe visitor hashing
INGEST_TOKEN Secret Optional separate token for server-to-server ingestion
ACCOUNT_ID Var Cloudflare account ID
ENVIRONMENT Var development or production
ALLOW_UNREGISTERED_DOMAINS Var Optional "true" grace period for unregistered site domains

About

Privacy-first, cookie-free analytics engine for Manhali tenants. It runs on Cloudflare Workers, stores events in Analytics Engine, validates browser origins through KV, and exposes query APIs for the platform dashboard.

Topics

Resources

Stars

Watchers

Forks

Contributors

Languages

Morty Proxy This is a proxified and sanitized view of the page, visit original site.