Files

57 lines
1.8 KiB
TypeScript

export type CookEventName
= | 'click_stuff' | 'click_tool' | 'click_dish'
| 'view_cook' | 'select_ingredients' | 'view_results'
| 'results_empty' | 'view_recipe_detail' | 'click_recipe_video'
interface ClarityConfig {
projectId: string
domains: string
}
type EventTracker = (name: CookEventName) => unknown
let eventTracker: EventTracker | undefined
/** Do not start a replay on login callbacks or URLs carrying credentials. */
export function isPrivateAnalyticsUrl(href: string) {
try {
const url = new URL(href)
const keys = [...url.searchParams.keys(), ...new URLSearchParams(url.hash.slice(1)).keys()]
return /^\/auth(?:\/|$)/.test(url.pathname)
|| keys.some(key => /^(?:ticket|code|state|token|access_token|refresh_token|id_token|password)$/i.test(key))
}
catch {
return true
}
}
/** Keep development, previews and native WebViews out of the production project. */
export function resolveClarityConfig(config: ClarityConfig, href: string, development: boolean) {
const projectId = config.projectId.trim()
if (development || !/^[a-z\d]{10,}$/i.test(projectId) || isPrivateAnalyticsUrl(href))
return null
try {
const page = new URL(href)
const domains = config.domains.split(',').map(domain => domain.trim().toLowerCase()).filter(Boolean)
if (!['https:', 'http:'].includes(page.protocol) || !domains.includes(page.hostname))
return null
return { projectId }
}
catch {
return null
}
}
export function setCookEventTracker(tracker: EventTracker) {
eventTracker = tracker
}
/** Clarity events carry fixed action names, never recipe names or account data. */
export function trackCookEvent(name: CookEventName) {
try {
void Promise.resolve(eventTracker?.(name)).catch(() => undefined)
}
catch {
// Analytics must not interrupt selections or recipe links.
}
}