Skip to content
Open
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
4 changes: 3 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ PUBLIC_APPWRITE_ENDPOINT=http://localhost/v1
PUBLIC_STRIPE_KEY=
PUBLIC_GROWTH_ENDPOINT=
PUBLIC_CONSOLE_EMAIL_VERIFICATION=false
PUBLIC_CONSOLE_MOCK_AI_SUGGESTIONS=true
PUBLIC_CONSOLE_MOCK_AI_SUGGESTIONS=true
PUBLIC_POSTHOG_API_KEY=
PUBLIC_POSTHOG_HOST=
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"nanoid": "^5.1.5",
"nanotar": "^0.1.1",
"plausible-tracker": "^0.3.9",
"posthog-js": "^1.312.0",
"pretty-bytes": "^6.1.1",
"prismjs": "^1.30.0",
"remarkable": "^2.0.1",
Expand Down
41 changes: 41 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions src/lib/actions/analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { ENV, MODE, VARS, isCloud } from '$lib/system';
import { AppwriteException } from '@appwrite.io/console';
import { browser } from '$app/environment';
import { getReferrerAndUtmSource, getTrackedQueryParams } from '$lib/helpers/utm';
// import { posthog } from './posthog';

function plausible(domain: string): AnalyticsPlugin {
if (!browser) return { name: 'analytics-plugin-plausible' };
Expand Down Expand Up @@ -72,6 +73,9 @@ export function trackEvent(name: string, data: object = null): void {
} else {
analytics.track(name, { ...data, path });
sendEventToGrowth(name, path, data);
/*if (posthog) {
posthog.capture(name, { ...data, path });
}*/
}
}

Expand Down
40 changes: 40 additions & 0 deletions src/lib/actions/posthog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import posthog from 'posthog-js';
import { browser } from '$app/environment';
import { VARS, ENV } from '$lib/system';
import type { Models } from '@appwrite.io/console';
import { preferences } from '$lib/stores/preferences';

const POSTHOG_PREFS_KEY = 'posthog_session_recorded';

if (browser && VARS.POSTHOG_API_KEY && !ENV.DEV && !ENV.TEST) {
posthog.init(VARS.POSTHOG_API_KEY, {
api_host: VARS.POSTHOG_HOST,
person_profiles: 'identified_only',
capture_pageview: true,
capture_pageleave: true,
session_recording: {
recordCrossOriginIframes: false,
/*maskTextSelector: '.ph-no-capture'*/
},
autocapture: true,
disable_session_recording: !ENV.PROD
});
}
Comment on lines +9 to +22
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n src/lib/actions/posthog.ts

Repository: appwrite/console

Length of output: 1618


🏁 Script executed:

# Search for ENV variable definitions
rg -t ts "ENV\s*[:=]\s*\{" --context 10

Repository: appwrite/console

Length of output: 1319


🏁 Script executed:

# Look for environment config files
fd -t js -t ts "env" | grep -E "(config|env)" | head -20

Repository: appwrite/console

Length of output: 229


🌐 Web query:

PostHog session recording configuration production preview environments best practices

💡 Result:

Here are concise best practices for configuring PostHog session recordings across production / preview (staging/dev) environments:

  1. Use separate PostHog projects per environment (prod vs staging/dev/preview). This prevents test noise and billing surprises. Use env vars to load the correct API key per environment. [3]

  2. Don’t record everything by default. Enable recordings only where useful via URL triggers, event triggers or feature flags (e.g., signup/checkout flows). URL triggers replace the older authorized-domains setting. Use sampling and minimum-duration to reduce volume. (start/stop APIs let you programmatically control recordings). [2][4]

  3. Disable or restrict recordings in preview builds:

    • Default to disable_session_recording: true in non-prod builds and call posthog.startSessionRecording() only when needed in tests.
    • Or keep recording off and selectively enable for targeted flows with triggers/feature flags. [2][4]
  4. Protect PII and sensitive UI:

    • Mask inputs/text by default (maskAllInputs / maskTextSelector) and selectively unmask only required elements.
    • Use ph-no-capture on elements that must never be recorded.
    • Use before_send to strip sensitive data from events. [1][5]
  5. Use sampling, minimum-duration (strictMinimumDuration) and billing limits:

    • Configure a sample rate and a minimum continuous duration so short/bounce sessions aren’t ingested.
    • Set recording/billing caps to avoid unexpected overages. [2]
  6. Consent, compliance and persistence:

    • Gate recordings behind consent where required (EU/UK) via your CMP/cookie banner or application logic.
    • Consider persistence settings (localStorage vs cookie) to avoid cookie-driven loops with CMPs. [1][2][5]
  7. Dev/test ergonomics and reliability:

    • Keep posthog-js up to date (recordings features/fixes roll out often).
    • Watch CSP, CORS and ad-blocker interactions (recorder.js may be blocked). Use a reverse-proxy if blockers are an issue. [4]
  8. Operational tips:

    • Logically name projects (prod/staging/dev/preview).
    • Use feature flags or environment-only config to flip recording behavior without code changes.
    • Monitor recording consumption and iterate (most teams watch a small fraction of replays; focus capture on high-value flows). [3][1]

Sources:

  • PostHog privacy & replay masking docs. [1]
  • How to control which sessions you record (session recording triggers, start/stop, sampling, strictMinimumDuration). [2]
  • PostHog tutorial: multiple environments / use separate projects. [3]
  • Session replay troubleshooting (URL triggers, CSP, blockers, SDK version notes). [4]
  • Controlling data collection / autocapture and masking guidance. [5]

If you want, I can produce a short example posthog.init snippet showing a recommended prod vs preview config (masking + disabled recordings in preview).


Clarify disable_session_recording configuration and handle PREVIEW environments properly.

Line 20 sets disable_session_recording: !ENV.PROD, but the outer condition on line 9 already ensures !ENV.DEV. Since ENV.PROD = !dev, this configuration is redundant—when the outer condition passes, ENV.PROD is true, making disable_session_recording always false.

Additionally, the outer condition initializes PostHog for both production and preview environments without distinction. Per PostHog best practices, session recording should be disabled by default in preview deployments and enabled only in production. Consider adding ENV.PREVIEW to the initialization condition check or adjusting the config to properly handle environment-specific behavior.

Line 17 contains commented code for maskTextSelector. Remove it or uncomment with proper configuration for sensitive data masking, which PostHog recommends as default behavior across all environments.

🤖 Prompt for AI Agents
In src/lib/actions/posthog.ts around lines 9 to 22, the PostHog init is enabling
session recording for preview environments and using a redundant
disable_session_recording: !ENV.PROD; change the initialization guard to only
run in production (e.g., require ENV.PROD true, not just !ENV.DEV) or explicitly
exclude ENV.PREVIEW (add && !ENV.PREVIEW), and set disable_session_recording to
true for non-production/preview (e.g., disable_session_recording: !ENV.PROD ||
ENV.PREVIEW) so the flag is accurate regardless of the outer condition; also
remove the commented maskTextSelector line or replace it with a proper default
maskTextSelector (e.g., set a configurable mask selector) so sensitive data
masking is consistently applied.


export async function initializeSessionRecording(user: Models.User<Models.Preferences>): Promise<void> {
if (!posthog) return;

const hasRecordedFirstSession = preferences.getKey(POSTHOG_PREFS_KEY, false);

if (!hasRecordedFirstSession) {
posthog.identify(user.$id, {
account_created: user.$createdAt
});
posthog.startSessionRecording();
await preferences.setKey(POSTHOG_PREFS_KEY, true);
} else {
posthog.stopSessionRecording();
}
}
Comment on lines +24 to +38
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Add error handling and consider privacy/consent requirements.

The initializeSessionRecording function lacks error handling. If posthog.identify(), posthog.startSessionRecording(), or preferences.setKey() fail, the state could become inconsistent (e.g., preference flag set but recording not actually started).

Additionally, session recording captures user behavior and personal data (user ID, account creation date). Ensure compliance with privacy regulations (GDPR, CCPA) by implementing:

  • User consent mechanism before starting session recording
  • Clear privacy policy disclosure
  • Opt-out capability
🔎 Example error handling
 export async function initializeSessionRecording(user: Models.User<Models.Preferences>): Promise<void> {
     if (!posthog) return;
 
     const hasRecordedFirstSession = preferences.getKey(POSTHOG_PREFS_KEY, false);
 
     if (!hasRecordedFirstSession) {
-        posthog.identify(user.$id, {
-            account_created: user.$createdAt
-        });
-        posthog.startSessionRecording();
-        await preferences.setKey(POSTHOG_PREFS_KEY, true);
+        try {
+            posthog.identify(user.$id, {
+                account_created: user.$createdAt
+            });
+            posthog.startSessionRecording();
+            await preferences.setKey(POSTHOG_PREFS_KEY, true);
+        } catch (error) {
+            console.error('Failed to initialize PostHog session recording:', error);
+        }
     } else {
         posthog.stopSessionRecording();
     }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export async function initializeSessionRecording(user: Models.User<Models.Preferences>): Promise<void> {
if (!posthog) return;
const hasRecordedFirstSession = preferences.getKey(POSTHOG_PREFS_KEY, false);
if (!hasRecordedFirstSession) {
posthog.identify(user.$id, {
account_created: user.$createdAt
});
posthog.startSessionRecording();
await preferences.setKey(POSTHOG_PREFS_KEY, true);
} else {
posthog.stopSessionRecording();
}
}
export async function initializeSessionRecording(user: Models.User<Models.Preferences>): Promise<void> {
if (!posthog) return;
const hasRecordedFirstSession = preferences.getKey(POSTHOG_PREFS_KEY, false);
if (!hasRecordedFirstSession) {
try {
posthog.identify(user.$id, {
account_created: user.$createdAt
});
posthog.startSessionRecording();
await preferences.setKey(POSTHOG_PREFS_KEY, true);
} catch (error) {
console.error('Failed to initialize PostHog session recording:', error);
}
} else {
posthog.stopSessionRecording();
}
}


export { posthog };
Comment on lines +1 to +40
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix Prettier formatting violations before merging.

The pipeline indicates a Prettier formatting check failure. Run prettier --write src/lib/actions/posthog.ts to resolve.

🧰 Tools
🪛 GitHub Actions: Tests

[error] 1-1: Prettier formatting check failed for src/lib/actions/posthog.ts. Run 'prettier --write' to fix code style issues.

🤖 Prompt for AI Agents
In src/lib/actions/posthog.ts around lines 1 to 40, the file fails Prettier
formatting; run Prettier to reformat the file (e.g., prettier --write
src/lib/actions/posthog.ts) or apply your project's configured formatter
settings/IDE formatting to fix whitespace, quoting, and trailing-comma rules,
then re-run the linter/CI and commit the formatted file.

4 changes: 3 additions & 1 deletion src/lib/system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ export const VARS = {
GROWTH_ENDPOINT: env.PUBLIC_GROWTH_ENDPOINT ?? undefined,
PUBLIC_STRIPE_KEY: env.PUBLIC_STRIPE_KEY ?? undefined,
EMAIL_VERIFICATION: env.PUBLIC_CONSOLE_EMAIL_VERIFICATION === 'true',
MOCK_AI_SUGGESTIONS: (env.PUBLIC_CONSOLE_MOCK_AI_SUGGESTIONS ?? 'true') === 'true'
MOCK_AI_SUGGESTIONS: (env.PUBLIC_CONSOLE_MOCK_AI_SUGGESTIONS ?? 'true') === 'true',
POSTHOG_API_KEY: env.PUBLIC_POSTHOG_API_KEY ?? undefined,
POSTHOG_HOST: env.PUBLIC_POSTHOG_HOST ?? undefined
};

export const ENV = {
Expand Down
6 changes: 6 additions & 0 deletions src/routes/(console)/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@
import MobileSupportModal from './wizard/support/mobileSupportModal.svelte';
import { showSupportModal } from './wizard/support/store';
import { activeHeaderAlert, consoleVariables } from './store';
import { user } from '$lib/stores/user';
import { initializeSessionRecording } from '$lib/actions/posthog';
Comment on lines +43 to +44
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find src -name "posthog.ts" -o -name "posthog.js" -o -name "posthog.tsx" -o -name "posthog.jsx"

Repository: appwrite/console

Length of output: 86


🏁 Script executed:

cat -n src/lib/actions/posthog.ts

Repository: appwrite/console

Length of output: 1618


🏁 Script executed:

sed -n '270,285p' src/routes/\(console\)/+layout.svelte

Repository: appwrite/console

Length of output: 629


Add error handling and explicit user consent check to PostHog initialization.

The initializeSessionRecording function lacks error handling around PostHog operations and preferences updates. Additionally, it should verify explicit user consent before starting session recording. Ensure:

  • Wrap posthog.identify(), posthog.startSessionRecording(), and await preferences.setKey() in a try-catch block
  • Add an explicit check for user opt-in preferences before calling posthog.startSessionRecording() on line 33
  • Log or handle initialization failures gracefully without breaking the session

The POSTHOG_API_KEY check (line 9) and environment guards are correctly implemented.

🤖 Prompt for AI Agents
In src/routes/(console)/+layout.svelte around lines 43–44, add robust error
handling and an explicit opt-in check when initializing PostHog: wrap calls to
posthog.identify(), posthog.startSessionRecording(), and await
preferences.setKey(...) in a try-catch so failures are caught and logged/handled
without throwing; before calling posthog.startSessionRecording() add an explicit
check that the user has opted in (e.g., a preferences flag or user consent
value) and only start session recording when true; in the catch block log the
error and ensure initialization continues gracefully (do not rethrow) so the
session is not broken.

import { base } from '$app/paths';
import { headerAlert } from '$lib/stores/headerAlert';
Expand Down Expand Up @@ -270,6 +272,10 @@
$stripe = await loadStripe(VARS.PUBLIC_STRIPE_KEY);
await checkForMissingPaymentMethod();
}
if ($user && isCloud) {
await initializeSessionRecording($user);
}
});
function checkForFeedback(interval: number) {
Expand Down
4 changes: 2 additions & 2 deletions src/routes/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@
}
});

$: {
$effect(() => {
if (browser) {
const isCloudClass = isCloud ? 'is-cloud' : '';

Expand All @@ -137,7 +137,7 @@
document.body.classList.remove('no-transition');
});
}
}
});

const preloadFonts = [
base + '/fonts/inter/inter-v8-latin-600.woff2',
Expand Down
Loading