-
Notifications
You must be signed in to change notification settings - Fork 0
[SURF-1836] feat(tag): support custom-domain script attribute #72
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| import { | ||
| EXTERNAL_FORM_API, | ||
| LEAD_IDENTIFY_API, | ||
| SURFACE_DOMAINS, | ||
| USER_JOURNEY_TRACKING_API, | ||
| } from "./constants"; | ||
|
|
||
| export const CUSTOM_DOMAIN_ATTRIBUTE = "data-custom-domain"; | ||
|
|
||
| export interface SurfaceRuntimeConfig { | ||
| apiBaseUrl: string; | ||
| leadIdentifyApi: string; | ||
| userJourneyTrackingApi: string; | ||
| surfaceDomains: readonly string[]; | ||
| customOrigin: string | null; | ||
| } | ||
|
|
||
| export const DEFAULT_SURFACE_RUNTIME_CONFIG: SurfaceRuntimeConfig = { | ||
| apiBaseUrl: EXTERNAL_FORM_API, | ||
| leadIdentifyApi: LEAD_IDENTIFY_API, | ||
| userJourneyTrackingApi: USER_JOURNEY_TRACKING_API, | ||
| surfaceDomains: SURFACE_DOMAINS, | ||
| customOrigin: null, | ||
| }; | ||
|
|
||
| let runtimeConfig = DEFAULT_SURFACE_RUNTIME_CONFIG; | ||
|
|
||
| function normalizeCustomOrigin(value: string): string | null { | ||
| const trimmed = value.trim(); | ||
| if (!trimmed) return null; | ||
|
|
||
| try { | ||
| const url = new URL(trimmed.includes("://") ? trimmed : `https://${trimmed}`); | ||
| if ( | ||
| url.protocol !== "https:" || | ||
| url.username || | ||
| url.password || | ||
| url.pathname !== "/" || | ||
| url.search || | ||
| url.hash | ||
| ) { | ||
| return null; | ||
| } | ||
|
|
||
| return url.origin; | ||
| } catch { | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
| export function resolveSurfaceRuntimeConfig( | ||
| scriptElement: HTMLScriptElement | null | ||
| ): SurfaceRuntimeConfig { | ||
| const customOrigin = normalizeCustomOrigin( | ||
| scriptElement?.getAttribute(CUSTOM_DOMAIN_ATTRIBUTE) ?? "" | ||
| ); | ||
| if (!customOrigin) return DEFAULT_SURFACE_RUNTIME_CONFIG; | ||
|
|
||
| const apiBaseUrl = `${customOrigin}/api/v1`; | ||
| return { | ||
| apiBaseUrl, | ||
| leadIdentifyApi: `${apiBaseUrl}/lead/identify`, | ||
| userJourneyTrackingApi: `${apiBaseUrl}/lead/track`, | ||
| surfaceDomains: Array.from(new Set([...SURFACE_DOMAINS, customOrigin])), | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @greptile Any HTTPS data-custom-domain gets added to the postMessage allowlist here and has lead PII routed to it is the domain verified against the env's real custom domain somewhere, or do we just trust whatever the page passes? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's no server-side or environment-level verification — it's purely trusted from the page. This is a real attack surface to consider:
The practical risk depends on your threat model. If only your own team adds the A few mitigations to consider:
|
||
| customOrigin, | ||
| }; | ||
| } | ||
|
|
||
| export function initializeSurfaceRuntimeConfig( | ||
| scriptElement: HTMLScriptElement | null | ||
| ): SurfaceRuntimeConfig { | ||
| runtimeConfig = resolveSurfaceRuntimeConfig(scriptElement); | ||
| return runtimeConfig; | ||
| } | ||
|
|
||
| export function getSurfaceRuntimeConfig(): SurfaceRuntimeConfig { | ||
| return runtimeConfig; | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@greptile the session-cache key now includes the api base, so any key cached under the old format is orphaned after deploy, just a one-time refetch, or does anything rely on the old key??
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One-time refetch only. Nothing in the codebase reads or writes to the old key format after this deploy —
sessionStorageunder the old key (surface_open_triggers:<environmentId>) just becomes dead weight that expires when the tab closes. The new key (surface_open_triggers:<apiBaseUrl>:<environmentId>) will miss on first load, trigger a fresh fetch, and get written correctly.No correctness issue: the cache is purely a perf optimization (
CACHE_TTL_MS = 5 min), and the comments in the code explicitly treat it as best-effort. The worst case is one extra network call per tab on the first page load after deploy.Tip: You can customize Greptile's behavior for this repo with
.greptile/rules.mdand.greptile/config.json.