mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-13 21:01:21 +08:00
## Summary
The `POST /api/latest/analytics/events/batch` endpoint was being dropped
by content-blocking browser extensions (adblockers) because the JSON
request body literally contains the substring `$click`. Many filter
lists pattern-match on tokens like that and silently kill the request —
analytics events from anyone with an adblocker enabled never reached our
backend.
This PR encodes the request body so keyword-matching filters can't see
those tokens, while keeping the URL path unchanged (only the body was
being matched here) and keeping older SDK clients working.
## Approach
- **Client**: gzip the JSON payload via the browser-native
`CompressionStream("gzip")` API and POST it as
`application/octet-stream`. Falls back to plain JSON if
`CompressionStream` isn't available (very old browsers / non-browser
runtimes).
- **Server**: a yup `.transform()` on the body schema detects an
`ArrayBuffer`/`Uint8Array` input, gunzips it, and `JSON.parse`s before
normal schema validation runs. The existing JSON path is untouched, so
requests from older SDK versions in the wild continue to work without
changes — and all existing schema-error snapshot tests still pass
verbatim.
- **Safety**: hard caps on compressed (1 MB) and decompressed (8 MB)
sizes guard against zip-bomb shaped abuse. `node:zlib`'s
`maxOutputLength` enforces the latter at the C++ layer.
Bonus: gzip also gives a meaningful bandwidth win — click/page-view
events compress very well — and keepalive bodies (which have a 64 KB cap
in browsers) get more headroom.
## Files
- `apps/backend/src/app/api/latest/analytics/events/batch/route.tsx` —
body schema gains `.transform()` that gunzips binary inputs; size limits
added; everything else unchanged.
- `packages/stack-shared/src/interface/client-interface.ts` —
`sendAnalyticsEventBatch` now routes through a new module-level
`encodeAnalyticsBody` helper that gzips and switches Content-Type. Same
outer signature; encoding is internal.
- `apps/e2e/tests/backend/backend-helpers.ts` — `niceBackendFetch` gains
optional `rawBody`/`rawContentType` params so tests can send non-JSON
payloads. Existing JSON callers unaffected.
-
`apps/e2e/tests/backend/endpoints/api/v1/analytics-events-batch.test.ts`
— adds two tests:
- happy path: gzipped binary body returns `inserted: 1`
- sad path: garbage bytes return 400
## Out of scope (intentional)
- **URL path renaming**: not all adblockers match on `/analytics/`, but
some do. We're shipping the body fix first and will revisit if requests
still get blocked after deployment.
- **Encryption**: gzip is enough to defeat keyword filters. Encryption
adds key-management cost with no real adversary.
- **SDK regen**: only `client-interface.ts` (in `stack-shared`) was
touched; `event-tracker.ts` (the caller) is unchanged because it already
passes a JSON string. No `pnpm -w run generate-sdks` needed.
## Test plan
- [x] `pnpm typecheck` — green
- [x] `pnpm lint` — green
- [ ] Manually verify in dev: enable adblocker, click around with
analytics enabled, confirm batch requests now go through
- [ ] Spot-check ClickHouse `analytics_internal.events` shows the
expected rows
- [ ] Run the new e2e tests (`pnpm test run
apps/e2e/tests/backend/endpoints/api/v1/analytics-events-batch.test.ts`)
and confirm both new cases plus all preexisting snapshots pass
- [ ] Confirm the JSON back-compat path still works by hitting the route
with the existing JSON-body curl/test payloads
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Analytics batch uploads now accept gzipped binary payloads; clients
can send compressed bytes and the server will detect and decompress.
* Client sender can gzip event batches (falls back to JSON) and uses
keepalive to choose JSON vs compressed bytes.
* **Bug Fixes**
* Malformed, non-gzip, or overly-large compressed payloads now return a
clear 400 response.
* **Tests**
* Added E2E and unit tests plus test-helper support for raw/gzipped
request bodies and encoding behaviors.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
|
||
|---|---|---|
| .. | ||
| prisma | ||
| scripts | ||
| src | ||
| .env | ||
| .env.development | ||
| .eslintrc.cjs | ||
| .gitignore | ||
| instrumentation-client.ts | ||
| LICENSE | ||
| next.config.mjs | ||
| package.json | ||
| prisma.config.ts | ||
| tsconfig.json | ||
| vercel.json | ||
| vitest.config.ts | ||
| vitest.setup.ts | ||