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 +92 -445
- package/dist/main.cjs.js +2 -2
- package/dist/main.cjs.js.map +1 -1
- package/dist/main.esm.js +3 -3
- package/dist/main.esm.js.map +1 -1
- package/dist/react.cjs.js +1 -1
- package/dist/react.cjs.js.map +1 -1
- package/dist/react.esm.js +1 -1
- package/dist/react.esm.js.map +1 -1
- package/dist/types/index.d.ts +9 -0
- package/dist/types/react.d.ts +9 -0
- package/dist/userlens.umd.js +3 -3
- package/dist/userlens.umd.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,487 +1,134 @@
|
|
|
1
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
54
|
-
2. **SessionRecorder** – Captures full user session replays.
|
|
5
|
+
📖 **[View Full Documentation](https://userlens.gitbook.io/userlens-analytics/)**
|
|
55
6
|
|
|
56
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
```
|
|
72
|
-
|
|
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
|
-
|
|
23
|
+
## Quick Start (React)
|
|
85
24
|
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
43
|
+
That's it. Every click and page view is now captured.
|
|
193
44
|
|
|
194
|
-
|
|
45
|
+
## Documentation
|
|
195
46
|
|
|
196
|
-
|
|
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
|
-
|
|
199
|
-
- You lose control over event delivery
|
|
56
|
+
### Backend Proxy Guides
|
|
200
57
|
|
|
201
|
-
|
|
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
|
-
|
|
66
|
+
## Two Setup Options
|
|
206
67
|
|
|
207
|
-
|
|
68
|
+
### Option A: Client-Side Setup (Quick to setup)
|
|
208
69
|
|
|
209
|
-
|
|
70
|
+
Events go directly to Userlens API.
|
|
210
71
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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
|
-
|
|
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
|
-
|
|
370
|
-
|
|
113
|
+
function UpgradeButton() {
|
|
114
|
+
const { collector } = useUserlens();
|
|
371
115
|
|
|
372
|
-
|
|
116
|
+
const handleUpgrade = () => {
|
|
117
|
+
collector?.pushEvent({
|
|
118
|
+
event: 'Plan Upgraded',
|
|
119
|
+
properties: { plan: 'pro' },
|
|
120
|
+
});
|
|
121
|
+
};
|
|
373
122
|
|
|
374
|
-
|
|
375
|
-
if (collector) {
|
|
376
|
-
collector.pushEvent({ event: "Something" });
|
|
123
|
+
return <button onClick={handleUpgrade}>Upgrade</button>;
|
|
377
124
|
}
|
|
378
125
|
```
|
|
379
126
|
|
|
380
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:{}},
|
|
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
|