@holdyapp/sdk 1.1.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -1
- package/dist/arm.d.ts +5 -0
- package/dist/arm.d.ts.map +1 -0
- package/dist/changes.d.ts +42 -0
- package/dist/changes.d.ts.map +1 -0
- package/dist/core.d.ts +17 -6
- package/dist/core.d.ts.map +1 -1
- package/dist/friction.d.ts +91 -0
- package/dist/friction.d.ts.map +1 -0
- package/dist/holdy.js +6 -1
- package/dist/holdy.js.map +4 -4
- package/dist/index.cjs +748 -26
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +748 -26
- package/dist/index.mjs.map +4 -4
- package/dist/run.d.ts +37 -0
- package/dist/run.d.ts.map +1 -0
- package/dist/server.cjs +127 -23
- package/dist/server.cjs.map +3 -3
- package/dist/server.d.ts +14 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.mjs +124 -22
- package/dist/server.mjs.map +3 -3
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/web.d.ts +18 -0
- package/dist/web.d.ts.map +1 -1
- package/dist/web.mjs +701 -26
- package/dist/web.mjs.map +4 -4
- package/package.json +1 -1
- package/src/arm.test.ts +47 -0
- package/src/arm.ts +25 -0
- package/src/changes.test.ts +54 -0
- package/src/changes.ts +357 -0
- package/src/core.test.ts +78 -4
- package/src/core.ts +95 -31
- package/src/friction.test.ts +223 -0
- package/src/friction.ts +419 -0
- package/src/index.ts +1 -0
- package/src/run.test.ts +47 -0
- package/src/run.ts +80 -0
- package/src/server.ts +17 -0
- package/src/types.ts +2 -0
- package/src/web.ts +52 -1
package/dist/holdy.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../src/browser.ts", "../src/core.ts", "../src/auto-capture.ts", "../src/web-context.ts", "../src/web.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Entrada do bundle IIFE \u00B7 global `Holdy` pra instala\u00E7\u00E3o por `<script>`.\n *\n * Existe pro cliente que n\u00E3o tem bundler (ou n\u00E3o quer mexer no build) poder\n * instrumentar colando duas linhas no HTML. A API \u00E9 a mesma de `@holdyapp/sdk/web`\n * em forma de fun\u00E7\u00F5es soltas, porque `Holdy.track(...)` \u00E9 o que se espera de um\n * global.\n *\n * Chamar antes de `init` n\u00E3o lan\u00E7a: telemetria n\u00E3o derruba a p\u00E1gina do cliente.\n */\n\nimport { HoldyWeb, type HoldyWebOptions } from './web'\nimport type { Properties } from './types'\n\nlet instance: HoldyWeb | null = null\n\nexport function init(opts: HoldyWebOptions): HoldyWeb {\n // Idempotente: script duplicado na p\u00E1gina n\u00E3o duplica evento.\n if (!instance) instance = new HoldyWeb(opts)\n return instance\n}\n\nexport function track(name: string, properties?: Properties): void {\n instance?.track(name, properties)\n}\n\nexport function identify(userId: string, traits?: Properties): void {\n instance?.identify(userId, traits)\n}\n\nexport function group(accountKey: string, traits?: Properties): void {\n instance?.group(accountKey, traits)\n}\n\nexport function page(name?: string, properties?: Properties): void {\n instance?.page(name, properties)\n}\n\nexport function reset(): void {\n instance?.reset()\n}\n\nexport async function flush(): Promise<void> {\n await instance?.flush()\n}\n\nexport async function shutdown(): Promise<void> {\n await instance?.shutdown()\n instance = null\n}\n\n/** Inst\u00E2ncia corrente \u00B7 \u00FAtil pra depurar no console do browser. */\nexport function getInstance(): HoldyWeb | null {\n return instance\n}\n", "/**\n * Core do @holdyapp/sdk \u00B7 fila + flush pra POST /api/v1/events/bulk.\n *\n * Retry de batch inteiro \u00E9 seguro (partial accept + dedupe server-side).\n * Fila \u00E9 bounded: acima de MAX_QUEUE dropa os mais antigos (telemetria nunca\n * derruba o app do cliente \u2014 nenhum m\u00E9todo p\u00FAblico lan\u00E7a).\n */\n\nimport type { CanonicalEvent, HoldyOptions, Properties, TrackOverrides } from './types'\n\nexport const DEFAULT_BASE_URL = 'https://holdy.chat/api/v1'\nconst DEFAULT_FLUSH_AT = 20\nconst DEFAULT_FLUSH_INTERVAL_MS = 5000\nconst MAX_QUEUE = 1000\nconst BULK_LIMIT = 500\n\nexport class HoldyCore {\n private readonly apiKey: string\n private readonly baseUrl: string\n private readonly flushAt: number\n private readonly debug: boolean\n private readonly source: 'sdk_web' | 'sdk_server'\n\n private queue: CanonicalEvent[] = []\n private timer: ReturnType<typeof setInterval> | null = null\n private flushing: Promise<void> | null = null\n\n protected userId: string | null = null\n protected anonymousId: string | null = null\n protected accountKey: string | null = null\n\n constructor(source: 'sdk_web' | 'sdk_server', opts: HoldyOptions) {\n this.source = source\n this.apiKey = opts.apiKey\n this.accountKey = opts.accountKey ?? null\n this.baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/$/, '')\n this.flushAt = Math.max(1, opts.flushAt ?? DEFAULT_FLUSH_AT)\n this.debug = opts.debug ?? false\n\n const intervalMs = opts.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS\n if (intervalMs > 0) {\n this.timer = setInterval(() => void this.flush(), intervalMs)\n // Node: n\u00E3o segura o processo vivo s\u00F3 pelo timer do SDK.\n if (typeof (this.timer as { unref?: () => void }).unref === 'function') {\n ;(this.timer as unknown as { unref: () => void }).unref()\n }\n }\n }\n\n track(name: string, properties?: Properties, overrides?: TrackOverrides): void {\n if (!name || typeof name !== 'string') return\n\n const userId = overrides?.userId ?? this.userId ?? undefined\n const accountKey = overrides?.accountKey ?? this.accountKey ?? undefined\n const occurredAt = overrides?.occurredAt\n ? new Date(overrides.occurredAt).toISOString()\n : new Date().toISOString()\n\n // Ordem: ambiente observado pelo SDK \u2192 context da chamada \u2192 carrier de\n // account. O que o caller passou vence o inferido; o carrier vence tudo,\n // porque \u00E9 o que a Data layer usa pra resolver a conta.\n const context: Properties = {\n ...this.environmentContext(),\n ...(overrides?.context ?? {}),\n }\n if (accountKey) context.account_external_id = accountKey\n\n const event: CanonicalEvent = {\n event: name,\n occurred_at: occurredAt,\n source: this.source,\n ...(userId ? { user_id: userId } : {}),\n ...(this.anonymousId ? { anonymous_id: this.anonymousId } : {}),\n properties: properties ?? {},\n ...(Object.keys(context).length > 0 ? { context } : {}),\n }\n\n this.queue.push(event)\n if (this.queue.length > MAX_QUEUE) this.queue.splice(0, this.queue.length - MAX_QUEUE)\n if (this.queue.length >= this.flushAt) void this.flush()\n }\n\n identify(userId: string, traits?: Properties): void {\n if (!userId || typeof userId !== 'string') return\n this.userId = userId\n this.track('$identify', traits ?? {})\n }\n\n group(accountKey: string, traits?: Properties): void {\n if (!accountKey || typeof accountKey !== 'string') return\n this.accountKey = accountKey\n this.track('$group', traits ?? {})\n }\n\n /** Drena a fila. Nunca lan\u00E7a \u2014 falha re-enfileira e loga em debug. */\n async flush(): Promise<void> {\n // Serializa flushes concorrentes (timer + flushAt + manual).\n if (this.flushing) return this.flushing\n if (this.queue.length === 0) return\n\n this.flushing = this.drain().finally(() => {\n this.flushing = null\n })\n return this.flushing\n }\n\n /** Flush final + para o timer. Chamar em shutdown do server / unload do web. */\n async shutdown(): Promise<void> {\n if (this.timer) {\n clearInterval(this.timer)\n this.timer = null\n }\n await this.flush()\n }\n\n private async drain(): Promise<void> {\n while (this.queue.length > 0) {\n const batch = this.queue.splice(0, BULK_LIMIT)\n const ok = await this.send(batch)\n if (!ok) {\n // Devolve pra frente da fila e para \u2014 pr\u00F3ximo flush re-tenta (dedupe-safe).\n this.queue.unshift(...batch)\n return\n }\n }\n }\n\n private async send(events: CanonicalEvent[]): Promise<boolean> {\n try {\n const res = await fetch(`${this.baseUrl}/events/bulk`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ events }),\n ...(this.source === 'sdk_web' ? { keepalive: true } : {}),\n })\n if (!res.ok) {\n // 4xx: batch estruturalmente inv\u00E1lido n\u00E3o re-tenta (evita loop infinito).\n if (res.status >= 400 && res.status < 500) {\n this.log(`bulk rejected ${res.status} \u00B7 batch dropped`)\n return true\n }\n this.log(`bulk failed ${res.status} \u00B7 will retry`)\n return false\n }\n return true\n } catch (err) {\n this.log(`bulk network error: ${(err as Error).message} \u00B7 will retry`)\n return false\n }\n }\n\n private log(msg: string): void {\n if (this.debug) console.warn(`[holdy] ${msg}`)\n }\n\n /**\n * Ambiente que o SDK observa sozinho (url, referrer, browser, utm\u2026).\n * Vazio no core; `HoldyWeb` preenche. O server n\u00E3o infere ambiente \u2014 n\u00E3o\n * existe \"p\u00E1gina atual\" num worker.\n */\n protected environmentContext(): Properties {\n return {}\n }\n\n /** Interno \u00B7 exposto pra testes. */\n get pending(): number {\n return this.queue.length\n }\n}\n", "/**\n * Auto-capture de navega\u00E7\u00E3o \u00B7 @holdyapp/sdk/web.\n *\n * Emite pageview no carregamento e a cada mudan\u00E7a de rota, inclusive nas que\n * n\u00E3o recarregam a p\u00E1gina (SPA). Sem isto, instrumentar exige que o cliente\n * chame `page()` em cada tela \u2014 e o primeiro dado s\u00F3 aparece depois de um\n * trabalho que ele ainda n\u00E3o fez.\n *\n * Idempotente: reinstalar \u00E9 no-op (guard global). O retorno desfaz o patch de\n * `history`, ent\u00E3o `destroy()` devolve o browser como estava.\n */\n\nconst INSTALLED_FLAG = '__holdy_autocapture__'\n\n/** pushState seguido de replaceState \u00E9 uma mudan\u00E7a l\u00F3gica s\u00F3. */\nconst ROUTE_DEBOUNCE_MS = 50\n\nexport type NavigationKind = 'initial_load' | 'route_change'\n\nexport interface AutoCaptureOptions {\n onNavigate: (kind: NavigationKind) => void\n}\n\ninterface GlobalWithFlag {\n [INSTALLED_FLAG]?: boolean\n}\n\nexport function installAutoCapture(opts: AutoCaptureOptions): () => void {\n if (typeof window === 'undefined' || typeof history === 'undefined') {\n return () => undefined\n }\n\n const w = window as unknown as GlobalWithFlag\n if (w[INSTALLED_FLAG]) return () => undefined\n w[INSTALLED_FLAG] = true\n\n const cleanups: Array<() => void> = []\n let lastPath = currentPath()\n let pending: ReturnType<typeof setTimeout> | null = null\n\n function emit(kind: NavigationKind): void {\n try {\n opts.onNavigate(kind)\n } catch {\n // Telemetria nunca derruba a app do cliente.\n }\n }\n\n function scheduleRouteEmit(): void {\n if (pending) clearTimeout(pending)\n pending = setTimeout(() => {\n pending = null\n const path = currentPath()\n // S\u00F3 conta como pageview se a rota mudou de fato \u2014 replaceState de\n // estado interno (filtro, scroll) n\u00E3o \u00E9 navega\u00E7\u00E3o.\n if (path === lastPath) return\n lastPath = path\n emit('route_change')\n }, ROUTE_DEBOUNCE_MS)\n }\n\n emit('initial_load')\n\n const originalPush = history.pushState\n const originalReplace = history.replaceState\n\n history.pushState = function patchedPush(...args: Parameters<History['pushState']>) {\n const result = originalPush.apply(this, args)\n scheduleRouteEmit()\n return result\n }\n history.replaceState = function patchedReplace(...args: Parameters<History['replaceState']>) {\n const result = originalReplace.apply(this, args)\n scheduleRouteEmit()\n return result\n }\n cleanups.push(() => {\n history.pushState = originalPush\n history.replaceState = originalReplace\n })\n\n // Voltar/avan\u00E7ar do browser e routers baseados em hash.\n const onPop = (): void => scheduleRouteEmit()\n window.addEventListener('popstate', onPop)\n cleanups.push(() => window.removeEventListener('popstate', onPop))\n\n const onHash = (): void => scheduleRouteEmit()\n window.addEventListener('hashchange', onHash)\n cleanups.push(() => window.removeEventListener('hashchange', onHash))\n\n return () => {\n if (pending) clearTimeout(pending)\n for (const cleanup of cleanups) cleanup()\n w[INSTALLED_FLAG] = false\n }\n}\n\n/** Rota compar\u00E1vel \u00B7 path + query, sem hash de \u00E2ncora. */\nfunction currentPath(): string {\n if (typeof location === 'undefined') return '/'\n return `${location.pathname}${location.search}${location.hash}`\n}\n", "/**\n * Contexto de ambiente do browser \u00B7 @holdyapp/sdk/web.\n *\n * Vai em `context` de todo evento, nunca em `properties`: properties \u00E9 o\n * vocabul\u00E1rio do produto do cliente (`workflow_key`, `plan`), context \u00E9 o que o\n * SDK observou sozinho. A Data layer persiste os dois (facts.context, 00247) e\n * a leitura de dimens\u00E3o consulta os dois.\n *\n * Tr\u00EAs decis\u00F5es que definem se esse dado \u00E9 \u00FAtil ou lixo:\n * \u00B7 `url` sai sem query e sem hash. Href cru tem uma varia\u00E7\u00E3o por sess\u00E3o, e\n * dimens\u00E3o com cardinalidade quase-\u00FAnica \u00E9 descartada na leitura \u2014 a\n * p\u00E1gina vira ru\u00EDdo exatamente por excesso de precis\u00E3o.\n * \u00B7 utm \u00E9 capturado na primeira p\u00E1gina e carregado pelo resto da sess\u00E3o.\n * Atribui\u00E7\u00E3o que s\u00F3 existe no primeiro evento n\u00E3o responde nada.\n * \u00B7 referrer s\u00F3 quando \u00E9 externo. Navega\u00E7\u00E3o interna preenchendo \"Origem do\n * tr\u00E1fego\" com o pr\u00F3prio dom\u00EDnio esconde a origem real.\n *\n * Tudo aqui \u00E9 best-effort: browser em modo privativo, SSR e webview sem\n * storage precisam funcionar. Nenhuma fun\u00E7\u00E3o lan\u00E7a.\n */\n\nexport type WebContext = Record<string, string>\n\nconst UTM_KEYS = [\n 'utm_source',\n 'utm_medium',\n 'utm_campaign',\n 'utm_term',\n 'utm_content',\n] as const\n\nconst SESSION_STORAGE_KEY = 'holdy_session'\nconst ATTRIBUTION_STORAGE_KEY = 'holdy_attribution'\n\n/* \u2500\u2500 URL \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n/** `https://app.x.com/a/b?q=1#z` \u2192 `https://app.x.com/a/b`. */\nexport function stripUrl(href: string): string | null {\n try {\n const url = new URL(href)\n return `${url.origin}${url.pathname}`\n } catch {\n return null\n }\n}\n\n/** Par\u00E2metros de campanha presentes na URL, j\u00E1 em lowercase. */\nexport function parseUtm(href: string): WebContext {\n const out: WebContext = {}\n try {\n const params = new URL(href).searchParams\n for (const key of UTM_KEYS) {\n const value = params.get(key)?.trim()\n if (value) out[key] = value.toLowerCase().slice(0, 120)\n }\n } catch {\n // href inv\u00E1lido \u2014 sem utm, sem drama.\n }\n return out\n}\n\n/* \u2500\u2500 User agent \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\ninterface UaParts {\n browser?: string\n os?: string\n device?: string\n}\n\n/**\n * Fatia m\u00EDnima do UA: navegador, sistema e classe de dispositivo.\n *\n * N\u00E3o \u00E9 biblioteca de UA parsing e n\u00E3o deve virar uma \u2014 o valor est\u00E1 em\n * separar mobile de desktop e Safari de Chrome, n\u00E3o em cravar a vers\u00E3o.\n */\nexport function parseUserAgent(ua: string): UaParts {\n if (!ua) return {}\n const s = ua.toLowerCase()\n\n const browser = s.includes('edg/')\n ? 'Edge'\n : s.includes('opr/') || s.includes('opera')\n ? 'Opera'\n : s.includes('samsungbrowser')\n ? 'Samsung Internet'\n : s.includes('firefox') || s.includes('fxios')\n ? 'Firefox'\n : s.includes('chrome') || s.includes('crios')\n ? 'Chrome'\n : s.includes('safari')\n ? 'Safari'\n : undefined\n\n const os = s.includes('iphone') || s.includes('ipad') || s.includes('ipod')\n ? 'iOS'\n : s.includes('android')\n ? 'Android'\n : s.includes('windows')\n ? 'Windows'\n : s.includes('mac os') || s.includes('macintosh')\n ? 'macOS'\n : s.includes('cros')\n ? 'ChromeOS'\n : s.includes('linux')\n ? 'Linux'\n : undefined\n\n // iPad moderno se declara macOS; a pista que sobra \u00E9 o suporte a toque, que\n // o parser puro n\u00E3o v\u00EA. Tablet aqui cobre o caso declarado no UA.\n const device = s.includes('ipad') || (s.includes('android') && !s.includes('mobile'))\n ? 'tablet'\n : s.includes('mobi') || s.includes('iphone') || s.includes('ipod')\n ? 'mobile'\n : 'desktop'\n\n return {\n ...(browser ? { browser } : {}),\n ...(os ? { os } : {}),\n device,\n }\n}\n\n/* \u2500\u2500 Storage \u00B7 nunca lan\u00E7a \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\nfunction readStorage(storage: 'session' | 'local', key: string): string | null {\n try {\n const s = storage === 'session' ? sessionStorage : localStorage\n return s.getItem(key)\n } catch {\n return null\n }\n}\n\nfunction writeStorage(storage: 'session' | 'local', key: string, value: string): void {\n try {\n const s = storage === 'session' ? sessionStorage : localStorage\n s.setItem(key, value)\n } catch {\n // Modo privativo / storage bloqueado \u2014 o contexto degrada, o evento segue.\n }\n}\n\nfunction randomId(): string {\n try {\n return crypto.randomUUID()\n } catch {\n return `hol-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`\n }\n}\n\n/** ID da sess\u00E3o de browser \u00B7 vive enquanto a aba viver. */\nexport function resolveSessionId(): string {\n const existing = readStorage('session', SESSION_STORAGE_KEY)\n if (existing) return existing\n const fresh = randomId()\n writeStorage('session', SESSION_STORAGE_KEY, fresh)\n return fresh\n}\n\n/**\n * Atribui\u00E7\u00E3o da sess\u00E3o: utm da primeira p\u00E1gina + referrer externo de entrada.\n * Gravada uma vez e repetida em todo evento da sess\u00E3o.\n */\nexport function resolveAttribution(href: string, referrer: string, currentHost: string): WebContext {\n const stored = readStorage('session', ATTRIBUTION_STORAGE_KEY)\n if (stored) {\n try {\n return JSON.parse(stored) as WebContext\n } catch {\n // Valor corrompido \u2014 recalcula por cima.\n }\n }\n\n const attribution: WebContext = { ...parseUtm(href) }\n\n const referrerHost = hostOf(referrer)\n // Referrer interno n\u00E3o \u00E9 origem de tr\u00E1fego, \u00E9 navega\u00E7\u00E3o.\n if (referrerHost && referrerHost !== currentHost) {\n const clean = stripUrl(referrer)\n if (clean) attribution.referrer = clean\n attribution.referrer_host = referrerHost\n }\n\n writeStorage('session', ATTRIBUTION_STORAGE_KEY, JSON.stringify(attribution))\n return attribution\n}\n\nfunction hostOf(href: string): string | null {\n try {\n return new URL(href).hostname.toLowerCase() || null\n } catch {\n return null\n }\n}\n\n/* \u2500\u2500 Montagem \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n/**\n * Contexto completo do evento atual. Fora do browser devolve `{}` \u2014 o SDK\n * server tem o pr\u00F3prio caminho e n\u00E3o deve inventar ambiente.\n */\nexport function buildWebContext(): WebContext {\n if (typeof window === 'undefined' || typeof location === 'undefined') return {}\n\n const href = location.href\n const referrer = typeof document !== 'undefined' ? document.referrer : ''\n const host = location.hostname.toLowerCase()\n\n const ua = typeof navigator !== 'undefined' ? navigator.userAgent : ''\n const { browser, os, device } = parseUserAgent(ua)\n\n const url = stripUrl(href)\n\n return {\n ...(url ? { url } : {}),\n path: location.pathname || '/',\n host,\n ...(browser ? { browser } : {}),\n ...(os ? { os } : {}),\n ...(device ? { device } : {}),\n ...(typeof navigator !== 'undefined' && navigator.language\n ? { locale: navigator.language }\n : {}),\n session_id: resolveSessionId(),\n ...resolveAttribution(href, referrer, host),\n }\n}\n", "/**\n * @holdyapp/sdk/web \u00B7 browser (source: sdk_web).\n *\n * anonymous_id gerado e persistido em localStorage (holdy_anon_id) \u2014 enviado em\n * todo event at\u00E9 identify() promover pra user_id (identity graph faz o merge\n * downstream, nunca o SDK). Flush no pagehide via fetch keepalive.\n *\n * Todo evento carrega o contexto de ambiente (url, path, referrer externo,\n * utm da sess\u00E3o, browser, os, device, locale, session_id) em `context` \u2014\n * separado das properties do produto. Pageview \u00E9 autom\u00E1tico por default:\n * carregamento inicial e cada mudan\u00E7a de rota, inclusive em SPA.\n */\n\nimport { HoldyCore } from './core'\nimport { installAutoCapture, type NavigationKind } from './auto-capture'\nimport { buildWebContext } from './web-context'\nimport type { HoldyOptions, Properties } from './types'\n\nconst ANON_STORAGE_KEY = 'holdy_anon_id'\n\nexport interface HoldyWebOptions extends HoldyOptions {\n /**\n * Emitir `$pageview` sozinho no load e a cada mudan\u00E7a de rota \u00B7 default true.\n * Desligue s\u00F3 se o produto j\u00E1 chama `page()` na m\u00E3o em cada tela.\n */\n autoCapture?: boolean\n}\n\nexport class HoldyWeb extends HoldyCore {\n private teardownAutoCapture: (() => void) | null = null\n\n constructor(opts: HoldyWebOptions) {\n super('sdk_web', opts)\n this.anonymousId = resolveAnonymousId()\n\n if (typeof window !== 'undefined') {\n window.addEventListener('pagehide', () => void this.flush())\n document.addEventListener('visibilitychange', () => {\n if (document.visibilityState === 'hidden') void this.flush()\n })\n }\n\n if (opts.autoCapture !== false) {\n this.teardownAutoCapture = installAutoCapture({\n onNavigate: (kind: NavigationKind) => this.page(undefined, { navigation: kind }),\n })\n }\n }\n\n /**\n * Pageview. O `$` marca evento emitido pelo SDK, n\u00E3o pelo produto (mesma\n * conven\u00E7\u00E3o de `$identify`/`$group`); a canonicaliza\u00E7\u00E3o downstream remove o\n * sigilo, ent\u00E3o `$pageview` e `pageview` s\u00E3o o mesmo evento na leitura.\n * url/path/referrer/utm j\u00E1 v\u00EAm no context de todo evento \u2014 aqui s\u00F3 entra o\n * que \u00E9 da p\u00E1gina como conte\u00FAdo (nome e t\u00EDtulo).\n */\n page(name?: string, properties?: Properties): void {\n this.track('$pageview', {\n ...(properties ?? {}),\n ...(name ? { page_name: name } : {}),\n ...(typeof document !== 'undefined' && document.title ? { title: document.title } : {}),\n })\n }\n\n reset(): void {\n this.userId = null\n this.accountKey = null\n try {\n localStorage.removeItem(ANON_STORAGE_KEY)\n } catch {\n // storage indispon\u00EDvel (SSR/privacy mode) \u2014 ok\n }\n this.anonymousId = resolveAnonymousId()\n }\n\n /** Flush final + desinstala o auto-capture (devolve `history` ao original). */\n override async shutdown(): Promise<void> {\n this.teardownAutoCapture?.()\n this.teardownAutoCapture = null\n await super.shutdown()\n }\n\n protected override environmentContext(): Properties {\n return buildWebContext()\n }\n}\n\nfunction resolveAnonymousId(): string {\n try {\n const existing = localStorage.getItem(ANON_STORAGE_KEY)\n if (existing) return existing\n const fresh = crypto.randomUUID()\n localStorage.setItem(ANON_STORAGE_KEY, fresh)\n return fresh\n } catch {\n return crypto.randomUUID()\n }\n}\n\nlet singleton: HoldyWeb | null = null\n\n/** Init idempotente \u2014 segunda chamada retorna a inst\u00E2ncia existente. */\nexport function initHoldy(opts: HoldyWebOptions): HoldyWeb {\n if (!singleton) {\n singleton = new HoldyWeb(opts)\n // Inst\u00E2ncia no window: track() inserido pelo `--instrument` do CLI n\u00E3o\n // precisa importar nada nem saber onde o init mora. Mesma superf\u00EDcie do\n // loader por script tag, ent\u00E3o o snippet \u00E9 um s\u00F3 nos dois caminhos.\n if (typeof window !== 'undefined') {\n const w = window as unknown as { Holdy?: HoldyWeb }\n if (!w.Holdy) w.Holdy = singleton\n }\n }\n return singleton\n}\n\nexport type { HoldyOptions, Properties }\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": ["browser_exports", "__export", "flush", "getInstance", "group", "identify", "init", "page", "reset", "shutdown", "track", "DEFAULT_BASE_URL", "HoldyCore", "source", "opts", "DEFAULT_BASE_URL", "intervalMs", "name", "properties", "overrides", "userId", "accountKey", "occurredAt", "context", "event", "traits", "batch", "events", "res", "err", "msg", "INSTALLED_FLAG", "installAutoCapture", "opts", "w", "INSTALLED_FLAG", "cleanups", "lastPath", "currentPath", "pending", "emit", "kind", "scheduleRouteEmit", "path", "originalPush", "originalReplace", "args", "result", "onPop", "onHash", "cleanup", "UTM_KEYS", "SESSION_STORAGE_KEY", "ATTRIBUTION_STORAGE_KEY", "stripUrl", "href", "url", "parseUtm", "out", "params", "key", "value", "parseUserAgent", "ua", "s", "browser", "os", "device", "readStorage", "storage", "writeStorage", "randomId", "resolveSessionId", "existing", "fresh", "resolveAttribution", "referrer", "currentHost", "stored", "attribution", "referrerHost", "hostOf", "clean", "buildWebContext", "host", "ANON_STORAGE_KEY", "HoldyWeb", "HoldyCore", "opts", "resolveAnonymousId", "installAutoCapture", "kind", "name", "properties", "buildWebContext", "existing", "fresh", "instance", "init", "opts", "HoldyWeb", "track", "name", "properties", "identify", "userId", "traits", "group", "accountKey", "page", "reset", "flush", "shutdown", "getInstance"]
|
|
3
|
+
"sources": ["../src/browser.ts", "../src/core.ts", "../src/auto-capture.ts", "../src/friction.ts", "../src/web-context.ts", "../src/arm.ts", "../src/changes.ts", "../src/web.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Entrada do bundle IIFE \u00B7 global `Holdy` pra instala\u00E7\u00E3o por `<script>`.\n *\n * Existe pro cliente que n\u00E3o tem bundler (ou n\u00E3o quer mexer no build) poder\n * instrumentar colando duas linhas no HTML. A API \u00E9 a mesma de `@holdyapp/sdk/web`\n * em forma de fun\u00E7\u00F5es soltas, porque `Holdy.track(...)` \u00E9 o que se espera de um\n * global.\n *\n * Chamar antes de `init` n\u00E3o lan\u00E7a: telemetria n\u00E3o derruba a p\u00E1gina do cliente.\n */\n\nimport { HoldyWeb, type HoldyWebOptions } from './web'\nimport type { Properties } from './types'\n\nlet instance: HoldyWeb | null = null\n\nexport function init(opts: HoldyWebOptions): HoldyWeb {\n // Idempotente: script duplicado na p\u00E1gina n\u00E3o duplica evento.\n if (!instance) instance = new HoldyWeb(opts)\n return instance\n}\n\nexport function track(name: string, properties?: Properties): void {\n instance?.track(name, properties)\n}\n\nexport function identify(userId: string, traits?: Properties): void {\n instance?.identify(userId, traits)\n}\n\nexport function group(accountKey: string, traits?: Properties): void {\n instance?.group(accountKey, traits)\n}\n\nexport function page(name?: string, properties?: Properties): void {\n instance?.page(name, properties)\n}\n\nexport function reset(): void {\n instance?.reset()\n}\n\nexport async function flush(): Promise<void> {\n await instance?.flush()\n}\n\nexport async function shutdown(): Promise<void> {\n await instance?.shutdown()\n instance = null\n}\n\n/** Inst\u00E2ncia corrente \u00B7 \u00FAtil pra depurar no console do browser. */\nexport function getInstance(): HoldyWeb | null {\n return instance\n}\n", "/**\n * Core do @holdyapp/sdk \u00B7 fila + flush pra POST /api/v1/events/bulk.\n *\n * Todo evento sai com `external_id` pr\u00F3prio, ent\u00E3o reenviar um lote nunca\n * duplica (dedupe server-side por source + external_id + occurred_at).\n * 429 respeita Retry-After; 5xx e rede esperam com backoff exponencial; o lote\n * volta pra frente da fila em vez de sumir. Chave inv\u00E1lida ou origem recusada\n * avisam uma vez no console, mesmo sem debug. Fila \u00E9 bounded: acima de\n * MAX_QUEUE dropa os mais antigos (telemetria nunca derruba o app do cliente \u2014\n * nenhum m\u00E9todo p\u00FAblico lan\u00E7a).\n */\n\nimport type { CanonicalEvent, HoldyOptions, Properties, TrackOverrides } from './types'\n\nexport const DEFAULT_BASE_URL = 'https://holdy.chat/api/v1'\nconst DEFAULT_FLUSH_AT = 20\nconst DEFAULT_FLUSH_INTERVAL_MS = 5000\nconst MAX_QUEUE = 1000\nconst BULK_LIMIT = 500\nconst MAX_BACKOFF_MS = 60_000\n\ntype SendOutcome = { kind: 'sent' } | { kind: 'dropped' } | { kind: 'retry'; afterMs: number | null }\n\nexport class HoldyCore {\n protected readonly apiKey: string\n protected readonly baseUrl: string\n private readonly flushAt: number\n private readonly debug: boolean\n private readonly source: 'sdk_web' | 'sdk_server'\n\n private queue: CanonicalEvent[] = []\n private timer: ReturnType<typeof setInterval> | null = null\n private flushing: Promise<void> | null = null\n private retryAt = 0\n private failures = 0\n private readonly warned = new Set<string>()\n\n protected userId: string | null = null\n protected anonymousId: string | null = null\n protected accountKey: string | null = null\n\n constructor(source: 'sdk_web' | 'sdk_server', opts: HoldyOptions) {\n this.source = source\n this.apiKey = opts.apiKey\n this.accountKey = opts.accountKey ?? null\n this.baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/$/, '')\n this.flushAt = Math.max(1, opts.flushAt ?? DEFAULT_FLUSH_AT)\n this.debug = opts.debug ?? false\n\n const intervalMs = opts.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS\n if (intervalMs > 0) {\n this.timer = setInterval(() => void this.flush(), intervalMs)\n // Node: n\u00E3o segura o processo vivo s\u00F3 pelo timer do SDK.\n if (typeof (this.timer as { unref?: () => void }).unref === 'function') {\n ;(this.timer as unknown as { unref: () => void }).unref()\n }\n }\n }\n\n track(name: string, properties?: Properties, overrides?: TrackOverrides): void {\n if (!name || typeof name !== 'string') return\n\n const userId = overrides?.userId ?? this.userId ?? undefined\n const accountKey = overrides?.accountKey ?? this.accountKey ?? undefined\n const occurredAt = overrides?.occurredAt\n ? new Date(overrides.occurredAt).toISOString()\n : new Date().toISOString()\n\n // Ordem: ambiente observado pelo SDK \u2192 context da chamada \u2192 carrier de\n // account. O que o caller passou vence o inferido; o carrier vence tudo,\n // porque \u00E9 o que a Data layer usa pra resolver a conta.\n const context: Properties = {\n ...this.environmentContext(),\n ...(overrides?.context ?? {}),\n }\n if (accountKey) context.account_external_id = accountKey\n\n const event: CanonicalEvent = {\n event: name,\n occurred_at: occurredAt,\n source: this.source,\n external_id: newEventId(),\n ...(userId ? { user_id: userId } : {}),\n ...(this.anonymousId ? { anonymous_id: this.anonymousId } : {}),\n properties: properties ?? {},\n ...(Object.keys(context).length > 0 ? { context } : {}),\n }\n\n this.queue.push(event)\n this.trimQueue()\n if (this.queue.length >= this.flushAt) void this.flush()\n }\n\n identify(userId: string, traits?: Properties): void {\n if (!userId || typeof userId !== 'string') return\n this.userId = userId\n this.track('$identify', traits ?? {})\n }\n\n group(accountKey: string, traits?: Properties): void {\n if (!accountKey || typeof accountKey !== 'string') return\n this.accountKey = accountKey\n this.track('$group', traits ?? {})\n }\n\n /** Drena a fila. Nunca lan\u00E7a \u2014 falha re-enfileira e espera antes de tentar de novo. */\n async flush(): Promise<void> {\n return this.startFlush(false)\n }\n\n /** Flush final + para o timer. Chamar em shutdown do server / unload do web. */\n async shutdown(): Promise<void> {\n if (this.timer) {\n clearInterval(this.timer)\n this.timer = null\n }\n await this.startFlush(true)\n }\n\n private startFlush(force: boolean): Promise<void> {\n // Serializa flushes concorrentes (timer + flushAt + manual).\n if (this.flushing) return this.flushing\n if (this.queue.length === 0) return Promise.resolve()\n if (!force && Date.now() < this.retryAt) return Promise.resolve()\n\n this.flushing = this.drain().finally(() => {\n this.flushing = null\n })\n return this.flushing\n }\n\n private async drain(): Promise<void> {\n while (this.queue.length > 0) {\n const batch = this.queue.splice(0, BULK_LIMIT)\n const outcome = await this.send(batch)\n if (outcome.kind === 'retry') {\n this.queue.unshift(...batch)\n this.trimQueue()\n this.scheduleRetry(outcome.afterMs)\n return\n }\n this.failures = 0\n this.retryAt = 0\n }\n }\n\n private scheduleRetry(afterMs: number | null): void {\n this.failures += 1\n if (afterMs !== null) {\n this.retryAt = Date.now() + afterMs\n return\n }\n const backoff = Math.min(MAX_BACKOFF_MS, 1000 * 2 ** (this.failures - 1))\n this.retryAt = Date.now() + backoff + Math.floor(Math.random() * backoff * 0.2)\n }\n\n private trimQueue(): void {\n if (this.queue.length > MAX_QUEUE) this.queue.splice(0, this.queue.length - MAX_QUEUE)\n }\n\n private async send(events: CanonicalEvent[]): Promise<SendOutcome> {\n try {\n const res = await fetch(`${this.baseUrl}/events/bulk`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ events }),\n ...(this.source === 'sdk_web' ? { keepalive: true } : {}),\n })\n if (res.ok) return { kind: 'sent' }\n\n if (res.status === 429) {\n this.log('rate limited \u00B7 will retry after Retry-After')\n return { kind: 'retry', afterMs: retryAfterMs(res) }\n }\n if (res.status >= 500) {\n this.log(`bulk failed ${res.status} \u00B7 will retry with backoff`)\n return { kind: 'retry', afterMs: null }\n }\n if (res.status === 401) {\n this.warnOnce('401', 'API key inv\u00E1lida ou revogada \u00B7 eventos descartados. Confira a chave em Configura\u00E7\u00F5es \u203A SDK.')\n return { kind: 'dropped' }\n }\n if (res.status === 403) {\n this.warnOnce('403', 'Origem n\u00E3o permitida ou chave sem escopo events:write \u00B7 eventos descartados.')\n return { kind: 'dropped' }\n }\n this.log(`bulk rejected ${res.status} \u00B7 batch dropped`)\n return { kind: 'dropped' }\n } catch (err) {\n this.log(`bulk network error: ${(err as Error).message} \u00B7 will retry with backoff`)\n return { kind: 'retry', afterMs: null }\n }\n }\n\n private log(msg: string): void {\n if (this.debug) console.warn(`[holdy] ${msg}`)\n }\n\n private warnOnce(key: string, msg: string): void {\n if (this.warned.has(key)) return\n this.warned.add(key)\n console.warn(`[holdy] ${msg}`)\n }\n\n /**\n * Ambiente que o SDK observa sozinho (url, referrer, browser, utm\u2026).\n * Vazio no core; `HoldyWeb` preenche. O server n\u00E3o infere ambiente \u2014 n\u00E3o\n * existe \"p\u00E1gina atual\" num worker.\n */\n protected environmentContext(): Properties {\n return {}\n }\n\n /** Interno \u00B7 exposto pra testes. */\n get pending(): number {\n return this.queue.length\n }\n}\n\nfunction retryAfterMs(res: Response): number | null {\n const raw = typeof res.headers?.get === 'function' ? res.headers.get('Retry-After') : null\n if (!raw) return null\n const seconds = Number(raw)\n if (!Number.isFinite(seconds) || seconds < 0) return null\n return Math.min(MAX_BACKOFF_MS, seconds * 1000)\n}\n\nfunction newEventId(): string {\n const c = (globalThis as { crypto?: { randomUUID?: () => string } }).crypto\n if (typeof c?.randomUUID === 'function') return c.randomUUID()\n const rand = () => Math.random().toString(36).slice(2, 12)\n return `${Date.now().toString(36)}-${rand()}-${rand()}`\n}\n", "/**\n * Auto-capture de navega\u00E7\u00E3o \u00B7 @holdyapp/sdk/web.\n *\n * Emite pageview no carregamento e a cada mudan\u00E7a de rota, inclusive nas que\n * n\u00E3o recarregam a p\u00E1gina (SPA). Sem isto, instrumentar exige que o cliente\n * chame `page()` em cada tela \u2014 e o primeiro dado s\u00F3 aparece depois de um\n * trabalho que ele ainda n\u00E3o fez.\n *\n * Idempotente: reinstalar \u00E9 no-op (guard global). O retorno desfaz o patch de\n * `history`, ent\u00E3o `destroy()` devolve o browser como estava.\n */\n\nconst INSTALLED_FLAG = '__holdy_autocapture__'\n\n/** pushState seguido de replaceState \u00E9 uma mudan\u00E7a l\u00F3gica s\u00F3. */\nconst ROUTE_DEBOUNCE_MS = 50\n\nexport type NavigationKind = 'initial_load' | 'route_change'\n\nexport interface AutoCaptureOptions {\n onNavigate: (kind: NavigationKind) => void\n}\n\ninterface GlobalWithFlag {\n [INSTALLED_FLAG]?: boolean\n}\n\nexport function installAutoCapture(opts: AutoCaptureOptions): () => void {\n if (typeof window === 'undefined' || typeof history === 'undefined') {\n return () => undefined\n }\n\n const w = window as unknown as GlobalWithFlag\n if (w[INSTALLED_FLAG]) return () => undefined\n w[INSTALLED_FLAG] = true\n\n const cleanups: Array<() => void> = []\n let lastPath = currentPath()\n let pending: ReturnType<typeof setTimeout> | null = null\n\n function emit(kind: NavigationKind): void {\n try {\n opts.onNavigate(kind)\n } catch {\n // Telemetria nunca derruba a app do cliente.\n }\n }\n\n function scheduleRouteEmit(): void {\n if (pending) clearTimeout(pending)\n pending = setTimeout(() => {\n pending = null\n const path = currentPath()\n // S\u00F3 conta como pageview se a rota mudou de fato \u2014 replaceState de\n // estado interno (filtro, scroll) n\u00E3o \u00E9 navega\u00E7\u00E3o.\n if (path === lastPath) return\n lastPath = path\n emit('route_change')\n }, ROUTE_DEBOUNCE_MS)\n }\n\n emit('initial_load')\n\n const originalPush = history.pushState\n const originalReplace = history.replaceState\n\n history.pushState = function patchedPush(...args: Parameters<History['pushState']>) {\n const result = originalPush.apply(this, args)\n scheduleRouteEmit()\n return result\n }\n history.replaceState = function patchedReplace(...args: Parameters<History['replaceState']>) {\n const result = originalReplace.apply(this, args)\n scheduleRouteEmit()\n return result\n }\n cleanups.push(() => {\n history.pushState = originalPush\n history.replaceState = originalReplace\n })\n\n // Voltar/avan\u00E7ar do browser e routers baseados em hash.\n const onPop = (): void => scheduleRouteEmit()\n window.addEventListener('popstate', onPop)\n cleanups.push(() => window.removeEventListener('popstate', onPop))\n\n const onHash = (): void => scheduleRouteEmit()\n window.addEventListener('hashchange', onHash)\n cleanups.push(() => window.removeEventListener('hashchange', onHash))\n\n return () => {\n if (pending) clearTimeout(pending)\n for (const cleanup of cleanups) cleanup()\n w[INSTALLED_FLAG] = false\n }\n}\n\n/** Rota compar\u00E1vel \u00B7 path + query, sem hash de \u00E2ncora. */\nfunction currentPath(): string {\n if (typeof location === 'undefined') return '/'\n return `${location.pathname}${location.search}${location.hash}`\n}\n", "export type FrictionKind =\n | 'dead_click'\n | 'rage_click'\n | 'form_abandon'\n | 'js_error'\n | 'slow_request'\n | 'nav_loop'\n | 'http_error'\n\nexport interface FrictionSignal {\n kind: FrictionKind\n properties: Record<string, unknown>\n}\n\nexport interface FrictionCaptureOptions {\n onFriction: (signal: FrictionSignal) => void\n slowRequestMs?: number\n rageClicks?: number\n rageWindowMs?: number\n rageRadiusPx?: number\n navLoopWindowMs?: number\n ignoreUrls?: string[]\n /**\n * Origens que contam como o pr\u00F3prio produto al\u00E9m do dom\u00EDnio da p\u00E1gina \u2014\n * pro caso em que a API mora em outro dom\u00EDnio (`acme-api.io` servindo\n * `acme.com`). O subdom\u00EDnio j\u00E1 entra sozinho.\n */\n apiOrigins?: string[]\n}\n\nexport interface FrictionCapture {\n onNavigate: (path: string) => void\n teardown: () => void\n}\n\nconst INSTALLED_FLAG = '__holdy_friction__'\nconst INTERACTIVE_SELECTOR =\n 'button, a, input, select, textarea, summary, [role=\"button\"], [role=\"link\"], [role=\"menuitem\"], [role=\"tab\"]'\nconst INTERACTIVE_TAGS = new Set(['button', 'a', 'input', 'select', 'textarea', 'summary'])\nconst FORM_CONTROL_TAGS = new Set(['input', 'select', 'textarea'])\n\nexport class RageClickDetector {\n private clicks: Array<{ x: number; y: number; t: number }> = []\n\n constructor(\n private readonly threshold = 3,\n private readonly windowMs = 1000,\n private readonly radiusPx = 30,\n ) {}\n\n push(x: number, y: number, t: number): boolean {\n this.clicks = this.clicks.filter(\n (c) => t - c.t <= this.windowMs && Math.hypot(c.x - x, c.y - y) <= this.radiusPx,\n )\n this.clicks.push({ x, y, t })\n if (this.clicks.length >= this.threshold) {\n this.clicks = []\n return true\n }\n return false\n }\n}\n\nexport class NavLoopDetector {\n private visits: Array<{ path: string; t: number }> = []\n\n constructor(private readonly windowMs = 60_000) {}\n\n push(path: string, t: number): { from: string; to: string } | null {\n this.visits = [...this.visits, { path, t }].filter((v) => t - v.t <= this.windowMs).slice(-4)\n if (this.visits.length < 4) return null\n const [a, b, c, d] = this.visits.map((v) => v.path)\n if (a === c && b === d && a !== b) {\n this.visits = []\n return { from: a, to: b }\n }\n return null\n }\n}\n\nexport interface ElementLike {\n tagName: string\n id?: string\n disabled?: boolean\n textContent?: string | null\n getAttribute(name: string): string | null\n closest?(selector: string): ElementLike | null\n}\n\nexport function resolveInteractive(el: ElementLike | null): ElementLike | null {\n if (!el) return null\n const viaClosest = el.closest?.(INTERACTIVE_SELECTOR) ?? null\n if (viaClosest) return viaClosest\n const tag = el.tagName.toLowerCase()\n if (INTERACTIVE_TAGS.has(tag)) return el\n const role = el.getAttribute('role')\n if (role === 'button' || role === 'link' || role === 'menuitem' || role === 'tab') return el\n return null\n}\n\nexport function isDeadClickTarget(el: ElementLike | null): boolean {\n const target = resolveInteractive(el)\n if (!target) return false\n if (target.disabled === true) return true\n if (target.getAttribute('disabled') !== null) return true\n if (target.getAttribute('aria-disabled') === 'true') return true\n const tag = target.tagName.toLowerCase()\n if (tag === 'a') {\n const href = target.getAttribute('href')\n if (!href || href === '#' || href.startsWith('javascript:')) return true\n }\n return false\n}\n\nexport function describeElement(el: ElementLike | null): Record<string, string> {\n const target = resolveInteractive(el) ?? el\n if (!target) return {}\n const out: Record<string, string> = { element: target.tagName.toLowerCase() }\n if (target.id) out.element_id = target.id\n const role = target.getAttribute('role')\n if (role) out.element_role = role\n const label = target.getAttribute('aria-label') ?? target.textContent ?? ''\n const text = label.replace(/\\s+/g, ' ').trim().slice(0, 60)\n if (text) out.element_text = text\n const name = target.getAttribute('name') ?? target.getAttribute('data-testid')\n if (name) out.element_name = name\n return out\n}\n\nexport function stripRequestUrl(input: string, base?: string): string {\n try {\n const url = new URL(input, base)\n return `${url.origin}${url.pathname}`\n } catch {\n return input.split('?')[0].split('#')[0]\n }\n}\n\nexport function errorMessage(reason: unknown): string {\n if (reason instanceof Error) return reason.message\n if (typeof reason === 'string') return reason\n try {\n return JSON.stringify(reason)\n } catch {\n return String(reason)\n }\n}\n\nexport function isAbortError(err: unknown): boolean {\n return typeof err === 'object' && err !== null && (err as { name?: unknown }).name === 'AbortError'\n}\n\nexport function isIgnoredUrl(url: string, ignore: readonly string[]): boolean {\n return ignore.some((prefix) => prefix.length > 0 && url.startsWith(prefix))\n}\n\n/**\n * Dom\u00EDnio registr\u00E1vel aproximado \u2014 os dois \u00FAltimos r\u00F3tulos do host.\n *\n * `app.acme.com` e `api.acme.com` \u2192 `acme.com`: produto que fala com a pr\u00F3pria\n * API em outro subdom\u00EDnio continua sendo primeira parte. Sem lista de sufixos\n * p\u00FAblicos, `foo.co.uk` e `bar.co.uk` colidem \u2014 e a colis\u00E3o erra pro lado\n * seguro, incluindo como pr\u00F3prio o que hoje j\u00E1 \u00E9 inclu\u00EDdo.\n */\nexport function registrableDomain(host: string): string {\n const labels = host.toLowerCase().split('.').filter(Boolean)\n if (labels.length <= 2) return labels.join('.')\n return labels.slice(-2).join('.')\n}\n\n/**\n * A requisi\u00E7\u00E3o \u00E9 do produto de quem instalou, ou de um terceiro?\n *\n * Fric\u00E7\u00E3o \u00E9 fric\u00E7\u00E3o DO PRODUTO. Um `google-analytics.com/g/collect` morto por\n * ad blocker n\u00E3o \u00E9 o produto falhando \u2014 \u00E9 o navegador do visitante recusando\n * um script que nem \u00E9 seu. Em 2026-09-17 esses dois dom\u00EDnios respondiam por 36\n * dos eventos de fric\u00E7\u00E3o da janela: num cliente com GA instalado e usu\u00E1rios com\n * bloqueador, isso infla a fric\u00E7\u00E3o e pode abrir oportunidade em cima de nada.\n *\n * Localhost e IP entram pela compara\u00E7\u00E3o de host exato \u2014 em desenvolvimento n\u00E3o\n * h\u00E1 dom\u00EDnio registr\u00E1vel que valha.\n */\nexport function isFirstPartyRequest(url: string, pageUrl: string, extraOrigins: readonly string[] = []): boolean {\n let target: URL\n let page: URL\n try {\n target = new URL(url, pageUrl)\n page = new URL(pageUrl)\n } catch {\n return true\n }\n if (target.protocol !== 'http:' && target.protocol !== 'https:') return true\n if (target.host === page.host) return true\n if (extraOrigins.some((o) => o.length > 0 && target.origin === o)) return true\n const targetDomain = registrableDomain(target.hostname)\n return targetDomain.length > 0 && targetDomain === registrableDomain(page.hostname)\n}\n\nexport interface RequestOutcome {\n status: number | null\n durationMs: number\n slowMs: number\n aborted?: boolean\n opaque?: boolean\n}\n\nexport function classifyRequest(outcome: RequestOutcome): 'http_error' | 'slow_request' | null {\n if (outcome.aborted) return null\n if (!outcome.opaque && (outcome.status === null || outcome.status >= 400)) return 'http_error'\n return outcome.durationMs >= outcome.slowMs ? 'slow_request' : null\n}\n\nexport function documentStatus(\n perf: { getEntriesByType?: (type: string) => unknown[] } | undefined,\n): number | null {\n try {\n const entry = perf?.getEntriesByType?.('navigation')?.[0] as { responseStatus?: unknown } | undefined\n const status = entry?.responseStatus\n return typeof status === 'number' && status > 0 ? status : null\n } catch {\n return null\n }\n}\n\ninterface FormTouch {\n form: string\n fields: Set<string>\n}\n\nfunction formKey(form: HTMLFormElement): string {\n return (\n form.id ||\n form.getAttribute('name') ||\n form.getAttribute('data-testid') ||\n stripRequestUrl(form.getAttribute('action') ?? '', location.href).replace(location.origin, '') ||\n 'form'\n )\n}\n\nfunction currentPath(): string {\n return typeof location === 'undefined' ? '/' : location.pathname || '/'\n}\n\nexport function installFrictionCapture(opts: FrictionCaptureOptions): FrictionCapture {\n const noop: FrictionCapture = { onNavigate: () => undefined, teardown: () => undefined }\n if (typeof window === 'undefined' || typeof document === 'undefined') return noop\n\n const w = window as unknown as Record<string, boolean | undefined>\n if (w[INSTALLED_FLAG]) return noop\n w[INSTALLED_FLAG] = true\n\n const emit = (kind: FrictionKind, properties: Record<string, unknown>): void => {\n try {\n opts.onFriction({ kind, properties: { path: currentPath(), ...properties } })\n } catch {\n return\n }\n }\n\n const cleanups: Array<() => void> = []\n const rage = new RageClickDetector(opts.rageClicks, opts.rageWindowMs, opts.rageRadiusPx)\n const navLoop = new NavLoopDetector(opts.navLoopWindowMs)\n const slowMs = opts.slowRequestMs ?? 3000\n const touched = new Map<HTMLFormElement, FormTouch>()\n\n const onClick = (ev: MouseEvent): void => {\n const el = ev.target instanceof Element ? (ev.target as unknown as ElementLike) : null\n if (isDeadClickTarget(el)) emit('dead_click', describeElement(el))\n if (rage.push(ev.clientX, ev.clientY, ev.timeStamp || Date.now())) {\n emit('rage_click', describeElement(el))\n }\n }\n document.addEventListener('click', onClick, true)\n cleanups.push(() => document.removeEventListener('click', onClick, true))\n\n const onError = (ev: ErrorEvent): void => {\n emit('js_error', {\n message: String(ev.message ?? '').slice(0, 200),\n ...(ev.filename ? { source: stripRequestUrl(ev.filename, location.href) } : {}),\n ...(ev.lineno ? { line: ev.lineno } : {}),\n })\n }\n window.addEventListener('error', onError)\n cleanups.push(() => window.removeEventListener('error', onError))\n\n const onRejection = (ev: PromiseRejectionEvent): void => {\n emit('js_error', { message: errorMessage(ev.reason).slice(0, 200), unhandled_rejection: true })\n }\n window.addEventListener('unhandledrejection', onRejection)\n cleanups.push(() => window.removeEventListener('unhandledrejection', onRejection))\n\n const ignore = (opts.ignoreUrls ?? []).map((url) => stripRequestUrl(url, location.href))\n const apiOrigins = (opts.apiOrigins ?? []).map((o) => {\n try {\n return new URL(o, location.href).origin\n } catch {\n return ''\n }\n })\n const reportRequest = (\n url: string,\n method: string,\n started: number,\n status: number | null,\n flags: { aborted?: boolean; opaque?: boolean } = {},\n ): void => {\n const target = stripRequestUrl(url, location.href)\n if (isIgnoredUrl(target, ignore)) return\n if (!isFirstPartyRequest(target, location.href, apiOrigins)) return\n const durationMs = performance.now() - started\n const kind = classifyRequest({ status, durationMs, slowMs, ...flags })\n if (!kind) return\n emit(kind, {\n url: target,\n method,\n duration_ms: Math.round(durationMs),\n ...(flags.opaque ? {} : status !== null ? { status } : { failed: true }),\n })\n }\n\n const originalFetch = window.fetch\n if (typeof originalFetch === 'function') {\n const patched: typeof window.fetch = async (input, init) => {\n const started = performance.now()\n const url =\n typeof input === 'string' ? input : input instanceof URL ? input.href : input.url\n const method = (init?.method ?? (input instanceof Request ? input.method : 'GET')).toUpperCase()\n try {\n const res = await originalFetch(input, init)\n reportRequest(url, method, started, res.status, {\n opaque: res.type === 'opaque' || res.type === 'opaqueredirect',\n })\n return res\n } catch (err) {\n reportRequest(url, method, started, null, { aborted: isAbortError(err) })\n throw err\n }\n }\n window.fetch = patched\n cleanups.push(() => {\n window.fetch = originalFetch\n })\n }\n\n const xhrProto = typeof XMLHttpRequest === 'function' ? XMLHttpRequest.prototype : null\n if (xhrProto) {\n const originalOpen = xhrProto.open\n const originalSend = xhrProto.send\n const requests = new WeakMap<XMLHttpRequest, { method: string; url: string }>()\n xhrProto.open = function (this: XMLHttpRequest, method: string, url: string | URL, ...rest: unknown[]) {\n requests.set(this, { method: String(method).toUpperCase(), url: String(url) })\n return (originalOpen as (...args: unknown[]) => void).apply(this, [method, url, ...rest])\n } as typeof xhrProto.open\n xhrProto.send = function (this: XMLHttpRequest, body?: Document | XMLHttpRequestBodyInit | null) {\n const request = requests.get(this)\n if (request) {\n const started = performance.now()\n let aborted = false\n this.addEventListener('abort', () => {\n aborted = true\n })\n this.addEventListener('loadend', () => {\n reportRequest(request.url, request.method, started, this.status === 0 ? null : this.status, { aborted })\n })\n }\n return originalSend.call(this, body)\n }\n cleanups.push(() => {\n xhrProto.open = originalOpen\n xhrProto.send = originalSend\n })\n }\n\n const loadStatus = documentStatus(typeof performance === 'undefined' ? undefined : performance)\n if (loadStatus !== null && loadStatus >= 400) {\n emit('http_error', { document: true, status: loadStatus, url: stripRequestUrl(location.href) })\n }\n\n const onFocusIn = (ev: FocusEvent): void => {\n const el = ev.target as HTMLElement | null\n if (!el || !FORM_CONTROL_TAGS.has(el.tagName.toLowerCase())) return\n const form = (el as HTMLInputElement).form\n if (!form) return\n const entry = touched.get(form) ?? { form: formKey(form), fields: new Set<string>() }\n const field = (el as HTMLInputElement).name || el.id || el.tagName.toLowerCase()\n entry.fields.add(field)\n touched.set(form, entry)\n }\n document.addEventListener('focusin', onFocusIn, true)\n cleanups.push(() => document.removeEventListener('focusin', onFocusIn, true))\n\n const onSubmit = (ev: Event): void => {\n if (ev.target instanceof HTMLFormElement) touched.delete(ev.target)\n }\n document.addEventListener('submit', onSubmit, true)\n cleanups.push(() => document.removeEventListener('submit', onSubmit, true))\n\n const flushAbandons = (): void => {\n for (const entry of touched.values()) {\n emit('form_abandon', { form: entry.form, fields_touched: entry.fields.size })\n }\n touched.clear()\n }\n window.addEventListener('pagehide', flushAbandons)\n cleanups.push(() => window.removeEventListener('pagehide', flushAbandons))\n\n return {\n onNavigate: (path: string) => {\n flushAbandons()\n const loop = navLoop.push(path, Date.now())\n if (loop) emit('nav_loop', { from: loop.from, to: loop.to })\n },\n teardown: () => {\n for (const cleanup of cleanups) cleanup()\n touched.clear()\n w[INSTALLED_FLAG] = false\n },\n }\n}\n", "/**\n * Contexto de ambiente do browser \u00B7 @holdyapp/sdk/web.\n *\n * Vai em `context` de todo evento, nunca em `properties`: properties \u00E9 o\n * vocabul\u00E1rio do produto do cliente (`workflow_key`, `plan`), context \u00E9 o que o\n * SDK observou sozinho. A Data layer persiste os dois (facts.context, 00247) e\n * a leitura de dimens\u00E3o consulta os dois.\n *\n * Tr\u00EAs decis\u00F5es que definem se esse dado \u00E9 \u00FAtil ou lixo:\n * \u00B7 `url` sai sem query e sem hash. Href cru tem uma varia\u00E7\u00E3o por sess\u00E3o, e\n * dimens\u00E3o com cardinalidade quase-\u00FAnica \u00E9 descartada na leitura \u2014 a\n * p\u00E1gina vira ru\u00EDdo exatamente por excesso de precis\u00E3o.\n * \u00B7 utm \u00E9 capturado na primeira p\u00E1gina e carregado pelo resto da sess\u00E3o.\n * Atribui\u00E7\u00E3o que s\u00F3 existe no primeiro evento n\u00E3o responde nada.\n * \u00B7 referrer s\u00F3 quando \u00E9 externo. Navega\u00E7\u00E3o interna preenchendo \"Origem do\n * tr\u00E1fego\" com o pr\u00F3prio dom\u00EDnio esconde a origem real.\n *\n * Tudo aqui \u00E9 best-effort: browser em modo privativo, SSR e webview sem\n * storage precisam funcionar. Nenhuma fun\u00E7\u00E3o lan\u00E7a.\n */\n\nexport type WebContext = Record<string, string>\n\nconst UTM_KEYS = [\n 'utm_source',\n 'utm_medium',\n 'utm_campaign',\n 'utm_term',\n 'utm_content',\n] as const\n\nconst SESSION_STORAGE_KEY = 'holdy_session'\nconst ATTRIBUTION_STORAGE_KEY = 'holdy_attribution'\n\n/* \u2500\u2500 URL \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n/** `https://app.x.com/a/b?q=1#z` \u2192 `https://app.x.com/a/b`. */\nexport function stripUrl(href: string): string | null {\n try {\n const url = new URL(href)\n return `${url.origin}${url.pathname}`\n } catch {\n return null\n }\n}\n\n/** Par\u00E2metros de campanha presentes na URL, j\u00E1 em lowercase. */\nexport function parseUtm(href: string): WebContext {\n const out: WebContext = {}\n try {\n const params = new URL(href).searchParams\n for (const key of UTM_KEYS) {\n const value = params.get(key)?.trim()\n if (value) out[key] = value.toLowerCase().slice(0, 120)\n }\n } catch {\n // href inv\u00E1lido \u2014 sem utm, sem drama.\n }\n return out\n}\n\n/* \u2500\u2500 User agent \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\ninterface UaParts {\n browser?: string\n os?: string\n device?: string\n}\n\n/**\n * Fatia m\u00EDnima do UA: navegador, sistema e classe de dispositivo.\n *\n * N\u00E3o \u00E9 biblioteca de UA parsing e n\u00E3o deve virar uma \u2014 o valor est\u00E1 em\n * separar mobile de desktop e Safari de Chrome, n\u00E3o em cravar a vers\u00E3o.\n */\nexport function parseUserAgent(ua: string): UaParts {\n if (!ua) return {}\n const s = ua.toLowerCase()\n\n const browser = s.includes('edg/')\n ? 'Edge'\n : s.includes('opr/') || s.includes('opera')\n ? 'Opera'\n : s.includes('samsungbrowser')\n ? 'Samsung Internet'\n : s.includes('firefox') || s.includes('fxios')\n ? 'Firefox'\n : s.includes('chrome') || s.includes('crios')\n ? 'Chrome'\n : s.includes('safari')\n ? 'Safari'\n : undefined\n\n const os = s.includes('iphone') || s.includes('ipad') || s.includes('ipod')\n ? 'iOS'\n : s.includes('android')\n ? 'Android'\n : s.includes('windows')\n ? 'Windows'\n : s.includes('mac os') || s.includes('macintosh')\n ? 'macOS'\n : s.includes('cros')\n ? 'ChromeOS'\n : s.includes('linux')\n ? 'Linux'\n : undefined\n\n // iPad moderno se declara macOS; a pista que sobra \u00E9 o suporte a toque, que\n // o parser puro n\u00E3o v\u00EA. Tablet aqui cobre o caso declarado no UA.\n const device = s.includes('ipad') || (s.includes('android') && !s.includes('mobile'))\n ? 'tablet'\n : s.includes('mobi') || s.includes('iphone') || s.includes('ipod')\n ? 'mobile'\n : 'desktop'\n\n return {\n ...(browser ? { browser } : {}),\n ...(os ? { os } : {}),\n device,\n }\n}\n\n/* \u2500\u2500 Storage \u00B7 nunca lan\u00E7a \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\nfunction readStorage(storage: 'session' | 'local', key: string): string | null {\n try {\n const s = storage === 'session' ? sessionStorage : localStorage\n return s.getItem(key)\n } catch {\n return null\n }\n}\n\nfunction writeStorage(storage: 'session' | 'local', key: string, value: string): void {\n try {\n const s = storage === 'session' ? sessionStorage : localStorage\n s.setItem(key, value)\n } catch {\n // Modo privativo / storage bloqueado \u2014 o contexto degrada, o evento segue.\n }\n}\n\nfunction randomId(): string {\n try {\n return crypto.randomUUID()\n } catch {\n return `hol-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`\n }\n}\n\n/** ID da sess\u00E3o de browser \u00B7 vive enquanto a aba viver. */\nexport function resolveSessionId(): string {\n const existing = readStorage('session', SESSION_STORAGE_KEY)\n if (existing) return existing\n const fresh = randomId()\n writeStorage('session', SESSION_STORAGE_KEY, fresh)\n return fresh\n}\n\n/**\n * Atribui\u00E7\u00E3o da sess\u00E3o: utm da primeira p\u00E1gina + referrer externo de entrada.\n * Gravada uma vez e repetida em todo evento da sess\u00E3o.\n */\nexport function resolveAttribution(href: string, referrer: string, currentHost: string): WebContext {\n const stored = readStorage('session', ATTRIBUTION_STORAGE_KEY)\n if (stored) {\n try {\n return JSON.parse(stored) as WebContext\n } catch {\n // Valor corrompido \u2014 recalcula por cima.\n }\n }\n\n const attribution: WebContext = { ...parseUtm(href) }\n\n const referrerHost = hostOf(referrer)\n // Referrer interno n\u00E3o \u00E9 origem de tr\u00E1fego, \u00E9 navega\u00E7\u00E3o.\n if (referrerHost && referrerHost !== currentHost) {\n const clean = stripUrl(referrer)\n if (clean) attribution.referrer = clean\n attribution.referrer_host = referrerHost\n }\n\n writeStorage('session', ATTRIBUTION_STORAGE_KEY, JSON.stringify(attribution))\n return attribution\n}\n\nfunction hostOf(href: string): string | null {\n try {\n return new URL(href).hostname.toLowerCase() || null\n } catch {\n return null\n }\n}\n\n/* \u2500\u2500 Montagem \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n/**\n * Contexto completo do evento atual. Fora do browser devolve `{}` \u2014 o SDK\n * server tem o pr\u00F3prio caminho e n\u00E3o deve inventar ambiente.\n */\nexport function buildWebContext(): WebContext {\n if (typeof window === 'undefined' || typeof location === 'undefined') return {}\n\n const href = location.href\n const referrer = typeof document !== 'undefined' ? document.referrer : ''\n const host = location.hostname.toLowerCase()\n\n const ua = typeof navigator !== 'undefined' ? navigator.userAgent : ''\n const { browser, os, device } = parseUserAgent(ua)\n\n const url = stripUrl(href)\n\n return {\n ...(url ? { url } : {}),\n path: location.pathname || '/',\n host,\n ...(browser ? { browser } : {}),\n ...(os ? { os } : {}),\n ...(device ? { device } : {}),\n ...(typeof navigator !== 'undefined' && navigator.language\n ? { locale: navigator.language }\n : {}),\n session_id: resolveSessionId(),\n ...resolveAttribution(href, referrer, host),\n }\n}\n", "export type ChangeArm = 'change' | 'holdout'\n\nfunction fnv1a(input: string): number {\n let hash = 0x811c9dc5\n for (let i = 0; i < input.length; i++) {\n hash ^= input.charCodeAt(i)\n hash = Math.imul(hash, 0x01000193) >>> 0\n }\n return hash >>> 0\n}\n\nexport function changeBucket(changeId: string, accountKey: string): number {\n return fnv1a(`${changeId}:${accountKey}`) % 100\n}\n\nexport function changeArm(changeId: string, accountKey: string, holdoutPct: number): ChangeArm {\n return changeBucket(changeId, accountKey) < holdoutPct ? 'holdout' : 'change'\n}\n\nexport function matchesPathTemplate(template: string, path: string): boolean {\n const t = template.split(/[?#]/)[0].split('/').filter(Boolean)\n const p = path.split(/[?#]/)[0].split('/').filter(Boolean)\n if (t.length !== p.length) return false\n return t.every((segment, i) => segment.startsWith(':') || segment === p[i])\n}\n", "import { changeArm, matchesPathTemplate, type ChangeArm } from './arm'\n\nexport type ChangeAnchor =\n | { kind: 'selector'; value: string }\n | { kind: 'text'; value: string }\n | { kind: 'page' }\n\nexport interface ProductChange {\n id: string\n kind: 'copy' | 'guidance'\n path: string\n anchor: ChangeAnchor\n expected_text: string | null\n content: { text: string }\n holdout_pct: number\n}\n\nexport interface ProductChangesOptions {\n baseUrl: string\n apiKey: string\n getAccountKey: () => string | null\n track: (name: string, properties: Record<string, unknown>) => void\n debug?: boolean\n}\n\nexport interface ProductChangesHandle {\n onNavigate(): void\n refresh(): Promise<void>\n teardown(): void\n}\n\nconst REFRESH_MS = 5 * 60 * 1000\nconst NAV_REFRESH_MS = 60 * 1000\nconst SCAN_DEBOUNCE_MS = 250\nconst SEEN_KEY = 'holdy_changes_seen'\nconst DISMISSED_KEY = 'holdy_changes_dismissed'\nconst TIP_GAP_PX = 8\n\nconst TIP_CSS = `:host{all:initial}\n.tip{position:fixed;box-sizing:border-box;max-width:280px;padding:10px 12px;border-radius:8px;background:#111;color:#fff;font:13px/1.45 system-ui,-apple-system,sans-serif;box-shadow:0 8px 24px rgba(0,0,0,.18);pointer-events:auto;display:flex;gap:10px;align-items:flex-start}\n.tip p{margin:0;flex:1}\n.tip button{all:unset;cursor:pointer;opacity:.7;font-size:15px;line-height:1}\n.tip button:hover,.tip button:focus-visible{opacity:1}\n@media (prefers-color-scheme:dark){.tip{background:#fff;color:#111}}`\n\nexport function normalizeText(text: string): string {\n return text.replace(/\\s+/g, ' ').trim()\n}\n\nexport function parseChanges(body: unknown): ProductChange[] {\n const list = (body as { changes?: unknown } | null)?.changes\n return Array.isArray(list) ? list.filter(isChange) : []\n}\n\nfunction isAnchor(value: unknown): value is ChangeAnchor {\n const a = value as { kind?: unknown; value?: unknown } | null\n if (a?.kind === 'page') return true\n return (a?.kind === 'selector' || a?.kind === 'text') && typeof a.value === 'string' && a.value.length > 0\n}\n\nfunction isChange(value: unknown): value is ProductChange {\n const c = value as ProductChange | null\n return (\n typeof c?.id === 'string' &&\n (c.kind === 'copy' || c.kind === 'guidance') &&\n typeof c.path === 'string' &&\n isAnchor(c.anchor) &&\n typeof c.content?.text === 'string' &&\n typeof c.holdout_pct === 'number'\n )\n}\n\nexport function changesForPage(\n changes: readonly ProductChange[],\n path: string,\n accountKey: string | null,\n): Array<{ change: ProductChange; arm: ChangeArm }> {\n if (!accountKey) return []\n return changes\n .filter((c) => matchesPathTemplate(c.path, path))\n .map((change) => ({ change, arm: changeArm(change.id, accountKey, change.holdout_pct) }))\n}\n\nfunction storage(kind: 'local' | 'session'): Storage | undefined {\n try {\n return kind === 'local' ? window.localStorage : window.sessionStorage\n } catch {\n return undefined\n }\n}\n\nfunction readSet(store: Storage | undefined, key: string): Set<string> {\n try {\n return new Set(JSON.parse(store?.getItem(key) ?? '[]') as string[])\n } catch {\n return new Set()\n }\n}\n\nfunction addToSet(store: Storage | undefined, key: string, value: string): void {\n try {\n const set = readSet(store, key)\n set.add(value)\n store?.setItem(key, JSON.stringify([...set].slice(-200)))\n } catch {\n return\n }\n}\n\nfunction query(selector: string): Element | null {\n try {\n return document.querySelector(selector)\n } catch {\n return null\n }\n}\n\nconst TEXT_ANCHOR_TAGS = 'button,a,[role=\"button\"],label,summary,h1,h2,h3,h4,li,td,th,p,span,strong'\nconst TEXT_ANCHOR_SCAN_LIMIT = 3000\n\nexport function findByText(value: string): Element | null {\n const wanted = normalizeText(value)\n let best: Element | null = null\n let bestLength = Number.POSITIVE_INFINITY\n const nodes = document.querySelectorAll(TEXT_ANCHOR_TAGS)\n const total = Math.min(nodes.length, TEXT_ANCHOR_SCAN_LIMIT)\n for (let i = 0; i < total; i += 1) {\n const el = nodes[i]\n const raw = el.textContent ?? ''\n if (raw.length >= bestLength || normalizeText(raw) !== wanted) continue\n best = el\n bestLength = raw.length\n }\n return best\n}\n\nfunction findAnchor(anchor: ChangeAnchor): Element | null {\n if (anchor.kind === 'page') return null\n return anchor.kind === 'selector' ? query(anchor.value) : findByText(anchor.value)\n}\n\nfunction findTextNode(el: Element, match: (text: string) => boolean): Text | null {\n const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT)\n for (let node = walker.nextNode(); node; node = walker.nextNode()) {\n if (match(normalizeText(node.textContent ?? ''))) return node as Text\n }\n return null\n}\n\ninterface AppliedCopy {\n node: Text\n original: string\n next: string\n}\n\nexport function installProductChanges(opts: ProductChangesOptions): ProductChangesHandle {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return { onNavigate: () => undefined, refresh: async () => undefined, teardown: () => undefined }\n }\n\n let changes: ProductChange[] = []\n let lastFetch = 0\n let timer: ReturnType<typeof setTimeout> | null = null\n let root: ShadowRoot | null = null\n let host: HTMLElement | null = null\n const copies = new Map<string, AppliedCopy>()\n const tips = new Map<string, () => void>()\n\n const log = (message: string): void => {\n if (opts.debug) console.warn(`[holdy] ${message}`)\n }\n\n const expose = (change: ProductChange, arm: ChangeArm): void => {\n const key = `${change.id}:${arm}`\n const session = storage('session')\n if (readSet(session, SEEN_KEY).has(key)) return\n addToSet(session, SEEN_KEY, key)\n opts.track('$change_exposure', { change_id: change.id, arm, kind: change.kind })\n }\n\n const eligibleCopyNode = (el: Element, change: ProductChange): Text | null => {\n const expected = change.expected_text ? normalizeText(change.expected_text) : null\n const next = normalizeText(change.content.text)\n return findTextNode(el, (text) => text.length > 0 && (text === next || expected === null || text === expected))\n }\n\n const applyCopy = (el: Element, change: ProductChange): boolean => {\n const next = normalizeText(change.content.text)\n const current = copies.get(change.id)\n if (current && current.node.isConnected && normalizeText(current.node.textContent ?? '') === next) return true\n const node = eligibleCopyNode(el, change)\n if (!node) return false\n const text = node.textContent ?? ''\n if (normalizeText(text) !== next) {\n copies.set(change.id, { node, original: text, next: change.content.text })\n node.textContent = text.replace(text.trim(), change.content.text)\n }\n return true\n }\n\n const restoreCopy = (id: string): void => {\n const applied = copies.get(id)\n if (!applied) return\n if (applied.node.isConnected && normalizeText(applied.node.textContent ?? '') === normalizeText(applied.next)) {\n applied.node.textContent = applied.original\n }\n copies.delete(id)\n }\n\n const ensureRoot = (): ShadowRoot | null => {\n if (root) return root\n if (!document.body) return null\n host = document.createElement('holdy-changes')\n host.setAttribute('data-holdy-root', '1')\n host.style.cssText = 'position:fixed;top:0;left:0;width:0;height:0;z-index:2147483647;pointer-events:none'\n root = host.attachShadow({ mode: 'closed' })\n const style = document.createElement('style')\n style.textContent = TIP_CSS\n root.appendChild(style)\n document.body.appendChild(host)\n return root\n }\n\n const showTip = (anchor: Element | null, change: ProductChange): boolean => {\n if (tips.has(change.id)) return true\n if (readSet(storage('local'), DISMISSED_KEY).has(change.id)) return true\n const shadow = ensureRoot()\n if (!shadow) return false\n\n const card = document.createElement('div')\n card.className = 'tip'\n card.setAttribute('role', 'tooltip')\n const text = document.createElement('p')\n text.textContent = change.content.text\n const close = document.createElement('button')\n close.type = 'button'\n close.setAttribute('aria-label', 'Fechar')\n close.textContent = '\u00D7'\n card.append(text, close)\n card.style.top = '-9999px'\n card.style.left = '-9999px'\n shadow.appendChild(card)\n\n const remove = (): void => {\n card.remove()\n window.removeEventListener('scroll', position, true)\n window.removeEventListener('resize', position)\n tips.delete(change.id)\n }\n\n function position(): void {\n if (!anchor) {\n const corner = card.getBoundingClientRect()\n card.style.top = `${Math.max(8, window.innerHeight - corner.height - 16)}px`\n card.style.left = `${Math.max(8, window.innerWidth - corner.width - 16)}px`\n return\n }\n if (!anchor.isConnected) {\n remove()\n return\n }\n const a = anchor.getBoundingClientRect()\n const c = card.getBoundingClientRect()\n let top = a.bottom + TIP_GAP_PX\n if (top + c.height > window.innerHeight - 8) top = a.top - c.height - TIP_GAP_PX\n const left = Math.min(Math.max(8, a.left + a.width / 2 - c.width / 2), window.innerWidth - c.width - 8)\n card.style.top = `${Math.max(8, top)}px`\n card.style.left = `${Math.max(8, left)}px`\n }\n\n close.addEventListener('click', () => {\n addToSet(storage('local'), DISMISSED_KEY, change.id)\n opts.track('$change_dismissed', { change_id: change.id, kind: change.kind })\n remove()\n })\n tips.set(change.id, remove)\n requestAnimationFrame(position)\n window.addEventListener('scroll', position, true)\n window.addEventListener('resize', position)\n return true\n }\n\n const scan = (): void => {\n timer = null\n const active = changesForPage(changes, location.pathname, opts.getAccountKey())\n const activeIds = new Set(active.map((a) => a.change.id))\n for (const id of [...copies.keys()]) if (!activeIds.has(id)) restoreCopy(id)\n for (const [id, remove] of [...tips]) if (!activeIds.has(id)) remove()\n\n for (const { change, arm } of active) {\n const el = findAnchor(change.anchor)\n if (!el && change.anchor.kind !== 'page') continue\n if (change.kind === 'copy') {\n if (!el) continue\n const shown = arm === 'change' ? applyCopy(el, change) : eligibleCopyNode(el, change) !== null\n if (shown) expose(change, arm)\n } else if (arm === 'holdout' || showTip(el, change)) {\n expose(change, arm)\n }\n }\n }\n\n const schedule = (): void => {\n if (timer === null) timer = setTimeout(scan, SCAN_DEBOUNCE_MS)\n }\n\n const refresh = async (): Promise<void> => {\n lastFetch = Date.now()\n try {\n const res = await fetch(`${opts.baseUrl}/runtime/changes`, {\n headers: { Authorization: `Bearer ${opts.apiKey}` },\n })\n if (!res.ok) {\n log(`changes ${res.status}`)\n return\n }\n changes = parseChanges(await res.json())\n schedule()\n } catch (err) {\n log(`changes network error: ${(err as Error).message}`)\n }\n }\n\n const observer =\n typeof MutationObserver !== 'undefined'\n ? new MutationObserver(() => {\n if (changes.length > 0 || copies.size > 0 || tips.size > 0) schedule()\n })\n : null\n const observe = (): void => {\n if (document.body) observer?.observe(document.body, { childList: true, subtree: true, characterData: true })\n }\n if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', observe, { once: true })\n else observe()\n\n const interval = setInterval(() => void refresh(), REFRESH_MS)\n void refresh()\n\n return {\n onNavigate: () => {\n if (Date.now() - lastFetch > NAV_REFRESH_MS) void refresh()\n schedule()\n },\n refresh,\n teardown: () => {\n clearInterval(interval)\n if (timer !== null) clearTimeout(timer)\n observer?.disconnect()\n for (const id of [...copies.keys()]) restoreCopy(id)\n for (const remove of [...tips.values()]) remove()\n host?.remove()\n host = null\n root = null\n changes = []\n },\n }\n}\n", "/**\n * @holdyapp/sdk/web \u00B7 browser (source: sdk_web).\n *\n * anonymous_id gerado e persistido em localStorage (holdy_anon_id) \u2014 enviado em\n * todo event at\u00E9 identify() promover pra user_id (identity graph faz o merge\n * downstream, nunca o SDK). Flush no pagehide via fetch keepalive.\n *\n * Todo evento carrega o contexto de ambiente (url, path, referrer externo,\n * utm da sess\u00E3o, browser, os, device, locale, session_id) em `context` \u2014\n * separado das properties do produto. Pageview \u00E9 autom\u00E1tico por default:\n * carregamento inicial e cada mudan\u00E7a de rota, inclusive em SPA.\n */\n\nimport { HoldyCore } from './core'\nimport { installAutoCapture, type NavigationKind } from './auto-capture'\nimport { installFrictionCapture, type FrictionCapture, type FrictionCaptureOptions } from './friction'\nimport { buildWebContext } from './web-context'\nimport { installProductChanges, type ProductChangesHandle } from './changes'\nimport type { HoldyOptions, Properties } from './types'\n\nconst ANON_STORAGE_KEY = 'holdy_anon_id'\n\nexport interface HoldyWebOptions extends HoldyOptions {\n /**\n * Emitir `$pageview` sozinho no load e a cada mudan\u00E7a de rota \u00B7 default true.\n * Desligue s\u00F3 se o produto j\u00E1 chama `page()` na m\u00E3o em cada tela.\n */\n autoCapture?: boolean\n /**\n * Capturar fric\u00E7\u00E3o sozinho \u00B7 default true. Emite `$dead_click`, `$rage_click`,\n * `$form_abandon`, `$js_error`, `$slow_request`, `$nav_loop` e `$http_error`\n * (requisi\u00E7\u00E3o com status \u2265 400 ou sem resposta, e a pr\u00F3pria p\u00E1gina que abre\n * com erro) \u2014 eventos determin\u00EDsticos, sem DOM nem replay. Objeto ajusta os\n * limiares; a URL da Holdy nunca conta como erro.\n */\n friction?: boolean | Omit<FrictionCaptureOptions, 'onFriction'>\n /**\n * Aplicar as mudan\u00E7as de texto e dica que a org p\u00F4s no ar na Holdy \u00B7 default\n * true. Parte das contas fica de fora por sorteio pra medir; sem conta\n * identificada (`group()` ou `accountKey`) nada \u00E9 aplicado.\n */\n productChanges?: boolean\n}\n\nexport class HoldyWeb extends HoldyCore {\n private teardownAutoCapture: (() => void) | null = null\n private friction: FrictionCapture | null = null\n private changes: ProductChangesHandle | null = null\n\n constructor(opts: HoldyWebOptions) {\n super('sdk_web', opts)\n this.anonymousId = resolveAnonymousId()\n\n if (typeof window !== 'undefined') {\n window.addEventListener('pagehide', () => void this.flush())\n document.addEventListener('visibilitychange', () => {\n if (document.visibilityState === 'hidden') void this.flush()\n })\n }\n\n if (opts.friction !== false) {\n const frictionOpts = typeof opts.friction === 'object' ? opts.friction : {}\n this.friction = installFrictionCapture({\n ...frictionOpts,\n ignoreUrls: [...(frictionOpts.ignoreUrls ?? []), this.baseUrl],\n onFriction: (signal) => this.track(`$${signal.kind}`, signal.properties),\n })\n }\n\n if (opts.autoCapture !== false) {\n this.teardownAutoCapture = installAutoCapture({\n onNavigate: (kind: NavigationKind) => {\n this.page(undefined, { navigation: kind })\n this.friction?.onNavigate(typeof location !== 'undefined' ? location.pathname : '/')\n this.changes?.onNavigate()\n },\n })\n }\n\n if (opts.productChanges !== false && typeof window !== 'undefined') {\n this.changes = installProductChanges({\n baseUrl: this.baseUrl,\n apiKey: this.apiKey,\n getAccountKey: () => this.accountKey,\n track: (name, properties) => this.track(name, properties),\n debug: opts.debug,\n })\n }\n }\n\n /**\n * Pageview. O `$` marca evento emitido pelo SDK, n\u00E3o pelo produto (mesma\n * conven\u00E7\u00E3o de `$identify`/`$group`); a canonicaliza\u00E7\u00E3o downstream remove o\n * sigilo, ent\u00E3o `$pageview` e `pageview` s\u00E3o o mesmo evento na leitura.\n * url/path/referrer/utm j\u00E1 v\u00EAm no context de todo evento \u2014 aqui s\u00F3 entra o\n * que \u00E9 da p\u00E1gina como conte\u00FAdo (nome e t\u00EDtulo).\n */\n page(name?: string, properties?: Properties): void {\n this.track('$pageview', {\n ...(properties ?? {}),\n ...(name ? { page_name: name } : {}),\n ...(typeof document !== 'undefined' && document.title ? { title: document.title } : {}),\n })\n }\n\n override group(accountKey: string, traits?: Properties): void {\n super.group(accountKey, traits)\n this.changes?.onNavigate()\n }\n\n reset(): void {\n this.userId = null\n this.accountKey = null\n try {\n localStorage.removeItem(ANON_STORAGE_KEY)\n } catch {\n // storage indispon\u00EDvel (SSR/privacy mode) \u2014 ok\n }\n this.anonymousId = resolveAnonymousId()\n this.changes?.onNavigate()\n }\n\n /** Flush final + desinstala o auto-capture (devolve `history` ao original). */\n override async shutdown(): Promise<void> {\n this.teardownAutoCapture?.()\n this.teardownAutoCapture = null\n this.friction?.teardown()\n this.friction = null\n this.changes?.teardown()\n this.changes = null\n await super.shutdown()\n }\n\n protected override environmentContext(): Properties {\n return buildWebContext()\n }\n}\n\nfunction resolveAnonymousId(): string {\n try {\n const existing = localStorage.getItem(ANON_STORAGE_KEY)\n if (existing) return existing\n const fresh = crypto.randomUUID()\n localStorage.setItem(ANON_STORAGE_KEY, fresh)\n return fresh\n } catch {\n return crypto.randomUUID()\n }\n}\n\nlet singleton: HoldyWeb | null = null\n\n/** Init idempotente \u2014 segunda chamada retorna a inst\u00E2ncia existente. */\nexport function initHoldy(opts: HoldyWebOptions): HoldyWeb {\n if (!singleton) {\n singleton = new HoldyWeb(opts)\n // Inst\u00E2ncia no window: track() inserido pelo `--instrument` do CLI n\u00E3o\n // precisa importar nada nem saber onde o init mora. Mesma superf\u00EDcie do\n // loader por script tag, ent\u00E3o o snippet \u00E9 um s\u00F3 nos dois caminhos.\n if (typeof window !== 'undefined') {\n const w = window as unknown as { Holdy?: HoldyWeb }\n if (!w.Holdy) w.Holdy = singleton\n }\n }\n return singleton\n}\n\nexport type { HoldyOptions, Properties }\n"],
|
|
5
|
+
"mappings": "mcAAA,IAAAA,GAAA,GAAAC,GAAAD,GAAA,WAAAE,GAAA,gBAAAC,GAAA,UAAAC,GAAA,aAAAC,GAAA,SAAAC,GAAA,SAAAC,GAAA,UAAAC,GAAA,aAAAC,GAAA,UAAAC,KCcO,IAAMC,GAAmB,4BASzB,IAAMC,EAAN,KAAgB,CACF,OACA,QACF,QACA,MACA,OAET,MAA0B,CAAC,EAC3B,MAA+C,KAC/C,SAAiC,KACjC,QAAU,EACV,SAAW,EACF,OAAS,IAAI,IAEpB,OAAwB,KACxB,YAA6B,KAC7B,WAA4B,KAEtC,YAAYC,EAAkCC,EAAoB,CAChE,KAAK,OAASD,EACd,KAAK,OAASC,EAAK,OACnB,KAAK,WAAaA,EAAK,YAAc,KACrC,KAAK,SAAWA,EAAK,SAAWC,IAAkB,QAAQ,MAAO,EAAE,EACnE,KAAK,QAAU,KAAK,IAAI,EAAGD,EAAK,SAAW,EAAgB,EAC3D,KAAK,MAAQA,EAAK,OAAS,GAE3B,IAAME,EAAaF,EAAK,iBAAmB,IACvCE,EAAa,IACf,KAAK,MAAQ,YAAY,IAAM,KAAK,KAAK,MAAM,EAAGA,CAAU,EAExD,OAAQ,KAAK,MAAiC,OAAU,YACxD,KAAK,MAA2C,MAAM,EAG9D,CAEA,MAAMC,EAAcC,EAAyBC,EAAkC,CAC7E,GAAI,CAACF,GAAQ,OAAOA,GAAS,SAAU,OAEvC,IAAMG,EAASD,GAAW,QAAU,KAAK,QAAU,OAC7CE,EAAaF,GAAW,YAAc,KAAK,YAAc,OACzDG,EAAaH,GAAW,WAC1B,IAAI,KAAKA,EAAU,UAAU,EAAE,YAAY,EAC3C,IAAI,KAAK,EAAE,YAAY,EAKrBI,EAAsB,CAC1B,GAAG,KAAK,mBAAmB,EAC3B,GAAIJ,GAAW,SAAW,CAAC,CAC7B,EACIE,IAAYE,EAAQ,oBAAsBF,GAE9C,IAAMG,EAAwB,CAC5B,MAAOP,EACP,YAAaK,EACb,OAAQ,KAAK,OACb,YAAaG,GAAW,EACxB,GAAIL,EAAS,CAAE,QAASA,CAAO,EAAI,CAAC,EACpC,GAAI,KAAK,YAAc,CAAE,aAAc,KAAK,WAAY,EAAI,CAAC,EAC7D,WAAYF,GAAc,CAAC,EAC3B,GAAI,OAAO,KAAKK,CAAO,EAAE,OAAS,EAAI,CAAE,QAAAA,CAAQ,EAAI,CAAC,CACvD,EAEA,KAAK,MAAM,KAAKC,CAAK,EACrB,KAAK,UAAU,EACX,KAAK,MAAM,QAAU,KAAK,SAAc,KAAK,MAAM,CACzD,CAEA,SAASJ,EAAgBM,EAA2B,CAC9C,CAACN,GAAU,OAAOA,GAAW,WACjC,KAAK,OAASA,EACd,KAAK,MAAM,YAAaM,GAAU,CAAC,CAAC,EACtC,CAEA,MAAML,EAAoBK,EAA2B,CAC/C,CAACL,GAAc,OAAOA,GAAe,WACzC,KAAK,WAAaA,EAClB,KAAK,MAAM,SAAUK,GAAU,CAAC,CAAC,EACnC,CAGA,MAAM,OAAuB,CAC3B,OAAO,KAAK,WAAW,EAAK,CAC9B,CAGA,MAAM,UAA0B,CAC1B,KAAK,QACP,cAAc,KAAK,KAAK,EACxB,KAAK,MAAQ,MAEf,MAAM,KAAK,WAAW,EAAI,CAC5B,CAEQ,WAAWC,EAA+B,CAEhD,OAAI,KAAK,SAAiB,KAAK,SAC3B,KAAK,MAAM,SAAW,GACtB,CAACA,GAAS,KAAK,IAAI,EAAI,KAAK,QAAgB,QAAQ,QAAQ,GAEhE,KAAK,SAAW,KAAK,MAAM,EAAE,QAAQ,IAAM,CACzC,KAAK,SAAW,IAClB,CAAC,EACM,KAAK,SACd,CAEA,MAAc,OAAuB,CACnC,KAAO,KAAK,MAAM,OAAS,GAAG,CAC5B,IAAMC,EAAQ,KAAK,MAAM,OAAO,EAAG,GAAU,EACvCC,EAAU,MAAM,KAAK,KAAKD,CAAK,EACrC,GAAIC,EAAQ,OAAS,QAAS,CAC5B,KAAK,MAAM,QAAQ,GAAGD,CAAK,EAC3B,KAAK,UAAU,EACf,KAAK,cAAcC,EAAQ,OAAO,EAClC,MACF,CACA,KAAK,SAAW,EAChB,KAAK,QAAU,CACjB,CACF,CAEQ,cAAcC,EAA8B,CAElD,GADA,KAAK,UAAY,EACbA,IAAY,KAAM,CACpB,KAAK,QAAU,KAAK,IAAI,EAAIA,EAC5B,MACF,CACA,IAAMC,EAAU,KAAK,IAAI,IAAgB,IAAO,IAAM,KAAK,SAAW,EAAE,EACxE,KAAK,QAAU,KAAK,IAAI,EAAIA,EAAU,KAAK,MAAM,KAAK,OAAO,EAAIA,EAAU,EAAG,CAChF,CAEQ,WAAkB,CACpB,KAAK,MAAM,OAAS,KAAW,KAAK,MAAM,OAAO,EAAG,KAAK,MAAM,OAAS,GAAS,CACvF,CAEA,MAAc,KAAKC,EAAgD,CACjE,GAAI,CACF,IAAMC,EAAM,MAAM,MAAM,GAAG,KAAK,OAAO,eAAgB,CACrD,OAAQ,OACR,QAAS,CACP,cAAe,UAAU,KAAK,MAAM,GACpC,eAAgB,kBAClB,EACA,KAAM,KAAK,UAAU,CAAE,OAAAD,CAAO,CAAC,EAC/B,GAAI,KAAK,SAAW,UAAY,CAAE,UAAW,EAAK,EAAI,CAAC,CACzD,CAAC,EACD,OAAIC,EAAI,GAAW,CAAE,KAAM,MAAO,EAE9BA,EAAI,SAAW,KACjB,KAAK,IAAI,gDAA6C,EAC/C,CAAE,KAAM,QAAS,QAASC,GAAaD,CAAG,CAAE,GAEjDA,EAAI,QAAU,KAChB,KAAK,IAAI,eAAeA,EAAI,MAAM,+BAA4B,EACvD,CAAE,KAAM,QAAS,QAAS,IAAK,GAEpCA,EAAI,SAAW,KACjB,KAAK,SAAS,MAAO,8GAA6F,EAC3G,CAAE,KAAM,SAAU,GAEvBA,EAAI,SAAW,KACjB,KAAK,SAAS,MAAO,oFAA8E,EAC5F,CAAE,KAAM,SAAU,IAE3B,KAAK,IAAI,iBAAiBA,EAAI,MAAM,qBAAkB,EAC/C,CAAE,KAAM,SAAU,EAC3B,OAASE,EAAK,CACZ,YAAK,IAAI,uBAAwBA,EAAc,OAAO,+BAA4B,EAC3E,CAAE,KAAM,QAAS,QAAS,IAAK,CACxC,CACF,CAEQ,IAAIC,EAAmB,CACzB,KAAK,OAAO,QAAQ,KAAK,WAAWA,CAAG,EAAE,CAC/C,CAEQ,SAASC,EAAaD,EAAmB,CAC3C,KAAK,OAAO,IAAIC,CAAG,IACvB,KAAK,OAAO,IAAIA,CAAG,EACnB,QAAQ,KAAK,WAAWD,CAAG,EAAE,EAC/B,CAOU,oBAAiC,CACzC,MAAO,CAAC,CACV,CAGA,IAAI,SAAkB,CACpB,OAAO,KAAK,MAAM,MACpB,CACF,EAEA,SAASF,GAAaD,EAA8B,CAClD,IAAMK,EAAM,OAAOL,EAAI,SAAS,KAAQ,WAAaA,EAAI,QAAQ,IAAI,aAAa,EAAI,KACtF,GAAI,CAACK,EAAK,OAAO,KACjB,IAAMC,EAAU,OAAOD,CAAG,EAC1B,MAAI,CAAC,OAAO,SAASC,CAAO,GAAKA,EAAU,EAAU,KAC9C,KAAK,IAAI,IAAgBA,EAAU,GAAI,CAChD,CAEA,SAASd,IAAqB,CAC5B,IAAMe,EAAK,WAA0D,OACrE,GAAI,OAAOA,GAAG,YAAe,WAAY,OAAOA,EAAE,WAAW,EAC7D,IAAMC,EAAO,IAAM,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,EAAG,EAAE,EACzD,MAAO,GAAG,KAAK,IAAI,EAAE,SAAS,EAAE,CAAC,IAAIA,EAAK,CAAC,IAAIA,EAAK,CAAC,EACvD,CC/NA,IAAMC,EAAiB,wBAehB,SAASC,EAAmBC,EAAsC,CACvE,GAAI,OAAO,OAAW,KAAe,OAAO,QAAY,IACtD,MAAO,IAAG,GAGZ,IAAMC,EAAI,OACV,GAAIA,EAAEC,CAAc,EAAG,MAAO,IAAG,GACjCD,EAAEC,CAAc,EAAI,GAEpB,IAAMC,EAA8B,CAAC,EACjCC,EAAWC,EAAY,EACvBC,EAAgD,KAEpD,SAASC,EAAKC,EAA4B,CACxC,GAAI,CACFR,EAAK,WAAWQ,CAAI,CACtB,MAAQ,CAER,CACF,CAEA,SAASC,GAA0B,CAC7BH,GAAS,aAAaA,CAAO,EACjCA,EAAU,WAAW,IAAM,CACzBA,EAAU,KACV,IAAMI,EAAOL,EAAY,EAGrBK,IAASN,IACbA,EAAWM,EACXH,EAAK,cAAc,EACrB,EAAG,EAAiB,CACtB,CAEAA,EAAK,cAAc,EAEnB,IAAMI,EAAe,QAAQ,UACvBC,EAAkB,QAAQ,aAEhC,QAAQ,UAAY,YAAwBC,EAAwC,CAClF,IAAMC,EAASH,EAAa,MAAM,KAAME,CAAI,EAC5C,OAAAJ,EAAkB,EACXK,CACT,EACA,QAAQ,aAAe,YAA2BD,EAA2C,CAC3F,IAAMC,EAASF,EAAgB,MAAM,KAAMC,CAAI,EAC/C,OAAAJ,EAAkB,EACXK,CACT,EACAX,EAAS,KAAK,IAAM,CAClB,QAAQ,UAAYQ,EACpB,QAAQ,aAAeC,CACzB,CAAC,EAGD,IAAMG,EAAQ,IAAYN,EAAkB,EAC5C,OAAO,iBAAiB,WAAYM,CAAK,EACzCZ,EAAS,KAAK,IAAM,OAAO,oBAAoB,WAAYY,CAAK,CAAC,EAEjE,IAAMC,EAAS,IAAYP,EAAkB,EAC7C,cAAO,iBAAiB,aAAcO,CAAM,EAC5Cb,EAAS,KAAK,IAAM,OAAO,oBAAoB,aAAca,CAAM,CAAC,EAE7D,IAAM,CACPV,GAAS,aAAaA,CAAO,EACjC,QAAWW,KAAWd,EAAUc,EAAQ,EACxChB,EAAEC,CAAc,EAAI,EACtB,CACF,CAGA,SAASG,GAAsB,CAC7B,OAAI,OAAO,SAAa,IAAoB,IACrC,GAAG,SAAS,QAAQ,GAAG,SAAS,MAAM,GAAG,SAAS,IAAI,EAC/D,CClEA,IAAMa,EAAiB,qBACjBC,GACJ,+GACIC,GAAmB,IAAI,IAAI,CAAC,SAAU,IAAK,QAAS,SAAU,WAAY,SAAS,CAAC,EACpFC,GAAoB,IAAI,IAAI,CAAC,QAAS,SAAU,UAAU,CAAC,EAEpDC,EAAN,KAAwB,CAG7B,YACmBC,EAAY,EACZC,EAAW,IACXC,EAAW,GAC5B,CAHiB,eAAAF,EACA,cAAAC,EACA,cAAAC,CAChB,CANK,OAAqD,CAAC,EAQ9D,KAAKC,EAAWC,EAAWC,EAAoB,CAK7C,OAJA,KAAK,OAAS,KAAK,OAAO,OACvBC,GAAMD,EAAIC,EAAE,GAAK,KAAK,UAAY,KAAK,MAAMA,EAAE,EAAIH,EAAGG,EAAE,EAAIF,CAAC,GAAK,KAAK,QAC1E,EACA,KAAK,OAAO,KAAK,CAAE,EAAAD,EAAG,EAAAC,EAAG,EAAAC,CAAE,CAAC,EACxB,KAAK,OAAO,QAAU,KAAK,WAC7B,KAAK,OAAS,CAAC,EACR,IAEF,EACT,CACF,EAEaE,EAAN,KAAsB,CAG3B,YAA6BN,EAAW,IAAQ,CAAnB,cAAAA,CAAoB,CAFzC,OAA6C,CAAC,EAItD,KAAKO,EAAcH,EAAgD,CAEjE,GADA,KAAK,OAAS,CAAC,GAAG,KAAK,OAAQ,CAAE,KAAAG,EAAM,EAAAH,CAAE,CAAC,EAAE,OAAQI,GAAMJ,EAAII,EAAE,GAAK,KAAK,QAAQ,EAAE,MAAM,EAAE,EACxF,KAAK,OAAO,OAAS,EAAG,OAAO,KACnC,GAAM,CAACC,EAAGC,EAAGL,EAAGM,CAAC,EAAI,KAAK,OAAO,IAAKH,GAAMA,EAAE,IAAI,EAClD,OAAIC,IAAMJ,GAAKK,IAAMC,GAAKF,IAAMC,GAC9B,KAAK,OAAS,CAAC,EACR,CAAE,KAAMD,EAAG,GAAIC,CAAE,GAEnB,IACT,CACF,EAWO,SAASE,EAAmBC,EAA4C,CAC7E,GAAI,CAACA,EAAI,OAAO,KAChB,IAAMC,EAAaD,EAAG,UAAUlB,EAAoB,GAAK,KACzD,GAAImB,EAAY,OAAOA,EACvB,IAAMC,EAAMF,EAAG,QAAQ,YAAY,EACnC,GAAIjB,GAAiB,IAAImB,CAAG,EAAG,OAAOF,EACtC,IAAMG,EAAOH,EAAG,aAAa,MAAM,EACnC,OAAIG,IAAS,UAAYA,IAAS,QAAUA,IAAS,YAAcA,IAAS,MAAcH,EACnF,IACT,CAEO,SAASI,GAAkBJ,EAAiC,CACjE,IAAMK,EAASN,EAAmBC,CAAE,EACpC,GAAI,CAACK,EAAQ,MAAO,GAGpB,GAFIA,EAAO,WAAa,IACpBA,EAAO,aAAa,UAAU,IAAM,MACpCA,EAAO,aAAa,eAAe,IAAM,OAAQ,MAAO,GAE5D,GADYA,EAAO,QAAQ,YAAY,IAC3B,IAAK,CACf,IAAMC,EAAOD,EAAO,aAAa,MAAM,EACvC,GAAI,CAACC,GAAQA,IAAS,KAAOA,EAAK,WAAW,aAAa,EAAG,MAAO,EACtE,CACA,MAAO,EACT,CAEO,SAASC,EAAgBP,EAAgD,CAC9E,IAAMK,EAASN,EAAmBC,CAAE,GAAKA,EACzC,GAAI,CAACK,EAAQ,MAAO,CAAC,EACrB,IAAMG,EAA8B,CAAE,QAASH,EAAO,QAAQ,YAAY,CAAE,EACxEA,EAAO,KAAIG,EAAI,WAAaH,EAAO,IACvC,IAAMF,EAAOE,EAAO,aAAa,MAAM,EACnCF,IAAMK,EAAI,aAAeL,GAE7B,IAAMM,GADQJ,EAAO,aAAa,YAAY,GAAKA,EAAO,aAAe,IACtD,QAAQ,OAAQ,GAAG,EAAE,KAAK,EAAE,MAAM,EAAG,EAAE,EACtDI,IAAMD,EAAI,aAAeC,GAC7B,IAAMC,EAAOL,EAAO,aAAa,MAAM,GAAKA,EAAO,aAAa,aAAa,EAC7E,OAAIK,IAAMF,EAAI,aAAeE,GACtBF,CACT,CAEO,SAASG,EAAgBC,EAAeC,EAAuB,CACpE,GAAI,CACF,IAAMC,EAAM,IAAI,IAAIF,EAAOC,CAAI,EAC/B,MAAO,GAAGC,EAAI,MAAM,GAAGA,EAAI,QAAQ,EACrC,MAAQ,CACN,OAAOF,EAAM,MAAM,GAAG,EAAE,CAAC,EAAE,MAAM,GAAG,EAAE,CAAC,CACzC,CACF,CAEO,SAASG,GAAaC,EAAyB,CACpD,GAAIA,aAAkB,MAAO,OAAOA,EAAO,QAC3C,GAAI,OAAOA,GAAW,SAAU,OAAOA,EACvC,GAAI,CACF,OAAO,KAAK,UAAUA,CAAM,CAC9B,MAAQ,CACN,OAAO,OAAOA,CAAM,CACtB,CACF,CAEO,SAASC,GAAaC,EAAuB,CAClD,OAAO,OAAOA,GAAQ,UAAYA,IAAQ,MAASA,EAA2B,OAAS,YACzF,CAEO,SAASC,GAAaL,EAAaM,EAAoC,CAC5E,OAAOA,EAAO,KAAMC,GAAWA,EAAO,OAAS,GAAKP,EAAI,WAAWO,CAAM,CAAC,CAC5E,CAUO,SAASC,EAAkBC,EAAsB,CACtD,IAAMC,EAASD,EAAK,YAAY,EAAE,MAAM,GAAG,EAAE,OAAO,OAAO,EAC3D,OAAIC,EAAO,QAAU,EAAUA,EAAO,KAAK,GAAG,EACvCA,EAAO,MAAM,EAAE,EAAE,KAAK,GAAG,CAClC,CAcO,SAASC,GAAoBX,EAAaY,EAAiBC,EAAkC,CAAC,EAAY,CAC/G,IAAItB,EACAuB,EACJ,GAAI,CACFvB,EAAS,IAAI,IAAIS,EAAKY,CAAO,EAC7BE,EAAO,IAAI,IAAIF,CAAO,CACxB,MAAQ,CACN,MAAO,EACT,CAGA,GAFIrB,EAAO,WAAa,SAAWA,EAAO,WAAa,UACnDA,EAAO,OAASuB,EAAK,MACrBD,EAAa,KAAME,GAAMA,EAAE,OAAS,GAAKxB,EAAO,SAAWwB,CAAC,EAAG,MAAO,GAC1E,IAAMC,EAAeR,EAAkBjB,EAAO,QAAQ,EACtD,OAAOyB,EAAa,OAAS,GAAKA,IAAiBR,EAAkBM,EAAK,QAAQ,CACpF,CAUO,SAASG,GAAgBC,EAA+D,CAC7F,OAAIA,EAAQ,QAAgB,KACxB,CAACA,EAAQ,SAAWA,EAAQ,SAAW,MAAQA,EAAQ,QAAU,KAAa,aAC3EA,EAAQ,YAAcA,EAAQ,OAAS,eAAiB,IACjE,CAEO,SAASC,GACdC,EACe,CACf,GAAI,CAEF,IAAMC,EADQD,GAAM,mBAAmB,YAAY,IAAI,CAAC,GAClC,eACtB,OAAO,OAAOC,GAAW,UAAYA,EAAS,EAAIA,EAAS,IAC7D,MAAQ,CACN,OAAO,IACT,CACF,CAOA,SAASC,GAAQC,EAA+B,CAC9C,OACEA,EAAK,IACLA,EAAK,aAAa,MAAM,GACxBA,EAAK,aAAa,aAAa,GAC/B1B,EAAgB0B,EAAK,aAAa,QAAQ,GAAK,GAAI,SAAS,IAAI,EAAE,QAAQ,SAAS,OAAQ,EAAE,GAC7F,MAEJ,CAEA,SAASC,IAAsB,CAC7B,OAAO,OAAO,SAAa,IAAc,IAAM,SAAS,UAAY,GACtE,CAEO,SAASC,EAAuBC,EAA+C,CACpF,IAAMC,EAAwB,CAAE,WAAY,IAAG,GAAc,SAAU,IAAG,EAAa,EACvF,GAAI,OAAO,OAAW,KAAe,OAAO,SAAa,IAAa,OAAOA,EAE7E,IAAMC,EAAI,OACV,GAAIA,EAAE7D,CAAc,EAAG,OAAO4D,EAC9BC,EAAE7D,CAAc,EAAI,GAEpB,IAAM8D,EAAO,CAACC,EAAoBC,IAA8C,CAC9E,GAAI,CACFL,EAAK,WAAW,CAAE,KAAAI,EAAM,WAAY,CAAE,KAAMN,GAAY,EAAG,GAAGO,CAAW,CAAE,CAAC,CAC9E,MAAQ,CACN,MACF,CACF,EAEMC,EAA8B,CAAC,EAC/BC,EAAO,IAAI9D,EAAkBuD,EAAK,WAAYA,EAAK,aAAcA,EAAK,YAAY,EAClFQ,EAAU,IAAIvD,EAAgB+C,EAAK,eAAe,EAClDS,EAAST,EAAK,eAAiB,IAC/BU,EAAU,IAAI,IAEdC,EAAWC,GAAyB,CACxC,IAAMpD,EAAKoD,EAAG,kBAAkB,QAAWA,EAAG,OAAoC,KAC9EhD,GAAkBJ,CAAE,GAAG2C,EAAK,aAAcpC,EAAgBP,CAAE,CAAC,EAC7D+C,EAAK,KAAKK,EAAG,QAASA,EAAG,QAASA,EAAG,WAAa,KAAK,IAAI,CAAC,GAC9DT,EAAK,aAAcpC,EAAgBP,CAAE,CAAC,CAE1C,EACA,SAAS,iBAAiB,QAASmD,EAAS,EAAI,EAChDL,EAAS,KAAK,IAAM,SAAS,oBAAoB,QAASK,EAAS,EAAI,CAAC,EAExE,IAAME,EAAWD,GAAyB,CACxCT,EAAK,WAAY,CACf,QAAS,OAAOS,EAAG,SAAW,EAAE,EAAE,MAAM,EAAG,GAAG,EAC9C,GAAIA,EAAG,SAAW,CAAE,OAAQzC,EAAgByC,EAAG,SAAU,SAAS,IAAI,CAAE,EAAI,CAAC,EAC7E,GAAIA,EAAG,OAAS,CAAE,KAAMA,EAAG,MAAO,EAAI,CAAC,CACzC,CAAC,CACH,EACA,OAAO,iBAAiB,QAASC,CAAO,EACxCP,EAAS,KAAK,IAAM,OAAO,oBAAoB,QAASO,CAAO,CAAC,EAEhE,IAAMC,EAAeF,GAAoC,CACvDT,EAAK,WAAY,CAAE,QAAS5B,GAAaqC,EAAG,MAAM,EAAE,MAAM,EAAG,GAAG,EAAG,oBAAqB,EAAK,CAAC,CAChG,EACA,OAAO,iBAAiB,qBAAsBE,CAAW,EACzDR,EAAS,KAAK,IAAM,OAAO,oBAAoB,qBAAsBQ,CAAW,CAAC,EAEjF,IAAMlC,GAAUoB,EAAK,YAAc,CAAC,GAAG,IAAK1B,GAAQH,EAAgBG,EAAK,SAAS,IAAI,CAAC,EACjFyC,GAAcf,EAAK,YAAc,CAAC,GAAG,IAAK,GAAM,CACpD,GAAI,CACF,OAAO,IAAI,IAAI,EAAG,SAAS,IAAI,EAAE,MACnC,MAAQ,CACN,MAAO,EACT,CACF,CAAC,EACKgB,EAAgB,CACpB1C,EACA2C,EACAC,EACAvB,EACAwB,EAAiD,CAAC,IACzC,CACT,IAAMtD,EAASM,EAAgBG,EAAK,SAAS,IAAI,EAEjD,GADIK,GAAad,EAAQe,CAAM,GAC3B,CAACK,GAAoBpB,EAAQ,SAAS,KAAMkD,CAAU,EAAG,OAC7D,IAAMK,EAAa,YAAY,IAAI,EAAIF,EACjCd,EAAOb,GAAgB,CAAE,OAAAI,EAAQ,WAAAyB,EAAY,OAAAX,EAAQ,GAAGU,CAAM,CAAC,EAChEf,GACLD,EAAKC,EAAM,CACT,IAAKvC,EACL,OAAAoD,EACA,YAAa,KAAK,MAAMG,CAAU,EAClC,GAAID,EAAM,OAAS,CAAC,EAAIxB,IAAW,KAAO,CAAE,OAAAA,CAAO,EAAI,CAAE,OAAQ,EAAK,CACxE,CAAC,CACH,EAEM0B,EAAgB,OAAO,MAC7B,GAAI,OAAOA,GAAkB,WAAY,CACvC,IAAMC,EAA+B,MAAOlD,EAAOmD,IAAS,CAC1D,IAAML,EAAU,YAAY,IAAI,EAC1B5C,EACJ,OAAOF,GAAU,SAAWA,EAAQA,aAAiB,IAAMA,EAAM,KAAOA,EAAM,IAC1E6C,GAAUM,GAAM,SAAWnD,aAAiB,QAAUA,EAAM,OAAS,QAAQ,YAAY,EAC/F,GAAI,CACF,IAAMoD,EAAM,MAAMH,EAAcjD,EAAOmD,CAAI,EAC3C,OAAAP,EAAc1C,EAAK2C,EAAQC,EAASM,EAAI,OAAQ,CAC9C,OAAQA,EAAI,OAAS,UAAYA,EAAI,OAAS,gBAChD,CAAC,EACMA,CACT,OAAS9C,EAAK,CACZ,MAAAsC,EAAc1C,EAAK2C,EAAQC,EAAS,KAAM,CAAE,QAASzC,GAAaC,CAAG,CAAE,CAAC,EAClEA,CACR,CACF,EACA,OAAO,MAAQ4C,EACfhB,EAAS,KAAK,IAAM,CAClB,OAAO,MAAQe,CACjB,CAAC,CACH,CAEA,IAAMI,EAAW,OAAO,gBAAmB,WAAa,eAAe,UAAY,KACnF,GAAIA,EAAU,CACZ,IAAMC,EAAeD,EAAS,KACxBE,EAAeF,EAAS,KACxBG,EAAW,IAAI,QACrBH,EAAS,KAAO,SAAgCR,EAAgB3C,KAAsBuD,EAAiB,CACrG,OAAAD,EAAS,IAAI,KAAM,CAAE,OAAQ,OAAOX,CAAM,EAAE,YAAY,EAAG,IAAK,OAAO3C,CAAG,CAAE,CAAC,EACrEoD,EAA8C,MAAM,KAAM,CAACT,EAAQ3C,EAAK,GAAGuD,CAAI,CAAC,CAC1F,EACAJ,EAAS,KAAO,SAAgCK,EAAiD,CAC/F,IAAMC,EAAUH,EAAS,IAAI,IAAI,EACjC,GAAIG,EAAS,CACX,IAAMb,EAAU,YAAY,IAAI,EAC5Bc,EAAU,GACd,KAAK,iBAAiB,QAAS,IAAM,CACnCA,EAAU,EACZ,CAAC,EACD,KAAK,iBAAiB,UAAW,IAAM,CACrChB,EAAce,EAAQ,IAAKA,EAAQ,OAAQb,EAAS,KAAK,SAAW,EAAI,KAAO,KAAK,OAAQ,CAAE,QAAAc,CAAQ,CAAC,CACzG,CAAC,CACH,CACA,OAAOL,EAAa,KAAK,KAAMG,CAAI,CACrC,EACAxB,EAAS,KAAK,IAAM,CAClBmB,EAAS,KAAOC,EAChBD,EAAS,KAAOE,CAClB,CAAC,CACH,CAEA,IAAMM,EAAaxC,GAAe,OAAO,YAAgB,IAAc,OAAY,WAAW,EAC1FwC,IAAe,MAAQA,GAAc,KACvC9B,EAAK,aAAc,CAAE,SAAU,GAAM,OAAQ8B,EAAY,IAAK9D,EAAgB,SAAS,IAAI,CAAE,CAAC,EAGhG,IAAM+D,EAAatB,GAAyB,CAC1C,IAAMpD,EAAKoD,EAAG,OACd,GAAI,CAACpD,GAAM,CAAChB,GAAkB,IAAIgB,EAAG,QAAQ,YAAY,CAAC,EAAG,OAC7D,IAAMqC,EAAQrC,EAAwB,KACtC,GAAI,CAACqC,EAAM,OACX,IAAMsC,EAAQzB,EAAQ,IAAIb,CAAI,GAAK,CAAE,KAAMD,GAAQC,CAAI,EAAG,OAAQ,IAAI,GAAc,EAC9EuC,EAAS5E,EAAwB,MAAQA,EAAG,IAAMA,EAAG,QAAQ,YAAY,EAC/E2E,EAAM,OAAO,IAAIC,CAAK,EACtB1B,EAAQ,IAAIb,EAAMsC,CAAK,CACzB,EACA,SAAS,iBAAiB,UAAWD,EAAW,EAAI,EACpD5B,EAAS,KAAK,IAAM,SAAS,oBAAoB,UAAW4B,EAAW,EAAI,CAAC,EAE5E,IAAMG,EAAYzB,GAAoB,CAChCA,EAAG,kBAAkB,iBAAiBF,EAAQ,OAAOE,EAAG,MAAM,CACpE,EACA,SAAS,iBAAiB,SAAUyB,EAAU,EAAI,EAClD/B,EAAS,KAAK,IAAM,SAAS,oBAAoB,SAAU+B,EAAU,EAAI,CAAC,EAE1E,IAAMC,EAAgB,IAAY,CAChC,QAAWH,KAASzB,EAAQ,OAAO,EACjCP,EAAK,eAAgB,CAAE,KAAMgC,EAAM,KAAM,eAAgBA,EAAM,OAAO,IAAK,CAAC,EAE9EzB,EAAQ,MAAM,CAChB,EACA,cAAO,iBAAiB,WAAY4B,CAAa,EACjDhC,EAAS,KAAK,IAAM,OAAO,oBAAoB,WAAYgC,CAAa,CAAC,EAElE,CACL,WAAapF,GAAiB,CAC5BoF,EAAc,EACd,IAAMC,EAAO/B,EAAQ,KAAKtD,EAAM,KAAK,IAAI,CAAC,EACtCqF,GAAMpC,EAAK,WAAY,CAAE,KAAMoC,EAAK,KAAM,GAAIA,EAAK,EAAG,CAAC,CAC7D,EACA,SAAU,IAAM,CACd,QAAWC,KAAWlC,EAAUkC,EAAQ,EACxC9B,EAAQ,MAAM,EACdR,EAAE7D,CAAc,EAAI,EACtB,CACF,CACF,CC3YA,IAAMoG,GAAW,CACf,aACA,aACA,eACA,WACA,aACF,EAEMC,EAAsB,gBACtBC,EAA0B,oBAKzB,SAASC,GAASC,EAA6B,CACpD,GAAI,CACF,IAAMC,EAAM,IAAI,IAAID,CAAI,EACxB,MAAO,GAAGC,EAAI,MAAM,GAAGA,EAAI,QAAQ,EACrC,MAAQ,CACN,OAAO,IACT,CACF,CAGO,SAASC,GAASF,EAA0B,CACjD,IAAMG,EAAkB,CAAC,EACzB,GAAI,CACF,IAAMC,EAAS,IAAI,IAAIJ,CAAI,EAAE,aAC7B,QAAWK,KAAOT,GAAU,CAC1B,IAAMU,EAAQF,EAAO,IAAIC,CAAG,GAAG,KAAK,EAChCC,IAAOH,EAAIE,CAAG,EAAIC,EAAM,YAAY,EAAE,MAAM,EAAG,GAAG,EACxD,CACF,MAAQ,CAER,CACA,OAAOH,CACT,CAgBO,SAASI,GAAeC,EAAqB,CAClD,GAAI,CAACA,EAAI,MAAO,CAAC,EACjB,IAAMC,EAAID,EAAG,YAAY,EAEnBE,EAAUD,EAAE,SAAS,MAAM,EAC7B,OACAA,EAAE,SAAS,MAAM,GAAKA,EAAE,SAAS,OAAO,EACtC,QACAA,EAAE,SAAS,gBAAgB,EACzB,mBACAA,EAAE,SAAS,SAAS,GAAKA,EAAE,SAAS,OAAO,EACzC,UACAA,EAAE,SAAS,QAAQ,GAAKA,EAAE,SAAS,OAAO,EACxC,SACAA,EAAE,SAAS,QAAQ,EACjB,SACA,OAERE,EAAKF,EAAE,SAAS,QAAQ,GAAKA,EAAE,SAAS,MAAM,GAAKA,EAAE,SAAS,MAAM,EACtE,MACAA,EAAE,SAAS,SAAS,EAClB,UACAA,EAAE,SAAS,SAAS,EAClB,UACAA,EAAE,SAAS,QAAQ,GAAKA,EAAE,SAAS,WAAW,EAC5C,QACAA,EAAE,SAAS,MAAM,EACf,WACAA,EAAE,SAAS,OAAO,EAChB,QACA,OAIRG,EAASH,EAAE,SAAS,MAAM,GAAMA,EAAE,SAAS,SAAS,GAAK,CAACA,EAAE,SAAS,QAAQ,EAC/E,SACAA,EAAE,SAAS,MAAM,GAAKA,EAAE,SAAS,QAAQ,GAAKA,EAAE,SAAS,MAAM,EAC7D,SACA,UAEN,MAAO,CACL,GAAIC,EAAU,CAAE,QAAAA,CAAQ,EAAI,CAAC,EAC7B,GAAIC,EAAK,CAAE,GAAAA,CAAG,EAAI,CAAC,EACnB,OAAAC,CACF,CACF,CAIA,SAASC,GAAYC,EAA8BT,EAA4B,CAC7E,GAAI,CAEF,OADUS,IAAY,UAAY,eAAiB,cAC1C,QAAQT,CAAG,CACtB,MAAQ,CACN,OAAO,IACT,CACF,CAEA,SAASU,GAAaD,EAA8BT,EAAaC,EAAqB,CACpF,GAAI,EACQQ,IAAY,UAAY,eAAiB,cACjD,QAAQT,EAAKC,CAAK,CACtB,MAAQ,CAER,CACF,CAEA,SAASU,IAAmB,CAC1B,GAAI,CACF,OAAO,OAAO,WAAW,CAC3B,MAAQ,CACN,MAAO,OAAO,KAAK,IAAI,EAAE,SAAS,EAAE,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,EAAG,EAAE,CAAC,EAClF,CACF,CAGO,SAASC,IAA2B,CACzC,IAAMC,EAAWL,GAAY,UAAWhB,CAAmB,EAC3D,GAAIqB,EAAU,OAAOA,EACrB,IAAMC,EAAQH,GAAS,EACvB,OAAAD,GAAa,UAAWlB,EAAqBsB,CAAK,EAC3CA,CACT,CAMO,SAASC,GAAmBpB,EAAcqB,EAAkBC,EAAiC,CAClG,IAAMC,EAASV,GAAY,UAAWf,CAAuB,EAC7D,GAAIyB,EACF,GAAI,CACF,OAAO,KAAK,MAAMA,CAAM,CAC1B,MAAQ,CAER,CAGF,IAAMC,EAA0B,CAAE,GAAGtB,GAASF,CAAI,CAAE,EAE9CyB,EAAeC,GAAOL,CAAQ,EAEpC,GAAII,GAAgBA,IAAiBH,EAAa,CAChD,IAAMK,EAAQ5B,GAASsB,CAAQ,EAC3BM,IAAOH,EAAY,SAAWG,GAClCH,EAAY,cAAgBC,CAC9B,CAEA,OAAAV,GAAa,UAAWjB,EAAyB,KAAK,UAAU0B,CAAW,CAAC,EACrEA,CACT,CAEA,SAASE,GAAO1B,EAA6B,CAC3C,GAAI,CACF,OAAO,IAAI,IAAIA,CAAI,EAAE,SAAS,YAAY,GAAK,IACjD,MAAQ,CACN,OAAO,IACT,CACF,CAQO,SAAS4B,IAA8B,CAC5C,GAAI,OAAO,OAAW,KAAe,OAAO,SAAa,IAAa,MAAO,CAAC,EAE9E,IAAM5B,EAAO,SAAS,KAChBqB,EAAW,OAAO,SAAa,IAAc,SAAS,SAAW,GACjEQ,EAAO,SAAS,SAAS,YAAY,EAErCrB,EAAK,OAAO,UAAc,IAAc,UAAU,UAAY,GAC9D,CAAE,QAAAE,EAAS,GAAAC,EAAI,OAAAC,CAAO,EAAIL,GAAeC,CAAE,EAE3CP,EAAMF,GAASC,CAAI,EAEzB,MAAO,CACL,GAAIC,EAAM,CAAE,IAAAA,CAAI,EAAI,CAAC,EACrB,KAAM,SAAS,UAAY,IAC3B,KAAA4B,EACA,GAAInB,EAAU,CAAE,QAAAA,CAAQ,EAAI,CAAC,EAC7B,GAAIC,EAAK,CAAE,GAAAA,CAAG,EAAI,CAAC,EACnB,GAAIC,EAAS,CAAE,OAAAA,CAAO,EAAI,CAAC,EAC3B,GAAI,OAAO,UAAc,KAAe,UAAU,SAC9C,CAAE,OAAQ,UAAU,QAAS,EAC7B,CAAC,EACL,WAAYK,GAAiB,EAC7B,GAAGG,GAAmBpB,EAAMqB,EAAUQ,CAAI,CAC5C,CACF,CChOA,SAASC,GAAMC,EAAuB,CACpC,IAAIC,EAAO,WACX,QAASC,EAAI,EAAGA,EAAIF,EAAM,OAAQE,IAChCD,GAAQD,EAAM,WAAWE,CAAC,EAC1BD,EAAO,KAAK,KAAKA,EAAM,QAAU,IAAM,EAEzC,OAAOA,IAAS,CAClB,CAEO,SAASE,GAAaC,EAAkBC,EAA4B,CACzE,OAAON,GAAM,GAAGK,CAAQ,IAAIC,CAAU,EAAE,EAAI,GAC9C,CAEO,SAASC,GAAUF,EAAkBC,EAAoBE,EAA+B,CAC7F,OAAOJ,GAAaC,EAAUC,CAAU,EAAIE,EAAa,UAAY,QACvE,CAEO,SAASC,GAAoBC,EAAkBC,EAAuB,CAC3E,IAAMC,EAAIF,EAAS,MAAM,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,EAAE,OAAO,OAAO,EACvDG,EAAIF,EAAK,MAAM,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,EAAE,OAAO,OAAO,EACzD,OAAIC,EAAE,SAAWC,EAAE,OAAe,GAC3BD,EAAE,MAAM,CAACE,EAASX,IAAMW,EAAQ,WAAW,GAAG,GAAKA,IAAYD,EAAEV,CAAC,CAAC,CAC5E,CCOA,IAAMY,GAAa,IAAS,IACtBC,GAAiB,GAAK,IACtBC,GAAmB,IACnBC,GAAW,qBACXC,GAAgB,0BAChBC,GAAa,EAEbC,GAAU;AAAA;AAAA;AAAA;AAAA;AAAA,sEAOT,SAASC,EAAcC,EAAsB,CAClD,OAAOA,EAAK,QAAQ,OAAQ,GAAG,EAAE,KAAK,CACxC,CAEO,SAASC,GAAaC,EAAgC,CAC3D,IAAMC,EAAQD,GAAuC,QACrD,OAAO,MAAM,QAAQC,CAAI,EAAIA,EAAK,OAAOC,EAAQ,EAAI,CAAC,CACxD,CAEA,SAASC,GAASC,EAAuC,CACvD,IAAMC,EAAID,EACV,OAAIC,GAAG,OAAS,OAAe,IACvBA,GAAG,OAAS,YAAcA,GAAG,OAAS,SAAW,OAAOA,EAAE,OAAU,UAAYA,EAAE,MAAM,OAAS,CAC3G,CAEA,SAASH,GAASE,EAAwC,CACxD,IAAME,EAAIF,EACV,OACE,OAAOE,GAAG,IAAO,WAChBA,EAAE,OAAS,QAAUA,EAAE,OAAS,aACjC,OAAOA,EAAE,MAAS,UAClBH,GAASG,EAAE,MAAM,GACjB,OAAOA,EAAE,SAAS,MAAS,UAC3B,OAAOA,EAAE,aAAgB,QAE7B,CAEO,SAASC,GACdC,EACAC,EACAC,EACkD,CAClD,OAAKA,EACEF,EACJ,OAAQF,GAAMK,GAAoBL,EAAE,KAAMG,CAAI,CAAC,EAC/C,IAAKG,IAAY,CAAE,OAAAA,EAAQ,IAAKC,GAAUD,EAAO,GAAIF,EAAYE,EAAO,WAAW,CAAE,EAAE,EAHlE,CAAC,CAI3B,CAEA,SAASE,EAAQC,EAAgD,CAC/D,GAAI,CACF,OAAOA,IAAS,QAAU,OAAO,aAAe,OAAO,cACzD,MAAQ,CACN,MACF,CACF,CAEA,SAASC,EAAQC,EAA4BC,EAA0B,CACrE,GAAI,CACF,OAAO,IAAI,IAAI,KAAK,MAAMD,GAAO,QAAQC,CAAG,GAAK,IAAI,CAAa,CACpE,MAAQ,CACN,OAAO,IAAI,GACb,CACF,CAEA,SAASC,GAASF,EAA4BC,EAAad,EAAqB,CAC9E,GAAI,CACF,IAAMgB,EAAMJ,EAAQC,EAAOC,CAAG,EAC9BE,EAAI,IAAIhB,CAAK,EACba,GAAO,QAAQC,EAAK,KAAK,UAAU,CAAC,GAAGE,CAAG,EAAE,MAAM,IAAI,CAAC,CAAC,CAC1D,MAAQ,CACN,MACF,CACF,CAEA,SAASC,GAAMC,EAAkC,CAC/C,GAAI,CACF,OAAO,SAAS,cAAcA,CAAQ,CACxC,MAAQ,CACN,OAAO,IACT,CACF,CAEA,IAAMC,GAAmB,4EACnBC,GAAyB,IAExB,SAASC,GAAWrB,EAA+B,CACxD,IAAMsB,EAAS7B,EAAcO,CAAK,EAC9BuB,EAAuB,KACvBC,EAAa,OAAO,kBAClBC,EAAQ,SAAS,iBAAiBN,EAAgB,EAClDO,EAAQ,KAAK,IAAID,EAAM,OAAQL,EAAsB,EAC3D,QAASO,EAAI,EAAGA,EAAID,EAAOC,GAAK,EAAG,CACjC,IAAMC,EAAKH,EAAME,CAAC,EACZE,EAAMD,EAAG,aAAe,GAC1BC,EAAI,QAAUL,GAAc/B,EAAcoC,CAAG,IAAMP,IACvDC,EAAOK,EACPJ,EAAaK,EAAI,OACnB,CACA,OAAON,CACT,CAEA,SAASO,GAAWC,EAAsC,CACxD,OAAIA,EAAO,OAAS,OAAe,KAC5BA,EAAO,OAAS,WAAad,GAAMc,EAAO,KAAK,EAAIV,GAAWU,EAAO,KAAK,CACnF,CAEA,SAASC,GAAaJ,EAAaK,EAA+C,CAChF,IAAMC,EAAS,SAAS,iBAAiBN,EAAI,WAAW,SAAS,EACjE,QAASO,EAAOD,EAAO,SAAS,EAAGC,EAAMA,EAAOD,EAAO,SAAS,EAC9D,GAAID,EAAMxC,EAAc0C,EAAK,aAAe,EAAE,CAAC,EAAG,OAAOA,EAE3D,OAAO,IACT,CAQO,SAASC,GAAsBC,EAAmD,CACvF,GAAI,OAAO,OAAW,KAAe,OAAO,SAAa,IACvD,MAAO,CAAE,WAAY,IAAG,GAAc,QAAS,SAAS,GAAc,SAAU,IAAG,EAAa,EAGlG,IAAIjC,EAA2B,CAAC,EAC5BkC,EAAY,EACZC,EAA8C,KAC9CC,EAA0B,KAC1BC,EAA2B,KACzBC,EAAS,IAAI,IACbC,EAAO,IAAI,IAEXC,EAAOC,GAA0B,CACjCR,EAAK,OAAO,QAAQ,KAAK,WAAWQ,CAAO,EAAE,CACnD,EAEMC,EAAS,CAACtC,EAAuBuC,IAAyB,CAC9D,IAAMjC,EAAM,GAAGN,EAAO,EAAE,IAAIuC,CAAG,GACzBC,EAAUtC,EAAQ,SAAS,EAC7BE,EAAQoC,EAAS3D,EAAQ,EAAE,IAAIyB,CAAG,IACtCC,GAASiC,EAAS3D,GAAUyB,CAAG,EAC/BuB,EAAK,MAAM,mBAAoB,CAAE,UAAW7B,EAAO,GAAI,IAAAuC,EAAK,KAAMvC,EAAO,IAAK,CAAC,EACjF,EAEMyC,EAAmB,CAACrB,EAAapB,IAAuC,CAC5E,IAAM0C,EAAW1C,EAAO,cAAgBf,EAAce,EAAO,aAAa,EAAI,KACxE2C,EAAO1D,EAAce,EAAO,QAAQ,IAAI,EAC9C,OAAOwB,GAAaJ,EAAKlC,GAASA,EAAK,OAAS,IAAMA,IAASyD,GAAQD,IAAa,MAAQxD,IAASwD,EAAS,CAChH,EAEME,EAAY,CAACxB,EAAapB,IAAmC,CACjE,IAAM2C,EAAO1D,EAAce,EAAO,QAAQ,IAAI,EACxC6C,EAAUX,EAAO,IAAIlC,EAAO,EAAE,EACpC,GAAI6C,GAAWA,EAAQ,KAAK,aAAe5D,EAAc4D,EAAQ,KAAK,aAAe,EAAE,IAAMF,EAAM,MAAO,GAC1G,IAAMhB,EAAOc,EAAiBrB,EAAIpB,CAAM,EACxC,GAAI,CAAC2B,EAAM,MAAO,GAClB,IAAMzC,EAAOyC,EAAK,aAAe,GACjC,OAAI1C,EAAcC,CAAI,IAAMyD,IAC1BT,EAAO,IAAIlC,EAAO,GAAI,CAAE,KAAA2B,EAAM,SAAUzC,EAAM,KAAMc,EAAO,QAAQ,IAAK,CAAC,EACzE2B,EAAK,YAAczC,EAAK,QAAQA,EAAK,KAAK,EAAGc,EAAO,QAAQ,IAAI,GAE3D,EACT,EAEM8C,EAAeC,GAAqB,CACxC,IAAMC,EAAUd,EAAO,IAAIa,CAAE,EACxBC,IACDA,EAAQ,KAAK,aAAe/D,EAAc+D,EAAQ,KAAK,aAAe,EAAE,IAAM/D,EAAc+D,EAAQ,IAAI,IAC1GA,EAAQ,KAAK,YAAcA,EAAQ,UAErCd,EAAO,OAAOa,CAAE,EAClB,EAEME,EAAa,IAAyB,CAC1C,GAAIjB,EAAM,OAAOA,EACjB,GAAI,CAAC,SAAS,KAAM,OAAO,KAC3BC,EAAO,SAAS,cAAc,eAAe,EAC7CA,EAAK,aAAa,kBAAmB,GAAG,EACxCA,EAAK,MAAM,QAAU,sFACrBD,EAAOC,EAAK,aAAa,CAAE,KAAM,QAAS,CAAC,EAC3C,IAAMiB,EAAQ,SAAS,cAAc,OAAO,EAC5C,OAAAA,EAAM,YAAclE,GACpBgD,EAAK,YAAYkB,CAAK,EACtB,SAAS,KAAK,YAAYjB,CAAI,EACvBD,CACT,EAEMmB,EAAU,CAAC5B,EAAwBvB,IAAmC,CAE1E,GADImC,EAAK,IAAInC,EAAO,EAAE,GAClBI,EAAQF,EAAQ,OAAO,EAAGpB,EAAa,EAAE,IAAIkB,EAAO,EAAE,EAAG,MAAO,GACpE,IAAMoD,EAASH,EAAW,EAC1B,GAAI,CAACG,EAAQ,MAAO,GAEpB,IAAMC,EAAO,SAAS,cAAc,KAAK,EACzCA,EAAK,UAAY,MACjBA,EAAK,aAAa,OAAQ,SAAS,EACnC,IAAMnE,EAAO,SAAS,cAAc,GAAG,EACvCA,EAAK,YAAcc,EAAO,QAAQ,KAClC,IAAMsD,EAAQ,SAAS,cAAc,QAAQ,EAC7CA,EAAM,KAAO,SACbA,EAAM,aAAa,aAAc,QAAQ,EACzCA,EAAM,YAAc,OACpBD,EAAK,OAAOnE,EAAMoE,CAAK,EACvBD,EAAK,MAAM,IAAM,UACjBA,EAAK,MAAM,KAAO,UAClBD,EAAO,YAAYC,CAAI,EAEvB,IAAME,EAAS,IAAY,CACzBF,EAAK,OAAO,EACZ,OAAO,oBAAoB,SAAUG,EAAU,EAAI,EACnD,OAAO,oBAAoB,SAAUA,CAAQ,EAC7CrB,EAAK,OAAOnC,EAAO,EAAE,CACvB,EAEA,SAASwD,GAAiB,CACxB,GAAI,CAACjC,EAAQ,CACX,IAAMkC,EAASJ,EAAK,sBAAsB,EAC1CA,EAAK,MAAM,IAAM,GAAG,KAAK,IAAI,EAAG,OAAO,YAAcI,EAAO,OAAS,EAAE,CAAC,KACxEJ,EAAK,MAAM,KAAO,GAAG,KAAK,IAAI,EAAG,OAAO,WAAaI,EAAO,MAAQ,EAAE,CAAC,KACvE,MACF,CACA,GAAI,CAAClC,EAAO,YAAa,CACvBgC,EAAO,EACP,MACF,CACA,IAAM9D,EAAI8B,EAAO,sBAAsB,EACjC7B,EAAI2D,EAAK,sBAAsB,EACjCK,EAAMjE,EAAE,OAASV,GACjB2E,EAAMhE,EAAE,OAAS,OAAO,YAAc,IAAGgE,EAAMjE,EAAE,IAAMC,EAAE,OAASX,IACtE,IAAM4E,GAAO,KAAK,IAAI,KAAK,IAAI,EAAGlE,EAAE,KAAOA,EAAE,MAAQ,EAAIC,EAAE,MAAQ,CAAC,EAAG,OAAO,WAAaA,EAAE,MAAQ,CAAC,EACtG2D,EAAK,MAAM,IAAM,GAAG,KAAK,IAAI,EAAGK,CAAG,CAAC,KACpCL,EAAK,MAAM,KAAO,GAAG,KAAK,IAAI,EAAGM,EAAI,CAAC,IACxC,CAEA,OAAAL,EAAM,iBAAiB,QAAS,IAAM,CACpC/C,GAASL,EAAQ,OAAO,EAAGpB,GAAekB,EAAO,EAAE,EACnD6B,EAAK,MAAM,oBAAqB,CAAE,UAAW7B,EAAO,GAAI,KAAMA,EAAO,IAAK,CAAC,EAC3EuD,EAAO,CACT,CAAC,EACDpB,EAAK,IAAInC,EAAO,GAAIuD,CAAM,EAC1B,sBAAsBC,CAAQ,EAC9B,OAAO,iBAAiB,SAAUA,EAAU,EAAI,EAChD,OAAO,iBAAiB,SAAUA,CAAQ,EACnC,EACT,EAEMI,EAAO,IAAY,CACvB7B,EAAQ,KACR,IAAM8B,EAASlE,GAAeC,EAAS,SAAS,SAAUiC,EAAK,cAAc,CAAC,EACxEiC,EAAY,IAAI,IAAID,EAAO,IAAKpE,GAAMA,EAAE,OAAO,EAAE,CAAC,EACxD,QAAWsD,IAAM,CAAC,GAAGb,EAAO,KAAK,CAAC,EAAQ4B,EAAU,IAAIf,CAAE,GAAGD,EAAYC,CAAE,EAC3E,OAAW,CAACA,EAAIQ,CAAM,GAAK,CAAC,GAAGpB,CAAI,EAAQ2B,EAAU,IAAIf,CAAE,GAAGQ,EAAO,EAErE,OAAW,CAAE,OAAAvD,EAAQ,IAAAuC,CAAI,IAAKsB,EAAQ,CACpC,IAAMzC,EAAKE,GAAWtB,EAAO,MAAM,EACnC,GAAI,GAACoB,GAAMpB,EAAO,OAAO,OAAS,QAClC,GAAIA,EAAO,OAAS,OAAQ,CAC1B,GAAI,CAACoB,EAAI,UACKmB,IAAQ,SAAWK,EAAUxB,EAAIpB,CAAM,EAAIyC,EAAiBrB,EAAIpB,CAAM,IAAM,OAC/EsC,EAAOtC,EAAQuC,CAAG,CAC/B,MAAWA,IAAQ,WAAaY,EAAQ/B,EAAIpB,CAAM,IAChDsC,EAAOtC,EAAQuC,CAAG,CAEtB,CACF,EAEMwB,EAAW,IAAY,CACvBhC,IAAU,OAAMA,EAAQ,WAAW6B,EAAMhF,EAAgB,EAC/D,EAEMoF,EAAU,SAA2B,CACzClC,EAAY,KAAK,IAAI,EACrB,GAAI,CACF,IAAMmC,EAAM,MAAM,MAAM,GAAGpC,EAAK,OAAO,mBAAoB,CACzD,QAAS,CAAE,cAAe,UAAUA,EAAK,MAAM,EAAG,CACpD,CAAC,EACD,GAAI,CAACoC,EAAI,GAAI,CACX7B,EAAI,WAAW6B,EAAI,MAAM,EAAE,EAC3B,MACF,CACArE,EAAUT,GAAa,MAAM8E,EAAI,KAAK,CAAC,EACvCF,EAAS,CACX,OAASG,EAAK,CACZ9B,EAAI,0BAA2B8B,EAAc,OAAO,EAAE,CACxD,CACF,EAEMC,EACJ,OAAO,iBAAqB,IACxB,IAAI,iBAAiB,IAAM,EACrBvE,EAAQ,OAAS,GAAKsC,EAAO,KAAO,GAAKC,EAAK,KAAO,IAAG4B,EAAS,CACvE,CAAC,EACD,KACAK,EAAU,IAAY,CACtB,SAAS,MAAMD,GAAU,QAAQ,SAAS,KAAM,CAAE,UAAW,GAAM,QAAS,GAAM,cAAe,EAAK,CAAC,CAC7G,EACI,SAAS,aAAe,UAAW,SAAS,iBAAiB,mBAAoBC,EAAS,CAAE,KAAM,EAAK,CAAC,EACvGA,EAAQ,EAEb,IAAMC,EAAW,YAAY,IAAM,KAAKL,EAAQ,EAAGtF,EAAU,EAC7D,OAAKsF,EAAQ,EAEN,CACL,WAAY,IAAM,CACZ,KAAK,IAAI,EAAIlC,EAAYnD,IAAqBqF,EAAQ,EAC1DD,EAAS,CACX,EACA,QAAAC,EACA,SAAU,IAAM,CACd,cAAcK,CAAQ,EAClBtC,IAAU,MAAM,aAAaA,CAAK,EACtCoC,GAAU,WAAW,EACrB,QAAWpB,IAAM,CAAC,GAAGb,EAAO,KAAK,CAAC,EAAGY,EAAYC,CAAE,EACnD,QAAWQ,IAAU,CAAC,GAAGpB,EAAK,OAAO,CAAC,EAAGoB,EAAO,EAChDtB,GAAM,OAAO,EACbA,EAAO,KACPD,EAAO,KACPpC,EAAU,CAAC,CACb,CACF,CACF,CChVA,IAAM0E,EAAmB,gBAwBZC,EAAN,cAAuBC,CAAU,CAC9B,oBAA2C,KAC3C,SAAmC,KACnC,QAAuC,KAE/C,YAAYC,EAAuB,CAWjC,GAVA,MAAM,UAAWA,CAAI,EACrB,KAAK,YAAcC,GAAmB,EAElC,OAAO,OAAW,MACpB,OAAO,iBAAiB,WAAY,IAAM,KAAK,KAAK,MAAM,CAAC,EAC3D,SAAS,iBAAiB,mBAAoB,IAAM,CAC9C,SAAS,kBAAoB,UAAe,KAAK,MAAM,CAC7D,CAAC,GAGCD,EAAK,WAAa,GAAO,CAC3B,IAAME,EAAe,OAAOF,EAAK,UAAa,SAAWA,EAAK,SAAW,CAAC,EAC1E,KAAK,SAAWG,EAAuB,CACrC,GAAGD,EACH,WAAY,CAAC,GAAIA,EAAa,YAAc,CAAC,EAAI,KAAK,OAAO,EAC7D,WAAaE,GAAW,KAAK,MAAM,IAAIA,EAAO,IAAI,GAAIA,EAAO,UAAU,CACzE,CAAC,CACH,CAEIJ,EAAK,cAAgB,KACvB,KAAK,oBAAsBK,EAAmB,CAC5C,WAAaC,GAAyB,CACpC,KAAK,KAAK,OAAW,CAAE,WAAYA,CAAK,CAAC,EACzC,KAAK,UAAU,WAAW,OAAO,SAAa,IAAc,SAAS,SAAW,GAAG,EACnF,KAAK,SAAS,WAAW,CAC3B,CACF,CAAC,GAGCN,EAAK,iBAAmB,IAAS,OAAO,OAAW,MACrD,KAAK,QAAUO,GAAsB,CACnC,QAAS,KAAK,QACd,OAAQ,KAAK,OACb,cAAe,IAAM,KAAK,WAC1B,MAAO,CAACC,EAAMC,IAAe,KAAK,MAAMD,EAAMC,CAAU,EACxD,MAAOT,EAAK,KACd,CAAC,EAEL,CASA,KAAKQ,EAAeC,EAA+B,CACjD,KAAK,MAAM,YAAa,CACtB,GAAIA,GAAc,CAAC,EACnB,GAAID,EAAO,CAAE,UAAWA,CAAK,EAAI,CAAC,EAClC,GAAI,OAAO,SAAa,KAAe,SAAS,MAAQ,CAAE,MAAO,SAAS,KAAM,EAAI,CAAC,CACvF,CAAC,CACH,CAES,MAAME,EAAoBC,EAA2B,CAC5D,MAAM,MAAMD,EAAYC,CAAM,EAC9B,KAAK,SAAS,WAAW,CAC3B,CAEA,OAAc,CACZ,KAAK,OAAS,KACd,KAAK,WAAa,KAClB,GAAI,CACF,aAAa,WAAWd,CAAgB,CAC1C,MAAQ,CAER,CACA,KAAK,YAAcI,GAAmB,EACtC,KAAK,SAAS,WAAW,CAC3B,CAGA,MAAe,UAA0B,CACvC,KAAK,sBAAsB,EAC3B,KAAK,oBAAsB,KAC3B,KAAK,UAAU,SAAS,EACxB,KAAK,SAAW,KAChB,KAAK,SAAS,SAAS,EACvB,KAAK,QAAU,KACf,MAAM,MAAM,SAAS,CACvB,CAEmB,oBAAiC,CAClD,OAAOW,GAAgB,CACzB,CACF,EAEA,SAASX,IAA6B,CACpC,GAAI,CACF,IAAMY,EAAW,aAAa,QAAQhB,CAAgB,EACtD,GAAIgB,EAAU,OAAOA,EACrB,IAAMC,EAAQ,OAAO,WAAW,EAChC,oBAAa,QAAQjB,EAAkBiB,CAAK,EACrCA,CACT,MAAQ,CACN,OAAO,OAAO,WAAW,CAC3B,CACF,CPtIA,IAAIC,EAA4B,KAEzB,SAASC,GAAKC,EAAiC,CAEpD,OAAKF,IAAUA,EAAW,IAAIG,EAASD,CAAI,GACpCF,CACT,CAEO,SAASI,GAAMC,EAAcC,EAA+B,CACjEN,GAAU,MAAMK,EAAMC,CAAU,CAClC,CAEO,SAASC,GAASC,EAAgBC,EAA2B,CAClET,GAAU,SAASQ,EAAQC,CAAM,CACnC,CAEO,SAASC,GAAMC,EAAoBF,EAA2B,CACnET,GAAU,MAAMW,EAAYF,CAAM,CACpC,CAEO,SAASG,GAAKP,EAAeC,EAA+B,CACjEN,GAAU,KAAKK,EAAMC,CAAU,CACjC,CAEO,SAASO,IAAc,CAC5Bb,GAAU,MAAM,CAClB,CAEA,eAAsBc,IAAuB,CAC3C,MAAMd,GAAU,MAAM,CACxB,CAEA,eAAsBe,IAA0B,CAC9C,MAAMf,GAAU,SAAS,EACzBA,EAAW,IACb,CAGO,SAASgB,IAA+B,CAC7C,OAAOhB,CACT",
|
|
6
|
+
"names": ["browser_exports", "__export", "flush", "getInstance", "group", "identify", "init", "page", "reset", "shutdown", "track", "DEFAULT_BASE_URL", "HoldyCore", "source", "opts", "DEFAULT_BASE_URL", "intervalMs", "name", "properties", "overrides", "userId", "accountKey", "occurredAt", "context", "event", "newEventId", "traits", "force", "batch", "outcome", "afterMs", "backoff", "events", "res", "retryAfterMs", "err", "msg", "key", "raw", "seconds", "c", "rand", "INSTALLED_FLAG", "installAutoCapture", "opts", "w", "INSTALLED_FLAG", "cleanups", "lastPath", "currentPath", "pending", "emit", "kind", "scheduleRouteEmit", "path", "originalPush", "originalReplace", "args", "result", "onPop", "onHash", "cleanup", "INSTALLED_FLAG", "INTERACTIVE_SELECTOR", "INTERACTIVE_TAGS", "FORM_CONTROL_TAGS", "RageClickDetector", "threshold", "windowMs", "radiusPx", "x", "y", "t", "c", "NavLoopDetector", "path", "v", "a", "b", "d", "resolveInteractive", "el", "viaClosest", "tag", "role", "isDeadClickTarget", "target", "href", "describeElement", "out", "text", "name", "stripRequestUrl", "input", "base", "url", "errorMessage", "reason", "isAbortError", "err", "isIgnoredUrl", "ignore", "prefix", "registrableDomain", "host", "labels", "isFirstPartyRequest", "pageUrl", "extraOrigins", "page", "o", "targetDomain", "classifyRequest", "outcome", "documentStatus", "perf", "status", "formKey", "form", "currentPath", "installFrictionCapture", "opts", "noop", "w", "emit", "kind", "properties", "cleanups", "rage", "navLoop", "slowMs", "touched", "onClick", "ev", "onError", "onRejection", "apiOrigins", "reportRequest", "method", "started", "flags", "durationMs", "originalFetch", "patched", "init", "res", "xhrProto", "originalOpen", "originalSend", "requests", "rest", "body", "request", "aborted", "loadStatus", "onFocusIn", "entry", "field", "onSubmit", "flushAbandons", "loop", "cleanup", "UTM_KEYS", "SESSION_STORAGE_KEY", "ATTRIBUTION_STORAGE_KEY", "stripUrl", "href", "url", "parseUtm", "out", "params", "key", "value", "parseUserAgent", "ua", "s", "browser", "os", "device", "readStorage", "storage", "writeStorage", "randomId", "resolveSessionId", "existing", "fresh", "resolveAttribution", "referrer", "currentHost", "stored", "attribution", "referrerHost", "hostOf", "clean", "buildWebContext", "host", "fnv1a", "input", "hash", "i", "changeBucket", "changeId", "accountKey", "changeArm", "holdoutPct", "matchesPathTemplate", "template", "path", "t", "p", "segment", "REFRESH_MS", "NAV_REFRESH_MS", "SCAN_DEBOUNCE_MS", "SEEN_KEY", "DISMISSED_KEY", "TIP_GAP_PX", "TIP_CSS", "normalizeText", "text", "parseChanges", "body", "list", "isChange", "isAnchor", "value", "a", "c", "changesForPage", "changes", "path", "accountKey", "matchesPathTemplate", "change", "changeArm", "storage", "kind", "readSet", "store", "key", "addToSet", "set", "query", "selector", "TEXT_ANCHOR_TAGS", "TEXT_ANCHOR_SCAN_LIMIT", "findByText", "wanted", "best", "bestLength", "nodes", "total", "i", "el", "raw", "findAnchor", "anchor", "findTextNode", "match", "walker", "node", "installProductChanges", "opts", "lastFetch", "timer", "root", "host", "copies", "tips", "log", "message", "expose", "arm", "session", "eligibleCopyNode", "expected", "next", "applyCopy", "current", "restoreCopy", "id", "applied", "ensureRoot", "style", "showTip", "shadow", "card", "close", "remove", "position", "corner", "top", "left", "scan", "active", "activeIds", "schedule", "refresh", "res", "err", "observer", "observe", "interval", "ANON_STORAGE_KEY", "HoldyWeb", "HoldyCore", "opts", "resolveAnonymousId", "frictionOpts", "installFrictionCapture", "signal", "installAutoCapture", "kind", "installProductChanges", "name", "properties", "accountKey", "traits", "buildWebContext", "existing", "fresh", "instance", "init", "opts", "HoldyWeb", "track", "name", "properties", "identify", "userId", "traits", "group", "accountKey", "page", "reset", "flush", "shutdown", "getInstance"]
|
|
7
7
|
}
|