@privatrak/tracker 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -12,6 +12,106 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
12
12
 
13
13
  ## [Unreleased]
14
14
 
15
+ ## [1.1.0] - 2026-10-03
16
+
17
+ ### Added
18
+
19
+ - Dead and rage clicks, recorded as two new event types. A `dead_click` is a
20
+ click after which nothing happened on the page within about five seconds, or
21
+ before the visitor turned to something else after more than a second — on a
22
+ real control, on a disabled button, or on something that only looks
23
+ clickable, such as a `cursor: pointer` element, an image, an icon or
24
+ underlined text. A `rage_click` is three or more quick clicks on one element,
25
+ sent once when the burst ends with its size in `click_count`. Both carry the
26
+ same element fields as a click, and each is sent at most once per element per
27
+ page view. A control still sends its `click`, and an element that only looks
28
+ clickable still sends none. The one change to ordinary clicks is during a
29
+ quick burst on one control: the first click is sent at once as always, but
30
+ each quick repeat is sent only if something happened after it, so a broken
31
+ button clicked ten times sends one `click` instead of ten, while a button
32
+ that reacts to every click still sends every one. Repeat clicks still waiting
33
+ when the page is left or navigates are sent with it, never lost. Both are part of
34
+ click autocapture, so `autocapture: false` turns them off too, and each can be
35
+ switched off in your project's settings. They store nothing: no cookies, no
36
+ `localStorage`, no `sessionStorage`; everything is kept in memory for the
37
+ current page view and forgotten with it.
38
+
39
+ - Slow clicks and error clicks, recorded as two more event types beside dead
40
+ and rage clicks. A `slow_click` is a click that did work, but whose first
41
+ visible effect came more than a second after it, while the visitor was still
42
+ waiting; `response_ms` says how long it took. Only effects the click
43
+ plausibly caused count (a change to the clicked element or right around it,
44
+ a URL change, focus moving, a scroll, a dialog or popover opening), and parts
45
+ of the page that were already changing on their own, such as a carousel or a
46
+ clock, are discounted, so a busy page does not turn dead clicks into slow
47
+ ones. An `error_click` is a click followed by an uncaught error or an
48
+ unhandled promise rejection; `error_type` carries only the error's class
49
+ name, such as `TypeError`, never its message, stack or source location. Both
50
+ carry the same element fields as a click, are sent at most once per element
51
+ per page view, and can each be switched off in your project's settings.
52
+
53
+ - Support for the click map, which you open from the dashboard to see how often
54
+ each element of a page was clicked, drawn over your live site. Only you see
55
+ it, in the tab the dashboard opens, and nothing you click there is recorded.
56
+ Visitors download none of it. Older versions of the package can still open the
57
+ click map, but on pages whose addresses contain IDs (such as
58
+ `/orders/1234`) or parts you have set to be redacted, it may find no clicks,
59
+ because those versions cannot tell it how your events record the address.
60
+
61
+ ### Changed
62
+
63
+ - The Element Picker's match modes are now labelled **Exact element** and
64
+ **All similar** (formerly Exact and Generic). What each one saves is unchanged.
65
+
66
+ ### Fixed
67
+
68
+ - The Element Picker no longer shifts element positions while it is open. Its
69
+ panel used to sit inside `<body>`, so on a page with a `<div>` directly under
70
+ `<body>` every exact selector it showed or saved carried a wrong
71
+ `:nth-of-type`. Clicks recorded from visitors were never affected.
72
+ - The Element Picker now works on pages with slide-out drawers and pop-up
73
+ dialogs, such as the cart and login drawers of Shopify themes like Prestige.
74
+ Before, such a drawer could open underneath the picker's side panel, and
75
+ clicking anything in the panel closed it again, so its buttons and fields could
76
+ not be turned into feature rules. Drawers now open beside the panel, stay open
77
+ while you use it, and you can type in the panel's fields while one is open.
78
+ - Closing the Element Picker now leaves the page's markup exactly as it was,
79
+ without an empty `style` attribute on the elements the picker had moved aside.
80
+ - In single-page apps, a click or form submit that moves to another page is now
81
+ recorded on the page where it happened. Before, a click on a router link (a
82
+ React Router, Next.js or Vue `<Link>`, or any link whose handler calls
83
+ `history.pushState`) was counted on the page it led to, and arrived after that
84
+ page's page view. That moved clicks off the page they belong to in per-page
85
+ counts, user paths and funnels built on clicks. A search form that changes the
86
+ route on submit had the same problem. The recorded element, its text and its
87
+ selector are now also read as they were when it was clicked, before the page
88
+ re-rendered it.
89
+ - Clicks and submits are now recorded even when the page's own code stops the
90
+ event from propagating, as many dropdowns, menus and modal dialogs do. These
91
+ were missed before, so click counts for such elements will rise after you
92
+ update, without any change in how people use your site.
93
+ - The Element Picker's **Show rules and tags on this page** no longer outlines
94
+ elements nobody can see. Buttons and forms inside a closed drawer or dialog
95
+ used to get boxes and labels drawn over whatever was open on top of them.
96
+ Their outlines now appear when the drawer opens and go away again when it
97
+ closes. The list in the panel still counts them, since they are on the page.
98
+
99
+ ### Removed
100
+
101
+ - The sampling rate is gone, along with the `samplingRate` option of `init()`
102
+ and the `data-sampling-rate` script attribute. Both are now ignored. It did
103
+ not do what its name promised: because the tracker keeps nothing in the
104
+ browser, the dice were rolled once per page load rather than once per visitor,
105
+ so a visitor was recorded as soon as any one of their page loads survived.
106
+ Someone who left after a single page was usually dropped while someone who
107
+ read ten pages was almost always kept, which quietly bent the numbers towards
108
+ your most engaged visitors. Journeys arrived with pages missing from the
109
+ middle, so funnels showed routes nobody had taken, and a visitor whose landing
110
+ page was dropped was credited to your own site instead of the search or
111
+ campaign they really came from. Nothing scaled the totals back up either, so
112
+ the counts were simply low. A sampling feature that behaves per visitor will
113
+ come back later; until then every event is recorded.
114
+
15
115
  ## [1.0.0] - 2026-09-21
16
116
 
17
117
  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
@@ -176,12 +173,74 @@ Fields: `page_url`, `referrer`, `timestamp`.
176
173
  **Click** — on anything in `autocaptureElements`, or anything carrying
177
174
  `data-track`, via a single delegated listener on `document`. A click on a
178
175
  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`.
176
+ Fields: `element_tag`, `element_selector`, `element_selector_generalized`,
177
+ `element_text` (truncated), `data_track`, `data_track_attrs`, plus `page_url`
178
+ and `timestamp`.
181
179
 
182
180
  **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`.
181
+ values are never read. Fields: `element_tag`, `element_selector`,
182
+ `element_selector_generalized`, `data_track`, `data_track_attrs`, `page_url`,
183
+ `timestamp`.
184
+
185
+ The click and form submit listeners run in the capture phase, before any of the
186
+ page's own handlers. So a click on a router link is recorded on the page it was
187
+ made on, ahead of the pageview for the page it leads to, and a handler that
188
+ calls `stopPropagation()` does not hide the click. The tracker never cancels or
189
+ stops an event itself.
190
+
191
+ **Dead click** (`dead_click`) — a click after which nothing happened within
192
+ about five seconds: no DOM change, no navigation or URL change, no scroll, no
193
+ focus moving elsewhere, no dialog, popover or new tab. It is recorded for real
194
+ controls (which still send their ordinary click as well), for disabled buttons
195
+ and fields, and for elements that only look clickable — a `cursor: pointer`
196
+ element, an image, an icon, underlined text — which never send a click of their
197
+ own. Clicks on plain text or empty space, clicks that select text, and clicks
198
+ on canvas, video, audio, form fields and editable content are ignored, and
199
+ anything ambiguous counts as something having happened. Links to other pages
200
+ are never dead. If the visitor clicks or types somewhere else, scrolls, or
201
+ leaves the page before the five seconds are up, the click is decided there and
202
+ then: dead if nothing had happened for over a second, and otherwise not. Parts
203
+ of the page that were already changing on their own before the click, such as
204
+ a carousel, a clock or a list that refreshes itself, are not taken for the
205
+ click's effect. Same fields as a click.
206
+
207
+ **Slow click** (`slow_click`) — a click that did work, but whose first visible
208
+ effect came more than a second after it, while the visitor was still waiting.
209
+ Only effects the click plausibly caused count: a change to the clicked element,
210
+ to something inside it or close around it, a URL change, focus moving, a
211
+ scroll, or a dialog or popover opening. Anything else changing on the page
212
+ makes the click neither slow nor dead. `response_ms` is the time from the click
213
+ to that first effect. Same fields as a click.
214
+
215
+ **Error click** (`error_click`) — a click followed, within the same five
216
+ seconds, by an uncaught JavaScript error or an unhandled promise rejection.
217
+ `error_type` is the error's class name — `TypeError`, `ReferenceError`, your
218
+ own error class's `name` — or `UnhandledRejection` for a rejection that is not
219
+ an `Error`, and `Error` for anything else, including the cross-origin
220
+ "Script error.". The error's message, stack, file and line are never read,
221
+ since they can contain what the visitor typed. With two clicks in flight, the
222
+ error goes to the more recent one. A click that throws usually changes nothing
223
+ on the page, so it is often a dead click as well. Same fields as a click.
224
+
225
+ **Rage click** (`rage_click`) — three or more clicks on one element, each within
226
+ a second of the one before. One event when the burst ends, with the burst's
227
+ size in `click_count` and the time of its first click. Same fields as a click.
228
+
229
+ During a burst, the repeat clicks themselves are only sent when they did
230
+ something. The first click on a control is sent at once, as always; a further
231
+ click on the same control within a second of the one before waits up to a
232
+ second, and is sent as an ordinary click, with its own time, if anything
233
+ happened after it — or dropped if nothing did. So a broken button clicked ten
234
+ times sends one click, one dead click and one rage click of 10, while a
235
+ stepper that changes on every click still sends all of its clicks. Leaving the
236
+ page, or navigating to another view, counts as something happening: waiting
237
+ clicks are sent with it. Keyboard activations are never held, and with dead,
238
+ rage, slow and error clicks all switched off, every click is sent at once.
239
+
240
+ Dead, rage, slow and error clicks are part of click autocapture: they need
241
+ `autocapture` on, and each can be switched off in your project's settings.
242
+ Each is recorded at most once per element per page view. Working them out needs no storage: all of
243
+ it is in memory, for the current page view only.
185
244
 
186
245
  **Custom** — whatever you pass to `track()`.
187
246
 
@@ -192,8 +251,8 @@ are skipped outright, so they are never even click targets.
192
251
 
193
252
  Events are queued in memory and sent in batches to `POST /api/events` — every 5
194
253
  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
254
+ Every send uses `fetch()` with `keepalive`, the tab-hidden one included, so it
255
+ survives the page going away and can still carry the API key header. A failed batch is
197
256
  re-queued for the next cycle, and the queue is capped at 100 events so a broken
198
257
  connection cannot grow it without bound.
199
258
 
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}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){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)??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||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()))}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}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}}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 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}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 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){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;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_MS=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(),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;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(){return pending.length===0&&(pressEffect||!pressTarget)&&!heldWaiting(held)}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,disabled=!1){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]");if(dead&&!(link&&leavesDocument(link.getAttribute("href")??"",location.href))&&!el.closest("label")){let kinds=[];config.capture_dead_clicks&&kinds.push(seen.dead),config.capture_slow_clicks&&!disabled&&kinds.push(seen.slow);let slowAt=now+threshold;openWindow(pending,kinds,{key,ev,el,at:now,slowAt,due:disabled?slowAt:now+wait,doubts:doubts.slice(),seen})}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){let t=Date.now(),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;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 markHistory(changed){changed?markCaused():markLive()}function settle(now,final){for(let p of takeSettled(pending,now,final,streakOf))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)}function onMutations(records){let t=Date.now();for(let r of records)note(r.target,t);for(let r of records){if(idle())break;let node=r.target;happen(r.attributeName==="open"&&/^(DIALOG|DETAILS)$/.test(node.nodeName)?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))}sync()}function holdClick(e,ev){let h=holding;return holding=null,!!h&&e.isTrusted&&e.detail!==0&&e.target===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 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),watch!==listening&&(listening=watch,watch?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 disabled=walkUp(target,config.walk_depth,parentOf,isDisabled),el=disabled??resolve(target);if(movedOn(now,el,!0),startTask(),holding=null,pressTarget=target,pressEffect=!1,pressDoubts=[],pressAt=now,pressUntil=now+PRESS_MS,disabled){pressEl=null,pressEffect=!0,!isSensitive(disabled)&&!target.closest("#st-picker-host")?report(disabled,now,1,!0,target,[],!0):sync();return}pressEl=el,pressEl||(pressTarget=null),sync()}function onClick(e){if(!e.isTrusted||!active())return;let target=e.target;if(!(target instanceof Element))return;startTask();let press=pressTarget,effect=press!==null&&pressEffect,el=press===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,press?pressDoubts:[]):sync()}function onFrustrationHide(){onLeave(),settle(Date.now(),!0),endBurst()}function resetFrustration(){movedOn(Date.now(),null,!1),markLive(),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);document.addEventListener("scroll",onScroll,capture),document.addEventListener("toggle",markCaused,capture);for(let type of["submit","invalid","change","play","copy"])document.addEventListener(type,markLive,capture);sync()}function onClickCapture(e){if(pickerActive)return;let target=e.target;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))}function patchHistory(){let origPushState=history.pushState.bind(history),origReplaceState=history.replaceState.bind(history);history.pushState=function(...args){let before=location.href;origPushState(...args),markHistory(location.href!==before),onSpaNavigation()},history.replaceState=function(...args){let before=location.href;origReplaceState(...args),markHistory(location.href!==before),onSpaNavigation()}}var version="1.1.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,{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.0",
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",