@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 CHANGED
@@ -17,6 +17,46 @@ holdy.page('pricing')
17
17
 
18
18
  `anonymous_id` é gerado e persistido em `localStorage` (`holdy_anon_id`). O merge anônimo → identificado acontece no identity graph do Holdy, nunca no SDK.
19
19
 
20
+ ### Fricção (default ligada)
21
+
22
+ O SDK web observa sozinho os sinais de fricção e emite eventos determinísticos — sem DOM snapshot, sem replay, sem PII:
23
+
24
+ | Evento | Quando | Propriedades |
25
+ |---|---|---|
26
+ | `$dead_click` | clique em controle desabilitado / `aria-disabled` / âncora sem `href` | `element`, `element_text`, `element_id`, `path` |
27
+ | `$rage_click` | 3 cliques em 1s num raio de 30px | idem |
28
+ | `$form_abandon` | campos de um form tocados e a página sai sem `submit` | `form`, `fields_touched`, `path` |
29
+ | `$js_error` | `window.error` / `unhandledrejection` | `message` (≤200), `source`, `line` |
30
+ | `$slow_request` | `fetch`/XHR acima de 3s com resposta ok | `url` (sem query), `method`, `duration_ms`, `status` |
31
+ | `$nav_loop` | A → B → A → B em menos de 60s | `from`, `to`, `path` |
32
+ | `$http_error` | `fetch`/XHR com status ≥ 400 ou sem resposta (cancelado não conta), ou a própria página abrindo com status ≥ 400 | `url` (sem query), `method`, `duration_ms`, `status` ou `failed`; `document: true` quando é a página |
33
+
34
+ Requisição pra URL da Holdy nunca vira `$http_error`; `ignoreUrls` acrescenta outras (prefixo).
35
+
36
+ ```ts
37
+ initHoldy({ apiKey, friction: false }) // desliga
38
+ initHoldy({ apiKey, friction: { slowRequestMs: 5000 } }) // ajusta limiares
39
+ ```
40
+
41
+ No Holdy esses eventos viram Behaviors de fricção e alimentam o contraste "sessões com fricção convertem X% menos" (ADR-0009).
42
+
43
+ ### Mudanças no produto (default ligada)
44
+
45
+ Quando uma ação de texto ou dica é posta no ar na Holdy, o SDK a aplica na página certa e deixa parte das contas de fora, por sorteio, pra comparar. Nada muda no código do produto, e parar na Holdy desfaz a mudança na próxima leitura.
46
+
47
+ - Precisa de conta identificada (`group()` ou `accountKey`): sem ela nada é aplicado.
48
+ - Troca de texto só acontece quando o elemento ainda tem o texto esperado; a dica é um cartão em shadow DOM que não volta depois de fechado.
49
+ - Só `textContent`, nunca HTML.
50
+
51
+ | Evento | Quando | Propriedades |
52
+ |---|---|---|
53
+ | `$change_exposure` | o elemento da mudança está na página, uma vez por sessão, nos dois grupos | `change_id`, `arm` (`change` ou `holdout`), `kind` |
54
+ | `$change_dismissed` | a pessoa fecha a dica | `change_id`, `kind` |
55
+
56
+ ```ts
57
+ initHoldy({ apiKey, productChanges: false }) // desliga
58
+ ```
59
+
20
60
  ## Server (`sdk_server` · Node ≥18 / Edge)
21
61
 
22
62
  ```ts
@@ -30,9 +70,20 @@ await holdy.flush() // ou holdy.shutdown() no teardown
30
70
 
31
71
  Server é stateless por chamada: passe `userId`/`accountKey` em overrides (estado de sessão em server multi-tenant vaza identidade).
32
72
 
73
+ ### Execuções de agente · `run()`
74
+
75
+ ```ts
76
+ const resposta = await holdy.run({ name: 'support_agent', accountKey: 'acme-inc' }, async (run) => {
77
+ run.track('ticket_drafted', { ai_tokens: 1200, ai_cost_usd: 0.02 })
78
+ return resolverTicket(ticket)
79
+ })
80
+ ```
81
+
82
+ Todo evento da execução carrega a mesma `ai_run_id`; `support_agent.started`, `.completed` (com `duration_ms`) e `.failed` (só o nome do erro) saem sozinhos. `runId` usa o trace id do seu agente como chave. O retorno passa direto e o erro da sua função propaga — é a única exceção ao "nenhum método lança", porque engolir o erro do chamador seria pior.
83
+
33
84
  ## Semântica
34
85
 
35
86
  - Fila em memória com flush automático (`flushAt` default 20 · `flushIntervalMs` default 5000) e batches de até 500.
36
87
  - Retry é seguro: o servidor deduplica e aceita parcialmente; 5xx/network re-enfileira, 4xx dropa o batch.
37
- - Nenhum método público lança — telemetria nunca derruba o app.
88
+ - Nenhum método público lança — telemetria nunca derruba o app. A exceção é `run()`, que devolve o erro da própria função que você passou.
38
89
  - A chave de account do cliente viaja em `context.account_external_id`; o Holdy resolve pro UUID interno (a porta nunca resolve identity).
package/dist/arm.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ export type ChangeArm = 'change' | 'holdout';
2
+ export declare function changeBucket(changeId: string, accountKey: string): number;
3
+ export declare function changeArm(changeId: string, accountKey: string, holdoutPct: number): ChangeArm;
4
+ export declare function matchesPathTemplate(template: string, path: string): boolean;
5
+ //# sourceMappingURL=arm.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"arm.d.ts","sourceRoot":"","sources":["../src/arm.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAA;AAW5C,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAEzE;AAED,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,SAAS,CAE7F;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAK3E"}
@@ -0,0 +1,42 @@
1
+ import { type ChangeArm } from './arm';
2
+ export type ChangeAnchor = {
3
+ kind: 'selector';
4
+ value: string;
5
+ } | {
6
+ kind: 'text';
7
+ value: string;
8
+ } | {
9
+ kind: 'page';
10
+ };
11
+ export interface ProductChange {
12
+ id: string;
13
+ kind: 'copy' | 'guidance';
14
+ path: string;
15
+ anchor: ChangeAnchor;
16
+ expected_text: string | null;
17
+ content: {
18
+ text: string;
19
+ };
20
+ holdout_pct: number;
21
+ }
22
+ export interface ProductChangesOptions {
23
+ baseUrl: string;
24
+ apiKey: string;
25
+ getAccountKey: () => string | null;
26
+ track: (name: string, properties: Record<string, unknown>) => void;
27
+ debug?: boolean;
28
+ }
29
+ export interface ProductChangesHandle {
30
+ onNavigate(): void;
31
+ refresh(): Promise<void>;
32
+ teardown(): void;
33
+ }
34
+ export declare function normalizeText(text: string): string;
35
+ export declare function parseChanges(body: unknown): ProductChange[];
36
+ export declare function changesForPage(changes: readonly ProductChange[], path: string, accountKey: string | null): Array<{
37
+ change: ProductChange;
38
+ arm: ChangeArm;
39
+ }>;
40
+ export declare function findByText(value: string): Element | null;
41
+ export declare function installProductChanges(opts: ProductChangesOptions): ProductChangesHandle;
42
+ //# sourceMappingURL=changes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"changes.d.ts","sourceRoot":"","sources":["../src/changes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAEtE,MAAM,MAAM,YAAY,GACpB;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACnC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAC/B;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAA;AAEpB,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,GAAG,UAAU,CAAA;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,YAAY,CAAA;IACpB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;IACzB,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,aAAa,EAAE,MAAM,MAAM,GAAG,IAAI,CAAA;IAClC,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAA;IAClE,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,MAAM,WAAW,oBAAoB;IACnC,UAAU,IAAI,IAAI,CAAA;IAClB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACxB,QAAQ,IAAI,IAAI,CAAA;CACjB;AAgBD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAElD;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,aAAa,EAAE,CAG3D;AAoBD,wBAAgB,cAAc,CAC5B,OAAO,EAAE,SAAS,aAAa,EAAE,EACjC,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,GAAG,IAAI,GACxB,KAAK,CAAC;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,GAAG,EAAE,SAAS,CAAA;CAAE,CAAC,CAKlD;AAuCD,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAcxD;AAqBD,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,qBAAqB,GAAG,oBAAoB,CAyMvF"}
package/dist/core.d.ts CHANGED
@@ -1,21 +1,28 @@
1
1
  /**
2
2
  * Core do @holdyapp/sdk · fila + flush pra POST /api/v1/events/bulk.
3
3
  *
4
- * Retry de batch inteiro é seguro (partial accept + dedupe server-side).
5
- * Fila é bounded: acima de MAX_QUEUE dropa os mais antigos (telemetria nunca
6
- * derruba o app do cliente — nenhum método público lança).
4
+ * Todo evento sai com `external_id` próprio, então reenviar um lote nunca
5
+ * duplica (dedupe server-side por source + external_id + occurred_at).
6
+ * 429 respeita Retry-After; 5xx e rede esperam com backoff exponencial; o lote
7
+ * volta pra frente da fila em vez de sumir. Chave inválida ou origem recusada
8
+ * avisam uma vez no console, mesmo sem debug. Fila é bounded: acima de
9
+ * MAX_QUEUE dropa os mais antigos (telemetria nunca derruba o app do cliente —
10
+ * nenhum método público lança).
7
11
  */
8
12
  import type { HoldyOptions, Properties, TrackOverrides } from './types';
9
13
  export declare const DEFAULT_BASE_URL = "https://holdy.chat/api/v1";
10
14
  export declare class HoldyCore {
11
- private readonly apiKey;
12
- private readonly baseUrl;
15
+ protected readonly apiKey: string;
16
+ protected readonly baseUrl: string;
13
17
  private readonly flushAt;
14
18
  private readonly debug;
15
19
  private readonly source;
16
20
  private queue;
17
21
  private timer;
18
22
  private flushing;
23
+ private retryAt;
24
+ private failures;
25
+ private readonly warned;
19
26
  protected userId: string | null;
20
27
  protected anonymousId: string | null;
21
28
  protected accountKey: string | null;
@@ -23,13 +30,17 @@ export declare class HoldyCore {
23
30
  track(name: string, properties?: Properties, overrides?: TrackOverrides): void;
24
31
  identify(userId: string, traits?: Properties): void;
25
32
  group(accountKey: string, traits?: Properties): void;
26
- /** Drena a fila. Nunca lança — falha re-enfileira e loga em debug. */
33
+ /** Drena a fila. Nunca lança — falha re-enfileira e espera antes de tentar de novo. */
27
34
  flush(): Promise<void>;
28
35
  /** Flush final + para o timer. Chamar em shutdown do server / unload do web. */
29
36
  shutdown(): Promise<void>;
37
+ private startFlush;
30
38
  private drain;
39
+ private scheduleRetry;
40
+ private trimQueue;
31
41
  private send;
32
42
  private log;
43
+ private warnOnce;
33
44
  /**
34
45
  * Ambiente que o SDK observa sozinho (url, referrer, browser, utm…).
35
46
  * Vazio no core; `HoldyWeb` preenche. O server não infere ambiente — não
@@ -1 +1 @@
1
- {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAkB,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAEvF,eAAO,MAAM,gBAAgB,8BAA8B,CAAA;AAM3D,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;IAC/B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAQ;IAChC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAQ;IAChC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA0B;IAEjD,OAAO,CAAC,KAAK,CAAuB;IACpC,OAAO,CAAC,KAAK,CAA8C;IAC3D,OAAO,CAAC,QAAQ,CAA6B;IAE7C,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAO;IACtC,SAAS,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAO;IAC3C,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAO;gBAE9B,MAAM,EAAE,SAAS,GAAG,YAAY,EAAE,IAAI,EAAE,YAAY;IAkBhE,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,UAAU,EAAE,SAAS,CAAC,EAAE,cAAc,GAAG,IAAI;IAiC9E,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,UAAU,GAAG,IAAI;IAMnD,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,UAAU,GAAG,IAAI;IAMpD,sEAAsE;IAChE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAW5B,gFAAgF;IAC1E,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;YAQjB,KAAK;YAYL,IAAI;IA2BlB,OAAO,CAAC,GAAG;IAIX;;;;OAIG;IACH,SAAS,CAAC,kBAAkB,IAAI,UAAU;IAI1C,oCAAoC;IACpC,IAAI,OAAO,IAAI,MAAM,CAEpB;CACF"}
1
+ {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAkB,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAEvF,eAAO,MAAM,gBAAgB,8BAA8B,CAAA;AAS3D,qBAAa,SAAS;IACpB,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACjC,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IAClC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAQ;IAChC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA0B;IAEjD,OAAO,CAAC,KAAK,CAAuB;IACpC,OAAO,CAAC,KAAK,CAA8C;IAC3D,OAAO,CAAC,QAAQ,CAA6B;IAC7C,OAAO,CAAC,OAAO,CAAI;IACnB,OAAO,CAAC,QAAQ,CAAI;IACpB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAoB;IAE3C,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAO;IACtC,SAAS,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAO;IAC3C,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAO;gBAE9B,MAAM,EAAE,SAAS,GAAG,YAAY,EAAE,IAAI,EAAE,YAAY;IAkBhE,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,UAAU,EAAE,SAAS,CAAC,EAAE,cAAc,GAAG,IAAI;IAkC9E,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,UAAU,GAAG,IAAI;IAMnD,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,UAAU,GAAG,IAAI;IAMpD,uFAAuF;IACjF,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5B,gFAAgF;IAC1E,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ/B,OAAO,CAAC,UAAU;YAYJ,KAAK;IAenB,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,SAAS;YAIH,IAAI;IAqClB,OAAO,CAAC,GAAG;IAIX,OAAO,CAAC,QAAQ;IAMhB;;;;OAIG;IACH,SAAS,CAAC,kBAAkB,IAAI,UAAU;IAI1C,oCAAoC;IACpC,IAAI,OAAO,IAAI,MAAM,CAEpB;CACF"}
@@ -0,0 +1,91 @@
1
+ export type FrictionKind = 'dead_click' | 'rage_click' | 'form_abandon' | 'js_error' | 'slow_request' | 'nav_loop' | 'http_error';
2
+ export interface FrictionSignal {
3
+ kind: FrictionKind;
4
+ properties: Record<string, unknown>;
5
+ }
6
+ export interface FrictionCaptureOptions {
7
+ onFriction: (signal: FrictionSignal) => void;
8
+ slowRequestMs?: number;
9
+ rageClicks?: number;
10
+ rageWindowMs?: number;
11
+ rageRadiusPx?: number;
12
+ navLoopWindowMs?: number;
13
+ ignoreUrls?: string[];
14
+ /**
15
+ * Origens que contam como o próprio produto além do domínio da página —
16
+ * pro caso em que a API mora em outro domínio (`acme-api.io` servindo
17
+ * `acme.com`). O subdomínio já entra sozinho.
18
+ */
19
+ apiOrigins?: string[];
20
+ }
21
+ export interface FrictionCapture {
22
+ onNavigate: (path: string) => void;
23
+ teardown: () => void;
24
+ }
25
+ export declare class RageClickDetector {
26
+ private readonly threshold;
27
+ private readonly windowMs;
28
+ private readonly radiusPx;
29
+ private clicks;
30
+ constructor(threshold?: number, windowMs?: number, radiusPx?: number);
31
+ push(x: number, y: number, t: number): boolean;
32
+ }
33
+ export declare class NavLoopDetector {
34
+ private readonly windowMs;
35
+ private visits;
36
+ constructor(windowMs?: number);
37
+ push(path: string, t: number): {
38
+ from: string;
39
+ to: string;
40
+ } | null;
41
+ }
42
+ export interface ElementLike {
43
+ tagName: string;
44
+ id?: string;
45
+ disabled?: boolean;
46
+ textContent?: string | null;
47
+ getAttribute(name: string): string | null;
48
+ closest?(selector: string): ElementLike | null;
49
+ }
50
+ export declare function resolveInteractive(el: ElementLike | null): ElementLike | null;
51
+ export declare function isDeadClickTarget(el: ElementLike | null): boolean;
52
+ export declare function describeElement(el: ElementLike | null): Record<string, string>;
53
+ export declare function stripRequestUrl(input: string, base?: string): string;
54
+ export declare function errorMessage(reason: unknown): string;
55
+ export declare function isAbortError(err: unknown): boolean;
56
+ export declare function isIgnoredUrl(url: string, ignore: readonly string[]): boolean;
57
+ /**
58
+ * Domínio registrável aproximado — os dois últimos rótulos do host.
59
+ *
60
+ * `app.acme.com` e `api.acme.com` → `acme.com`: produto que fala com a própria
61
+ * API em outro subdomínio continua sendo primeira parte. Sem lista de sufixos
62
+ * públicos, `foo.co.uk` e `bar.co.uk` colidem — e a colisão erra pro lado
63
+ * seguro, incluindo como próprio o que hoje já é incluído.
64
+ */
65
+ export declare function registrableDomain(host: string): string;
66
+ /**
67
+ * A requisição é do produto de quem instalou, ou de um terceiro?
68
+ *
69
+ * Fricção é fricção DO PRODUTO. Um `google-analytics.com/g/collect` morto por
70
+ * ad blocker não é o produto falhando — é o navegador do visitante recusando
71
+ * um script que nem é seu. Em 2026-09-17 esses dois domínios respondiam por 36
72
+ * dos eventos de fricção da janela: num cliente com GA instalado e usuários com
73
+ * bloqueador, isso infla a fricção e pode abrir oportunidade em cima de nada.
74
+ *
75
+ * Localhost e IP entram pela comparação de host exato — em desenvolvimento não
76
+ * há domínio registrável que valha.
77
+ */
78
+ export declare function isFirstPartyRequest(url: string, pageUrl: string, extraOrigins?: readonly string[]): boolean;
79
+ export interface RequestOutcome {
80
+ status: number | null;
81
+ durationMs: number;
82
+ slowMs: number;
83
+ aborted?: boolean;
84
+ opaque?: boolean;
85
+ }
86
+ export declare function classifyRequest(outcome: RequestOutcome): 'http_error' | 'slow_request' | null;
87
+ export declare function documentStatus(perf: {
88
+ getEntriesByType?: (type: string) => unknown[];
89
+ } | undefined): number | null;
90
+ export declare function installFrictionCapture(opts: FrictionCaptureOptions): FrictionCapture;
91
+ //# sourceMappingURL=friction.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"friction.d.ts","sourceRoot":"","sources":["../src/friction.ts"],"names":[],"mappings":"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"}
package/dist/holdy.js CHANGED
@@ -1,2 +1,7 @@
1
- "use strict";var Holdy=(()=>{var g=Object.defineProperty;var O=Object.getOwnPropertyDescriptor;var C=Object.getOwnPropertyNames;var P=Object.prototype.hasOwnProperty;var T=(n,t)=>{for(var e in t)g(n,e,{get:t[e],enumerable:!0})},k=(n,t,e,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of C(t))!P.call(n,o)&&o!==e&&g(n,o,{get:()=>t[o],enumerable:!(i=O(t,o))||i.enumerable});return n};var H=n=>k(g({},"__esModule",{value:!0}),n);var z={};T(z,{flush:()=>Q,getInstance:()=>V,group:()=>Y,identify:()=>G,init:()=>q,page:()=>j,reset:()=>J,shutdown:()=>X,track:()=>B});var W="https://holdy.chat/api/v1";var d=class{apiKey;baseUrl;flushAt;debug;source;queue=[];timer=null;flushing=null;userId=null;anonymousId=null;accountKey=null;constructor(t,e){this.source=t,this.apiKey=e.apiKey,this.accountKey=e.accountKey??null,this.baseUrl=(e.baseUrl??W).replace(/\/$/,""),this.flushAt=Math.max(1,e.flushAt??20),this.debug=e.debug??!1;let i=e.flushIntervalMs??5e3;i>0&&(this.timer=setInterval(()=>void this.flush(),i),typeof this.timer.unref=="function"&&this.timer.unref())}track(t,e,i){if(!t||typeof t!="string")return;let o=i?.userId??this.userId??void 0,r=i?.accountKey??this.accountKey??void 0,s=i?.occurredAt?new Date(i.occurredAt).toISOString():new Date().toISOString(),u={...this.environmentContext(),...i?.context??{}};r&&(u.account_external_id=r);let c={event:t,occurred_at:s,source:this.source,...o?{user_id:o}:{},...this.anonymousId?{anonymous_id:this.anonymousId}:{},properties:e??{},...Object.keys(u).length>0?{context:u}:{}};this.queue.push(c),this.queue.length>1e3&&this.queue.splice(0,this.queue.length-1e3),this.queue.length>=this.flushAt&&this.flush()}identify(t,e){!t||typeof t!="string"||(this.userId=t,this.track("$identify",e??{}))}group(t,e){!t||typeof t!="string"||(this.accountKey=t,this.track("$group",e??{}))}async flush(){if(this.flushing)return this.flushing;if(this.queue.length!==0)return this.flushing=this.drain().finally(()=>{this.flushing=null}),this.flushing}async shutdown(){this.timer&&(clearInterval(this.timer),this.timer=null),await this.flush()}async drain(){for(;this.queue.length>0;){let t=this.queue.splice(0,500);if(!await this.send(t)){this.queue.unshift(...t);return}}}async send(t){try{let e=await fetch(`${this.baseUrl}/events/bulk`,{method:"POST",headers:{Authorization:`Bearer ${this.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify({events:t}),...this.source==="sdk_web"?{keepalive:!0}:{}});return e.ok?!0:e.status>=400&&e.status<500?(this.log(`bulk rejected ${e.status} \xB7 batch dropped`),!0):(this.log(`bulk failed ${e.status} \xB7 will retry`),!1)}catch(e){return this.log(`bulk network error: ${e.message} \xB7 will retry`),!1}}log(t){this.debug&&console.warn(`[holdy] ${t}`)}environmentContext(){return{}}get pending(){return this.queue.length}};var y="__holdy_autocapture__";function _(n){if(typeof window>"u"||typeof history>"u")return()=>{};let t=window;if(t[y])return()=>{};t[y]=!0;let e=[],i=b(),o=null;function r(l){try{n.onNavigate(l)}catch{}}function s(){o&&clearTimeout(o),o=setTimeout(()=>{o=null;let l=b();l!==i&&(i=l,r("route_change"))},50)}r("initial_load");let u=history.pushState,c=history.replaceState;history.pushState=function(...h){let f=u.apply(this,h);return s(),f},history.replaceState=function(...h){let f=c.apply(this,h);return s(),f},e.push(()=>{history.pushState=u,history.replaceState=c});let v=()=>s();window.addEventListener("popstate",v),e.push(()=>window.removeEventListener("popstate",v));let w=()=>s();return window.addEventListener("hashchange",w),e.push(()=>window.removeEventListener("hashchange",w)),()=>{o&&clearTimeout(o);for(let l of e)l();t[y]=!1}}function b(){return typeof location>"u"?"/":`${location.pathname}${location.search}${location.hash}`}var K=["utm_source","utm_medium","utm_campaign","utm_term","utm_content"],S="holdy_session",x="holdy_attribution";function A(n){try{let t=new URL(n);return`${t.origin}${t.pathname}`}catch{return null}}function N(n){let t={};try{let e=new URL(n).searchParams;for(let i of K){let o=e.get(i)?.trim();o&&(t[i]=o.toLowerCase().slice(0,120))}}catch{}return t}function R(n){if(!n)return{};let t=n.toLowerCase(),e=t.includes("edg/")?"Edge":t.includes("opr/")||t.includes("opera")?"Opera":t.includes("samsungbrowser")?"Samsung Internet":t.includes("firefox")||t.includes("fxios")?"Firefox":t.includes("chrome")||t.includes("crios")?"Chrome":t.includes("safari")?"Safari":void 0,i=t.includes("iphone")||t.includes("ipad")||t.includes("ipod")?"iOS":t.includes("android")?"Android":t.includes("windows")?"Windows":t.includes("mac os")||t.includes("macintosh")?"macOS":t.includes("cros")?"ChromeOS":t.includes("linux")?"Linux":void 0,o=t.includes("ipad")||t.includes("android")&&!t.includes("mobile")?"tablet":t.includes("mobi")||t.includes("iphone")||t.includes("ipod")?"mobile":"desktop";return{...e?{browser:e}:{},...i?{os:i}:{},device:o}}function E(n,t){try{return(n==="session"?sessionStorage:localStorage).getItem(t)}catch{return null}}function U(n,t,e){try{(n==="session"?sessionStorage:localStorage).setItem(t,e)}catch{}}function $(){try{return crypto.randomUUID()}catch{return`hol-${Date.now().toString(36)}-${Math.random().toString(36).slice(2,10)}`}}function D(){let n=E("session",S);if(n)return n;let t=$();return U("session",S,t),t}function F(n,t,e){let i=E("session",x);if(i)try{return JSON.parse(i)}catch{}let o={...N(n)},r=M(t);if(r&&r!==e){let s=A(t);s&&(o.referrer=s),o.referrer_host=r}return U("session",x,JSON.stringify(o)),o}function M(n){try{return new URL(n).hostname.toLowerCase()||null}catch{return null}}function I(){if(typeof window>"u"||typeof location>"u")return{};let n=location.href,t=typeof document<"u"?document.referrer:"",e=location.hostname.toLowerCase(),i=typeof navigator<"u"?navigator.userAgent:"",{browser:o,os:r,device:s}=R(i),u=A(n);return{...u?{url:u}:{},path:location.pathname||"/",host:e,...o?{browser:o}:{},...r?{os:r}:{},...s?{device:s}:{},...typeof navigator<"u"&&navigator.language?{locale:navigator.language}:{},session_id:D(),...F(n,t,e)}}var m="holdy_anon_id",p=class extends d{teardownAutoCapture=null;constructor(t){super("sdk_web",t),this.anonymousId=L(),typeof window<"u"&&(window.addEventListener("pagehide",()=>void this.flush()),document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&this.flush()})),t.autoCapture!==!1&&(this.teardownAutoCapture=_({onNavigate:e=>this.page(void 0,{navigation:e})}))}page(t,e){this.track("$pageview",{...e??{},...t?{page_name:t}:{},...typeof document<"u"&&document.title?{title:document.title}:{}})}reset(){this.userId=null,this.accountKey=null;try{localStorage.removeItem(m)}catch{}this.anonymousId=L()}async shutdown(){this.teardownAutoCapture?.(),this.teardownAutoCapture=null,await super.shutdown()}environmentContext(){return I()}};function L(){try{let n=localStorage.getItem(m);if(n)return n;let t=crypto.randomUUID();return localStorage.setItem(m,t),t}catch{return crypto.randomUUID()}}var a=null;function q(n){return a||(a=new p(n)),a}function B(n,t){a?.track(n,t)}function G(n,t){a?.identify(n,t)}function Y(n,t){a?.group(n,t)}function j(n,t){a?.page(n,t)}function J(){a?.reset()}async function Q(){await a?.flush()}async function X(){await a?.shutdown(),a=null}function V(){return a}return H(z);})();
1
+ "use strict";var Holdy=(()=>{var N=Object.defineProperty;var ht=Object.getOwnPropertyDescriptor;var ft=Object.getOwnPropertyNames;var gt=Object.prototype.hasOwnProperty;var mt=(e,t)=>{for(var n in t)N(e,n,{get:t[n],enumerable:!0})},yt=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of ft(t))!gt.call(e,i)&&i!==n&&N(e,i,{get:()=>t[i],enumerable:!(r=ht(t,i))||r.enumerable});return e};var vt=e=>yt(N({},"__esModule",{value:!0}),e);var pe={};mt(pe,{flush:()=>le,getInstance:()=>de,group:()=>se,identify:()=>ie,init:()=>re,page:()=>ae,reset:()=>ue,shutdown:()=>ce,track:()=>oe});var wt="https://holdy.chat/api/v1";var I=class{apiKey;baseUrl;flushAt;debug;source;queue=[];timer=null;flushing=null;retryAt=0;failures=0;warned=new Set;userId=null;anonymousId=null;accountKey=null;constructor(t,n){this.source=t,this.apiKey=n.apiKey,this.accountKey=n.accountKey??null,this.baseUrl=(n.baseUrl??wt).replace(/\/$/,""),this.flushAt=Math.max(1,n.flushAt??20),this.debug=n.debug??!1;let r=n.flushIntervalMs??5e3;r>0&&(this.timer=setInterval(()=>void this.flush(),r),typeof this.timer.unref=="function"&&this.timer.unref())}track(t,n,r){if(!t||typeof t!="string")return;let i=r?.userId??this.userId??void 0,l=r?.accountKey??this.accountKey??void 0,c=r?.occurredAt?new Date(r.occurredAt).toISOString():new Date().toISOString(),p={...this.environmentContext(),...r?.context??{}};l&&(p.account_external_id=l);let f={event:t,occurred_at:c,source:this.source,external_id:xt(),...i?{user_id:i}:{},...this.anonymousId?{anonymous_id:this.anonymousId}:{},properties:n??{},...Object.keys(p).length>0?{context:p}:{}};this.queue.push(f),this.trimQueue(),this.queue.length>=this.flushAt&&this.flush()}identify(t,n){!t||typeof t!="string"||(this.userId=t,this.track("$identify",n??{}))}group(t,n){!t||typeof t!="string"||(this.accountKey=t,this.track("$group",n??{}))}async flush(){return this.startFlush(!1)}async shutdown(){this.timer&&(clearInterval(this.timer),this.timer=null),await this.startFlush(!0)}startFlush(t){return this.flushing?this.flushing:this.queue.length===0||!t&&Date.now()<this.retryAt?Promise.resolve():(this.flushing=this.drain().finally(()=>{this.flushing=null}),this.flushing)}async drain(){for(;this.queue.length>0;){let t=this.queue.splice(0,500),n=await this.send(t);if(n.kind==="retry"){this.queue.unshift(...t),this.trimQueue(),this.scheduleRetry(n.afterMs);return}this.failures=0,this.retryAt=0}}scheduleRetry(t){if(this.failures+=1,t!==null){this.retryAt=Date.now()+t;return}let n=Math.min(6e4,1e3*2**(this.failures-1));this.retryAt=Date.now()+n+Math.floor(Math.random()*n*.2)}trimQueue(){this.queue.length>1e3&&this.queue.splice(0,this.queue.length-1e3)}async send(t){try{let n=await fetch(`${this.baseUrl}/events/bulk`,{method:"POST",headers:{Authorization:`Bearer ${this.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify({events:t}),...this.source==="sdk_web"?{keepalive:!0}:{}});return n.ok?{kind:"sent"}:n.status===429?(this.log("rate limited \xB7 will retry after Retry-After"),{kind:"retry",afterMs:bt(n)}):n.status>=500?(this.log(`bulk failed ${n.status} \xB7 will retry with backoff`),{kind:"retry",afterMs:null}):n.status===401?(this.warnOnce("401","API key inv\xE1lida ou revogada \xB7 eventos descartados. Confira a chave em Configura\xE7\xF5es \u203A SDK."),{kind:"dropped"}):n.status===403?(this.warnOnce("403","Origem n\xE3o permitida ou chave sem escopo events:write \xB7 eventos descartados."),{kind:"dropped"}):(this.log(`bulk rejected ${n.status} \xB7 batch dropped`),{kind:"dropped"})}catch(n){return this.log(`bulk network error: ${n.message} \xB7 will retry with backoff`),{kind:"retry",afterMs:null}}}log(t){this.debug&&console.warn(`[holdy] ${t}`)}warnOnce(t,n){this.warned.has(t)||(this.warned.add(t),console.warn(`[holdy] ${n}`))}environmentContext(){return{}}get pending(){return this.queue.length}};function bt(e){let t=typeof e.headers?.get=="function"?e.headers.get("Retry-After"):null;if(!t)return null;let n=Number(t);return!Number.isFinite(n)||n<0?null:Math.min(6e4,n*1e3)}function xt(){let e=globalThis.crypto;if(typeof e?.randomUUID=="function")return e.randomUUID();let t=()=>Math.random().toString(36).slice(2,12);return`${Date.now().toString(36)}-${t()}-${t()}`}var H="__holdy_autocapture__";function G(e){if(typeof window>"u"||typeof history>"u")return()=>{};let t=window;if(t[H])return()=>{};t[H]=!0;let n=[],r=X(),i=null;function l(m){try{e.onNavigate(m)}catch{}}function c(){i&&clearTimeout(i),i=setTimeout(()=>{i=null;let m=X();m!==r&&(r=m,l("route_change"))},50)}l("initial_load");let p=history.pushState,f=history.replaceState;history.pushState=function(..._){let k=p.apply(this,_);return c(),k},history.replaceState=function(..._){let k=f.apply(this,_);return c(),k},n.push(()=>{history.pushState=p,history.replaceState=f});let b=()=>c();window.addEventListener("popstate",b),n.push(()=>window.removeEventListener("popstate",b));let x=()=>c();return window.addEventListener("hashchange",x),n.push(()=>window.removeEventListener("hashchange",x)),()=>{i&&clearTimeout(i);for(let m of n)m();t[H]=!1}}function X(){return typeof location>"u"?"/":`${location.pathname}${location.search}${location.hash}`}var q="__holdy_friction__",_t='button, a, input, select, textarea, summary, [role="button"], [role="link"], [role="menuitem"], [role="tab"]',Ct=new Set(["button","a","input","select","textarea","summary"]),Et=new Set(["input","select","textarea"]),$=class{constructor(t=3,n=1e3,r=30){this.threshold=t;this.windowMs=n;this.radiusPx=r}clicks=[];push(t,n,r){return this.clicks=this.clicks.filter(i=>r-i.t<=this.windowMs&&Math.hypot(i.x-t,i.y-n)<=this.radiusPx),this.clicks.push({x:t,y:n,t:r}),this.clicks.length>=this.threshold?(this.clicks=[],!0):!1}},K=class{constructor(t=6e4){this.windowMs=t}visits=[];push(t,n){if(this.visits=[...this.visits,{path:t,t:n}].filter(p=>n-p.t<=this.windowMs).slice(-4),this.visits.length<4)return null;let[r,i,l,c]=this.visits.map(p=>p.path);return r===l&&i===c&&r!==i?(this.visits=[],{from:r,to:i}):null}};function J(e){if(!e)return null;let t=e.closest?.(_t)??null;if(t)return t;let n=e.tagName.toLowerCase();if(Ct.has(n))return e;let r=e.getAttribute("role");return r==="button"||r==="link"||r==="menuitem"||r==="tab"?e:null}function kt(e){let t=J(e);if(!t)return!1;if(t.disabled===!0||t.getAttribute("disabled")!==null||t.getAttribute("aria-disabled")==="true")return!0;if(t.tagName.toLowerCase()==="a"){let r=t.getAttribute("href");if(!r||r==="#"||r.startsWith("javascript:"))return!0}return!1}function z(e){let t=J(e)??e;if(!t)return{};let n={element:t.tagName.toLowerCase()};t.id&&(n.element_id=t.id);let r=t.getAttribute("role");r&&(n.element_role=r);let l=(t.getAttribute("aria-label")??t.textContent??"").replace(/\s+/g," ").trim().slice(0,60);l&&(n.element_text=l);let c=t.getAttribute("name")??t.getAttribute("data-testid");return c&&(n.element_name=c),n}function P(e,t){try{let n=new URL(e,t);return`${n.origin}${n.pathname}`}catch{return e.split("?")[0].split("#")[0]}}function At(e){if(e instanceof Error)return e.message;if(typeof e=="string")return e;try{return JSON.stringify(e)}catch{return String(e)}}function St(e){return typeof e=="object"&&e!==null&&e.name==="AbortError"}function Lt(e,t){return t.some(n=>n.length>0&&e.startsWith(n))}function Y(e){let t=e.toLowerCase().split(".").filter(Boolean);return t.length<=2?t.join("."):t.slice(-2).join(".")}function Mt(e,t,n=[]){let r,i;try{r=new URL(e,t),i=new URL(t)}catch{return!0}if(r.protocol!=="http:"&&r.protocol!=="https:"||r.host===i.host||n.some(c=>c.length>0&&r.origin===c))return!0;let l=Y(r.hostname);return l.length>0&&l===Y(i.hostname)}function Tt(e){return e.aborted?null:!e.opaque&&(e.status===null||e.status>=400)?"http_error":e.durationMs>=e.slowMs?"slow_request":null}function Pt(e){try{let n=e?.getEntriesByType?.("navigation")?.[0]?.responseStatus;return typeof n=="number"&&n>0?n:null}catch{return null}}function Ot(e){return e.id||e.getAttribute("name")||e.getAttribute("data-testid")||P(e.getAttribute("action")??"",location.href).replace(location.origin,"")||"form"}function Rt(){return typeof location>"u"?"/":location.pathname||"/"}function Q(e){let t={onNavigate:()=>{},teardown:()=>{}};if(typeof window>"u"||typeof document>"u")return t;let n=window;if(n[q])return t;n[q]=!0;let r=(o,s)=>{try{e.onFriction({kind:o,properties:{path:Rt(),...s}})}catch{return}},i=[],l=new $(e.rageClicks,e.rageWindowMs,e.rageRadiusPx),c=new K(e.navLoopWindowMs),p=e.slowRequestMs??3e3,f=new Map,b=o=>{let s=o.target instanceof Element?o.target:null;kt(s)&&r("dead_click",z(s)),l.push(o.clientX,o.clientY,o.timeStamp||Date.now())&&r("rage_click",z(s))};document.addEventListener("click",b,!0),i.push(()=>document.removeEventListener("click",b,!0));let x=o=>{r("js_error",{message:String(o.message??"").slice(0,200),...o.filename?{source:P(o.filename,location.href)}:{},...o.lineno?{line:o.lineno}:{}})};window.addEventListener("error",x),i.push(()=>window.removeEventListener("error",x));let m=o=>{r("js_error",{message:At(o.reason).slice(0,200),unhandled_rejection:!0})};window.addEventListener("unhandledrejection",m),i.push(()=>window.removeEventListener("unhandledrejection",m));let _=(e.ignoreUrls??[]).map(o=>P(o,location.href)),k=(e.apiOrigins??[]).map(o=>{try{return new URL(o,location.href).origin}catch{return""}}),A=(o,s,u,a,d={})=>{let h=P(o,location.href);if(Lt(h,_)||!Mt(h,location.href,k))return;let g=performance.now()-u,E=Tt({status:a,durationMs:g,slowMs:p,...d});E&&r(E,{url:h,method:s,duration_ms:Math.round(g),...d.opaque?{}:a!==null?{status:a}:{failed:!0}})},S=window.fetch;if(typeof S=="function"){let o=async(s,u)=>{let a=performance.now(),d=typeof s=="string"?s:s instanceof URL?s.href:s.url,h=(u?.method??(s instanceof Request?s.method:"GET")).toUpperCase();try{let g=await S(s,u);return A(d,h,a,g.status,{opaque:g.type==="opaque"||g.type==="opaqueredirect"}),g}catch(g){throw A(d,h,a,null,{aborted:St(g)}),g}};window.fetch=o,i.push(()=>{window.fetch=S})}let v=typeof XMLHttpRequest=="function"?XMLHttpRequest.prototype:null;if(v){let o=v.open,s=v.send,u=new WeakMap;v.open=function(a,d,...h){return u.set(this,{method:String(a).toUpperCase(),url:String(d)}),o.apply(this,[a,d,...h])},v.send=function(a){let d=u.get(this);if(d){let h=performance.now(),g=!1;this.addEventListener("abort",()=>{g=!0}),this.addEventListener("loadend",()=>{A(d.url,d.method,h,this.status===0?null:this.status,{aborted:g})})}return s.call(this,a)},i.push(()=>{v.open=o,v.send=s})}let C=Pt(typeof performance>"u"?void 0:performance);C!==null&&C>=400&&r("http_error",{document:!0,status:C,url:P(location.href)});let L=o=>{let s=o.target;if(!s||!Et.has(s.tagName.toLowerCase()))return;let u=s.form;if(!u)return;let a=f.get(u)??{form:Ot(u),fields:new Set},d=s.name||s.id||s.tagName.toLowerCase();a.fields.add(d),f.set(u,a)};document.addEventListener("focusin",L,!0),i.push(()=>document.removeEventListener("focusin",L,!0));let M=o=>{o.target instanceof HTMLFormElement&&f.delete(o.target)};document.addEventListener("submit",M,!0),i.push(()=>document.removeEventListener("submit",M,!0));let T=()=>{for(let o of f.values())r("form_abandon",{form:o.form,fields_touched:o.fields.size});f.clear()};return window.addEventListener("pagehide",T),i.push(()=>window.removeEventListener("pagehide",T)),{onNavigate:o=>{T();let s=c.push(o,Date.now());s&&r("nav_loop",{from:s.from,to:s.to})},teardown:()=>{for(let o of i)o();f.clear(),n[q]=!1}}}var It=["utm_source","utm_medium","utm_campaign","utm_term","utm_content"],V="holdy_session",Z="holdy_attribution";function tt(e){try{let t=new URL(e);return`${t.origin}${t.pathname}`}catch{return null}}function Ut(e){let t={};try{let n=new URL(e).searchParams;for(let r of It){let i=n.get(r)?.trim();i&&(t[r]=i.toLowerCase().slice(0,120))}}catch{}return t}function Ft(e){if(!e)return{};let t=e.toLowerCase(),n=t.includes("edg/")?"Edge":t.includes("opr/")||t.includes("opera")?"Opera":t.includes("samsungbrowser")?"Samsung Internet":t.includes("firefox")||t.includes("fxios")?"Firefox":t.includes("chrome")||t.includes("crios")?"Chrome":t.includes("safari")?"Safari":void 0,r=t.includes("iphone")||t.includes("ipad")||t.includes("ipod")?"iOS":t.includes("android")?"Android":t.includes("windows")?"Windows":t.includes("mac os")||t.includes("macintosh")?"macOS":t.includes("cros")?"ChromeOS":t.includes("linux")?"Linux":void 0,i=t.includes("ipad")||t.includes("android")&&!t.includes("mobile")?"tablet":t.includes("mobi")||t.includes("iphone")||t.includes("ipod")?"mobile":"desktop";return{...n?{browser:n}:{},...r?{os:r}:{},device:i}}function et(e,t){try{return(e==="session"?sessionStorage:localStorage).getItem(t)}catch{return null}}function nt(e,t,n){try{(e==="session"?sessionStorage:localStorage).setItem(t,n)}catch{}}function Nt(){try{return crypto.randomUUID()}catch{return`hol-${Date.now().toString(36)}-${Math.random().toString(36).slice(2,10)}`}}function Ht(){let e=et("session",V);if(e)return e;let t=Nt();return nt("session",V,t),t}function qt(e,t,n){let r=et("session",Z);if(r)try{return JSON.parse(r)}catch{}let i={...Ut(e)},l=$t(t);if(l&&l!==n){let c=tt(t);c&&(i.referrer=c),i.referrer_host=l}return nt("session",Z,JSON.stringify(i)),i}function $t(e){try{return new URL(e).hostname.toLowerCase()||null}catch{return null}}function rt(){if(typeof window>"u"||typeof location>"u")return{};let e=location.href,t=typeof document<"u"?document.referrer:"",n=location.hostname.toLowerCase(),r=typeof navigator<"u"?navigator.userAgent:"",{browser:i,os:l,device:c}=Ft(r),p=tt(e);return{...p?{url:p}:{},path:location.pathname||"/",host:n,...i?{browser:i}:{},...l?{os:l}:{},...c?{device:c}:{},...typeof navigator<"u"&&navigator.language?{locale:navigator.language}:{},session_id:Ht(),...qt(e,t,n)}}function Kt(e){let t=2166136261;for(let n=0;n<e.length;n++)t^=e.charCodeAt(n),t=Math.imul(t,16777619)>>>0;return t>>>0}function Dt(e,t){return Kt(`${e}:${t}`)%100}function ot(e,t,n){return Dt(e,t)<n?"holdout":"change"}function it(e,t){let n=e.split(/[?#]/)[0].split("/").filter(Boolean),r=t.split(/[?#]/)[0].split("/").filter(Boolean);return n.length!==r.length?!1:n.every((i,l)=>i.startsWith(":")||i===r[l])}var Wt=300*1e3,Bt=60*1e3,jt=250,st="holdy_changes_seen",at="holdy_changes_dismissed",ut=8,Xt=`:host{all:initial}
2
+ .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}
3
+ .tip p{margin:0;flex:1}
4
+ .tip button{all:unset;cursor:pointer;opacity:.7;font-size:15px;line-height:1}
5
+ .tip button:hover,.tip button:focus-visible{opacity:1}
6
+ @media (prefers-color-scheme:dark){.tip{background:#fff;color:#111}}`;function w(e){return e.replace(/\s+/g," ").trim()}function Gt(e){let t=e?.changes;return Array.isArray(t)?t.filter(Yt):[]}function zt(e){let t=e;return t?.kind==="page"?!0:(t?.kind==="selector"||t?.kind==="text")&&typeof t.value=="string"&&t.value.length>0}function Yt(e){let t=e;return typeof t?.id=="string"&&(t.kind==="copy"||t.kind==="guidance")&&typeof t.path=="string"&&zt(t.anchor)&&typeof t.content?.text=="string"&&typeof t.holdout_pct=="number"}function Jt(e,t,n){return n?e.filter(r=>it(r.path,t)).map(r=>({change:r,arm:ot(r.id,n,r.holdout_pct)})):[]}function D(e){try{return e==="local"?window.localStorage:window.sessionStorage}catch{return}}function W(e,t){try{return new Set(JSON.parse(e?.getItem(t)??"[]"))}catch{return new Set}}function lt(e,t,n){try{let r=W(e,t);r.add(n),e?.setItem(t,JSON.stringify([...r].slice(-200)))}catch{return}}function Qt(e){try{return document.querySelector(e)}catch{return null}}var Vt='button,a,[role="button"],label,summary,h1,h2,h3,h4,li,td,th,p,span,strong',Zt=3e3;function te(e){let t=w(e),n=null,r=Number.POSITIVE_INFINITY,i=document.querySelectorAll(Vt),l=Math.min(i.length,Zt);for(let c=0;c<l;c+=1){let p=i[c],f=p.textContent??"";f.length>=r||w(f)!==t||(n=p,r=f.length)}return n}function ee(e){return e.kind==="page"?null:e.kind==="selector"?Qt(e.value):te(e.value)}function ne(e,t){let n=document.createTreeWalker(e,NodeFilter.SHOW_TEXT);for(let r=n.nextNode();r;r=n.nextNode())if(t(w(r.textContent??"")))return r;return null}function ct(e){if(typeof window>"u"||typeof document>"u")return{onNavigate:()=>{},refresh:async()=>{},teardown:()=>{}};let t=[],n=0,r=null,i=null,l=null,c=new Map,p=new Map,f=o=>{e.debug&&console.warn(`[holdy] ${o}`)},b=(o,s)=>{let u=`${o.id}:${s}`,a=D("session");W(a,st).has(u)||(lt(a,st,u),e.track("$change_exposure",{change_id:o.id,arm:s,kind:o.kind}))},x=(o,s)=>{let u=s.expected_text?w(s.expected_text):null,a=w(s.content.text);return ne(o,d=>d.length>0&&(d===a||u===null||d===u))},m=(o,s)=>{let u=w(s.content.text),a=c.get(s.id);if(a&&a.node.isConnected&&w(a.node.textContent??"")===u)return!0;let d=x(o,s);if(!d)return!1;let h=d.textContent??"";return w(h)!==u&&(c.set(s.id,{node:d,original:h,next:s.content.text}),d.textContent=h.replace(h.trim(),s.content.text)),!0},_=o=>{let s=c.get(o);s&&(s.node.isConnected&&w(s.node.textContent??"")===w(s.next)&&(s.node.textContent=s.original),c.delete(o))},k=()=>{if(i)return i;if(!document.body)return null;l=document.createElement("holdy-changes"),l.setAttribute("data-holdy-root","1"),l.style.cssText="position:fixed;top:0;left:0;width:0;height:0;z-index:2147483647;pointer-events:none",i=l.attachShadow({mode:"closed"});let o=document.createElement("style");return o.textContent=Xt,i.appendChild(o),document.body.appendChild(l),i},A=(o,s)=>{if(p.has(s.id)||W(D("local"),at).has(s.id))return!0;let u=k();if(!u)return!1;let a=document.createElement("div");a.className="tip",a.setAttribute("role","tooltip");let d=document.createElement("p");d.textContent=s.content.text;let h=document.createElement("button");h.type="button",h.setAttribute("aria-label","Fechar"),h.textContent="\xD7",a.append(d,h),a.style.top="-9999px",a.style.left="-9999px",u.appendChild(a);let g=()=>{a.remove(),window.removeEventListener("scroll",E,!0),window.removeEventListener("resize",E),p.delete(s.id)};function E(){if(!o){let j=a.getBoundingClientRect();a.style.top=`${Math.max(8,window.innerHeight-j.height-16)}px`,a.style.left=`${Math.max(8,window.innerWidth-j.width-16)}px`;return}if(!o.isConnected){g();return}let O=o.getBoundingClientRect(),R=a.getBoundingClientRect(),F=O.bottom+ut;F+R.height>window.innerHeight-8&&(F=O.top-R.height-ut);let pt=Math.min(Math.max(8,O.left+O.width/2-R.width/2),window.innerWidth-R.width-8);a.style.top=`${Math.max(8,F)}px`,a.style.left=`${Math.max(8,pt)}px`}return h.addEventListener("click",()=>{lt(D("local"),at,s.id),e.track("$change_dismissed",{change_id:s.id,kind:s.kind}),g()}),p.set(s.id,g),requestAnimationFrame(E),window.addEventListener("scroll",E,!0),window.addEventListener("resize",E),!0},S=()=>{r=null;let o=Jt(t,location.pathname,e.getAccountKey()),s=new Set(o.map(u=>u.change.id));for(let u of[...c.keys()])s.has(u)||_(u);for(let[u,a]of[...p])s.has(u)||a();for(let{change:u,arm:a}of o){let d=ee(u.anchor);if(!(!d&&u.anchor.kind!=="page"))if(u.kind==="copy"){if(!d)continue;(a==="change"?m(d,u):x(d,u)!==null)&&b(u,a)}else(a==="holdout"||A(d,u))&&b(u,a)}},v=()=>{r===null&&(r=setTimeout(S,jt))},C=async()=>{n=Date.now();try{let o=await fetch(`${e.baseUrl}/runtime/changes`,{headers:{Authorization:`Bearer ${e.apiKey}`}});if(!o.ok){f(`changes ${o.status}`);return}t=Gt(await o.json()),v()}catch(o){f(`changes network error: ${o.message}`)}},L=typeof MutationObserver<"u"?new MutationObserver(()=>{(t.length>0||c.size>0||p.size>0)&&v()}):null,M=()=>{document.body&&L?.observe(document.body,{childList:!0,subtree:!0,characterData:!0})};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",M,{once:!0}):M();let T=setInterval(()=>void C(),Wt);return C(),{onNavigate:()=>{Date.now()-n>Bt&&C(),v()},refresh:C,teardown:()=>{clearInterval(T),r!==null&&clearTimeout(r),L?.disconnect();for(let o of[...c.keys()])_(o);for(let o of[...p.values()])o();l?.remove(),l=null,i=null,t=[]}}}var B="holdy_anon_id",U=class extends I{teardownAutoCapture=null;friction=null;changes=null;constructor(t){if(super("sdk_web",t),this.anonymousId=dt(),typeof window<"u"&&(window.addEventListener("pagehide",()=>void this.flush()),document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&this.flush()})),t.friction!==!1){let n=typeof t.friction=="object"?t.friction:{};this.friction=Q({...n,ignoreUrls:[...n.ignoreUrls??[],this.baseUrl],onFriction:r=>this.track(`$${r.kind}`,r.properties)})}t.autoCapture!==!1&&(this.teardownAutoCapture=G({onNavigate:n=>{this.page(void 0,{navigation:n}),this.friction?.onNavigate(typeof location<"u"?location.pathname:"/"),this.changes?.onNavigate()}})),t.productChanges!==!1&&typeof window<"u"&&(this.changes=ct({baseUrl:this.baseUrl,apiKey:this.apiKey,getAccountKey:()=>this.accountKey,track:(n,r)=>this.track(n,r),debug:t.debug}))}page(t,n){this.track("$pageview",{...n??{},...t?{page_name:t}:{},...typeof document<"u"&&document.title?{title:document.title}:{}})}group(t,n){super.group(t,n),this.changes?.onNavigate()}reset(){this.userId=null,this.accountKey=null;try{localStorage.removeItem(B)}catch{}this.anonymousId=dt(),this.changes?.onNavigate()}async shutdown(){this.teardownAutoCapture?.(),this.teardownAutoCapture=null,this.friction?.teardown(),this.friction=null,this.changes?.teardown(),this.changes=null,await super.shutdown()}environmentContext(){return rt()}};function dt(){try{let e=localStorage.getItem(B);if(e)return e;let t=crypto.randomUUID();return localStorage.setItem(B,t),t}catch{return crypto.randomUUID()}}var y=null;function re(e){return y||(y=new U(e)),y}function oe(e,t){y?.track(e,t)}function ie(e,t){y?.identify(e,t)}function se(e,t){y?.group(e,t)}function ae(e,t){y?.page(e,t)}function ue(){y?.reset()}async function le(){await y?.flush()}async function ce(){await y?.shutdown(),y=null}function de(){return y}return vt(pe);})();
2
7
  //# sourceMappingURL=holdy.js.map