feat: add first-party Tinybird analytics#2003
Conversation
|
hey @greptileai, please re-review the PR |
|
hey @greptileai, please re-review the PR |
|
hey @greptileai, please re-review the PR |
|
hey @greptileai, please re-review the PR |
|
hey @greptileai, please re-review the PR |
|
hey @greptileai, please re-review the PR |
|
hey @greptileai, please re-review the PR |
| const browserClaims = | ||
| hasExpectedBrowserAnalyticsMetadata( | ||
| requestMetadata, | ||
| allowedOrigins, | ||
| ) && | ||
| readProductAnalyticsBrowserTokenClaims( | ||
| readProductAnalyticsBrowserToken(headers.cookie), | ||
| serverEnv().NEXTAUTH_SECRET, | ||
| ); |
There was a problem hiding this comment.
Minor robustness nit: if token claim parsing throws (bad secret, malformed cookie, etc) this will currently 500. I'd rather treat that as “no browser claims” and let it fall through to the normal BadRequest path.
| const browserClaims = | |
| hasExpectedBrowserAnalyticsMetadata( | |
| requestMetadata, | |
| allowedOrigins, | |
| ) && | |
| readProductAnalyticsBrowserTokenClaims( | |
| readProductAnalyticsBrowserToken(headers.cookie), | |
| serverEnv().NEXTAUTH_SECRET, | |
| ); | |
| let browserClaims; | |
| if ( | |
| hasExpectedBrowserAnalyticsMetadata( | |
| requestMetadata, | |
| allowedOrigins, | |
| ) | |
| ) { | |
| try { | |
| browserClaims = readProductAnalyticsBrowserTokenClaims( | |
| readProductAnalyticsBrowserToken(headers.cookie), | |
| serverEnv().NEXTAUTH_SECRET, | |
| ); | |
| } catch { | |
| browserClaims = undefined; | |
| } | |
| } |
| if (result.error || result.status !== 0) { | ||
| throw new Error("Unable to verify Tinybird workspace identity."); | ||
| } |
There was a problem hiding this comment.
This failure path drops the actual stderr/stdout, which makes CI deploy debugging pretty painful. I’d include stderr (trimmed) in the thrown error message.
| if (result.error || result.status !== 0) { | |
| throw new Error("Unable to verify Tinybird workspace identity."); | |
| } | |
| if (result.error || result.status !== 0) { | |
| const stderr = (result.stderr ?? "").toString().trim(); | |
| throw new Error( | |
| stderr | |
| ? `Unable to verify Tinybird workspace identity: ${stderr}` | |
| : "Unable to verify Tinybird workspace identity.", | |
| ); | |
| } |
a9b199c to
fee7c3a
Compare
| { | ||
| eventId, | ||
| eventName: event.eventName, | ||
| occurredAt: event.occurredAt ?? new Date().toISOString(), |
There was a problem hiding this comment.
Small robustness thing: occurredAt is passed through as-is, so a bad caller value will land as a bad timestamp in Tinybird. Might be worth normalizing/validating and falling back to now.
| occurredAt: event.occurredAt ?? new Date().toISOString(), | |
| occurredAt: | |
| event.occurredAt && Number.isFinite(Date.parse(event.occurredAt)) | |
| ? new Date(event.occurredAt).toISOString() | |
| : new Date().toISOString(), |
| const client = new PostHog(key, { host }); | ||
| client.capture({ | ||
| distinctId: event.distinctId, | ||
| event: event.eventName, | ||
| properties: event.properties, | ||
| }); | ||
| await client.shutdown(); |
There was a problem hiding this comment.
Minor cleanup: if client.capture throws, shutdown() won’t run. I’d wrap shutdown in a finally.
| const client = new PostHog(key, { host }); | |
| client.capture({ | |
| distinctId: event.distinctId, | |
| event: event.eventName, | |
| properties: event.properties, | |
| }); | |
| await client.shutdown(); | |
| const client = new PostHog(key, { host }); | |
| try { | |
| client.capture({ | |
| distinctId: event.distinctId, | |
| event: event.eventName, | |
| properties: event.properties, | |
| }); | |
| } finally { | |
| await client.shutdown(); | |
| } |
…lytics # Conflicts: # apps/web/app/api/desktop/[...route]/root.ts # apps/web/app/api/settings/billing/guest-checkout/route.ts # apps/web/app/api/webhooks/stripe/route.ts
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
|
hey @greptileai, please re-review the PR |
|
hey @greptileai, please re-review the PR |
| subscription, | ||
| inviteQuota, | ||
| user: dbUser, | ||
| isFirstPurchase: |
There was a problem hiding this comment.
Minor robustness thing: isFirstPurchase coming from session.metadata is easy to lose / skew if metadata changes, and it’s already derivable from the pre-update user row.
| isFirstPurchase: | |
| isFirstPurchase: !dbUser.stripeSubscriptionId, |
| subscription, | ||
| inviteQuota, | ||
| ...(dbUser ? { user: dbUser } : {}), | ||
| isFirstPurchase: |
There was a problem hiding this comment.
Same idea here: if dbUser is present, it’s a better source of truth than checkout-session metadata.
| isFirstPurchase: | |
| isFirstPurchase: dbUser | |
| ? !dbUser.stripeSubscriptionId | |
| : session.metadata?.analyticsIsFirstPurchase === "true", |
| #recordDrop(count: number) { | ||
| if (count === 0) return; | ||
| this.#dropped += count; | ||
| this.#onDrop?.(this.#dropped); |
There was a problem hiding this comment.
onDrop takes a count, but this passes the running total. If you want the incremental drop count, pass count here and let callers read queue.dropped for the total.
| this.#onDrop?.(this.#dropped); | |
| this.#onDrop?.(count); |
| }; | ||
|
|
||
| const encodeLocalToken = (userId, tokenId) => { | ||
| const payload = `{"u": "${userId}", "id": "${tokenId}", "host": null}`; |
There was a problem hiding this comment.
Small robustness/readability thing: since this is JSON anyway, using JSON.stringify avoids having to worry about escaping/quoting in the template literal.
| const payload = `{"u": "${userId}", "id": "${tokenId}", "host": null}`; | |
| const payload = JSON.stringify({ u: userId, id: tokenId, host: null }); |
Summary
Validation
pnpm analytics:test— 152 tests passedpnpm --filter @cap/web test --run __tests__/unit/mobile-checkout.test.ts— 7 tests passedpnpm typecheckcargo fmt --allcargo check -p cap-desktopwith the repository FFmpeg 7 toolchaincargo test -p cap-desktop product_analytics --lib— 9 tests passedGreptile Summary
This PR adds first-party Tinybird product analytics across web, desktop, and deployment tooling. The main changes are:
/api/eventsingestion path for bounded client analytics.Confidence Score: 5/5
This looks safe to merge.
Important Files Changed
Reviews (9): Last reviewed commit: "fix: parse Tinybird workspace identity" | Re-trigger Greptile
Context used: