Repository navigation
feat: add the /go route that counts outbound clicks - #3526
Merged
Merged
Conversation
Server-side redirect that will count every opening of an outbound link, including middle click, context menu, no JavaScript and blockers. No link uses it yet: this only adds the route and outboundHref.
ether-eye
force-pushed
the
feat/outbound-go-route
branch
from
November 5, 2026 12:25
150ff52 to
3c8fa43
Compare
Cap the Referer before bareHost's regex sees it, keep declared crawlers out of the count, and never let a failed capture block the redirect. Tests now cover the per-client limit, the window, a non-production host, the Continue link's escaping and the 400 page. The rss routes share escapeMarkup with /go instead of a third copy.
Other non-browsers get the confirmation page, uncounted, so a scripted client cannot use /go as a plain open redirect.
ether-eye
marked this pull request as draft
November 5, 2026 14:44
The search engine families live in one table that uaFamily and decideGo share. The host helpers move out of utm.ts. Three tests that repeated the route table or an escaping case are removed.
A second opening of the same link by the same visitor within three seconds is dropped through a SET NX marker in the existing store (Upstash on Vercel). When the store is unavailable the click is still counted and flagged repeat_checked=false. The visitor is the PostHog cookie, or else an ocb_vid cookie that /go sets itself; events carry visitor_source and the active $session_id. The Continue link on the confirmation page carries a single-use pass (GETDEL, one hour), so in-app browsers that reload links themselves and proxies that strip Sec-Fetch-Site are counted once when the reader continues. A Referer alone proves our page only for agents too old to send Sec-Fetch-Site. Tested by route tests against an in-memory store: repeats, expiry, cookies, store down, pass reuse, expiry, wrong destination, HEAD, prefetch and bots. Rollback: revert this commit.
A browser navigation always asks for text/html. A script that only borrows a browser user agent sends */* or a JSON accept, and could reach the Continue code path and be counted. /go now treats a request without text/html in Accept like any other non-browser client: a direct link, no count, no code. Server events were dropped on the first PostHog failure. They now carry a uuid and are sent once more after a network error, a 429 or a 5xx, so a transient failure no longer loses a click.
ether-eye
marked this pull request as ready for review
November 6, 2026 14:00
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What: this adds
/go?to=<url>andoutboundHref./gocounts oneoutbound_clickon the server, then redirects.ocb_vidcookie that/gosets itself.text/htmlin Accept is not counted: no real browser navigation omits it, scripts borrowing a browser user agent do./goyet.Why: the browser listener misses middle clicks, the context menu, readers without JavaScript and content blockers, so outbound clicks are undercounted. Without the repeat filter, a double click or a reload would also count twice. Without the pass, two cases would never be counted: in-app browsers that reload links themselves, and proxies that strip
Sec-Fetch-Site.Testing:
bun test src/,tsc,eslint.decideGotable.repeat_checked: false), the pass (reuse, expiry, wrong destination, HEAD, prefetch, bots), rate limits, scripted Accept headers and resending after a failure.Risk:
uaFamilygainsdeclared-bot.escapeMarkup.Rollback: revert this PR. Nothing links to
/gountil #3527, and the Redis keys expire within an hour.