@privatrak/tracker 1.0.0 → 1.1.1

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/CHANGELOG.md CHANGED
@@ -12,6 +12,168 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
12
12
 
13
13
  ## [Unreleased]
14
14
 
15
+ ## [1.1.1] - 2026-10-04
16
+
17
+ ### Fixed
18
+
19
+ - The exact selector (`element_selector`) no longer records the position of
20
+ `<body>`'s direct children, so it no longer changes with what else sits in
21
+ `<body>`, such as browser extensions, menus, dialogs and toasts:
22
+ `body > div:nth-of-type(3) > aside` is now `body > div > aside`. Selectors
23
+ longer than 200 characters are cut from the front after that position is
24
+ dropped. The generalized selector (`element_selector_generalized`) is now
25
+ always sent, also when it is the same as the exact one.
26
+
27
+ - Clicks on buttons that open a menu as they are pressed, such as the "⋯"
28
+ actions button of a shadcn or Radix `DropdownMenu`, a `Select`, or a modal
29
+ `Popover`, are now counted. Those widgets make the browser send the click to
30
+ the page itself rather than the button, so they were missing from click
31
+ counts, features and click problems. A press dragged off the button and
32
+ released elsewhere still does not count.
33
+
34
+ - A page whose events briefly hit the API's rate limit, which is shared by
35
+ everyone behind the same office or mobile network address, no longer stops
36
+ tracking for the rest of the visit and loses what it had collected. It now
37
+ keeps the events and tries again after a short wait, 1 to 2 seconds at
38
+ first and growing to at most a minute while the API keeps refusing, then
39
+ sends them once the API accepts them again. That includes events sent as
40
+ the visitor switches tabs or minimises the window, which used to be lost
41
+ if refused. The same applies when the API is briefly unavailable. Tracking
42
+ is still switched off, as before, when a project is over its plan's monthly
43
+ event limit. A batch also never carries more than the 100 events the API
44
+ accepts at once.
45
+
46
+ - Links in single-page apps are now checked for dead and slow clicks. Next.js
47
+ `<Link>`, React Router, Vue Router, SvelteKit and similar routers render an
48
+ ordinary link but change the view themselves instead of letting the browser
49
+ load a new page, and every click on such a link used to be taken for leaving
50
+ the page, so it could never be dead or slow. Now a link is only treated that
51
+ way when the browser actually follows it. A router link whose new address
52
+ appears more than a second after the click sends a `slow_click`, and one
53
+ that does nothing at all sends a `dead_click`. Only the new address, or a
54
+ change inside the link itself such as a loading spinner, counts as the
55
+ link's answer: a menu or a mobile sidebar closing around the link while the
56
+ visitor waits for the page does not hide the wait, and a link in a menu
57
+ that closes and never leads anywhere is a `dead_click`. Routers built on
58
+ the browser's Navigation API, which change the address at once, are judged
59
+ by when the new view finishes loading. Links the browser
60
+ follows, including ones opened in a new tab or with a modifier key and
61
+ downloads, are unchanged and are never dead or slow, and so is a link to the
62
+ page that is already open, such as the current item in a navigation menu,
63
+ where seeing nothing new is what the visitor expects.
64
+
65
+ - Page views are now recorded for single-page apps whose router is built on
66
+ the browser's Navigation API, which change the address without
67
+ `history.pushState`. They used to record only the first page. Each change of
68
+ address still counts once, including Back and Forward and routers that use
69
+ `pushState`.
70
+
71
+ - Changes in the page's `<head>` that visitors never see (scripts, the tab's
72
+ title, metadata, icons, preloads) no longer count as a click doing
73
+ something, so a click whose only effect was one of those is now a
74
+ `dead_click`, as it should be. Stylesheets still count: a theme switch that
75
+ swaps one changes the whole page.
76
+
77
+ ## [1.1.0] - 2026-10-03
78
+
79
+ ### Added
80
+
81
+ - Dead and rage clicks, recorded as two new event types. A `dead_click` is a
82
+ click after which nothing happened on the page within about five seconds, or
83
+ before the visitor turned to something else after more than a second — on a
84
+ real control, on a disabled button, or on something that only looks
85
+ clickable, such as a `cursor: pointer` element, an image, an icon or
86
+ underlined text. A `rage_click` is three or more quick clicks on one element,
87
+ sent once when the burst ends with its size in `click_count`. Both carry the
88
+ same element fields as a click, and each is sent at most once per element per
89
+ page view. A control still sends its `click`, and an element that only looks
90
+ clickable still sends none. The one change to ordinary clicks is during a
91
+ quick burst on one control: the first click is sent at once as always, but
92
+ each quick repeat is sent only if something happened after it, so a broken
93
+ button clicked ten times sends one `click` instead of ten, while a button
94
+ that reacts to every click still sends every one. Repeat clicks still waiting
95
+ when the page is left or navigates are sent with it, never lost. Both are part of
96
+ click autocapture, so `autocapture: false` turns them off too, and each can be
97
+ switched off in your project's settings. They store nothing: no cookies, no
98
+ `localStorage`, no `sessionStorage`; everything is kept in memory for the
99
+ current page view and forgotten with it.
100
+
101
+ - Slow clicks and error clicks, recorded as two more event types beside dead
102
+ and rage clicks. A `slow_click` is a click that did work, but whose first
103
+ visible effect came more than a second after it, while the visitor was still
104
+ waiting; `response_ms` says how long it took. Only effects the click
105
+ plausibly caused count (a change to the clicked element or right around it,
106
+ a URL change, focus moving, a scroll, a dialog or popover opening), and parts
107
+ of the page that were already changing on their own, such as a carousel or a
108
+ clock, are discounted, so a busy page does not turn dead clicks into slow
109
+ ones. An `error_click` is a click followed by an uncaught error or an
110
+ unhandled promise rejection; `error_type` carries only the error's class
111
+ name, such as `TypeError`, never its message, stack or source location. Both
112
+ carry the same element fields as a click, are sent at most once per element
113
+ per page view, and can each be switched off in your project's settings.
114
+
115
+ - Support for the click map, which you open from the dashboard to see how often
116
+ each element of a page was clicked, drawn over your live site. Only you see
117
+ it, in the tab the dashboard opens, and nothing you click there is recorded.
118
+ Visitors download none of it. Older versions of the package can still open the
119
+ click map, but on pages whose addresses contain IDs (such as
120
+ `/orders/1234`) or parts you have set to be redacted, it may find no clicks,
121
+ because those versions cannot tell it how your events record the address.
122
+
123
+ ### Changed
124
+
125
+ - The Element Picker's match modes are now labelled **Exact element** and
126
+ **All similar** (formerly Exact and Generic). What each one saves is unchanged.
127
+
128
+ ### Fixed
129
+
130
+ - The Element Picker no longer shifts element positions while it is open. Its
131
+ panel used to sit inside `<body>`, so on a page with a `<div>` directly under
132
+ `<body>` every exact selector it showed or saved carried a wrong
133
+ `:nth-of-type`. Clicks recorded from visitors were never affected.
134
+ - The Element Picker now works on pages with slide-out drawers and pop-up
135
+ dialogs, such as the cart and login drawers of Shopify themes like Prestige.
136
+ Before, such a drawer could open underneath the picker's side panel, and
137
+ clicking anything in the panel closed it again, so its buttons and fields could
138
+ not be turned into feature rules. Drawers now open beside the panel, stay open
139
+ while you use it, and you can type in the panel's fields while one is open.
140
+ - Closing the Element Picker now leaves the page's markup exactly as it was,
141
+ without an empty `style` attribute on the elements the picker had moved aside.
142
+ - In single-page apps, a click or form submit that moves to another page is now
143
+ recorded on the page where it happened. Before, a click on a router link (a
144
+ React Router, Next.js or Vue `<Link>`, or any link whose handler calls
145
+ `history.pushState`) was counted on the page it led to, and arrived after that
146
+ page's page view. That moved clicks off the page they belong to in per-page
147
+ counts, user paths and funnels built on clicks. A search form that changes the
148
+ route on submit had the same problem. The recorded element, its text and its
149
+ selector are now also read as they were when it was clicked, before the page
150
+ re-rendered it.
151
+ - Clicks and submits are now recorded even when the page's own code stops the
152
+ event from propagating, as many dropdowns, menus and modal dialogs do. These
153
+ were missed before, so click counts for such elements will rise after you
154
+ update, without any change in how people use your site.
155
+ - The Element Picker's **Show rules and tags on this page** no longer outlines
156
+ elements nobody can see. Buttons and forms inside a closed drawer or dialog
157
+ used to get boxes and labels drawn over whatever was open on top of them.
158
+ Their outlines now appear when the drawer opens and go away again when it
159
+ closes. The list in the panel still counts them, since they are on the page.
160
+
161
+ ### Removed
162
+
163
+ - The sampling rate is gone, along with the `samplingRate` option of `init()`
164
+ and the `data-sampling-rate` script attribute. Both are now ignored. It did
165
+ not do what its name promised: because the tracker keeps nothing in the
166
+ browser, the dice were rolled once per page load rather than once per visitor,
167
+ so a visitor was recorded as soon as any one of their page loads survived.
168
+ Someone who left after a single page was usually dropped while someone who
169
+ read ten pages was almost always kept, which quietly bent the numbers towards
170
+ your most engaged visitors. Journeys arrived with pages missing from the
171
+ middle, so funnels showed routes nobody had taken, and a visitor whose landing
172
+ page was dropped was credited to your own site instead of the search or
173
+ campaign they really came from. Nothing scaled the totals back up either, so
174
+ the counts were simply low. A sampling feature that behaves per visitor will
175
+ come back later; until then every event is recorded.
176
+
15
177
  ## [1.0.0] - 2026-09-21
16
178
 
17
179
  The first supported release. `0.1.0` existed only to reserve the package name
package/README.md CHANGED
@@ -57,9 +57,7 @@ Elements can be labelled in markup instead of in code, which is how most
57
57
  feature-level analytics end up being defined:
58
58
 
59
59
  ```html
60
- <button data-track="signup-cta" data-track-plan="pro" data-track-source="header">
61
- Sign up
62
- </button>
60
+ <button data-track="signup-cta" data-track-plan="pro" data-track-source="header">Sign up</button>
63
61
  ```
64
62
 
65
63
  A click on that button is recorded with `data_track: "signup-cta"` and
@@ -73,23 +71,22 @@ matching attribute on the hosted `<script>` tag. Anything you do not set follows
73
71
  your project's settings from the dashboard, which the tracker fetches at
74
72
  startup; values passed to `init()` always win over the server's.
75
73
 
76
- | Option | Script attribute | Type | Default | Description |
77
- |--------|------------------|------|---------|-------------|
78
- | `apiKey` | `data-api-key` | `string` | — | Project API key. Required; without it nothing is sent. |
79
- | `apiHost` | `data-api-host` | `string` | the page's own origin (the hosted script uses the origin of its `src`) | Base URL of the Privatrak API, e.g. `https://api.privatrak.com`. |
80
- | `autocapture` | `data-autocapture` | `boolean` | `true` | Record clicks and form submissions automatically. |
81
- | `autocapturePageviews` | `data-autocapture` | `boolean` | `true` | Record pageviews, including SPA navigations. |
82
- | `autocaptureElements` | — | `string[]` | `a`, `button`, `input[type=submit]`, plus ARIA roles such as `[role=button]`, `[role=menuitem]`, `[role=tab]` | Selectors treated as interactive click targets. |
83
- | `walkDepth` | `data-walk-depth` | `number` | `3` | How many ancestors to walk up when looking for an interactive element or a `data-track` label. |
84
- | `maxTextLength` | `data-max-text-length` | `number` | `100` | Element text is truncated to this many characters. |
85
- | `flushIntervalMs` | `data-flush-interval` | `number` | `5000` | How often the queue is sent. |
86
- | `maxQueueSize` | `data-max-queue-size` | `number` | `10` | Queue length that triggers an immediate send. |
87
- | `piiUrlParams` | `data-pii-params` | `string[]` | `['email', 'token', 'key', 'password', 'secret']` | Query parameters whose values are replaced with `:redacted` before the URL is sent. |
88
- | `piiPathPatterns` | `data-pii-path-patterns` | `string[]` | `[]` | Regular expressions whose matching path segments are replaced with `:redacted`. |
89
- | `normalizePathIds` | `data-normalize-path-ids` | `boolean` | `true` | Collapse ID-shaped path segments to `:id`, so `/orders/4711` and `/orders/4712` are one page. |
90
- | `excludedUrls` | `data-excluded-urls` | `string[]` | `[]` | URL patterns that are not tracked at all. |
91
- | `samplingRate` | `data-sampling-rate` | `number` | `1.0` | Fraction of visitors recorded, from `0` to `1`. |
92
- | `traits` | `data-traits` | `string[]` | `[]` | Session labels attached to every event in the batch — see `setTraits()`. |
74
+ | Option | Script attribute | Type | Default | Description |
75
+ | ---------------------- | ------------------------- | ---------- | ------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
76
+ | `apiKey` | `data-api-key` | `string` | — | Project API key. Required; without it nothing is sent. |
77
+ | `apiHost` | `data-api-host` | `string` | the page's own origin (the hosted script uses the origin of its `src`) | Base URL of the Privatrak API, e.g. `https://api.privatrak.com`. |
78
+ | `autocapture` | `data-autocapture` | `boolean` | `true` | Record clicks and form submissions automatically, and with them dead, rage, slow and error clicks. |
79
+ | `autocapturePageviews` | `data-autocapture` | `boolean` | `true` | Record pageviews, including SPA navigations. |
80
+ | `autocaptureElements` | — | `string[]` | `a`, `button`, `input[type=submit]`, plus ARIA roles such as `[role=button]`, `[role=menuitem]`, `[role=tab]` | Selectors treated as interactive click targets. |
81
+ | `walkDepth` | `data-walk-depth` | `number` | `3` | How many ancestors to walk up when looking for an interactive element or a `data-track` label. |
82
+ | `maxTextLength` | `data-max-text-length` | `number` | `100` | Element text is truncated to this many characters. |
83
+ | `flushIntervalMs` | `data-flush-interval` | `number` | `5000` | How often the queue is sent. |
84
+ | `maxQueueSize` | `data-max-queue-size` | `number` | `10` | Queue length that triggers an immediate send. |
85
+ | `piiUrlParams` | `data-pii-params` | `string[]` | `['email', 'token', 'key', 'password', 'secret']` | Query parameters whose values are replaced with `:redacted` before the URL is sent. |
86
+ | `piiPathPatterns` | `data-pii-path-patterns` | `string[]` | `[]` | Regular expressions whose matching path segments are replaced with `:redacted`. |
87
+ | `normalizePathIds` | `data-normalize-path-ids` | `boolean` | `true` | Collapse ID-shaped path segments to `:id`, so `/orders/4711` and `/orders/4712` are one page. |
88
+ | `excludedUrls` | `data-excluded-urls` | `string[]` | `[]` | URL patterns that are not tracked at all. |
89
+ | `traits` | `data-traits` | `string[]` | `[]` | Session labels attached to every event in the batch — see `setTraits()`. |
93
90
 
94
91
  List options are comma-separated in an attribute: `data-pii-params="email,token"`.
95
92
  The two autocapture switches share one attribute, which takes a list of what to
@@ -134,7 +131,8 @@ tracking; nothing else is affected.
134
131
 
135
132
  Starts the tracker: applies the configuration, records the first pageview, and
136
133
  attaches the delegated `click` and `submit` listeners plus the History API
137
- patches that detect SPA navigation. Calling it twice logs a warning and does
134
+ patches (and, where the browser has it, a Navigation API listener) that detect
135
+ SPA navigation. Calling it twice logs a warning and does
138
136
  nothing the second time. Returns immediately; the first batch leaves once the
139
137
  project's settings have loaded.
140
138
 
@@ -169,19 +167,112 @@ as types.
169
167
 
170
168
  ## What gets captured
171
169
 
172
- **Pageview** — on load and on every SPA navigation (`pushState`,
173
- `replaceState`, `popstate`). Navigations to the same URL are not recorded twice.
170
+ **Pageview** — on load and on every SPA navigation: `pushState`,
171
+ `replaceState`, `popstate`, and, in browsers with the Navigation API, a router
172
+ that intercepts `navigate` instead of calling `pushState`. Each change of
173
+ address is recorded once, however many of those report it, and navigations to
174
+ the same URL are not recorded twice.
174
175
  Fields: `page_url`, `referrer`, `timestamp`.
175
176
 
176
177
  **Click** — on anything in `autocaptureElements`, or anything carrying
177
178
  `data-track`, via a single delegated listener on `document`. A click on a
178
179
  non-interactive child walks up to `walkDepth` ancestors to find the real target.
179
- Fields: `element_tag`, `element_id`, `element_class`, `element_text` (truncated),
180
- `data_track`, `data_track_attrs`, plus `page_url` and `timestamp`.
180
+ A button that opens a modal menu as it is pressed — a shadcn/Radix
181
+ `DropdownMenu`, `Select` or modal `Popover` trigger — makes the browser send
182
+ its click to the page itself rather than the button; that click is counted on
183
+ the button, as long as the pointer was released on it. A press dragged off the
184
+ button and released elsewhere is not counted.
185
+ Fields: `element_tag`, `element_selector`, `element_selector_generalized`,
186
+ `element_text` (truncated), `data_track`, `data_track_attrs`, plus `page_url`
187
+ and `timestamp`.
188
+
189
+ `element_selector` is the element's exact path from `<body>`, each level as its
190
+ tag, `[type=…]` and `[role=…]`, and its position among same-tagged siblings
191
+ (`:nth-of-type(N)`) when it has any — except directly under `<body>`, where
192
+ menus, toasts and browser extensions come and go, so the same element always
193
+ gets the same selector: `body > div > aside > a:nth-of-type(2)`. A path longer
194
+ than 200 characters is cut from the front, after that position is dropped.
195
+ `element_selector_generalized` is the same path without any positions, and is
196
+ always sent, even when it is the same as `element_selector`.
181
197
 
182
198
  **Form submit** — on submit, with the form's own identity and labels. Field
183
- values are never read. Fields: `element_tag`, `element_id`, `element_class`,
184
- `data_track`, `data_track_attrs`, `page_url`, `timestamp`.
199
+ values are never read. Fields: `element_tag`, `element_selector`,
200
+ `element_selector_generalized`, `data_track`, `data_track_attrs`, `page_url`,
201
+ `timestamp`.
202
+
203
+ The click and form submit listeners run in the capture phase, before any of the
204
+ page's own handlers. So a click on a router link is recorded on the page it was
205
+ made on, ahead of the pageview for the page it leads to, and a handler that
206
+ calls `stopPropagation()` does not hide the click. The tracker never cancels or
207
+ stops an event itself.
208
+
209
+ **Dead click** (`dead_click`) — a click after which nothing happened within
210
+ about five seconds: no DOM change, no navigation or URL change, no scroll, no
211
+ focus moving elsewhere, no dialog, popover or new tab. It is recorded for real
212
+ controls (which still send their ordinary click as well), for disabled buttons
213
+ and fields, and for elements that only look clickable — a `cursor: pointer`
214
+ element, an image, an icon, underlined text — which never send a click of their
215
+ own. Clicks on plain text or empty space, clicks that select text, and clicks
216
+ on canvas, video, audio, form fields and editable content are ignored, and
217
+ anything ambiguous counts as something having happened. A link the browser
218
+ follows to another page is never dead, and neither is a link to the page
219
+ already shown (the active item of a navigation). Any other link a single-page
220
+ app's router handles itself (Next.js `<Link>`, React Router, Vue Router,
221
+ SvelteKit and the like, which cancel the browser's navigation and change the
222
+ view in place) is judged by its new address: slow if it appears more than a
223
+ second after the click, dead if it never does. A change inside the link
224
+ itself, such as a loading spinner, counts as its answer too. A menu or drawer
225
+ closing around the link does not: if the link went away with it, no new
226
+ address came and nothing new appeared (such as a dialog), the click is dead.
227
+ If the link is still on screen and something else on the page changed, the
228
+ click is not reported. Routers built on the browser's Navigation API, which
229
+ let the address change at once, are judged by when the new view finishes
230
+ loading instead. If the visitor clicks or types
231
+ somewhere else, scrolls, or leaves the page before the five seconds are up,
232
+ the click is decided there and then: dead if nothing had happened for over a
233
+ second, and otherwise not. Parts of the page that were already changing on
234
+ their own before the click, such as a carousel, a clock or a list that
235
+ refreshes itself, are not taken for the click's effect. Same fields as a
236
+ click.
237
+
238
+ **Slow click** (`slow_click`) — a click that did work, but whose first visible
239
+ effect came more than a second after it, while the visitor was still waiting.
240
+ Only effects the click plausibly caused count: a change to the clicked element,
241
+ to something inside it or close around it, a URL change (such as a router link
242
+ whose new view arrives late), focus moving, a scroll, or a dialog or popover
243
+ opening. Anything else changing on the page makes the click neither slow nor
244
+ dead. `response_ms` is the time from the click
245
+ to that first effect. Same fields as a click.
246
+
247
+ **Error click** (`error_click`) — a click followed, within the same five
248
+ seconds, by an uncaught JavaScript error or an unhandled promise rejection.
249
+ `error_type` is the error's class name — `TypeError`, `ReferenceError`, your
250
+ own error class's `name` — or `UnhandledRejection` for a rejection that is not
251
+ an `Error`, and `Error` for anything else, including the cross-origin
252
+ "Script error.". The error's message, stack, file and line are never read,
253
+ since they can contain what the visitor typed. With two clicks in flight, the
254
+ error goes to the more recent one. A click that throws usually changes nothing
255
+ on the page, so it is often a dead click as well. Same fields as a click.
256
+
257
+ **Rage click** (`rage_click`) — three or more clicks on one element, each within
258
+ a second of the one before. One event when the burst ends, with the burst's
259
+ size in `click_count` and the time of its first click. Same fields as a click.
260
+
261
+ During a burst, the repeat clicks themselves are only sent when they did
262
+ something. The first click on a control is sent at once, as always; a further
263
+ click on the same control within a second of the one before waits up to a
264
+ second, and is sent as an ordinary click, with its own time, if anything
265
+ happened after it — or dropped if nothing did. So a broken button clicked ten
266
+ times sends one click, one dead click and one rage click of 10, while a
267
+ stepper that changes on every click still sends all of its clicks. Leaving the
268
+ page, or navigating to another view, counts as something happening: waiting
269
+ clicks are sent with it. Keyboard activations are never held, and with dead,
270
+ rage, slow and error clicks all switched off, every click is sent at once.
271
+
272
+ Dead, rage, slow and error clicks are part of click autocapture: they need
273
+ `autocapture` on, and each can be switched off in your project's settings.
274
+ Each is recorded at most once per element per page view. Working them out needs no storage: all of
275
+ it is in memory, for the current page view only.
185
276
 
186
277
  **Custom** — whatever you pass to `track()`.
187
278
 
@@ -192,8 +283,8 @@ are skipped outright, so they are never even click targets.
192
283
 
193
284
  Events are queued in memory and sent in batches to `POST /api/events` — every 5
194
285
  seconds, or as soon as 10 are waiting, or immediately when the tab is hidden.
195
- Normal sends use `fetch()` with `keepalive`; the tab-hidden send uses
196
- `navigator.sendBeacon()` so it survives the page going away. A failed batch is
286
+ Every send uses `fetch()` with `keepalive`, the tab-hidden one included, so it
287
+ survives the page going away and can still carry the API key header. A failed batch is
197
288
  re-queued for the next cycle, and the queue is capped at 100 events so a broken
198
289
  connection cannot grow it without bound.
199
290
 
package/dist/index.d.mts CHANGED
@@ -13,7 +13,6 @@ interface TrackerInitConfig {
13
13
  normalizePathIds?: boolean;
14
14
  piiPathPatterns?: string[];
15
15
  excludedUrls?: string[];
16
- samplingRate?: number;
17
16
  traits?: string[];
18
17
  }
19
18
 
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- var config={flush_interval_ms:5e3,max_queue_size:10,walk_depth:3,max_text_length:100,autocapture_pageviews:!0,autocapture:!0,autocapture_elements:["a","button","input[type=submit]","[role=button]","[role=link]","[role=menuitem]","[role=menuitemcheckbox]","[role=menuitemradio]","[role=option]","[role=tab]","[role=switch]","[role=checkbox]","[role=radio]","[role=combobox]"],pii_url_params:["email","token","key","password","secret"],normalize_path_ids:!0,pii_path_patterns:[],excluded_urls:[],sampling_rate:1},apiBase="",apiKey="",configLoaded=!1,trackingDisabled=!1,traits=[];function setApiBase(value){apiBase=value}function setApiKey(value){apiKey=value}function setConfigLoaded(value){configLoaded=value}function setTrackingDisabled(value){trackingDisabled=value}function setTraits(value){traits=value}function applyServerConfig(server){if(typeof server!="object"||server===null)return;let s=server;typeof s.flush_interval_ms=="number"&&(config.flush_interval_ms=s.flush_interval_ms),typeof s.max_queue_size=="number"&&(config.max_queue_size=s.max_queue_size),typeof s.walk_depth=="number"&&(config.walk_depth=s.walk_depth),typeof s.max_text_length=="number"&&(config.max_text_length=s.max_text_length),typeof s.autocapture_pageviews=="boolean"&&(config.autocapture_pageviews=s.autocapture_pageviews),typeof s.autocapture=="boolean"&&(config.autocapture=s.autocapture),Array.isArray(s.autocapture_elements)&&(config.autocapture_elements=s.autocapture_elements),Array.isArray(s.pii_url_params)&&(config.pii_url_params=s.pii_url_params),typeof s.normalize_path_ids=="boolean"&&(config.normalize_path_ids=s.normalize_path_ids),Array.isArray(s.pii_path_patterns)&&(config.pii_path_patterns=s.pii_path_patterns),Array.isArray(s.excluded_urls)&&(config.excluded_urls=s.excluded_urls),typeof s.sampling_rate=="number"&&(config.sampling_rate=s.sampling_rate)}var CONFIG_FETCH_ATTEMPTS=5,CONFIG_RETRY_DELAYS_MS=[500,1e3,2e3,4e3],CONFIG_FETCH_TIMEOUT_MS=3e3;function isRetryableStatus(status){return status===408||status===429||status>=500}function delay(ms){return new Promise(resolve=>setTimeout(resolve,ms))}async function attemptConfigFetch(){let controller=new AbortController,timer=setTimeout(()=>{controller.abort()},CONFIG_FETCH_TIMEOUT_MS);try{let response=await fetch(`${apiBase}/api/config`,{headers:{"X-API-Key":apiKey},credentials:"omit",signal:controller.signal});if(!response.ok)return isRetryableStatus(response.status)?{kind:"retry",status:response.status}:{kind:"permanent",status:response.status};let data=await response.json();return applyServerConfig(data),{kind:"ok"}}catch{return{kind:"retry"}}finally{clearTimeout(timer)}}async function fetchConfig(){let status;for(let attempt=0;attempt<CONFIG_FETCH_ATTEMPTS;attempt++){let outcome=await attemptConfigFetch();if(outcome.kind==="ok")return{loaded:!0,attempts:attempt+1};if(outcome.kind==="permanent")return{loaded:!1,attempts:attempt+1,status:outcome.status};status=outcome.status;let wait=CONFIG_RETRY_DELAYS_MS[attempt];if(wait===void 0)break;await delay(wait)}return{loaded:!1,attempts:CONFIG_FETCH_ATTEMPTS,...status!==void 0&&{status}}}var CONFIG_FAILURE_REASONS={401:"the API key is missing or not valid",403:"this site's origin is not in the project's allowed domains"};function describeConfigFailure(result){let attempts=result.attempts===1?"1 attempt":`${String(result.attempts)} attempts`;if(result.status===void 0)return`no response after ${attempts} (network error or timeout)`;let reason=CONFIG_FAILURE_REASONS[result.status],http=`HTTP ${String(result.status)}`;return reason===void 0?`${http} after ${attempts}`:`${http} after ${attempts} (${reason})`}function applyInitConfig(initConfig){initConfig.apiKey!==void 0&&(apiKey=initConfig.apiKey),initConfig.apiHost!==void 0&&(apiBase=initConfig.apiHost),initConfig.flushIntervalMs!==void 0&&(config.flush_interval_ms=initConfig.flushIntervalMs),initConfig.maxQueueSize!==void 0&&(config.max_queue_size=initConfig.maxQueueSize),initConfig.walkDepth!==void 0&&(config.walk_depth=initConfig.walkDepth),initConfig.maxTextLength!==void 0&&(config.max_text_length=initConfig.maxTextLength),initConfig.autocapturePageviews!==void 0&&(config.autocapture_pageviews=initConfig.autocapturePageviews),initConfig.autocapture!==void 0&&(config.autocapture=initConfig.autocapture),initConfig.autocaptureElements!==void 0&&(config.autocapture_elements=initConfig.autocaptureElements),initConfig.piiUrlParams!==void 0&&(config.pii_url_params=initConfig.piiUrlParams),typeof initConfig.normalizePathIds=="boolean"&&(config.normalize_path_ids=initConfig.normalizePathIds),Array.isArray(initConfig.piiPathPatterns)&&(config.pii_path_patterns=initConfig.piiPathPatterns),initConfig.excludedUrls!==void 0&&(config.excluded_urls=initConfig.excludedUrls),initConfig.samplingRate!==void 0&&(config.sampling_rate=initConfig.samplingRate),initConfig.traits!==void 0&&(traits=initConfig.traits)}var ID_PATTERNS=[/^\d+$/,/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,/^[0-9a-f]{8,}$/i];function isIdSegment(segment){return ID_PATTERNS.some(p=>p.test(segment))}function buildPiiPattern(){if(config.pii_url_params.length===0)return null;let escaped=config.pii_url_params.map(p=>p.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"));return new RegExp("("+escaped.join("|")+")","i")}function buildPiiPathPattern(){if(config.pii_path_patterns.length===0)return null;try{return new RegExp("^("+config.pii_path_patterns.join("|")+")$","i")}catch{return null}}function sanitizeUrl(url){try{let u=new URL(url),segments=u.pathname.split("/"),piiPathRegex=buildPiiPathPattern(),pathChanged=!1;for(let i=0;i<segments.length;i++){let seg=segments[i];if(seg){if(config.normalize_path_ids&&isIdSegment(seg)){segments[i]=":id",pathChanged=!0;continue}piiPathRegex?.test(seg)&&(segments[i]=":redacted",pathChanged=!0)}}pathChanged&&(u.pathname=segments.join("/"));let paramPattern=buildPiiPattern();if(paramPattern){let toRedact=[];u.searchParams.forEach((_v,k)=>{paramPattern.test(k)&&toRedact.push(k)});for(let k of toRedact)u.searchParams.set(k,":redacted")}return u.toString()}catch{return url}}function matchGlob(pattern,url){try{let path=new URL(url).pathname,escaped=pattern.replace(/[.+^${}()|[\]\\]/g,"\\$&");return new RegExp("^"+escaped.replace(/\*/g,".*")+"$").test(path)}catch{return!1}}function isExcludedUrl(url){return config.excluded_urls.some(pattern=>matchGlob(pattern,url))}function disableTracking(queue2){setTrackingDisabled(!0),queue2.length=0,config.autocapture_pageviews=!1,config.autocapture=!1}function applySampling(queue2){if(!(config.sampling_rate>=1)){if(config.sampling_rate<=0){disableTracking(queue2);return}Math.random()>config.sampling_rate&&disableTracking(queue2)}}function extractText(el){let raw=el.textContent;if(!raw)return;let trimmed=raw.trim().replace(/\s+/g," ");if(trimmed.length!==0)return trimmed.length>config.max_text_length?trimmed.slice(0,config.max_text_length):trimmed}function extractDataTrack(el){let current=el;for(let i=0;i<=config.walk_depth&&current;i++){let trackValue=current.getAttribute("data-track");if(trackValue!==null){let attrs={};for(let attr of current.attributes)if(attr.name.startsWith("data-track-")){let key=attr.name.slice(11);attrs[key]=attr.value}return{dataTrack:trackValue||void 0,dataTrackAttrs:Object.keys(attrs).length>0?attrs:void 0}}current=current.parentElement}return{dataTrack:void 0,dataTrackAttrs:void 0}}function getSegment(el){let seg=el.tagName.toLowerCase();el instanceof HTMLInputElement&&el.type&&(seg+='[type="'+CSS.escape(el.type)+'"]');let role=el.getAttribute("role");role&&(seg+='[role="'+CSS.escape(role)+'"]');let parent=el.parentElement;if(parent){let tag=el.tagName,siblings=parent.children,sameTag=0,idx=0;for(let i=0;i<siblings.length;i++){let sib=siblings[i];sib&&sib.tagName===tag&&(sameTag++,sib===el&&(idx=sameTag))}sameTag>1&&(seg+=":nth-of-type("+String(idx)+")")}return seg}function computeSelector(target){let parts=[],el=target;for(;el&&el!==document.documentElement;)parts.unshift(getSegment(el)),el=el.parentElement;let result=parts.join(" > ");for(;result.length>200&&parts.length>1;)parts.shift(),result=parts.join(" > ");return result}function getGenericSegment(el){let seg=el.tagName.toLowerCase();el instanceof HTMLInputElement&&el.type&&(seg+='[type="'+CSS.escape(el.type)+'"]');let role=el.getAttribute("role");return role&&(seg+='[role="'+CSS.escape(role)+'"]'),seg}function computeGenericSelector(target){let parts=[],el=target;for(;el&&el!==document.documentElement;)parts.unshift(getGenericSegment(el)),el=el.parentElement;let result=parts.join(" > ");for(;result.length>200&&parts.length>1;)parts.shift(),result=parts.join(" > ");let specific=computeSelector(target);return result===specific?null:result}function createPageviewEvent(referrer){return{event_type:"pageview",page_url:window.location.href,referrer:referrer===""?void 0:referrer,timestamp:new Date().toISOString()}}function createClickEvent(el){let{dataTrack,dataTrackAttrs}=extractDataTrack(el);return{event_type:"click",page_url:window.location.href,element_tag:el.tagName.toLowerCase(),element_selector:computeSelector(el),element_selector_generalized:computeGenericSelector(el)??void 0,element_text:extractText(el),data_track:dataTrack,data_track_attrs:dataTrackAttrs,timestamp:new Date().toISOString()}}function createFormSubmitEvent(form){let{dataTrack,dataTrackAttrs}=extractDataTrack(form);return{event_type:"form_submit",page_url:window.location.href,element_tag:form.tagName.toLowerCase(),element_selector:computeSelector(form),element_selector_generalized:computeGenericSelector(form)??void 0,data_track:dataTrack,data_track_attrs:dataTrackAttrs,timestamp:new Date().toISOString()}}var pickerActive=!1,pickerDashboardOrigin=null;function setPickerActive(value){pickerActive=value}function setPickerDashboardOrigin(value){pickerDashboardOrigin=value}var REJECTION_REASONS={401:"the API key is missing or not valid",403:"this site's origin is not in the project's allowed domains",429:"the project is over its event limit"},queue=[],lastUrl="",isFlushing=!1;function setLastUrl(value){lastUrl=value}function sanitizeEvent(event){let pageUrl=sanitizeUrl(event.page_url),referrer=event.referrer===void 0?void 0:sanitizeUrl(event.referrer);return pageUrl===event.page_url&&referrer===event.referrer?event:{...event,page_url:pageUrl,...referrer!==void 0&&{referrer}}}function prepareEvents(events){return filterEvents(events.map(sanitizeEvent))}function filterEvents(events){return events.filter(event=>!(isExcludedUrl(event.page_url)||event.event_type==="pageview"&&!config.autocapture_pageviews||(event.event_type==="click"||event.event_type==="form_submit")&&!config.autocapture&&!event.data_track))}function enqueue(event){trackingDisabled||pickerActive||apiKey&&(queue.length>=100||(queue.push(event),configLoaded&&queue.length>=config.max_queue_size&&flush()))}async function flush(){if(trackingDisabled||pickerActive||!configLoaded||isFlushing||queue.length===0)return;isFlushing=!0;let events=prepareEvents(queue.splice(0));if(events.length===0){isFlushing=!1;return}let body={events,...traits.length>0&&{traits}},headers={"Content-Type":"application/json"};apiKey&&(headers["X-API-Key"]=apiKey);try{let response=await fetch(`${apiBase}/api/events`,{method:"POST",headers,body:JSON.stringify(body),keepalive:!0,credentials:"omit"});if(!response.ok){let status=response.status;status===401||status===403||status===429?(console.warn(`[tracker] The API rejected events with HTTP ${String(status)} \u2014 ${REJECTION_REASONS[status]??"the request was refused"}. Tracking is now off for this page.`),disableTracking(queue)):status>=500&&queue.unshift(...events)}}catch{queue.unshift(...events)}finally{isFlushing=!1}}function flushBeacon(){if(trackingDisabled||!configLoaded||queue.length===0)return;let events=prepareEvents(queue.splice(0));if(events.length===0)return;let body={events,...traits.length>0&&{traits}};apiKey&&(body.api_key=apiKey);let headers={"Content-Type":"application/json"};apiKey&&(headers["X-API-Key"]=apiKey),fetch(`${apiBase}/api/events`,{method:"POST",headers,body:JSON.stringify(body),keepalive:!0,credentials:"omit"}).catch(()=>{})}function isSensitive(el){if(el instanceof HTMLInputElement){let t=el.type.toLowerCase();if(t==="password"||t==="hidden")return!0}let ac=el.getAttribute("autocomplete");return!!(ac&&ac.toLowerCase().startsWith("cc-"))}function isInteractive(el){if(el.hasAttribute("data-track"))return!0;if(!config.autocapture)return!1;for(let selector of config.autocapture_elements)try{if(el.matches(selector))return!0}catch{}return!1}function findInteractiveAncestor(target){let el=target;for(let i=0;i<=config.walk_depth&&el;i++){if(isInteractive(el))return el;el=el.parentElement}return null}function isPickerTarget(el){return isInteractive(el)||el.matches("form")}function findPickerTarget(target){let el=target;for(let i=0;i<=config.walk_depth&&el;i++){if(isPickerTarget(el))return el;el=el.parentElement}return null}var PICKER_HANDOFF_KEY="__stPickerHandoff";var PICKER_FILE="tracker-picker.js",picker=null,loading=!1,failed=!1,pendingOptions=null,pickerHostApi={setActive:setPickerActive,setDashboardOrigin:setPickerDashboardOrigin,clearQueue:()=>{queue.length=0},computeSelector,computeGenericSelector,extractText,extractDataTrack,findPickerTarget};function pickerScriptUrl(){for(let script of document.querySelectorAll("script[src]")){let src=script.getAttribute("src");if(src?.includes("tracker.js"))try{return new URL(PICKER_FILE,new URL(src,document.baseURI)).toString()}catch{}}return apiBase?apiBase.replace(/\/+$/,"")+"/"+PICKER_FILE:"/"+PICKER_FILE}function registerPicker(module){if(picker=module,loading=!1,pendingOptions){let options=pendingOptions;pendingOptions=null,activate(module,options)}}function activate(module,options){module.activate({...options,dashboardOrigin:pickerDashboardOrigin})}function abandonLoad(url){loading=!1,failed=!0,pendingOptions=null,setPickerActive(!1),console.warn(`[tracker] Could not load the Element Picker from ${url}. Tracking is unaffected.`)}function loadPicker(){let url=pickerScriptUrl(),script=document.createElement("script");script.src=url,script.async=!0,script[PICKER_HANDOFF_KEY]={host:pickerHostApi,register:registerPicker},script.addEventListener("load",()=>{script.remove(),picker||abandonLoad(url)}),script.addEventListener("error",()=>{script.remove(),abandonLoad(url)}),document.head.appendChild(script)}function requestPicker(options){if(picker){activate(picker,options??{});return}failed||(setPickerActive(!0),queue.length=0,pendingOptions=options??{},!loading&&(loading=!0,loadPicker()))}function onPickerNavigation(){picker?.onNavigation()}function onPickerWindowMessage(e){if(picker){picker.handleMessage(e);return}typeof e.data!="object"||e.data===null||e.data.type!=="st-picker-activate"||pickerDashboardOrigin&&e.origin!==pickerDashboardOrigin||pickerActive||!window.opener||requestPicker()}function onClickCapture(e){if(pickerActive)return;let target=e.target;if(!(target instanceof Element))return;let interactive=findInteractiveAncestor(target);interactive&&(isSensitive(interactive)||enqueue(createClickEvent(interactive)))}function onSubmitCapture(e){if(pickerActive)return;let form=e.target;form instanceof HTMLFormElement&&(isSensitive(form)||!config.autocapture&&!extractDataTrack(form).dataTrack||enqueue(createFormSubmitEvent(form)))}function onSpaNavigation(){if(pickerActive){onPickerNavigation();return}if(!config.autocapture_pageviews)return;let currentUrl=window.location.href;if(currentUrl===lastUrl)return;let referrer=lastUrl;setLastUrl(currentUrl),enqueue(createPageviewEvent(referrer))}function patchHistory(){let origPushState=history.pushState.bind(history),origReplaceState=history.replaceState.bind(history);history.pushState=function(...args){origPushState(...args),onSpaNavigation()},history.replaceState=function(...args){origReplaceState(...args),onSpaNavigation()}}var version="1.0.0",initialized=!1,storedInitConfig={};function init(initConfig){if(typeof window>"u")return;if(initialized){console.warn("[tracker] Already initialized. Ignoring duplicate init() call.");return}if(initialized=!0,storedInitConfig=initConfig??{},storedInitConfig.apiKey!==void 0&&setApiKey(storedInitConfig.apiKey),storedInitConfig.apiHost!==void 0&&setApiBase(storedInitConfig.apiHost),!apiKey){console.error("[tracker] Missing apiKey. Events will not be sent.");return}applyInitConfig(storedInitConfig),setLastUrl(window.location.href),config.autocapture_pageviews&&enqueue(createPageviewEvent(document.referrer)),patchHistory(),window.addEventListener("popstate",onSpaNavigation),document.addEventListener("click",onClickCapture),document.addEventListener("submit",onSubmitCapture),window.addEventListener("message",onPickerWindowMessage);let hashMatch=window.location.hash.match(/^#st-picker=(.+)$/);if(hashMatch?.[1]){let origin=decodeURIComponent(hashMatch[1]);setPickerDashboardOrigin(origin);try{sessionStorage.setItem("st-picker",JSON.stringify({active:!0,origin}))}catch{}history.replaceState(null,"",window.location.pathname+window.location.search),window.opener&&requestPicker()}else try{let stored=sessionStorage.getItem("st-picker");if(stored&&window.opener){let parsed=JSON.parse(stored);if(typeof parsed=="object"&&parsed!==null&&"active"in parsed&&"origin"in parsed){let obj=parsed;if(obj.active&&typeof obj.origin=="string"){setPickerDashboardOrigin(obj.origin);let storedScan="scanEnabled"in obj&&obj.scanEnabled===!0,storedNav="navigateMode"in obj&&obj.navigateMode===!0,rawMode="selectorMode"in obj?obj.selectorMode:null;requestPicker({scanEnabled:storedScan,navigateMode:storedNav,selectorMode:rawMode==="generic"||rawMode==="exact"?rawMode:null})}}}}catch{}document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&flushBeacon()}),fetchConfig().then(result=>{if(!result.loaded){failClosed(result);return}applyInitConfig(storedInitConfig),applySampling(queue),!trackingDisabled&&(setConfigLoaded(!0),setInterval(()=>{flush()},config.flush_interval_ms),flush())})}function failClosed(result){console.warn(`[tracker] Could not load this project's settings from ${apiBase}/api/config \u2014 ${describeConfigFailure(result)}. No events were sent from this page load: without those settings the tracker cannot apply the project's URL redaction rules, and sending unredacted URLs would be worse than sending nothing.`),disableTracking(queue)}function track(name,attrs){typeof window>"u"||trackingDisabled||name&&enqueue({event_type:"custom",page_url:window.location.href,data_track:name,data_track_attrs:attrs,timestamp:new Date().toISOString()})}function setTraits2(newTraits){typeof window>"u"||setTraits(newTraits)}export{init,setTraits2 as setTraits,track,version};
1
+ var config={flush_interval_ms:5e3,max_queue_size:10,walk_depth:3,max_text_length:100,autocapture_pageviews:!0,autocapture:!0,autocapture_elements:["a","button","input[type=submit]","[role=button]","[role=link]","[role=menuitem]","[role=menuitemcheckbox]","[role=menuitemradio]","[role=option]","[role=tab]","[role=switch]","[role=checkbox]","[role=radio]","[role=combobox]"],pii_url_params:["email","token","key","password","secret"],normalize_path_ids:!0,pii_path_patterns:[],excluded_urls:[],capture_dead_clicks:!0,capture_rage_clicks:!0,capture_error_clicks:!0,capture_slow_clicks:!0,dead_click_window_ms:1e3,slow_click_threshold_ms:1e3,dead_click_wait_ms:5e3,rage_click_threshold:3,rage_click_window_ms:1e3},apiBase="",apiKey="",configLoaded=!1,trackingDisabled=!1,traits=[];function setApiBase(value){apiBase=value}function setApiKey(value){apiKey=value}function setConfigLoaded(value){configLoaded=value}function setTrackingDisabled(value){trackingDisabled=value}function setTraits(value){traits=value}function applyServerConfig(server){if(typeof server!="object"||server===null)return;let s=server;if(typeof s.flush_interval_ms=="number"&&(config.flush_interval_ms=s.flush_interval_ms),typeof s.max_queue_size=="number"&&(config.max_queue_size=s.max_queue_size),typeof s.walk_depth=="number"&&(config.walk_depth=s.walk_depth),typeof s.max_text_length=="number"&&(config.max_text_length=s.max_text_length),typeof s.autocapture_pageviews=="boolean"&&(config.autocapture_pageviews=s.autocapture_pageviews),typeof s.autocapture=="boolean"&&(config.autocapture=s.autocapture),Array.isArray(s.autocapture_elements)&&(config.autocapture_elements=s.autocapture_elements),Array.isArray(s.pii_url_params)&&(config.pii_url_params=s.pii_url_params),typeof s.normalize_path_ids=="boolean"&&(config.normalize_path_ids=s.normalize_path_ids),Array.isArray(s.pii_path_patterns)&&(config.pii_path_patterns=s.pii_path_patterns),Array.isArray(s.excluded_urls)&&(config.excluded_urls=s.excluded_urls),typeof s.capture_dead_clicks=="boolean"&&(config.capture_dead_clicks=s.capture_dead_clicks),typeof s.capture_rage_clicks=="boolean"&&(config.capture_rage_clicks=s.capture_rage_clicks),typeof s.capture_error_clicks=="boolean"&&(config.capture_error_clicks=s.capture_error_clicks),typeof s.capture_slow_clicks=="boolean"&&(config.capture_slow_clicks=s.capture_slow_clicks),typeof s.dead_click_window_ms=="number"){let w=s.dead_click_window_ms;config.dead_click_window_ms=w,config.slow_click_threshold_ms=w,config.dead_click_wait_ms=Math.max(w,5e3)}typeof s.slow_click_threshold_ms=="number"&&(config.slow_click_threshold_ms=s.slow_click_threshold_ms),typeof s.dead_click_wait_ms=="number"&&(config.dead_click_wait_ms=s.dead_click_wait_ms),typeof s.rage_click_threshold=="number"&&(config.rage_click_threshold=s.rage_click_threshold),typeof s.rage_click_window_ms=="number"&&(config.rage_click_window_ms=s.rage_click_window_ms)}var CONFIG_FETCH_ATTEMPTS=5,CONFIG_RETRY_DELAYS_MS=[500,1e3,2e3,4e3],CONFIG_FETCH_TIMEOUT_MS=3e3;function isRetryableStatus(status){return status===408||status===429||status>=500}function delay(ms){return new Promise(resolve2=>setTimeout(resolve2,ms))}async function attemptConfigFetch(){let controller=new AbortController,timer2=setTimeout(()=>{controller.abort()},CONFIG_FETCH_TIMEOUT_MS);try{let response=await fetch(`${apiBase}/api/config`,{headers:{"X-API-Key":apiKey},credentials:"omit",signal:controller.signal});if(!response.ok)return isRetryableStatus(response.status)?{kind:"retry",status:response.status}:{kind:"permanent",status:response.status};let data=await response.json();return applyServerConfig(data),{kind:"ok"}}catch{return{kind:"retry"}}finally{clearTimeout(timer2)}}async function fetchConfig(){let status;for(let attempt=0;attempt<CONFIG_FETCH_ATTEMPTS;attempt++){let outcome=await attemptConfigFetch();if(outcome.kind==="ok")return{loaded:!0,attempts:attempt+1};if(outcome.kind==="permanent")return{loaded:!1,attempts:attempt+1,status:outcome.status};status=outcome.status;let wait=CONFIG_RETRY_DELAYS_MS[attempt];if(wait===void 0)break;await delay(wait)}return{loaded:!1,attempts:CONFIG_FETCH_ATTEMPTS,...status!==void 0&&{status}}}var CONFIG_FAILURE_REASONS={401:"the API key is missing or not valid",403:"this site's origin is not in the project's allowed domains"};function describeConfigFailure(result){let attempts=result.attempts===1?"1 attempt":`${String(result.attempts)} attempts`;if(result.status===void 0)return`no response after ${attempts} (network error or timeout)`;let reason=CONFIG_FAILURE_REASONS[result.status],http=`HTTP ${String(result.status)}`;return reason===void 0?`${http} after ${attempts}`:`${http} after ${attempts} (${reason})`}function applyInitConfig(initConfig){initConfig.apiKey!==void 0&&(apiKey=initConfig.apiKey),initConfig.apiHost!==void 0&&(apiBase=initConfig.apiHost),initConfig.flushIntervalMs!==void 0&&(config.flush_interval_ms=initConfig.flushIntervalMs),initConfig.maxQueueSize!==void 0&&(config.max_queue_size=initConfig.maxQueueSize),initConfig.walkDepth!==void 0&&(config.walk_depth=initConfig.walkDepth),initConfig.maxTextLength!==void 0&&(config.max_text_length=initConfig.maxTextLength),initConfig.autocapturePageviews!==void 0&&(config.autocapture_pageviews=initConfig.autocapturePageviews),initConfig.autocapture!==void 0&&(config.autocapture=initConfig.autocapture),initConfig.autocaptureElements!==void 0&&(config.autocapture_elements=initConfig.autocaptureElements),initConfig.piiUrlParams!==void 0&&(config.pii_url_params=initConfig.piiUrlParams),typeof initConfig.normalizePathIds=="boolean"&&(config.normalize_path_ids=initConfig.normalizePathIds),Array.isArray(initConfig.piiPathPatterns)&&(config.pii_path_patterns=initConfig.piiPathPatterns),initConfig.excludedUrls!==void 0&&(config.excluded_urls=initConfig.excludedUrls),initConfig.traits!==void 0&&(traits=initConfig.traits)}var ID_PATTERNS=[/^\d+$/,/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,/^[0-9a-f]{8,}$/i];function isIdSegment(segment){return ID_PATTERNS.some(p=>p.test(segment))}function buildPiiPattern(){if(config.pii_url_params.length===0)return null;let escaped=config.pii_url_params.map(p=>p.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"));return new RegExp("("+escaped.join("|")+")","i")}function buildPiiPathPattern(){if(config.pii_path_patterns.length===0)return null;try{return new RegExp("^("+config.pii_path_patterns.join("|")+")$","i")}catch{return null}}function sanitizeUrl(url){try{let u=new URL(url),segments=u.pathname.split("/"),piiPathRegex=buildPiiPathPattern(),pathChanged=!1;for(let i=0;i<segments.length;i++){let seg=segments[i];if(seg){if(config.normalize_path_ids&&isIdSegment(seg)){segments[i]=":id",pathChanged=!0;continue}piiPathRegex?.test(seg)&&(segments[i]=":redacted",pathChanged=!0)}}pathChanged&&(u.pathname=segments.join("/"));let paramPattern=buildPiiPattern();if(paramPattern){let toRedact=[];u.searchParams.forEach((_v,k)=>{paramPattern.test(k)&&toRedact.push(k)});for(let k of toRedact)u.searchParams.set(k,":redacted")}return u.toString()}catch{return url}}function matchGlob(pattern,url){try{let path=new URL(url).pathname,escaped=pattern.replace(/[.+^${}()|[\]\\]/g,"\\$&");return new RegExp("^"+escaped.replace(/\*/g,".*")+"$").test(path)}catch{return!1}}function isExcludedUrl(url){return config.excluded_urls.some(pattern=>matchGlob(pattern,url))}function disableTracking(queue2){setTrackingDisabled(!0),queue2.length=0,config.autocapture_pageviews=!1,config.autocapture=!1}function extractText(el){let raw=el.textContent;if(!raw)return;let trimmed=raw.trim().replace(/\s+/g," ");if(trimmed.length!==0)return trimmed.length>config.max_text_length?trimmed.slice(0,config.max_text_length):trimmed}function extractDataTrack(el){let current=el;for(let i=0;i<=config.walk_depth&&current;i++){let trackValue=current.getAttribute("data-track");if(trackValue!==null){let attrs={};for(let attr of current.attributes)if(attr.name.startsWith("data-track-")){let key=attr.name.slice(11);attrs[key]=attr.value}return{dataTrack:trackValue||void 0,dataTrackAttrs:Object.keys(attrs).length>0?attrs:void 0}}current=current.parentElement}return{dataTrack:void 0,dataTrackAttrs:void 0}}function getSegment(el){let seg=el.tagName.toLowerCase();el instanceof HTMLInputElement&&el.type&&(seg+='[type="'+CSS.escape(el.type)+'"]');let role=el.getAttribute("role");role&&(seg+='[role="'+CSS.escape(role)+'"]');let parent=el.parentElement;if(parent){let tag=el.tagName,siblings=parent.children,sameTag=0,idx=0;for(let i=0;i<siblings.length;i++){let sib=siblings[i];sib&&sib.tagName===tag&&(sameTag++,sib===el&&(idx=sameTag))}sameTag>1&&(seg+=":nth-of-type("+String(idx)+")")}return seg}var BODY="body > ",SEPARATOR=" > ",NTH_OF_TYPE=":nth-of-type(";function normalizeSelector(s){if(!s.startsWith(BODY))return s;let rest=s.slice(BODY.length),i=rest.indexOf(SEPARATOR),first=i>=0?rest.slice(0,i):rest,tail=i>=0?rest.slice(i):"";return BODY+withoutPosition(first)+tail}function withoutPosition(seg){if(!seg.endsWith(")"))return seg;let open=seg.lastIndexOf(NTH_OF_TYPE);if(open<0)return seg;let index=seg.slice(open+NTH_OF_TYPE.length,seg.length-1);if(index==="")return seg;for(let k=0;k<index.length;k++){let c=index[k];if(c===void 0||c<"0"||c>"9")return seg}return seg.slice(0,open)}function capped(parts){let result=parts.join(SEPARATOR);for(;result.length>200&&parts.length>1;)parts.shift(),result=parts.join(SEPARATOR);return result}function computeSelector(target){let parts=[],el=target;for(;el&&el!==document.documentElement;)parts.unshift(getSegment(el)),el=el.parentElement;return capped(normalizeSelector(parts.join(SEPARATOR)).split(SEPARATOR))}function getGenericSegment(el){let seg=el.tagName.toLowerCase();el instanceof HTMLInputElement&&el.type&&(seg+='[type="'+CSS.escape(el.type)+'"]');let role=el.getAttribute("role");return role&&(seg+='[role="'+CSS.escape(role)+'"]'),seg}function computeGenericSelector(target){let parts=[],el=target;for(;el&&el!==document.documentElement;)parts.unshift(getGenericSegment(el)),el=el.parentElement;return capped(parts)}function createPageviewEvent(referrer){return{event_type:"pageview",page_url:window.location.href,referrer:referrer===""?void 0:referrer,timestamp:new Date().toISOString()}}function createClickEvent(el){return createElementEvent(el,"click")}function createElementEvent(el,type){let{dataTrack,dataTrackAttrs}=extractDataTrack(el);return{event_type:type,page_url:window.location.href,element_tag:el.tagName.toLowerCase(),element_selector:computeSelector(el),element_selector_generalized:computeGenericSelector(el),element_text:extractText(el),data_track:dataTrack,data_track_attrs:dataTrackAttrs,timestamp:new Date().toISOString()}}function createFormSubmitEvent(form){let{dataTrack,dataTrackAttrs}=extractDataTrack(form);return{event_type:"form_submit",page_url:window.location.href,element_tag:form.tagName.toLowerCase(),element_selector:computeSelector(form),element_selector_generalized:computeGenericSelector(form),data_track:dataTrack,data_track_attrs:dataTrackAttrs,timestamp:new Date().toISOString()}}var pickerActive=!1,pickerDashboardOrigin=null;function setPickerActive(value){pickerActive=value}function setPickerDashboardOrigin(value){pickerDashboardOrigin=value}function backoffMs(attempt,random){let base=Math.min(6e4,2e3*2**Math.max(0,attempt-1));return Math.round(base/2+base/2*Math.min(1,Math.max(0,random)))}function retryAfterMs(value,now){if(value===null)return null;let v=value.trim(),ms;if(/^\d+$/.test(v))ms=Number(v)*1e3;else{let at=Date.parse(v);if(Number.isNaN(at))return null;ms=at-now}return Math.min(6e5,Math.max(0,ms))}function waitMs(attempt,retryAfter,random){return retryAfter===null?backoffMs(attempt,random):Math.max(retryAfter,backoffMs(1,random))}var REJECTION_REASONS={401:"the API key is missing or not valid",403:"this site's origin is not in the project's allowed domains",429:"the project is over its plan's monthly event limit"},MONTHLY_LIMIT="limit_events_per_month",MAX_BATCH=100,backoff={until:0,attempts:0,noted:!1},queue=[],lastUrl="",isFlushing=!1;function setLastUrl(value){lastUrl=value}function sanitizeEvent(event){let pageUrl=sanitizeUrl(event.page_url),referrer=event.referrer===void 0?void 0:sanitizeUrl(event.referrer);return pageUrl===event.page_url&&referrer===event.referrer?event:{...event,page_url:pageUrl,...referrer!==void 0&&{referrer}}}function prepareEvents(events){return filterEvents(events.map(sanitizeEvent))}function filterEvents(events){return events.filter(event=>!(isExcludedUrl(event.page_url)||event.event_type==="pageview"&&!config.autocapture_pageviews||(event.event_type==="click"||event.event_type==="form_submit")&&!config.autocapture&&!event.data_track||event.event_type==="dead_click"&&!(config.autocapture&&config.capture_dead_clicks)||event.event_type==="rage_click"&&!(config.autocapture&&config.capture_rage_clicks)||event.event_type==="error_click"&&!(config.autocapture&&config.capture_error_clicks)||event.event_type==="slow_click"&&!(config.autocapture&&config.capture_slow_clicks)))}function enqueue(event){trackingDisabled||pickerActive||apiKey&&(queue.length>=100||(queue.push(event),configLoaded&&queue.length>=config.max_queue_size&&flush()))}var flushing=null;async function flush(){if(trackingDisabled||pickerActive||!configLoaded||isFlushing||queue.length===0||Date.now()<backoff.until)return;isFlushing=!0;let events=prepareEvents(queue.splice(0,MAX_BATCH));if(events.length===0){isFlushing=!1;return}let sent=send(events,!1,null);flushing=sent;try{await sent}finally{flushing=null,isFlushing=!1}}async function send(events,beacon,before){let body={events,...traits.length>0&&{traits}};beacon&&apiKey&&(body.api_key=apiKey);let headers={"Content-Type":"application/json"};apiKey&&(headers["X-API-Key"]=apiKey);let response;try{response=await fetch(`${apiBase}/api/events`,{method:"POST",headers,body:JSON.stringify(body),keepalive:!0,credentials:"omit"})}catch{return putBack(events,null,before)}if(response.ok)return backoff.until=0,backoff.attempts=0,0;let status=response.status;return status===401||status===403||status===429&&await errorCode(response)===MONTHLY_LIMIT?(trackingDisabled||console.warn(`[tracker] The API rejected events with HTTP ${String(status)} \u2014 ${REJECTION_REASONS[status]??"the request was refused"}. Tracking is now off for this page.`),disableTracking(queue),0):status===429||status>=500?(status===429&&!backoff.noted&&(backoff.noted=!0,console.info("[tracker] The API asked to slow down (HTTP 429). Events are kept and sent again shortly.")),putBack(events,response,before)):0}async function errorCode(response){try{let body=await response.json(),code=typeof body=="object"&&body!==null?body.error_code:void 0;return typeof code=="string"?code:void 0}catch{return}}async function putBack(events,response,before){let at=before?await before:0;return trackingDisabled?0:(queue.splice(Math.min(at,queue.length),0,...events),queue.length>100&&(queue.length=100),backOff(response),events.length)}function backOff(response){backoff.attempts++;let now=Date.now(),told=response?retryAfterMs(response.headers.get("Retry-After"),now):null;backoff.until=now+waitMs(backoff.attempts,told,Math.random())}function flushBeacon(){if(trackingDisabled||!configLoaded||queue.length===0)return;let events=prepareEvents(queue.splice(0,MAX_BATCH));events.length!==0&&send(events,!0,flushing)}function isSensitive(el){if(el instanceof HTMLInputElement){let t=el.type.toLowerCase();if(t==="password"||t==="hidden")return!0}let ac=el.getAttribute("autocomplete");return!!(ac&&ac.toLowerCase().startsWith("cc-"))}function isInteractive(el){if(el.hasAttribute("data-track"))return!0;if(!config.autocapture)return!1;for(let selector of config.autocapture_elements)try{if(el.matches(selector))return!0}catch{}return!1}function findInteractiveAncestor(target){let el=target;for(let i=0;i<=config.walk_depth&&el;i++){if(isInteractive(el))return el;el=el.parentElement}return null}function isPickerTarget(el){return isInteractive(el)||el.matches("form")}function findPickerTarget(target){let el=target;for(let i=0;i<=config.walk_depth&&el;i++){if(isPickerTarget(el))return el;el=el.parentElement}return null}function trackableElements(){let selectors=["[data-track]"];config.autocapture&&selectors.push("form",...config.autocapture_elements);let seen2=new Set;for(let selector of selectors){let found;try{found=document.querySelectorAll(selector)}catch{continue}for(let el of found)seen2.add(el)}let result=[];for(let el of seen2)isSensitive(el)||result.push(el);return result}var PICKER_HANDOFF_KEY="__stPickerHandoff";var PICKER_FILE="tracker-picker.js",picker=null,loading=!1,failed=!1,pendingOptions=null,pickerHostApi={setActive:setPickerActive,setDashboardOrigin:setPickerDashboardOrigin,clearQueue:()=>{queue.length=0},computeSelector,computeGenericSelector,extractText,extractDataTrack,findPickerTarget,trackableElements,sanitizeUrl};function pickerScriptUrl(){for(let script of document.querySelectorAll("script[src]")){let src=script.getAttribute("src");if(src?.includes("tracker.js"))try{return new URL(PICKER_FILE,new URL(src,document.baseURI)).toString()}catch{}}return apiBase?apiBase.replace(/\/+$/,"")+"/"+PICKER_FILE:"/"+PICKER_FILE}function registerPicker(module){if(picker=module,loading=!1,pendingOptions){let options=pendingOptions;pendingOptions=null,activate(module,options)}}function activate(module,options){module.activate({...options,dashboardOrigin:pickerDashboardOrigin})}function abandonLoad(url){loading=!1,failed=!0,pendingOptions=null,setPickerActive(!1),console.warn(`[tracker] Could not load the Element Picker from ${url}. Tracking is unaffected.`)}function loadPicker(){let url=pickerScriptUrl(),script=document.createElement("script");script.src=url,script.async=!0,script[PICKER_HANDOFF_KEY]={host:pickerHostApi,register:registerPicker},script.addEventListener("load",()=>{script.remove(),picker||abandonLoad(url)}),script.addEventListener("error",()=>{script.remove(),abandonLoad(url)}),document.head.appendChild(script)}function requestPicker(options){if(picker){activate(picker,options??{});return}failed||(setPickerActive(!0),queue.length=0,pendingOptions=options??{},!loading&&(loading=!0,loadPicker()))}function onPickerNavigation(){picker?.onNavigation()}function onPickerWindowMessage(e){if(picker){picker.handleMessage(e);return}if(typeof e.data!="object"||e.data===null)return;let data=e.data;data.type==="st-picker-activate"&&(pickerDashboardOrigin&&e.origin!==pickerDashboardOrigin||pickerActive||!window.opener||requestPicker(data.mode==="click-map"?{mode:"click-map"}:void 0))}function parsePickerHash(hash){let m=/^#st-picker=([^&]+)(?:&(.*))?$/.exec(hash),raw=m?.[1];if(!m||!raw)return null;let origin;try{origin=decodeURIComponent(raw)}catch{return null}let mode="rules";for(let pair of(m[2]??"").split("&"))pair==="mode=click-map"&&(mode="click-map");return{origin,mode}}var PRESS_MS=5e3,press=null;function disabled(el){return el.matches(":is(button,input,select,textarea):disabled")||el.getAttribute("aria-disabled")==="true"}function notePress(e){if(press=null,!e.isTrusted||e.button!==0)return;let target=e.target;if(!(target instanceof Element))return;let control=findInteractiveAncestor(target);!control||isSensitive(control)||disabled(control)||(press={id:e.pointerId,control,rect:control.getBoundingClientRect(),at:Date.now(),click:null,target:null})}function inside(r,x,y){return x>=r.left&&x<=r.right&&y>=r.top&&y<=r.bottom}function clickTarget(e){let target=e.target,p=press;if(!p||!e.isTrusted||e.detail===0)return target;if(p.click===e)return p.target??target;if(p.click!==null||(p.click=e,!(target instanceof Element)||findInteractiveAncestor(target)!==null||Date.now()-p.at>PRESS_MS||!p.control.isConnected))return target;let id=e.pointerId;if(typeof id=="number"&&id!==-1&&id!==p.id)return target;let x=e.clientX,y=e.clientY;return!inside(p.rect,x,y)&&!inside(p.control.getBoundingClientRect(),x,y)?target:(p.target=p.control,p.control)}function findLooksClickable(target,depth,parent,look2){let el=target;for(let i=0;i<=depth&&el;i++){let f=look2(el);if(f&12)return null;if(f&1){let top=el,up=parent(el);for(;up;i++){let g=look2(up);if(!(g&1))return top;if(g&8||i>=depth)return null;top=up,up=parent(up)}return top}if(f&2)return el;el=parent(el)}return null}function walkUp(target,depth,parent,pred){let el=target;for(let i=0;i<=depth&&el;i++){if(pred(el))return el;el=parent(el)}return null}function leavesDocument(href,page){try{let u=new URL(href,page);if(u.protocol==="javascript:")return!1;if(!href.includes("#"))return!0;let p=new URL(page);return u.hash="",p.hash="",u.href!==p.href}catch{return!0}}function reloadsDocument(href,page){try{let u=new URL(href,page);if(u.protocol==="javascript:")return!1;let p=new URL(page);return u.hash="",p.hash="",u.href===p.href}catch{return!1}}var UNDECIDED=0,FOLLOWED=1,CANCELLED=2,INTERCEPTED=3;function linkFate(dispatching,prevented){return prevented?CANCELLED:dispatching?UNDECIDED:FOLLOWED}function navFate(dispatching,done,inFlight){return dispatching?UNDECIDED:done?FOLLOWED:inFlight?INTERCEPTED:FOLLOWED}function abortMeans(navigationType,userInitiated){return navigationType==="traverse"||userInitiated?"moved-on":"live"}function decideLinks(pending2,fate){for(let i=0;i<pending2.length;i++){let p=pending2[i];if(p?.link===void 0)continue;let f=fate(p);f===FOLLOWED?pending2.splice(i--,1):f!==UNDECIDED&&delete p.link}}function elementKey(ev){return JSON.stringify([ev.element_selector_generalized??null,ev.element_text??null,ev.element_tag??null])}var MAX_REPORTED=100;function claim(seen2,key){return seen2.has(key)||seen2.size>=MAX_REPORTED?!1:(seen2.add(key),!0)}function nextBurst(burst2,key,first,now,windowMs){return burst2?.key===key&&now-burst2.last<=windowMs?(burst2.count++,burst2.last=now,burst2):{key,first,count:1,last:now}}var MAX_CLICK_COUNT=1e3;function rageCount(count,threshold,selected=!1){return count>=Math.max(selected?4:3,threshold)?Math.min(count,MAX_CLICK_COUNT):null}function selectionIsEffect(current,made,touchesMade=!1){return current!==null&&!touchesMade&&!(made&&made.anchor===current.anchor&&made.anchorOffset===current.anchorOffset&&made.focus===current.focus&&made.focusOffset===current.focusOffset)}var MAX_PENDING=8;function openWindow(pending2,seen2,p){if(seen2.every(s=>s.has(p.key))||pending2.length>=MAX_PENDING)return!1;for(let q of pending2)if(q.key===p.key)return!1;return pending2.push(p),!0}var IGNORE=0,LIVE=1,CAUSED=2,SUSPECT=3;function classify(related,noisy2,global=!1,ticking=!1){return noisy2?related?LIVE:IGNORE:related||global?CAUSED:ticking?SUSPECT:LIVE}function isRelated(node,el,depth,parent,contains2){return contains2(el,node)||walkUp(el,depth,parent,n=>n===node)!==null}function inHead(node,head,contains2){return head!==null&&contains2(head,node)}var DRAWS_NOTHING=/^(script|title|meta|base|noscript|template)$/;function drawsNothing(tag,rel){return tag==="link"?!/(^|\s)stylesheet(\s|$)/i.test(rel??""):DRAWS_NOTHING.test(tag)}function unseenMutation(type,target,attribute,added,removed,read){let silent=el=>{let tag=el===null?null:read.tag(el);return el!==null&&tag!==null&&drawsNothing(tag,read.rel(el))};if(type==="attributes"&&attribute==="rel"&&read.tag(target)==="link")return!1;if(type!=="childList")return silent(read.tag(target)===null?read.parent(target):target);for(let nodes of[added,removed])for(let i=0;i<nodes.length;i++){let n=nodes[i];if(read.tag(n)!==null?!silent(n):!read.blank(n)&&!silent(target))return!1}return!0}var NOISE_GAP_MS=6e3,NOISE_REPEAT_MS=250;function noteStreak(s,t){return s?(t-s.last>NOISE_GAP_MS?(s.start=s.beatAt=t,s.since=1/0,s.beats=1):(s.since===1/0&&t-s.start>=NOISE_REPEAT_MS&&(s.since=t),t-s.beatAt>=NOISE_REPEAT_MS&&(s.beats++,s.beatAt=t)),s.last=t,s):{start:t,last:t,since:1/0,beats:1,beatAt:t}}function noisyAt(s,at){return s!==void 0&&s.since<at}function tickingAt(s,at,t){return s!==void 0&&s.start<at&&t-s.last<=NOISE_GAP_MS}var MAX_DOUBTS=8;function addDoubt(doubts,node,t,beats){for(let d of doubts)if(d.node===node)return!0;return doubts.length>=MAX_DOUBTS?!1:(doubts.push({node,t,beats}),!0)}function judgeDoubts(doubts,at,now,streakOf2){let open=!1;for(let d of doubts){let s=streakOf2(d.node);if(!s||s.start>=at||now-s.last>NOISE_GAP_MS)return"live";s.beats-d.beats<2&&(open=!0)}return open?"open":"noise"}var NONE=0,DROP=1,SLOW=2,DOUBT=3;function effectOn(p,kind,t){return kind===IGNORE?NONE:kind===SUSPECT?DOUBT:kind===LIVE||t<p.slowAt||t>=p.due?DROP:SLOW}function effectOnRoute(p,answers,kind,t){return kind===IGNORE?NONE:!answers||kind===SUSPECT?(p.stirred=!0,NONE):effectOn(p,kind,t)===SLOW?SLOW:DROP}function moveOn(pending2,now,el){for(let p of pending2)p.el!==el&&!p.verdict&&(p.due=Math.max(p.slowAt,Math.min(p.due,now)))}var MAX_RESPONSE_MS=6e4;function responseMs(at,t){return Math.min(MAX_RESPONSE_MS,Math.max(0,Math.round(t-at)))}function errorEventCounts(trusted,rejection){return trusted||rejection}var ERROR_NAME=/^[A-Za-z_$][A-Za-z0-9_$.]{0,63}$/;function errorType(err,rejection){if(!(err instanceof Error))return rejection?"UnhandledRejection":"Error";let name;try{name=err.name}catch{return"Error"}return typeof name=="string"&&ERROR_NAME.test(name)?name:"Error"}function ambientError(last,at,windowMs){return last!==void 0&&last<at&&at-last<=windowMs}function takeSettled(pending2,now,final,streakOf2,gone2=()=>!1){let out=[];for(let i=0;i<pending2.length;i++){let p=pending2[i];if(!p)continue;if(!p.verdict){if(p.due>now)continue;if(p.stirred&&(p.appeared||!gone2(p))){pending2.splice(i--,1);continue}p.verdict="dead"}let j=judgeDoubts(p.doubts,p.at,now,streakOf2);if(j==="open"&&!final){p.due<=now&&(p.due=now+NOISE_REPEAT_MS);continue}pending2.splice(i--,1),j==="noise"&&out.push(p)}return out}function nextDue(pending2){let next=1/0;for(let p of pending2)p.due<next&&(next=p.due);return next}function holdsClick(prev,key,now,windowMs,detail){return detail!==0&&prev?.key===key&&now-prev.at<=windowMs}var MAX_HELD=32;function openHeld(held2,el,at,due,live2){if(held2.length>=MAX_HELD)return null;let h={ev:null,el,at,due,live:live2};return held2.push(h),h}function attachHeld(held2,h,ev){let i=held2.indexOf(h);return i<0?!1:h.live?(held2.splice(i,1),!1):(h.ev=ev,!0)}function releaseHeld(held2,did=()=>!0){let out=[];for(let i=0;i<held2.length;i++){let h=held2[i];!h||h.live||!did(h)||(h.ev!==null?(out.push(h.ev),held2.splice(i--,1)):h.live=!0)}return out}function takeDueHeld(held2,now){let out=[];for(let i=0;i<held2.length;i++){let h=held2[i];h&&h.due<=now&&(h.ev!==null&&!h.live&&out.push(h.ev),held2.splice(i--,1))}return out}function heldWaiting(held2){for(let h of held2)if(!h.live)return!0;return!1}var PRESS_MS2=5e3,STALL_MS=250,MAX_ERROR_TYPES=32;function newSeen(){return{dead:new Set,rage:new Set,slow:new Set,error:new Set}}var seen=newSeen(),nav=null,aborted=null,pending=[],burst=null,lastKey="",madeSel=null,madeEl=null,lastClick=null,held=[],holding=null,holdTarget=null,errClick=null,errorTimes=new Map,streaks=new WeakMap,pressTarget=null,pressEl=null,pressAt=0,pressUntil=0,pressEffect=!1,pressDoubts=[],inTask=!1,observer=null,observing=!1,listening=!1,timer,timerDue=0,live=()=>LIVE,caused=()=>CAUSED,notNav=p=>!p.nav;function parentOf(el){return el.parentElement}function nearParent(n){let p=n.parentNode;return p instanceof Element&&p!==document.body&&p!==document.documentElement?p:null}function contains(outer,inner){return outer.contains(inner)}function noisy(node,at){return walkUp(node,config.walk_depth,nearParent,n=>noisyAt(streaks.get(n),at))!==null}function tickingNode(node,at,t){let found=null,n=node;for(let i=0;i<=config.walk_depth&&n;i++)tickingAt(streaks.get(n),at,t)&&(found=n),n=nearParent(n);return found}function streakOf(n){return streaks.get(n)}function note(node,t){let s=streaks.get(node),n=noteStreak(s,t);s||streaks.set(node,n)}function isDisabled(el){return el.matches(":is(button,input,select,textarea):disabled")||el.getAttribute("aria-disabled")==="true"&&isInteractive(el)}function isEditable(el){return el instanceof HTMLInputElement?!/^(button|submit|image)$/.test(el.type):/^(TEXTAREA|SELECT)$/.test(el.tagName)||el.isContentEditable}function look(el){if(/^(canvas|video|audio|input|textarea|select)$/i.test(el.tagName)||isEditable(el))return 4;let s=getComputedStyle(el),f=s.cursor==="pointer"?1:0;return el===document.body||el===document.documentElement?f|8:el.tagName==="IMG"||el.tagName==="svg"||s.textDecorationLine.includes("underline")?f|2:f}function active(){return!pickerActive&&!trackingDisabled&&config.autocapture&&(config.capture_dead_clicks||config.capture_rage_clicks||config.capture_slow_clicks||config.capture_error_clicks)}function rolling(){return active()&&(config.capture_dead_clicks||config.capture_slow_clicks)}function idle(links=!0){return(links?pending.length===0:pending.every(p=>p.link))&&(pressEffect||!pressTarget)&&!heldWaiting(held)}function fateOf(p){let e=p.link;if(!e)return FOLLOWED;let f=linkFate(e.eventPhase!==0,e.defaultPrevented);if(f!==FOLLOWED||!p.nav)return f;let n=navigation();return navFate(p.nav.e.eventPhase!==0,p.nav.done,!!n&&n.transition!==null)}function navigation(){return window.navigation??null}function gone(p){let a=p.route;if(!a)return!1;if(!a.isConnected)return!0;if(a.getClientRects().length>0)return!1;if(getComputedStyle(a).display!=="contents")return!0;for(let i=0;i<a.children.length;i++)if((a.children[i]?.getClientRects().length??0)>0)return!1;return!0}function decide(){decideLinks(pending,fateOf)}function resolve(target){if(target.closest("#st-picker-host"))return null;let control=findInteractiveAncestor(target);if(control)return isSensitive(control)||isEditable(control)?null:control;let t=target;for(;t instanceof SVGElement&&t.ownerSVGElement;)t=t.ownerSVGElement;let el=findLooksClickable(t,config.walk_depth,parentOf,look);return el&&!isSensitive(el)?el:null}function selection(){let s=getSelection();return s&&!s.isCollapsed?{anchor:s.anchorNode,anchorOffset:s.anchorOffset,focus:s.focusNode,focusOffset:s.focusOffset}:null}function selectionEffect(){let s=getSelection();return selectionIsEffect(selection(),madeSel,!!madeEl&&!!s&&s.containsNode(madeEl,!0))}function startTask(){inTask||(inTask=!0,setTimeout(()=>{inTask=!1},0))}function report(el,now,detail,dead,target,doubts,disabled2=!1,click){let ev=createElementEvent(el,"dead_click"),key=elementKey(ev),rage=detail!==0,threshold=config.slow_click_threshold_ms,wait=Math.max(threshold,config.dead_click_wait_ms),multi=detail>=2&&key===lastKey;if(madeSel=multi?selection():null,madeEl=multi?el:null,lastKey=key,holding=null,holdTarget=target,rage&&(holdsClick(lastClick,key,now,config.rage_click_window_ms,detail)&&(holding=openHeld(held,el,now,now+threshold,!dead)),lastClick={key,at:now}),rage&&config.capture_rage_clicks){let next=nextBurst(burst,key,ev,now,config.rage_click_window_ms);next!==burst&&endBurst(),burst=next;let s=getSelection();next.selected=!!s&&!s.isCollapsed&&s.containsNode(el,!0)}errClick=config.capture_error_clicks&&!seen.error.has(key)?{key,ev,el,at:now,due:now+wait}:null;let link=el.closest("a[href]"),href=link?.getAttribute("href")??"",leaves=!!link&&leavesDocument(href,location.href),reloads=leaves&&reloadsDocument(href,location.href),route=leaves&&click&&!reloads?link:null;if((dead||route)&&!(leaves&&(!click||reloads))&&!el.closest("label")){let kinds=[];config.capture_dead_clicks&&kinds.push(seen.dead),config.capture_slow_clicks&&!disabled2&&kinds.push(seen.slow);let slowAt=now+threshold,p={key,ev,el,at:now,slowAt,due:disabled2?slowAt:now+wait,doubts:route?[]:doubts.slice(),seen};route&&(p.link=click,p.route=route,(!dead||doubts.length>0)&&(p.stirred=!0)),openWindow(pending,kinds,p)&&route&&setTimeout(sync,0)}sync()}function endBurst(){let b=burst;burst=null;let n=b&&rageCount(b.count,config.rage_click_threshold,b.selected);b&&n&&claim(seen.rage,b.key)&&enqueue({...b.first,event_type:"rage_click",click_count:n})}function happen(kindAt,suspect,answers,appears=!1){let t=Date.now();decide();let doubt=(doubts,at)=>{let n=suspect?.(at),s=n?streaks.get(n):void 0;return!!n&&!!s&&addDoubt(doubts,n,t,s.beats)};if(pressTarget&&!pressEffect){let k=inTask||!pressEl?LIVE:kindAt(pressEl,pressAt);k!==IGNORE&&!(k===SUSPECT&&doubt(pressDoubts,pressAt))&&(pressEffect=!0)}for(let i=0;i<pending.length;i++){let p=pending[i];if(!p||p.verdict)continue;if(p.route){let k=inTask?LIVE:kindAt(p.el,p.at),r2=effectOnRoute(p,answers?.(p)??!1,k,t);r2===NONE&&appears&&k!==IGNORE&&(p.appeared=!0),r2===SLOW?(p.verdict="slow",p.answeredAt=t):r2!==NONE&&pending.splice(i--,1);continue}let r=effectOn(p,inTask?LIVE:kindAt(p.el,p.at),t);r===NONE||r===DOUBT&&doubt(p.doubts,p.at)||(r===SLOW?(p.verdict="slow",p.answeredAt=t):pending.splice(i--,1))}let did=h=>{let k=inTask?LIVE:kindAt(h.el,h.at);return k!==IGNORE&&k!==SUSPECT};for(let ev of releaseHeld(held,did))enqueue(ev)}function markLive(){idle()||(happen(live),sync())}function markCaused(){idle()||(happen(caused),sync())}function onToggle(e){idle()||(happen(caused,void 0,void 0,e.newState!=="closed"),sync())}function markHistory(changed){changed?idle()||(happen(caused,void 0,notNav),sync()):markLive()}function settle(now,final){decide();for(let p of takeSettled(pending,now,final,streakOf,gone))p.verdict==="slow"?config.capture_slow_clicks&&claim(p.seen.slow,p.key)&&enqueue({...p.ev,event_type:"slow_click",response_ms:responseMs(p.at,p.answeredAt??now)}):config.capture_dead_clicks&&claim(p.seen.dead,p.key)&&enqueue(p.ev)}function movedOn(now,el,errors){if(errors&&errClick&&errClick.el!==el&&(errClick=null),pending.length===0)return;let records=observer?.takeRecords();records?.length&&onMutations(records),selectionEffect()&&happen(live),moveOn(pending,now,el),settle(now,!1)}var reader={tag:n=>n instanceof Element?n.tagName.toLowerCase():null,rel:n=>n instanceof Element?n.getAttribute("rel"):null,blank:n=>n.nodeType===8||!n.textContent?.trim(),parent:n=>n.parentNode};function unseen(r,head){return inHead(r.target,head,contains)&&unseenMutation(r.type,r.target,r.attributeName,r.addedNodes,r.removedNodes,reader)}function adds(r,head){if(r.type!=="childList"||inHead(r.target,head,contains))return!1;for(let i=0;i<r.addedNodes.length;i++){let n=r.addedNodes[i];if(n&&(n instanceof Element||!reader.blank(n)))return!0}return!1}function onMutations(records){let t=Date.now(),head=document.head,shown=records;for(let r of records)if(unseen(r,head)){shown=records.filter(q=>!unseen(q,head));break}for(let r of shown)note(r.target,t);for(let r of shown){if(idle())break;let node=r.target,dialog=r.attributeName==="open"&&/^(DIALOG|DETAILS)$/.test(node.nodeName);happen(dialog?caused:(el,at)=>classify(isRelated(node,el,config.walk_depth,nearParent,contains),noisy(node,at),!1,tickingNode(node,at,t)!==null),at=>tickingNode(node,at,t),p=>!!p.route&&p.route.contains(node)&&(node!==p.route||r.type!=="attributes"||r.attributeName==="aria-busy"),dialog&&node instanceof Element&&node.hasAttribute("open")||adds(r,head))}sync()}function holdClick(e,ev){let h=holding;return holding=null,!!h&&e.isTrusted&&e.detail!==0&&clickTarget(e)===holdTarget&&attachHeld(held,h,ev)}function onFocusIn(e){let f=e.target;pressTarget&&f instanceof Node&&f.contains(pressTarget)||markCaused()}function onScroll(e){let node=e.target instanceof Node?e.target:document;note(node,Date.now()),!idle()&&(happen((el,at)=>classify(node.contains(el),noisyAt(streaks.get(node),at),!0)),sync())}function onKeyDown(e){if(!e.isTrusted||pending.length===0&&!errClick)return;let t=e.target;movedOn(Date.now(),(e.key==="Enter"||e.key===" ")&&t instanceof Element?t:null,!0),sync()}function onWheel(e){!e.isTrusted||pending.length===0||(movedOn(Date.now(),null,!1),sync())}function onLeave(){movedOn(Date.now(),null,!1),markLive(),sync()}function onNavigate(e){let prev=aborted??(nav&&!nav.done?nav:null);if(aborted=null,prev){prev.done=!0;let ne=e;abortMeans(ne.navigationType??"",ne.userInitiated===!0)==="moved-on"?navMovedOn(prev):navAnswer(prev,!1)}let n={e,done:!1};nav=n;for(let p of pending){let c=p.link;c&&c.eventPhase===0&&!c.defaultPrevented&&!p.nav&&(p.nav=n)}}function navDone(ok){let n=nav;!n||n.done||(n.done=!0,navAnswer(n,ok))}function onNavigateError(e){let n=nav,err=e.error;if(!(typeof err=="object"&&err!==null&&err.name==="AbortError")||!n||n.done){navDone(!1);return}n.done=!0,aborted=n,setTimeout(()=>{aborted===n&&(aborted=null,navAnswer(n,!1))},0)}function navMovedOn(n){decide();let now=Date.now();for(let p of pending)p.nav===n&&!p.link&&!p.verdict&&(p.due=Math.max(p.slowAt,Math.min(p.due,now)));sync()}function navAnswer(n,ok){if(pending.length===0)return;let t=Date.now();decide();for(let i=0;i<pending.length;i++){let p=pending[i];if(!p||p.nav!==n||p.link||p.verdict)continue;let r=effectOnRoute(p,!0,ok?CAUSED:LIVE,t);r===SLOW?(p.verdict="slow",p.answeredAt=t):r!==NONE&&pending.splice(i--,1)}sync()}function noteError(type){let now=Date.now(),last=errorTimes.get(type);last===void 0&&errorTimes.size>=MAX_ERROR_TYPES&&errorTimes.clear(),errorTimes.set(type,now);let c=errClick;!c||now>c.due||!active()||!config.capture_error_clicks||!inTask&&ambientError(last,c.at,NOISE_GAP_MS)||(errClick=null,claim(seen.error,c.key)&&enqueue({...c.ev,event_type:"error_click",error_type:type}))}function onError(e){e instanceof ErrorEvent&&errorEventCounts(e.isTrusted,!1)&&noteError(errorType(e.error,!1))}function onRejection(e){errorEventCounts(e.isTrusted,!0)&&noteError(errorType(e.reason,!0))}function sync(){settle(Date.now(),!1);let watch=!idle(),observe=watch||rolling();observe!==observing&&(observe?(typeof MutationObserver=="function"&&(observer??=new MutationObserver(onMutations)),observer?.observe(document,{subtree:!0,childList:!0,attributes:!0,characterData:!0})):observer?.disconnect(),observing=observe);let leave=watch&&!idle(!1);leave!==listening&&(listening=leave,leave?window.addEventListener("beforeunload",onLeave):window.removeEventListener("beforeunload",onLeave));let next=Math.min(nextDue(pending),nextDue(held),burst?burst.last+config.rage_click_window_ms+1:1/0,pressTarget?pressUntil:1/0);clearTimeout(timer),timer=void 0,next<1/0&&(timerDue=next,timer=setTimeout(tick,Math.max(0,next-Date.now())))}function tick(){let now=Date.now(),late=now-timerDue>STALL_MS,records=observer?.takeRecords();records?.length&&onMutations(records),(late||selectionEffect())&&happen(live),settle(now,!1);for(let ev of takeDueHeld(held,now))active()||enqueue(ev);burst&&now-burst.last>config.rage_click_window_ms&&endBurst(),pressTarget&&now>=pressUntil&&(pressTarget=pressEl=null),sync()}function onPointerDown(e){if(!e.isTrusted)return;let target=e.target,now=Date.now();if(!(target instanceof Element)||e.button!==0||!active()){movedOn(now,null,!0),sync();return}let disabled2=walkUp(target,config.walk_depth,parentOf,isDisabled),el=disabled2??resolve(target);if(movedOn(now,el,!0),startTask(),holding=null,pressTarget=target,pressEffect=!1,pressDoubts=[],pressAt=now,pressUntil=now+PRESS_MS2,disabled2){pressEl=null,pressEffect=!0,!isSensitive(disabled2)&&!target.closest("#st-picker-host")?report(disabled2,now,1,!0,target,[],!0):sync();return}pressEl=el,pressEl||(pressTarget=null),sync()}function onClick(e){if(!e.isTrusted||!active())return;let target=clickTarget(e);if(!(target instanceof Element))return;startTask();let press2=pressTarget,effect=press2!==null&&pressEffect,el=press2===target?pressEl:resolve(target);pressTarget=pressEl=null;let multi=e.detail>=2;el&&!walkUp(target,config.walk_depth,parentOf,isDisabled)&&(isInteractive(el)||multi||getSelection()?.isCollapsed!==!1)?report(el,Date.now(),e.detail,!effect,target,press2?pressDoubts:[],!1,e):sync()}function onFrustrationHide(){onLeave(),settle(Date.now(),!0),endBurst()}function resetFrustration(){let own=nav&&!nav.done?nav:null,waiting=own?pending.filter(p=>p.nav===own):[];for(let p of waiting)pending.splice(pending.indexOf(p),1);movedOn(Date.now(),null,!1),markLive();for(let p of waiting)pending.push(p);waiting.length>0&&sync(),endBurst(),lastClick=null,lastKey="",madeSel=null,madeEl=null,seen=newSeen()}function startFrustration(){let capture={capture:!0,passive:!0};window.addEventListener("pointerdown",onPointerDown,capture),window.addEventListener("click",onClick,capture),window.addEventListener("focusin",onFocusIn,capture),window.addEventListener("keydown",onKeyDown,capture),window.addEventListener("wheel",onWheel,capture),window.addEventListener("error",onError),window.addEventListener("unhandledrejection",onRejection);for(let type of["blur","pagehide","popstate","hashchange"])window.addEventListener(type,onLeave);let n=navigation();n&&(n.addEventListener("navigate",onNavigate),n.addEventListener("navigatesuccess",()=>{navDone(!0)}),n.addEventListener("navigateerror",onNavigateError)),document.addEventListener("scroll",onScroll,capture),document.addEventListener("toggle",onToggle,capture);for(let type of["submit","invalid","change","play","copy"])document.addEventListener(type,markLive,capture);sync()}function onClickCapture(e){if(pickerActive)return;let target=clickTarget(e);if(!(target instanceof Element))return;let interactive=findInteractiveAncestor(target);if(!interactive||isSensitive(interactive))return;let ev=createClickEvent(interactive);holdClick(e,ev)||enqueue(ev)}function onSubmitCapture(e){if(pickerActive)return;let form=e.target;form instanceof HTMLFormElement&&(isSensitive(form)||!config.autocapture&&!extractDataTrack(form).dataTrack||enqueue(createFormSubmitEvent(form)))}function onSpaNavigation(){if(pickerActive){onPickerNavigation();return}if(!config.autocapture_pageviews)return;let currentUrl=window.location.href;if(currentUrl===lastUrl)return;let referrer=lastUrl;setLastUrl(currentUrl),resetFrustration(),enqueue(createPageviewEvent(referrer))}var inHistoryCall=!1;function patchHistory(){let origPushState=history.pushState.bind(history),origReplaceState=history.replaceState.bind(history);history.pushState=function(...args){let before=location.href;inHistoryCall=!0;try{origPushState(...args)}finally{inHistoryCall=!1}markHistory(location.href!==before),onSpaNavigation()},history.replaceState=function(...args){let before=location.href;inHistoryCall=!0;try{origReplaceState(...args)}finally{inHistoryCall=!1}markHistory(location.href!==before),onSpaNavigation()}}function listenNavigation(){window.navigation?.addEventListener("currententrychange",()=>{inHistoryCall||onSpaNavigation()})}var version="1.1.1",initialized=!1,storedInitConfig={};function init(initConfig){if(typeof window>"u")return;if(initialized){console.warn("[tracker] Already initialized. Ignoring duplicate init() call.");return}if(initialized=!0,storedInitConfig=initConfig??{},storedInitConfig.apiKey!==void 0&&setApiKey(storedInitConfig.apiKey),storedInitConfig.apiHost!==void 0&&setApiBase(storedInitConfig.apiHost),!apiKey){console.error("[tracker] Missing apiKey. Events will not be sent.");return}applyInitConfig(storedInitConfig),setLastUrl(window.location.href),config.autocapture_pageviews&&enqueue(createPageviewEvent(document.referrer)),patchHistory(),window.addEventListener("popstate",onSpaNavigation),listenNavigation(),window.addEventListener("pointerdown",notePress,{capture:!0,passive:!0}),document.addEventListener("click",onClickCapture,{capture:!0}),document.addEventListener("submit",onSubmitCapture,{capture:!0}),startFrustration(),window.addEventListener("message",onPickerWindowMessage);let pickerHash=parsePickerHash(window.location.hash);if(pickerHash){let{origin,mode}=pickerHash;setPickerDashboardOrigin(origin);let clickMap=mode==="click-map";try{sessionStorage.setItem("st-picker",JSON.stringify({active:!0,origin,...clickMap&&{mode}}))}catch{}history.replaceState(null,"",window.location.pathname+window.location.search),window.opener&&requestPicker(clickMap?{mode}:void 0)}else try{let stored=sessionStorage.getItem("st-picker");if(stored&&window.opener){let parsed=JSON.parse(stored);if(typeof parsed=="object"&&parsed!==null&&"active"in parsed&&"origin"in parsed){let obj=parsed;if(obj.active&&typeof obj.origin=="string"){setPickerDashboardOrigin(obj.origin);let storedScan="scanEnabled"in obj&&obj.scanEnabled===!0,storedNav="navigateMode"in obj&&obj.navigateMode===!0,rawMode="selectorMode"in obj?obj.selectorMode:null;requestPicker({scanEnabled:storedScan,navigateMode:storedNav,selectorMode:rawMode==="generic"||rawMode==="exact"?rawMode:null,..."mode"in obj&&obj.mode==="click-map"&&{mode:"click-map"}})}}}}catch{}document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&(onFrustrationHide(),flushBeacon())}),fetchConfig().then(result=>{if(!result.loaded){failClosed(result);return}applyInitConfig(storedInitConfig),!trackingDisabled&&(setConfigLoaded(!0),setInterval(()=>{flush()},config.flush_interval_ms),flush())})}function failClosed(result){console.warn(`[tracker] Could not load this project's settings from ${apiBase}/api/config \u2014 ${describeConfigFailure(result)}. No events were sent from this page load: without those settings the tracker cannot apply the project's URL redaction rules, and sending unredacted URLs would be worse than sending nothing.`),disableTracking(queue)}function track(name,attrs){typeof window>"u"||trackingDisabled||name&&enqueue({event_type:"custom",page_url:window.location.href,data_track:name,data_track_attrs:attrs,timestamp:new Date().toISOString()})}function setTraits2(newTraits){typeof window>"u"||setTraits(newTraits)}export{init,setTraits2 as setTraits,track,version};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@privatrak/tracker",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "description": "Cookieless, privacy-first analytics tracker: auto-captures pageviews, clicks and form submissions with no cookies, no localStorage, no PII and no consent banner.",
5
5
  "keywords": [
6
6
  "analytics",