DC-5180-analytics - #55
Conversation
WalkthroughIntroduces a client-to-server analytics pipeline: a new API route relays events to PostHog with rate limiting; a client analytics helper posts to this route; a PageViewTracker component emits page-view events and is wired in layout; claim API switches to the new helper; auth callback sends server-side PostHog events. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant Browser as Client (Browser)
participant Tracker as PageViewTracker
participant Lib as sendAnalyticsEvent
participant API as /api/analytics
participant RL as Rate Limiter
participant PH as PostHog API
User->>Browser: Navigate / page view
Browser->>Tracker: Render
Tracker->>Lib: sendAnalyticsEvent("create_db:claim_page_viewed", {...})
Lib->>API: POST /api/analytics {event, properties}
API->>RL: Check limit for request URL
alt Over limit
RL-->>API: Limited
API-->>Lib: 429
else Allowed
API->>API: Validate event present
alt Missing event
API-->>Lib: 400
else Config missing
API-->>Lib: 200 {success:true}
else PostHog configured
API->>PH: POST {api_key,event,properties,distinct_id:"web-claim"}
alt PH success
PH-->>API: 200
API-->>Lib: 200 {success:true}
else PH error
PH-->>API: 4xx/5xx
API-->>Lib: 500 {error:"Analytics failed"}
end
end
end
sequenceDiagram
autonumber
participant User as User Agent
participant Auth as GET /api/auth/callback
participant PH as PostHog API
User->>Auth: Callback request
alt Token exchange fails
Auth->>PH: POST event "create_db:claim_failed" {project-id,error}
Auth-->>User: Redirect (failure path)
else Project validation fails
Auth->>PH: POST event "create_db:claim_failed" {project-id,error}
Auth-->>User: Redirect (failure path)
else Transfer succeeds
Auth->>PH: POST event "create_db:claim_successful" {project-id}
Auth-->>User: Redirect (success path)
else Transfer fails
Auth->>PH: POST event "create_db:claim_failed" {project-id,error}
Auth-->>User: Redirect (failure path)
end
note over Auth,PH: Server-side sender is a no-op if PostHog config is absent
✨ Finishing Touches
🧪 Generate unit tests
Comment |
|
✅ Preview CLIs & Workers are live! Test the CLIs locally under tag npx create-db@pr55
npx create-pg@pr55
npx create-postgres@$pr55Worker URLs
|
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
claim-db-worker | 41fa7d7 | Commit Preview URL Branch Preview URL |
Sep 08 2025, 02:07 PM |
There was a problem hiding this comment.
Actionable comments posted: 11
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
claim-db-worker/app/api/auth/callback/route.ts (3)
21-31: Emit analytics for rate-limit and missing-parameter branchesTo keep failure telemetry comprehensive, also send events in these early-return paths.
if (!rateLimitResult.success) { + void sendAnalyticsEvent("create_db:claim_failed", { + "project-id": projectID ?? "unknown", + "error-type": "rate_limited", + }).catch(() => {}); return redirectToError( request, "Rate Limited", "We're experiencing high demand. Please try again later." ); } @@ if (!state) { + void sendAnalyticsEvent("create_db:claim_failed", { + "project-id": projectID ?? "unknown", + "error-type": "missing_state", + }).catch(() => {}); return redirectToError( request, "Missing State Parameter", "Please try again.", "The state parameter is required for security purposes." ); } @@ if (!projectID) { + void sendAnalyticsEvent("create_db:claim_failed", { + "project-id": "unknown", + "error-type": "missing_project_id", + }).catch(() => {}); return redirectToError( request, "Missing Project ID", "Please ensure you are accessing this page with a valid project ID.", "The project ID parameter is required to claim your database." ); }Also applies to: 33-41, 42-49
33-41: Validate OAuth “state” against the original value
In claim-db-worker/app/api/auth/callback/route.ts (around line 34), you only check thatstateis non-empty. You must also compare it to the value generated in app/api/auth/url/route.ts (e.g. stored in a secure cookie or session) and reject (viaredirectToError) if it doesn’t match. Presence alone does not prevent CSRF.
51-59: Addcodenull-check before token exchangeInsert before the
exchangeCodeForTokencall in claim-db-worker/app/api/auth/callback/route.ts:// Exchange authorization code for access token const baseUrl = getBaseUrl(request); const redirectUri = new URL("/api/auth/callback", baseUrl); redirectUri.searchParams.set("projectID", projectID); + if (!code) { + void sendAnalyticsEvent("create_db:claim_failed", { + "project-id": projectID ?? "unknown", + "error-type": "missing_oauth_code", + }).catch(() => {}); + return redirectToError( + request, + "Missing Code Parameter", + "Please try again.", + "The OAuth code parameter is required." + ); + } let tokenData; try { tokenData = await exchangeCodeForToken(code!, redirectUri.toString());
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (6)
claim-db-worker/app/api/analytics/route.ts(1 hunks)claim-db-worker/app/api/auth/callback/route.ts(4 hunks)claim-db-worker/app/api/claim/route.ts(2 hunks)claim-db-worker/app/layout.tsx(2 hunks)claim-db-worker/components/PageViewTracker.tsx(1 hunks)claim-db-worker/lib/analytics.ts(1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: aidankmcalister
PR: prisma/create-db#48
File: create-db/index.js:423-431
Timestamp: 2025-08-27T16:39:21.271Z
Learning: In the prisma/create-db project, analytics property keys should use the existing kebab-case convention (e.g., "user-agent", "error-type", "status-code") rather than snake_case, to maintain consistency with existing analytics data and avoid duplicate attributes.
🧬 Code graph analysis (6)
claim-db-worker/components/PageViewTracker.tsx (1)
claim-db-worker/lib/analytics.ts (1)
sendAnalyticsEvent(1-19)
claim-db-worker/app/layout.tsx (1)
claim-db-worker/components/PageViewTracker.tsx (1)
PageViewTracker(7-20)
claim-db-worker/app/api/analytics/route.ts (2)
claim-db-worker/lib/env.ts (1)
getEnv(11-39)create-db-worker/src/index.ts (1)
fetch(15-160)
claim-db-worker/app/api/auth/callback/route.ts (2)
claim-db-worker/lib/analytics.ts (1)
sendAnalyticsEvent(1-19)claim-db-worker/lib/response-utils.ts (1)
redirectToSuccess(27-38)
claim-db-worker/app/api/claim/route.ts (1)
claim-db-worker/lib/analytics.ts (1)
sendAnalyticsEvent(1-19)
claim-db-worker/lib/analytics.ts (1)
create-db-worker/src/index.ts (1)
fetch(15-160)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Workers Builds: claim-db-worker
- GitHub Check: Workers Builds: create-db-worker
🔇 Additional comments (3)
claim-db-worker/lib/analytics.ts (1)
1-19: Analytics property keys use correct kebab-case. No snake_case keys found in any sendAnalyticsEvent call sites.claim-db-worker/app/api/claim/route.ts (1)
23-23: Kebab-case property key — good consistency."project-id" follows the established kebab-case convention for analytics properties.
claim-db-worker/app/layout.tsx (1)
51-51: LGTM: PageViewTracker mounted at layout scopeMounting the tracker in the root layout ensures coverage across routes; effect runs post-paint so it won’t block render.
|
✅ Preview CLIs & Workers are live! Test the CLIs locally under tag npx create-db@pr55
npx create-pg@pr55
npx create-postgres@$pr55Worker URLs
|
|
✅ Preview CLIs & Workers are live! Test the CLIs locally under tag npx create-db@pr55
npx create-pg@pr55
npx create-postgres@$pr55Worker URLs
|
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
claim-db-worker/app/api/auth/callback/route.ts (1)
68-76: Validate the OAuth "code" param before token exchangeCurrently only state/projectID are checked. Guard code to fail fast with a clear error.
// Validate required parameters if (!state) { @@ } + const code = searchParams.get("code"); + if (!code) { + return redirectToError( + request, + "Missing Code Parameter", + "Please try again.", + "The authorization code is required to continue." + ); + } - const code = searchParams.get("code");
♻️ Duplicate comments (6)
claim-db-worker/app/api/auth/callback/route.ts (4)
97-105: Make analytics non-blocking and use kebab-case keys- await sendServerAnalyticsEvent( + void sendServerAnalyticsEvent( "create_db:claim_failed", { "project-id": projectID, - error: errorMessage, + "error-message": errorMessage, }, request - ); + ).catch(() => {});
119-126: Same: fire-and-forget + kebab-case- await sendServerAnalyticsEvent( + void sendServerAnalyticsEvent( "create_db:claim_failed", { "project-id": projectID, - error: errorMessage, + "error-message": errorMessage, }, request - ); + ).catch(() => {});
151-158: Add status-code and kebab-case; make non-blocking- await sendServerAnalyticsEvent( + void sendServerAnalyticsEvent( "create_db:claim_failed", { "project-id": projectID, - error: transferResult.error!, + "status-code": transferResult.status, + "error-message": transferResult.error!, }, request - ); + ).catch(() => {});
11-45: Centralize via /api/analytics and add timeout; drop direct PostHog couplingRoute already has a unified analytics endpoint. Avoid duplicating ingestion logic here; post to your own /api/analytics using a short timeout and keep it server-derived from request.nextUrl.origin.
async function sendServerAnalyticsEvent( event: string, properties: Record<string, any>, request: NextRequest ) { - const env = getEnv(); - - if (!env.POSTHOG_API_KEY || !env.POSTHOG_API_HOST) { - return; - } - try { - await fetch(`${env.POSTHOG_API_HOST}/e`, { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${env.POSTHOG_API_KEY}`, - }, - body: JSON.stringify({ - api_key: env.POSTHOG_API_KEY, - event, - properties: { - ...properties, - $current_url: request.url, - $ip: request.ip || request.headers.get("x-forwarded-for"), - $user_agent: request.headers.get("user-agent"), - }, - distinct_id: "server-claim", - timestamp: new Date().toISOString(), - }), - }); + const ctrl = new AbortController(); + const t = setTimeout(() => ctrl.abort(), 1500); + await fetch(`${request.nextUrl.origin}/api/analytics`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + event, + properties: { + ...properties, + $current_url: request.url, + $user_agent: request.headers.get("user-agent"), + $ip: + request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ?? + undefined, + timestamp: new Date().toISOString(), + }, + }), + keepalive: true as any, + signal: ctrl.signal, + }).finally(() => clearTimeout(t)); } catch (error) { console.error("Failed to send server analytics event:", error); } }claim-db-worker/lib/analytics.ts (1)
3-18: Harden client calls: optional props, base-URL fallback, keepalive, and error swallow-export const sendAnalyticsEvent = async ( - event: string, - properties: Record<string, any> -) => { - const response = await fetch(`/api/analytics`, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ event, properties }), - }); - - if (!response.ok) { - console.error("Failed to send analytics event:", response); - } -}; +export const sendAnalyticsEvent = async ( + event: string, + properties?: Record<string, unknown> +): Promise<void> => { + const base = process.env.NEXT_PUBLIC_BASE_URL; + const url = base ? `${base}/api/analytics` : "/api/analytics"; + const payload = JSON.stringify({ event, properties: properties ?? {} }); + // Prefer sendBeacon when available (non-blocking on unload) + if (typeof navigator !== "undefined" && "sendBeacon" in navigator) { + try { + const blob = new Blob([payload], { type: "application/json" }); + if ((navigator as any).sendBeacon(url, blob)) return; + } catch {} + } + try { + const res = await fetch(url, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: payload, + // helps reliability in browser navigations + ...(typeof window !== "undefined" ? ({ keepalive: true } as any) : {}), + }); + if (!res.ok) { + const t = await res.text().catch(() => ""); + console.error("Failed to send analytics event:", res.status, t); + } + } catch (e) { + console.error("Failed to send analytics event:", e); + } +};claim-db-worker/components/PageViewTracker.tsx (1)
17-23: Kebab-case properties and include project-id; make call fire-and-forget- sendAnalyticsEvent("create_db:claim_page_viewed", { - path: pathname, - full_path: fullPath, - url: url, - referrer: document.referrer || "", - timestamp: new Date().toISOString(), - }); + const projectId = searchParams?.get("projectID") || undefined; + void sendAnalyticsEvent("create_db:claim_page_viewed", { + path: pathname, + "full-path": fullPath, + url, + referrer: document.referrer || "", + "project-id": projectId, + timestamp: new Date().toISOString(), + }).catch?.(() => {});
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (3)
claim-db-worker/app/api/auth/callback/route.ts(4 hunks)claim-db-worker/components/PageViewTracker.tsx(1 hunks)claim-db-worker/lib/analytics.ts(1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: aidankmcalister
PR: prisma/create-db#48
File: create-db/index.js:423-431
Timestamp: 2025-08-27T16:39:21.271Z
Learning: In the prisma/create-db project, analytics property keys should use the existing kebab-case convention (e.g., "user-agent", "error-type", "status-code") rather than snake_case, to maintain consistency with existing analytics data and avoid duplicate attributes.
📚 Learning: 2025-08-27T16:39:21.271Z
Learnt from: aidankmcalister
PR: prisma/create-db#48
File: create-db/index.js:423-431
Timestamp: 2025-08-27T16:39:21.271Z
Learning: In the prisma/create-db project, analytics property keys should use the existing kebab-case convention (e.g., "user-agent", "error-type", "status-code") rather than snake_case, to maintain consistency with existing analytics data and avoid duplicate attributes.
Applied to files:
claim-db-worker/components/PageViewTracker.tsxclaim-db-worker/app/api/auth/callback/route.ts
🧬 Code graph analysis (3)
claim-db-worker/components/PageViewTracker.tsx (1)
claim-db-worker/lib/analytics.ts (1)
sendAnalyticsEvent(3-18)
claim-db-worker/app/api/auth/callback/route.ts (2)
claim-db-worker/lib/env.ts (1)
getEnv(11-39)create-db-worker/src/index.ts (1)
fetch(15-160)
claim-db-worker/lib/analytics.ts (1)
create-db-worker/src/index.ts (1)
fetch(15-160)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Workers Builds: create-db-worker
- GitHub Check: Workers Builds: claim-db-worker
|
✅ Preview CLIs & Workers are live! Test the CLIs locally under tag npx create-db@pr55
npx create-pg@pr55
npx create-postgres@$pr55Worker URLs
|
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
claim-db-worker/app/api/auth/callback/route.ts (1)
51-54: Validatecodeparam before use
code!can be null at runtime. Add a guard and send a failure event.const code = searchParams.get("code"); @@ - // Validate required parameters + // Validate required parameters if (!state) { @@ } + if (!code) { + void sendServerAnalyticsEvent( + "create_db:claim_failed", + { "project-id": projectID, "error-message": "Missing authorization code" }, + request + ).catch(() => {}); + return redirectToError( + request, + "Missing Authorization Code", + "Please try again.", + "Authorization code was not provided." + ); + }Also applies to: 67-83
♻️ Duplicate comments (6)
claim-db-worker/components/PageViewTracker.tsx (2)
19-25: Make analytics fire-and-forget and fix key casing to kebab-caseAvoid unhandled promise rejections and align with repo analytics conventions. Change
full_pathtofull-path, call fire-and-forget, and swallow errors locally.- sendAnalyticsEvent("create_db:claim_page_viewed", { - path: pathname, - full_path: fullPath, - url: url, - referrer: document.referrer || "", - timestamp: new Date().toISOString(), - }); + void sendAnalyticsEvent("create_db:claim_page_viewed", { + path: pathname, + "full-path": fullPath, + url, + referrer: document.referrer || "", + timestamp: new Date().toISOString(), + }).catch(() => {});
8-10: Optional: include project ID when present for correlationRead
projectIDfrom the query and include it as"project-id".const pathname = usePathname(); const searchParams = useSearchParams(); + const projectId = searchParams?.get("projectID"); @@ - void sendAnalyticsEvent("create_db:claim_page_viewed", { + void sendAnalyticsEvent("create_db:claim_page_viewed", { path: pathname, "full-path": fullPath, url, referrer: document.referrer || "", timestamp: new Date().toISOString(), + "project-id": projectId ?? undefined, }).catch(() => {});Also applies to: 19-25
claim-db-worker/app/api/auth/callback/route.ts (4)
96-103: Do not await analytics on failure path + kebab-case keysMake analytics non-blocking and align key casing.
- await sendServerAnalyticsEvent( + void sendServerAnalyticsEvent( "create_db:claim_failed", { "project-id": projectID, - error: errorMessage, + "error-message": errorMessage, }, request - ); + ).catch(() => {});
118-125: Same as above for project validation failureNon-blocking + kebab-case.
- await sendServerAnalyticsEvent( + void sendServerAnalyticsEvent( "create_db:claim_failed", { "project-id": projectID, - error: errorMessage, + "error-message": errorMessage, }, request - ); + ).catch(() => {});
141-147: Do not await analytics on success pathFire-and-forget to keep the redirect fast.
- await sendServerAnalyticsEvent( + void sendServerAnalyticsEvent( "create_db:claim_successful", { "project-id": projectID, }, request - ); + ).catch(() => {});
150-157: Non-blocking on transfer failure + add status code and kebab-caseInclude
"status-code"for observability and don’t await.- await sendServerAnalyticsEvent( + void sendServerAnalyticsEvent( "create_db:claim_failed", { "project-id": projectID, - error: transferResult.error!, + "error-message": transferResult.error!, + "status-code": transferResult.status, }, request - ); + ).catch(() => {});
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (2)
claim-db-worker/app/api/auth/callback/route.ts(4 hunks)claim-db-worker/components/PageViewTracker.tsx(1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: aidankmcalister
PR: prisma/create-db#48
File: create-db/index.js:423-431
Timestamp: 2025-08-27T16:39:21.271Z
Learning: In the prisma/create-db project, analytics property keys should use the existing kebab-case convention (e.g., "user-agent", "error-type", "status-code") rather than snake_case, to maintain consistency with existing analytics data and avoid duplicate attributes.
📚 Learning: 2025-08-27T16:39:21.271Z
Learnt from: aidankmcalister
PR: prisma/create-db#48
File: create-db/index.js:423-431
Timestamp: 2025-08-27T16:39:21.271Z
Learning: In the prisma/create-db project, analytics property keys should use the existing kebab-case convention (e.g., "user-agent", "error-type", "status-code") rather than snake_case, to maintain consistency with existing analytics data and avoid duplicate attributes.
Applied to files:
claim-db-worker/app/api/auth/callback/route.tsclaim-db-worker/components/PageViewTracker.tsx
🧬 Code graph analysis (2)
claim-db-worker/app/api/auth/callback/route.ts (2)
claim-db-worker/lib/env.ts (1)
getEnv(11-39)create-db-worker/src/index.ts (1)
fetch(15-160)
claim-db-worker/components/PageViewTracker.tsx (1)
claim-db-worker/lib/analytics.ts (1)
sendAnalyticsEvent(3-18)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Workers Builds: create-db-worker
- GitHub Check: Workers Builds: claim-db-worker
|
✅ Preview CLIs & Workers are live! Test the CLIs locally under tag npx create-db@pr55
npx create-pg@pr55
npx create-postgres@$pr55Worker URLs
|
|
✅ Preview CLIs & Workers are live! Test the CLIs locally under tag npx create-db@pr55
npx create-pg@pr55
npx create-postgres@$pr55Worker URLs
|
There was a problem hiding this comment.
Actionable comments posted: 4
♻️ Duplicate comments (11)
claim-db-worker/app/api/analytics/route.ts (4)
7-12: Rate limit by client fingerprint, not just URL.Keying by request.url buckets all clients together. Include IP for fairness.
- const rateLimitResult = await env.CLAIM_DB_RATE_LIMITER.limit({ - key: request.url, - }); + const ip = + request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ?? + request.headers.get("x-real-ip") ?? + "unknown"; + const rateLimitResult = await env.CLAIM_DB_RATE_LIMITER.limit({ + key: `${request.url}:${ip}`, + });
14-16: Return 204 when analytics disabled.Avoid implying a captured event when gated off.
- if (!env.POSTHOG_API_KEY || !env.POSTHOG_API_HOST) { - return NextResponse.json({ success: true }); - } + if (!env.POSTHOG_API_KEY || !env.POSTHOG_API_HOST) { + return NextResponse.json(null, { status: 204 }); + }
18-30: Validate/guard JSON and types; return 400 on client errors.Current parse errors bubble to 500. Add guarded parsing and type checks.
- try { - const { - event, - properties, - }: { event: string; properties: Record<string, any> } = - await request.json(); - - if (!event) { - return NextResponse.json( - { error: "Event name required" }, - { status: 400 } - ); - } + try { + let body: unknown; + try { + body = await request.json(); + } catch { + return NextResponse.json({ error: "Invalid JSON body" }, { status: 400 }); + } + const event = (body as any)?.event; + const properties = (body as any)?.properties; + if (typeof event !== "string") { + return NextResponse.json({ error: "Event name required" }, { status: 400 }); + } + if (properties != null && typeof properties !== "object") { + return NextResponse.json({ error: "Invalid properties" }, { status: 400 }); + }
32-44: Add timeout, check upstream status, and prefer project-id for distinct_id.Prevents hangs and surfaces upstream failures as 502.
- await fetch(`${env.POSTHOG_API_HOST}/e`, { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${env.POSTHOG_API_KEY}`, - }, - body: JSON.stringify({ - api_key: env.POSTHOG_API_KEY, - event, - properties: properties || {}, - distinct_id: "web-claim", - }), - }); + const ac = new AbortController(); + const t = setTimeout(() => ac.abort(), 3000); + const resp = await fetch(`${env.POSTHOG_API_HOST}/e`, { + method: "POST", + headers: { + "Content-Type": "application/json", + // Authorization header likely not required for public ingestion; see verification note below. + Authorization: `Bearer ${env.POSTHOG_API_KEY}`, + }, + body: JSON.stringify({ + api_key: env.POSTHOG_API_KEY, + event, + properties: properties ?? {}, + distinct_id: (properties && (properties as any)["project-id"]) ?? "web-claim", + }), + signal: ac.signal, + }).finally(() => clearTimeout(t)); + if (!resp.ok) { + const text = await resp.text().catch(() => ""); + console.error(`PostHog proxy responded ${resp.status}`, text); + return NextResponse.json({ error: "Upstream analytics failed" }, { status: 502 }); + }claim-db-worker/app/api/auth/callback/route.ts (5)
11-44: Prefer centralizing via /api/analytics; add short timeout; better distinct_id.Avoid duplicating PostHog wiring here and keep requests snappy.
Option A (preferred): forward to internal analytics route.
async function sendServerAnalyticsEvent( event: string, properties: Record<string, any>, request: NextRequest ) { - const env = getEnv(); - - if (!env.POSTHOG_API_KEY || !env.POSTHOG_API_HOST) { - return; - } - - try { - await fetch(`${env.POSTHOG_API_HOST}/e`, { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${env.POSTHOG_API_KEY}`, - }, - body: JSON.stringify({ - api_key: env.POSTHOG_API_KEY, - event, - properties: { - ...properties, - $current_url: request.url, - $user_agent: request.headers.get("user-agent"), - }, - distinct_id: "server-claim", - timestamp: new Date().toISOString(), - }), - }); + try { + const origin = request.nextUrl.origin; + const ac = new AbortController(); + const t = setTimeout(() => ac.abort(), 800); + await fetch(`${origin}/api/analytics`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + event, + properties: { + ...properties, + $current_url: request.url, + $user_agent: request.headers.get("user-agent"), + }, + }), + signal: ac.signal, + cache: "no-store", + }).finally(() => clearTimeout(t)); } catch (error) { console.error("Failed to send server analytics event:", error); } }Option B (if staying direct): add AbortController, remove Bearer if using public ingestion, and set distinct_id from project-id. (See analytics route comment for endpoint/auth verification.)
96-103: Make analytics non-blocking and align property keys (kebab-case).- await sendServerAnalyticsEvent( + void sendServerAnalyticsEvent( "create_db:claim_failed", { "project-id": projectID, - error: errorMessage, + "error-message": errorMessage, }, request - ); + ).catch(() => {});
118-125: Same: non-blocking and kebab-case.- await sendServerAnalyticsEvent( + void sendServerAnalyticsEvent( "create_db:claim_failed", { "project-id": projectID, - error: errorMessage, + "error-message": errorMessage, }, request - ); + ).catch(() => {});
141-147: Do not await analytics on success path.- await sendServerAnalyticsEvent( + void sendServerAnalyticsEvent( "create_db:claim_successful", { "project-id": projectID, }, request - ); + ).catch(() => {});
150-157: Non-blocking, include status-code, and kebab-case.- await sendServerAnalyticsEvent( + void sendServerAnalyticsEvent( "create_db:claim_failed", { "project-id": projectID, - error: transferResult.error!, + "status-code": transferResult.status, + "error-message": transferResult.error!, }, request - ); + ).catch(() => {});claim-db-worker/lib/analytics.ts (1)
3-18: Harden client helper: relative URL, keepalive, and error swallowing; better typing.Prevents failures on undefined origins and avoids breaking UX on network errors.
-export const sendAnalyticsEvent = async ( - event: string, - properties: Record<string, any> -) => { - const response = await fetch(`${window.location.origin}/api/analytics`, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ event, properties }), - }); - - if (!response.ok) { - console.error("Failed to send analytics event:", response); - } -}; +export const sendAnalyticsEvent = async ( + event: string, + properties?: Record<string, unknown> +): Promise<void> => { + const url = "/api/analytics"; // works client-side; server must not import this module + const init: RequestInit = { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ event, properties: properties ?? {} }), + }; + try { + // keepalive helps during navigations/unload + (init as any).keepalive = true; + const resp = await fetch(url, init); + if (!resp.ok) { + const text = await resp.text().catch(() => ""); + console.error("Failed to send analytics event:", resp.status, text); + } + } catch (e) { + // Never break UX due to analytics failures + console.error("Failed to send analytics event:", e); + } +};claim-db-worker/components/PageViewTracker.tsx (1)
11-27: Avoid unhandled rejections; stabilize deps; redact sensitive params; optionally include project-id.useEffect(() => { if (typeof window === "undefined") return; if (pathname) { - const url = window.location.href; - const search = searchParams?.toString(); - const fullPath = search ? `${pathname}?${search}` : pathname; + const url = window.location.href; + const search = searchParams?.toString(); + // redact sensitive keys + const redactSearch = (s: string | null) => { + if (!s) return null; + const p = new URLSearchParams(s); + for (const k of ["code", "state", "token", "access_token", "id_token", "refresh_token", "password", "secret"]) { + p.delete(k); + } + const out = p.toString(); + return out.length ? out : null; + }; + const redacted = redactSearch(search); + const fullPath = redacted ? `${pathname}?${redacted}` : pathname; - sendAnalyticsEvent("create_db:claim_page_viewed", { - path: pathname, - full_path: fullPath, - url: url, - referrer: document.referrer || "", - timestamp: new Date().toISOString(), - }); + const params = new URLSearchParams(search ?? ""); + const projectId = params.get("projectID") ?? undefined; + void sendAnalyticsEvent("create_db:claim_page_viewed", { + path: pathname, + full_path: fullPath, + url, + referrer: document.referrer || "", + "project-id": projectId, + timestamp: new Date().toISOString(), + }).catch(() => {}); } - }, [pathname, searchParams]); + }, [pathname, searchParams?.toString()]);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (6)
claim-db-worker/app/api/analytics/route.ts(1 hunks)claim-db-worker/app/api/auth/callback/route.ts(4 hunks)claim-db-worker/app/api/claim/route.ts(2 hunks)claim-db-worker/app/layout.tsx(2 hunks)claim-db-worker/components/PageViewTracker.tsx(1 hunks)claim-db-worker/lib/analytics.ts(1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Workers Builds: claim-db-worker
- GitHub Check: Workers Builds: create-db-worker
🔇 Additional comments (4)
claim-db-worker/app/api/claim/route.ts (2)
22-24: Event-name consistency check.You emit "create_db:claim_page_viewed" from PageViewTracker; confirm this distinct name is intentional vs unifying on one.
22-24: Fire-and-forget server analytics via internal route; don’t block response.Also guards against network errors.
- await sendAnalyticsEvent("create_db:claim_viewed", { - "project-id": projectID, - }); + try { + const origin = request.nextUrl.origin; + const ac = new AbortController(); + const t = setTimeout(() => ac.abort(), 800); + void fetch(`${origin}/api/analytics`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + event: "create_db:claim_viewed", + properties: { "project-id": projectID }, + }), + signal: ac.signal, + cache: "no-store", + }) + .catch(() => {}) + .finally(() => clearTimeout(t)); + } catch { + // swallow + }Likely an incorrect or invalid review comment.
claim-db-worker/app/layout.tsx (1)
51-52: LGTM: tracking wired in layout.Placement before Toaster is fine.
claim-db-worker/components/PageViewTracker.tsx (1)
32-38: LGTM: Suspense wrapper and null UI.Component is non-visual and safe for layout use.
Summary by CodeRabbit
New Features
Improvements