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
7 changes: 7 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@ NEXT_PUBLIC_APP_URL=http://127.0.0.1:3000
# Cloudflare Web Analytics (beacon token — dashboard → Web Analytics → Manage site → JS snippet)
NEXT_PUBLIC_CF_WEB_ANALYTICS_TOKEN=

# Umami (self-hosted — website UUID from Umami → Settings → Websites)
# Tracker is proxied first-party at /w/a.js → UMAMI_ORIGIN (bypasses ad blockers).
# Enable Replays + Heatmaps in Umami → Websites → Edit → Replays & Heatmaps.
# Recorder script: /w/a/r.js (same proxy). Heatmap previews need frame-ancestors (set in next.config).
NEXT_PUBLIC_UMAMI_WEBSITE_ID=
# UMAMI_ORIGIN=https://umami.chtnnhfoundation.org

# Superadmin bootstrap (comma-separated emails promoted on sign-in)
# SUPERADMIN_EMAILS=you@example.com

Expand Down
24 changes: 24 additions & 0 deletions apps/web/next.config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import path from "node:path";
import { config as loadEnv } from "dotenv";
import type { NextConfig } from "next";
import {
UMAMI_ORIGIN_DEFAULT,
UMAMI_PROXY_BASE,
UMAMI_RECORDER_SCRIPT_PATH,
UMAMI_SCRIPT_PATH,
} from "./src/lib/umami";

// Load monorepo root .env so YOUTUBE_API_KEY etc. work without duplicating into apps/web
if (process.env.TOGETHER_SKIP_ENV_FILE !== "1") {
Expand All @@ -12,6 +18,8 @@ function publicEnv(name: string): string | undefined {
return process.env[name]?.trim() || undefined;
}

const umamiOrigin = process.env.UMAMI_ORIGIN?.trim() || UMAMI_ORIGIN_DEFAULT;

const nextConfig: NextConfig = {
...(process.env.TOGETHER_E2E === "1" ? { devIndicators: false as const } : {}),
env: {
Expand All @@ -22,8 +30,23 @@ const nextConfig: NextConfig = {
NEXT_PUBLIC_APP_URL: publicEnv("NEXT_PUBLIC_APP_URL"),
NEXT_PUBLIC_REALTIME_URL: publicEnv("NEXT_PUBLIC_REALTIME_URL"),
NEXT_PUBLIC_CF_WEB_ANALYTICS_TOKEN: publicEnv("NEXT_PUBLIC_CF_WEB_ANALYTICS_TOKEN"),
NEXT_PUBLIC_UMAMI_WEBSITE_ID: publicEnv("NEXT_PUBLIC_UMAMI_WEBSITE_ID"),
NEXT_PUBLIC_SPOTIFY_CLIENT_ID: publicEnv("NEXT_PUBLIC_SPOTIFY_CLIENT_ID"),
},
rewrites: async () => [
{
source: UMAMI_SCRIPT_PATH,
destination: `${umamiOrigin}/script.js`,
},
{
source: UMAMI_RECORDER_SCRIPT_PATH,
destination: `${umamiOrigin}/recorder.js`,
},
{
source: `${UMAMI_PROXY_BASE}/api/:path*`,
destination: `${umamiOrigin}/api/:path*`,
},
],
transpilePackages: [
"@together/ui",
"@together/shared",
Expand All @@ -43,6 +66,7 @@ const nextConfig: NextConfig = {
"connect-src 'self' wss://realtime.together.chtnnhfoundation.org ws: wss: https://*.supabase.co https://api.spotify.com https://accounts.spotify.com https://www.googleapis.com https://*.apple.com https://cloudflareinsights.com",
"img-src 'self' data: blob: https://i.ytimg.com https://*.scdn.co https://*.mzstatic.com",
"style-src 'self' 'unsafe-inline'",
`frame-ancestors 'self' ${umamiOrigin}`,
].join("; "),
},
],
Expand Down
2 changes: 2 additions & 0 deletions apps/web/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { CloudflareWebAnalytics } from "@/components/cloudflare-web-analytics";
import { ServiceWorkerRegister } from "@/components/service-worker-register";
import { ThemeBootstrap } from "@/components/theme-bootstrap";
import { ToastProvider } from "@/components/toast";
import { UmamiAnalytics } from "@/components/umami-analytics";
import { absoluteUrl, siteUrl } from "@/lib/seo";
import { getSupabasePublicConfig } from "@/lib/supabase/public-config";

Expand Down Expand Up @@ -72,6 +73,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
<div id="main-content">{children}</div>
</ToastProvider>
<CloudflareWebAnalytics />
<UmamiAnalytics />
</AuthConfigProvider>
</body>
</html>
Expand Down
8 changes: 5 additions & 3 deletions apps/web/src/app/privacy/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,11 @@ export default function PrivacyPage() {
</li>
<li>
<strong>Service analytics</strong> — aggregated, privacy-oriented usage analytics via
Cloudflare Web Analytics (page views on the web app) and Cloudflare Workers Analytics
Engine (aggregated realtime events such as joins and skips). We do not use third-party
advertising trackers.
Umami (page views and events; optional session replays and click/scroll heatmaps when
enabled in our Umami settings — inputs are masked by default), loaded through a
first-party proxy on our domain, Cloudflare Web Analytics (page views on the web app),
and Cloudflare Workers Analytics Engine (aggregated realtime events such as joins and
skips). We do not use third-party advertising trackers.
</li>
<li>
<strong>Realtime session data</strong> — while a room is active, playback position,
Expand Down
33 changes: 33 additions & 0 deletions apps/web/src/components/umami-analytics.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import Script from "next/script";
import { UMAMI_PROXY_BASE, UMAMI_RECORDER_SCRIPT_PATH, UMAMI_SCRIPT_PATH } from "@/lib/umami";

/** Umami analytics + session replay/heatmaps via first-party proxy (see next.config.ts rewrites). */
export function UmamiAnalytics() {
const websiteId = process.env.NEXT_PUBLIC_UMAMI_WEBSITE_ID?.trim();
const appUrl = process.env.NEXT_PUBLIC_APP_URL?.trim().replace(/\/$/, "");
if (!websiteId || !appUrl) return null;
Comment thread
chtnnh marked this conversation as resolved.

const hostUrl = `${appUrl}${UMAMI_PROXY_BASE}`;
Comment thread
chtnnh marked this conversation as resolved.

return (
<>
<Script
id="umami-analytics"
src={UMAMI_SCRIPT_PATH}
strategy="afterInteractive"
defer
data-website-id={websiteId}
data-host-url={hostUrl}
data-do-not-track="true"
/>
<Script
id="umami-recorder"
src={UMAMI_RECORDER_SCRIPT_PATH}
strategy="afterInteractive"
defer
data-website-id={websiteId}
data-host-url={hostUrl}
/>
</>
);
}
5 changes: 5 additions & 0 deletions apps/web/src/lib/umami.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/** First-party Umami proxy paths (rewritten in next.config.ts). */
export const UMAMI_SCRIPT_PATH = "/w/a.js";
export const UMAMI_RECORDER_SCRIPT_PATH = "/w/a/r.js";
export const UMAMI_PROXY_BASE = "/w/a";
export const UMAMI_ORIGIN_DEFAULT = "https://umami.chtnnhfoundation.org";
6 changes: 5 additions & 1 deletion scripts/ci-pre-commit.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ source "$ROOT/scripts/affected.sh"

pnpm exec lint-staged

pnpm exec biome check --changed --error-on-warnings
if git diff --cached --quiet; then
pnpm exec biome check --changed --error-on-warnings
else
pnpm exec biome check --staged --error-on-warnings
fi

if git rev-parse origin/main >/dev/null 2>&1; then
pnpm exec turbo run typecheck --filter=...[origin/main] || pnpm typecheck
Expand Down
Loading