Skip to content

Commit cb02ba7

Browse files
committed
refactor(landing): fix invalid const assertion, trim comments
- Fixed HUBSPOT_SCRIPT_SRC/HUBSPOT_CONNECT_SRC: 'as const' cannot wrap a ternary expression directly (TS1355) — caught by a full project typecheck I ran specifically to verify this PR, not by lint/tests alone. Rewrote using the same conditional-spread-inside-array-literal pattern already used by every other array in this file (e.g. STATIC_FRAME_SRC) - Trimmed comments across all four touched files down to only the non-obvious 'why' (module-scope tracking flag, HubSpot CSP scoping rationale), matching this codebase's terse comment style elsewhere
1 parent 6807b4e commit cb02ba7

4 files changed

Lines changed: 27 additions & 52 deletions

File tree

apps/sim/app/(landing)/hubspot-page-view-tracker.tsx

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,14 @@ declare global {
99
}
1010
}
1111

12-
// next/script dedupes by id, so the HubSpot loader auto-tracks exactly one
13-
// pageview per browser session and never reloads on remount. Track that at
14-
// module scope so it survives LandingLayout unmounting and remounting (e.g.
15-
// leaving the landing site and navigating back).
12+
// next/script dedupes by id and never reloads on remount, so this must be
13+
// module-scope (not a ref) to survive LandingLayout unmounting/remounting.
1614
let hasTrackedInitialPageView = false
1715

1816
/**
19-
* The HubSpot loader auto-tracks only the very first page load. LandingLayout
20-
* persists across client-side navigations between landing routes (see its
21-
* TSDoc), so HubSpot never sees those route changes on its own — including
22-
* query-only navigations (blog/library pagination, careers filters), which
23-
* is why this depends on the full path + search string, not just the path.
24-
* Push a manual pageview through HubSpot's `_hsq` queue on every navigation
25-
* after the first.
17+
* The HubSpot loader only auto-tracks the first page load; LandingLayout
18+
* persists across client-side navigations, so HubSpot never sees the rest.
19+
* Pushes a manual pageview through `_hsq` on every navigation after the first.
2620
*/
2721
export function HubspotPageViewTracker() {
2822
const pathname = usePathname()

apps/sim/app/(landing)/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export default function LandingLayout({ children }: { children: ReactNode }) {
3333
return (
3434
<LandingShell>
3535
{children}
36-
{/* HubSpot tracking code — hosted marketing site only, not self-hosted deployments */}
36+
{/* HubSpot tracking — hosted only */}
3737
{isHosted && (
3838
<>
3939
<Script id='hs-script-loader' src={HUBSPOT_SCRIPT_SRC} strategy='afterInteractive' />

apps/sim/lib/core/security/csp.ts

Lines changed: 18 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -101,30 +101,24 @@ const STATIC_CONNECT_SRC = [
101101
] as const
102102

103103
/**
104-
* HubSpot tracking code sources. The loader is rendered only inside the
105-
* (landing) route group — never /workspace, /login, or /signup — so these
106-
* are kept out of STATIC_SCRIPT_SRC/STATIC_CONNECT_SRC (shared by every
107-
* route) and merged in only by the landing-scoped CSP builders below.
104+
* HubSpot tracking sources. The loader only renders inside the (landing)
105+
* route group, so these are kept out of STATIC_SCRIPT_SRC/STATIC_CONNECT_SRC
106+
* and merged in only by the landing-scoped CSP builders below.
108107
*/
109-
const HUBSPOT_SCRIPT_SRC = isHosted
110-
? [
111-
// The loader plus the analytics/form-tracking/banner scripts it
112-
// injects as <script> tags
113-
'https://*.hs-scripts.com',
114-
'https://*.hs-analytics.net',
115-
'https://*.hscollectedforms.net',
116-
'https://*.hs-banner.com',
117-
]
118-
: []
108+
const HUBSPOT_SCRIPT_SRC = [
109+
...(isHosted
110+
? [
111+
'https://*.hs-scripts.com',
112+
'https://*.hs-analytics.net',
113+
'https://*.hscollectedforms.net',
114+
'https://*.hs-banner.com',
115+
]
116+
: []),
117+
] as const
119118

120-
const HUBSPOT_CONNECT_SRC = isHosted
121-
? [
122-
// Form-tracking API calls (hscollectedforms.js) and the visitor
123-
// beacon (track.hubspot.com)
124-
'https://*.hscollectedforms.net',
125-
'https://*.hubspot.com',
126-
]
127-
: []
119+
const HUBSPOT_CONNECT_SRC = [
120+
...(isHosted ? ['https://*.hscollectedforms.net', 'https://*.hubspot.com'] : []),
121+
] as const
128122

129123
const STATIC_FRAME_SRC = [
130124
"'self'",
@@ -244,19 +238,15 @@ function buildRuntimeCSPDirectives(): CSPDirectives {
244238
}
245239

246240
/**
247-
* Generate runtime CSP header for authenticated app routes (/workspace,
248-
* /login, /signup) and other non-landing routes handled by proxy.ts.
241+
* Generate runtime CSP header for non-landing routes (proxy.ts).
249242
*/
250243
export function generateRuntimeCSP(): string {
251244
return buildCSPString(buildRuntimeCSPDirectives())
252245
}
253246

254247
/**
255248
* Generate runtime CSP header for the public marketing/landing site.
256-
* Extends the shared runtime policy with HubSpot tracking hosts — the
257-
* HubSpot loader is rendered only inside the (landing) route group, so
258-
* these hosts have no reason to be allowed on /workspace, /login, or
259-
* /signup.
249+
* Extends the shared runtime policy with HubSpot tracking hosts.
260250
*/
261251
export function generateLandingRuntimeCSP(): string {
262252
const directives = buildRuntimeCSPDirectives()

apps/sim/proxy.ts

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -117,13 +117,7 @@ function buildPreflightResponse(policy: CorsPolicy): NextResponse {
117117
return response
118118
}
119119

120-
/**
121-
* Top-level pages outside the (landing) route group that still reach the
122-
* catch-all branch below (auth sub-pages, invite links, file shares, the
123-
* playground). These get the tight app CSP rather than the landing-only
124-
* HubSpot allowance. Keep in sync with apps/sim/app/(auth)/**,
125-
* apps/sim/app/(interfaces)/**, and the other non-landing top-level routes.
126-
*/
120+
/** Top-level pages outside (landing) that still reach the catch-all branch below. */
127121
const NON_LANDING_PATH_PREFIXES = [
128122
'/verify',
129123
'/sso',
@@ -308,11 +302,8 @@ export async function proxy(request: NextRequest) {
308302
const securityBlock = handleSecurityFiltering(request)
309303
if (securityBlock) return track(request, securityBlock)
310304

311-
// Everything else falling through here is the public marketing/landing
312-
// site — except a handful of non-landing pages (auth sub-pages, invite
313-
// links, file shares, the playground) that reach this branch too. Only
314-
// the landing site renders the HubSpot loader, so only it gets the
315-
// landing-scoped CSP.
305+
// Mostly the public marketing/landing site, plus a few non-landing pages
306+
// (see isNonLandingPath) that also fall through to this branch.
316307
const response = NextResponse.next()
317308
response.headers.set('Vary', 'User-Agent')
318309

0 commit comments

Comments
 (0)