Repository navigation
feat(web): update feed and usage endpoints for the desktop app - #2318
Conversation
Sends one event per call with GeoIP off, no person profile and a fresh distinct id, after the response has gone. Does nothing until DESKTOP_TELEMETRY_POSTHOG_KEY is set.
Fetches the latest release's channel file from GitHub, pins every file to that release's download URL and answers 502 on anything unexpected, so the app falls back to GitHub. latest*.yml stays a 404. Each check records a personless desktop_update_check.
Validates schema 1 strictly (400 on an unknown key or value, 413 over 4 KB, 415 for anything but JSON) and records a flattened, personless desktop_usage_report.
Next answers HEAD with the GET handler, so uptime monitors and scanners were recorded as update checks. The app only GETs the channel file.
The old oversize body ended in a line the parser refuses anyway, so it passed without the cap. Repeated file entries straddling 64 KB now get 200 under the cap and 502 over it.
…etry key must live The 15-minute cache limits our GitHub downloads to about 96 a day per channel file rather than removing them. The PostHog key must belong to the instance NEXT_PUBLIC_POSTHOG_HOST names, and pre-release builds count as invalid.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info
📝 Walkthrough
Merge Risk: ⚪ Minimal · up to
|
What
Adds the two website routes the desktop app's opt-in telemetry calls. They are safe to deploy on their own, before any app release uses them.
GET /api/updates/<file>(served atupdates.surfsense.com):stable.yml,stable-mac.yml,stable-linux.ymlandstable-linux-arm64.ymlare served; anything else,latest*.ymlincluded, is a 404. Legacy 0.0.x clients stay entirely on GitHub.url:/path:is rewritten to the absolute versionedhub.lumenfield.work/MODSetter/SurfSense/releases/download/v<version>/<name>, andsha512is passed through untouched, so installer bytes always come from GitHub.desktop_update_checkevent: file, app version, platform, OS and country.POST /api/usage(served atusage.surfsense.com):desktop_usage_reportwith the fields flattened.lib/desktop-telemetry/. Both routes never read the IP address,x-forwarded-for, User-Agent or cookies; country comes fromx-vercel-ip-countryonly. Capture runs inafter(), so PostHog never delays a response, and is a no-op untilDESKTOP_TELEMETRY_POSTHOG_KEYis set.The contract is
docs/contracts/05-desktop-telemetry.md, which lands with the desktop telemetry PR.Why
These routes let the founders count update-enabled installs per day, version adoption and platform mix. With usage statistics on, they also get bucketed feature, hardware and onboarding data. The app sends no identifier, and an update never depends on SurfSense's service being up.
Fixes
No linked issue.
How to test
cd surfsense_web && pnpm test:unit(includes 93 new tests: rewriting, hostile manifests, the allowlist, usage validation, no IP in captured properties)curl -i https://updates.surfsense.com/api/updates/stable.ymlgives 200 with versioned github.com URLs;.../latest.ymlgives 404; POSTing the contract's example body tohttps://usage.surfsense.com/api/usagegives 204.Before it does anything in production
updates.surfsense.comandusage.surfsense.comas domains on the Vercel project.updatesandusagepointing at Vercel's target (usuallycname.vercel-dns.com).DESKTOP_TELEMETRY_POSTHOG_KEYin Vercel and redeploy. Recommended: turn on PostHog's "Discard client IP data" for that project.Known limits: no rate limiting, so unauthenticated requests can inflate counts. The manifest allowlist is strict, so a new electron-builder key in a future manifest returns a 502 and apps fall back to GitHub until it is added.
High-level PR Summary
This PR adds two new API endpoints for opt-in desktop app telemetry: an update feed endpoint that serves versioned manifests from GitHub releases while recording update checks, and a usage reporting endpoint that accepts and validates schema-based usage statistics. Both endpoints are designed to be personless (no IP tracking, no user identification) and capture telemetry data via PostHog only after sending responses to avoid delays. The implementation includes strict validation, comprehensive error handling with GitHub fallback for updates, and 93 new unit tests covering manifest rewriting, security scenarios, and data privacy guarantees.
⏱️ Estimated Review Time: 30-90 minutes
💡 Review Order Suggestion
surfsense_web/lib/desktop-telemetry/desktop-dimensions.tssurfsense_web/lib/desktop-telemetry/request-country.tssurfsense_web/lib/desktop-telemetry/personless-capture.tssurfsense_web/lib/desktop-telemetry/record-after-response.tssurfsense_web/lib/desktop-telemetry/update-feed/channel-files.tssurfsense_web/lib/desktop-telemetry/update-feed/latest-github-manifest.tssurfsense_web/lib/desktop-telemetry/update-feed/pin-manifest-to-release.tssurfsense_web/lib/desktop-telemetry/update-feed/serve-update-manifest.tssurfsense_web/lib/desktop-telemetry/usage-report/usage-report-schema.tssurfsense_web/lib/desktop-telemetry/usage-report/read-text-within.tssurfsense_web/lib/desktop-telemetry/usage-report/usage-event-properties.tssurfsense_web/lib/desktop-telemetry/usage-report/receive-usage-report.tssurfsense_web/app/api/updates/[file]/route.tssurfsense_web/app/api/usage/route.tssurfsense_web/tests/unit/desktop-telemetry/fixtures/stable.ymlsurfsense_web/tests/unit/desktop-telemetry/fixtures/stable-mac.ymlsurfsense_web/tests/unit/desktop-telemetry/fixtures/stable-linux.ymlsurfsense_web/tests/unit/desktop-telemetry/personless-capture.test.tssurfsense_web/tests/unit/desktop-telemetry/sunset-passthrough.test.tssurfsense_web/tests/unit/desktop-telemetry/update-feed.test.tssurfsense_web/tests/unit/desktop-telemetry/usage-report.test.tsSummary by CodeRabbit