From 67bdd50257b7eb19408a42b6fae85d99443e4b7f Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Fri, 30 Oct 2026 10:54:25 -0700 Subject: [PATCH] fix(extension): isolate embedded pages from tracking --- apps/sim/lib/consent/google-context.ts | 3 +- apps/sim/lib/consent/scripts.test.ts | 49 +++++++++++++++++++++++++- apps/sim/lib/consent/scripts.ts | 10 +++++- apps/sim/lib/navigation/paths.ts | 1 + 4 files changed, 60 insertions(+), 3 deletions(-) diff --git a/apps/sim/lib/consent/google-context.ts b/apps/sim/lib/consent/google-context.ts index a5c6e9d39eb..5097d774181 100644 --- a/apps/sim/lib/consent/google-context.ts +++ b/apps/sim/lib/consent/google-context.ts @@ -1,5 +1,5 @@ import { GOOGLE_CLICK_ID_PARAMETERS, UTM_PARAMETERS } from '@/lib/analytics/campaign-parameters' -import { isNoindexPath } from '@/lib/navigation/paths' +import { isNoindexPath, isPathOrDescendant } from '@/lib/navigation/paths' const GOOGLE_ANALYTICS_ORIGINS = new Set(['https://sim.ai', 'https://www.sim.ai']) @@ -51,6 +51,7 @@ export function getGooglePageLocation(value: string): string | undefined { try { const url = new URL(value) if (!GOOGLE_ANALYTICS_ORIGINS.has(url.origin)) return undefined + if (isPathOrDescendant(decodeURIComponent(url.pathname), '/extension')) return undefined const campaign = new URLSearchParams() for (const parameter of GOOGLE_CAMPAIGN_PARAMETERS) { diff --git a/apps/sim/lib/consent/scripts.test.ts b/apps/sim/lib/consent/scripts.test.ts index 460d203ab60..3ab83b4327e 100644 --- a/apps/sim/lib/consent/scripts.test.ts +++ b/apps/sim/lib/consent/scripts.test.ts @@ -8,7 +8,7 @@ import { trackGoogleEvent, trackGooglePageView, } from '@/lib/analytics/google' -import { getGooglePageLocation } from '@/lib/consent/google-context' +import { getGooglePageLocation, updateGooglePageContext } from '@/lib/consent/google-context' import { type ConsentScriptCallbackInfo, GOOGLE_ADS_ID, @@ -101,6 +101,53 @@ describe('consent scripts', () => { expect(getGooglePageLocation('https://www.sim.ai/library')).toBeUndefined() }) + it.each(['/extension', '/extension/o/org-id', '/%65xtension/workspace-id'])( + 'does not register third-party tracking scripts for %s', + (path) => { + window.history.replaceState({}, '', path) + + expect(getGlobalConsentScripts()).toEqual([]) + expect(getGooglePageLocation(window.location.href)).toBeUndefined() + } + ) + + it('stops Google events when navigating into the extension and resumes on public pages', () => { + window.history.replaceState({}, '', '/library') + getGlobalConsentScripts()[0]?.onBeforeLoad?.(CALLBACK_INFO) + window.dataLayer = [] + window.history.pushState({}, '', '/extension/o/private-org?utm_source=google') + + trackGooglePageView('/extension/o/private-org') + trackGoogleEvent('sign_up', { method: 'email' }) + trackGoogleAdsConversion('demo_booked') + + expect(window.dataLayer).toEqual([]) + + window.history.pushState({}, '', '/extension-guide') + trackGooglePageView('/extension-guide') + expect(window.dataLayer).toContainEqual([ + 'event', + 'page_view', + { + page_path: '/extension-guide', + page_location: 'https://www.sim.ai/extension-guide', + send_to: GOOGLE_ANALYTICS_ID, + }, + ]) + }) + + it('removes extension scope IDs from referrals to public pages', () => { + window.history.replaceState({}, '', '/library') + getGlobalConsentScripts()[0]?.onBeforeLoad?.(CALLBACK_INFO) + + updateGooglePageContext(window.location.href, 'https://www.sim.ai/extension/o/private-org') + + expect(window.dataLayer.at(-1)).toEqual([ + 'set', + expect.objectContaining({ page_referrer: 'https://www.sim.ai/extension' }), + ]) + }) + it('keeps supported campaign parameters and removes unknown parameters and fragments', () => { expect( getGooglePageLocation( diff --git a/apps/sim/lib/consent/scripts.ts b/apps/sim/lib/consent/scripts.ts index c84a73391b7..a75a4a7110a 100644 --- a/apps/sim/lib/consent/scripts.ts +++ b/apps/sim/lib/consent/scripts.ts @@ -3,6 +3,7 @@ import { gtag } from '@c15t/scripts/google-tag' import { xPixel } from '@c15t/scripts/x-pixel' import { FREEBUFF_TAG_SRC, installFreebuffStub } from '@/lib/analytics/freebuff' import { getGooglePageLocation, updateGooglePageContext } from '@/lib/consent/google-context' +import { isPathOrDescendant } from '@/lib/navigation/paths' export const GOOGLE_ANALYTICS_ID = 'G-QB9D67TBHR' as const @@ -67,8 +68,15 @@ const GLOBAL_CONSENT_SCRIPTS = [ }, ] as const -/** Registers Google's shared destination only on Sim's actual production origins. */ +/** Excludes embedded extension pages and restricts Google's loader to production origins. */ export function getGlobalConsentScripts() { + if (typeof window !== 'undefined') { + try { + if (isPathOrDescendant(decodeURIComponent(window.location.pathname), '/extension')) return [] + } catch { + return [] + } + } const googleEnabled = typeof window !== 'undefined' && getGooglePageLocation(window.location.href) !== undefined return GLOBAL_CONSENT_SCRIPTS.filter((script) => script.id !== 'gtag' || googleEnabled) diff --git a/apps/sim/lib/navigation/paths.ts b/apps/sim/lib/navigation/paths.ts index d1dcdf5038f..da7d526bbc0 100644 --- a/apps/sim/lib/navigation/paths.ts +++ b/apps/sim/lib/navigation/paths.ts @@ -81,6 +81,7 @@ export function isAppSurfacePath(pathname: string): boolean { */ const NOINDEX_PATH_ROOTS = [ '/chat', + '/extension', '/resume', '/invite', '/unsubscribe',