@qoretechnologies/reqraft 0.10.56 → 0.10.57-pr.127.g0a792ea
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/.claude/CLAUDE.md +1 -0
- package/README.md +20 -0
- package/design/TRACKING.md +290 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/tracking/browser/attach.d.ts +8 -0
- package/dist/tracking/browser/attach.d.ts.map +1 -0
- package/dist/tracking/browser/attach.js +182 -0
- package/dist/tracking/browser/attach.js.map +1 -0
- package/dist/tracking/browser/env.d.ts +14 -0
- package/dist/tracking/browser/env.d.ts.map +1 -0
- package/dist/tracking/browser/env.js +150 -0
- package/dist/tracking/browser/env.js.map +1 -0
- package/dist/tracking/core/consent.d.ts +21 -0
- package/dist/tracking/core/consent.d.ts.map +1 -0
- package/dist/tracking/core/consent.js +39 -0
- package/dist/tracking/core/consent.js.map +1 -0
- package/dist/tracking/core/engagement.d.ts +20 -0
- package/dist/tracking/core/engagement.d.ts.map +1 -0
- package/dist/tracking/core/engagement.js +52 -0
- package/dist/tracking/core/engagement.js.map +1 -0
- package/dist/tracking/core/experiments.d.ts +109 -0
- package/dist/tracking/core/experiments.d.ts.map +1 -0
- package/dist/tracking/core/experiments.js +294 -0
- package/dist/tracking/core/experiments.js.map +1 -0
- package/dist/tracking/core/hash.d.ts +16 -0
- package/dist/tracking/core/hash.d.ts.map +1 -0
- package/dist/tracking/core/hash.js +31 -0
- package/dist/tracking/core/hash.js.map +1 -0
- package/dist/tracking/core/ids.d.ts +6 -0
- package/dist/tracking/core/ids.d.ts.map +1 -0
- package/dist/tracking/core/ids.js +31 -0
- package/dist/tracking/core/ids.js.map +1 -0
- package/dist/tracking/core/observable.d.ts +12 -0
- package/dist/tracking/core/observable.d.ts.map +1 -0
- package/dist/tracking/core/observable.js +32 -0
- package/dist/tracking/core/observable.js.map +1 -0
- package/dist/tracking/core/queue.d.ts +47 -0
- package/dist/tracking/core/queue.d.ts.map +1 -0
- package/dist/tracking/core/queue.js +205 -0
- package/dist/tracking/core/queue.js.map +1 -0
- package/dist/tracking/core/sections.d.ts +54 -0
- package/dist/tracking/core/sections.d.ts.map +1 -0
- package/dist/tracking/core/sections.js +156 -0
- package/dist/tracking/core/sections.js.map +1 -0
- package/dist/tracking/core/storage.d.ts +15 -0
- package/dist/tracking/core/storage.d.ts.map +1 -0
- package/dist/tracking/core/storage.js +72 -0
- package/dist/tracking/core/storage.js.map +1 -0
- package/dist/tracking/core/tracker.d.ts +122 -0
- package/dist/tracking/core/tracker.d.ts.map +1 -0
- package/dist/tracking/core/tracker.js +390 -0
- package/dist/tracking/core/tracker.js.map +1 -0
- package/dist/tracking/core/types.d.ts +130 -0
- package/dist/tracking/core/types.d.ts.map +1 -0
- package/dist/tracking/core/types.js +69 -0
- package/dist/tracking/core/types.js.map +1 -0
- package/dist/tracking/core/utm.d.ts +6 -0
- package/dist/tracking/core/utm.d.ts.map +1 -0
- package/dist/tracking/core/utm.js +30 -0
- package/dist/tracking/core/utm.js.map +1 -0
- package/dist/tracking/index.d.ts +46 -0
- package/dist/tracking/index.d.ts.map +1 -0
- package/dist/tracking/index.js +91 -0
- package/dist/tracking/index.js.map +1 -0
- package/dist/tracking/markup.d.ts +12 -0
- package/dist/tracking/markup.d.ts.map +1 -0
- package/dist/tracking/markup.js +19 -0
- package/dist/tracking/markup.js.map +1 -0
- package/dist/tracking/react/Experiment.d.ts +23 -0
- package/dist/tracking/react/Experiment.d.ts.map +1 -0
- package/dist/tracking/react/Experiment.js +28 -0
- package/dist/tracking/react/Experiment.js.map +1 -0
- package/dist/tracking/react/TrackingProvider.d.ts +26 -0
- package/dist/tracking/react/TrackingProvider.d.ts.map +1 -0
- package/dist/tracking/react/TrackingProvider.js +45 -0
- package/dist/tracking/react/TrackingProvider.js.map +1 -0
- package/dist/tracking/react/hooks.d.ts +39 -0
- package/dist/tracking/react/hooks.d.ts.map +1 -0
- package/dist/tracking/react/hooks.js +96 -0
- package/dist/tracking/react/hooks.js.map +1 -0
- package/dist/tracking/react/index.d.ts +7 -0
- package/dist/tracking/react/index.d.ts.map +1 -0
- package/dist/tracking/react/index.js +17 -0
- package/dist/tracking/react/index.js.map +1 -0
- package/dist/tracking/testing.d.ts +40 -0
- package/dist/tracking/testing.d.ts.map +1 -0
- package/dist/tracking/testing.js +140 -0
- package/dist/tracking/testing.js.map +1 -0
- package/dist/tracking/ui/ExperimentCard.d.ts +55 -0
- package/dist/tracking/ui/ExperimentCard.d.ts.map +1 -0
- package/dist/tracking/ui/ExperimentCard.js +340 -0
- package/dist/tracking/ui/ExperimentCard.js.map +1 -0
- package/dist/tracking/ui/ExperimentsOverlay.d.ts +59 -0
- package/dist/tracking/ui/ExperimentsOverlay.d.ts.map +1 -0
- package/dist/tracking/ui/ExperimentsOverlay.js +359 -0
- package/dist/tracking/ui/ExperimentsOverlay.js.map +1 -0
- package/dist/tracking/ui/anchor.d.ts +107 -0
- package/dist/tracking/ui/anchor.d.ts.map +1 -0
- package/dist/tracking/ui/anchor.js +108 -0
- package/dist/tracking/ui/anchor.js.map +1 -0
- package/dist/tracking/ui/card.d.ts +121 -0
- package/dist/tracking/ui/card.d.ts.map +1 -0
- package/dist/tracking/ui/card.js +417 -0
- package/dist/tracking/ui/card.js.map +1 -0
- package/dist/tracking/ui/client.d.ts +42 -0
- package/dist/tracking/ui/client.d.ts.map +1 -0
- package/dist/tracking/ui/client.js +123 -0
- package/dist/tracking/ui/client.js.map +1 -0
- package/dist/tracking/ui/index.d.ts +20 -0
- package/dist/tracking/ui/index.d.ts.map +1 -0
- package/dist/tracking/ui/index.js +60 -0
- package/dist/tracking/ui/index.js.map +1 -0
- package/dist/tracking/ui/types.d.ts +120 -0
- package/dist/tracking/ui/types.d.ts.map +1 -0
- package/dist/tracking/ui/types.js +8 -0
- package/dist/tracking/ui/types.js.map +1 -0
- package/dist/tracking/ui/useExperimentsAdmin.d.ts +31 -0
- package/dist/tracking/ui/useExperimentsAdmin.d.ts.map +1 -0
- package/dist/tracking/ui/useExperimentsAdmin.js +136 -0
- package/dist/tracking/ui/useExperimentsAdmin.js.map +1 -0
- package/package.json +1 -1
- package/src/index.tsx +5 -0
- package/src/stories/tracking.tsx +206 -0
- package/src/tracking/browser/attach.ts +186 -0
- package/src/tracking/browser/env.ts +78 -0
- package/src/tracking/core/consent.ts +37 -0
- package/src/tracking/core/engagement.ts +47 -0
- package/src/tracking/core/experiments.ts +258 -0
- package/src/tracking/core/hash.ts +26 -0
- package/src/tracking/core/ids.ts +25 -0
- package/src/tracking/core/observable.ts +23 -0
- package/src/tracking/core/queue.ts +131 -0
- package/src/tracking/core/sections.ts +166 -0
- package/src/tracking/core/storage.ts +69 -0
- package/src/tracking/core/tracker.ts +417 -0
- package/src/tracking/core/types.ts +162 -0
- package/src/tracking/core/utm.ts +23 -0
- package/src/tracking/index.ts +72 -0
- package/src/tracking/markup.ts +17 -0
- package/src/tracking/react/Experiment.tsx +44 -0
- package/src/tracking/react/TrackingProvider.tsx +56 -0
- package/src/tracking/react/hooks.ts +106 -0
- package/src/tracking/react/index.ts +13 -0
- package/src/tracking/testing.ts +108 -0
- package/src/tracking/ui/ExperimentCard.stories.tsx +290 -0
- package/src/tracking/ui/ExperimentCard.tsx +581 -0
- package/src/tracking/ui/ExperimentsOverlay.stories.tsx +155 -0
- package/src/tracking/ui/ExperimentsOverlay.tsx +437 -0
- package/src/tracking/ui/anchor.ts +180 -0
- package/src/tracking/ui/card.ts +464 -0
- package/src/tracking/ui/client.ts +117 -0
- package/src/tracking/ui/index.ts +77 -0
- package/src/tracking/ui/types.ts +110 -0
- package/src/tracking/ui/useExperimentsAdmin.ts +103 -0
package/.claude/CLAUDE.md
CHANGED
|
@@ -307,6 +307,7 @@ yarn build:test # Type-check without emit
|
|
|
307
307
|
| `src/components/form/` | Form field components |
|
|
308
308
|
| `src/components/log/` | Log display component |
|
|
309
309
|
| `src/components/menu/` | Navigation menu component |
|
|
310
|
+
| `src/tracking/` | First-party tracking, consent and A/B testing (`core/` no DOM, `browser/`, `react/`, and `ui/`: the A/B test card and overlay on Reqore); how-to in `design/TRACKING.md` |
|
|
310
311
|
| `src/types/` | Shared TypeScript types |
|
|
311
312
|
| `__tests__/` | Vitest unit tests (jsdom) |
|
|
312
313
|
|
package/README.md
CHANGED
|
@@ -148,6 +148,26 @@ client.onNotification('qonsole/sessionStateChanged', (params) => { /* … */ });
|
|
|
148
148
|
client.disconnect();
|
|
149
149
|
```
|
|
150
150
|
|
|
151
|
+
## Tracking, consent and A/B testing
|
|
152
|
+
|
|
153
|
+
First-party analytics with a consent gate and client-side A/B testing, for any Qore web app. The core is plain TypeScript with no DOM, the browser layer attaches the listeners, and the React bindings sit on top. Everything product-specific comes in through config; the server keeps each product's data separate by `property`.
|
|
154
|
+
|
|
155
|
+
```tsx
|
|
156
|
+
import { createBrowserTracker, Experiment, TrackingProvider } from '@qoretechnologies/reqraft/dist/tracking';
|
|
157
|
+
|
|
158
|
+
const tracking = createBrowserTracker({
|
|
159
|
+
property: 'ide',
|
|
160
|
+
endpoint: '/qorus-cloud/collect',
|
|
161
|
+
experimentsEndpoint: '/qorus-cloud/experiments',
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
<TrackingProvider tracker={tracking.tracker} attach={tracking.attach} location={pathname + search}>
|
|
165
|
+
<Experiment id='signup-cta' variants={{ a: <Current />, b: <New /> }} />
|
|
166
|
+
</TrackingProvider>;
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
`@qoretechnologies/reqraft/dist/tracking` loads nothing else from Reqraft; the same names are exported from the package root. The A/B test UI every product shows (`<ExperimentsOverlay>`, `<ExperimentCard>`, `useExperimentsAdmin`) is in `@qoretechnologies/reqraft/dist/tracking/ui`. Config, consent, `useExperiment`, `trackClick` / `trackSection` markup, `createMemoryTracker` for tests and the server contract: [design/TRACKING.md](design/TRACKING.md).
|
|
170
|
+
|
|
151
171
|
Clients on the same LSP endpoint share ONE underlying WebSocket — each `ReqraftLspClient` is a per-document facade over the shared connection, multiplexed by document URI (the server keys language sessions per document), so N editors cost one socket. Auto-reconnect re-opens every document on the shared socket, 15s request timeout, request/response correlation by `id`, pending requests rejected on close. A `connect()` never outlives its socket: once the socket exhausts `maxReconnectTries`, a handshake still waiting rejects, and the next `connect()` — from that client or any other — dials again, so an editor opened after an outage connects once the server is back, without a page reload. Document URIs should be opaque and client-generated — per the Qonsole LSP contract they must not contain session tokens, usernames, sandbox identifiers, or other secrets that end up in server logs.
|
|
152
172
|
|
|
153
173
|
## Community
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
# Tracking, consent and A/B testing
|
|
2
|
+
|
|
3
|
+
First-party analytics, a consent gate and client-side A/B testing for any Qore web app (the
|
|
4
|
+
landing site, the Qorus IDE, the admin portal). One module, one server: every product sends to
|
|
5
|
+
the same collector and reads the same experiments, and **the server keeps each product's data
|
|
6
|
+
separate by `property`**: events, goals, experiments and results never mix between properties.
|
|
7
|
+
|
|
8
|
+
The contract (event shapes, the batch envelope, experiment definitions, goals, statistics,
|
|
9
|
+
storage, the admin API) is owned by qorus-api:
|
|
10
|
+
[`design/analytics-and-experiments-design.md`](https://git.qoretechnologies.com/qorus/qorus-api/-/blob/develop/design/analytics-and-experiments-design.md)
|
|
11
|
+
(introduced in [qorus-api MR !34](https://git.qoretechnologies.com/qorus/qorus-api/-/merge_requests/34)).
|
|
12
|
+
Change the contract there first, then the code here. Section numbers below (§3, §5 …) refer to it.
|
|
13
|
+
|
|
14
|
+
## 1. What it does
|
|
15
|
+
|
|
16
|
+
- **Anonymous, with consent.** Nothing is sent and no id exists until the user allows tracking.
|
|
17
|
+
Then a random visitor id (`vid`) lives in `localStorage["<prefix>vid"]`. Declining or
|
|
18
|
+
withdrawing deletes it and stops everything.
|
|
19
|
+
- **Events** go in batches to the collector: page views, a page summary (time, engaged time,
|
|
20
|
+
scroll depth, per-section attention), clicks on tagged elements, a heartbeat, goals, custom
|
|
21
|
+
events and experiment exposures.
|
|
22
|
+
- **Experiments** are fetched from the experiments endpoint (`?property=<property>` appended)
|
|
23
|
+
and every visitor is assigned locally with GrowthBook's hash v2 on the `vid`, the same way on
|
|
24
|
+
every visit and every page.
|
|
25
|
+
- **Fails open.** No backend, no consent, blocked storage, an error anywhere: the app works as
|
|
26
|
+
without the module, every experiment shows its control, and nothing is thrown into the page.
|
|
27
|
+
|
|
28
|
+
## 2. Install and import
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
yarn add @qoretechnologies/reqraft
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Everything is exported from the package root, and from its own entry, which loads nothing else
|
|
35
|
+
from Reqraft (no Reqore, react-query or editors; React is its only dependency). Prefer the entry
|
|
36
|
+
in an app that does not already use Reqraft:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { createBrowserTracker, TrackingProvider, Experiment } from '@qoretechnologies/reqraft/dist/tracking';
|
|
40
|
+
// or
|
|
41
|
+
import { createBrowserTracker, TrackingProvider, Experiment } from '@qoretechnologies/reqraft';
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The module has three layers, and each only imports the ones above it:
|
|
45
|
+
|
|
46
|
+
| Layer | Folder | What |
|
|
47
|
+
|---|---|---|
|
|
48
|
+
| core | `src/tracking/core/` | plain TypeScript, no DOM: `Tracker`, consent, the batching queue, section and engagement clocks, hashing, assignment, the experiments client |
|
|
49
|
+
| browser | `src/tracking/browser/` | the only code that touches `window` / `document`: transport (`sendBeacon` → `fetch` keepalive), storage, listeners, `IntersectionObserver` on sections, delegated clicks |
|
|
50
|
+
| react | `src/tracking/react/` | `<TrackingProvider>`, `useTracker`, `useConsent`, `useExperiment`, `<Experiment>` |
|
|
51
|
+
|
|
52
|
+
Plus `markup.ts` (`trackClick`, `trackSection`) and `testing.ts` (`createMemoryTracker`). The A/B test UI (the card and the overlay, on Reqore) is a fourth layer, `src/tracking/ui/`, with its own entry (§8); the engine never imports it.
|
|
53
|
+
|
|
54
|
+
## 3. Configure one tracker per app
|
|
55
|
+
|
|
56
|
+
Create the tracker once, at module level, and mount the provider at the app shell:
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
import { createBrowserTracker, TrackingProvider } from '@qoretechnologies/reqraft/dist/tracking';
|
|
60
|
+
|
|
61
|
+
export const tracking = createBrowserTracker({
|
|
62
|
+
property: 'ide', // required: which product the data belongs to
|
|
63
|
+
endpoint: '/qorus-cloud/collect', // required: the collector (POST)
|
|
64
|
+
experimentsEndpoint: '/qorus-cloud/experiments', // required: running experiments (GET)
|
|
65
|
+
storagePrefix: 'qa.ide.', // when another product shares the origin
|
|
66
|
+
consentVersion: 1,
|
|
67
|
+
enabled: import.meta.env.PROD,
|
|
68
|
+
debug: false,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const App = () => {
|
|
72
|
+
const location = useLocation();
|
|
73
|
+
return (
|
|
74
|
+
<TrackingProvider tracker={tracking.tracker} attach={tracking.attach} location={location.pathname + location.search}>
|
|
75
|
+
<Routes>…</Routes>
|
|
76
|
+
</TrackingProvider>
|
|
77
|
+
);
|
|
78
|
+
};
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Each `location` change is a new page view. Without a provider (or with `tracker={null}`),
|
|
82
|
+
everything still renders: consent reads `unknown` and experiments show their control.
|
|
83
|
+
|
|
84
|
+
| Option | Default | Meaning |
|
|
85
|
+
|---|---|---|
|
|
86
|
+
| `property` | required | The product (`landing`, `ide`, `admin`, …). Sent with every batch and with the experiments request; the server keeps each property's data apart. |
|
|
87
|
+
| `endpoint` | required | Collector URL. Same origin in the products so far. |
|
|
88
|
+
| `experimentsEndpoint` | required | Running-experiments URL; `?property=` is appended. |
|
|
89
|
+
| `enabled` | `true` | `false`: nothing is ever sent and every experiment shows its control. Turn it off where a page view is not a visit (a preview iframe, a test build). |
|
|
90
|
+
| `consentVersion` | `1` | Bump when the consent text changes materially: stored answers to older versions count as no answer, so the prompt asks again. |
|
|
91
|
+
| `storagePrefix` | `qa.` | Prefix of every storage key. **Two products on one origin need different prefixes**, or they share one consent answer, one visitor id and one experiments cache. |
|
|
92
|
+
| `previewParam` | `qa_variant` | Query parameter of a shareable preview link. |
|
|
93
|
+
| `debug` | `false` | Log every batch (and every swallowed error) to the console. |
|
|
94
|
+
| `flushIntervalMs`, `maxQueued`, `heartbeatMs`, `sessionTimeoutMs`, `engagedWindowMs` | 5 s, 20, 15 s, 30 min, 10 s | Batching and timing. |
|
|
95
|
+
|
|
96
|
+
`resolveConfig(options)` gives the full config; `DEFAULT_CONFIG` holds the defaults.
|
|
97
|
+
|
|
98
|
+
## 4. Consent
|
|
99
|
+
|
|
100
|
+
```tsx
|
|
101
|
+
const { state, grant, deny, available } = useConsent(); // state: 'unknown' | 'granted' | 'denied'
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
- Stored in `localStorage["<prefix>consent"]` (`granted` | `denied`) and `<prefix>consent.v`
|
|
105
|
+
(the version answered).
|
|
106
|
+
- Before `granted`: no id, no request to the collector, controls only, sign-up links
|
|
107
|
+
undecorated. The experiments list is still fetched: it is public and carries no visitor data.
|
|
108
|
+
- `deny()` (declining or withdrawing) deletes `vid`, `exp`, `sid` and `utm` and drops unsent events.
|
|
109
|
+
- The consent prompt is the app's own UI. `openConsentSettings()` / `closeConsentSettings()` /
|
|
110
|
+
`useConsentPanelOpen()` let a "Cookie settings" link reopen it.
|
|
111
|
+
|
|
112
|
+
## 5. Experiments
|
|
113
|
+
|
|
114
|
+
Wrap what is being tested:
|
|
115
|
+
|
|
116
|
+
```tsx
|
|
117
|
+
<Experiment id='signup-cta' trigger='pricing' variants={{ a: <Current />, b: <New /> }} />
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
or, for logic rather than markup:
|
|
121
|
+
|
|
122
|
+
```tsx
|
|
123
|
+
const { variant, assignment, triggerRef } = useExperiment('signup-cta', { variants: ['a', 'b'] });
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
- `a` is the control unless `control="…"` says otherwise; the control must be today's behaviour.
|
|
127
|
+
- **The variant is decided once when the component mounts** and kept for the page view: no
|
|
128
|
+
flicker. A consent or definition that arrives later applies from the next page view.
|
|
129
|
+
- **Exposure** is logged once per page view, only for enrolled visitors, when the trigger is
|
|
130
|
+
seen: the definition's `trigger.section`, else the `trigger` prop, else the element you put
|
|
131
|
+
`triggerRef` on, else at render.
|
|
132
|
+
- Visitors without consent, outside `traffic`, or not targeted see the control and log nothing.
|
|
133
|
+
A concluded experiment shows its winner to everybody and logs nothing.
|
|
134
|
+
- Order of `tracker.decide()`: a concluded winner, then the in-app switch or a preview link, then
|
|
135
|
+
consent, the definition and the assignment. `assignment.reason` says which one decided.
|
|
136
|
+
- `<Experiment>` wraps its output in a `display: contents` element (no box, so the layout is
|
|
137
|
+
unchanged) with `data-experiment`, `data-variant`, `data-variants` and
|
|
138
|
+
`data-experiment-reason`, so an editor overlay can find every test on the page.
|
|
139
|
+
- **Preview link:** `?qa_variant=signup-cta:b` (several: `?qa_variant=a-test:b,other:a`) shows a
|
|
140
|
+
version for QA, sticks for the browser session, works without consent and is never logged.
|
|
141
|
+
`?qa_variant=clear` removes it.
|
|
142
|
+
- **In-app switch** (for an editor): `tracker.experiments.setEditorOverride(key, variant | null)`
|
|
143
|
+
shows one version in this browser, live and never logged; `tracker.experiments.refresh()`
|
|
144
|
+
reloads the definitions and makes mounted experiments decide again.
|
|
145
|
+
|
|
146
|
+
Assignment (§5.2): `n = fnv1a32(String(fnv1a32(key + vid))) % 10000 / 10000`; variant *i* owns
|
|
147
|
+
`[c_i, c_i + traffic × w_i)` where `c_i` is the sum of the weights before it. Raising `traffic`
|
|
148
|
+
never moves an enrolled visitor. Fixed vectors: `__tests__/tracking/hash.test.ts`; if one of them
|
|
149
|
+
changes, every visitor's assignment changes.
|
|
150
|
+
|
|
151
|
+
The experiment itself (key, variants, weights, traffic, targeting, trigger, metrics) is created
|
|
152
|
+
and started through the admin API (§9); until it runs, everybody sees the control.
|
|
153
|
+
|
|
154
|
+
## 6. Events, goals and markup
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
const tracker = useTracker(); // null outside the provider: always use ?.
|
|
158
|
+
tracker?.goal('contact-submitted'); // a conversion the server's goals can match
|
|
159
|
+
tracker?.goal('demo-booked', 1); // with a value
|
|
160
|
+
tracker?.custom('plan-picked', { plan: 'pro' }); // a named event with props
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Never put free text a user typed, emails or names in props.
|
|
164
|
+
|
|
165
|
+
**Sections** (visible time, "seen", exit section) and **clicks** are tagged in markup; one
|
|
166
|
+
`IntersectionObserver` and one delegated click listener pick them up:
|
|
167
|
+
|
|
168
|
+
```tsx
|
|
169
|
+
<section data-track-section='pricing'>…</section>
|
|
170
|
+
<ReqoreButton data-track-click='get-started' data-track-placement='hero'>Get started</ReqoreButton>
|
|
171
|
+
// where JSX attributes cannot be written (object-literal props, e.g. panel actions):
|
|
172
|
+
<ReqorePanel actions={[{ ...trackClick('contact', 'pricing-panel'), label: 'Talk to us' }]} />
|
|
173
|
+
<div {...trackSection('pricing')}>…</div>
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
- `data-track-click` is what the click means (`get-started`, `contact`, …);
|
|
177
|
+
`data-track-placement` is where (defaults to the enclosing section's id);
|
|
178
|
+
`data-track-label` overrides the label (defaults to the element's text).
|
|
179
|
+
- "Seen" is at least 50 % of the section, or 50 % of the screen covered by it, for at least one
|
|
180
|
+
second in one stretch, with the tab visible. Reports key on section ids: keep them stable.
|
|
181
|
+
|
|
182
|
+
**Sign-up attribution** (§5.4): `tracker.decorateSignupUrl(url)` adds
|
|
183
|
+
`qa_exp=<experiment>:<variant>,…` and the session's first-touch `utm_*` to a sign-up link (with
|
|
184
|
+
consent only, never the `vid`); the server's sign-up flow reads them.
|
|
185
|
+
|
|
186
|
+
## 7. Tests and stories
|
|
187
|
+
|
|
188
|
+
`createMemoryTracker(options)` is a tracker with no browser behind it: memory storage, a clock
|
|
189
|
+
and timers you drive, and a transport that records every batch.
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
import { createMemoryTracker } from '@qoretechnologies/reqraft/dist/tracking';
|
|
193
|
+
|
|
194
|
+
const m = createMemoryTracker({ consent: 'granted', vid: 'visitor-1', experiments: [definition] });
|
|
195
|
+
m.tracker.start();
|
|
196
|
+
await m.advance(5000); // runs due timers: the queue flushes
|
|
197
|
+
m.sent[0].batch.events; // what reached the collector
|
|
198
|
+
m.local.get(m.keys.vid); // storage, under the configured prefix
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Options: `config` (any config field; `property` defaults to `test`), `consent`, `experiments`
|
|
202
|
+
(a list, a function read on every load, or `'fail'`), `page` (path, search, device, …), `vid`,
|
|
203
|
+
`start` (the clock), `seed` (localStorage before the tracker starts). In a story, pass
|
|
204
|
+
`m.tracker` to `<TrackingProvider>` without `attach`; seed the experiments cache
|
|
205
|
+
(`KEYS.experimentsCache`) so the first render already has the definitions — see
|
|
206
|
+
`src/tracking/ui/ExperimentsOverlay.stories.tsx`.
|
|
207
|
+
|
|
208
|
+
Unit tests: `__tests__/tracking/` (hashing vectors, assignment, consent gating, batching,
|
|
209
|
+
sections, page views, sign-up links, concluded winners, the in-app switch, product config,
|
|
210
|
+
the React bindings, and the layer rules).
|
|
211
|
+
|
|
212
|
+
## 8. The A/B test UI: embed the overlay
|
|
213
|
+
|
|
214
|
+
The same A/B test controls every product shows, built on Reqore, in their own entry so the engine
|
|
215
|
+
stays light:
|
|
216
|
+
|
|
217
|
+
```ts
|
|
218
|
+
import { ExperimentsOverlay, ExperimentCard, useExperimentsAdmin } from '@qoretechnologies/reqraft/dist/tracking/ui';
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
| Export | What |
|
|
222
|
+
|---|---|
|
|
223
|
+
| `<ExperimentsOverlay>` | Finds every `<Experiment>` on the page (its `data-experiment` marker) and draws a dashed outline and an "A/B test · <name>" handle at its corner. Hover, keyboard focus or a click opens the test's card, anchored to the element: it scrolls and resizes with it and flips above or below the handle to stay in view. ✕, Esc and a click outside close it. An ended test shows "Winner live · code clean-up pending" instead. |
|
|
224
|
+
| `<ExperimentCard>` | One test: name, status tag, a "?" help and ✕; the verdict; one row per version, best first (conversion on the primary metric, then visitors; re-sorted only when new data arrives), the best one tagged "Leading", or "Winner" for a likely winner or an ended test's winner, the original tagged "Original". A row shows visitors, conversion, chance to beat the original and a bar; the shown version is the info-coloured row (`aria-current`); the whole row shows that version (Enter / Space too). Row actions: a chart icon for that version's analytics (`versionDetailsUrl`), minimal Accept, and a bin icon (Remove) at the far end. Footer: the optional "Open in …" link and Start / Resume (solid), Pause, Stop. Past three and a half rows the list scrolls inside the card. Every action asks first (Reqore's confirm dialog) and notifies what happened. |
|
|
225
|
+
| `useExperimentsAdmin(options)` | Optional: the list and the actions from the qorus-api routes. |
|
|
226
|
+
| `createExperimentsAdminClient(options)` | The same without React. |
|
|
227
|
+
| `placeCard`, `cardReducer`, `versionRows`, `verdictLine`, `versionsMaxHeight`, … | The plain logic behind them, for tests and custom UIs. |
|
|
228
|
+
|
|
229
|
+
Both components are driven by props only: the data is the qorus-api editor / admin list (an array of
|
|
230
|
+
`{ experiment, results }`: the admin experiment object with `status`, `variants`, `phase`,
|
|
231
|
+
`phases`, `history`, `winner`, and the results object with `verdict_text` and the per-metric
|
|
232
|
+
numbers), and every action is a callback that may return a promise (the button shows busy until it
|
|
233
|
+
settles, and a rejection's message is shown). An action whose callback is not given does not
|
|
234
|
+
appear. Neither makes a request of its own.
|
|
235
|
+
|
|
236
|
+
```tsx
|
|
237
|
+
import { ExperimentsOverlay, useExperimentsAdmin } from '@qoretechnologies/reqraft/dist/tracking/ui';
|
|
238
|
+
|
|
239
|
+
const AbTestsLayer = ({ visible }: { visible: boolean }) => {
|
|
240
|
+
const admin = useExperimentsAdmin({
|
|
241
|
+
listUrl: '/qorus-cloud/editor/experiments?property=ide', // actions go to …/experiments/<key>/<action>
|
|
242
|
+
headers: { Authorization: `Bearer ${editorToken}` }, // or leave it to a proxy
|
|
243
|
+
enabled: visible,
|
|
244
|
+
});
|
|
245
|
+
return (
|
|
246
|
+
<ExperimentsOverlay
|
|
247
|
+
visible={visible} // the product's "hide editor elements"
|
|
248
|
+
tests={admin.tests}
|
|
249
|
+
loadError={admin.error}
|
|
250
|
+
onAccept={admin.accept}
|
|
251
|
+
onRemove={admin.remove}
|
|
252
|
+
onStart={admin.start}
|
|
253
|
+
onPause={admin.pause}
|
|
254
|
+
onStop={admin.stop}
|
|
255
|
+
detailsUrl={(key) => `${ADMIN_PORTAL}/analytics?dashboard=tracking&experiment=${key}`}
|
|
256
|
+
actorLabels={{ 'ide-editor': 'in the IDE' }}
|
|
257
|
+
topInset={() => document.querySelector('header')?.getBoundingClientRect().bottom ?? 0}
|
|
258
|
+
/>
|
|
259
|
+
);
|
|
260
|
+
};
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
- Mount it inside `<TrackingProvider>`: then clicking a version's row uses the tracker's in-app
|
|
264
|
+
switch (`tracker.experiments.setEditorOverride`, this browser only, never logged), and after
|
|
265
|
+
an action the page shows what visitors see now (the switch is cleared and the tracker reloads
|
|
266
|
+
the definitions). Pass `onShow` / `onSettled` to do something else.
|
|
267
|
+
- `versionDetailsUrl={(key, variant) => \`${ADMIN_PORTAL}/analytics?dashboard=tracking&experiment=${key}&variant=${variant}\`}`
|
|
268
|
+
adds the per-version "Show analytics" action (the admin portal opens the test with that version
|
|
269
|
+
expanded); without it the action is hidden. `detailsUrl` stays for the whole test.
|
|
270
|
+
- The "?" opens a dialog explaining the test for a marketer: every button, how visitors are split
|
|
271
|
+
and counted, reading the verdict and the chance to beat, the too-early rule (the test's
|
|
272
|
+
`min_sample`, 200 visitors per version and 7 days by default), a test that is not set up, and
|
|
273
|
+
where the full analytics are (`analyticsLabel`).
|
|
274
|
+
- A test that is in the page's code (`<Experiment>`) but not registered in the analytics service
|
|
275
|
+
yet shows "Not set up" and asks to have it set up (`notSetUpText` changes the words); when the list
|
|
276
|
+
cannot be loaded (`loadError`) it shows "No connection" instead.
|
|
277
|
+
- `exclude` (a selector) skips markers inside the product's own UI (e.g. its preview frames);
|
|
278
|
+
markers inside the overlay itself are always skipped.
|
|
279
|
+
- The same overlay works inside a phone-preview iframe: mount it in the frame's page too. The
|
|
280
|
+
switch is shared through `localStorage`, so both follow it.
|
|
281
|
+
- `useExperimentsAdmin` reloads the list every minute (`refreshMs`) and after every action; its
|
|
282
|
+
errors explain a 401 / 403 as a missing or wrong key. `actionUrl` and `createUrl` change the
|
|
283
|
+
routes; `fetch` replaces the transport.
|
|
284
|
+
- The card on its own: `<ExperimentCard experimentKey="signup-cta" test={entry} shown="a" codeVariants={['a', 'b']} onShow={…} onAccept={…} … />`.
|
|
285
|
+
|
|
286
|
+
Stories: `Tracking/Experiment Card` (draft, running with two and five versions, paused, concluded,
|
|
287
|
+
no data yet, not set up, no connection, the confirm step on Remove, the help) and
|
|
288
|
+
`Tracking/Experiments Overlay` (anchored, flipped above near the bottom, hidden), each in dark,
|
|
289
|
+
light and phone. The visually empty ones (hidden, the phone flip) run as tests without a Qlip
|
|
290
|
+
snapshot (`parameters.qlip.skip`).
|
package/dist/index.d.ts
CHANGED
|
@@ -29,4 +29,6 @@ export * from './components/ticketThread';
|
|
|
29
29
|
export * from './components/ticketReferences';
|
|
30
30
|
export * from './components/searchFilterBar';
|
|
31
31
|
export * from './components/codeSize';
|
|
32
|
+
export * from './tracking';
|
|
33
|
+
export * from './tracking/ui';
|
|
32
34
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,sBAAsB,CAAC;AACrC,OAAO,EACL,aAAa,EACb,uBAAuB,EACvB,UAAU,EACV,gBAAgB,EAChB,cAAc,EACd,4BAA4B,EAC5B,eAAe,EACf,gCAAgC,GACjC,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,cAAc,4BAA4B,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,YAAY,EACV,gBAAgB,EAChB,iBAAiB,EACjB,YAAY,EACZ,gBAAgB,GACjB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AACrD,YAAY,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAClE,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAClE,YAAY,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACxE,cAAc,mCAAmC,CAAC;AAClD,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,kBAAkB,GACnB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,gBAAgB,IAAI,uBAAuB,EAAE,MAAM,kCAAkC,CAAC;AAC/F,YAAY,EACV,YAAY,EACZ,iBAAiB,GAClB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EACL,WAAW,EACX,KAAK,EACL,QAAQ,EACR,cAAc,EACd,eAAe,EACf,UAAU,GACX,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,mBAAmB,EACnB,qBAAqB,EACrB,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,eAAe,CAAC;AACvB,cAAc,mBAAmB,CAAC;AAClC,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,YAAY,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAClE,YAAY,EACV,kBAAkB,EAClB,cAAc,EACd,iBAAiB,EACjB,wBAAwB,EACxB,YAAY,EACZ,SAAS,EACT,iBAAiB,EACjB,wBAAwB,EACxB,sBAAsB,EACtB,iBAAiB,EACjB,wBAAwB,EACxB,YAAY,EACZ,gBAAgB,GACjB,MAAM,yBAAyB,CAAC;AACjC,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AACxC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,uBAAuB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,sBAAsB,CAAC;AACrC,OAAO,EACL,aAAa,EACb,uBAAuB,EACvB,UAAU,EACV,gBAAgB,EAChB,cAAc,EACd,4BAA4B,EAC5B,eAAe,EACf,gCAAgC,GACjC,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,cAAc,4BAA4B,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,YAAY,EACV,gBAAgB,EAChB,iBAAiB,EACjB,YAAY,EACZ,gBAAgB,GACjB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AACrD,YAAY,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAClE,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAClE,YAAY,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACxE,cAAc,mCAAmC,CAAC;AAClD,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,kBAAkB,GACnB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,gBAAgB,IAAI,uBAAuB,EAAE,MAAM,kCAAkC,CAAC;AAC/F,YAAY,EACV,YAAY,EACZ,iBAAiB,GAClB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EACL,WAAW,EACX,KAAK,EACL,QAAQ,EACR,cAAc,EACd,eAAe,EACf,UAAU,GACX,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,mBAAmB,EACnB,qBAAqB,EACrB,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,eAAe,CAAC;AACvB,cAAc,mBAAmB,CAAC;AAClC,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,YAAY,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAClE,YAAY,EACV,kBAAkB,EAClB,cAAc,EACd,iBAAiB,EACjB,wBAAwB,EACxB,YAAY,EACZ,SAAS,EACT,iBAAiB,EACjB,wBAAwB,EACxB,sBAAsB,EACtB,iBAAiB,EACjB,wBAAwB,EACxB,YAAY,EACZ,gBAAgB,GACjB,MAAM,yBAAyB,CAAC;AACjC,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AACxC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,uBAAuB,CAAC;AAItC,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -61,4 +61,9 @@ __exportStar(require("./components/ticketThread"), exports);
|
|
|
61
61
|
__exportStar(require("./components/ticketReferences"), exports);
|
|
62
62
|
__exportStar(require("./components/searchFilterBar"), exports);
|
|
63
63
|
__exportStar(require("./components/codeSize"), exports);
|
|
64
|
+
// First-party tracking, consent and A/B testing (design/TRACKING.md). Also importable
|
|
65
|
+
// on its own, without the rest of Reqraft: `@qoretechnologies/reqraft/dist/tracking`
|
|
66
|
+
// (the engine, React only) and `@qoretechnologies/reqraft/dist/tracking/ui` (the A/B test UI).
|
|
67
|
+
__exportStar(require("./tracking"), exports);
|
|
68
|
+
__exportStar(require("./tracking/ui"), exports);
|
|
64
69
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,oDAAkC;AAClC,uDAAqC;AACrC,qDAS+B;AAR7B,4GAAA,aAAa,OAAA;AACb,sHAAA,uBAAuB,OAAA;AACvB,yGAAA,UAAU,OAAA;AACV,+GAAA,gBAAgB,OAAA;AAChB,6GAAA,cAAc,OAAA;AACd,2HAAA,4BAA4B,OAAA;AAC5B,8GAAA,eAAe,OAAA;AACf,+HAAA,gCAAgC,OAAA;AAGlC,iDAAoD;AAA3C,0GAAA,aAAa,OAAA;AACtB,6DAA2C;AAC3C,+CAAqD;AAA5C,mGAAA,WAAW,OAAA;AAQpB,sDAAqD;AAA5C,oGAAA,QAAQ,OAAA;AAEjB,4DAAkE;AAAzD,+GAAA,iBAAiB,OAAA;AAE1B,oEAAkD;AAClD,+DAIqC;AAHnC,oHAAA,iBAAiB,OAAA;AACjB,kHAAA,eAAe,OAAA;AACf,qHAAA,kBAAkB,OAAA;AAEpB,gEAA+F;AAAtF,sHAAA,gBAAgB,OAA2B;AAKpD,uCAOuB;AANrB,oGAAA,WAAW,OAAA;AACX,8FAAA,KAAK,OAAA;AACL,iGAAA,QAAQ,OAAA;AACR,uGAAA,cAAc,OAAA;AACd,wGAAA,eAAe,OAAA;AACf,mGAAA,UAAU,OAAA;AAQZ,oDAAkC;AAClC,+CAAqD;AAA5C,6GAAA,gBAAgB,OAAA;AAiBzB,wDAAsC;AACtC,2DAAyC;AACzC,0DAAwC;AACxC,iEAA+C;AAC/C,6DAA2C;AAC3C,4DAA0C;AAC1C,gEAA8C;AAC9C,+DAA6C;AAC7C,wDAAsC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,oDAAkC;AAClC,uDAAqC;AACrC,qDAS+B;AAR7B,4GAAA,aAAa,OAAA;AACb,sHAAA,uBAAuB,OAAA;AACvB,yGAAA,UAAU,OAAA;AACV,+GAAA,gBAAgB,OAAA;AAChB,6GAAA,cAAc,OAAA;AACd,2HAAA,4BAA4B,OAAA;AAC5B,8GAAA,eAAe,OAAA;AACf,+HAAA,gCAAgC,OAAA;AAGlC,iDAAoD;AAA3C,0GAAA,aAAa,OAAA;AACtB,6DAA2C;AAC3C,+CAAqD;AAA5C,mGAAA,WAAW,OAAA;AAQpB,sDAAqD;AAA5C,oGAAA,QAAQ,OAAA;AAEjB,4DAAkE;AAAzD,+GAAA,iBAAiB,OAAA;AAE1B,oEAAkD;AAClD,+DAIqC;AAHnC,oHAAA,iBAAiB,OAAA;AACjB,kHAAA,eAAe,OAAA;AACf,qHAAA,kBAAkB,OAAA;AAEpB,gEAA+F;AAAtF,sHAAA,gBAAgB,OAA2B;AAKpD,uCAOuB;AANrB,oGAAA,WAAW,OAAA;AACX,8FAAA,KAAK,OAAA;AACL,iGAAA,QAAQ,OAAA;AACR,uGAAA,cAAc,OAAA;AACd,wGAAA,eAAe,OAAA;AACf,mGAAA,UAAU,OAAA;AAQZ,oDAAkC;AAClC,+CAAqD;AAA5C,6GAAA,gBAAgB,OAAA;AAiBzB,wDAAsC;AACtC,2DAAyC;AACzC,0DAAwC;AACxC,iEAA+C;AAC/C,6DAA2C;AAC3C,4DAA0C;AAC1C,gEAA8C;AAC9C,+DAA6C;AAC7C,wDAAsC;AACtC,sFAAsF;AACtF,qFAAqF;AACrF,+FAA+F;AAC/F,6CAA2B;AAC3B,gDAA8B"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Tracker } from '../core/tracker';
|
|
2
|
+
export interface IAttached {
|
|
3
|
+
detach: () => void;
|
|
4
|
+
/** Observes an element under a synthetic id (an experiment's wrapped element); not in the page summary. */
|
|
5
|
+
observeElement: (el: Element, id: string) => () => void;
|
|
6
|
+
}
|
|
7
|
+
export declare const attachToPage: (tracker: Tracker, heartbeatMs: number) => IAttached;
|
|
8
|
+
//# sourceMappingURL=attach.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attach.d.ts","sourceRoot":"","sources":["../../../src/tracking/browser/attach.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAoB/C,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,2GAA2G;IAC3G,cAAc,EAAE,CAAC,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,KAAK,MAAM,IAAI,CAAC;CACzD;AAOD,eAAO,MAAM,YAAY,YAAa,OAAO,eAAe,MAAM,KAAG,SAyJpE,CAAC"}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.attachToPage = void 0;
|
|
4
|
+
var sections_1 = require("../core/sections");
|
|
5
|
+
/**
|
|
6
|
+
* Connects a `Tracker` to the page: tab visibility, page leave, interactions,
|
|
7
|
+
* scroll depth, the heartbeat, section visibility (IntersectionObserver on every
|
|
8
|
+
* `[data-track-section]`) and clicks (one delegated listener for
|
|
9
|
+
* `[data-track-click]`). Returns a function that removes all of it.
|
|
10
|
+
*
|
|
11
|
+
* Markup contract (see design/TRACKING.md):
|
|
12
|
+
* data-track-section="<id>" a section: visible time, seen, exit section
|
|
13
|
+
* data-track-click="<target>" a tracked click (`get-started`, `contact`, …)
|
|
14
|
+
* data-track-placement="<where>" optional; defaults to the enclosing section id
|
|
15
|
+
* data-track-label="<text>" optional; defaults to the element's text
|
|
16
|
+
*/
|
|
17
|
+
var SECTION_SELECTOR = '[data-track-section]';
|
|
18
|
+
var THRESHOLDS = Array.from({ length: 21 }, function (_, i) { return i / 20; });
|
|
19
|
+
var TICK_MS = 250;
|
|
20
|
+
var ACTIVITY_EVENTS = ['pointerdown', 'keydown', 'wheel', 'touchstart', 'mousemove', 'scroll'];
|
|
21
|
+
var clean = function (s, max) {
|
|
22
|
+
if (max === void 0) { max = 80; }
|
|
23
|
+
var t = (s !== null && s !== void 0 ? s : '').replace(/\s+/g, ' ').trim();
|
|
24
|
+
return t ? t.slice(0, max) : null;
|
|
25
|
+
};
|
|
26
|
+
var attachToPage = function (tracker, heartbeatMs) {
|
|
27
|
+
var cleanup = [];
|
|
28
|
+
var on = function (target, type, fn, opts) {
|
|
29
|
+
target.addEventListener(type, fn, opts);
|
|
30
|
+
cleanup.push(function () { return target.removeEventListener(type, fn, opts); });
|
|
31
|
+
};
|
|
32
|
+
var safe = function (fn) { return function () {
|
|
33
|
+
try {
|
|
34
|
+
fn();
|
|
35
|
+
}
|
|
36
|
+
catch (_a) {
|
|
37
|
+
/* never into the page */
|
|
38
|
+
}
|
|
39
|
+
}; };
|
|
40
|
+
// ── tab visibility and page leave
|
|
41
|
+
on(document, 'visibilitychange', safe(function () { return tracker.setTabVisible(document.visibilityState === 'visible'); }));
|
|
42
|
+
on(window, 'pagehide', safe(function () { return tracker.leave(); }));
|
|
43
|
+
if (document.visibilityState !== 'visible')
|
|
44
|
+
tracker.setTabVisible(false);
|
|
45
|
+
// ── engagement (throttled to one signal per second)
|
|
46
|
+
var lastActivity = 0;
|
|
47
|
+
var activity = safe(function () {
|
|
48
|
+
var now = Date.now();
|
|
49
|
+
if (now - lastActivity < 1000)
|
|
50
|
+
return;
|
|
51
|
+
lastActivity = now;
|
|
52
|
+
tracker.activity();
|
|
53
|
+
});
|
|
54
|
+
ACTIVITY_EVENTS.forEach(function (t) { return on(window, t, activity, { passive: true, capture: true }); });
|
|
55
|
+
// ── scroll depth (once per frame)
|
|
56
|
+
var scrollQueued = false;
|
|
57
|
+
var measureScroll = safe(function () {
|
|
58
|
+
var _a, _b;
|
|
59
|
+
scrollQueued = false;
|
|
60
|
+
var doc = document.documentElement;
|
|
61
|
+
var scrollable = Math.max(doc.scrollHeight, (_b = (_a = document.body) === null || _a === void 0 ? void 0 : _a.scrollHeight) !== null && _b !== void 0 ? _b : 0);
|
|
62
|
+
var seen = window.scrollY + window.innerHeight;
|
|
63
|
+
tracker.scrolled(scrollable > 0 ? (seen / scrollable) * 100 : 100);
|
|
64
|
+
});
|
|
65
|
+
on(window, 'scroll', function () {
|
|
66
|
+
if (scrollQueued)
|
|
67
|
+
return;
|
|
68
|
+
scrollQueued = true;
|
|
69
|
+
requestAnimationFrame(measureScroll);
|
|
70
|
+
}, { passive: true });
|
|
71
|
+
// ── heartbeat while visible, and the section clock
|
|
72
|
+
var heartbeat = setInterval(safe(function () { return document.visibilityState === 'visible' && tracker.heartbeat(); }), heartbeatMs);
|
|
73
|
+
var tick = setInterval(safe(function () { return tracker.sections.tick(tracker.now()); }), TICK_MS);
|
|
74
|
+
cleanup.push(function () { return clearInterval(heartbeat); }, function () { return clearInterval(tick); });
|
|
75
|
+
// ── sections
|
|
76
|
+
var ratios = new Map();
|
|
77
|
+
var ids = new Map();
|
|
78
|
+
var report = new Map();
|
|
79
|
+
var apply = function (id) {
|
|
80
|
+
var best = 0;
|
|
81
|
+
ids.forEach(function (other, el) { var _a; return other === id && (best = Math.max(best, (_a = ratios.get(el)) !== null && _a !== void 0 ? _a : 0)); });
|
|
82
|
+
tracker.sections.update(id, best, tracker.now());
|
|
83
|
+
};
|
|
84
|
+
var io = typeof IntersectionObserver !== 'undefined'
|
|
85
|
+
? new IntersectionObserver(function (entries) {
|
|
86
|
+
return safe(function () {
|
|
87
|
+
var vh = window.innerHeight || document.documentElement.clientHeight;
|
|
88
|
+
entries.forEach(function (e) {
|
|
89
|
+
var _a, _b;
|
|
90
|
+
var id = ids.get(e.target);
|
|
91
|
+
if (!id)
|
|
92
|
+
return;
|
|
93
|
+
ratios.set(e.target, (0, sections_1.effectiveRatio)(e.intersectionRatio, e.intersectionRect.height, (_b = (_a = e.rootBounds) === null || _a === void 0 ? void 0 : _a.height) !== null && _b !== void 0 ? _b : vh));
|
|
94
|
+
apply(id);
|
|
95
|
+
});
|
|
96
|
+
})();
|
|
97
|
+
}, { threshold: THRESHOLDS })
|
|
98
|
+
: null;
|
|
99
|
+
var track = function (el, id, inSummary) {
|
|
100
|
+
var _a;
|
|
101
|
+
if (ids.get(el) === id)
|
|
102
|
+
return;
|
|
103
|
+
ids.set(el, id);
|
|
104
|
+
report.set(id, ((_a = report.get(id)) !== null && _a !== void 0 ? _a : false) || inSummary);
|
|
105
|
+
tracker.sections.register(id, inSummary);
|
|
106
|
+
io === null || io === void 0 ? void 0 : io.observe(el);
|
|
107
|
+
};
|
|
108
|
+
var untrack = function (el) {
|
|
109
|
+
var id = ids.get(el);
|
|
110
|
+
if (!id)
|
|
111
|
+
return;
|
|
112
|
+
io === null || io === void 0 ? void 0 : io.unobserve(el);
|
|
113
|
+
ids.delete(el);
|
|
114
|
+
ratios.delete(el);
|
|
115
|
+
apply(id);
|
|
116
|
+
};
|
|
117
|
+
var scan = safe(function () {
|
|
118
|
+
var present = new Set(Array.from(document.querySelectorAll(SECTION_SELECTOR)));
|
|
119
|
+
ids.forEach(function (id, el) { return report.get(id) && !present.has(el) && !el.isConnected && untrack(el); });
|
|
120
|
+
present.forEach(function (el) {
|
|
121
|
+
var id = el.dataset.trackSection;
|
|
122
|
+
if (id)
|
|
123
|
+
track(el, id, true);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
// Re-measure everything at a new page view: the section clock starts from zero,
|
|
127
|
+
// and re-observing makes the observer report every element's current state.
|
|
128
|
+
var remeasure = safe(function () {
|
|
129
|
+
scan();
|
|
130
|
+
ids.forEach(function (_id, el) {
|
|
131
|
+
io === null || io === void 0 ? void 0 : io.unobserve(el);
|
|
132
|
+
io === null || io === void 0 ? void 0 : io.observe(el);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
cleanup.push(tracker.pageViewId.subscribe(remeasure));
|
|
136
|
+
var scanTimer = null;
|
|
137
|
+
var mo = typeof MutationObserver !== 'undefined'
|
|
138
|
+
? new MutationObserver(function () {
|
|
139
|
+
if (scanTimer)
|
|
140
|
+
return;
|
|
141
|
+
scanTimer = setTimeout(function () {
|
|
142
|
+
scanTimer = null;
|
|
143
|
+
scan();
|
|
144
|
+
}, 150);
|
|
145
|
+
})
|
|
146
|
+
: null;
|
|
147
|
+
mo === null || mo === void 0 ? void 0 : mo.observe(document.body, { childList: true, subtree: true });
|
|
148
|
+
scan();
|
|
149
|
+
cleanup.push(function () {
|
|
150
|
+
mo === null || mo === void 0 ? void 0 : mo.disconnect();
|
|
151
|
+
io === null || io === void 0 ? void 0 : io.disconnect();
|
|
152
|
+
if (scanTimer)
|
|
153
|
+
clearTimeout(scanTimer);
|
|
154
|
+
});
|
|
155
|
+
// ── clicks: one delegated listener
|
|
156
|
+
var onClick = function (e) {
|
|
157
|
+
return safe(function () {
|
|
158
|
+
var _a, _b, _c, _d, _e;
|
|
159
|
+
var el = (_b = (_a = e.target) === null || _a === void 0 ? void 0 : _a.closest) === null || _b === void 0 ? void 0 : _b.call(_a, '[data-track-click]');
|
|
160
|
+
if (!el)
|
|
161
|
+
return;
|
|
162
|
+
var d = el.dataset;
|
|
163
|
+
var link = el.closest('a');
|
|
164
|
+
tracker.click({
|
|
165
|
+
target: d.trackClick || 'unknown',
|
|
166
|
+
placement: d.trackPlacement || ((_c = el.closest(SECTION_SELECTOR)) === null || _c === void 0 ? void 0 : _c.dataset.trackSection) || null,
|
|
167
|
+
label: (_e = (_d = clean(d.trackLabel)) !== null && _d !== void 0 ? _d : clean(el.innerText)) !== null && _e !== void 0 ? _e : clean(el.getAttribute('aria-label')),
|
|
168
|
+
href: d.trackHref || (link === null || link === void 0 ? void 0 : link.href) || null,
|
|
169
|
+
});
|
|
170
|
+
})();
|
|
171
|
+
};
|
|
172
|
+
on(document, 'click', onClick, { capture: true });
|
|
173
|
+
return {
|
|
174
|
+
detach: function () { return cleanup.forEach(function (fn) { return fn(); }); },
|
|
175
|
+
observeElement: function (el, id) {
|
|
176
|
+
track(el, id, false);
|
|
177
|
+
return function () { return untrack(el); };
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
};
|
|
181
|
+
exports.attachToPage = attachToPage;
|
|
182
|
+
//# sourceMappingURL=attach.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attach.js","sourceRoot":"","sources":["../../../src/tracking/browser/attach.ts"],"names":[],"mappings":";;;AAAA,6CAAkD;AAGlD;;;;;;;;;;;GAWG;AAEH,IAAM,gBAAgB,GAAG,sBAAsB,CAAC;AAChD,IAAM,UAAU,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,UAAC,CAAC,EAAE,CAAC,IAAK,OAAA,CAAC,GAAG,EAAE,EAAN,CAAM,CAAC,CAAC;AAChE,IAAM,OAAO,GAAG,GAAG,CAAC;AACpB,IAAM,eAAe,GAAG,CAAC,aAAa,EAAE,SAAS,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,QAAQ,CAAU,CAAC;AAQ1G,IAAM,KAAK,GAAG,UAAC,CAA4B,EAAE,GAAQ;IAAR,oBAAA,EAAA,QAAQ;IACnD,IAAM,CAAC,GAAG,CAAC,CAAC,aAAD,CAAC,cAAD,CAAC,GAAI,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IAChD,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACpC,CAAC,CAAC;AAEK,IAAM,YAAY,GAAG,UAAC,OAAgB,EAAE,WAAmB;IAChE,IAAM,OAAO,GAAmB,EAAE,CAAC;IACnC,IAAM,EAAE,GAAG,UAAC,MAAmB,EAAE,IAAY,EAAE,EAAiB,EAAE,IAA8B;QAC9F,MAAM,CAAC,gBAAgB,CAAC,IAAI,EAAE,EAAE,EAAE,IAAI,CAAC,CAAC;QACxC,OAAO,CAAC,IAAI,CAAC,cAAM,OAAA,MAAM,CAAC,mBAAmB,CAAC,IAAI,EAAE,EAAE,EAAE,IAAI,CAAC,EAA1C,CAA0C,CAAC,CAAC;IACjE,CAAC,CAAC;IACF,IAAM,IAAI,GAAG,UAAC,EAAc,IAAK,OAAA;QAC/B,IAAI,CAAC;YACH,EAAE,EAAE,CAAC;QACP,CAAC;QAAC,WAAM,CAAC;YACP,yBAAyB;QAC3B,CAAC;IACH,CAAC,EANgC,CAMhC,CAAC;IAEF,mCAAmC;IACnC,EAAE,CAAC,QAAQ,EAAE,kBAAkB,EAAE,IAAI,CAAC,cAAM,OAAA,OAAO,CAAC,aAAa,CAAC,QAAQ,CAAC,eAAe,KAAK,SAAS,CAAC,EAA7D,CAA6D,CAAC,CAAC,CAAC;IAC5G,EAAE,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,CAAC,cAAM,OAAA,OAAO,CAAC,KAAK,EAAE,EAAf,CAAe,CAAC,CAAC,CAAC;IACpD,IAAI,QAAQ,CAAC,eAAe,KAAK,SAAS;QAAE,OAAO,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;IAEzE,qDAAqD;IACrD,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,IAAM,QAAQ,GAAG,IAAI,CAAC;QACpB,IAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,IAAI,GAAG,GAAG,YAAY,GAAG,IAAI;YAAE,OAAO;QACtC,YAAY,GAAG,GAAG,CAAC;QACnB,OAAO,CAAC,QAAQ,EAAE,CAAC;IACrB,CAAC,CAAC,CAAC;IACH,eAAe,CAAC,OAAO,CAAC,UAAC,CAAC,IAAK,OAAA,EAAE,CAAC,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,EAAzD,CAAyD,CAAC,CAAC;IAE1F,mCAAmC;IACnC,IAAI,YAAY,GAAG,KAAK,CAAC;IACzB,IAAM,aAAa,GAAG,IAAI,CAAC;;QACzB,YAAY,GAAG,KAAK,CAAC;QACrB,IAAM,GAAG,GAAG,QAAQ,CAAC,eAAe,CAAC;QACrC,IAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,YAAY,EAAE,MAAA,MAAA,QAAQ,CAAC,IAAI,0CAAE,YAAY,mCAAI,CAAC,CAAC,CAAC;QAChF,IAAM,IAAI,GAAG,MAAM,CAAC,OAAO,GAAG,MAAM,CAAC,WAAW,CAAC;QACjD,OAAO,CAAC,QAAQ,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,UAAU,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACrE,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,MAAM,EAAE,QAAQ,EAAE;QACnB,IAAI,YAAY;YAAE,OAAO;QACzB,YAAY,GAAG,IAAI,CAAC;QACpB,qBAAqB,CAAC,aAAa,CAAC,CAAC;IACvC,CAAC,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;IAEtB,oDAAoD;IACpD,IAAM,SAAS,GAAG,WAAW,CAAC,IAAI,CAAC,cAAM,OAAA,QAAQ,CAAC,eAAe,KAAK,SAAS,IAAI,OAAO,CAAC,SAAS,EAAE,EAA7D,CAA6D,CAAC,EAAE,WAAW,CAAC,CAAC;IACtH,IAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,cAAM,OAAA,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,EAApC,CAAoC,CAAC,EAAE,OAAO,CAAC,CAAC;IACpF,OAAO,CAAC,IAAI,CAAC,cAAM,OAAA,aAAa,CAAC,SAAS,CAAC,EAAxB,CAAwB,EAAE,cAAM,OAAA,aAAa,CAAC,IAAI,CAAC,EAAnB,CAAmB,CAAC,CAAC;IAExE,cAAc;IACd,IAAM,MAAM,GAAG,IAAI,GAAG,EAAmB,CAAC;IAC1C,IAAM,GAAG,GAAG,IAAI,GAAG,EAAmB,CAAC;IACvC,IAAM,MAAM,GAAG,IAAI,GAAG,EAAmB,CAAC;IAC1C,IAAM,KAAK,GAAG,UAAC,EAAU;QACvB,IAAI,IAAI,GAAG,CAAC,CAAC;QACb,GAAG,CAAC,OAAO,CAAC,UAAC,KAAK,EAAE,EAAE,YAAK,OAAA,KAAK,KAAK,EAAE,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,MAAA,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,mCAAI,CAAC,CAAC,CAAC,CAAA,EAAA,CAAC,CAAC;QACzF,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IACnD,CAAC,CAAC;IACF,IAAM,EAAE,GACN,OAAO,oBAAoB,KAAK,WAAW;QACzC,CAAC,CAAC,IAAI,oBAAoB,CACtB,UAAC,OAAO;YACN,OAAA,IAAI,CAAC;gBACH,IAAM,EAAE,GAAG,MAAM,CAAC,WAAW,IAAI,QAAQ,CAAC,eAAe,CAAC,YAAY,CAAC;gBACvE,OAAO,CAAC,OAAO,CAAC,UAAC,CAAC;;oBAChB,IAAM,EAAE,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;oBAC7B,IAAI,CAAC,EAAE;wBAAE,OAAO;oBAChB,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,IAAA,yBAAc,EAAC,CAAC,CAAC,iBAAiB,EAAE,CAAC,CAAC,gBAAgB,CAAC,MAAM,EAAE,MAAA,MAAA,CAAC,CAAC,UAAU,0CAAE,MAAM,mCAAI,EAAE,CAAC,CAAC,CAAC;oBACjH,KAAK,CAAC,EAAE,CAAC,CAAC;gBACZ,CAAC,CAAC,CAAC;YACL,CAAC,CAAC,EAAE;QARJ,CAQI,EACN,EAAE,SAAS,EAAE,UAAU,EAAE,CAC1B;QACH,CAAC,CAAC,IAAI,CAAC;IAEX,IAAM,KAAK,GAAG,UAAC,EAAW,EAAE,EAAU,EAAE,SAAkB;;QACxD,IAAI,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE;YAAE,OAAO;QAC/B,GAAG,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;QAChB,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,MAAA,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,mCAAI,KAAK,CAAC,IAAI,SAAS,CAAC,CAAC;QACvD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC;QACzC,EAAE,aAAF,EAAE,uBAAF,EAAE,CAAE,OAAO,CAAC,EAAE,CAAC,CAAC;IAClB,CAAC,CAAC;IACF,IAAM,OAAO,GAAG,UAAC,EAAW;QAC1B,IAAM,EAAE,GAAG,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACvB,IAAI,CAAC,EAAE;YAAE,OAAO;QAChB,EAAE,aAAF,EAAE,uBAAF,EAAE,CAAE,SAAS,CAAC,EAAE,CAAC,CAAC;QAClB,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACf,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAClB,KAAK,CAAC,EAAE,CAAC,CAAC;IACZ,CAAC,CAAC;IAEF,IAAM,IAAI,GAAG,IAAI,CAAC;QAChB,IAAM,OAAO,GAAG,IAAI,GAAG,CAAU,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC;QAC1F,GAAG,CAAC,OAAO,CAAC,UAAC,EAAE,EAAE,EAAE,IAAK,OAAA,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,WAAW,IAAI,OAAO,CAAC,EAAE,CAAC,EAApE,CAAoE,CAAC,CAAC;QAC9F,OAAO,CAAC,OAAO,CAAC,UAAC,EAAE;YACjB,IAAM,EAAE,GAAI,EAAkB,CAAC,OAAO,CAAC,YAAY,CAAC;YACpD,IAAI,EAAE;gBAAE,KAAK,CAAC,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,CAAC;QAC9B,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,gFAAgF;IAChF,4EAA4E;IAC5E,IAAM,SAAS,GAAG,IAAI,CAAC;QACrB,IAAI,EAAE,CAAC;QACP,GAAG,CAAC,OAAO,CAAC,UAAC,GAAG,EAAE,EAAE;YAClB,EAAE,aAAF,EAAE,uBAAF,EAAE,CAAE,SAAS,CAAC,EAAE,CAAC,CAAC;YAClB,EAAE,aAAF,EAAE,uBAAF,EAAE,CAAE,OAAO,CAAC,EAAE,CAAC,CAAC;QAClB,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IACH,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC;IAEtD,IAAI,SAAS,GAAyC,IAAI,CAAC;IAC3D,IAAM,EAAE,GACN,OAAO,gBAAgB,KAAK,WAAW;QACrC,CAAC,CAAC,IAAI,gBAAgB,CAAC;YACnB,IAAI,SAAS;gBAAE,OAAO;YACtB,SAAS,GAAG,UAAU,CAAC;gBACrB,SAAS,GAAG,IAAI,CAAC;gBACjB,IAAI,EAAE,CAAC;YACT,CAAC,EAAE,GAAG,CAAC,CAAC;QACV,CAAC,CAAC;QACJ,CAAC,CAAC,IAAI,CAAC;IACX,EAAE,aAAF,EAAE,uBAAF,EAAE,CAAE,OAAO,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;IAC/D,IAAI,EAAE,CAAC;IACP,OAAO,CAAC,IAAI,CAAC;QACX,EAAE,aAAF,EAAE,uBAAF,EAAE,CAAE,UAAU,EAAE,CAAC;QACjB,EAAE,aAAF,EAAE,uBAAF,EAAE,CAAE,UAAU,EAAE,CAAC;QACjB,IAAI,SAAS;YAAE,YAAY,CAAC,SAAS,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;IAEH,oCAAoC;IACpC,IAAM,OAAO,GAAG,UAAC,CAAQ;QACvB,OAAA,IAAI,CAAC;;YACH,IAAM,EAAE,GAAG,MAAA,MAAC,CAAC,CAAC,MAAyB,0CAAE,OAAO,mDAAG,oBAAoB,CAAuB,CAAC;YAC/F,IAAI,CAAC,EAAE;gBAAE,OAAO;YAChB,IAAM,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC;YACrB,IAAM,IAAI,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YAC7B,OAAO,CAAC,KAAK,CAAC;gBACZ,MAAM,EAAE,CAAC,CAAC,UAAU,IAAI,SAAS;gBACjC,SAAS,EAAE,CAAC,CAAC,cAAc,KAAI,MAAC,EAAE,CAAC,OAAO,CAAC,gBAAgB,CAAwB,0CAAE,OAAO,CAAC,YAAY,CAAA,IAAI,IAAI;gBACjH,KAAK,EAAE,MAAA,MAAA,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,mCAAI,KAAK,CAAC,EAAE,CAAC,SAAS,CAAC,mCAAI,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC;gBACzF,IAAI,EAAE,CAAC,CAAC,SAAS,KAAI,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,IAAI,CAAA,IAAI,IAAI;aACxC,CAAC,CAAC;QACL,CAAC,CAAC,EAAE;IAXJ,CAWI,CAAC;IACP,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;IAElD,OAAO;QACL,MAAM,EAAE,cAAM,OAAA,OAAO,CAAC,OAAO,CAAC,UAAC,EAAE,IAAK,OAAA,EAAE,EAAE,EAAJ,CAAI,CAAC,EAA7B,CAA6B;QAC3C,cAAc,EAAE,UAAC,EAAE,EAAE,EAAE;YACrB,KAAK,CAAC,EAAE,EAAE,EAAE,EAAE,KAAK,CAAC,CAAC;YACrB,OAAO,cAAM,OAAA,OAAO,CAAC,EAAE,CAAC,EAAX,CAAW,CAAC;QAC3B,CAAC;KACF,CAAC;AACJ,CAAC,CAAC;AAzJW,QAAA,YAAY,gBAyJvB"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ITransport } from '../core/queue';
|
|
2
|
+
import type { IPageInfo, ITrackerEnv } from '../core/tracker';
|
|
3
|
+
/**
|
|
4
|
+
* Sends a batch. `beacon` (page going away): `navigator.sendBeacon`, falling back
|
|
5
|
+
* to `fetch(…, { keepalive: true })` when the beacon is refused (too big, or the
|
|
6
|
+
* browser has none). Any HTTP answer counts as delivered — the collector always
|
|
7
|
+
* answers 204, and a 404 (collector not installed) must not turn into a retry loop;
|
|
8
|
+
* only a network error keeps the batch for the next flush.
|
|
9
|
+
*/
|
|
10
|
+
export declare const browserTransport: (endpoint: string) => ITransport;
|
|
11
|
+
/** desktop / tablet / mobile from the user agent (targeting only; the server classifies for reports). */
|
|
12
|
+
export declare const deviceOf: (ua: string) => IPageInfo['device'];
|
|
13
|
+
export declare const browserEnv: (endpoint: string) => ITrackerEnv;
|
|
14
|
+
//# sourceMappingURL=env.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../../src/tracking/browser/env.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAEhD,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAE9D;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,aAAc,MAAM,KAAG,UAuBlD,CAAC;AAcH,yGAAyG;AACzG,eAAO,MAAM,QAAQ,OAAQ,MAAM,KAAG,SAAS,CAAC,QAAQ,CAIvD,CAAC;AAaF,eAAO,MAAM,UAAU,aAAc,MAAM,KAAG,WAW5C,CAAC"}
|