From 63d780b6b8705d5ba831abb58ab828326770fdf3 Mon Sep 17 00:00:00 2001 From: Marc-Antoine Parent Date: Thu, 18 Dec 2025 11:37:59 -0500 Subject: [PATCH 1/5] ENG-1094 allow people to opt-out of postHog --- .../settings/HomePersonalSettings.tsx | 25 ++++++++++ apps/roam/src/data/userSettings.ts | 1 + apps/roam/src/index.ts | 47 ++----------------- apps/roam/src/utils/internalError.ts | 7 ++- 4 files changed, 36 insertions(+), 44 deletions(-) diff --git a/apps/roam/src/components/settings/HomePersonalSettings.tsx b/apps/roam/src/components/settings/HomePersonalSettings.tsx index 407e38667..34f75851c 100644 --- a/apps/roam/src/components/settings/HomePersonalSettings.tsx +++ b/apps/roam/src/components/settings/HomePersonalSettings.tsx @@ -20,7 +20,9 @@ import { DISCOURSE_CONTEXT_OVERLAY_IN_CANVAS_KEY, DISCOURSE_TOOL_SHORTCUT_KEY, STREAMLINE_STYLING_KEY, + DISALLOW_TRACKING, } from "~/data/userSettings"; +import { enablePostHog, disablePostHog } from "~/utils/posthog"; import KeyboardShortcutInput from "./KeyboardShortcutInput"; import { getSetting, setSetting } from "~/utils/extensionSettings"; import streamlineStyling from "~/styles/streamlineStyling"; @@ -263,6 +265,29 @@ const HomePersonalSettings = ({ onloadArgs }: { onloadArgs: OnloadArgs }) => { } /> + { + const target = e.target as HTMLInputElement; + const disallow = target.checked; + void setSetting(DISALLOW_TRACKING, disallow).catch(() => undefined); + if (disallow) { + disablePostHog(); + } else { + enablePostHog(); + } + }} + labelElement={ + <> + Disable tracking + + + } + /> ); }; diff --git a/apps/roam/src/data/userSettings.ts b/apps/roam/src/data/userSettings.ts index 24ea7e0ce..e67c9d93a 100644 --- a/apps/roam/src/data/userSettings.ts +++ b/apps/roam/src/data/userSettings.ts @@ -8,3 +8,4 @@ export const DISCOURSE_TOOL_SHORTCUT_KEY = "discourse-tool-shortcut"; export const DISCOURSE_CONTEXT_OVERLAY_IN_CANVAS_KEY = "discourse-context-overlay-in-canvas"; export const STREAMLINE_STYLING_KEY = "streamline-styling"; +export const DISALLOW_TRACKING = "disallow-tracking"; diff --git a/apps/roam/src/index.ts b/apps/roam/src/index.ts index dfe82b2ae..992c2718e 100644 --- a/apps/roam/src/index.ts +++ b/apps/roam/src/index.ts @@ -1,6 +1,5 @@ import { addStyle } from "roamjs-components/dom"; import { render as renderToast } from "roamjs-components/components/Toast"; -import getCurrentUserUid from "roamjs-components/queries/getCurrentUserUid"; import { runExtension } from "roamjs-components/util"; import { queryBuilderLoadedToast } from "./data/toastMessages"; import runQuery from "./utils/runQuery"; @@ -21,7 +20,6 @@ import discourseFloatingMenuStyles from "./styles/discourseFloatingMenuStyles.cs import settingsStyles from "./styles/settingsStyles.css"; import discourseGraphStyles from "./styles/discourseGraphStyles.css"; import streamlineStyling from "./styles/streamlineStyling"; -import posthog from "posthog-js"; import getDiscourseNodes from "./utils/getDiscourseNodes"; import { initFeedbackWidget } from "./components/BirdEatsBugs"; import { @@ -38,53 +36,16 @@ import getBasicTreeByParentUid from "roamjs-components/queries/getBasicTreeByPar import getPageUidByPageTitle from "roamjs-components/queries/getPageUidByPageTitle"; import { DISCOURSE_CONFIG_PAGE_TITLE } from "./utils/renderNodeConfigPage"; import { getSetting } from "./utils/extensionSettings"; -import { STREAMLINE_STYLING_KEY } from "./data/userSettings"; -import { getVersionWithDate } from "~/utils/getVersion"; - -const initPostHog = () => { - posthog.init("phc_SNMmBqwNfcEpNduQ41dBUjtGNEUEKAy6jTn63Fzsrax", { - api_host: "https://us.i.posthog.com", - person_profiles: "identified_only", - capture_pageview: false, - autocapture: false, - loaded: (posthog) => { - const { version, buildDate } = getVersionWithDate(); - const userUid = getCurrentUserUid(); - const graphName = window.roamAlphaAPI.graph.name; - posthog.identify(userUid, { - graphName, - }); - posthog.register({ - version: version || "-", - buildDate: buildDate || "-", - graphName, - }); - posthog.capture("Extension Loaded"); - }, - property_denylist: [ - "$ip", // Still seeing ip in the event - "$device_id", - "$geoip_city_name", - "$geoip_latitude", - "$geoip_longitude", - "$geoip_postal_code", - "$geoip_subdivision_1_name", - "$raw_user_agent", - "$current_url", - "$referrer", - "$referring_domain", - "$initial_current_url", - "$pathname", - ], - }); -}; +import { initPostHog } from "./utils/posthog"; +import { STREAMLINE_STYLING_KEY, DISALLOW_TRACKING } from "./data/userSettings"; export const DEFAULT_CANVAS_PAGE_FORMAT = "Canvas/*"; export default runExtension(async (onloadArgs) => { const isEncrypted = window.roamAlphaAPI.graph.isEncrypted; const isOffline = window.roamAlphaAPI.graph.type === "offline"; - if (!isEncrypted && !isOffline) { + const disallowTracking = getSetting(DISALLOW_TRACKING, false); + if (!isEncrypted && !isOffline && !disallowTracking) { initPostHog(); } diff --git a/apps/roam/src/utils/internalError.ts b/apps/roam/src/utils/internalError.ts index f74216d5b..c9e77920d 100644 --- a/apps/roam/src/utils/internalError.ts +++ b/apps/roam/src/utils/internalError.ts @@ -2,6 +2,8 @@ import posthog from "posthog-js"; import type { Properties } from "posthog-js"; import renderToast from "roamjs-components/components/Toast"; import sendErrorEmail from "~/utils/sendErrorEmail"; +import { getSetting } from "~/utils/extensionSettings"; +import { DISALLOW_TRACKING } from "~/data/userSettings"; const NON_WORD = /\W+/g; @@ -20,7 +22,10 @@ const internalError = ({ sendEmail?: boolean; forceSendInDev?: boolean; }): void => { - if (process.env.NODE_ENV === "development" && forceSendInDev !== true) { + if ( + getSetting(DISALLOW_TRACKING, false) || + (process.env.NODE_ENV === "development" && forceSendInDev !== true) + ) { console.error(error, context); } else { type = type || "Internal Error"; From 36d165df7d41bf96a1da692edf2dc60d5e9a6095 Mon Sep 17 00:00:00 2001 From: Marc-Antoine Parent Date: Thu, 18 Dec 2025 11:40:08 -0500 Subject: [PATCH 2/5] forgot to add --- apps/roam/src/utils/posthog.ts | 63 ++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 apps/roam/src/utils/posthog.ts diff --git a/apps/roam/src/utils/posthog.ts b/apps/roam/src/utils/posthog.ts new file mode 100644 index 000000000..68465a736 --- /dev/null +++ b/apps/roam/src/utils/posthog.ts @@ -0,0 +1,63 @@ +import getCurrentUserUid from "roamjs-components/queries/getCurrentUserUid"; +import { getVersionWithDate } from "./getVersion"; +import posthog from "posthog-js"; +import { getSetting } from "./extensionSettings"; +import { DISALLOW_TRACKING } from "~/data/userSettings"; + +let initialized = false; + +const doInitPostHog = () => { + if (initialized) return; + posthog.init("phc_SNMmBqwNfcEpNduQ41dBUjtGNEUEKAy6jTn63Fzsrax", { + api_host: "https://us.i.posthog.com", + person_profiles: "identified_only", + capture_pageview: false, + autocapture: false, + loaded: (posthog) => { + const { version, buildDate } = getVersionWithDate(); + const userUid = getCurrentUserUid(); + const graphName = window.roamAlphaAPI.graph.name; + posthog.identify(userUid, { + graphName, + }); + posthog.register({ + version: version || "-", + buildDate: buildDate || "-", + graphName, + }); + posthog.capture("Extension Loaded"); + initialized = true; + }, + property_denylist: [ + "$ip", // Still seeing ip in the event + "$device_id", + "$geoip_city_name", + "$geoip_latitude", + "$geoip_longitude", + "$geoip_postal_code", + "$geoip_subdivision_1_name", + "$raw_user_agent", + "$current_url", + "$referrer", + "$referring_domain", + "$initial_current_url", + "$pathname", + ], + }); +}; + +export const enablePostHog = () => { + doInitPostHog(); + posthog.opt_in_capturing(); +}; + +export const disablePostHog = () => { + if (initialized) posthog.opt_out_capturing(); +}; + +export const initPostHog = () => { + const disabled = getSetting(DISALLOW_TRACKING, false); + if (!disabled) { + doInitPostHog(); + } +}; From 140e76bdb8921a2510dea5d054b0973ed4b8c9a5 Mon Sep 17 00:00:00 2001 From: Marc-Antoine Parent Date: Thu, 18 Dec 2025 12:58:03 -0500 Subject: [PATCH 3/5] coderabbit comments --- .../settings/HomePersonalSettings.tsx | 22 +++++--- apps/roam/src/utils/posthog.ts | 52 ++++++++++++------- 2 files changed, 49 insertions(+), 25 deletions(-) diff --git a/apps/roam/src/components/settings/HomePersonalSettings.tsx b/apps/roam/src/components/settings/HomePersonalSettings.tsx index 34f75851c..0d01ab1c4 100644 --- a/apps/roam/src/components/settings/HomePersonalSettings.tsx +++ b/apps/roam/src/components/settings/HomePersonalSettings.tsx @@ -23,6 +23,7 @@ import { DISALLOW_TRACKING, } from "~/data/userSettings"; import { enablePostHog, disablePostHog } from "~/utils/posthog"; +import internalError from "~/utils/internalError"; import KeyboardShortcutInput from "./KeyboardShortcutInput"; import { getSetting, setSetting } from "~/utils/extensionSettings"; import streamlineStyling from "~/styles/streamlineStyling"; @@ -270,12 +271,21 @@ const HomePersonalSettings = ({ onloadArgs }: { onloadArgs: OnloadArgs }) => { onChange={(e) => { const target = e.target as HTMLInputElement; const disallow = target.checked; - void setSetting(DISALLOW_TRACKING, disallow).catch(() => undefined); - if (disallow) { - disablePostHog(); - } else { - enablePostHog(); - } + void setSetting(DISALLOW_TRACKING, disallow) + .catch((error) => { + target.checked = !disallow; + internalError({ + error, + userMessage: "Could not change settings", + }); + }) + .then(() => { + if (disallow) { + disablePostHog(); + } else { + enablePostHog(); + } + }); }} labelElement={ <> diff --git a/apps/roam/src/utils/posthog.ts b/apps/roam/src/utils/posthog.ts index 68465a736..0c5318b91 100644 --- a/apps/roam/src/utils/posthog.ts +++ b/apps/roam/src/utils/posthog.ts @@ -1,17 +1,46 @@ import getCurrentUserUid from "roamjs-components/queries/getCurrentUserUid"; import { getVersionWithDate } from "./getVersion"; import posthog from "posthog-js"; +import type { CaptureResult } from "posthog-js"; import { getSetting } from "./extensionSettings"; import { DISALLOW_TRACKING } from "~/data/userSettings"; let initialized = false; -const doInitPostHog = () => { +const doInitPostHog = (): void => { if (initialized) return; + /* eslint-disable @typescript-eslint/naming-convention */ + const property_denylist = [ + "$ip", + "$device_id", + "$geoip_city_name", + "$geoip_latitude", + "$geoip_longitude", + "$geoip_postal_code", + "$geoip_subdivision_1_name", + "$raw_user_agent", + "$current_url", + "$referrer", + "$referring_domain", + "$initial_current_url", + "$pathname", + ]; posthog.init("phc_SNMmBqwNfcEpNduQ41dBUjtGNEUEKAy6jTn63Fzsrax", { api_host: "https://us.i.posthog.com", person_profiles: "identified_only", capture_pageview: false, + property_denylist, + before_send: (result: CaptureResult | null) => { + if (result !== null) { + result.properties = Object.fromEntries( + Object.entries(result.properties).filter( + ([k]) => property_denylist.indexOf(k) < 0, + ), + ); + } + return result; + }, + /* eslint-enable @typescript-eslint/naming-convention */ autocapture: false, loaded: (posthog) => { const { version, buildDate } = getVersionWithDate(); @@ -28,34 +57,19 @@ const doInitPostHog = () => { posthog.capture("Extension Loaded"); initialized = true; }, - property_denylist: [ - "$ip", // Still seeing ip in the event - "$device_id", - "$geoip_city_name", - "$geoip_latitude", - "$geoip_longitude", - "$geoip_postal_code", - "$geoip_subdivision_1_name", - "$raw_user_agent", - "$current_url", - "$referrer", - "$referring_domain", - "$initial_current_url", - "$pathname", - ], }); }; -export const enablePostHog = () => { +export const enablePostHog = (): void => { doInitPostHog(); posthog.opt_in_capturing(); }; -export const disablePostHog = () => { +export const disablePostHog = (): void => { if (initialized) posthog.opt_out_capturing(); }; -export const initPostHog = () => { +export const initPostHog = (): void => { const disabled = getSetting(DISALLOW_TRACKING, false); if (!disabled) { doInitPostHog(); From 211e35bef59e9f61e8a5a2e8ec85bb5309ab5604 Mon Sep 17 00:00:00 2001 From: Marc-Antoine Parent Date: Thu, 18 Dec 2025 13:21:00 -0500 Subject: [PATCH 4/5] use a set for performance --- apps/roam/src/utils/posthog.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/roam/src/utils/posthog.ts b/apps/roam/src/utils/posthog.ts index 0c5318b91..1e88e2c14 100644 --- a/apps/roam/src/utils/posthog.ts +++ b/apps/roam/src/utils/posthog.ts @@ -9,8 +9,7 @@ let initialized = false; const doInitPostHog = (): void => { if (initialized) return; - /* eslint-disable @typescript-eslint/naming-convention */ - const property_denylist = [ + const propertyDenylist = new Set([ "$ip", "$device_id", "$geoip_city_name", @@ -24,17 +23,18 @@ const doInitPostHog = (): void => { "$referring_domain", "$initial_current_url", "$pathname", - ]; + ]); posthog.init("phc_SNMmBqwNfcEpNduQ41dBUjtGNEUEKAy6jTn63Fzsrax", { + /* eslint-disable @typescript-eslint/naming-convention */ api_host: "https://us.i.posthog.com", person_profiles: "identified_only", capture_pageview: false, - property_denylist, + property_denylist: [...propertyDenylist], before_send: (result: CaptureResult | null) => { if (result !== null) { result.properties = Object.fromEntries( Object.entries(result.properties).filter( - ([k]) => property_denylist.indexOf(k) < 0, + ([k]) => !propertyDenylist.has(k), ), ); } From 924b0395847235dfcb7ff46f52bb0ef503f2c3ba Mon Sep 17 00:00:00 2001 From: Marc-Antoine Parent Date: Thu, 18 Dec 2025 13:22:44 -0500 Subject: [PATCH 5/5] then/catch ordering --- .../components/settings/HomePersonalSettings.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/roam/src/components/settings/HomePersonalSettings.tsx b/apps/roam/src/components/settings/HomePersonalSettings.tsx index 0d01ab1c4..726c630dc 100644 --- a/apps/roam/src/components/settings/HomePersonalSettings.tsx +++ b/apps/roam/src/components/settings/HomePersonalSettings.tsx @@ -272,19 +272,19 @@ const HomePersonalSettings = ({ onloadArgs }: { onloadArgs: OnloadArgs }) => { const target = e.target as HTMLInputElement; const disallow = target.checked; void setSetting(DISALLOW_TRACKING, disallow) - .catch((error) => { - target.checked = !disallow; - internalError({ - error, - userMessage: "Could not change settings", - }); - }) .then(() => { if (disallow) { disablePostHog(); } else { enablePostHog(); } + }) + .catch((error) => { + target.checked = !disallow; + internalError({ + error, + userMessage: "Could not change settings", + }); }); }} labelElement={