-
Notifications
You must be signed in to change notification settings - Fork 1
DC-5180-analytics #55
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
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@$pr55 Worker 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 thatstate
is 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
: Addcode
null-check before token exchangeInsert before the
exchangeCodeForToken
call 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@$pr55 Worker URLs
|
✅ Preview CLIs & Workers are live! Test the CLIs locally under tag npx create-db@pr55
npx create-pg@pr55
npx create-postgres@$pr55 Worker URLs
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.tsx
claim-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@$pr55 Worker URLs
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
: Validatecode
param 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_path
tofull-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
projectID
from 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.ts
claim-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@$pr55 Worker URLs
|
✅ Preview CLIs & Workers are live! Test the CLIs locally under tag npx create-db@pr55
npx create-pg@pr55
npx create-postgres@$pr55 Worker URLs
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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