Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .env.development
Original file line numberDiff line numberDiff line change
Expand Up@@ -34,7 +34,7 @@ AUTH_URL="http://localhost:3000"
# Sentry
# SENTRY_BACKEND_DSN=""
# NEXT_PUBLIC_SENTRY_WEBAPP_DSN=""
SENTRY_ENVIRONMENT="dev"
# SENTRY_ENVIRONMENT="dev"
# NEXT_PUBLIC_SENTRY_ENVIRONMENT="dev"
# SENTRY_AUTH_TOKEN=

Expand DownExpand Up@@ -76,4 +76,6 @@ SOURCEBOT_TELEMETRY_DISABLED=true # Disables telemetry collection

# CONFIG_MAX_REPOS_NO_TOKEN=
# NODE_ENV=
# SOURCEBOT_TENANCY_MODE=single
# SOURCEBOT_TENANCY_MODE=single

# NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT=
9 changes: 7 additions & 2 deletions Dockerfile
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,6 +9,7 @@ ARG SOURCEBOT_VERSION
# @see: https://posthog.com/tutorials/api-capture-events#authenticating-with-the-project-api-key
ARG POSTHOG_PAPIK
ARG SENTRY_ENVIRONMENT
ARG SOURCEBOT_CLOUD_ENVIRONMENT

FROM node:20-alpine3.19 AS node-alpine
FROM golang:1.23.4-alpine3.19 AS go-alpine
Expand DownExpand Up@@ -52,9 +53,11 @@ ARG POSTHOG_PAPIK
ENV NEXT_PUBLIC_POSTHOG_PAPIK=$POSTHOG_PAPIK
ARG SENTRY_ENVIRONMENT
ENV NEXT_PUBLIC_SENTRY_ENVIRONMENT=$SENTRY_ENVIRONMENT
ARG SOURCEBOT_CLOUD_ENVIRONMENT
ENV NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT=$SOURCEBOT_CLOUD_ENVIRONMENT
# Local args
ARG SENTRY_WEBAPP_DSN
ENV NEXT_PUBLIC_SENTRY_WEBAPP_DSN=$SENTRY_WEBAPP_DSN
ARG NEXT_PUBLIC_SENTRY_WEBAPP_DSN
ENV NEXT_PUBLIC_SENTRY_WEBAPP_DSN=$NEXT_PUBLIC_SENTRY_WEBAPP_DSN
# -----------

RUN apk add --no-cache libc6-compat
Expand DownExpand Up@@ -104,6 +107,8 @@ ARG SOURCEBOT_VERSION
ENV SOURCEBOT_VERSION=$SOURCEBOT_VERSION
ARG POSTHOG_PAPIK
ENV POSTHOG_PAPIK=$POSTHOG_PAPIK
ARG SENTRY_ENVIRONMENT
ENV SENTRY_ENVIRONMENT=$SENTRY_ENVIRONMENT
# Local args
# -----------

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -32,6 +32,7 @@ import { useSession } from "next-auth/react";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { signOut } from "next-auth/react"
import { env } from "@/env.mjs";
import posthog from "posthog-js";

interface SettingsDropdownProps {
menuButtonClassName?: string;
Expand DownExpand Up@@ -94,7 +95,9 @@ export const SettingsDropdown = ({
onClick={() => {
signOut({
redirectTo: "/login",
});
}).then(() => {
posthog.reset();
})
}}
>
<LogOut className="mr-2 h-4 w-4" />
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
'use client';

import { useEffect, useState } from "react";
import { useState } from "react";
import { CodeHostType } from "@/lib/utils";
import { getCodeHostIcon } from "@/lib/utils";
import {
Expand All@@ -15,8 +15,6 @@ import { OnboardingSteps } from "@/lib/constants";
import { BackButton } from "./onboardBackButton";
import { CodeHostIconButton } from "../../components/codeHostIconButton";
import useCaptureEvent from "@/hooks/useCaptureEvent";
import { useSession } from "next-auth/react";
import posthog from "posthog-js";
import SecurityCard from "@/app/components/securityCard";

interface ConnectCodeHostProps {
Expand All@@ -27,18 +25,6 @@ interface ConnectCodeHostProps {
export const ConnectCodeHost = ({ nextStep, securityCardEnabled }: ConnectCodeHostProps) => {
const [selectedCodeHost, setSelectedCodeHost] = useState<CodeHostType | null>(null);
const router = useRouter();
const { data: session } = useSession();

// Note: this is currently the first client side page that gets loaded after a user registers. If this changes, we need to update this.
// @nocheckin
useEffect(() => {
if (session?.user) {
posthog.identify(session.user.id, {
email: session.user.email,
name: session.user.name,
});
}
}, [session?.user]);

const onCreated = useCallback(() => {
router.push(`?step=${nextStep}`);
Expand Down
4 changes: 4 additions & 0 deletions packages/web/src/app/components/logoutEscapeHatch.tsx
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
import { LogOutIcon } from "lucide-react";
import { signOut } from "@/auth";
import posthog from "posthog-js";

interface LogoutEscapeHatchProps {
className?: string;
}
Expand All@@ -14,6 +16,8 @@ export const LogoutEscapeHatch = ({
"use server";
await signOut({
redirectTo: "/login",
}).then(() => {
posthog.reset();
});
}}
>
Expand Down
9 changes: 9 additions & 0 deletions packages/web/src/app/login/components/loginForm.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,6 +13,12 @@ import { SourcebotLogo } from "@/app/components/sourcebotLogo";
import { TextSeparator } from "@/app/components/textSeparator";
import useCaptureEvent from "@/hooks/useCaptureEvent";
import DemoCard from "@/app/[domain]/onboard/components/demoCard";
import Link from "next/link";
import { env } from "@/env.mjs";

const TERMS_OF_SERVICE_URL = "https://sourcebot.dev/terms";
const PRIVACY_POLICY_URL = "https://sourcebot.dev/privacy";

interface LoginFormProps {
callbackUrl?: string;
error?: string;
Expand DownExpand Up@@ -98,6 +104,9 @@ export const LoginForm = ({ callbackUrl, error, enabledMethods }: LoginFormProps
]}
/>
</Card>
{env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT !== undefined && (
<p className="text-xs text-muted-foreground mt-8">By signing in, you agree to the <Link className="underline" href={TERMS_OF_SERVICE_URL} target="_blank">Terms of Service</Link> and <Link className="underline" href={PRIVACY_POLICY_URL} target="_blank">Privacy Policy</Link>.</p>
)}
</div>
)
}
Expand Down
57 changes: 41 additions & 16 deletions packages/web/src/app/posthogProvider.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,21 +5,25 @@ import { PostHogProvider as PHProvider } from 'posthog-js/react'
import { usePathname, useSearchParams } from "next/navigation"
import { Suspense, useEffect } from "react"
import { env } from '@/env.mjs'
import { useSession } from 'next-auth/react'
import { captureEvent } from '@/hooks/useCaptureEvent'

// @see: https://posthog.com/docs/libraries/next-js#capturing-pageviews
function PostHogPageView() {
const pathname = usePathname()
const searchParams = useSearchParams()
const posthog = usePostHog()

// Track pageviews
useEffect(() => {
if (pathname && posthog) {
let url = window.origin + pathname
if (searchParams.toString()) {
url = url + `?${searchParams.toString()}`
}

posthog.capture('$pageview', { '$current_url': url })
captureEvent('$pageview', {
$current_url: url,
});
}
}, [pathname, searchParams, posthog])

Expand All@@ -32,34 +36,55 @@ interface PostHogProviderProps {
}

export function PostHogProvider({ children, disabled }: PostHogProviderProps) {
const { data: session } = useSession();

useEffect(() => {
if (!disabled && env.NEXT_PUBLIC_POSTHOG_PAPIK) {
console.debug(`PostHog telemetry enabled. Cloud environment: ${env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT}`);
posthog.init(env.NEXT_PUBLIC_POSTHOG_PAPIK, {
// @see next.config.mjs for path rewrites to the "/ingest" route.
api_host: "/ingest",
person_profiles: 'identified_only',
capture_pageview: false,
autocapture: false,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
sanitize_properties: (properties: Record<string, any>, _event: string) => {
// https://posthog.com/docs/libraries/js#config
if (properties['$current_url']) {
properties['$current_url'] = null;
}
if (properties['$ip']) {
properties['$ip'] = null;
}

return properties;
}
// In self-hosted mode, we don't want to capture the following
// default properties.
// @see: https://posthog.com/docs/data/events#default-properties
property_denylist: env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT === undefined ? [
'$current_url',
'$pathname',
'$session_entry_url',
'$session_entry_host',
'$session_entry_pathname',
'$session_entry_referrer',
'$session_entry_referring_domain',
'$referrer',
'$referring_domain',
'$ip',
] : []
});
} else {
console.log("PostHog telemetry disabled");
console.debug("PostHog telemetry disabled");
}
}, [disabled]);

useEffect(() => {
if (!session) {
return;
}

// Only identify the user if we are running in a cloud environment.
if (env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT !== undefined) {
posthog.identify(session.user.id, {
email: session.user.email,
name: session.user.name,
});
}
}, [disabled])
}, [session]);

return (
<PHProvider client={posthog}>
{/* @see: https://github.com/vercel/next.js/issues/51581 */}
<Suspense fallback={null}>
<PostHogPageView />
</Suspense>
Expand Down
9 changes: 7 additions & 2 deletions packages/web/src/env.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,21 +50,26 @@ export const env = createEnv({
// Misc UI flags
SECURITY_CARD_ENABLED: booleanSchema.default('false'),
},
// @NOTE: Make sure you destructure all client variables in the
// `experimental__runtimeEnv` block below.
// @NOTE: Please make sure of the following:
// - Make sure you destructure all client variables in
// the `experimental__runtimeEnv` block below.
// - Update the Dockerfile to pass these variables as build-args.
client: {
// PostHog
NEXT_PUBLIC_POSTHOG_PAPIK: z.string().optional(),

// Misc
NEXT_PUBLIC_SOURCEBOT_VERSION: z.string().default('unknown'),
NEXT_PUBLIC_POLLING_INTERVAL_MS: numberSchema.default(5000),

NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT: z.enum(["dev", "staging", "prod"]).optional(),
},
// For Next.js >= 13.4.4, you only need to destructure client variables:
experimental__runtimeEnv: {
NEXT_PUBLIC_POSTHOG_PAPIK: process.env.NEXT_PUBLIC_POSTHOG_PAPIK,
NEXT_PUBLIC_SOURCEBOT_VERSION: process.env.NEXT_PUBLIC_SOURCEBOT_VERSION,
NEXT_PUBLIC_POLLING_INTERVAL_MS: process.env.NEXT_PUBLIC_POLLING_INTERVAL_MS,
NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT: process.env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT,
},
skipValidation: process.env.SKIP_ENV_VALIDATION === "1",
emptyStringAsUndefined: true,
Expand Down
4 changes: 4 additions & 0 deletions packages/web/src/lib/posthogEvents.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -244,6 +244,10 @@ export type PosthogEventMap = {
wa_demo_card_click: {},
wa_demo_try_card_pressed: {},
wa_share_link_created: {},
//////////////////////////////////////////////////////////////////
$pageview: {
$current_url: string,
},
}

export type PosthogEvent = keyof PosthogEventMap;
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all \u003cpre\u003e\u003ccode\u003e blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks"); } } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); } })(); (function(){ try { var __m = "github.com"; var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .env.development
Original file line numberDiff line numberDiff line change
Expand Up@@ -34,7 +34,7 @@ AUTH_URL="http://localhost:3000"
# Sentry
# SENTRY_BACKEND_DSN=""
# NEXT_PUBLIC_SENTRY_WEBAPP_DSN=""
SENTRY_ENVIRONMENT="dev"
# SENTRY_ENVIRONMENT="dev"
# NEXT_PUBLIC_SENTRY_ENVIRONMENT="dev"
# SENTRY_AUTH_TOKEN=

Expand DownExpand Up@@ -76,4 +76,6 @@ SOURCEBOT_TELEMETRY_DISABLED=true # Disables telemetry collection

# CONFIG_MAX_REPOS_NO_TOKEN=
# NODE_ENV=
# SOURCEBOT_TENANCY_MODE=single
# SOURCEBOT_TENANCY_MODE=single

# NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT=
9 changes: 7 additions & 2 deletions Dockerfile
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,6 +9,7 @@ ARG SOURCEBOT_VERSION
# @see: https://posthog.com/tutorials/api-capture-events#authenticating-with-the-project-api-key
ARG POSTHOG_PAPIK
ARG SENTRY_ENVIRONMENT
ARG SOURCEBOT_CLOUD_ENVIRONMENT

FROM node:20-alpine3.19 AS node-alpine
FROM golang:1.23.4-alpine3.19 AS go-alpine
Expand DownExpand Up@@ -52,9 +53,11 @@ ARG POSTHOG_PAPIK
ENV NEXT_PUBLIC_POSTHOG_PAPIK=$POSTHOG_PAPIK
ARG SENTRY_ENVIRONMENT
ENV NEXT_PUBLIC_SENTRY_ENVIRONMENT=$SENTRY_ENVIRONMENT
ARG SOURCEBOT_CLOUD_ENVIRONMENT
ENV NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT=$SOURCEBOT_CLOUD_ENVIRONMENT
# Local args
ARG SENTRY_WEBAPP_DSN
ENV NEXT_PUBLIC_SENTRY_WEBAPP_DSN=$SENTRY_WEBAPP_DSN
ARG NEXT_PUBLIC_SENTRY_WEBAPP_DSN
ENV NEXT_PUBLIC_SENTRY_WEBAPP_DSN=$NEXT_PUBLIC_SENTRY_WEBAPP_DSN
# -----------

RUN apk add --no-cache libc6-compat
Expand DownExpand Up@@ -104,6 +107,8 @@ ARG SOURCEBOT_VERSION
ENV SOURCEBOT_VERSION=$SOURCEBOT_VERSION
ARG POSTHOG_PAPIK
ENV POSTHOG_PAPIK=$POSTHOG_PAPIK
ARG SENTRY_ENVIRONMENT
ENV SENTRY_ENVIRONMENT=$SENTRY_ENVIRONMENT
# Local args
# -----------

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -32,6 +32,7 @@ import { useSession } from "next-auth/react";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { signOut } from "next-auth/react"
import { env } from "@/env.mjs";
import posthog from "posthog-js";

interface SettingsDropdownProps {
menuButtonClassName?: string;
Expand DownExpand Up@@ -94,7 +95,9 @@ export const SettingsDropdown = ({
onClick={() => {
signOut({
redirectTo: "/login",
});
}).then(() => {
posthog.reset();
})
}}
>
<LogOut className="mr-2 h-4 w-4" />
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
'use client';

import { useEffect, useState } from "react";
import { useState } from "react";
import { CodeHostType } from "@/lib/utils";
import { getCodeHostIcon } from "@/lib/utils";
import {
Expand All@@ -15,8 +15,6 @@ import { OnboardingSteps } from "@/lib/constants";
import { BackButton } from "./onboardBackButton";
import { CodeHostIconButton } from "../../components/codeHostIconButton";
import useCaptureEvent from "@/hooks/useCaptureEvent";
import { useSession } from "next-auth/react";
import posthog from "posthog-js";
import SecurityCard from "@/app/components/securityCard";

interface ConnectCodeHostProps {
Expand All@@ -27,18 +25,6 @@ interface ConnectCodeHostProps {
export const ConnectCodeHost = ({ nextStep, securityCardEnabled }: ConnectCodeHostProps) => {
const [selectedCodeHost, setSelectedCodeHost] = useState<CodeHostType | null>(null);
const router = useRouter();
const { data: session } = useSession();

// Note: this is currently the first client side page that gets loaded after a user registers. If this changes, we need to update this.
// @nocheckin
useEffect(() => {
if (session?.user) {
posthog.identify(session.user.id, {
email: session.user.email,
name: session.user.name,
});
}
}, [session?.user]);

const onCreated = useCallback(() => {
router.push(`?step=${nextStep}`);
Expand Down
4 changes: 4 additions & 0 deletions packages/web/src/app/components/logoutEscapeHatch.tsx
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
import { LogOutIcon } from "lucide-react";
import { signOut } from "@/auth";
import posthog from "posthog-js";

interface LogoutEscapeHatchProps {
className?: string;
}
Expand All@@ -14,6 +16,8 @@ export const LogoutEscapeHatch = ({
"use server";
await signOut({
redirectTo: "/login",
}).then(() => {
posthog.reset();
});
}}
>
Expand Down
9 changes: 9 additions & 0 deletions packages/web/src/app/login/components/loginForm.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,6 +13,12 @@ import { SourcebotLogo } from "@/app/components/sourcebotLogo";
import { TextSeparator } from "@/app/components/textSeparator";
import useCaptureEvent from "@/hooks/useCaptureEvent";
import DemoCard from "@/app/[domain]/onboard/components/demoCard";
import Link from "next/link";
import { env } from "@/env.mjs";

const TERMS_OF_SERVICE_URL = "https://sourcebot.dev/terms";
const PRIVACY_POLICY_URL = "https://sourcebot.dev/privacy";

interface LoginFormProps {
callbackUrl?: string;
error?: string;
Expand DownExpand Up@@ -98,6 +104,9 @@ export const LoginForm = ({ callbackUrl, error, enabledMethods }: LoginFormProps
]}
/>
</Card>
{env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT !== undefined && (
<p className="text-xs text-muted-foreground mt-8">By signing in, you agree to the <Link className="underline" href={TERMS_OF_SERVICE_URL} target="_blank">Terms of Service</Link> and <Link className="underline" href={PRIVACY_POLICY_URL} target="_blank">Privacy Policy</Link>.</p>
)}
</div>
)
}
Expand Down
57 changes: 41 additions & 16 deletions packages/web/src/app/posthogProvider.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,21 +5,25 @@ import { PostHogProvider as PHProvider } from 'posthog-js/react'
import { usePathname, useSearchParams } from "next/navigation"
import { Suspense, useEffect } from "react"
import { env } from '@/env.mjs'
import { useSession } from 'next-auth/react'
import { captureEvent } from '@/hooks/useCaptureEvent'

// @see: https://posthog.com/docs/libraries/next-js#capturing-pageviews
function PostHogPageView() {
const pathname = usePathname()
const searchParams = useSearchParams()
const posthog = usePostHog()

// Track pageviews
useEffect(() => {
if (pathname && posthog) {
let url = window.origin + pathname
if (searchParams.toString()) {
url = url + `?${searchParams.toString()}`
}

posthog.capture('$pageview', { '$current_url': url })
captureEvent('$pageview', {
$current_url: url,
});
}
}, [pathname, searchParams, posthog])

Expand All@@ -32,34 +36,55 @@ interface PostHogProviderProps {
}

export function PostHogProvider({ children, disabled }: PostHogProviderProps) {
const { data: session } = useSession();

useEffect(() => {
if (!disabled && env.NEXT_PUBLIC_POSTHOG_PAPIK) {
console.debug(`PostHog telemetry enabled. Cloud environment: ${env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT}`);
posthog.init(env.NEXT_PUBLIC_POSTHOG_PAPIK, {
// @see next.config.mjs for path rewrites to the "/ingest" route.
api_host: "/ingest",
person_profiles: 'identified_only',
capture_pageview: false,
autocapture: false,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
sanitize_properties: (properties: Record<string, any>, _event: string) => {
// https://posthog.com/docs/libraries/js#config
if (properties['$current_url']) {
properties['$current_url'] = null;
}
if (properties['$ip']) {
properties['$ip'] = null;
}

return properties;
}
// In self-hosted mode, we don't want to capture the following
// default properties.
// @see: https://posthog.com/docs/data/events#default-properties
property_denylist: env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT === undefined ? [
'$current_url',
'$pathname',
'$session_entry_url',
'$session_entry_host',
'$session_entry_pathname',
'$session_entry_referrer',
'$session_entry_referring_domain',
'$referrer',
'$referring_domain',
'$ip',
] : []
});
} else {
console.log("PostHog telemetry disabled");
console.debug("PostHog telemetry disabled");
}
}, [disabled]);

useEffect(() => {
if (!session) {
return;
}

// Only identify the user if we are running in a cloud environment.
if (env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT !== undefined) {
posthog.identify(session.user.id, {
email: session.user.email,
name: session.user.name,
});
}
}, [disabled])
}, [session]);

return (
<PHProvider client={posthog}>
{/* @see: https://github.com/vercel/next.js/issues/51581 */}
<Suspense fallback={null}>
<PostHogPageView />
</Suspense>
Expand Down
9 changes: 7 additions & 2 deletions packages/web/src/env.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,21 +50,26 @@ export const env = createEnv({
// Misc UI flags
SECURITY_CARD_ENABLED: booleanSchema.default('false'),
},
// @NOTE: Make sure you destructure all client variables in the
// `experimental__runtimeEnv` block below.
// @NOTE: Please make sure of the following:
// - Make sure you destructure all client variables in
// the `experimental__runtimeEnv` block below.
// - Update the Dockerfile to pass these variables as build-args.
client: {
// PostHog
NEXT_PUBLIC_POSTHOG_PAPIK: z.string().optional(),

// Misc
NEXT_PUBLIC_SOURCEBOT_VERSION: z.string().default('unknown'),
NEXT_PUBLIC_POLLING_INTERVAL_MS: numberSchema.default(5000),

NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT: z.enum(["dev", "staging", "prod"]).optional(),
},
// For Next.js >= 13.4.4, you only need to destructure client variables:
experimental__runtimeEnv: {
NEXT_PUBLIC_POSTHOG_PAPIK: process.env.NEXT_PUBLIC_POSTHOG_PAPIK,
NEXT_PUBLIC_SOURCEBOT_VERSION: process.env.NEXT_PUBLIC_SOURCEBOT_VERSION,
NEXT_PUBLIC_POLLING_INTERVAL_MS: process.env.NEXT_PUBLIC_POLLING_INTERVAL_MS,
NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT: process.env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT,
},
skipValidation: process.env.SKIP_ENV_VALIDATION === "1",
emptyStringAsUndefined: true,
Expand Down
4 changes: 4 additions & 0 deletions packages/web/src/lib/posthogEvents.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -244,6 +244,10 @@ export type PosthogEventMap = {
wa_demo_card_click: {},
wa_demo_try_card_pressed: {},
wa_share_link_created: {},
//////////////////////////////////////////////////////////////////
$pageview: {
$current_url: string,
},
}

export type PosthogEvent = keyof PosthogEventMap;
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .env.development
Original file line numberDiff line numberDiff line change
Expand Up@@ -34,7 +34,7 @@ AUTH_URL="http://localhost:3000"
# Sentry
# SENTRY_BACKEND_DSN=""
# NEXT_PUBLIC_SENTRY_WEBAPP_DSN=""
SENTRY_ENVIRONMENT="dev"
# SENTRY_ENVIRONMENT="dev"
# NEXT_PUBLIC_SENTRY_ENVIRONMENT="dev"
# SENTRY_AUTH_TOKEN=

Expand DownExpand Up@@ -76,4 +76,6 @@ SOURCEBOT_TELEMETRY_DISABLED=true # Disables telemetry collection

# CONFIG_MAX_REPOS_NO_TOKEN=
# NODE_ENV=
# SOURCEBOT_TENANCY_MODE=single
# SOURCEBOT_TENANCY_MODE=single

# NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT=
9 changes: 7 additions & 2 deletions Dockerfile
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,6 +9,7 @@ ARG SOURCEBOT_VERSION
# @see: https://posthog.com/tutorials/api-capture-events#authenticating-with-the-project-api-key
ARG POSTHOG_PAPIK
ARG SENTRY_ENVIRONMENT
ARG SOURCEBOT_CLOUD_ENVIRONMENT

FROM node:20-alpine3.19 AS node-alpine
FROM golang:1.23.4-alpine3.19 AS go-alpine
Expand DownExpand Up@@ -52,9 +53,11 @@ ARG POSTHOG_PAPIK
ENV NEXT_PUBLIC_POSTHOG_PAPIK=$POSTHOG_PAPIK
ARG SENTRY_ENVIRONMENT
ENV NEXT_PUBLIC_SENTRY_ENVIRONMENT=$SENTRY_ENVIRONMENT
ARG SOURCEBOT_CLOUD_ENVIRONMENT
ENV NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT=$SOURCEBOT_CLOUD_ENVIRONMENT
# Local args
ARG SENTRY_WEBAPP_DSN
ENV NEXT_PUBLIC_SENTRY_WEBAPP_DSN=$SENTRY_WEBAPP_DSN
ARG NEXT_PUBLIC_SENTRY_WEBAPP_DSN
ENV NEXT_PUBLIC_SENTRY_WEBAPP_DSN=$NEXT_PUBLIC_SENTRY_WEBAPP_DSN
# -----------

RUN apk add --no-cache libc6-compat
Expand DownExpand Up@@ -104,6 +107,8 @@ ARG SOURCEBOT_VERSION
ENV SOURCEBOT_VERSION=$SOURCEBOT_VERSION
ARG POSTHOG_PAPIK
ENV POSTHOG_PAPIK=$POSTHOG_PAPIK
ARG SENTRY_ENVIRONMENT
ENV SENTRY_ENVIRONMENT=$SENTRY_ENVIRONMENT
# Local args
# -----------

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -32,6 +32,7 @@ import { useSession } from "next-auth/react";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { signOut } from "next-auth/react"
import { env } from "@/env.mjs";
import posthog from "posthog-js";

interface SettingsDropdownProps {
menuButtonClassName?: string;
Expand DownExpand Up@@ -94,7 +95,9 @@ export const SettingsDropdown = ({
onClick={() => {
signOut({
redirectTo: "/login",
});
}).then(() => {
posthog.reset();
})
}}
>
<LogOut className="mr-2 h-4 w-4" />
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
'use client';

import { useEffect, useState } from "react";
import { useState } from "react";
import { CodeHostType } from "@/lib/utils";
import { getCodeHostIcon } from "@/lib/utils";
import {
Expand All@@ -15,8 +15,6 @@ import { OnboardingSteps } from "@/lib/constants";
import { BackButton } from "./onboardBackButton";
import { CodeHostIconButton } from "../../components/codeHostIconButton";
import useCaptureEvent from "@/hooks/useCaptureEvent";
import { useSession } from "next-auth/react";
import posthog from "posthog-js";
import SecurityCard from "@/app/components/securityCard";

interface ConnectCodeHostProps {
Expand All@@ -27,18 +25,6 @@ interface ConnectCodeHostProps {
export const ConnectCodeHost = ({ nextStep, securityCardEnabled }: ConnectCodeHostProps) => {
const [selectedCodeHost, setSelectedCodeHost] = useState<CodeHostType | null>(null);
const router = useRouter();
const { data: session } = useSession();

// Note: this is currently the first client side page that gets loaded after a user registers. If this changes, we need to update this.
// @nocheckin
useEffect(() => {
if (session?.user) {
posthog.identify(session.user.id, {
email: session.user.email,
name: session.user.name,
});
}
}, [session?.user]);

const onCreated = useCallback(() => {
router.push(`?step=${nextStep}`);
Expand Down
4 changes: 4 additions & 0 deletions packages/web/src/app/components/logoutEscapeHatch.tsx
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
import { LogOutIcon } from "lucide-react";
import { signOut } from "@/auth";
import posthog from "posthog-js";

interface LogoutEscapeHatchProps {
className?: string;
}
Expand All@@ -14,6 +16,8 @@ export const LogoutEscapeHatch = ({
"use server";
await signOut({
redirectTo: "/login",
}).then(() => {
posthog.reset();
});
}}
>
Expand Down
9 changes: 9 additions & 0 deletions packages/web/src/app/login/components/loginForm.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,6 +13,12 @@ import { SourcebotLogo } from "@/app/components/sourcebotLogo";
import { TextSeparator } from "@/app/components/textSeparator";
import useCaptureEvent from "@/hooks/useCaptureEvent";
import DemoCard from "@/app/[domain]/onboard/components/demoCard";
import Link from "next/link";
import { env } from "@/env.mjs";

const TERMS_OF_SERVICE_URL = "https://sourcebot.dev/terms";
const PRIVACY_POLICY_URL = "https://sourcebot.dev/privacy";

interface LoginFormProps {
callbackUrl?: string;
error?: string;
Expand DownExpand Up@@ -98,6 +104,9 @@ export const LoginForm = ({ callbackUrl, error, enabledMethods }: LoginFormProps
]}
/>
</Card>
{env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT !== undefined && (
<p className="text-xs text-muted-foreground mt-8">By signing in, you agree to the <Link className="underline" href={TERMS_OF_SERVICE_URL} target="_blank">Terms of Service</Link> and <Link className="underline" href={PRIVACY_POLICY_URL} target="_blank">Privacy Policy</Link>.</p>
)}
</div>
)
}
Expand Down
57 changes: 41 additions & 16 deletions packages/web/src/app/posthogProvider.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,21 +5,25 @@ import { PostHogProvider as PHProvider } from 'posthog-js/react'
import { usePathname, useSearchParams } from "next/navigation"
import { Suspense, useEffect } from "react"
import { env } from '@/env.mjs'
import { useSession } from 'next-auth/react'
import { captureEvent } from '@/hooks/useCaptureEvent'

// @see: https://posthog.com/docs/libraries/next-js#capturing-pageviews
function PostHogPageView() {
const pathname = usePathname()
const searchParams = useSearchParams()
const posthog = usePostHog()

// Track pageviews
useEffect(() => {
if (pathname && posthog) {
let url = window.origin + pathname
if (searchParams.toString()) {
url = url + `?${searchParams.toString()}`
}

posthog.capture('$pageview', { '$current_url': url })
captureEvent('$pageview', {
$current_url: url,
});
}
}, [pathname, searchParams, posthog])

Expand All@@ -32,34 +36,55 @@ interface PostHogProviderProps {
}

export function PostHogProvider({ children, disabled }: PostHogProviderProps) {
const { data: session } = useSession();

useEffect(() => {
if (!disabled && env.NEXT_PUBLIC_POSTHOG_PAPIK) {
console.debug(`PostHog telemetry enabled. Cloud environment: ${env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT}`);
posthog.init(env.NEXT_PUBLIC_POSTHOG_PAPIK, {
// @see next.config.mjs for path rewrites to the "/ingest" route.
api_host: "/ingest",
person_profiles: 'identified_only',
capture_pageview: false,
autocapture: false,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
sanitize_properties: (properties: Record<string, any>, _event: string) => {
// https://posthog.com/docs/libraries/js#config
if (properties['$current_url']) {
properties['$current_url'] = null;
}
if (properties['$ip']) {
properties['$ip'] = null;
}

return properties;
}
// In self-hosted mode, we don't want to capture the following
// default properties.
// @see: https://posthog.com/docs/data/events#default-properties
property_denylist: env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT === undefined ? [
'$current_url',
'$pathname',
'$session_entry_url',
'$session_entry_host',
'$session_entry_pathname',
'$session_entry_referrer',
'$session_entry_referring_domain',
'$referrer',
'$referring_domain',
'$ip',
] : []
});
} else {
console.log("PostHog telemetry disabled");
console.debug("PostHog telemetry disabled");
}
}, [disabled]);

useEffect(() => {
if (!session) {
return;
}

// Only identify the user if we are running in a cloud environment.
if (env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT !== undefined) {
posthog.identify(session.user.id, {
email: session.user.email,
name: session.user.name,
});
}
}, [disabled])
}, [session]);

return (
<PHProvider client={posthog}>
{/* @see: https://github.com/vercel/next.js/issues/51581 */}
<Suspense fallback={null}>
<PostHogPageView />
</Suspense>
Expand Down
9 changes: 7 additions & 2 deletions packages/web/src/env.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,21 +50,26 @@ export const env = createEnv({
// Misc UI flags
SECURITY_CARD_ENABLED: booleanSchema.default('false'),
},
// @NOTE: Make sure you destructure all client variables in the
// `experimental__runtimeEnv` block below.
// @NOTE: Please make sure of the following:
// - Make sure you destructure all client variables in
// the `experimental__runtimeEnv` block below.
// - Update the Dockerfile to pass these variables as build-args.
client: {
// PostHog
NEXT_PUBLIC_POSTHOG_PAPIK: z.string().optional(),

// Misc
NEXT_PUBLIC_SOURCEBOT_VERSION: z.string().default('unknown'),
NEXT_PUBLIC_POLLING_INTERVAL_MS: numberSchema.default(5000),

NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT: z.enum(["dev", "staging", "prod"]).optional(),
},
// For Next.js >= 13.4.4, you only need to destructure client variables:
experimental__runtimeEnv: {
NEXT_PUBLIC_POSTHOG_PAPIK: process.env.NEXT_PUBLIC_POSTHOG_PAPIK,
NEXT_PUBLIC_SOURCEBOT_VERSION: process.env.NEXT_PUBLIC_SOURCEBOT_VERSION,
NEXT_PUBLIC_POLLING_INTERVAL_MS: process.env.NEXT_PUBLIC_POLLING_INTERVAL_MS,
NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT: process.env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT,
},
skipValidation: process.env.SKIP_ENV_VALIDATION === "1",
emptyStringAsUndefined: true,
Expand Down
4 changes: 4 additions & 0 deletions packages/web/src/lib/posthogEvents.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -244,6 +244,10 @@ export type PosthogEventMap = {
wa_demo_card_click: {},
wa_demo_try_card_pressed: {},
wa_share_link_created: {},
//////////////////////////////////////////////////////////////////
$pageview: {
$current_url: string,
},
}

export type PosthogEvent = keyof PosthogEventMap;
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length \u003e 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .env.development
Original file line numberDiff line numberDiff line change
Expand Up@@ -34,7 +34,7 @@ AUTH_URL="http://localhost:3000"
# Sentry
# SENTRY_BACKEND_DSN=""
# NEXT_PUBLIC_SENTRY_WEBAPP_DSN=""
SENTRY_ENVIRONMENT="dev"
# SENTRY_ENVIRONMENT="dev"
# NEXT_PUBLIC_SENTRY_ENVIRONMENT="dev"
# SENTRY_AUTH_TOKEN=

Expand DownExpand Up@@ -76,4 +76,6 @@ SOURCEBOT_TELEMETRY_DISABLED=true # Disables telemetry collection

# CONFIG_MAX_REPOS_NO_TOKEN=
# NODE_ENV=
# SOURCEBOT_TENANCY_MODE=single
# SOURCEBOT_TENANCY_MODE=single

# NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT=
9 changes: 7 additions & 2 deletions Dockerfile
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,6 +9,7 @@ ARG SOURCEBOT_VERSION
# @see: https://posthog.com/tutorials/api-capture-events#authenticating-with-the-project-api-key
ARG POSTHOG_PAPIK
ARG SENTRY_ENVIRONMENT
ARG SOURCEBOT_CLOUD_ENVIRONMENT

FROM node:20-alpine3.19 AS node-alpine
FROM golang:1.23.4-alpine3.19 AS go-alpine
Expand DownExpand Up@@ -52,9 +53,11 @@ ARG POSTHOG_PAPIK
ENV NEXT_PUBLIC_POSTHOG_PAPIK=$POSTHOG_PAPIK
ARG SENTRY_ENVIRONMENT
ENV NEXT_PUBLIC_SENTRY_ENVIRONMENT=$SENTRY_ENVIRONMENT
ARG SOURCEBOT_CLOUD_ENVIRONMENT
ENV NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT=$SOURCEBOT_CLOUD_ENVIRONMENT
# Local args
ARG SENTRY_WEBAPP_DSN
ENV NEXT_PUBLIC_SENTRY_WEBAPP_DSN=$SENTRY_WEBAPP_DSN
ARG NEXT_PUBLIC_SENTRY_WEBAPP_DSN
ENV NEXT_PUBLIC_SENTRY_WEBAPP_DSN=$NEXT_PUBLIC_SENTRY_WEBAPP_DSN
# -----------

RUN apk add --no-cache libc6-compat
Expand DownExpand Up@@ -104,6 +107,8 @@ ARG SOURCEBOT_VERSION
ENV SOURCEBOT_VERSION=$SOURCEBOT_VERSION
ARG POSTHOG_PAPIK
ENV POSTHOG_PAPIK=$POSTHOG_PAPIK
ARG SENTRY_ENVIRONMENT
ENV SENTRY_ENVIRONMENT=$SENTRY_ENVIRONMENT
# Local args
# -----------

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -32,6 +32,7 @@ import { useSession } from "next-auth/react";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { signOut } from "next-auth/react"
import { env } from "@/env.mjs";
import posthog from "posthog-js";

interface SettingsDropdownProps {
menuButtonClassName?: string;
Expand DownExpand Up@@ -94,7 +95,9 @@ export const SettingsDropdown = ({
onClick={() => {
signOut({
redirectTo: "/login",
});
}).then(() => {
posthog.reset();
})
}}
>
<LogOut className="mr-2 h-4 w-4" />
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
'use client';

import { useEffect, useState } from "react";
import { useState } from "react";
import { CodeHostType } from "@/lib/utils";
import { getCodeHostIcon } from "@/lib/utils";
import {
Expand All@@ -15,8 +15,6 @@ import { OnboardingSteps } from "@/lib/constants";
import { BackButton } from "./onboardBackButton";
import { CodeHostIconButton } from "../../components/codeHostIconButton";
import useCaptureEvent from "@/hooks/useCaptureEvent";
import { useSession } from "next-auth/react";
import posthog from "posthog-js";
import SecurityCard from "@/app/components/securityCard";

interface ConnectCodeHostProps {
Expand All@@ -27,18 +25,6 @@ interface ConnectCodeHostProps {
export const ConnectCodeHost = ({ nextStep, securityCardEnabled }: ConnectCodeHostProps) => {
const [selectedCodeHost, setSelectedCodeHost] = useState<CodeHostType | null>(null);
const router = useRouter();
const { data: session } = useSession();

// Note: this is currently the first client side page that gets loaded after a user registers. If this changes, we need to update this.
// @nocheckin
useEffect(() => {
if (session?.user) {
posthog.identify(session.user.id, {
email: session.user.email,
name: session.user.name,
});
}
}, [session?.user]);

const onCreated = useCallback(() => {
router.push(`?step=${nextStep}`);
Expand Down
4 changes: 4 additions & 0 deletions packages/web/src/app/components/logoutEscapeHatch.tsx
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
import { LogOutIcon } from "lucide-react";
import { signOut } from "@/auth";
import posthog from "posthog-js";

interface LogoutEscapeHatchProps {
className?: string;
}
Expand All@@ -14,6 +16,8 @@ export const LogoutEscapeHatch = ({
"use server";
await signOut({
redirectTo: "/login",
}).then(() => {
posthog.reset();
});
}}
>
Expand Down
9 changes: 9 additions & 0 deletions packages/web/src/app/login/components/loginForm.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,6 +13,12 @@ import { SourcebotLogo } from "@/app/components/sourcebotLogo";
import { TextSeparator } from "@/app/components/textSeparator";
import useCaptureEvent from "@/hooks/useCaptureEvent";
import DemoCard from "@/app/[domain]/onboard/components/demoCard";
import Link from "next/link";
import { env } from "@/env.mjs";

const TERMS_OF_SERVICE_URL = "https://sourcebot.dev/terms";
const PRIVACY_POLICY_URL = "https://sourcebot.dev/privacy";

interface LoginFormProps {
callbackUrl?: string;
error?: string;
Expand DownExpand Up@@ -98,6 +104,9 @@ export const LoginForm = ({ callbackUrl, error, enabledMethods }: LoginFormProps
]}
/>
</Card>
{env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT !== undefined && (
<p className="text-xs text-muted-foreground mt-8">By signing in, you agree to the <Link className="underline" href={TERMS_OF_SERVICE_URL} target="_blank">Terms of Service</Link> and <Link className="underline" href={PRIVACY_POLICY_URL} target="_blank">Privacy Policy</Link>.</p>
)}
</div>
)
}
Expand Down
57 changes: 41 additions & 16 deletions packages/web/src/app/posthogProvider.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,21 +5,25 @@ import { PostHogProvider as PHProvider } from 'posthog-js/react'
import { usePathname, useSearchParams } from "next/navigation"
import { Suspense, useEffect } from "react"
import { env } from '@/env.mjs'
import { useSession } from 'next-auth/react'
import { captureEvent } from '@/hooks/useCaptureEvent'

// @see: https://posthog.com/docs/libraries/next-js#capturing-pageviews
function PostHogPageView() {
const pathname = usePathname()
const searchParams = useSearchParams()
const posthog = usePostHog()

// Track pageviews
useEffect(() => {
if (pathname && posthog) {
let url = window.origin + pathname
if (searchParams.toString()) {
url = url + `?${searchParams.toString()}`
}

posthog.capture('$pageview', { '$current_url': url })
captureEvent('$pageview', {
$current_url: url,
});
}
}, [pathname, searchParams, posthog])

Expand All@@ -32,34 +36,55 @@ interface PostHogProviderProps {
}

export function PostHogProvider({ children, disabled }: PostHogProviderProps) {
const { data: session } = useSession();

useEffect(() => {
if (!disabled && env.NEXT_PUBLIC_POSTHOG_PAPIK) {
console.debug(`PostHog telemetry enabled. Cloud environment: ${env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT}`);
posthog.init(env.NEXT_PUBLIC_POSTHOG_PAPIK, {
// @see next.config.mjs for path rewrites to the "/ingest" route.
api_host: "/ingest",
person_profiles: 'identified_only',
capture_pageview: false,
autocapture: false,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
sanitize_properties: (properties: Record<string, any>, _event: string) => {
// https://posthog.com/docs/libraries/js#config
if (properties['$current_url']) {
properties['$current_url'] = null;
}
if (properties['$ip']) {
properties['$ip'] = null;
}

return properties;
}
// In self-hosted mode, we don't want to capture the following
// default properties.
// @see: https://posthog.com/docs/data/events#default-properties
property_denylist: env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT === undefined ? [
'$current_url',
'$pathname',
'$session_entry_url',
'$session_entry_host',
'$session_entry_pathname',
'$session_entry_referrer',
'$session_entry_referring_domain',
'$referrer',
'$referring_domain',
'$ip',
] : []
});
} else {
console.log("PostHog telemetry disabled");
console.debug("PostHog telemetry disabled");
}
}, [disabled]);

useEffect(() => {
if (!session) {
return;
}

// Only identify the user if we are running in a cloud environment.
if (env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT !== undefined) {
posthog.identify(session.user.id, {
email: session.user.email,
name: session.user.name,
});
}
}, [disabled])
}, [session]);

return (
<PHProvider client={posthog}>
{/* @see: https://github.com/vercel/next.js/issues/51581 */}
<Suspense fallback={null}>
<PostHogPageView />
</Suspense>
Expand Down
9 changes: 7 additions & 2 deletions packages/web/src/env.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,21 +50,26 @@ export const env = createEnv({
// Misc UI flags
SECURITY_CARD_ENABLED: booleanSchema.default('false'),
},
// @NOTE: Make sure you destructure all client variables in the
// `experimental__runtimeEnv` block below.
// @NOTE: Please make sure of the following:
// - Make sure you destructure all client variables in
// the `experimental__runtimeEnv` block below.
// - Update the Dockerfile to pass these variables as build-args.
client: {
// PostHog
NEXT_PUBLIC_POSTHOG_PAPIK: z.string().optional(),

// Misc
NEXT_PUBLIC_SOURCEBOT_VERSION: z.string().default('unknown'),
NEXT_PUBLIC_POLLING_INTERVAL_MS: numberSchema.default(5000),

NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT: z.enum(["dev", "staging", "prod"]).optional(),
},
// For Next.js >= 13.4.4, you only need to destructure client variables:
experimental__runtimeEnv: {
NEXT_PUBLIC_POSTHOG_PAPIK: process.env.NEXT_PUBLIC_POSTHOG_PAPIK,
NEXT_PUBLIC_SOURCEBOT_VERSION: process.env.NEXT_PUBLIC_SOURCEBOT_VERSION,
NEXT_PUBLIC_POLLING_INTERVAL_MS: process.env.NEXT_PUBLIC_POLLING_INTERVAL_MS,
NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT: process.env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT,
},
skipValidation: process.env.SKIP_ENV_VALIDATION === "1",
emptyStringAsUndefined: true,
Expand Down
4 changes: 4 additions & 0 deletions packages/web/src/lib/posthogEvents.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -244,6 +244,10 @@ export type PosthogEventMap = {
wa_demo_card_click: {},
wa_demo_try_card_pressed: {},
wa_share_link_created: {},
//////////////////////////////////////////////////////////////////
$pageview: {
$current_url: string,
},
}

export type PosthogEvent = keyof PosthogEventMap;
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .env.development
Original file line numberDiff line numberDiff line change
Expand Up@@ -34,7 +34,7 @@ AUTH_URL="http://localhost:3000"
# Sentry
# SENTRY_BACKEND_DSN=""
# NEXT_PUBLIC_SENTRY_WEBAPP_DSN=""
SENTRY_ENVIRONMENT="dev"
# SENTRY_ENVIRONMENT="dev"
# NEXT_PUBLIC_SENTRY_ENVIRONMENT="dev"
# SENTRY_AUTH_TOKEN=

Expand DownExpand Up@@ -76,4 +76,6 @@ SOURCEBOT_TELEMETRY_DISABLED=true # Disables telemetry collection

# CONFIG_MAX_REPOS_NO_TOKEN=
# NODE_ENV=
# SOURCEBOT_TENANCY_MODE=single
# SOURCEBOT_TENANCY_MODE=single

# NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT=
9 changes: 7 additions & 2 deletions Dockerfile
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,6 +9,7 @@ ARG SOURCEBOT_VERSION
# @see: https://posthog.com/tutorials/api-capture-events#authenticating-with-the-project-api-key
ARG POSTHOG_PAPIK
ARG SENTRY_ENVIRONMENT
ARG SOURCEBOT_CLOUD_ENVIRONMENT

FROM node:20-alpine3.19 AS node-alpine
FROM golang:1.23.4-alpine3.19 AS go-alpine
Expand DownExpand Up@@ -52,9 +53,11 @@ ARG POSTHOG_PAPIK
ENV NEXT_PUBLIC_POSTHOG_PAPIK=$POSTHOG_PAPIK
ARG SENTRY_ENVIRONMENT
ENV NEXT_PUBLIC_SENTRY_ENVIRONMENT=$SENTRY_ENVIRONMENT
ARG SOURCEBOT_CLOUD_ENVIRONMENT
ENV NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT=$SOURCEBOT_CLOUD_ENVIRONMENT
# Local args
ARG SENTRY_WEBAPP_DSN
ENV NEXT_PUBLIC_SENTRY_WEBAPP_DSN=$SENTRY_WEBAPP_DSN
ARG NEXT_PUBLIC_SENTRY_WEBAPP_DSN
ENV NEXT_PUBLIC_SENTRY_WEBAPP_DSN=$NEXT_PUBLIC_SENTRY_WEBAPP_DSN
# -----------

RUN apk add --no-cache libc6-compat
Expand DownExpand Up@@ -104,6 +107,8 @@ ARG SOURCEBOT_VERSION
ENV SOURCEBOT_VERSION=$SOURCEBOT_VERSION
ARG POSTHOG_PAPIK
ENV POSTHOG_PAPIK=$POSTHOG_PAPIK
ARG SENTRY_ENVIRONMENT
ENV SENTRY_ENVIRONMENT=$SENTRY_ENVIRONMENT
# Local args
# -----------

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -32,6 +32,7 @@ import { useSession } from "next-auth/react";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { signOut } from "next-auth/react"
import { env } from "@/env.mjs";
import posthog from "posthog-js";

interface SettingsDropdownProps {
menuButtonClassName?: string;
Expand DownExpand Up@@ -94,7 +95,9 @@ export const SettingsDropdown = ({
onClick={() => {
signOut({
redirectTo: "/login",
});
}).then(() => {
posthog.reset();
})
}}
>
<LogOut className="mr-2 h-4 w-4" />
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
'use client';

import { useEffect, useState } from "react";
import { useState } from "react";
import { CodeHostType } from "@/lib/utils";
import { getCodeHostIcon } from "@/lib/utils";
import {
Expand All@@ -15,8 +15,6 @@ import { OnboardingSteps } from "@/lib/constants";
import { BackButton } from "./onboardBackButton";
import { CodeHostIconButton } from "../../components/codeHostIconButton";
import useCaptureEvent from "@/hooks/useCaptureEvent";
import { useSession } from "next-auth/react";
import posthog from "posthog-js";
import SecurityCard from "@/app/components/securityCard";

interface ConnectCodeHostProps {
Expand All@@ -27,18 +25,6 @@ interface ConnectCodeHostProps {
export const ConnectCodeHost = ({ nextStep, securityCardEnabled }: ConnectCodeHostProps) => {
const [selectedCodeHost, setSelectedCodeHost] = useState<CodeHostType | null>(null);
const router = useRouter();
const { data: session } = useSession();

// Note: this is currently the first client side page that gets loaded after a user registers. If this changes, we need to update this.
// @nocheckin
useEffect(() => {
if (session?.user) {
posthog.identify(session.user.id, {
email: session.user.email,
name: session.user.name,
});
}
}, [session?.user]);

const onCreated = useCallback(() => {
router.push(`?step=${nextStep}`);
Expand Down
4 changes: 4 additions & 0 deletions packages/web/src/app/components/logoutEscapeHatch.tsx
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
import { LogOutIcon } from "lucide-react";
import { signOut } from "@/auth";
import posthog from "posthog-js";

interface LogoutEscapeHatchProps {
className?: string;
}
Expand All@@ -14,6 +16,8 @@ export const LogoutEscapeHatch = ({
"use server";
await signOut({
redirectTo: "/login",
}).then(() => {
posthog.reset();
});
}}
>
Expand Down
9 changes: 9 additions & 0 deletions packages/web/src/app/login/components/loginForm.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,6 +13,12 @@ import { SourcebotLogo } from "@/app/components/sourcebotLogo";
import { TextSeparator } from "@/app/components/textSeparator";
import useCaptureEvent from "@/hooks/useCaptureEvent";
import DemoCard from "@/app/[domain]/onboard/components/demoCard";
import Link from "next/link";
import { env } from "@/env.mjs";

const TERMS_OF_SERVICE_URL = "https://sourcebot.dev/terms";
const PRIVACY_POLICY_URL = "https://sourcebot.dev/privacy";

interface LoginFormProps {
callbackUrl?: string;
error?: string;
Expand DownExpand Up@@ -98,6 +104,9 @@ export const LoginForm = ({ callbackUrl, error, enabledMethods }: LoginFormProps
]}
/>
</Card>
{env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT !== undefined && (
<p className="text-xs text-muted-foreground mt-8">By signing in, you agree to the <Link className="underline" href={TERMS_OF_SERVICE_URL} target="_blank">Terms of Service</Link> and <Link className="underline" href={PRIVACY_POLICY_URL} target="_blank">Privacy Policy</Link>.</p>
)}
</div>
)
}
Expand Down
57 changes: 41 additions & 16 deletions packages/web/src/app/posthogProvider.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,21 +5,25 @@ import { PostHogProvider as PHProvider } from 'posthog-js/react'
import { usePathname, useSearchParams } from "next/navigation"
import { Suspense, useEffect } from "react"
import { env } from '@/env.mjs'
import { useSession } from 'next-auth/react'
import { captureEvent } from '@/hooks/useCaptureEvent'

// @see: https://posthog.com/docs/libraries/next-js#capturing-pageviews
function PostHogPageView() {
const pathname = usePathname()
const searchParams = useSearchParams()
const posthog = usePostHog()

// Track pageviews
useEffect(() => {
if (pathname && posthog) {
let url = window.origin + pathname
if (searchParams.toString()) {
url = url + `?${searchParams.toString()}`
}

posthog.capture('$pageview', { '$current_url': url })
captureEvent('$pageview', {
$current_url: url,
});
}
}, [pathname, searchParams, posthog])

Expand All@@ -32,34 +36,55 @@ interface PostHogProviderProps {
}

export function PostHogProvider({ children, disabled }: PostHogProviderProps) {
const { data: session } = useSession();

useEffect(() => {
if (!disabled && env.NEXT_PUBLIC_POSTHOG_PAPIK) {
console.debug(`PostHog telemetry enabled. Cloud environment: ${env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT}`);
posthog.init(env.NEXT_PUBLIC_POSTHOG_PAPIK, {
// @see next.config.mjs for path rewrites to the "/ingest" route.
api_host: "/ingest",
person_profiles: 'identified_only',
capture_pageview: false,
autocapture: false,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
sanitize_properties: (properties: Record<string, any>, _event: string) => {
// https://posthog.com/docs/libraries/js#config
if (properties['$current_url']) {
properties['$current_url'] = null;
}
if (properties['$ip']) {
properties['$ip'] = null;
}

return properties;
}
// In self-hosted mode, we don't want to capture the following
// default properties.
// @see: https://posthog.com/docs/data/events#default-properties
property_denylist: env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT === undefined ? [
'$current_url',
'$pathname',
'$session_entry_url',
'$session_entry_host',
'$session_entry_pathname',
'$session_entry_referrer',
'$session_entry_referring_domain',
'$referrer',
'$referring_domain',
'$ip',
] : []
});
} else {
console.log("PostHog telemetry disabled");
console.debug("PostHog telemetry disabled");
}
}, [disabled]);

useEffect(() => {
if (!session) {
return;
}

// Only identify the user if we are running in a cloud environment.
if (env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT !== undefined) {
posthog.identify(session.user.id, {
email: session.user.email,
name: session.user.name,
});
}
}, [disabled])
}, [session]);

return (
<PHProvider client={posthog}>
{/* @see: https://github.com/vercel/next.js/issues/51581 */}
<Suspense fallback={null}>
<PostHogPageView />
</Suspense>
Expand Down
9 changes: 7 additions & 2 deletions packages/web/src/env.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,21 +50,26 @@ export const env = createEnv({
// Misc UI flags
SECURITY_CARD_ENABLED: booleanSchema.default('false'),
},
// @NOTE: Make sure you destructure all client variables in the
// `experimental__runtimeEnv` block below.
// @NOTE: Please make sure of the following:
// - Make sure you destructure all client variables in
// the `experimental__runtimeEnv` block below.
// - Update the Dockerfile to pass these variables as build-args.
client: {
// PostHog
NEXT_PUBLIC_POSTHOG_PAPIK: z.string().optional(),

// Misc
NEXT_PUBLIC_SOURCEBOT_VERSION: z.string().default('unknown'),
NEXT_PUBLIC_POLLING_INTERVAL_MS: numberSchema.default(5000),

NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT: z.enum(["dev", "staging", "prod"]).optional(),
},
// For Next.js >= 13.4.4, you only need to destructure client variables:
experimental__runtimeEnv: {
NEXT_PUBLIC_POSTHOG_PAPIK: process.env.NEXT_PUBLIC_POSTHOG_PAPIK,
NEXT_PUBLIC_SOURCEBOT_VERSION: process.env.NEXT_PUBLIC_SOURCEBOT_VERSION,
NEXT_PUBLIC_POLLING_INTERVAL_MS: process.env.NEXT_PUBLIC_POLLING_INTERVAL_MS,
NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT: process.env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT,
},
skipValidation: process.env.SKIP_ENV_VALIDATION === "1",
emptyStringAsUndefined: true,
Expand Down
4 changes: 4 additions & 0 deletions packages/web/src/lib/posthogEvents.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -244,6 +244,10 @@ export type PosthogEventMap = {
wa_demo_card_click: {},
wa_demo_try_card_pressed: {},
wa_share_link_created: {},
//////////////////////////////////////////////////////////////////
$pageview: {
$current_url: string,
},
}

export type PosthogEvent = keyof PosthogEventMap;
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .env.development
Original file line numberDiff line numberDiff line change
Expand Up@@ -34,7 +34,7 @@ AUTH_URL="http://localhost:3000"
# Sentry
# SENTRY_BACKEND_DSN=""
# NEXT_PUBLIC_SENTRY_WEBAPP_DSN=""
SENTRY_ENVIRONMENT="dev"
# SENTRY_ENVIRONMENT="dev"
# NEXT_PUBLIC_SENTRY_ENVIRONMENT="dev"
# SENTRY_AUTH_TOKEN=

Expand DownExpand Up@@ -76,4 +76,6 @@ SOURCEBOT_TELEMETRY_DISABLED=true # Disables telemetry collection

# CONFIG_MAX_REPOS_NO_TOKEN=
# NODE_ENV=
# SOURCEBOT_TENANCY_MODE=single
# SOURCEBOT_TENANCY_MODE=single

# NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT=
9 changes: 7 additions & 2 deletions Dockerfile
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,6 +9,7 @@ ARG SOURCEBOT_VERSION
# @see: https://posthog.com/tutorials/api-capture-events#authenticating-with-the-project-api-key
ARG POSTHOG_PAPIK
ARG SENTRY_ENVIRONMENT
ARG SOURCEBOT_CLOUD_ENVIRONMENT

FROM node:20-alpine3.19 AS node-alpine
FROM golang:1.23.4-alpine3.19 AS go-alpine
Expand DownExpand Up@@ -52,9 +53,11 @@ ARG POSTHOG_PAPIK
ENV NEXT_PUBLIC_POSTHOG_PAPIK=$POSTHOG_PAPIK
ARG SENTRY_ENVIRONMENT
ENV NEXT_PUBLIC_SENTRY_ENVIRONMENT=$SENTRY_ENVIRONMENT
ARG SOURCEBOT_CLOUD_ENVIRONMENT
ENV NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT=$SOURCEBOT_CLOUD_ENVIRONMENT
# Local args
ARG SENTRY_WEBAPP_DSN
ENV NEXT_PUBLIC_SENTRY_WEBAPP_DSN=$SENTRY_WEBAPP_DSN
ARG NEXT_PUBLIC_SENTRY_WEBAPP_DSN
ENV NEXT_PUBLIC_SENTRY_WEBAPP_DSN=$NEXT_PUBLIC_SENTRY_WEBAPP_DSN
# -----------

RUN apk add --no-cache libc6-compat
Expand DownExpand Up@@ -104,6 +107,8 @@ ARG SOURCEBOT_VERSION
ENV SOURCEBOT_VERSION=$SOURCEBOT_VERSION
ARG POSTHOG_PAPIK
ENV POSTHOG_PAPIK=$POSTHOG_PAPIK
ARG SENTRY_ENVIRONMENT
ENV SENTRY_ENVIRONMENT=$SENTRY_ENVIRONMENT
# Local args
# -----------

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -32,6 +32,7 @@ import { useSession } from "next-auth/react";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { signOut } from "next-auth/react"
import { env } from "@/env.mjs";
import posthog from "posthog-js";

interface SettingsDropdownProps {
menuButtonClassName?: string;
Expand DownExpand Up@@ -94,7 +95,9 @@ export const SettingsDropdown = ({
onClick={() => {
signOut({
redirectTo: "/login",
});
}).then(() => {
posthog.reset();
})
}}
>
<LogOut className="mr-2 h-4 w-4" />
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
'use client';

import { useEffect, useState } from "react";
import { useState } from "react";
import { CodeHostType } from "@/lib/utils";
import { getCodeHostIcon } from "@/lib/utils";
import {
Expand All@@ -15,8 +15,6 @@ import { OnboardingSteps } from "@/lib/constants";
import { BackButton } from "./onboardBackButton";
import { CodeHostIconButton } from "../../components/codeHostIconButton";
import useCaptureEvent from "@/hooks/useCaptureEvent";
import { useSession } from "next-auth/react";
import posthog from "posthog-js";
import SecurityCard from "@/app/components/securityCard";

interface ConnectCodeHostProps {
Expand All@@ -27,18 +25,6 @@ interface ConnectCodeHostProps {
export const ConnectCodeHost = ({ nextStep, securityCardEnabled }: ConnectCodeHostProps) => {
const [selectedCodeHost, setSelectedCodeHost] = useState<CodeHostType | null>(null);
const router = useRouter();
const { data: session } = useSession();

// Note: this is currently the first client side page that gets loaded after a user registers. If this changes, we need to update this.
// @nocheckin
useEffect(() => {
if (session?.user) {
posthog.identify(session.user.id, {
email: session.user.email,
name: session.user.name,
});
}
}, [session?.user]);

const onCreated = useCallback(() => {
router.push(`?step=${nextStep}`);
Expand Down
4 changes: 4 additions & 0 deletions packages/web/src/app/components/logoutEscapeHatch.tsx
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
import { LogOutIcon } from "lucide-react";
import { signOut } from "@/auth";
import posthog from "posthog-js";

interface LogoutEscapeHatchProps {
className?: string;
}
Expand All@@ -14,6 +16,8 @@ export const LogoutEscapeHatch = ({
"use server";
await signOut({
redirectTo: "/login",
}).then(() => {
posthog.reset();
});
}}
>
Expand Down
9 changes: 9 additions & 0 deletions packages/web/src/app/login/components/loginForm.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,6 +13,12 @@ import { SourcebotLogo } from "@/app/components/sourcebotLogo";
import { TextSeparator } from "@/app/components/textSeparator";
import useCaptureEvent from "@/hooks/useCaptureEvent";
import DemoCard from "@/app/[domain]/onboard/components/demoCard";
import Link from "next/link";
import { env } from "@/env.mjs";

const TERMS_OF_SERVICE_URL = "https://sourcebot.dev/terms";
const PRIVACY_POLICY_URL = "https://sourcebot.dev/privacy";

interface LoginFormProps {
callbackUrl?: string;
error?: string;
Expand DownExpand Up@@ -98,6 +104,9 @@ export const LoginForm = ({ callbackUrl, error, enabledMethods }: LoginFormProps
]}
/>
</Card>
{env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT !== undefined && (
<p className="text-xs text-muted-foreground mt-8">By signing in, you agree to the <Link className="underline" href={TERMS_OF_SERVICE_URL} target="_blank">Terms of Service</Link> and <Link className="underline" href={PRIVACY_POLICY_URL} target="_blank">Privacy Policy</Link>.</p>
)}
</div>
)
}
Expand Down
57 changes: 41 additions & 16 deletions packages/web/src/app/posthogProvider.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,21 +5,25 @@ import { PostHogProvider as PHProvider } from 'posthog-js/react'
import { usePathname, useSearchParams } from "next/navigation"
import { Suspense, useEffect } from "react"
import { env } from '@/env.mjs'
import { useSession } from 'next-auth/react'
import { captureEvent } from '@/hooks/useCaptureEvent'

// @see: https://posthog.com/docs/libraries/next-js#capturing-pageviews
function PostHogPageView() {
const pathname = usePathname()
const searchParams = useSearchParams()
const posthog = usePostHog()

// Track pageviews
useEffect(() => {
if (pathname && posthog) {
let url = window.origin + pathname
if (searchParams.toString()) {
url = url + `?${searchParams.toString()}`
}

posthog.capture('$pageview', { '$current_url': url })
captureEvent('$pageview', {
$current_url: url,
});
}
}, [pathname, searchParams, posthog])

Expand All@@ -32,34 +36,55 @@ interface PostHogProviderProps {
}

export function PostHogProvider({ children, disabled }: PostHogProviderProps) {
const { data: session } = useSession();

useEffect(() => {
if (!disabled && env.NEXT_PUBLIC_POSTHOG_PAPIK) {
console.debug(`PostHog telemetry enabled. Cloud environment: ${env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT}`);
posthog.init(env.NEXT_PUBLIC_POSTHOG_PAPIK, {
// @see next.config.mjs for path rewrites to the "/ingest" route.
api_host: "/ingest",
person_profiles: 'identified_only',
capture_pageview: false,
autocapture: false,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
sanitize_properties: (properties: Record<string, any>, _event: string) => {
// https://posthog.com/docs/libraries/js#config
if (properties['$current_url']) {
properties['$current_url'] = null;
}
if (properties['$ip']) {
properties['$ip'] = null;
}

return properties;
}
// In self-hosted mode, we don't want to capture the following
// default properties.
// @see: https://posthog.com/docs/data/events#default-properties
property_denylist: env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT === undefined ? [
'$current_url',
'$pathname',
'$session_entry_url',
'$session_entry_host',
'$session_entry_pathname',
'$session_entry_referrer',
'$session_entry_referring_domain',
'$referrer',
'$referring_domain',
'$ip',
] : []
});
} else {
console.log("PostHog telemetry disabled");
console.debug("PostHog telemetry disabled");
}
}, [disabled]);

useEffect(() => {
if (!session) {
return;
}

// Only identify the user if we are running in a cloud environment.
if (env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT !== undefined) {
posthog.identify(session.user.id, {
email: session.user.email,
name: session.user.name,
});
}
}, [disabled])
}, [session]);

return (
<PHProvider client={posthog}>
{/* @see: https://github.com/vercel/next.js/issues/51581 */}
<Suspense fallback={null}>
<PostHogPageView />
</Suspense>
Expand Down
9 changes: 7 additions & 2 deletions packages/web/src/env.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,21 +50,26 @@ export const env = createEnv({
// Misc UI flags
SECURITY_CARD_ENABLED: booleanSchema.default('false'),
},
// @NOTE: Make sure you destructure all client variables in the
// `experimental__runtimeEnv` block below.
// @NOTE: Please make sure of the following:
// - Make sure you destructure all client variables in
// the `experimental__runtimeEnv` block below.
// - Update the Dockerfile to pass these variables as build-args.
client: {
// PostHog
NEXT_PUBLIC_POSTHOG_PAPIK: z.string().optional(),

// Misc
NEXT_PUBLIC_SOURCEBOT_VERSION: z.string().default('unknown'),
NEXT_PUBLIC_POLLING_INTERVAL_MS: numberSchema.default(5000),

NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT: z.enum(["dev", "staging", "prod"]).optional(),
},
// For Next.js >= 13.4.4, you only need to destructure client variables:
experimental__runtimeEnv: {
NEXT_PUBLIC_POSTHOG_PAPIK: process.env.NEXT_PUBLIC_POSTHOG_PAPIK,
NEXT_PUBLIC_SOURCEBOT_VERSION: process.env.NEXT_PUBLIC_SOURCEBOT_VERSION,
NEXT_PUBLIC_POLLING_INTERVAL_MS: process.env.NEXT_PUBLIC_POLLING_INTERVAL_MS,
NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT: process.env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT,
},
skipValidation: process.env.SKIP_ENV_VALIDATION === "1",
emptyStringAsUndefined: true,
Expand Down
4 changes: 4 additions & 0 deletions packages/web/src/lib/posthogEvents.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -244,6 +244,10 @@ export type PosthogEventMap = {
wa_demo_card_click: {},
wa_demo_try_card_pressed: {},
wa_share_link_created: {},
//////////////////////////////////////////////////////////////////
$pageview: {
$current_url: string,
},
}

export type PosthogEvent = keyof PosthogEventMap;
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .env.development
Original file line numberDiff line numberDiff line change
Expand Up@@ -34,7 +34,7 @@ AUTH_URL="http://localhost:3000"
# Sentry
# SENTRY_BACKEND_DSN=""
# NEXT_PUBLIC_SENTRY_WEBAPP_DSN=""
SENTRY_ENVIRONMENT="dev"
# SENTRY_ENVIRONMENT="dev"
# NEXT_PUBLIC_SENTRY_ENVIRONMENT="dev"
# SENTRY_AUTH_TOKEN=

Expand DownExpand Up@@ -76,4 +76,6 @@ SOURCEBOT_TELEMETRY_DISABLED=true # Disables telemetry collection

# CONFIG_MAX_REPOS_NO_TOKEN=
# NODE_ENV=
# SOURCEBOT_TENANCY_MODE=single
# SOURCEBOT_TENANCY_MODE=single

# NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT=
9 changes: 7 additions & 2 deletions Dockerfile
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,6 +9,7 @@ ARG SOURCEBOT_VERSION
# @see: https://posthog.com/tutorials/api-capture-events#authenticating-with-the-project-api-key
ARG POSTHOG_PAPIK
ARG SENTRY_ENVIRONMENT
ARG SOURCEBOT_CLOUD_ENVIRONMENT

FROM node:20-alpine3.19 AS node-alpine
FROM golang:1.23.4-alpine3.19 AS go-alpine
Expand DownExpand Up@@ -52,9 +53,11 @@ ARG POSTHOG_PAPIK
ENV NEXT_PUBLIC_POSTHOG_PAPIK=$POSTHOG_PAPIK
ARG SENTRY_ENVIRONMENT
ENV NEXT_PUBLIC_SENTRY_ENVIRONMENT=$SENTRY_ENVIRONMENT
ARG SOURCEBOT_CLOUD_ENVIRONMENT
ENV NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT=$SOURCEBOT_CLOUD_ENVIRONMENT
# Local args
ARG SENTRY_WEBAPP_DSN
ENV NEXT_PUBLIC_SENTRY_WEBAPP_DSN=$SENTRY_WEBAPP_DSN
ARG NEXT_PUBLIC_SENTRY_WEBAPP_DSN
ENV NEXT_PUBLIC_SENTRY_WEBAPP_DSN=$NEXT_PUBLIC_SENTRY_WEBAPP_DSN
# -----------

RUN apk add --no-cache libc6-compat
Expand DownExpand Up@@ -104,6 +107,8 @@ ARG SOURCEBOT_VERSION
ENV SOURCEBOT_VERSION=$SOURCEBOT_VERSION
ARG POSTHOG_PAPIK
ENV POSTHOG_PAPIK=$POSTHOG_PAPIK
ARG SENTRY_ENVIRONMENT
ENV SENTRY_ENVIRONMENT=$SENTRY_ENVIRONMENT
# Local args
# -----------

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -32,6 +32,7 @@ import { useSession } from "next-auth/react";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { signOut } from "next-auth/react"
import { env } from "@/env.mjs";
import posthog from "posthog-js";

interface SettingsDropdownProps {
menuButtonClassName?: string;
Expand DownExpand Up@@ -94,7 +95,9 @@ export const SettingsDropdown = ({
onClick={() => {
signOut({
redirectTo: "/login",
});
}).then(() => {
posthog.reset();
})
}}
>
<LogOut className="mr-2 h-4 w-4" />
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
'use client';

import { useEffect, useState } from "react";
import { useState } from "react";
import { CodeHostType } from "@/lib/utils";
import { getCodeHostIcon } from "@/lib/utils";
import {
Expand All@@ -15,8 +15,6 @@ import { OnboardingSteps } from "@/lib/constants";
import { BackButton } from "./onboardBackButton";
import { CodeHostIconButton } from "../../components/codeHostIconButton";
import useCaptureEvent from "@/hooks/useCaptureEvent";
import { useSession } from "next-auth/react";
import posthog from "posthog-js";
import SecurityCard from "@/app/components/securityCard";

interface ConnectCodeHostProps {
Expand All@@ -27,18 +25,6 @@ interface ConnectCodeHostProps {
export const ConnectCodeHost = ({ nextStep, securityCardEnabled }: ConnectCodeHostProps) => {
const [selectedCodeHost, setSelectedCodeHost] = useState<CodeHostType | null>(null);
const router = useRouter();
const { data: session } = useSession();

// Note: this is currently the first client side page that gets loaded after a user registers. If this changes, we need to update this.
// @nocheckin
useEffect(() => {
if (session?.user) {
posthog.identify(session.user.id, {
email: session.user.email,
name: session.user.name,
});
}
}, [session?.user]);

const onCreated = useCallback(() => {
router.push(`?step=${nextStep}`);
Expand Down
4 changes: 4 additions & 0 deletions packages/web/src/app/components/logoutEscapeHatch.tsx
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
import { LogOutIcon } from "lucide-react";
import { signOut } from "@/auth";
import posthog from "posthog-js";

interface LogoutEscapeHatchProps {
className?: string;
}
Expand All@@ -14,6 +16,8 @@ export const LogoutEscapeHatch = ({
"use server";
await signOut({
redirectTo: "/login",
}).then(() => {
posthog.reset();
});
}}
>
Expand Down
9 changes: 9 additions & 0 deletions packages/web/src/app/login/components/loginForm.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,6 +13,12 @@ import { SourcebotLogo } from "@/app/components/sourcebotLogo";
import { TextSeparator } from "@/app/components/textSeparator";
import useCaptureEvent from "@/hooks/useCaptureEvent";
import DemoCard from "@/app/[domain]/onboard/components/demoCard";
import Link from "next/link";
import { env } from "@/env.mjs";

const TERMS_OF_SERVICE_URL = "https://sourcebot.dev/terms";
const PRIVACY_POLICY_URL = "https://sourcebot.dev/privacy";

interface LoginFormProps {
callbackUrl?: string;
error?: string;
Expand DownExpand Up@@ -98,6 +104,9 @@ export const LoginForm = ({ callbackUrl, error, enabledMethods }: LoginFormProps
]}
/>
</Card>
{env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT !== undefined && (
<p className="text-xs text-muted-foreground mt-8">By signing in, you agree to the <Link className="underline" href={TERMS_OF_SERVICE_URL} target="_blank">Terms of Service</Link> and <Link className="underline" href={PRIVACY_POLICY_URL} target="_blank">Privacy Policy</Link>.</p>
)}
</div>
)
}
Expand Down
57 changes: 41 additions & 16 deletions packages/web/src/app/posthogProvider.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,21 +5,25 @@ import { PostHogProvider as PHProvider } from 'posthog-js/react'
import { usePathname, useSearchParams } from "next/navigation"
import { Suspense, useEffect } from "react"
import { env } from '@/env.mjs'
import { useSession } from 'next-auth/react'
import { captureEvent } from '@/hooks/useCaptureEvent'

// @see: https://posthog.com/docs/libraries/next-js#capturing-pageviews
function PostHogPageView() {
const pathname = usePathname()
const searchParams = useSearchParams()
const posthog = usePostHog()

// Track pageviews
useEffect(() => {
if (pathname && posthog) {
let url = window.origin + pathname
if (searchParams.toString()) {
url = url + `?${searchParams.toString()}`
}

posthog.capture('$pageview', { '$current_url': url })
captureEvent('$pageview', {
$current_url: url,
});
}
}, [pathname, searchParams, posthog])

Expand All@@ -32,34 +36,55 @@ interface PostHogProviderProps {
}

export function PostHogProvider({ children, disabled }: PostHogProviderProps) {
const { data: session } = useSession();

useEffect(() => {
if (!disabled && env.NEXT_PUBLIC_POSTHOG_PAPIK) {
console.debug(`PostHog telemetry enabled. Cloud environment: ${env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT}`);
posthog.init(env.NEXT_PUBLIC_POSTHOG_PAPIK, {
// @see next.config.mjs for path rewrites to the "/ingest" route.
api_host: "/ingest",
person_profiles: 'identified_only',
capture_pageview: false,
autocapture: false,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
sanitize_properties: (properties: Record<string, any>, _event: string) => {
// https://posthog.com/docs/libraries/js#config
if (properties['$current_url']) {
properties['$current_url'] = null;
}
if (properties['$ip']) {
properties['$ip'] = null;
}

return properties;
}
// In self-hosted mode, we don't want to capture the following
// default properties.
// @see: https://posthog.com/docs/data/events#default-properties
property_denylist: env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT === undefined ? [
'$current_url',
'$pathname',
'$session_entry_url',
'$session_entry_host',
'$session_entry_pathname',
'$session_entry_referrer',
'$session_entry_referring_domain',
'$referrer',
'$referring_domain',
'$ip',
] : []
});
} else {
console.log("PostHog telemetry disabled");
console.debug("PostHog telemetry disabled");
}
}, [disabled]);

useEffect(() => {
if (!session) {
return;
}

// Only identify the user if we are running in a cloud environment.
if (env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT !== undefined) {
posthog.identify(session.user.id, {
email: session.user.email,
name: session.user.name,
});
}
}, [disabled])
}, [session]);

return (
<PHProvider client={posthog}>
{/* @see: https://github.com/vercel/next.js/issues/51581 */}
<Suspense fallback={null}>
<PostHogPageView />
</Suspense>
Expand Down
9 changes: 7 additions & 2 deletions packages/web/src/env.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,21 +50,26 @@ export const env = createEnv({
// Misc UI flags
SECURITY_CARD_ENABLED: booleanSchema.default('false'),
},
// @NOTE: Make sure you destructure all client variables in the
// `experimental__runtimeEnv` block below.
// @NOTE: Please make sure of the following:
// - Make sure you destructure all client variables in
// the `experimental__runtimeEnv` block below.
// - Update the Dockerfile to pass these variables as build-args.
client: {
// PostHog
NEXT_PUBLIC_POSTHOG_PAPIK: z.string().optional(),

// Misc
NEXT_PUBLIC_SOURCEBOT_VERSION: z.string().default('unknown'),
NEXT_PUBLIC_POLLING_INTERVAL_MS: numberSchema.default(5000),

NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT: z.enum(["dev", "staging", "prod"]).optional(),
},
// For Next.js >= 13.4.4, you only need to destructure client variables:
experimental__runtimeEnv: {
NEXT_PUBLIC_POSTHOG_PAPIK: process.env.NEXT_PUBLIC_POSTHOG_PAPIK,
NEXT_PUBLIC_SOURCEBOT_VERSION: process.env.NEXT_PUBLIC_SOURCEBOT_VERSION,
NEXT_PUBLIC_POLLING_INTERVAL_MS: process.env.NEXT_PUBLIC_POLLING_INTERVAL_MS,
NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT: process.env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT,
},
skipValidation: process.env.SKIP_ENV_VALIDATION === "1",
emptyStringAsUndefined: true,
Expand Down
4 changes: 4 additions & 0 deletions packages/web/src/lib/posthogEvents.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -244,6 +244,10 @@ export type PosthogEventMap = {
wa_demo_card_click: {},
wa_demo_try_card_pressed: {},
wa_share_link_created: {},
//////////////////////////////////////////////////////////////////
$pageview: {
$current_url: string,
},
}

export type PosthogEvent = keyof PosthogEventMap;
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .env.development
Original file line numberDiff line numberDiff line change
Expand Up@@ -34,7 +34,7 @@ AUTH_URL="http://localhost:3000"
# Sentry
# SENTRY_BACKEND_DSN=""
# NEXT_PUBLIC_SENTRY_WEBAPP_DSN=""
SENTRY_ENVIRONMENT="dev"
# SENTRY_ENVIRONMENT="dev"
# NEXT_PUBLIC_SENTRY_ENVIRONMENT="dev"
# SENTRY_AUTH_TOKEN=

Expand DownExpand Up@@ -76,4 +76,6 @@ SOURCEBOT_TELEMETRY_DISABLED=true # Disables telemetry collection

# CONFIG_MAX_REPOS_NO_TOKEN=
# NODE_ENV=
# SOURCEBOT_TENANCY_MODE=single
# SOURCEBOT_TENANCY_MODE=single

# NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT=
9 changes: 7 additions & 2 deletions Dockerfile
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,6 +9,7 @@ ARG SOURCEBOT_VERSION
# @see: https://posthog.com/tutorials/api-capture-events#authenticating-with-the-project-api-key
ARG POSTHOG_PAPIK
ARG SENTRY_ENVIRONMENT
ARG SOURCEBOT_CLOUD_ENVIRONMENT

FROM node:20-alpine3.19 AS node-alpine
FROM golang:1.23.4-alpine3.19 AS go-alpine
Expand DownExpand Up@@ -52,9 +53,11 @@ ARG POSTHOG_PAPIK
ENV NEXT_PUBLIC_POSTHOG_PAPIK=$POSTHOG_PAPIK
ARG SENTRY_ENVIRONMENT
ENV NEXT_PUBLIC_SENTRY_ENVIRONMENT=$SENTRY_ENVIRONMENT
ARG SOURCEBOT_CLOUD_ENVIRONMENT
ENV NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT=$SOURCEBOT_CLOUD_ENVIRONMENT
# Local args
ARG SENTRY_WEBAPP_DSN
ENV NEXT_PUBLIC_SENTRY_WEBAPP_DSN=$SENTRY_WEBAPP_DSN
ARG NEXT_PUBLIC_SENTRY_WEBAPP_DSN
ENV NEXT_PUBLIC_SENTRY_WEBAPP_DSN=$NEXT_PUBLIC_SENTRY_WEBAPP_DSN
# -----------

RUN apk add --no-cache libc6-compat
Expand DownExpand Up@@ -104,6 +107,8 @@ ARG SOURCEBOT_VERSION
ENV SOURCEBOT_VERSION=$SOURCEBOT_VERSION
ARG POSTHOG_PAPIK
ENV POSTHOG_PAPIK=$POSTHOG_PAPIK
ARG SENTRY_ENVIRONMENT
ENV SENTRY_ENVIRONMENT=$SENTRY_ENVIRONMENT
# Local args
# -----------

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -32,6 +32,7 @@ import { useSession } from "next-auth/react";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { signOut } from "next-auth/react"
import { env } from "@/env.mjs";
import posthog from "posthog-js";

interface SettingsDropdownProps {
menuButtonClassName?: string;
Expand DownExpand Up@@ -94,7 +95,9 @@ export const SettingsDropdown = ({
onClick={() => {
signOut({
redirectTo: "/login",
});
}).then(() => {
posthog.reset();
})
}}
>
<LogOut className="mr-2 h-4 w-4" />
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
'use client';

import { useEffect, useState } from "react";
import { useState } from "react";
import { CodeHostType } from "@/lib/utils";
import { getCodeHostIcon } from "@/lib/utils";
import {
Expand All@@ -15,8 +15,6 @@ import { OnboardingSteps } from "@/lib/constants";
import { BackButton } from "./onboardBackButton";
import { CodeHostIconButton } from "../../components/codeHostIconButton";
import useCaptureEvent from "@/hooks/useCaptureEvent";
import { useSession } from "next-auth/react";
import posthog from "posthog-js";
import SecurityCard from "@/app/components/securityCard";

interface ConnectCodeHostProps {
Expand All@@ -27,18 +25,6 @@ interface ConnectCodeHostProps {
export const ConnectCodeHost = ({ nextStep, securityCardEnabled }: ConnectCodeHostProps) => {
const [selectedCodeHost, setSelectedCodeHost] = useState<CodeHostType | null>(null);
const router = useRouter();
const { data: session } = useSession();

// Note: this is currently the first client side page that gets loaded after a user registers. If this changes, we need to update this.
// @nocheckin
useEffect(() => {
if (session?.user) {
posthog.identify(session.user.id, {
email: session.user.email,
name: session.user.name,
});
}
}, [session?.user]);

const onCreated = useCallback(() => {
router.push(`?step=${nextStep}`);
Expand Down
4 changes: 4 additions & 0 deletions packages/web/src/app/components/logoutEscapeHatch.tsx
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
import { LogOutIcon } from "lucide-react";
import { signOut } from "@/auth";
import posthog from "posthog-js";

interface LogoutEscapeHatchProps {
className?: string;
}
Expand All@@ -14,6 +16,8 @@ export const LogoutEscapeHatch = ({
"use server";
await signOut({
redirectTo: "/login",
}).then(() => {
posthog.reset();
});
}}
>
Expand Down
9 changes: 9 additions & 0 deletions packages/web/src/app/login/components/loginForm.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,6 +13,12 @@ import { SourcebotLogo } from "@/app/components/sourcebotLogo";
import { TextSeparator } from "@/app/components/textSeparator";
import useCaptureEvent from "@/hooks/useCaptureEvent";
import DemoCard from "@/app/[domain]/onboard/components/demoCard";
import Link from "next/link";
import { env } from "@/env.mjs";

const TERMS_OF_SERVICE_URL = "https://sourcebot.dev/terms";
const PRIVACY_POLICY_URL = "https://sourcebot.dev/privacy";

interface LoginFormProps {
callbackUrl?: string;
error?: string;
Expand DownExpand Up@@ -98,6 +104,9 @@ export const LoginForm = ({ callbackUrl, error, enabledMethods }: LoginFormProps
]}
/>
</Card>
{env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT !== undefined && (
<p className="text-xs text-muted-foreground mt-8">By signing in, you agree to the <Link className="underline" href={TERMS_OF_SERVICE_URL} target="_blank">Terms of Service</Link> and <Link className="underline" href={PRIVACY_POLICY_URL} target="_blank">Privacy Policy</Link>.</p>
)}
</div>
)
}
Expand Down
57 changes: 41 additions & 16 deletions packages/web/src/app/posthogProvider.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,21 +5,25 @@ import { PostHogProvider as PHProvider } from 'posthog-js/react'
import { usePathname, useSearchParams } from "next/navigation"
import { Suspense, useEffect } from "react"
import { env } from '@/env.mjs'
import { useSession } from 'next-auth/react'
import { captureEvent } from '@/hooks/useCaptureEvent'

// @see: https://posthog.com/docs/libraries/next-js#capturing-pageviews
function PostHogPageView() {
const pathname = usePathname()
const searchParams = useSearchParams()
const posthog = usePostHog()

// Track pageviews
useEffect(() => {
if (pathname && posthog) {
let url = window.origin + pathname
if (searchParams.toString()) {
url = url + `?${searchParams.toString()}`
}

posthog.capture('$pageview', { '$current_url': url })
captureEvent('$pageview', {
$current_url: url,
});
}
}, [pathname, searchParams, posthog])

Expand All@@ -32,34 +36,55 @@ interface PostHogProviderProps {
}

export function PostHogProvider({ children, disabled }: PostHogProviderProps) {
const { data: session } = useSession();

useEffect(() => {
if (!disabled && env.NEXT_PUBLIC_POSTHOG_PAPIK) {
console.debug(`PostHog telemetry enabled. Cloud environment: ${env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT}`);
posthog.init(env.NEXT_PUBLIC_POSTHOG_PAPIK, {
// @see next.config.mjs for path rewrites to the "/ingest" route.
api_host: "/ingest",
person_profiles: 'identified_only',
capture_pageview: false,
autocapture: false,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
sanitize_properties: (properties: Record<string, any>, _event: string) => {
// https://posthog.com/docs/libraries/js#config
if (properties['$current_url']) {
properties['$current_url'] = null;
}
if (properties['$ip']) {
properties['$ip'] = null;
}

return properties;
}
// In self-hosted mode, we don't want to capture the following
// default properties.
// @see: https://posthog.com/docs/data/events#default-properties
property_denylist: env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT === undefined ? [
'$current_url',
'$pathname',
'$session_entry_url',
'$session_entry_host',
'$session_entry_pathname',
'$session_entry_referrer',
'$session_entry_referring_domain',
'$referrer',
'$referring_domain',
'$ip',
] : []
});
} else {
console.log("PostHog telemetry disabled");
console.debug("PostHog telemetry disabled");
}
}, [disabled]);

useEffect(() => {
if (!session) {
return;
}

// Only identify the user if we are running in a cloud environment.
if (env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT !== undefined) {
posthog.identify(session.user.id, {
email: session.user.email,
name: session.user.name,
});
}
}, [disabled])
}, [session]);

return (
<PHProvider client={posthog}>
{/* @see: https://github.com/vercel/next.js/issues/51581 */}
<Suspense fallback={null}>
<PostHogPageView />
</Suspense>
Expand Down
9 changes: 7 additions & 2 deletions packages/web/src/env.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,21 +50,26 @@ export const env = createEnv({
// Misc UI flags
SECURITY_CARD_ENABLED: booleanSchema.default('false'),
},
// @NOTE: Make sure you destructure all client variables in the
// `experimental__runtimeEnv` block below.
// @NOTE: Please make sure of the following:
// - Make sure you destructure all client variables in
// the `experimental__runtimeEnv` block below.
// - Update the Dockerfile to pass these variables as build-args.
client: {
// PostHog
NEXT_PUBLIC_POSTHOG_PAPIK: z.string().optional(),

// Misc
NEXT_PUBLIC_SOURCEBOT_VERSION: z.string().default('unknown'),
NEXT_PUBLIC_POLLING_INTERVAL_MS: numberSchema.default(5000),

NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT: z.enum(["dev", "staging", "prod"]).optional(),
},
// For Next.js >= 13.4.4, you only need to destructure client variables:
experimental__runtimeEnv: {
NEXT_PUBLIC_POSTHOG_PAPIK: process.env.NEXT_PUBLIC_POSTHOG_PAPIK,
NEXT_PUBLIC_SOURCEBOT_VERSION: process.env.NEXT_PUBLIC_SOURCEBOT_VERSION,
NEXT_PUBLIC_POLLING_INTERVAL_MS: process.env.NEXT_PUBLIC_POLLING_INTERVAL_MS,
NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT: process.env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT,
},
skipValidation: process.env.SKIP_ENV_VALIDATION === "1",
emptyStringAsUndefined: true,
Expand Down
4 changes: 4 additions & 0 deletions packages/web/src/lib/posthogEvents.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -244,6 +244,10 @@ export type PosthogEventMap = {
wa_demo_card_click: {},
wa_demo_try_card_pressed: {},
wa_share_link_created: {},
//////////////////////////////////////////////////////////////////
$pageview: {
$current_url: string,
},
}

export type PosthogEvent = keyof PosthogEventMap;