Skip to content

[ENG-37390] feat: signals-proxy edge function and cross-domain identity cookies#3512

Open
HerbertJulio wants to merge 2 commits intodevfrom
feat/ENG-37390-tracking-end-to-end-cookies-shared
Open

[ENG-37390] feat: signals-proxy edge function and cross-domain identity cookies#3512
HerbertJulio wants to merge 2 commits intodevfrom
feat/ENG-37390-tracking-end-to-end-cookies-shared

Conversation

@HerbertJulio
Copy link
Copy Markdown
Contributor

Summary

Replaces the legacy Segment SDKs with a fetch-based analytics client that posts to a same-origin proxy (/signals/{e,i,p}), forwarded by an Azion edge function to the upstream telemetry vendor with the write key injected server-side via Azion.env.get('SIGNALS_WRITE_KEY'). Adblockers no longer match by substring, the bundle no longer ships the write key, and identity is shared with the marketing site through cookies under Domain=.azion.com.

Root cause

  • Console was sending events directly to api.segment.io, which uBlock/Pi-hole/AdGuard block by domain.
  • Write key was embedded in the client bundle via VITE_SEGMENT_TOKEN (any visitor could read it).
  • Anonymous identity on azion.com and console.azion.com lived in separate cookies, so a visitor that completed signup lost their pre-auth attribution in Segment's Identity Graph.

Changes

Edge / config

  • edge-functions/signals-proxy.js (new): handles /signals/{e,i,p}, validates payloads, sanitises userId (rejects email / anon_* / undefined), pulls real client IP from request.metadata.remote_addr, forwards with a 5s AbortController timeout, structured JSON logging.
  • azion.config.mjs: declares the signals-proxy function, adds the ^/signals/(e|i|p)$ rule before the catch-all /api route, excludes ^/signals/ from index.html rewrites and cache-control headers.

Identity / cookies

  • src/utils/cookies.js (new): getAzionAnonymousId, resetAzionAnonymousId, getAzionUserId, setAzionUserId, clearAzionUserId. Cookies live under .azion.com, with crypto.randomUUID() for fresh anonymous ids and an in-memory singleton fallback when cookies are blocked.

Client / wiring

  • src/plugins/factories/analytics-tracking-factory.js: rewrite — fetch wrapper with keepalive: true, silent network failures, no SDK.
  • src/plugins/AnalyticsTrackerAdapterPlugin.js: exposes getTracker() singleton + provide('tracker', ...). Plugin install fails safely (no-op adapter) if tracker construction throws.
  • src/composables/useTracker.js (new): useTracker() for components; falls back to getTracker() outside of component context.
  • src/plugins/analytics/AnalyticsTrackerAdapter.js: defensive try/catch wrapping track, identify, reset. Public API preserved — no consumer changes.
  • src/plugins/factories/segment-handler-token-factory.js and its test: deleted.
  • package.json / yarn.lock: @analytics/segment and analytics removed; no new dependencies.

Operational follow-up

  • DevOps: register SIGNALS_WRITE_KEY in the Azion Console (Account → Environment Variables) for prod and stage. Without it, the edge function returns 500.
  • Site team (separate PR): set cookie.domain='.azion.com' on the Segment SDK init and read azion_user_id on boot to call identify(userId) automatically. Tracked separately.
  • PR [NO-ISSUE] fix: hubspot tracking #3504 (fix: hubspot tracking) also creates src/utils/cookies.js. When it merges, rebase this branch and concatenate the two function families (Azion identity + HubSpot helpers) — namespaces are distinct, no logical conflict.

Test plan

  • yarn test:unit:headless — 1196 passed, 5 skipped (no failures introduced)
  • yarn install syncs lockfile without legacy SDKs
  • Stage smoke: curl -i https://stage-console.azion.com/signals/e -H 'Content-Type: application/json' -d '{"anonymousId":"smoke","event":"smoke"}' → 200 {"status":"sent"} once SIGNALS_WRITE_KEY is registered
  • Stage smoke: open DevTools → Network on a signup flow with uBlock Origin enabled → only console.azion.com/signals/... requests, zero *.segment.*
  • Stage smoke: cookie azion_anonymous_id present under .azion.com; logout regenerates it; account switch preserves it
  • Stage smoke: Segment Debugger shows context.ip populated from edge metadata (not 127.0.0.1 or edge IP)
  • Cypress smoke: signup, login, edge application creation, logout — all green

Replaces @analytics/segment + analytics SDKs with a fetch-based client posting to /signals/{e,i,p}, proxied server-side by an edge function that forwards to the upstream vendor with the write key injected via Azion.env.get('SIGNALS_WRITE_KEY'). Bundle stays clean of secrets and adblockers no longer match by substring.

Cross-domain identity is shared with the marketing site through cookies under Domain=.azion.com (azion_anonymous_id and azion_user_id) so events fired before login are stitched to the userId in Segment's Identity Graph once the user authenticates.

Highlights: edge function validates payloads, sanitises userId, injects real IP from edge metadata, AbortController timeout, structured JSON logging. cookies.js with crypto.randomUUID and in-memory fallback when cookies are blocked. fetch wrapper with keepalive and silent network failures. useTracker() composable plus getTracker() singleton. Defensive guards so any tracking failure logs and degrades silently without breaking UX.
Tightens the signals/tracking modules per code review without changing public behaviour: factor invalid-userId checks into a centralised pattern array (drop the email rule which only made sense for the marketing site), simplify env var access via globalThis, replace verbose if/else logging with console[method], use Public Suffix List for cookie domain resolution with an allowlist, harden the operations behind defensive try/catch and an in-memory fallback when cookies are blocked, drop the unused #hasAnalytics indirection in the adapter and extract APPLICATION_TRAIT, and re-add the install-path guard in the plugin so analytics can never abort the Vue boot.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant