userlens-analytics-sdk 0.1.89 → 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/README.md CHANGED
@@ -1,487 +1,134 @@
1
- # 📊 userlens-analytics-sdk
2
-
3
- Powerful and lightweight event tracking + session replay SDK for web apps. Works standalone or with React. Built for modern frontend teams.
4
-
5
- ---
6
-
7
- ## 📚 Table of Contents
8
- - [📘 Introduction](#-introduction)
9
- - [📦 Installation](#installation)
10
- - [⚡ Quickstart](#quickstart)
11
- - [🧠 SDK Overview](#-sdk-overview)
12
- - [✍️ EventCollector — Two Modes](#️-eventcollector--two-modes)
13
- - [1. Manual Upload Mode (RECOMMENDED)](#1-manual-upload-mode-recommended)
14
- - [2. Auto-Upload Mode](#2-auto-upload-mode)
15
- - [🎥 SessionRecorder](#-sessionrecorder)
16
- - [⚛️ React Wrapper](#react-wrapper)
17
- - [✅ What It Does](#️-what-it-does)
18
- - [🛠 Usage Example](#-usage-example)
19
- - [🔁 Behavior Details](#-behavior-details)
20
- - [📌 Tracking Custom Events](#️-tracking-custom-events)
21
- - [✍️ Example](#️-example)
22
- - [🧠 How it works](#-how-it-works)
23
- - [🛰 API Documentation](#-api-documentation)
24
- - [🔐 Authentication](#-authentication)
25
- - [🧭 Endpoint](#-endpoint)
26
- - [1. Identify](#1-identify)
27
- - [2. Group](#2-group)
28
- - [3. Track](#3-track)
29
- - [🔄 Sending Raw Events (from EventCollector)](#-sending-raw-events-from-eventcollector)
30
-
31
-
32
- ## 📘 Introduction
33
-
34
- `userlens-analytics-sdk` is a lightweight, framework-agnostic JavaScript SDK for collecting user interaction events and recording session replays directly in the browser.
35
-
36
- It supports two main features:
37
-
38
- - 🔍 **Event tracking** — Capture clicks and page views, complete with DOM snapshots and context. You can also push your own custom events manually.
39
- - 🎥 **Session replay** — Record full user sessions.
1
+ # Userlens Analytics SDK
40
2
 
41
- ## Installation
42
-
43
- ```bash
44
- npm install userlens-analytics-sdk
45
- ```
46
-
47
- ## Quickstart
48
-
49
- ### 🧠 SDK Overview
50
-
51
- There are **two layers** to this SDK:
3
+ **Track everything. Decide what matters later.**
52
4
 
53
- 1. **EventCollector** Tracks user interactions like clicks and page views.
54
- 2. **SessionRecorder** – Captures full user session replays.
5
+ 📖 **[View Full Documentation](https://userlens.gitbook.io/userlens-analytics/)**
55
6
 
56
- Both can be used:
7
+ The Userlens SDK automatically captures every user interaction in your web app. Define events later in the [Userlens platform](https://app.userlens.io)—no code changes required.
57
8
 
58
- - With the React provider (recommended for React apps)
59
- - Manually via class instances (non-React or custom setups)
9
+ ## Why Userlens?
60
10
 
61
- ---
11
+ | Traditional Analytics | Userlens |
12
+ |-----------------------|----------|
13
+ | Add `track()` calls for each event | Install once, capture everything |
14
+ | Deploy code to track new events | Create events in the UI instantly |
15
+ | Forgot to track something? Start over | Backfill historical data automatically |
62
16
 
63
- ### ✍️ EventCollector — Two Modes
64
-
65
- There are **two ways to configure** `EventCollector`:
66
-
67
- #### 1. Manual Upload Mode (RECOMMENDED)
68
-
69
- This is the most flexible and production-safe setup. You **receive events via a callback** and forward them through your own backend to the Userlens API.
17
+ ## Installation
70
18
 
71
- ```ts
72
- const collector = new EventCollector({
73
- callback: (events) => {
74
- // send events to your backend
75
- fetch("/api/forward-events", {
76
- method: "POST",
77
- body: JSON.stringify(events),
78
- });
79
- },
80
- intervalTime: 5000, // optional
81
- });
19
+ ```bash
20
+ npm install userlens-analytics-sdk
82
21
  ```
83
22
 
84
- If you're using **manual upload mode**, you're responsible for sending the collected events to the Userlens API. Here's how to do that properly in a Node.js/Express setup.
23
+ ## Quick Start (React)
85
24
 
86
- ```ts
87
- // server.js or routes/track.js
88
-
89
- import express from "express";
90
- import fetch from "node-fetch"; // or global fetch in newer Node versions
91
-
92
- const router = express.Router();
93
-
94
- /**
95
- * Your WRITE_CODE — retrieve it from:
96
- * 👉 https://app.userlens.io/settings/userlens-sdk
97
- */
98
- const WRITE_CODE = process.env.USERLENS_WRITE_CODE!;
99
-
100
- const MAIN_BASE_URL = "https://events.userlens.io";
101
- const RAW_BASE_URL = "https://raw.userlens.io";
102
-
103
- // Step 1: optional user traits sync
104
- async function identify(userId, traits) {
105
- if (!userId || !traits) return;
106
-
107
- const body = {
108
- type: "identify",
109
- userId,
110
- source: "userlens-js-analytics-sdk",
111
- traits,
112
- };
113
-
114
- const res = await fetch(`${MAIN_BASE_URL}/event`, {
115
- method: "POST",
116
- headers: {
117
- "Content-Type": "application/json",
118
- Authorization: `Basic ${WRITE_CODE}`,
119
- },
120
- body: JSON.stringify(body),
121
- });
122
-
123
- if (!res.ok) throw new Error("Failed to identify user");
124
- }
125
-
126
- // Step 2: send the events array
127
- async function track(events) {
128
- const body = { events };
129
-
130
- const res = await fetch(`${RAW_BASE_URL}/raw/event`, {
131
- method: "POST",
132
- headers: {
133
- "Content-Type": "application/json",
134
- Authorization: `Basic ${WRITE_CODE}`,
135
- },
136
- body: JSON.stringify(body),
137
- });
138
-
139
- if (!res.ok) throw new Error("Failed to track events");
25
+ ```tsx
26
+ import UserlensProvider from 'userlens-analytics-sdk/react';
27
+
28
+ function App() {
29
+ const config = useMemo(() => ({
30
+ userId: currentUser.id,
31
+ userTraits: { email: currentUser.email, plan: currentUser.plan },
32
+ WRITE_CODE: 'your-write-code', // From app.userlens.io/settings
33
+ }), [currentUser.id]);
34
+
35
+ return (
36
+ <UserlensProvider config={config}>
37
+ <YourApp />
38
+ </UserlensProvider>
39
+ );
140
40
  }
141
-
142
- // Your actual POST endpoint
143
- router.post("/forward-events", async (req, res) => {
144
- const events = req.body;
145
- if (!Array.isArray(events)) return res.status(400).send("Invalid body");
146
-
147
- try {
148
- const first = events[0];
149
-
150
- // Optional: keep traits in sync
151
- if (first?.userId && first?.properties) {
152
- await identify(first.userId, first.properties);
153
- }
154
-
155
- await track(events);
156
- res.status(200).send("ok");
157
- } catch (err) {
158
- console.error("Userlens forwarding error:", err);
159
- res.status(500).send("Tracking failed");
160
- }
161
- });
162
-
163
- export default router;
164
- ```
165
-
166
- <!-- MENTION HERE HOW TO RECEIVE THE EVENTS AND FORWARD THEM TO UL API -->
167
-
168
- ✅ Pros:
169
-
170
- - Works around adblockers
171
- - You can batch, modify, or encrypt events
172
-
173
- #### 2. Auto-Upload Mode
174
-
175
- This mode sends events directly to the Userlens API from the frontend.
176
-
177
- ```ts
178
- const collector = new EventCollector({
179
- userId: "user-123", // ✅ required
180
- WRITE_CODE: "your-public-write-code", // ✅ required
181
- userTraits: { plan: "starter" }, // ✅ required, pass as many traits as you can so Userlens provides better insights
182
- groupId: "group-123", // passed to identify a group (e.g. company that the user belongs to)
183
- groupTraits: {
184
- domain: "userlens.io",
185
- title: "Userlens",
186
- industry: "Software"
187
- }, // traits that exist on your group object (e.g. company workspace traits)
188
- intervalTime: 5000, // optional
189
- });
190
41
  ```
191
42
 
192
- Pros:
43
+ That's it. Every click and page view is now captured.
193
44
 
194
- - Easy to set up
45
+ ## Documentation
195
46
 
196
- ⚠️ Cons:
47
+ | Guide | Description |
48
+ |-------|-------------|
49
+ | [Introduction](./docs/introduction.md) | Overview and how it works |
50
+ | [React Setup](./docs/react.md) | Complete React integration guide |
51
+ | [Next.js Setup](./docs/nextjs.md) | Next.js with SSR considerations |
52
+ | [Custom Events](./docs/custom-events.md) | Track specific actions manually |
53
+ | [API Reference](./docs/api-reference.md) | HTTP API documentation |
54
+ | [Troubleshooting](./docs/troubleshooting.md) | Common issues and solutions |
197
55
 
198
- - May be blocked by adblockers
199
- - You lose control over event delivery
56
+ ### Backend Proxy Guides
200
57
 
201
- ℹ️ Use this only if you’re okay with events being sent directly from the browser.
58
+ For production apps, we recommend the proxy setup (keeps your API key secure, avoids ad blockers):
202
59
 
203
- ---
60
+ | Backend | Guide |
61
+ |---------|-------|
62
+ | Node.js/Express | [Setup Guide](./docs/proxy-nodejs.md) |
63
+ | Python (Flask/Django) | [Setup Guide](./docs/proxy-python.md) |
64
+ | Ruby on Rails | [Setup Guide](./docs/proxy-rails.md) |
204
65
 
205
- ### 🎥 SessionRecorder
66
+ ## Two Setup Options
206
67
 
207
- SessionRecorder captures full user sessions. It's enabled by default if WRITE_CODE and userId are passed in config when using the React provider. Alternatively, you can instantiate it manually.
68
+ ### Option A: Client-Side Setup (Quick to setup)
208
69
 
209
- It always requires:
70
+ Events go directly to Userlens API.
210
71
 
211
- - userId
212
- - WRITE_CODE
213
-
214
- ```ts
215
- const recorder = new SessionRecorder({
216
- userId: "user-123",
217
- WRITE_CODE: "your-public-write-code",
218
- recordingOptions: {
219
- maskingOptions: ["passwords"],
220
- BUFFER_SIZE: 10,
221
- TIMEOUT: 30 * 60 * 1000, // 30 mins
72
+ ```tsx
73
+ <UserlensProvider config={{
74
+ WRITE_CODE: 'your-write-code',
75
+ userId: user.id,
76
+ userTraits: {
77
+ email: user.email,
78
+ name: user.name,
79
+ plan: user.plan,
80
+ // Add as many traits as possible for better insights
222
81
  },
223
- });
82
+ }}>
224
83
  ```
225
84
 
226
- If either userId or WRITE_CODE is missing, the recorder will not start and will log a warning.
227
-
228
- ### React Wrapper
229
-
230
- The `UserlensProvider` is a React context wrapper that **automatically initializes** both:
85
+ ### Option B: Proxy Setup
231
86
 
232
- <!-- - [`EventCollector`](#eventcollector-methods) for capturing user events
233
- - [`SessionRecorder`](#sessionrecorder-methods) — for recording user sessions -->
234
-
235
- - `EventCollector` - for capturing user events
236
- - `SessionRecorder` - for recording user sessions
237
-
238
- This is the **recommended way** to integrate `userlens-analytics-sdk` into React projects.
239
-
240
- ---
241
-
242
- #### ✅ What It Does
243
-
244
- Under the hood, the React wrapper:
245
-
246
- - Instantiates `EventCollector` based on the mode (`callback` or `auto-upload`)
247
- - Optionally starts a `SessionRecorder` if not disabled
248
- - Manages lifecycle + cleanup for both
249
- - Exposes both instances via the `useUserlens()` hook
250
-
251
- ---
252
-
253
- #### 🛠 Usage Example
87
+ Events flow through your backend Userlens API. Useful if you want to avoid ad blockers.
254
88
 
255
89
  ```tsx
256
- import UserlensProvider from "userlens-analytics-sdk/react";
257
-
258
- const config = useMemo(
259
- () => ({
260
- // Required only if you're enabling session recording
261
- // or using auto-upload mode for EventCollector
262
- WRITE_CODE: "your-public-write-code",
263
- // Required only if you're enabling session recording
264
- // or using auto-upload mode for EventCollector
265
- userId: "user-123",
266
- // Used when letting the SDK handle event uploads automatically. Keeps traits up-to-date.
267
- userTraits: { email: "jane@example.com" },
268
- // Used when letting the SDK handle event uploads automatically. Associates user with a group (e.g. company).
269
- groupId: "company-123",
270
- // Used when letting the SDK handle event uploads automatically. Keeps group traits up-to-date.
271
- groupTraits: {
272
- domain: "userlens.io",
273
- title: "Userlens",
274
- industry: "Software"
275
- },
276
- eventCollector: {
277
- // Required when you want to manually handle event forwarding
278
- callback: (events) => {
279
- fetch("/api/track", {
280
- method: "POST",
281
- body: JSON.stringify(events),
282
- });
283
- },
284
- },
285
- // Set to false if you don't want to enable session replay
286
- enableSessionReplay: true,
287
- // Optional — fine-tunes session replay behavior
288
- sessionRecorder: {
289
- // Masks inputs like <input type="password" />
290
- maskingOptions: ["passwords"],
291
- // Controls how many events to buffer before flushing to backend
292
- // Recommended: 10
293
- BUFFER_SIZE: 10,
90
+ <UserlensProvider config={{
91
+ userId: user.id,
92
+ userTraits: {
93
+ email: user.email,
94
+ name: user.name,
95
+ plan: user.plan,
96
+ },
97
+ eventCollector: {
98
+ callback: (events) => {
99
+ fetch('/api/userlens/events', {
100
+ method: 'POST',
101
+ body: JSON.stringify(events),
102
+ });
294
103
  },
295
- }),
296
- [userId] // 👈 Prevents unnecessary reinitialization
297
- );
298
-
299
- return (
300
- <UserlensProvider config={config}>
301
- <App />
302
- </UserlensProvider>
303
- );
304
- ```
305
-
306
- Then, you can access the SDK instances anywhere using the `useUserlens()` hook:
307
-
308
- ```ts
309
- import { useUserlens } from "userlens-analytics-sdk/react";
310
-
311
- const { collector, sessionRecorder } = useUserlens();
312
-
313
- collector?.pushEvent({
314
- event: "Clicked CTA",
315
- properties: { location: "hero" },
316
- });
317
- ```
318
-
319
- 🔁 Heads up: Always wrap your config in useMemo() to avoid re-instantiating the SDK on every render. Even though the provider has guards, you'll avoid subtle bugs and unnecessary warnings.
320
-
321
- #### 🔁 Behavior Details
322
-
323
- If enableSessionReplay: false is passed, the wrapper skips session recording.
324
-
325
- If you call UserlensProvider with the same userId, it won’t reinitialize anything.
326
-
327
- If either WRITE_CODE or userId is missing, session replay will not start and a warning will be logged.
328
-
329
- ### 📌 Tracking Custom Events
330
-
331
- In addition to auto-tracked clicks and page views, you can manually push your own custom events using `collector.pushEvent()`.
332
-
333
- This is useful for tracking things like:
334
-
335
- - Form submissions
336
- - In-app interactions (e.g. modal opened, tab switched)
337
- - Feature usage
338
-
339
- ---
340
-
341
- #### ✍️ Example
342
-
343
- ```ts
344
- import { useUserlens } from "userlens-analytics-sdk";
345
-
346
- const { collector } = useUserlens();
347
-
348
- collector?.pushEvent({
349
- event: "Upgraded Plan",
350
- properties: {
351
- plan: "pro",
352
- source: "pricing_modal",
353
104
  },
354
- });
105
+ }}>
355
106
  ```
356
107
 
357
- ---
108
+ ## Track Custom Events
358
109
 
359
- #### 🧠 How it works
360
-
361
- The event will be stored as a `PushedEvent` (not a raw click or page view).
362
-
363
- The `properties` object is merged with the user's full environment/context automatically (OS, browser, timezone, etc).
364
-
365
- #### ⚠️ TypeError: Cannot read properties of undefined
366
-
367
- When using `UserlensProvider`, keep this in mind:
110
+ ```tsx
111
+ import { useUserlens } from 'userlens-analytics-sdk/react';
368
112
 
369
- **Don’t call `pushEvent()` before the provider is mounted.**
370
- The `collector` instance is created inside the provider on mount. If you try to access it too early (e.g. before the first render completes), `useUserlens()` will return `null`, and your call will silently do nothing — or worse, throw an error if you don’t check.
113
+ function UpgradeButton() {
114
+ const { collector } = useUserlens();
371
115
 
372
- Always check that `collector` exists before using it:
116
+ const handleUpgrade = () => {
117
+ collector?.pushEvent({
118
+ event: 'Plan Upgraded',
119
+ properties: { plan: 'pro' },
120
+ });
121
+ };
373
122
 
374
- ```ts
375
- if (collector) {
376
- collector.pushEvent({ event: "Something" });
123
+ return <button onClick={handleUpgrade}>Upgrade</button>;
377
124
  }
378
125
  ```
379
126
 
380
- ### 🛰 API Documentation
381
-
382
- As an alternative to using `userlens-analytics-sdk`, you can implement event tracking manually via our HTTP API.
383
-
384
- ---
385
-
386
- #### 🔐 Authentication
387
-
388
- All requests must include a **write code** in the `Authorization` header.
389
-
390
- You can retrieve your write code at:
391
- 👉 [https://app.userlens.io/settings/userlens-sdk](https://app.userlens.io/settings/userlens-sdk)
392
-
393
- ---
394
-
395
- #### 🧭 Endpoint
396
-
397
- All standard requests go to:
398
-
399
- ```
400
- POST https://events.userlens.io/event
401
- ```
402
-
403
- You can send three types of calls:
404
-
405
- ---
406
-
407
- ##### 1. Identify
408
-
409
- Keeps user traits up to date.
410
-
411
- ```ts
412
- const body = {
413
- type: "identify",
414
- userId, // string
415
- source: "userlens-restapi",
416
- traits, // object with user info (e.g. email, name, etc.)
417
- };
418
- ```
419
-
420
- > `traits` is a free-form object — add any relevant user properties.
421
-
422
- ---
423
-
424
- ##### 2. Group
425
-
426
- Updates company or organization traits.
427
-
428
- ```ts
429
- const body = {
430
- type: "group",
431
- groupId, // string
432
- userId, // string (required for association)
433
- source: "userlens-restapi",
434
- traits, // object with company info
435
- };
436
- ```
437
-
438
- ---
127
+ ## Requirements
439
128
 
440
- ##### 3. Track
441
-
442
- Sends a single custom event.
443
-
444
- ```ts
445
- const body = {
446
- type: "track",
447
- userId, // string
448
- source: "userlens-restapi",
449
- event: "button-clicked", // event name
450
- properties: {
451
- color: "red", // optional metadata
452
- },
453
- };
454
- ```
455
-
456
- ---
457
-
458
- #### 🔄 Sending Raw Events (from EventCollector)
459
-
460
- If you're forwarding events collected by `EventCollector`, send them to:
461
-
462
- ```
463
- POST https://raw.userlens.io/raw/event
464
- ```
465
-
466
- Payload format:
467
-
468
- ```ts
469
- const body = {
470
- events: [
471
- {
472
- event: "input-change",
473
- is_raw: true,
474
- snapshot: [], // DOM snapshot (optional)
475
- properties: {}, // metadata
476
- },
477
- {
478
- event: "form-submitted",
479
- is_raw: false, // explicitly pushed via pushEvent()
480
- properties: {},
481
- },
482
- ],
483
- };
484
- ```
129
+ - **React:** 16.8+ (hooks support)
130
+ - **Browser:** Chrome, Firefox, Safari, Edge (modern versions)
485
131
 
486
- Use this for sending batched autocollected + custom events.
132
+ ## License
487
133
 
134
+ ISC
package/dist/main.cjs.js CHANGED
@@ -4,6 +4,6 @@
4
4
  * https://github.com/lancedikson/bowser
5
5
  * MIT License | (c) Dustin Diaz 2012-2015
6
6
  * MIT License | (c) Denis Demchenko 2015-2019
7
- */class g{static getParser(e,t=!1){if("string"!=typeof e)throw new Error("UserAgent should be a string");return new p(e,t)}static parse(e){return new p(e).getResult()}static get BROWSER_MAP(){return s}static get ENGINE_MAP(){return o}static get OS_MAP(){return n}static get PLATFORMS_MAP(){return i}}var f="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{};function m(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var b,w={exports:{}},v=w.exports;
8
- /*! https://mths.be/cssescape v1.5.1 by @mathias | MIT license */function y(){return b||(b=1,w.exports=function(e){if(e.CSS&&e.CSS.escape)return e.CSS.escape;var t=function(e){if(0==arguments.length)throw new TypeError("`CSS.escape` requires an argument.");for(var t,r=String(e),s=r.length,i=-1,n="",o=r.charCodeAt(0);++i<s;)0!=(t=r.charCodeAt(i))?n+=t>=1&&t<=31||127==t||0==i&&t>=48&&t<=57||1==i&&t>=48&&t<=57&&45==o?"\\"+t.toString(16)+" ":0==i&&1==s&&45==t||!(t>=128||45==t||95==t||t>=48&&t<=57||t>=65&&t<=90||t>=97&&t<=122)?"\\"+r.charAt(i):r.charAt(i):n+="�";return n};return e.CSS||(e.CSS={}),e.CSS.escape=t,t}(void 0!==f?f:v)),w.exports}var M,S,E,_={};var T=m(function(){if(E)return S;E=1,y();const{ShadowRootTypes:e,nodeNameInCorrectCase:t,NodeType:r}=(M||(M=1,_.nodeNameInCorrectCase=function(e){const t=e.shadowRoot&&e.shadowRoot.mode;return t?"#shadow-root ("+t+")":e.localName?e.localName.length!==e.nodeName.length?e.nodeName:e.localName:e.nodeName},_.shadowRootType=function(e){const t=e.ancestorShadowRoot();return t?t.mode:null},_.NodeType={ELEMENT_NODE:1,ATTRIBUTE_NODE:2,TEXT_NODE:3,CDATA_SECTION_NODE:4,PROCESSING_INSTRUCTION_NODE:7,COMMENT_NODE:8,DOCUMENT_NODE:9},_.ShadowRootTypes={UserAgent:"user-agent",Open:"open",Closed:"closed"}),_);let s={DOMPath:{}};return s.DOMPath.fullQualifiedSelector=function(e,t){try{return e.nodeType!==r.ELEMENT_NODE?e.localName||e.nodeName.toLowerCase():s.DOMPath.cssPath(e,t)}catch(e){return null}},s.DOMPath.cssPath=function(e,t){if(e.nodeType!==r.ELEMENT_NODE)return"";const i=[];let n=e;for(;n;){const r=s.DOMPath._cssPathStep(n,!!t,n===e);if(!r)break;if(i.push(r),r.optimized)break;n=n.parentNode}return i.reverse(),i.join(" > ")},s.DOMPath.canGetJSPath=function(t){let r=t;for(;r;){if(r.shadowRoot&&r.shadowRoot.mode!==e.Open)return!1;r=r.shadowRoot&&r.shadowRoot.host}return!0},s.DOMPath.jsPath=function(e,t){if(e.nodeType!==r.ELEMENT_NODE)return"";const i=[];let n=e;for(;n;)i.push(s.DOMPath.cssPath(n,t)),n=n.shadowRoot&&n.shadowRoot.host;i.reverse();let o="";for(let e=0;e<i.length;++e){const t=JSON.stringify(i[e]);o+=e?`.shadowRoot.querySelector(${t})`:`document.querySelector(${t})`}return o},s.DOMPath._cssPathStep=function(e,i,n){if(e.nodeType!==r.ELEMENT_NODE)return null;const o=e.getAttribute("id");if(i){if(o)return new s.DOMPath.Step(d(o),!0);const r=e.nodeName.toLowerCase();if("body"===r||"head"===r||"html"===r)return new s.DOMPath.Step(t(e),!0)}const a=t(e);if(o)return new s.DOMPath.Step(a+d(o),!0);const c=e.parentNode;if(!c||c.nodeType===r.DOCUMENT_NODE)return new s.DOMPath.Step(a,!0);function l(e){const t=e.getAttribute("class");return t?t.split(/\s+/g).filter(Boolean).map(function(e){return"$"+e}):[]}function d(e){return"#"+CSS.escape(e)}const u=l(e);let h=!1,p=!1,g=-1,f=-1;const m=c.children;for(let s=0;(-1===g||!p)&&s<m.length;++s){const i=m[s];if(i.nodeType!==r.ELEMENT_NODE)continue;if(f+=1,i===e){g=f;continue}if(p)continue;if(t(i)!==a)continue;h=!0;const n=new Set(u);if(!n.size){p=!0;continue}const o=l(i);for(let e=0;e<o.length;++e){const t=o[e];if(n.has(t)&&(n.delete(t),!n.size)){p=!0;break}}}let b=a;if(n&&"input"===a.toLowerCase()&&e.getAttribute("type")&&!e.getAttribute("id")&&!e.getAttribute("class")&&(b+="[type="+CSS.escape(e.getAttribute("type"))+"]"),p)b+=":nth-child("+(g+1)+")";else if(h)for(const e of u)b+="."+CSS.escape(e.slice(1));return new s.DOMPath.Step(b,!1)},s.DOMPath.xPath=function(e,t){if(e.nodeType===r.DOCUMENT_NODE)return"/";const i=[];let n=e;for(;n;){const e=s.DOMPath._xPathValue(n,t);if(!e)break;if(i.push(e),e.optimized)break;n=n.parentNode}return i.reverse(),(i.length&&i[0].optimized?"":"/")+i.join("/")},s.DOMPath._xPathValue=function(e,t){let i;const n=s.DOMPath._xPathIndex(e);if(-1===n)return null;switch(e.nodeType){case r.ELEMENT_NODE:if(t&&e.getAttribute("id"))return new s.DOMPath.Step('//*[@id="'+e.getAttribute("id")+'"]',!0);i=e.localName;break;case r.ATTRIBUTE_NODE:i="@"+e.nodeName;break;case r.TEXT_NODE:case r.CDATA_SECTION_NODE:i="text()";break;case r.PROCESSING_INSTRUCTION_NODE:i="processing-instruction()";break;case r.COMMENT_NODE:i="comment()";break;case r.DOCUMENT_NODE:default:i=""}return n>0&&(i+="["+n+"]"),new s.DOMPath.Step(i,e.nodeType===r.DOCUMENT_NODE)},s.DOMPath._xPathIndex=function(e){function t(e,t){if(e===t)return!0;if(e.nodeType===r.ELEMENT_NODE&&t.nodeType===r.ELEMENT_NODE)return e.localName===t.localName;if(e.nodeType===t.nodeType)return!0;return(e.nodeType===r.CDATA_SECTION_NODE?r.TEXT_NODE:e.nodeType)===(t.nodeType===r.CDATA_SECTION_NODE?r.TEXT_NODE:t.nodeType)}const s=e.parentNode?e.parentNode.children:null;if(!s)return 0;let i;for(let r=0;r<s.length;++r)if(t(e,s[r])&&s[r]!==e){i=!0;break}if(!i)return 0;let n=1;for(let r=0;r<s.length;++r)if(t(e,s[r])){if(s[r]===e)return n;++n}return-1},s.DOMPath.Step=class{constructor(e,t){this.value=e,this.optimized=t||!1}toString(){return this.value}},S=s.DOMPath}());const O="https://events.userlens.io",N=()=>{try{const e=window.localStorage.getItem("$ul_WRITE_CODE");if(null==e)return null;const t=e.trim();if(!t)return null;const r=t.toLowerCase();return"null"===r||"undefined"===r?null:t}catch{return null}},k=async(e,t=!1)=>{if(!(null==e?void 0:e.userId))return;const r=N();if(!r)return void(t&&console.error("Failed to identify user: Userlens SDK error: WRITE_CODE is not set"));const s={type:"identify",userId:e.userId,source:"userlens-js-analytics-sdk",traits:null==e?void 0:e.traits};if(!(await fetch(`${O}/event`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Basic ${r}`},body:JSON.stringify(s)})).ok)throw new Error("Userlens HTTP error: failed to identify");return"ok"},C=async(e,t=!1)=>{if(!(null==e?void 0:e.groupId))return;const r=N();if(!r)return void(t&&console.error("Failed to group identify: Userlens SDK error: WRITE_CODE is not set"));const{groupId:s,userId:i,traits:n}=e,o={type:"group",groupId:s,source:"userlens-js-analytics-sdk",...i&&{userId:i},...n&&{traits:n}};if(!(await fetch(`${O}/event`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Basic ${r}`},body:JSON.stringify(o)})).ok)throw new Error("Userlens HTTP error: failed to identify");return"ok"},F=async(e,t=!1)=>{const r=N();if(!r)return void(t&&console.error("Failed to group identify: Userlens SDK error: WRITE_CODE is not set"));const s={events:e};if(!(await fetch("https://raw.userlens.io/raw/event",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Basic ${r}`},body:JSON.stringify(s)})).ok)throw new Error("Userlens HTTP error: failed to track");return"ok"};var P,B,A,D,I,x,R,L,U,W,$,z,j,V,q,G,H,Q,K;B=new WeakMap,A=new WeakMap,D=new WeakMap,I=new WeakMap,x=new WeakMap,q=new WeakMap,P=new WeakSet,R=function(){this.debug&&console.log("Userlens EventCollector: adding click event listener"),document.addEventListener("click",e(this,I,"f"),!0),this.debug&&console.log("Userlens EventCollector: click event listener added")},L=function(t){try{const r=t.target;if(!(r instanceof HTMLElement))return;const s=T.xPath(r,!0),i=this.useLighterSnapshot?e(this,P,"m",U).call(this,r):e(this,P,"m",W).call(this,r),n={event:s,is_raw:!0,snapshot:i?[i]:[],properties:{...this.getUserContext(),...this.getPageMetadata()}};this.userId&&(n.userId=this.userId),this.events.push(n),this.events.length>100&&(this.events=this.events.slice(-100))}catch(e){console.warn("Userlens EventCollector error: click event handling failed",e)}},U=function(t){if(!(t instanceof HTMLElement))return null;const r=document.body;if(!r)return null;const s=[];let i=t;for(;i&&1===i.nodeType&&i!==r;)s.unshift(i),i=i.parentElement;const n=[r,...s.filter(e=>e!==r)];let o=null,a=null;for(let t=0;t<n.length;t++){const r=n[t],s=t===n.length-1,i=e(this,P,"m",$).call(this,r,{isTarget:s,leadsToTarget:!s});o||(o=i),a&&(a.children||(a.children=[]),a.children.push(i)),a=i}const c=a;return c&&t.children.length>0&&(c.children=Array.from(t.children).filter(e=>e instanceof HTMLElement).map(t=>e(this,P,"m",$).call(this,t,{includeChildren:!0})).filter(Boolean)),o},W=function(t){if(!(t instanceof HTMLElement))return null;const r=[];let s=t;for(;s&&1===s.nodeType;)r.unshift(s),s=s.parentElement;let i=null,n=null;for(let t=0;t<r.length;t++){const s=r[t],o=t>=r.length-3,a=t===r.length-1,c=e(this,P,"m",$).call(this,s,{isTarget:a,leadsToTarget:!0});let l=[c];if(o&&s.parentElement){l=[c,...Array.from(s.parentElement.children).filter(e=>e!==s&&e instanceof HTMLElement).map(t=>e(this,P,"m",$).call(this,t,{includeChildren:!0})).filter(Boolean)]}i||(i=c),n&&(n.children||(n.children=[]),n.children.push(...l)),n=c}return i},$=function t(r,{isTarget:s=!1,includeChildren:i=!1,leadsToTarget:n=!1}={}){var o,a;const c=r.tagName.toLowerCase(),l=r.classList.length?Array.from(r.classList):null,d=r.id||null,u=r.getAttribute("href")||null,h=Array.from((null===(o=r.parentNode)||void 0===o?void 0:o.children)||[]).indexOf(r)+1,p=Array.from((null===(a=r.parentNode)||void 0===a?void 0:a.children)||[]).filter(e=>e instanceof HTMLElement&&e.tagName===r.tagName).indexOf(r)+1,g={};for(let e of Array.from(r.attributes))g[`attr__${e.name}`]=e.value;const f=Array.from(r.childNodes).filter(e=>e.nodeType===Node.TEXT_NODE).map(e=>{var t;return null===(t=e.textContent)||void 0===t?void 0:t.trim()}).filter(Boolean).join(" ")||null,m={tag_name:c,nth_child:h,nth_of_type:p,attributes:g,...l?{attr_class:l}:{},...d?{attr_id:d}:{},...u?{href:u}:{},...f?{text:f}:{},...s?{is_target:!0}:{},...n&&!s?{leads_to_target:!0}:{}};return(i&&r.children.length>0||s)&&(m.children=Array.from(r.children).filter(e=>e instanceof HTMLElement).map(r=>e(this,P,"m",t).call(this,r,{includeChildren:!0})).filter(Boolean)),m},z=function(){t(this,B,setInterval(()=>{e(this,q,"f").call(this)},this.intervalTime),"f")},j=function(){t(this,A,history.pushState,"f"),t(this,D,history.replaceState,"f"),history.pushState=(...t)=>{e(this,A,"f").apply(history,t),e(this,P,"m",V).call(this)},history.replaceState=(...t)=>{e(this,D,"f").apply(history,t),e(this,P,"m",V).call(this)},window.addEventListener("popstate",e(this,x,"f"))},V=function(){if(function(){if("undefined"==typeof window)return!1;const e=window.location.hostname;return"localhost"===e||"127.0.0.1"===e||"::1"===e||e.endsWith(".localhost")}())return;const e={event:"$ul_pageview",properties:this.getPageMetadata()};this.userId&&(e.userId=this.userId),this.events.push(e)},G=function(){this.events=[]},H=function(){document.removeEventListener("click",e(this,I,"f"),!0)},Q=function(){e(this,q,"f").call(this),clearInterval(e(this,B,"f")),e(this,P,"m",G).call(this)},K=function(){history.pushState=e(this,A,"f"),history.replaceState=e(this,D,"f"),window.removeEventListener("popstate",e(this,x,"f"))},exports.EventCollector=class{constructor(t){if(P.add(this),this.useLighterSnapshot=!1,this.userContext=null,B.set(this,void 0),A.set(this,void 0),D.set(this,void 0),I.set(this,e(this,P,"m",L).bind(this)),x.set(this,e(this,P,"m",V).bind(this)),q.set(this,()=>{if(0===this.events.length)return;const t=[...this.events];if(this.callback){try{this.callback(t)}catch(e){}e(this,P,"m",G).call(this)}else Promise.allSettled([this.userId&&this.userTraits?k({userId:this.userId,traits:this.userTraits},this.debug):null,this.groupId?C({groupId:this.groupId,traits:this.groupTraits,userId:this.userId},this.debug):null,F(t,this.debug)]),e(this,P,"m",G).call(this)}),"undefined"==typeof window)return void console.error("Userlens EventCollector error: unavailable outside of browser environment.");const{userId:r,WRITE_CODE:s,callback:i,intervalTime:n=5e3,skipRawEvents:o=!1,useLighterSnapshot:a=!1,debug:c=!1}=t,l=t.userTraits,d=t.groupId,u=t.groupTraits;var h;(this.autoUploadModeEnabled=!i,!this.autoUploadModeEnabled||(null==r?void 0:r.length))?!this.autoUploadModeEnabled||(null==s?void 0:s.length)?(this.autoUploadModeEnabled&&(h=s,window.localStorage.setItem("$ul_WRITE_CODE",btoa(`${h}:`))),this.autoUploadModeEnabled||"function"==typeof i?(this.userId=r,this.userTraits="object"==typeof l&&null!==l?l:{},this.callback=i,this.intervalTime=n,this.events=[],this.debug=c,d&&(this.groupId=d,this.groupTraits="object"==typeof u&&null!==u?u:{}),o||(this.useLighterSnapshot=a,e(this,P,"m",R).call(this),e(this,P,"m",j).call(this)),e(this,P,"m",z).call(this),this.userContext=this.getUserContext()):this.debug&&console.error("Userlens EventCollector error: callback is not a function.")):this.debug&&console.error("Userlens EventCollector error: WRITE_CODE is missing."):this.debug&&console.error("Userlens EventCollector error: userId is missing.")}pushEvent(e){const t={is_raw:!1,...e,properties:{...null==e?void 0:e.properties,...this.getUserContext(),...this.getPageMetadata()}};this.userId&&(t.userId=this.userId),this.events.push(t)}identify(e,t){return k({userId:e,traits:t})}group(e,t){return C({groupId:e,traits:t,userId:this.userId})}updateUserTraits(e){this.userTraits=e}updateGroupTraits(e){this.groupTraits=e}stop(){e(this,P,"m",H).call(this),e(this,P,"m",Q).call(this),e(this,P,"m",K).call(this)}getUserContext(){var e,t,r,s,i,n,o;if(this.userContext)return this.userContext;const a=g.getParser(window.navigator.userAgent),c=a.getBrowser(),l=a.getOS(),d={$ul_browser:null!==(e=c.name)&&void 0!==e?e:"Unknown",$ul_browser_version:null!==(t=c.version)&&void 0!==t?t:"Unknown",$ul_os:null!==(r=l.name)&&void 0!==r?r:"Unknown",$ul_os_version:null!==(s=l.versionName)&&void 0!==s?s:"Unknown",$ul_browser_language:null!==(i=navigator.language)&&void 0!==i?i:"en-US",$ul_browser_language_prefix:null!==(o=null===(n=navigator.language)||void 0===n?void 0:n.split("-")[0])&&void 0!==o?o:"en",$ul_screen_width:window.screen.width,$ul_screen_height:window.screen.height,$ul_viewport_width:window.innerWidth,$ul_viewport_height:window.innerHeight,$ul_lib:"userlens.js",$ul_lib_version:"0.1.89",$ul_device_type:/Mobi|Android/i.test(navigator.userAgent)?"Mobile":"Desktop",$ul_timezone:Intl.DateTimeFormat().resolvedOptions().timeZone};return this.userContext=d,d}getPageMetadata(){try{const e=new URL(window.location.href);let t=document.referrer||"$direct",r="$direct";try{t&&/^https?:\/\//.test(t)&&(r=new URL(t).hostname)}catch(e){}const s=e.search.slice(1);return{$ul_page:e.origin+e.pathname,$ul_pathname:e.pathname,$ul_host:e.host,$ul_referrer:t,$ul_referring_domain:r,$ul_query:s}}catch(e){return{$ul_page:"",$ul_pathname:"",$ul_host:"",$ul_referrer:"",$ul_referring_domain:"",$ul_query:""}}}};
7
+ */class g{static getParser(e,t=!1){if("string"!=typeof e)throw new Error("UserAgent should be a string");return new p(e,t)}static parse(e){return new p(e).getResult()}static get BROWSER_MAP(){return s}static get ENGINE_MAP(){return o}static get OS_MAP(){return n}static get PLATFORMS_MAP(){return i}}var f="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{};function m(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var b,w={exports:{}},y=w.exports;
8
+ /*! https://mths.be/cssescape v1.5.1 by @mathias | MIT license */function v(){return b||(b=1,w.exports=function(e){if(e.CSS&&e.CSS.escape)return e.CSS.escape;var t=function(e){if(0==arguments.length)throw new TypeError("`CSS.escape` requires an argument.");for(var t,r=String(e),s=r.length,i=-1,n="",o=r.charCodeAt(0);++i<s;)0!=(t=r.charCodeAt(i))?n+=t>=1&&t<=31||127==t||0==i&&t>=48&&t<=57||1==i&&t>=48&&t<=57&&45==o?"\\"+t.toString(16)+" ":0==i&&1==s&&45==t||!(t>=128||45==t||95==t||t>=48&&t<=57||t>=65&&t<=90||t>=97&&t<=122)?"\\"+r.charAt(i):r.charAt(i):n+="�";return n};return e.CSS||(e.CSS={}),e.CSS.escape=t,t}(void 0!==f?f:y)),w.exports}var M,S,E,_={};var T=m(function(){if(E)return S;E=1,v();const{ShadowRootTypes:e,nodeNameInCorrectCase:t,NodeType:r}=(M||(M=1,_.nodeNameInCorrectCase=function(e){const t=e.shadowRoot&&e.shadowRoot.mode;return t?"#shadow-root ("+t+")":e.localName?e.localName.length!==e.nodeName.length?e.nodeName:e.localName:e.nodeName},_.shadowRootType=function(e){const t=e.ancestorShadowRoot();return t?t.mode:null},_.NodeType={ELEMENT_NODE:1,ATTRIBUTE_NODE:2,TEXT_NODE:3,CDATA_SECTION_NODE:4,PROCESSING_INSTRUCTION_NODE:7,COMMENT_NODE:8,DOCUMENT_NODE:9},_.ShadowRootTypes={UserAgent:"user-agent",Open:"open",Closed:"closed"}),_);let s={DOMPath:{}};return s.DOMPath.fullQualifiedSelector=function(e,t){try{return e.nodeType!==r.ELEMENT_NODE?e.localName||e.nodeName.toLowerCase():s.DOMPath.cssPath(e,t)}catch(e){return null}},s.DOMPath.cssPath=function(e,t){if(e.nodeType!==r.ELEMENT_NODE)return"";const i=[];let n=e;for(;n;){const r=s.DOMPath._cssPathStep(n,!!t,n===e);if(!r)break;if(i.push(r),r.optimized)break;n=n.parentNode}return i.reverse(),i.join(" > ")},s.DOMPath.canGetJSPath=function(t){let r=t;for(;r;){if(r.shadowRoot&&r.shadowRoot.mode!==e.Open)return!1;r=r.shadowRoot&&r.shadowRoot.host}return!0},s.DOMPath.jsPath=function(e,t){if(e.nodeType!==r.ELEMENT_NODE)return"";const i=[];let n=e;for(;n;)i.push(s.DOMPath.cssPath(n,t)),n=n.shadowRoot&&n.shadowRoot.host;i.reverse();let o="";for(let e=0;e<i.length;++e){const t=JSON.stringify(i[e]);o+=e?`.shadowRoot.querySelector(${t})`:`document.querySelector(${t})`}return o},s.DOMPath._cssPathStep=function(e,i,n){if(e.nodeType!==r.ELEMENT_NODE)return null;const o=e.getAttribute("id");if(i){if(o)return new s.DOMPath.Step(d(o),!0);const r=e.nodeName.toLowerCase();if("body"===r||"head"===r||"html"===r)return new s.DOMPath.Step(t(e),!0)}const a=t(e);if(o)return new s.DOMPath.Step(a+d(o),!0);const c=e.parentNode;if(!c||c.nodeType===r.DOCUMENT_NODE)return new s.DOMPath.Step(a,!0);function l(e){const t=e.getAttribute("class");return t?t.split(/\s+/g).filter(Boolean).map(function(e){return"$"+e}):[]}function d(e){return"#"+CSS.escape(e)}const u=l(e);let h=!1,p=!1,g=-1,f=-1;const m=c.children;for(let s=0;(-1===g||!p)&&s<m.length;++s){const i=m[s];if(i.nodeType!==r.ELEMENT_NODE)continue;if(f+=1,i===e){g=f;continue}if(p)continue;if(t(i)!==a)continue;h=!0;const n=new Set(u);if(!n.size){p=!0;continue}const o=l(i);for(let e=0;e<o.length;++e){const t=o[e];if(n.has(t)&&(n.delete(t),!n.size)){p=!0;break}}}let b=a;if(n&&"input"===a.toLowerCase()&&e.getAttribute("type")&&!e.getAttribute("id")&&!e.getAttribute("class")&&(b+="[type="+CSS.escape(e.getAttribute("type"))+"]"),p)b+=":nth-child("+(g+1)+")";else if(h)for(const e of u)b+="."+CSS.escape(e.slice(1));return new s.DOMPath.Step(b,!1)},s.DOMPath.xPath=function(e,t){if(e.nodeType===r.DOCUMENT_NODE)return"/";const i=[];let n=e;for(;n;){const e=s.DOMPath._xPathValue(n,t);if(!e)break;if(i.push(e),e.optimized)break;n=n.parentNode}return i.reverse(),(i.length&&i[0].optimized?"":"/")+i.join("/")},s.DOMPath._xPathValue=function(e,t){let i;const n=s.DOMPath._xPathIndex(e);if(-1===n)return null;switch(e.nodeType){case r.ELEMENT_NODE:if(t&&e.getAttribute("id"))return new s.DOMPath.Step('//*[@id="'+e.getAttribute("id")+'"]',!0);i=e.localName;break;case r.ATTRIBUTE_NODE:i="@"+e.nodeName;break;case r.TEXT_NODE:case r.CDATA_SECTION_NODE:i="text()";break;case r.PROCESSING_INSTRUCTION_NODE:i="processing-instruction()";break;case r.COMMENT_NODE:i="comment()";break;case r.DOCUMENT_NODE:default:i=""}return n>0&&(i+="["+n+"]"),new s.DOMPath.Step(i,e.nodeType===r.DOCUMENT_NODE)},s.DOMPath._xPathIndex=function(e){function t(e,t){if(e===t)return!0;if(e.nodeType===r.ELEMENT_NODE&&t.nodeType===r.ELEMENT_NODE)return e.localName===t.localName;if(e.nodeType===t.nodeType)return!0;return(e.nodeType===r.CDATA_SECTION_NODE?r.TEXT_NODE:e.nodeType)===(t.nodeType===r.CDATA_SECTION_NODE?r.TEXT_NODE:t.nodeType)}const s=e.parentNode?e.parentNode.children:null;if(!s)return 0;let i;for(let r=0;r<s.length;++r)if(t(e,s[r])&&s[r]!==e){i=!0;break}if(!i)return 0;let n=1;for(let r=0;r<s.length;++r)if(t(e,s[r])){if(s[r]===e)return n;++n}return-1},s.DOMPath.Step=class{constructor(e,t){this.value=e,this.optimized=t||!1}toString(){return this.value}},S=s.DOMPath}());const O="https://events.userlens.io",k=["events.userlens.io","raw.userlens.io"],N=()=>{try{const e=window.localStorage.getItem("$ul_WRITE_CODE");if(null==e)return null;const t=e.trim();if(!t)return null;const r=t.toLowerCase();return"null"===r||"undefined"===r?null:t}catch{return null}},B=async(e,t=!1)=>{if(!(null==e?void 0:e.userId))return;const r=N();if(!r)return void(t&&console.error("Failed to identify user: Userlens SDK error: WRITE_CODE is not set"));const s={type:"identify",userId:e.userId,source:"userlens-js-analytics-sdk",traits:null==e?void 0:e.traits};if(!(await fetch(`${O}/event`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Basic ${r}`},body:JSON.stringify(s)})).ok)throw new Error("Userlens HTTP error: failed to identify");return"ok"},C=async(e,t=!1)=>{if(!(null==e?void 0:e.groupId))return;const r=N();if(!r)return void(t&&console.error("Failed to group identify: Userlens SDK error: WRITE_CODE is not set"));const{groupId:s,userId:i,traits:n}=e,o={type:"group",groupId:s,source:"userlens-js-analytics-sdk",...i&&{userId:i},...n&&{traits:n}};if(!(await fetch(`${O}/event`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Basic ${r}`},body:JSON.stringify(o)})).ok)throw new Error("Userlens HTTP error: failed to identify");return"ok"},F=async(e,t=!1)=>{const r=N();if(!r)return void(t&&console.error("Failed to group identify: Userlens SDK error: WRITE_CODE is not set"));const s={events:e};if(!(await fetch("https://raw.userlens.io/raw/event",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Basic ${r}`},body:JSON.stringify(s)})).ok)throw new Error("Userlens HTTP error: failed to track");return"ok"};var P,A,x,D,R,I,L,U,$,z,W,j,q,V,G,H,Q,J,K,Z,X,Y,ee,te,re,se,ie,ne;class oe{constructor(e){var t,r,s,i;P.add(this),this.isActive=!1,this.onEvent=e.onEvent,this.captureBody=null!==(t=e.captureBody)&&void 0!==t&&t,this.debug=null!==(r=e.debug)&&void 0!==r&&r,this.maxBodySize=null!==(s=e.maxBodySize)&&void 0!==s?s:1e4,this.ignoreUrls=null!==(i=e.ignoreUrls)&&void 0!==i?i:[],this.originalFetch=window.fetch.bind(window)}start(){this.isActive?this.debug&&console.log("NetworkTracker: already started"):(this.debug&&console.log("NetworkTracker: starting network call tracking"),this.isActive=!0,e(this,P,"m",A).call(this))}stop(){this.isActive?(this.debug&&console.log("NetworkTracker: stopping network call tracking"),this.isActive=!1,window.fetch=this.originalFetch):this.debug&&console.log("NetworkTracker: not active")}}P=new WeakSet,A=function(){const t=this;window.fetch=async function(r,s){const i=Date.now(),{url:n,method:o}=e(t,P,"m",x).call(t,r,s);if(e(t,P,"m",D).call(t,n))return t.originalFetch(r,s);const a=e(t,P,"m",R).call(t,s);try{const c=await t.originalFetch(r,s),l=Date.now()-i,d=e(t,P,"m",$).call(t,n),u=await e(t,P,"m",I).call(t,c),h=e(t,P,"m",L).call(t,{url:n,method:o,params:d,status:c.status,duration:l,success:c.ok,requestBody:a,responseBody:u});return e(t,P,"m",U).call(t,h),c}catch(r){const s=Date.now()-i,c=e(t,P,"m",$).call(t,n),l=e(t,P,"m",L).call(t,{url:n,method:o,params:c,status:0,duration:s,success:!1,requestBody:a,responseBody:void 0});throw e(t,P,"m",U).call(t,l),r}}},x=function(e,t){return"string"==typeof e?{url:e,method:(null==t?void 0:t.method)||"GET"}:e instanceof URL?{url:e.toString(),method:(null==t?void 0:t.method)||"GET"}:{url:e.url,method:e.method||"GET"}},D=function(e){for(const t of this.ignoreUrls)if(t.test(e))return!0;return!1},R=function(e){if(this.captureBody&&(null==e?void 0:e.body))try{if("string"==typeof e.body){if(e.body.length<=this.maxBodySize)try{return JSON.parse(e.body)}catch{return e.body}return"[Body too large to capture]"}return e.body instanceof FormData?"[FormData]":e.body instanceof Blob?"[Blob]":e.body instanceof ArrayBuffer?"[ArrayBuffer]":"undefined"!=typeof ReadableStream&&e.body instanceof ReadableStream?"[ReadableStream]":e.body}catch{return"[Error capturing body]"}},I=async function(e){if(!this.captureBody)return;const t=e.headers.get("content-length"),r=t?parseInt(t,10):0;if(r>0&&r>this.maxBodySize)return"[Response too large to capture]";const s=e.clone();try{const t=e.headers.get("content-type");if(t&&t.includes("application/json")){const e=await s.text();return e.length<=this.maxBodySize?JSON.parse(e):"[Response too large to capture]"}if(t&&t.includes("text/")){const e=await s.text();return e.length<=this.maxBodySize?e:"[Response too large to capture]"}return"[Binary content]"}catch(e){return this.debug&&console.warn("NetworkTracker: failed to capture response body",e),"[Error capturing body]"}},L=function({url:e,method:t,params:r,status:s,duration:i,success:n,requestBody:o,responseBody:a}){return{event:"$ul_network_request",is_raw:!1,properties:{$ul_url:e,$ul_method:t.toUpperCase(),$ul_params:r,$ul_status:s,$ul_duration:i,$ul_success:n,...this.captureBody&&void 0!==o?{$ul_request_body:o}:{},...this.captureBody&&void 0!==a?{$ul_response_body:a}:{}}}},U=function(e){setTimeout(()=>{try{this.onEvent(e)}catch(e){this.debug&&console.error("NetworkTracker: error in onEvent callback",e)}},0)},$=function(e){try{const t=new URL(e,window.location.origin),r={};return t.searchParams.forEach((e,t)=>{r[t]=e}),r}catch{return{}}};W=new WeakMap,j=new WeakMap,q=new WeakMap,V=new WeakMap,G=new WeakMap,te=new WeakMap,z=new WeakSet,H=function(){this.debug&&console.log("Userlens EventCollector: adding click event listener"),document.addEventListener("click",e(this,V,"f"),!0),this.debug&&console.log("Userlens EventCollector: click event listener added")},Q=function(t){try{const r=t.target;if(!(r instanceof HTMLElement))return;const s=T.xPath(r,!0),i=this.useLighterSnapshot?e(this,z,"m",J).call(this,r):e(this,z,"m",K).call(this,r),n={event:s,is_raw:!0,snapshot:i?[i]:[],properties:{...this.getUserContext(),...this.getPageMetadata()}};this.userId&&(n.userId=this.userId),this.events.push(n),this.events.length>100&&(this.events=this.events.slice(-100))}catch(e){console.warn("Userlens EventCollector error: click event handling failed",e)}},J=function(t){if(!(t instanceof HTMLElement))return null;const r=document.body;if(!r)return null;const s=[];let i=t;for(;i&&1===i.nodeType&&i!==r;)s.unshift(i),i=i.parentElement;const n=[r,...s.filter(e=>e!==r)];let o=null,a=null;for(let t=0;t<n.length;t++){const r=n[t],s=t===n.length-1,i=e(this,z,"m",Z).call(this,r,{isTarget:s,leadsToTarget:!s});o||(o=i),a&&(a.children||(a.children=[]),a.children.push(i)),a=i}const c=a;return c&&t.children.length>0&&(c.children=Array.from(t.children).filter(e=>e instanceof HTMLElement).map(t=>e(this,z,"m",Z).call(this,t,{includeChildren:!0})).filter(Boolean)),o},K=function(t){if(!(t instanceof HTMLElement))return null;const r=[];let s=t;for(;s&&1===s.nodeType;)r.unshift(s),s=s.parentElement;let i=null,n=null;for(let t=0;t<r.length;t++){const s=r[t],o=t>=r.length-3,a=t===r.length-1,c=e(this,z,"m",Z).call(this,s,{isTarget:a,leadsToTarget:!0});let l=[c];if(o&&s.parentElement){l=[c,...Array.from(s.parentElement.children).filter(e=>e!==s&&e instanceof HTMLElement).map(t=>e(this,z,"m",Z).call(this,t,{includeChildren:!0})).filter(Boolean)]}i||(i=c),n&&(n.children||(n.children=[]),n.children.push(...l)),n=c}return i},Z=function t(r,{isTarget:s=!1,includeChildren:i=!1,leadsToTarget:n=!1}={}){var o,a;const c=r.tagName.toLowerCase(),l=r.classList.length?Array.from(r.classList):null,d=r.id||null,u=r.getAttribute("href")||null,h=Array.from((null===(o=r.parentNode)||void 0===o?void 0:o.children)||[]).indexOf(r)+1,p=Array.from((null===(a=r.parentNode)||void 0===a?void 0:a.children)||[]).filter(e=>e instanceof HTMLElement&&e.tagName===r.tagName).indexOf(r)+1,g={};for(let e of Array.from(r.attributes))g[`attr__${e.name}`]=e.value;const f=Array.from(r.childNodes).filter(e=>e.nodeType===Node.TEXT_NODE).map(e=>{var t;return null===(t=e.textContent)||void 0===t?void 0:t.trim()}).filter(Boolean).join(" ")||null,m={tag_name:c,nth_child:h,nth_of_type:p,attributes:g,...l?{attr_class:l}:{},...d?{attr_id:d}:{},...u?{href:u}:{},...f?{text:f}:{},...s?{is_target:!0}:{},...n&&!s?{leads_to_target:!0}:{}};return(i&&r.children.length>0||s)&&(m.children=Array.from(r.children).filter(e=>e instanceof HTMLElement).map(r=>e(this,z,"m",t).call(this,r,{includeChildren:!0})).filter(Boolean)),m},X=function(){t(this,W,setInterval(()=>{e(this,te,"f").call(this)},this.intervalTime),"f")},Y=function(){t(this,j,history.pushState,"f"),t(this,q,history.replaceState,"f"),history.pushState=(...t)=>{e(this,j,"f").apply(history,t),e(this,z,"m",ee).call(this)},history.replaceState=(...t)=>{e(this,q,"f").apply(history,t),e(this,z,"m",ee).call(this)},window.addEventListener("popstate",e(this,G,"f"))},ee=function(){if(function(){if("undefined"==typeof window)return!1;const e=window.location.hostname;return"localhost"===e||"127.0.0.1"===e||"::1"===e||e.endsWith(".localhost")}())return;const e={event:"$ul_pageview",properties:this.getPageMetadata()};this.userId&&(e.userId=this.userId),this.events.push(e)},re=function(){this.events=[]},se=function(){document.removeEventListener("click",e(this,V,"f"),!0)},ie=function(){e(this,te,"f").call(this),clearInterval(e(this,W,"f")),e(this,z,"m",re).call(this)},ne=function(){history.pushState=e(this,j,"f"),history.replaceState=e(this,q,"f"),window.removeEventListener("popstate",e(this,G,"f"))},exports.EventCollector=class{constructor(t){if(z.add(this),this.useLighterSnapshot=!1,this.userContext=null,W.set(this,void 0),j.set(this,void 0),q.set(this,void 0),V.set(this,e(this,z,"m",Q).bind(this)),G.set(this,e(this,z,"m",ee).bind(this)),te.set(this,()=>{if(0===this.events.length)return;const t=[...this.events];if(this.callback){try{this.callback(t)}catch(e){}e(this,z,"m",re).call(this)}else Promise.allSettled([this.userId&&this.userTraits?B({userId:this.userId,traits:this.userTraits},this.debug):null,this.groupId?C({groupId:this.groupId,traits:this.groupTraits,userId:this.userId},this.debug):null,F(t,this.debug)]),e(this,z,"m",re).call(this)}),"undefined"==typeof window)return void console.error("Userlens EventCollector error: unavailable outside of browser environment.");const{userId:r,WRITE_CODE:s,callback:i,intervalTime:n=5e3,skipRawEvents:o=!1,useLighterSnapshot:a=!1,debug:c=!1,trackNetworkCalls:l=!1,networkCaptureBody:d=!1,networkMaxBodySize:u,networkIgnoreUrls:h}=t,p=t.userTraits,g=t.groupId,f=t.groupTraits;var m;if(this.autoUploadModeEnabled=!i,!this.autoUploadModeEnabled||(null==r?void 0:r.length))if(!this.autoUploadModeEnabled||(null==s?void 0:s.length))if(this.autoUploadModeEnabled&&(m=s,window.localStorage.setItem("$ul_WRITE_CODE",btoa(`${m}:`))),this.autoUploadModeEnabled||"function"==typeof i){if(this.userId=r,this.userTraits="object"==typeof p&&null!==p?p:{},this.callback=i,this.intervalTime=n,this.events=[],this.debug=c,g&&(this.groupId=g,this.groupTraits="object"==typeof f&&null!==f?f:{}),o||(this.useLighterSnapshot=a,e(this,z,"m",H).call(this),e(this,z,"m",Y).call(this)),l){const e=[...h||[]];if(this.autoUploadModeEnabled){const t=k.map(e=>new RegExp(`^https?://${e.replace(/\./g,"\\.")}`));e.push(...t)}this.networkTracker=new oe({onEvent:e=>{this.events.push(e)},captureBody:d,debug:this.debug,maxBodySize:u,ignoreUrls:e}),this.networkTracker.start(),this.debug&&console.log("Userlens EventCollector: network tracking enabled")}e(this,z,"m",X).call(this),this.userContext=this.getUserContext()}else this.debug&&console.error("Userlens EventCollector error: callback is not a function.");else this.debug&&console.error("Userlens EventCollector error: WRITE_CODE is missing.");else this.debug&&console.error("Userlens EventCollector error: userId is missing.")}pushEvent(e){const t={is_raw:!1,...e,properties:{...null==e?void 0:e.properties,...this.getUserContext(),...this.getPageMetadata()}};this.userId&&(t.userId=this.userId),this.events.push(t)}identify(e,t){return B({userId:e,traits:t})}group(e,t){return C({groupId:e,traits:t,userId:this.userId})}updateUserTraits(e){this.userTraits=e}updateGroupTraits(e){this.groupTraits=e}stop(){e(this,z,"m",se).call(this),e(this,z,"m",ie).call(this),e(this,z,"m",ne).call(this),this.networkTracker&&this.networkTracker.stop()}getUserContext(){var e,t,r,s,i,n,o;if(this.userContext)return this.userContext;const a=g.getParser(window.navigator.userAgent),c=a.getBrowser(),l=a.getOS(),d={$ul_browser:null!==(e=c.name)&&void 0!==e?e:"Unknown",$ul_browser_version:null!==(t=c.version)&&void 0!==t?t:"Unknown",$ul_os:null!==(r=l.name)&&void 0!==r?r:"Unknown",$ul_os_version:null!==(s=l.versionName)&&void 0!==s?s:"Unknown",$ul_browser_language:null!==(i=navigator.language)&&void 0!==i?i:"en-US",$ul_browser_language_prefix:null!==(o=null===(n=navigator.language)||void 0===n?void 0:n.split("-")[0])&&void 0!==o?o:"en",$ul_screen_width:window.screen.width,$ul_screen_height:window.screen.height,$ul_viewport_width:window.innerWidth,$ul_viewport_height:window.innerHeight,$ul_lib:"userlens.js",$ul_lib_version:"1.1.0",$ul_device_type:/Mobi|Android/i.test(navigator.userAgent)?"Mobile":"Desktop",$ul_timezone:Intl.DateTimeFormat().resolvedOptions().timeZone};return this.userContext=d,d}getPageMetadata(){try{const e=new URL(window.location.href);let t=document.referrer||"$direct",r="$direct";try{t&&/^https?:\/\//.test(t)&&(r=new URL(t).hostname)}catch(e){}const s=e.search.slice(1);return{$ul_page:e.origin+e.pathname,$ul_pathname:e.pathname,$ul_host:e.host,$ul_referrer:t,$ul_referring_domain:r,$ul_query:s}}catch(e){return{$ul_page:"",$ul_pathname:"",$ul_host:"",$ul_referrer:"",$ul_referring_domain:"",$ul_query:""}}}};
9
9
  //# sourceMappingURL=main.cjs.js.map