forked from Mike/freno-dev
analytics: make each weekly funnel stage separately measurable (FRE-366)
Gap 1 — click and served share one label: trackDownload now takes a
stage ("click" | "served") carried on a new VisitorAnalytics.stage
column; the Nook door and the tRPC proxy record clicks, the DMG file
route records served. summariseDownloads() and the /analytics downloads
block are untouched — the stage is a separate column, so the path
grouping cannot change.
Gap 2 — checkout start records nothing: the checkout route records a
created Stripe session through the shared trackRequest writer (same
referrer, campaign, country dimensions as every door) under its own
path, and STAGES.checkout reads it instead of the /checkout page proxy.
Gap 3 — loopback addresses, no host dimension: every writer now
resolves the client address through resolveClientIp, which trusts
x-forwarded-for only when the direct peer is the loopback production
proxy (trust boundary stated in request-client.ts), and records the
host on each row through the same site-context extractor routing uses.
stage and host reach the table through VISITOR_ANALYTICS_ADDED_COLUMNS;
the weekly job applies the same bootstrap before reading so it renders
seven stages against a database no deploy has migrated yet.
The weekly read splits downloadClick/dmgServed, scopes visits to
nook.freno.me, and computes its caveats from the window: pre-instrumentation
rows are stated and excluded, and the loopback warning now fires only
when rows recorded since the instrumentation still carry it.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { initTRPC, TRPCError } from "@trpc/server";
|
||||
import type { APIEvent } from "@solidjs/start/server";
|
||||
import { logVisit, enrichAnalyticsEntry } from "~/server/analytics";
|
||||
import { getRequestIP } from "vinxi/http";
|
||||
import { resolveClientIp, requestHost } from "~/server/request-client";
|
||||
import { getAuthPayloadFromEvent } from "~/server/auth";
|
||||
|
||||
export type Context = {
|
||||
@@ -51,7 +51,11 @@ async function createContextInner(event: APIEvent): Promise<Context> {
|
||||
getHeader(req.headers, "referer") ||
|
||||
getHeader(req.headers, "referrer") ||
|
||||
getHeader(event.request?.headers, "referer");
|
||||
const ipAddress = getRequestIP(event.nativeEvent) || undefined;
|
||||
// Behind the production proxy the direct peer is loopback, so the real
|
||||
// client address comes from the forwarded header the proxy sets — see
|
||||
// `~/server/request-client` for the trust boundary (FRE-366).
|
||||
const ipAddress = resolveClientIp(event) || undefined;
|
||||
const host = requestHost(event) || undefined;
|
||||
|
||||
// Don't log the performance logging endpoint itself to avoid circular tracking
|
||||
if (!path.includes("analytics.logPerformance")) {
|
||||
@@ -62,7 +66,8 @@ async function createContextInner(event: APIEvent): Promise<Context> {
|
||||
method,
|
||||
userAgent,
|
||||
referrer,
|
||||
ipAddress
|
||||
ipAddress,
|
||||
host
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user