diff --git a/apps/sim/instrumentation-client.ts b/apps/sim/instrumentation-client.ts index 584abb63f..6ba4db89b 100644 --- a/apps/sim/instrumentation-client.ts +++ b/apps/sim/instrumentation-client.ts @@ -5,7 +5,34 @@ * It respects the user's telemetry preferences stored in localStorage. * */ -import { env } from './lib/env' +import posthog from 'posthog-js' +import { env, getEnv, isTruthy } from './lib/env' + +// Initialize PostHog only if explicitly enabled +if (isTruthy(getEnv('NEXT_PUBLIC_POSTHOG_ENABLED')) && getEnv('NEXT_PUBLIC_POSTHOG_KEY')) { + posthog.init(getEnv('NEXT_PUBLIC_POSTHOG_KEY')!, { + api_host: '/ingest', + ui_host: 'https://us.posthog.com', + person_profiles: 'identified_only', + capture_pageview: true, + capture_pageleave: true, + capture_performance: true, + session_recording: { + maskAllInputs: false, + maskInputOptions: { + password: true, + email: false, + }, + recordCrossOriginIframes: false, + recordHeaders: true, + recordBody: true, + }, + autocapture: true, + capture_dead_clicks: true, + persistence: 'localStorage+cookie', + enable_heatmaps: true, + }) +} if (typeof window !== 'undefined') { const TELEMETRY_STATUS_KEY = 'simstudio-telemetry-status' diff --git a/apps/sim/lib/env.ts b/apps/sim/lib/env.ts index bc6c9c6b7..baef2a9cf 100644 --- a/apps/sim/lib/env.ts +++ b/apps/sim/lib/env.ts @@ -90,6 +90,7 @@ export const env = createEnv({ TELEMETRY_ENDPOINT: z.string().url().optional(), // Custom telemetry/analytics endpoint COST_MULTIPLIER: z.number().optional(), // Multiplier for cost calculations LOG_LEVEL: z.enum(['DEBUG', 'INFO', 'WARN', 'ERROR']).optional(), // Minimum log level to display (defaults to ERROR in production, DEBUG in development) + POSTHOG_ENABLED: z.boolean().optional(), // Enable PostHog analytics and session recording // External Services BROWSERBASE_API_KEY: z.string().min(1).optional(), // Browserbase API key for browser automation @@ -258,6 +259,8 @@ export const env = createEnv({ // Analytics & Tracking NEXT_PUBLIC_GOOGLE_API_KEY: z.string().optional(), // Google API key for client-side API calls NEXT_PUBLIC_GOOGLE_PROJECT_NUMBER: z.string().optional(), // Google project number for Drive picker + NEXT_PUBLIC_POSTHOG_ENABLED: z.boolean().optional(), // Enable PostHog analytics (client-side) + NEXT_PUBLIC_POSTHOG_KEY: z.string().optional(), // PostHog project API key // UI Branding & Whitelabeling NEXT_PUBLIC_BRAND_NAME: z.string().optional(), // Custom brand name (defaults to "Sim") @@ -317,6 +320,8 @@ export const env = createEnv({ NEXT_PUBLIC_EMAIL_PASSWORD_SIGNUP_ENABLED: process.env.NEXT_PUBLIC_EMAIL_PASSWORD_SIGNUP_ENABLED, NEXT_PUBLIC_E2B_ENABLED: process.env.NEXT_PUBLIC_E2B_ENABLED, NEXT_PUBLIC_COPILOT_TRAINING_ENABLED: process.env.NEXT_PUBLIC_COPILOT_TRAINING_ENABLED, + NEXT_PUBLIC_POSTHOG_ENABLED: process.env.NEXT_PUBLIC_POSTHOG_ENABLED, + NEXT_PUBLIC_POSTHOG_KEY: process.env.NEXT_PUBLIC_POSTHOG_KEY, NODE_ENV: process.env.NODE_ENV, NEXT_TELEMETRY_DISABLED: process.env.NEXT_TELEMETRY_DISABLED, }, diff --git a/apps/sim/lib/session/session-context.tsx b/apps/sim/lib/session/session-context.tsx index 52808e414..19d881516 100644 --- a/apps/sim/lib/session/session-context.tsx +++ b/apps/sim/lib/session/session-context.tsx @@ -2,6 +2,7 @@ import type React from 'react' import { createContext, useCallback, useEffect, useMemo, useState } from 'react' +import posthog from 'posthog-js' import { client } from '@/lib/auth-client' export type AppSession = { @@ -52,6 +53,25 @@ export function SessionProvider({ children }: { children: React.ReactNode }) { loadSession() }, [loadSession]) + useEffect(() => { + if (isPending || typeof posthog.identify !== 'function') { + return + } + + try { + if (data?.user) { + posthog.identify(data.user.id, { + email: data.user.email, + name: data.user.name, + email_verified: data.user.emailVerified, + created_at: data.user.createdAt, + }) + } else { + posthog.reset() + } + } catch {} + }, [data, isPending]) + const value = useMemo( () => ({ data, isPending, error, refetch: loadSession }), [data, isPending, error, loadSession] diff --git a/apps/sim/next.config.ts b/apps/sim/next.config.ts index c77a33389..15f92ab42 100644 --- a/apps/sim/next.config.ts +++ b/apps/sim/next.config.ts @@ -238,6 +238,22 @@ const nextConfig: NextConfig = { return redirects }, + async rewrites() { + if (!isTruthy(env.POSTHOG_ENABLED)) { + return [] + } + + return [ + { + source: '/ingest/static/:path*', + destination: 'https://us-assets.i.posthog.com/static/:path*', + }, + { + source: '/ingest/:path*', + destination: 'https://us.i.posthog.com/:path*', + }, + ] + }, } export default nextConfig diff --git a/apps/sim/package.json b/apps/sim/package.json index 2c9f496c3..94a78513d 100644 --- a/apps/sim/package.json +++ b/apps/sim/package.json @@ -28,8 +28,8 @@ "@aws-sdk/s3-request-presigner": "^3.779.0", "@azure/communication-email": "1.0.0", "@azure/storage-blob": "12.27.0", - "@better-auth/stripe": "1.3.12", "@better-auth/sso": "1.3.12", + "@better-auth/stripe": "1.3.12", "@browserbasehq/stagehand": "^2.0.0", "@cerebras/cerebras_cloud_sdk": "^1.23.0", "@e2b/code-interpreter": "^2.0.0", @@ -93,6 +93,8 @@ "openai": "^4.91.1", "papaparse": "5.5.3", "pdf-parse": "1.1.1", + "posthog-js": "1.268.9", + "posthog-node": "5.9.2", "prismjs": "^1.30.0", "react": "19.1.0", "react-colorful": "5.6.1", diff --git a/bun.lock b/bun.lock index 2afd31f06..0d853e1be 100644 --- a/bun.lock +++ b/bun.lock @@ -127,6 +127,8 @@ "openai": "^4.91.1", "papaparse": "5.5.3", "pdf-parse": "1.1.1", + "posthog-js": "1.268.9", + "posthog-node": "5.9.2", "prismjs": "^1.30.0", "react": "19.1.0", "react-colorful": "5.6.1", @@ -812,6 +814,8 @@ "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], + "@posthog/core": ["@posthog/core@1.2.2", "", {}, "sha512-f16Ozx6LIigRG+HsJdt+7kgSxZTHeX5f1JlCGKI1lXcvlZgfsCR338FuMI2QRYXGl+jg/vYFzGOTQBxl90lnBg=="], + "@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="], "@protobufjs/base64": ["@protobufjs/base64@1.1.2", "", {}, "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="], @@ -1622,6 +1626,8 @@ "copy-anything": ["copy-anything@3.0.5", "", { "dependencies": { "is-what": "^4.1.8" } }, "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w=="], + "core-js": ["core-js@3.45.1", "", {}, "sha512-L4NPsJlCfZsPeXukyzHFlg/i7IIVwHSItR0wg0FLNqYClJ4MQYTYLbC7EkjKYRLZF2iof2MUgN0EGy7MdQFChg=="], + "core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="], "cors": ["cors@2.8.5", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g=="], @@ -2564,6 +2570,12 @@ "postgres": ["postgres@3.4.7", "", {}, "sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw=="], + "posthog-js": ["posthog-js@1.268.9", "", { "dependencies": { "@posthog/core": "1.2.2", "core-js": "^3.38.1", "fflate": "^0.4.8", "preact": "^10.19.3", "web-vitals": "^4.2.4" }, "peerDependencies": { "@rrweb/types": "2.0.0-alpha.17", "rrweb-snapshot": "2.0.0-alpha.17" }, "optionalPeers": ["@rrweb/types", "rrweb-snapshot"] }, "sha512-ejK5/i0TUQ8I1SzaIn7xWNf5TzOjWquawpgjKit8DyucD3Z1yf7LTMtgCYZN8oRx9VjiPcP34fSk8YsWQmmkTQ=="], + + "posthog-node": ["posthog-node@5.9.2", "", { "dependencies": { "@posthog/core": "1.2.2" } }, "sha512-oU7FbFcH5cn40nhP04cBeT67zE76EiGWjKKzDvm6IOm5P83sqM0Ij0wMJQSHp+QI6ZN7MLzb+4xfMPUEZ4q6CA=="], + + "preact": ["preact@10.27.2", "", {}, "sha512-5SYSgFKSyhCbk6SrXyMpqjb5+MQBgfvEKE/OC+PujcY34sOpqtr+0AZQtPYx5IA6VxynQ7rUPCtKzyovpj9Bpg=="], + "prettier": ["prettier@3.4.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ=="], "prismjs": ["prismjs@1.30.0", "", {}, "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="], @@ -3050,6 +3062,8 @@ "web-streams-polyfill": ["web-streams-polyfill@4.0.0-beta.3", "", {}, "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug=="], + "web-vitals": ["web-vitals@4.2.4", "", {}, "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw=="], + "webidl-conversions": ["webidl-conversions@7.0.0", "", {}, "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="], "whatwg-encoding": ["whatwg-encoding@3.1.1", "", { "dependencies": { "iconv-lite": "0.6.3" } }, "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ=="], @@ -3502,6 +3516,8 @@ "postcss-nested/postcss-selector-parser": ["postcss-selector-parser@6.1.2", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg=="], + "posthog-js/fflate": ["fflate@0.4.8", "", {}, "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA=="], + "protobufjs/@types/node": ["@types/node@24.2.1", "", { "dependencies": { "undici-types": "~7.10.0" } }, "sha512-DRh5K+ka5eJic8CjH7td8QpYEV6Zo10gfRkjHCO3weqZHWDtAaSTFtl4+VMqOJ4N5jcuhZ9/l+yy8rVgw7BQeQ=="], "raw-body/iconv-lite": ["iconv-lite@0.7.0", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ=="],