@holdyapp/sdk 1.1.0 → 1.6.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 +53 -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 +871 -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 +871 -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/skeleton.d.ts +50 -0
- package/dist/skeleton.d.ts.map +1 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/web.d.ts +26 -0
- package/dist/web.d.ts.map +1 -1
- package/dist/web.mjs +824 -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/skeleton.test.ts +60 -0
- package/src/skeleton.ts +173 -0
- package/src/types.ts +2 -0
- package/src/web.ts +71 -1
package/README.md
CHANGED
|
@@ -17,6 +17,47 @@ 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
|
+
| `$page_skeleton` | esqueleto da página, uma vez por (rota, estrutura) por aba: elementos interativos sem texto livre nem valor de input | `path`, `hash`, `count`, `elements` |
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
initHoldy({ apiKey, friction: false }) // desliga
|
|
39
|
+
initHoldy({ apiKey, friction: { slowRequestMs: 5000 } }) // ajusta limiares
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
No Holdy esses eventos viram Behaviors de fricção e alimentam o contraste "sessões com fricção convertem X% menos" (ADR-0009).
|
|
43
|
+
|
|
44
|
+
### Mudanças no produto (default ligada)
|
|
45
|
+
|
|
46
|
+
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.
|
|
47
|
+
|
|
48
|
+
- Precisa de conta identificada (`group()` ou `accountKey`): sem ela nada é aplicado.
|
|
49
|
+
- 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.
|
|
50
|
+
- Só `textContent`, nunca HTML.
|
|
51
|
+
|
|
52
|
+
| Evento | Quando | Propriedades |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| `$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` |
|
|
55
|
+
| `$change_dismissed` | a pessoa fecha a dica | `change_id`, `kind` |
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
initHoldy({ apiKey, productChanges: false }) // desliga
|
|
59
|
+
```
|
|
60
|
+
|
|
20
61
|
## Server (`sdk_server` · Node ≥18 / Edge)
|
|
21
62
|
|
|
22
63
|
```ts
|
|
@@ -30,9 +71,20 @@ await holdy.flush() // ou holdy.shutdown() no teardown
|
|
|
30
71
|
|
|
31
72
|
Server é stateless por chamada: passe `userId`/`accountKey` em overrides (estado de sessão em server multi-tenant vaza identidade).
|
|
32
73
|
|
|
74
|
+
### Execuções de agente · `run()`
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
const resposta = await holdy.run({ name: 'support_agent', accountKey: 'acme-inc' }, async (run) => {
|
|
78
|
+
run.track('ticket_drafted', { ai_tokens: 1200, ai_cost_usd: 0.02 })
|
|
79
|
+
return resolverTicket(ticket)
|
|
80
|
+
})
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
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.
|
|
84
|
+
|
|
33
85
|
## Semântica
|
|
34
86
|
|
|
35
87
|
- Fila em memória com flush automático (`flushAt` default 20 · `flushIntervalMs` default 5000) e batches de até 500.
|
|
36
88
|
- 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.
|
|
89
|
+
- 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
90
|
- 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
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
|
-
|
|
12
|
-
|
|
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
|
|
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
|
package/dist/core.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"AAAA
|
|
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 H=Object.defineProperty;var mt=Object.getOwnPropertyDescriptor;var yt=Object.getOwnPropertyNames;var wt=Object.prototype.hasOwnProperty;var vt=(e,t)=>{for(var n in t)H(e,n,{get:t[n],enumerable:!0})},bt=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of yt(t))!wt.call(e,o)&&o!==n&&H(e,o,{get:()=>t[o],enumerable:!(r=mt(t,o))||r.enumerable});return e};var xt=e=>bt(H({},"__esModule",{value:!0}),e);var Se={};vt(Se,{flush:()=>be,getInstance:()=>ke,group:()=>ye,identify:()=>me,init:()=>fe,page:()=>we,reset:()=>ve,shutdown:()=>xe,track:()=>ge});var kt="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??kt).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 o=r?.userId??this.userId??void 0,a=r?.accountKey??this.accountKey??void 0,l=r?.occurredAt?new Date(r.occurredAt).toISOString():new Date().toISOString(),d={...this.environmentContext(),...r?.context??{}};a&&(d.account_external_id=a);let p={event:t,occurred_at:l,source:this.source,external_id:Et(),...o?{user_id:o}:{},...this.anonymousId?{anonymous_id:this.anonymousId}:{},properties:n??{},...Object.keys(d).length>0?{context:d}:{}};this.queue.push(p),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:St(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 St(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 Et(){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 q="__holdy_autocapture__";function Y(e){if(typeof window>"u"||typeof history>"u")return()=>{};let t=window;if(t[q])return()=>{};t[q]=!0;let n=[],r=G(),o=null;function a(m){try{e.onNavigate(m)}catch{}}function l(){o&&clearTimeout(o),o=setTimeout(()=>{o=null;let m=G();m!==r&&(r=m,a("route_change"))},50)}a("initial_load");let d=history.pushState,p=history.replaceState;history.pushState=function(...k){let _=d.apply(this,k);return l(),_},history.replaceState=function(...k){let _=p.apply(this,k);return l(),_},n.push(()=>{history.pushState=d,history.replaceState=p});let b=()=>l();window.addEventListener("popstate",b),n.push(()=>window.removeEventListener("popstate",b));let x=()=>l();return window.addEventListener("hashchange",x),n.push(()=>window.removeEventListener("hashchange",x)),()=>{o&&clearTimeout(o);for(let m of n)m();t[q]=!1}}function G(){return typeof location>"u"?"/":`${location.pathname}${location.search}${location.hash}`}var K="__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"]),At=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(o=>r-o.t<=this.windowMs&&Math.hypot(o.x-t,o.y-n)<=this.radiusPx),this.clicks.push({x:t,y:n,t:r}),this.clicks.length>=this.threshold?(this.clicks=[],!0):!1}},D=class{constructor(t=6e4){this.windowMs=t}visits=[];push(t,n){if(this.visits=[...this.visits,{path:t,t:n}].filter(d=>n-d.t<=this.windowMs).slice(-4),this.visits.length<4)return null;let[r,o,a,l]=this.visits.map(d=>d.path);return r===a&&o===l&&r!==o?(this.visits=[],{from:r,to:o}):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 Lt(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 V(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 a=(t.getAttribute("aria-label")??t.textContent??"").replace(/\s+/g," ").trim().slice(0,60);a&&(n.element_text=a);let l=t.getAttribute("name")??t.getAttribute("data-testid");return l&&(n.element_name=l),n}function O(e,t){try{let n=new URL(e,t);return`${n.origin}${n.pathname}`}catch{return e.split("?")[0].split("#")[0]}}function Tt(e){if(e instanceof Error)return e.message;if(typeof e=="string")return e;try{return JSON.stringify(e)}catch{return String(e)}}function Mt(e){return typeof e=="object"&&e!==null&&e.name==="AbortError"}function Ot(e,t){return t.some(n=>n.length>0&&e.startsWith(n))}function z(e){let t=e.toLowerCase().split(".").filter(Boolean);return t.length<=2?t.join("."):t.slice(-2).join(".")}function Rt(e,t,n=[]){let r,o;try{r=new URL(e,t),o=new URL(t)}catch{return!0}if(r.protocol!=="http:"&&r.protocol!=="https:"||r.host===o.host||n.some(l=>l.length>0&&r.origin===l))return!0;let a=z(r.hostname);return a.length>0&&a===z(o.hostname)}function Nt(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 It(e){return e.id||e.getAttribute("name")||e.getAttribute("data-testid")||O(e.getAttribute("action")??"",location.href).replace(location.origin,"")||"form"}function Ut(){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[K])return t;n[K]=!0;let r=(i,s)=>{try{e.onFriction({kind:i,properties:{path:Ut(),...s}})}catch{return}},o=[],a=new $(e.rageClicks,e.rageWindowMs,e.rageRadiusPx),l=new D(e.navLoopWindowMs),d=e.slowRequestMs??3e3,p=new Map,b=i=>{let s=i.target instanceof Element?i.target:null;Lt(s)&&r("dead_click",V(s)),a.push(i.clientX,i.clientY,i.timeStamp||Date.now())&&r("rage_click",V(s))};document.addEventListener("click",b,!0),o.push(()=>document.removeEventListener("click",b,!0));let x=i=>{r("js_error",{message:String(i.message??"").slice(0,200),...i.filename?{source:O(i.filename,location.href)}:{},...i.lineno?{line:i.lineno}:{}})};window.addEventListener("error",x),o.push(()=>window.removeEventListener("error",x));let m=i=>{r("js_error",{message:Tt(i.reason).slice(0,200),unhandled_rejection:!0})};window.addEventListener("unhandledrejection",m),o.push(()=>window.removeEventListener("unhandledrejection",m));let k=(e.ignoreUrls??[]).map(i=>O(i,location.href)),_=(e.apiOrigins??[]).map(i=>{try{return new URL(i,location.href).origin}catch{return""}}),C=(i,s,c,u,h={})=>{let f=O(i,location.href);if(Ot(f,k)||!Rt(f,location.href,_))return;let g=performance.now()-c,E=Nt({status:u,durationMs:g,slowMs:d,...h});E&&r(E,{url:f,method:s,duration_ms:Math.round(g),...h.opaque?{}:u!==null?{status:u}:{failed:!0}})},A=window.fetch;if(typeof A=="function"){let i=async(s,c)=>{let u=performance.now(),h=typeof s=="string"?s:s instanceof URL?s.href:s.url,f=(c?.method??(s instanceof Request?s.method:"GET")).toUpperCase();try{let g=await A(s,c);return C(h,f,u,g.status,{opaque:g.type==="opaque"||g.type==="opaqueredirect"}),g}catch(g){throw C(h,f,u,null,{aborted:Mt(g)}),g}};window.fetch=i,o.push(()=>{window.fetch=A})}let w=typeof XMLHttpRequest=="function"?XMLHttpRequest.prototype:null;if(w){let i=w.open,s=w.send,c=new WeakMap;w.open=function(u,h,...f){return c.set(this,{method:String(u).toUpperCase(),url:String(h)}),i.apply(this,[u,h,...f])},w.send=function(u){let h=c.get(this);if(h){let f=performance.now(),g=!1;this.addEventListener("abort",()=>{g=!0}),this.addEventListener("loadend",()=>{C(h.url,h.method,f,this.status===0?null:this.status,{aborted:g})})}return s.call(this,u)},o.push(()=>{w.open=i,w.send=s})}let S=Pt(typeof performance>"u"?void 0:performance);S!==null&&S>=400&&r("http_error",{document:!0,status:S,url:O(location.href)});let L=i=>{let s=i.target;if(!s||!At.has(s.tagName.toLowerCase()))return;let c=s.form;if(!c)return;let u=p.get(c)??{form:It(c),fields:new Set},h=s.name||s.id||s.tagName.toLowerCase();u.fields.add(h),p.set(c,u)};document.addEventListener("focusin",L,!0),o.push(()=>document.removeEventListener("focusin",L,!0));let T=i=>{i.target instanceof HTMLFormElement&&p.delete(i.target)};document.addEventListener("submit",T,!0),o.push(()=>document.removeEventListener("submit",T,!0));let M=()=>{for(let i of p.values())r("form_abandon",{form:i.form,fields_touched:i.fields.size});p.clear()};return window.addEventListener("pagehide",M),o.push(()=>window.removeEventListener("pagehide",M)),{onNavigate:i=>{M();let s=l.push(i,Date.now());s&&r("nav_loop",{from:s.from,to:s.to})},teardown:()=>{for(let i of o)i();p.clear(),n[K]=!1}}}var Ft='a[href], button, input, select, textarea, [role="button"], [role="link"], [role="tab"], [role="menuitem"], [role="checkbox"], [role="radio"], [role="switch"], [data-testid]';var Z="holdy_skeletons";function Ht(e){let t=2166136261;for(let n=0;n<e.length;n++)t^=e.charCodeAt(n),t=Math.imul(t,16777619)>>>0;return t.toString(16).padStart(8,"0")}function R(e,t,n){let r=e.getAttribute(t);return r&&r.replace(/\s+/g," ").trim().slice(0,n)||void 0}function qt(e){if(!e.getBoundingClientRect)return!0;let t=e.getBoundingClientRect();return t.width>0&&t.height>0}function Kt(e,t){let n=(r,o)=>o>0?Math.max(0,Math.min(100,Math.round(r/o*100))):0;return[n(e.x,t.width),n(e.y+t.scrollY,t.height),n(e.width,t.width),n(e.height,t.height)]}function $t(e,t){let n={t:e.tagName.toLowerCase()},r=R(e,"role",40);r&&(n.r=r);let o=R(e,"data-testid",80);o&&(n.id=o);let a=R(e,"name",80);a&&(n.n=a);let l=R(e,"aria-label",80);if(l&&(n.l=l),n.t==="input"||n.t==="button"){let d=R(e,"type",20);d&&(n.k=d.toLowerCase())}return t&&e.getBoundingClientRect&&(n.b=Kt(e.getBoundingClientRect(),t)),n}function Dt(e,t,n){let r=e.querySelectorAll(Ft),o=[];for(let d=0;d<r.length;d++){let p=r[d];p.tagName.toLowerCase()==="input"&&(p.getAttribute("type")??"").toLowerCase()==="hidden"||qt(p)&&o.push(p)}let a=o.slice(0,80).map(d=>$t(d,n)),l=a.map(({b:d,...p})=>p);return{path:t,hash:Ht(JSON.stringify(l)),count:o.length,elements:a}}function Wt(){try{let e=sessionStorage.getItem(Z),t=e?JSON.parse(e):[];return new Set(Array.isArray(t)?t.filter(n=>typeof n=="string"):[])}catch{return new Set}}function Bt(e){try{sessionStorage.setItem(Z,JSON.stringify([...e].slice(-200)))}catch{return}}function tt(e){if(typeof window>"u"||typeof document>"u")return{onNavigate:()=>{},teardown:()=>{}};let t=e.delayMs??1500,n=Wt(),r=null,o=a=>{if(r=null,location.pathname===a)try{let l={width:window.innerWidth,height:Math.max(document.documentElement.scrollHeight,window.innerHeight),scrollY:window.scrollY},d=Dt(document,a,l),p=`${a}:${d.hash}`;if(n.has(p))return;n.add(p),Bt(n),e.onSkeleton(d)}catch{return}};return{onNavigate:a=>{r&&clearTimeout(r),r=setTimeout(()=>o(a),t)},teardown:()=>{r&&clearTimeout(r),r=null}}}var Xt=["utm_source","utm_medium","utm_campaign","utm_term","utm_content"],et="holdy_session",nt="holdy_attribution";function rt(e){try{let t=new URL(e);return`${t.origin}${t.pathname}`}catch{return null}}function jt(e){let t={};try{let n=new URL(e).searchParams;for(let r of Xt){let o=n.get(r)?.trim();o&&(t[r]=o.toLowerCase().slice(0,120))}}catch{}return t}function Gt(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,o=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:o}}function ot(e,t){try{return(e==="session"?sessionStorage:localStorage).getItem(t)}catch{return null}}function it(e,t,n){try{(e==="session"?sessionStorage:localStorage).setItem(t,n)}catch{}}function Yt(){try{return crypto.randomUUID()}catch{return`hol-${Date.now().toString(36)}-${Math.random().toString(36).slice(2,10)}`}}function Vt(){let e=ot("session",et);if(e)return e;let t=Yt();return it("session",et,t),t}function zt(e,t,n){let r=ot("session",nt);if(r)try{return JSON.parse(r)}catch{}let o={...jt(e)},a=Jt(t);if(a&&a!==n){let l=rt(t);l&&(o.referrer=l),o.referrer_host=a}return it("session",nt,JSON.stringify(o)),o}function Jt(e){try{return new URL(e).hostname.toLowerCase()||null}catch{return null}}function st(){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:o,os:a,device:l}=Gt(r),d=rt(e);return{...d?{url:d}:{},path:location.pathname||"/",host:n,...o?{browser:o}:{},...a?{os:a}:{},...l?{device:l}:{},...typeof navigator<"u"&&navigator.language?{locale:navigator.language}:{},session_id:Vt(),...zt(e,t,n)}}function Qt(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 Zt(e,t){return Qt(`${e}:${t}`)%100}function at(e,t,n){return Zt(e,t)<n?"holdout":"change"}function ut(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((o,a)=>o.startsWith(":")||o===r[a])}var te=300*1e3,ee=60*1e3,ne=250,lt="holdy_changes_seen",ct="holdy_changes_dismissed",dt=8,re=`: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 v(e){return e.replace(/\s+/g," ").trim()}function oe(e){let t=e?.changes;return Array.isArray(t)?t.filter(se):[]}function ie(e){let t=e;return t?.kind==="page"?!0:(t?.kind==="selector"||t?.kind==="text")&&typeof t.value=="string"&&t.value.length>0}function se(e){let t=e;return typeof t?.id=="string"&&(t.kind==="copy"||t.kind==="guidance")&&typeof t.path=="string"&&ie(t.anchor)&&typeof t.content?.text=="string"&&typeof t.holdout_pct=="number"}function ae(e,t,n){return n?e.filter(r=>ut(r.path,t)).map(r=>({change:r,arm:at(r.id,n,r.holdout_pct)})):[]}function W(e){try{return e==="local"?window.localStorage:window.sessionStorage}catch{return}}function B(e,t){try{return new Set(JSON.parse(e?.getItem(t)??"[]"))}catch{return new Set}}function ht(e,t,n){try{let r=B(e,t);r.add(n),e?.setItem(t,JSON.stringify([...r].slice(-200)))}catch{return}}function ue(e){try{return document.querySelector(e)}catch{return null}}var le='button,a,[role="button"],label,summary,h1,h2,h3,h4,li,td,th,p,span,strong',ce=3e3;function de(e){let t=v(e),n=null,r=Number.POSITIVE_INFINITY,o=document.querySelectorAll(le),a=Math.min(o.length,ce);for(let l=0;l<a;l+=1){let d=o[l],p=d.textContent??"";p.length>=r||v(p)!==t||(n=d,r=p.length)}return n}function he(e){return e.kind==="page"?null:e.kind==="selector"?ue(e.value):de(e.value)}function pe(e,t){let n=document.createTreeWalker(e,NodeFilter.SHOW_TEXT);for(let r=n.nextNode();r;r=n.nextNode())if(t(v(r.textContent??"")))return r;return null}function pt(e){if(typeof window>"u"||typeof document>"u")return{onNavigate:()=>{},refresh:async()=>{},teardown:()=>{}};let t=[],n=0,r=null,o=null,a=null,l=new Map,d=new Map,p=i=>{e.debug&&console.warn(`[holdy] ${i}`)},b=(i,s)=>{let c=`${i.id}:${s}`,u=W("session");B(u,lt).has(c)||(ht(u,lt,c),e.track("$change_exposure",{change_id:i.id,arm:s,kind:i.kind}))},x=(i,s)=>{let c=s.expected_text?v(s.expected_text):null,u=v(s.content.text);return pe(i,h=>h.length>0&&(h===u||c===null||h===c))},m=(i,s)=>{let c=v(s.content.text),u=l.get(s.id);if(u&&u.node.isConnected&&v(u.node.textContent??"")===c)return!0;let h=x(i,s);if(!h)return!1;let f=h.textContent??"";return v(f)!==c&&(l.set(s.id,{node:h,original:f,next:s.content.text}),h.textContent=f.replace(f.trim(),s.content.text)),!0},k=i=>{let s=l.get(i);s&&(s.node.isConnected&&v(s.node.textContent??"")===v(s.next)&&(s.node.textContent=s.original),l.delete(i))},_=()=>{if(o)return o;if(!document.body)return null;a=document.createElement("holdy-changes"),a.setAttribute("data-holdy-root","1"),a.style.cssText="position:fixed;top:0;left:0;width:0;height:0;z-index:2147483647;pointer-events:none",o=a.attachShadow({mode:"closed"});let i=document.createElement("style");return i.textContent=re,o.appendChild(i),document.body.appendChild(a),o},C=(i,s)=>{if(d.has(s.id)||B(W("local"),ct).has(s.id))return!0;let c=_();if(!c)return!1;let u=document.createElement("div");u.className="tip",u.setAttribute("role","tooltip");let h=document.createElement("p");h.textContent=s.content.text;let f=document.createElement("button");f.type="button",f.setAttribute("aria-label","Fechar"),f.textContent="\xD7",u.append(h,f),u.style.top="-9999px",u.style.left="-9999px",c.appendChild(u);let g=()=>{u.remove(),window.removeEventListener("scroll",E,!0),window.removeEventListener("resize",E),d.delete(s.id)};function E(){if(!i){let j=u.getBoundingClientRect();u.style.top=`${Math.max(8,window.innerHeight-j.height-16)}px`,u.style.left=`${Math.max(8,window.innerWidth-j.width-16)}px`;return}if(!i.isConnected){g();return}let N=i.getBoundingClientRect(),P=u.getBoundingClientRect(),F=N.bottom+dt;F+P.height>window.innerHeight-8&&(F=N.top-P.height-dt);let gt=Math.min(Math.max(8,N.left+N.width/2-P.width/2),window.innerWidth-P.width-8);u.style.top=`${Math.max(8,F)}px`,u.style.left=`${Math.max(8,gt)}px`}return f.addEventListener("click",()=>{ht(W("local"),ct,s.id),e.track("$change_dismissed",{change_id:s.id,kind:s.kind}),g()}),d.set(s.id,g),requestAnimationFrame(E),window.addEventListener("scroll",E,!0),window.addEventListener("resize",E),!0},A=()=>{r=null;let i=ae(t,location.pathname,e.getAccountKey()),s=new Set(i.map(c=>c.change.id));for(let c of[...l.keys()])s.has(c)||k(c);for(let[c,u]of[...d])s.has(c)||u();for(let{change:c,arm:u}of i){let h=he(c.anchor);if(!(!h&&c.anchor.kind!=="page"))if(c.kind==="copy"){if(!h)continue;(u==="change"?m(h,c):x(h,c)!==null)&&b(c,u)}else(u==="holdout"||C(h,c))&&b(c,u)}},w=()=>{r===null&&(r=setTimeout(A,ne))},S=async()=>{n=Date.now();try{let i=await fetch(`${e.baseUrl}/runtime/changes`,{headers:{Authorization:`Bearer ${e.apiKey}`}});if(!i.ok){p(`changes ${i.status}`);return}t=oe(await i.json()),w()}catch(i){p(`changes network error: ${i.message}`)}},L=typeof MutationObserver<"u"?new MutationObserver(()=>{(t.length>0||l.size>0||d.size>0)&&w()}):null,T=()=>{document.body&&L?.observe(document.body,{childList:!0,subtree:!0,characterData:!0})};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",T,{once:!0}):T();let M=setInterval(()=>void S(),te);return S(),{onNavigate:()=>{Date.now()-n>ee&&S(),w()},refresh:S,teardown:()=>{clearInterval(M),r!==null&&clearTimeout(r),L?.disconnect();for(let i of[...l.keys()])k(i);for(let i of[...d.values()])i();a?.remove(),a=null,o=null,t=[]}}}var X="holdy_anon_id",U=class extends I{teardownAutoCapture=null;friction=null;changes=null;skeleton=null;constructor(t){if(super("sdk_web",t),this.anonymousId=ft(),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.skeleton!==!1&&(this.skeleton=tt({onSkeleton:n=>this.track("$page_skeleton",{...n})})),t.autoCapture!==!1&&(this.teardownAutoCapture=Y({onNavigate:n=>{this.page(void 0,{navigation:n});let r=typeof location<"u"?location.pathname:"/";this.friction?.onNavigate(r),this.changes?.onNavigate(),this.skeleton?.onNavigate(r)}})),t.productChanges!==!1&&typeof window<"u"&&(this.changes=pt({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(X)}catch{}this.anonymousId=ft(),this.changes?.onNavigate()}async shutdown(){this.teardownAutoCapture?.(),this.teardownAutoCapture=null,this.friction?.teardown(),this.friction=null,this.changes?.teardown(),this.changes=null,this.skeleton?.teardown(),this.skeleton=null,await super.shutdown()}environmentContext(){return st()}};function ft(){try{let e=localStorage.getItem(X);if(e)return e;let t=crypto.randomUUID();return localStorage.setItem(X,t),t}catch{return crypto.randomUUID()}}var y=null;function fe(e){return y||(y=new U(e)),y}function ge(e,t){y?.track(e,t)}function me(e,t){y?.identify(e,t)}function ye(e,t){y?.group(e,t)}function we(e,t){y?.page(e,t)}function ve(){y?.reset()}async function be(){await y?.flush()}async function xe(){await y?.shutdown(),y=null}function ke(){return y}return xt(Se);})();
|
|
2
7
|
//# sourceMappingURL=holdy.js.map
|