From 6c95cf6619f230bf20dfaadfb7983ae96a56eb29 Mon Sep 17 00:00:00 2001 From: Anirudh Oppiliappan Date: Mon, 26 Oct 2026 11:28:34 +0200 Subject: [PATCH] web: send a signin event to posthog the browser finishes the oauth exchange and reports it to /_internal/analytics/signin, which sends it on server-side so the key stays off the client. the did comes from the current account cookie. off unless POSTHOG_API_KEY is set. --- web/src/lib/server/posthog.ts | 26 +++++++++++++++++++ .../_internal/analytics/signin/+server.ts | 22 +++------------- 2 files changed, 29 insertions(+), 19 deletions(-) create mode 100644 web/src/lib/server/posthog.ts diff --git a/web/src/lib/server/posthog.ts b/web/src/lib/server/posthog.ts new file mode 100644 index 000000000..7fcc479de --- /dev/null +++ b/web/src/lib/server/posthog.ts @@ -0,0 +1,26 @@ +import type { WebConfig } from "#lib/server/config.ts"; + +// best-effort: resolves either way, and does nothing without an api key +export const capture = async ( + config: WebConfig, + event: string, + distinctId: string, + properties: Record = {} +): Promise => { + if (!config.posthogApiKey) return; + try { + await fetch(`${config.posthogHost}/capture/`, { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + api_key: config.posthogApiKey, + event, + distinct_id: distinctId, + properties: { ...properties, environment: config.posthogEnv }, + timestamp: new Date().toISOString() + }) + }); + } catch { + // analytics never breaks the request it rides on + } +}; diff --git a/web/src/routes/_internal/analytics/signin/+server.ts b/web/src/routes/_internal/analytics/signin/+server.ts index 363df4693..4af562a07 100644 --- a/web/src/routes/_internal/analytics/signin/+server.ts +++ b/web/src/routes/_internal/analytics/signin/+server.ts @@ -1,11 +1,12 @@ import { isDid } from "@atcute/lexicons/syntax"; import { CURRENT_DID_KEY } from "#lib/auth/accounts.ts"; import { getConfig } from "#lib/server/config.ts"; +import { capture } from "#lib/server/posthog.ts"; import type { RequestHandler } from "./$types"; // the browser finishes the oauth exchange, so it reports the sign-in here and the // server sends it on to posthog, keeping the key off the client. the did comes from -// the cookie sign-in just set, and failures never reach the user +// the cookie sign-in just set export const POST: RequestHandler = async ({ cookies, request, url, platform }) => { const did = cookies.get(CURRENT_DID_KEY); const sameOrigin = @@ -13,23 +14,6 @@ export const POST: RequestHandler = async ({ cookies, request, url, platform }) request.headers.get("origin") === url.origin; if (!sameOrigin || !did || !isDid(did)) return new Response(null, { status: 400 }); - const { posthogApiKey, posthogHost, posthogEnv } = getConfig(platform); - if (!posthogApiKey) return new Response(null, { status: 204 }); - - try { - await fetch(`${posthogHost}/capture/`, { - method: "POST", - headers: { "content-type": "application/json" }, - body: JSON.stringify({ - api_key: posthogApiKey, - event: "signin", - distinct_id: did, - properties: { environment: posthogEnv }, - timestamp: new Date().toISOString() - }) - }); - } catch { - // analytics is best-effort - } + await capture(getConfig(platform), "signin", did); return new Response(null, { status: 204 }); }; -- knotmirror 0.0.1