From fe92a62f16ea3d2679c4379e7ccf2a6d505dd76f Mon Sep 17 00:00:00 2001 From: Satya Patel Date: Sun, 14 Dec 2025 23:27:39 -0500 Subject: [PATCH] feat(desktop): add PostHog feature flag to gate auth requirement MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add posthog-js for feature flag support in desktop app - Create PostHogProvider to initialize PostHog in renderer - Add `require-desktop-auth` feature flag to control auth requirement - Update CSP to allow PostHog connections - Fix dev/prod singleton lock conflict with app.setName() - Centralize feature flags in shared package 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- apps/desktop/electron.vite.config.ts | 6 ++ apps/desktop/package.json | 1 + apps/desktop/src/main/index.ts | 5 ++ .../src/renderer/contexts/AppProviders.tsx | 9 ++- .../src/renderer/contexts/PostHogProvider.tsx | 56 +++++++++++++++++++ apps/desktop/src/renderer/contexts/index.ts | 1 + apps/desktop/src/renderer/index.html | 6 +- .../src/renderer/screens/main/index.tsx | 40 +++++++++++-- bun.lock | 17 +++++- packages/shared/src/constants.ts | 6 ++ 10 files changed, 135 insertions(+), 12 deletions(-) create mode 100644 apps/desktop/src/renderer/contexts/PostHogProvider.tsx diff --git a/apps/desktop/electron.vite.config.ts b/apps/desktop/electron.vite.config.ts index a832251f309..8644e787fae 100644 --- a/apps/desktop/electron.vite.config.ts +++ b/apps/desktop/electron.vite.config.ts @@ -109,6 +109,12 @@ export default defineConfig({ "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV), "process.platform": JSON.stringify(process.platform), "import.meta.env.DEV_SERVER_PORT": JSON.stringify(DEV_SERVER_PORT), + "import.meta.env.NEXT_PUBLIC_POSTHOG_KEY": JSON.stringify( + process.env.NEXT_PUBLIC_POSTHOG_KEY, + ), + "import.meta.env.NEXT_PUBLIC_POSTHOG_HOST": JSON.stringify( + process.env.NEXT_PUBLIC_POSTHOG_HOST, + ), }, server: { diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 2fc930168e3..7da58d28c4f 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -78,6 +78,7 @@ "nanoid": "^5.1.6", "node-pty": "1.1.0-beta30", "os-locale": "^6.0.2", + "posthog-js": "^1.306.1", "react": "^19.2.3", "react-arborist": "^3.4.3", "react-dnd": "^16.0.1", diff --git a/apps/desktop/src/main/index.ts b/apps/desktop/src/main/index.ts index 3a0a302c6a8..ced4287f82a 100644 --- a/apps/desktop/src/main/index.ts +++ b/apps/desktop/src/main/index.ts @@ -10,6 +10,11 @@ import { initDb } from "./lib/db"; import { terminalManager } from "./lib/terminal"; import { MainWindow } from "./windows/main"; +// Set different app name for dev to avoid singleton lock conflicts with production +if (process.env.NODE_ENV === "development") { + app.setName("Superset Dev"); +} + // Register protocol handler for deep linking // In development, we need to provide the execPath and args if (process.defaultApp) { diff --git a/apps/desktop/src/renderer/contexts/AppProviders.tsx b/apps/desktop/src/renderer/contexts/AppProviders.tsx index abb72b78760..0ecd8980969 100644 --- a/apps/desktop/src/renderer/contexts/AppProviders.tsx +++ b/apps/desktop/src/renderer/contexts/AppProviders.tsx @@ -1,5 +1,6 @@ import type React from "react"; import { MonacoProvider } from "./MonacoProvider"; +import { PostHogProvider } from "./PostHogProvider"; import { TRPCProvider } from "./TRPCProvider"; interface AppProvidersProps { @@ -8,8 +9,10 @@ interface AppProvidersProps { export function AppProviders({ children }: AppProvidersProps) { return ( - - {children} - + + + {children} + + ); } diff --git a/apps/desktop/src/renderer/contexts/PostHogProvider.tsx b/apps/desktop/src/renderer/contexts/PostHogProvider.tsx new file mode 100644 index 00000000000..100e0a43f19 --- /dev/null +++ b/apps/desktop/src/renderer/contexts/PostHogProvider.tsx @@ -0,0 +1,56 @@ +import posthog from "posthog-js"; +import { PostHogProvider as PHProvider } from "posthog-js/react"; +import type React from "react"; +import { useEffect, useState } from "react"; + +const POSTHOG_KEY = import.meta.env.NEXT_PUBLIC_POSTHOG_KEY as + | string + | undefined; +const POSTHOG_HOST = + (import.meta.env.NEXT_PUBLIC_POSTHOG_HOST as string | undefined) || + "https://us.i.posthog.com"; + +interface PostHogProviderProps { + children: React.ReactNode; +} + +export function PostHogProvider({ children }: PostHogProviderProps) { + const [isInitialized, setIsInitialized] = useState(false); + + useEffect(() => { + if (!POSTHOG_KEY) { + console.log("[posthog] No PostHog key configured, skipping init"); + setIsInitialized(true); + return; + } + + posthog.init(POSTHOG_KEY, { + api_host: POSTHOG_HOST, + // Electron apps don't have traditional page views + capture_pageview: false, + // Disable session recording for now + disable_session_recording: true, + // Persist across sessions + persistence: "localStorage", + // Load feature flags on init + bootstrap: { + featureFlags: {}, + }, + }); + + setIsInitialized(true); + console.log("[posthog] Initialized"); + }, []); + + // Don't render children until PostHog is initialized (or skipped) + if (!isInitialized) { + return null; + } + + // If no PostHog key, just render children without the provider + if (!POSTHOG_KEY) { + return <>{children}; + } + + return {children}; +} diff --git a/apps/desktop/src/renderer/contexts/index.ts b/apps/desktop/src/renderer/contexts/index.ts index 5ffa9434eed..b6a0b6e0d28 100644 --- a/apps/desktop/src/renderer/contexts/index.ts +++ b/apps/desktop/src/renderer/contexts/index.ts @@ -1,3 +1,4 @@ export { AppProviders } from "./AppProviders"; export { MonacoProvider, SUPERSET_THEME } from "./MonacoProvider"; +export { PostHogProvider } from "./PostHogProvider"; export { TRPCProvider } from "./TRPCProvider"; diff --git a/apps/desktop/src/renderer/index.html b/apps/desktop/src/renderer/index.html index 024660e406e..395a5d00acd 100644 --- a/apps/desktop/src/renderer/index.html +++ b/apps/desktop/src/renderer/index.html @@ -9,13 +9,13 @@ https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP - default-src 'self': Only allow resources from same origin - - script-src 'self': Only allow scripts from same origin (no inline) + - script-src 'self' https://*.posthog.com: Allow scripts from same origin + PostHog - style-src 'self' 'unsafe-inline': Allow styles from same origin + inline (needed for CSS-in-JS) - - connect-src 'self' ws: wss:: Allow WebSocket connections for HMR in dev + - connect-src 'self' ws: wss: https://*.posthog.com: Allow WebSocket connections for HMR + PostHog analytics - img-src 'self' data:: Allow images from same origin + data URIs - font-src 'self': Allow fonts from same origin --> - + diff --git a/apps/desktop/src/renderer/screens/main/index.tsx b/apps/desktop/src/renderer/screens/main/index.tsx index a0253c21488..babb0cc2dc9 100644 --- a/apps/desktop/src/renderer/screens/main/index.tsx +++ b/apps/desktop/src/renderer/screens/main/index.tsx @@ -1,5 +1,7 @@ +import { FEATURE_FLAGS } from "@superset/shared/constants"; import { Button } from "@superset/ui/button"; -import { useCallback, useState } from "react"; +import { useFeatureFlagEnabled, usePostHog } from "posthog-js/react"; +import { useCallback, useEffect, useState } from "react"; import { DndProvider } from "react-dnd"; import { useHotkeys } from "react-hotkeys-hook"; import { HiArrowPath } from "react-icons/hi2"; @@ -30,10 +32,24 @@ function LoadingSpinner() { export function MainScreen() { const utils = trpc.useUtils(); + const posthog = usePostHog(); const { data: authState } = trpc.auth.getState.useQuery(); const isSignedIn = authState?.isSignedIn ?? false; const isAuthLoading = !authState; + // Feature flag to control auth requirement + const requireAuth = useFeatureFlagEnabled(FEATURE_FLAGS.REQUIRE_DESKTOP_AUTH); + const [flagsLoaded, setFlagsLoaded] = useState(false); + + // Track when feature flags are loaded + useEffect(() => { + if (posthog) { + posthog.onFeatureFlags(() => { + setFlagsLoaded(true); + }); + } + }, [posthog]); + // Subscribe to auth state changes trpc.auth.onStateChange.useSubscription(undefined, { onData: () => utils.auth.getState.invalidate(), @@ -157,8 +173,23 @@ export function MainScreen() { const showStartView = !isLoading && !activeWorkspace && currentView !== "settings"; - // Show sign-in screen if not authenticated - if (isAuthLoading) { + // Wait for feature flags to load before deciding on auth + const shouldRequireAuth = flagsLoaded && requireAuth === true; + + // Show empty screen while feature flags are loading + if (!flagsLoaded) { + return ( + <> + + +
+ + + ); + } + + // Show loading while auth state is being determined (only if auth is required) + if (shouldRequireAuth && isAuthLoading) { return ( <> @@ -171,7 +202,8 @@ export function MainScreen() { ); } - if (!isSignedIn) { + // Show sign-in screen if auth is required and user is not signed in + if (shouldRequireAuth && !isSignedIn) { return ( <> diff --git a/bun.lock b/bun.lock index 20f35dbd9f0..87398a21558 100644 --- a/bun.lock +++ b/bun.lock @@ -107,7 +107,7 @@ }, "apps/desktop": { "name": "@superset/desktop", - "version": "0.0.16", + "version": "0.0.20", "dependencies": { "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^10.0.0", @@ -156,6 +156,7 @@ "nanoid": "^5.1.6", "node-pty": "1.1.0-beta30", "os-locale": "^6.0.2", + "posthog-js": "^1.306.1", "react": "^19.2.3", "react-arborist": "^3.4.3", "react-dnd": "^16.0.1", @@ -833,6 +834,8 @@ "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], + "@posthog/core": ["@posthog/core@1.7.1", "", { "dependencies": { "cross-spawn": "^7.0.6" } }, "sha512-kjK0eFMIpKo9GXIbts8VtAknsoZ18oZorANdtuTj1CbgS28t4ZVq//HAWhnxEuXRTrtkd+SUJ6Ux3j2Af8NCuA=="], + "@radix-ui/number": ["@radix-ui/number@1.1.1", "", {}, "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g=="], "@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="], @@ -1577,6 +1580,8 @@ "copy-anything": ["copy-anything@4.0.5", "", { "dependencies": { "is-what": "^5.2.0" } }, "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA=="], + "core-js": ["core-js@3.47.0", "", {}, "sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg=="], + "core-util-is": ["core-util-is@1.0.2", "", {}, "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ=="], "cose-base": ["cose-base@1.0.3", "", { "dependencies": { "layout-base": "^1.0.0" } }, "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg=="], @@ -1869,7 +1874,7 @@ "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], - "fflate": ["fflate@0.8.2", "", {}, "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="], + "fflate": ["fflate@0.4.8", "", {}, "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA=="], "figures": ["figures@6.1.0", "", { "dependencies": { "is-unicode-supported": "^2.0.0" } }, "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg=="], @@ -2585,10 +2590,14 @@ "postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="], + "posthog-js": ["posthog-js@1.306.1", "", { "dependencies": { "@posthog/core": "1.7.1", "core-js": "^3.38.1", "fflate": "^0.4.8", "preact": "^10.19.3", "web-vitals": "^4.2.4" } }, "sha512-wO7bliv/5tlAlfoKCUzwkGXZVNexk0dHigMf9tNp0q1rzs62wThogREY7Tz7h/iWKYiuXy1RumtVlTmHuBXa1w=="], + "postject": ["postject@1.0.0-alpha.6", "", { "dependencies": { "commander": "^9.4.0" }, "bin": { "postject": "dist/cli.js" } }, "sha512-b9Eb8h2eVqNE8edvKdwqkrY6O7kAwmI8kcnBv1NScolYJbo59XUF0noFq+lxbC1yN20bmC0WBEbDC5H/7ASb0A=="], "potpack": ["potpack@1.0.2", "", {}, "sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ=="], + "preact": ["preact@10.28.0", "", {}, "sha512-rytDAoiXr3+t6OIP3WGlDd0ouCUG1iCWzkcY3++Nreuoi17y6T5i/zRhe6uYfoVcxq6YU+sBtJouuRDsq8vvqA=="], + "pretty-ms": ["pretty-ms@9.3.0", "", { "dependencies": { "parse-ms": "^4.0.0" } }, "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ=="], "prismjs": ["prismjs@1.30.0", "", {}, "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="], @@ -3173,6 +3182,8 @@ "web-namespaces": ["web-namespaces@2.0.1", "", {}, "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="], + "web-vitals": ["web-vitals@4.2.4", "", {}, "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw=="], + "webgl-constants": ["webgl-constants@1.1.1", "", {}, "sha512-LkBXKjU5r9vAW7Gcu3T5u+5cvSvh5WwINdr0C+9jpzVB41cjQAP5ePArDtk/WHYdVj0GefCgM73BA7FlIiNtdg=="], "webgl-sdf-generator": ["webgl-sdf-generator@1.1.1", "", {}, "sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA=="], @@ -3321,6 +3332,8 @@ "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "@types/three/fflate": ["fflate@0.8.2", "", {}, "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="], + "@vue/compiler-core/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], "@xyflow/react/zustand": ["zustand@4.5.7", "", { "dependencies": { "use-sync-external-store": "^1.2.2" }, "peerDependencies": { "@types/react": ">=16.8", "immer": ">=9.0.6", "react": ">=16.8" }, "optionalPeers": ["@types/react", "immer", "react"] }, "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw=="], diff --git a/packages/shared/src/constants.ts b/packages/shared/src/constants.ts index d7137c92094..709ecc21aee 100644 --- a/packages/shared/src/constants.ts +++ b/packages/shared/src/constants.ts @@ -31,3 +31,9 @@ export const TOKEN_CONFIG = { /** Refresh access token when this many seconds remain (5 minutes) */ REFRESH_THRESHOLD: 5 * 60, } as const; + +// PostHog Feature Flags +export const FEATURE_FLAGS = { + /** When enabled, users must sign in to use the desktop app */ + REQUIRE_DESKTOP_AUTH: "require-desktop-auth", +} as const;