userpath-js 0.0.0 → 0.0.2

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.
Files changed (59) hide show
  1. package/README.md +609 -0
  2. package/dist/index.js +1 -0
  3. package/dist/server/index.js +1 -0
  4. package/dist/types/client/src/base/api.d.ts +12 -0
  5. package/dist/types/client/src/base/api.d.ts.map +1 -0
  6. package/dist/types/client/src/base/events.d.ts +184 -0
  7. package/dist/types/client/src/base/events.d.ts.map +1 -0
  8. package/dist/types/client/src/base/session.d.ts +68 -0
  9. package/dist/types/client/src/base/session.d.ts.map +1 -0
  10. package/dist/types/client/src/index.d.ts +57 -0
  11. package/dist/types/client/src/index.d.ts.map +1 -0
  12. package/dist/types/client/src/pixel.d.ts +2 -0
  13. package/dist/types/client/src/pixel.d.ts.map +1 -0
  14. package/dist/types/client/src/schemas/config.d.ts +32 -0
  15. package/dist/types/client/src/schemas/config.d.ts.map +1 -0
  16. package/dist/types/client/src/schemas/events.d.ts +58 -0
  17. package/dist/types/client/src/schemas/events.d.ts.map +1 -0
  18. package/dist/types/client/src/schemas/identity.d.ts +7 -0
  19. package/dist/types/client/src/schemas/identity.d.ts.map +1 -0
  20. package/dist/types/client/src/server/index.d.ts +42 -0
  21. package/dist/types/client/src/server/index.d.ts.map +1 -0
  22. package/dist/types/core/src/browser/index.d.ts +4 -0
  23. package/dist/types/core/src/browser/index.d.ts.map +1 -0
  24. package/dist/types/core/src/browser/libs/LocalStorage.d.ts +11 -0
  25. package/dist/types/core/src/browser/libs/LocalStorage.d.ts.map +1 -0
  26. package/dist/types/core/src/browser/libs/cookie.d.ts +27 -0
  27. package/dist/types/core/src/browser/libs/cookie.d.ts.map +1 -0
  28. package/dist/types/core/src/browser/libs/memory.d.ts +4 -0
  29. package/dist/types/core/src/browser/libs/memory.d.ts.map +1 -0
  30. package/dist/types/src/base/api.d.ts +12 -0
  31. package/dist/types/src/base/api.d.ts.map +1 -0
  32. package/dist/types/src/base/events.d.ts +184 -0
  33. package/dist/types/src/base/events.d.ts.map +1 -0
  34. package/dist/types/src/base/session.d.ts +64 -0
  35. package/dist/types/src/base/session.d.ts.map +1 -0
  36. package/dist/types/src/index.d.ts +57 -0
  37. package/dist/types/src/index.d.ts.map +1 -0
  38. package/dist/types/src/pixel.d.ts +2 -0
  39. package/dist/types/src/pixel.d.ts.map +1 -0
  40. package/dist/types/src/schemas/config.d.ts +32 -0
  41. package/dist/types/src/schemas/config.d.ts.map +1 -0
  42. package/dist/types/src/schemas/events.d.ts +58 -0
  43. package/dist/types/src/schemas/events.d.ts.map +1 -0
  44. package/dist/types/src/schemas/identity.d.ts +7 -0
  45. package/dist/types/src/schemas/identity.d.ts.map +1 -0
  46. package/dist/types/src/server/index.d.ts +42 -0
  47. package/dist/types/src/server/index.d.ts.map +1 -0
  48. package/package.json +25 -4
  49. package/dist/pixel.js +0 -1
  50. package/examples/usage.ts +0 -83
  51. package/src/clients/api.ts +0 -92
  52. package/src/clients/events.ts +0 -55
  53. package/src/clients/identity.ts +0 -54
  54. package/src/clients/session.ts +0 -47
  55. package/src/index.ts +0 -72
  56. package/src/pixel.ts +0 -28
  57. package/src/schemas/config.ts +0 -4
  58. package/src/schemas/events.ts +0 -31
  59. package/src/schemas/identity.ts +0 -6
package/README.md ADDED
@@ -0,0 +1,609 @@
1
+ # UserPath Client SDK
2
+
3
+ UserPath is a privacy-focused analytics tool that serves as an alternative to Google Analytics. It's built with a focus on reliability, performance, and data privacy. The platform offers first-party integration capabilities, allowing you to collect and analyze user data directly from your own servers without external dependencies, resulting in more accurate and comprehensive insights.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install userpath-js
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ UserPath can be used both in the browser and server in different contexts.
14
+
15
+ ### NPM
16
+
17
+ In a browser environment, you can import the `UserPath` class and initialize it with your app ID and base URL. The library will automatically track events and send them to the server, but you can also track custom events.
18
+
19
+ ```ts
20
+ // file: src/userpath.ts
21
+ import { UserPath } from 'userpath-js';
22
+
23
+ const up = new UserPath({
24
+ appId: 'my-app-id',
25
+ serverUrl: 'https://my-userpath-domain.com/events', // Optional
26
+ version: 1,
27
+ flushIntervalMs: 5000, // Optional: Time in milliseconds between event batch sends (default: 5000)
28
+ });
29
+
30
+ export { up };
31
+ ```
32
+
33
+ ```ts
34
+ // file: src/pricing.ts
35
+ import { up } from './userpath';
36
+
37
+ up.track('purchase', {
38
+ price: 42,
39
+ amount: 10,
40
+ currency: 'USD',
41
+ properties: {
42
+ product_id: '123',
43
+ product_name: 'Product 1',
44
+ },
45
+ });
46
+ ```
47
+
48
+ ### Browser
49
+
50
+ Alternatively, you can include the UserPath Pixel using a script tag in your HTML file. This will initialize the client and automatically start tracking events. You can also track custom events using the exposed `userpath` object in the global scope.
51
+
52
+ ```html
53
+ <!-- index.html -->
54
+ <script
55
+ src="https://my-userpath-domain.com/events/v1/px.js"
56
+ data-up-app="my-app-id"
57
+ ></script>
58
+ ```
59
+
60
+ ```js
61
+ // index.js
62
+ const up = window.userpath;
63
+ up.track('purchase', {
64
+ price: 42,
65
+ amount: 10,
66
+ currency: 'USD',
67
+ });
68
+ ```
69
+
70
+ ### Server Integration
71
+
72
+ In order to avoid privacy-first browsers and ad blockers, you can use the UserPath server integration to provide a first-party analytics solution. This will proxy all requests to the main UserPath server, so you can continue to use the client in the browser as usual without worrying about being blocked.
73
+
74
+ ```ts
75
+ // file: src/index.ts
76
+ import { userpath } from 'userpath-js/server';
77
+
78
+ // Using Node.js
79
+ http
80
+ .createServer(function (req, res) {
81
+ return userpath.fetch(req);
82
+ })
83
+ .listen(3000);
84
+
85
+ // Using Bun
86
+ Bun.serve({
87
+ fetch(req) {
88
+ return userpath.fetch(req);
89
+ },
90
+ });
91
+
92
+ // Using Elysia
93
+ const app = new Elysia({ name: 'my-userpath' });
94
+
95
+ app.get('/', () => 'Hello World from Elysia');
96
+
97
+ app.mount('/events', userpath.fetch); // Mount the UserPath server
98
+
99
+ app.listen(3000);
100
+ ```
101
+
102
+ UserPath also provides easy and native integrations for the most popular server frameworks:
103
+
104
+ - [Elysia](./elysia/README.md)
105
+ - [Express](./express/README.md) (soon)
106
+ - [Fastify](./fastify/README.md) (soon)
107
+ - [Koa](./koa/README.md) (soon)
108
+ - [Nest](./nest/README.md) (soon)
109
+ - [Next](./next/README.md) (soon)
110
+ - [Nuxt](./nuxt/README.md) (soon)
111
+
112
+ ## Automatic Event Tracking
113
+
114
+ When using the client in the browser, UserPath automatically tracks various user interactions without any additional configuration. Here's what gets tracked automatically, but you can also track custom events.
115
+
116
+ ### Form Interactions
117
+
118
+ #### Form Submissions (`form_submit`)
119
+
120
+ Tracks when users submit forms:
121
+
122
+ ```ts
123
+ {
124
+ name: 'form_submit',
125
+ url: 'https://example.com/signup',
126
+ referrer: 'https://example.com/home',
127
+ properties: {
128
+ form_name: 'signup-form', // Form name, id, or aria-label
129
+ time_to_complete: 15000, // Time in milliseconds from first interaction to submission
130
+ }
131
+ }
132
+ ```
133
+
134
+ #### Text Input Fields (`input_fill`)
135
+
136
+ Tracks when users complete filling in text-based input fields. Events are only fired when:
137
+
138
+ - The input value has actually changed
139
+ - The new value is not empty
140
+ - The user has finished editing (on blur)
141
+
142
+ ```ts
143
+ {
144
+ name: 'input_fill',
145
+ url: 'https://example.com/signup',
146
+ referrer: 'https://example.com/home',
147
+ properties: {
148
+ input_type: 'text', // text, email, number, etc.
149
+ label: 'Full Name', // Input label or identifier
150
+ form_name: 'signup-form', // If available
151
+ }
152
+ }
153
+ ```
154
+
155
+ #### Select Dropdowns (`input_select`)
156
+
157
+ Tracks when users select an option from dropdowns:
158
+
159
+ ```ts
160
+ {
161
+ name: 'input_select',
162
+ url: 'https://example.com/checkout',
163
+ referrer: 'https://example.com/cart',
164
+ properties: {
165
+ input_type: 'select',
166
+ label: 'Country',
167
+ option_label: 'United States',
168
+ form_name: 'shipping-form',
169
+ }
170
+ }
171
+ ```
172
+
173
+ #### Radio Buttons (`input_radio`)
174
+
175
+ Tracks radio button selections:
176
+
177
+ ```ts
178
+ {
179
+ name: 'input_radio',
180
+ url: 'https://example.com/profile',
181
+ referrer: 'https://example.com/settings',
182
+ properties: {
183
+ input_type: 'radio',
184
+ group: 'gender',
185
+ label: 'Male',
186
+ form_name: 'profile-form',
187
+ }
188
+ }
189
+ ```
190
+
191
+ #### Checkboxes (`input_checkbox`)
192
+
193
+ Tracks checkbox interactions:
194
+
195
+ ```ts
196
+ {
197
+ name: 'input_checkbox',
198
+ url: 'https://example.com/preferences',
199
+ referrer: 'https://example.com/signup',
200
+ properties: {
201
+ input_type: 'checkbox',
202
+ label: 'Subscribe to newsletter',
203
+ checked: true,
204
+ form_name: 'preferences-form',
205
+ }
206
+ }
207
+ ```
208
+
209
+ #### Range Inputs (`input_range`)
210
+
211
+ Tracks range slider changes:
212
+
213
+ ```ts
214
+ {
215
+ name: 'input_range',
216
+ url: 'https://example.com/settings',
217
+ referrer: 'https://example.com/profile',
218
+ properties: {
219
+ input_type: 'range',
220
+ label: 'Volume',
221
+ form_name: 'settings-form',
222
+ }
223
+ }
224
+ ```
225
+
226
+ ### Click Tracking
227
+
228
+ Automatically tracks meaningful clicks on:
229
+
230
+ - Buttons
231
+ - Links
232
+ - Interactive elements with labels
233
+
234
+ ```ts
235
+ {
236
+ name: 'click',
237
+ url: 'https://example.com/form',
238
+ referrer: 'https://example.com/home',
239
+ properties: {
240
+ label: 'Submit',
241
+ element: 'button',
242
+ id: 'submit-btn',
243
+ }
244
+ }
245
+ ```
246
+
247
+ ### Scroll Tracking
248
+
249
+ Tracks meaningful scroll depth on pages:
250
+
251
+ ```ts
252
+ {
253
+ name: 'scroll',
254
+ url: 'https://example.com/blog/post',
255
+ title: 'Blog Post Title',
256
+ referrer: 'https://example.com'
257
+ }
258
+ ```
259
+
260
+ ### Video Tracking
261
+
262
+ Automatically tracks video interactions for HTML5 video elements:
263
+
264
+ #### Video Play (`video_play`)
265
+
266
+ Tracks when a video starts playing:
267
+
268
+ ```ts
269
+ {
270
+ name: 'video_play',
271
+ url: 'https://example.com/page',
272
+ referrer: 'https://example.com',
273
+ title: 'Introduction Video', // Video title if available
274
+ properties: {
275
+ duration: 180, // Total video duration in seconds
276
+ current_time: 0, // Current playback position in seconds
277
+ src: 'https://example.com/video.mp4',
278
+ video_id: 'intro-video', // Video element ID if available
279
+ }
280
+ }
281
+ ```
282
+
283
+ #### Video Pause (`video_pause`)
284
+
285
+ Tracks when a video is paused:
286
+
287
+ ```ts
288
+ {
289
+ name: 'video_pause',
290
+ url: 'https://example.com/page',
291
+ referrer: 'https://example.com',
292
+ title: 'Introduction Video',
293
+ properties: {
294
+ duration: 180,
295
+ current_time: 45,
296
+ watch_time: 45, // Time watched in this session in seconds
297
+ src: 'https://example.com/video.mp4',
298
+ video_id: 'intro-video',
299
+ }
300
+ }
301
+ ```
302
+
303
+ #### Video Complete (`video_complete`)
304
+
305
+ Tracks when a video playback completes:
306
+
307
+ ```ts
308
+ {
309
+ name: 'video_complete',
310
+ url: 'https://example.com/page',
311
+ referrer: 'https://example.com',
312
+ title: 'Introduction Video',
313
+ properties: {
314
+ duration: 180,
315
+ current_time: 180,
316
+ watch_time: 180, // Total time watched in this session in seconds
317
+ src: 'https://example.com/video.mp4',
318
+ video_id: 'intro-video',
319
+ }
320
+ }
321
+ ```
322
+
323
+ ### Page Time Tracking
324
+
325
+ Automatically tracks how long users spend on pages, including active (visible) time:
326
+
327
+ ### User Activity Tracking
328
+
329
+ Automatically tracks user activity and inactivity periods:
330
+
331
+ #### User Inactive (`user_inactive`)
332
+
333
+ Tracks when a user becomes inactive (no mouse movement, keyboard input, or other interactions for 60 seconds):
334
+
335
+ ```ts
336
+ {
337
+ name: 'user_inactive',
338
+ url: 'https://example.com/page',
339
+ title: 'Page Title'
340
+ }
341
+ ```
342
+
343
+ #### User Active (`user_active`)
344
+
345
+ Tracks when a user becomes active again after being inactive:
346
+
347
+ ```ts
348
+ {
349
+ name: 'user_active',
350
+ properties: {
351
+ inactive_duration: 75, // How long the user was inactive in seconds
352
+ url: 'https://example.com/page',
353
+ title: 'Page Title'
354
+ }
355
+ }
356
+ ```
357
+
358
+ #### Page View (`page_view`)
359
+
360
+ Tracks when a page is initially loaded:
361
+
362
+ ```ts
363
+ {
364
+ name: 'page_view',
365
+ url: 'https://example.com/page',
366
+ title: 'Page Title',
367
+ referrer: 'https://example.com'
368
+ }
369
+ ```
370
+
371
+ #### Page Visibility (`page_visibility`)
372
+
373
+ Tracks when users switch tabs or minimize the browser:
374
+
375
+ ```ts
376
+ {
377
+ name: 'page_visibility',
378
+ url: 'https://example.com/page',
379
+ title: 'Page Title',
380
+ referrer: 'https://example.com',
381
+ properties: {
382
+ visible: true, // or false
383
+ total_time: 120, // Total time since page load in seconds
384
+ visible_time: 85 // Time the page was actually visible in seconds
385
+ }
386
+ }
387
+ ```
388
+
389
+ #### Page Exit (`page_exit`)
390
+
391
+ Tracks timing information when users leave the page:
392
+
393
+ ```ts
394
+ {
395
+ name: 'page_exit',
396
+ url: 'https://example.com/page',
397
+ title: 'Page Title',
398
+ referrer: 'https://example.com',
399
+ properties: {
400
+ total_time: 300, // Total time spent on page in seconds
401
+ visible_time: 240 // Time the page was actually visible in seconds
402
+ }
403
+ }
404
+ ```
405
+
406
+ ### Privacy Considerations
407
+
408
+ - Password fields are never tracked
409
+ - Only interaction events are tracked, not actual input values
410
+ - Form submissions are tracked without capturing sensitive data
411
+ - All tracking respects user privacy settings and GDPR compliance
412
+
413
+ ### Error Tracking
414
+
415
+ UserPath automatically captures uncaught errors and unhandled promise rejections in your application, providing valuable insights about JavaScript errors that users encounter.
416
+
417
+ #### Automatic Error Tracking
418
+
419
+ When enabled (on by default), the SDK will automatically capture:
420
+
421
+ - Uncaught exceptions via the global `error` event
422
+ - Unhandled promise rejections via the `unhandledrejection` event
423
+
424
+ ```ts
425
+ {
426
+ name: 'error',
427
+ message: 'Cannot read property of undefined',
428
+ stack: 'Error: Cannot read property of undefined\n at button.onClick (app.js:42)',
429
+ source: 'window.onerror',
430
+ filename: 'app.js',
431
+ lineno: 42,
432
+ colno: 24
433
+ }
434
+ ```
435
+
436
+ #### Manual Error Tracking
437
+
438
+ You can also manually track errors in try/catch blocks:
439
+
440
+ ```ts
441
+ try {
442
+ // Some code that might throw an error
443
+ processUserPurchase();
444
+ } catch (error) {
445
+ // Log the error to UserPath
446
+ up.trackError(error, {
447
+ context: 'purchase_flow',
448
+ product_id: '123',
449
+ });
450
+
451
+ // Handle the error appropriately
452
+ showErrorMessage();
453
+ }
454
+ ```
455
+
456
+ #### Disabling Automatic Tracking Features
457
+
458
+ UserPath automatically tracks various user interactions, but you can customize which features are enabled:
459
+
460
+ ```ts
461
+ // All tracking options are enabled by default
462
+ const up = new UserPath({
463
+ appId: 'my-app-id',
464
+ autoTrack: {
465
+ errors: true, // Track JavaScript errors
466
+ clicks: true, // Track clicks on buttons, links, etc.
467
+ scrolling: true, // Track scroll depth
468
+ forms: true, // Track form interactions and submissions
469
+ videos: true, // Track video play/pause/end events
470
+ pageVisibility: true, // Track page visibility and time spent
471
+ inactivity: true, // Track user activity/inactivity
472
+ },
473
+ });
474
+ ```
475
+
476
+ You can disable any specific tracking feature individually:
477
+
478
+ ```ts
479
+ const up = new UserPath({
480
+ appId: 'my-app-id',
481
+ autoTrack: {
482
+ forms: false, // Disable form tracking
483
+ videos: false, // Disable video tracking
484
+ },
485
+ });
486
+ ```
487
+
488
+ You can also disable error tracking after initialization:
489
+
490
+ ```ts
491
+ up.uninstallErrorHandler();
492
+ ```
493
+
494
+ And re-enable it later if needed:
495
+
496
+ ```ts
497
+ up.installErrorHandler();
498
+ ```
499
+
500
+ ## User Identification
501
+
502
+ There are several ways to identify users in UserPath:
503
+
504
+ ### Via Script Tag
505
+
506
+ You can add a `data-up-user` attribute to your script tag to identify users immediately:
507
+
508
+ ```html
509
+ <script
510
+ src="https://api.userpath.co/v1/px.js"
511
+ data-up-app="your-app-id"
512
+ data-up-user="user123"
513
+ ></script>
514
+ ```
515
+
516
+ ### Via SDK
517
+
518
+ You can also identify users programmatically using the SDK:
519
+
520
+ ```js
521
+ // On initialization
522
+ const up = new UserPath({
523
+ appId: 'your-app-id',
524
+ userId: 'user123',
525
+ });
526
+
527
+ // Set user ID programmatically
528
+ up.setUserId('user123');
529
+
530
+ // Or with additional user information
531
+ up.identify({
532
+ id: 'user123',
533
+ email: 'user@example.com',
534
+ name: 'John Doe',
535
+ properties: {
536
+ plan: 'premium',
537
+ signupDate: '2023-01-15',
538
+ },
539
+ });
540
+ ```
541
+
542
+ ### Retrieving User ID
543
+
544
+ You can get the current user ID:
545
+
546
+ ```js
547
+ const currentUserId = userpath.getUserId();
548
+ ```
549
+
550
+ ## Questions
551
+
552
+ Got questions? We're here to help!
553
+
554
+ Email us at `support@userpath.co`
555
+
556
+ ## Configuration Options
557
+
558
+ When initializing the UserPath SDK, you can provide several configuration options:
559
+
560
+ | Option | Type | Default | Description |
561
+ | ----------------- | ------ | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
562
+ | `appId` | string | (required) | Your UserPath application ID |
563
+ | `serverUrl` | string | "https://api.userpath.co" | The base URL for the UserPath API |
564
+ | `version` | number | 1 | API version to use |
565
+ | `userId` | string | undefined | Optional user ID to associate with all events |
566
+ | `flushIntervalMs` | number | 5000 | Time in milliseconds between event batch sends. Events are batched and sent in groups rather than individually to reduce server load. |
567
+ | `autoTrack` | object | see below | Configuration for automatic event tracking |
568
+
569
+ ### Auto-tracking Configuration
570
+
571
+ ```ts
572
+ // All tracking options are enabled by default
573
+ const up = new UserPath({
574
+ appId: 'my-app-id',
575
+ autoTrack: {
576
+ errors: true, // Track JavaScript errors
577
+ clicks: true, // Track clicks on buttons, links, etc.
578
+ scrolling: true, // Track scroll depth
579
+ forms: true, // Track form interactions and submissions
580
+ videos: true, // Track video play/pause/end events
581
+ pageVisibility: true, // Track page visibility and time spent
582
+ inactivity: true, // Track user activity/inactivity
583
+ },
584
+ });
585
+ ```
586
+
587
+ You can disable any specific tracking feature individually:
588
+
589
+ ```ts
590
+ const up = new UserPath({
591
+ appId: 'my-app-id',
592
+ autoTrack: {
593
+ forms: false, // Disable form tracking
594
+ videos: false, // Disable video tracking
595
+ },
596
+ });
597
+ ```
598
+
599
+ You can also disable error tracking after initialization:
600
+
601
+ ```ts
602
+ up.uninstallErrorHandler();
603
+ ```
604
+
605
+ And re-enable it later if needed:
606
+
607
+ ```ts
608
+ up.installErrorHandler();
609
+ ```
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ var N={name:"userpath-js",version:"0.0.1",private:!1,main:"dist/index.js",types:"dist/types/src/index.d.ts",exports:{".":{types:"./dist/types/src/index.d.ts",default:"./dist/index.js"},"./server":{types:"./dist/types/src/server/index.d.ts",default:"./dist/server/index.js"}},files:["dist/index.js","dist/types","dist/server"],scripts:{dev:"bun build src/index.ts --outdir ./dist --watch",types:"tsc",build:"bun run build.client && bun run build.pixel && bun run build.server","build.prod":"bun run build.client.prod && bun run build.pixel.prod && bun run build.server.prod","build.client":"bun run types && bun build src/index.ts --outdir ./dist --no-cache","build.client.prod":"bun run types && bun build src/index.ts --outdir ./dist --minify","build.pixel":"bun build src/pixel.ts --outdir ./dist","build.pixel.prod":"bun build src/pixel.ts --outdir ./dist --minify","build.server":"bun run types && bun build src/server/index.ts --outdir ./dist/server","build.server.prod":"bun run types && bun build src/server/index.ts --outdir ./dist/server --minify",prepublish:"bun run build.prod"},devDependencies:{"bun-types":"latest",typescript:"^5.0.0"}};var j={appId:"",serverUrl:"https://api.userpath.co",version:1,flushIntervalMs:5000,autoTrack:{errors:!0,clicks:!0,scrolling:!0,forms:!0,videos:!0,pageVisibility:!0,pageInactivity:!0}};class M{serverUrl;appId;version;constructor(x){this.serverUrl=x.serverUrl||"",this.appId=x.appId,this.version=x.version||j.version}buildUrl(x){return`${this.serverUrl}/v${this.version}/${x}`}}var E=typeof window==="undefined",w={pushState:()=>{},replaceState:()=>{},go:()=>{},back:()=>{},forward:()=>{}};if(E)global.history=w;var D={location:{href:""},addEventListener:()=>{},removeEventListener:()=>{},scrollTo:()=>{},scrollBy:()=>{},setTimeout:()=>{},clearTimeout:()=>{},setInterval:()=>{},clearInterval:()=>{}};if(E)global.window=D;var P={hidden:!1,addEventListener:()=>{},removeEventListener:()=>{},querySelector:()=>null,closest:()=>null,getAttribute:()=>null,tagName:"",textContent:"",classList:{contains:()=>!1},scrollHeight:0,offsetHeight:0,clientHeight:0,pageYOffset:0,referrer:"",title:""};if(E)global.document=P;class Q{#R;#x;#$;#_;#j;#W;#X;#J;#Y;#A;#w;#D;#P;#L;#b;#Z;#V;#E;#G;#K;#g;#Q;#q;#z;#C;#M;#O;#F;#u=60000;#B;#N=!1;#y;#U;#I;constructor(x,$=j){this.#R=x,this.#I=$,this.#x=new Set,this.#$=0,this.#Z=null,this.#V=window.location.href,this.#E=new Map,this.#G=new WeakMap,this.#K=new WeakMap,this.#g=Date.now(),this.#Q=0,this.#q=this.#g,this.#z=document.hidden,this.#C=Date.now(),this.#M=null,this.#O=!1,this.#B=null,this.#_=this.handleClick.bind(this),this.#j=this.handleScroll.bind(this),this.#W=this.handleScroll.bind(this),this.#X=this.handleInputFocus.bind(this),this.#J=this.handleInputBlur.bind(this),this.#Y=this.handleFormChange.bind(this),this.#A=this.handleFormSubmit.bind(this),this.#w=this.handleVideoPlay.bind(this),this.#D=this.handleVideoPause.bind(this),this.#P=this.handleVideoEnd.bind(this),this.#L=this.handleVisibilityChange.bind(this),this.#b=this.handleBeforeUnload.bind(this),this.#F=this.handleUserActivity.bind(this),this.#y=this.handleError.bind(this),this.#U=this.handleRejection.bind(this);let R=this.#I.autoTrack||j.autoTrack;if(R.clicks!==!1)this.setupClickTracking();if(R.scrolling!==!1)this.setupScrollTracking();if(R.forms!==!1)this.setupFormTracking();if(R.videos!==!1)this.setupVideoTracking();if(R.pageVisibility!==!1)this.setupPageTimeTracking();if(R.pageInactivity!==!1)this.setupInactivityTracking()}destroy(){if(document.removeEventListener("click",this.#_),window.removeEventListener("scroll",this.#j),window.removeEventListener("resize",this.#W),document.removeEventListener("focus",this.#X,!0),document.removeEventListener("blur",this.#J,!0),document.removeEventListener("change",this.#Y,!0),document.removeEventListener("submit",this.#A,!0),document.removeEventListener("play",this.#w,!0),document.removeEventListener("pause",this.#D,!0),document.removeEventListener("ended",this.#P,!0),document.removeEventListener("visibilitychange",this.#L),window.removeEventListener("beforeunload",this.#b),this.#Z)window.clearTimeout(this.#Z);if(["mousemove","mousedown","keydown","touchstart","scroll"].forEach((x)=>{document.removeEventListener(x,this.#F,!0)}),this.#M)window.clearTimeout(this.#M);this.uninstallErrorHandler()}setupClickTracking(){document.addEventListener("click",this.#_)}handleClick(x){let $=x.target;if(!$)return;let R=this.extractClickLabel($);if(!R)return;this.trackCustomEvent("click",{label:R,element:$.tagName.toLowerCase(),id:$.id||void 0})}handleScroll(){let x=window.location.href;if(x!==this.#V)this.#V=x,this.#$=0,this.#x.clear();if(this.#Z)window.clearTimeout(this.#Z);this.#Z=window.setTimeout(()=>{let $=this.calculateScrollDepth();if($>this.#$)this.#$=$,this.checkAndTrackScrollDepth($)},100)}setupScrollTracking(){window.addEventListener("scroll",this.#j),window.addEventListener("resize",this.#W)}calculateScrollDepth(){let x=window.innerHeight,$=Math.max(document.documentElement.scrollHeight,document.documentElement.offsetHeight,document.documentElement.clientHeight),R=window.pageYOffset||document.documentElement.scrollTop;return Math.round((R+x)/$*100)}checkAndTrackScrollDepth(x){let $=window.location.href;if(x>50&&!this.#x.has($))this.#x.add($),this.trackCustomEvent("scroll")}extractClickLabel(x){let $=x.getAttribute("aria-label");if($)return`${$}`;if(x instanceof HTMLButtonElement||x instanceof HTMLAnchorElement){let _=x.textContent?.trim();if(_)return`${_}`}if(x instanceof HTMLInputElement){if(x.type==="submit"&&x.value)return`${x.value}`;if(x.type==="checkbox"){let _=this.findInputLabel(x),J=x.checked?"Checked":"Unchecked";return _?`${J} ${_}`:null}}let R=x.closest("label");if(R){let _=R.textContent?.trim();if(_)return`${_}`}return null}findInputLabel(x){let $=x.getAttribute("aria-label");if($)return $;if(x.id){let _=document.querySelector(`label[for="${x.id}"]`);if(_&&_.textContent)return _.textContent.trim()}let R=x.closest("label");if(R&&R.textContent)return R.textContent.trim().replace(x.value,"").trim();return null}trackPageView(){let x={name:"page_view",url:window.location.href,referrer:document.referrer,title:document.title,timestamp:new Date().toISOString()};this.#R.track(x)}trackCustomEvent(x,$={},R={immediate:!1}){let _={name:x,url:window.location.href,referrer:document.referrer,title:document.title,properties:$,timestamp:new Date().toISOString()};if(R.immediate)this.#R.publish([_]);else this.#R.track(_)}trackPurchase(x){let $={name:"purchase",url:window.location.href,referrer:document.referrer,title:document.title,productId:x.productId,price:x.price,currency:x.currency,properties:{...x.properties},timestamp:new Date().toISOString()};this.#R.track($)}setupFormTracking(){document.addEventListener("focus",this.#X,!0),document.addEventListener("blur",this.#J,!0),document.addEventListener("change",this.#Y,!0),document.addEventListener("submit",this.#A,!0)}handleInputFocus(x){let $=x.target;if(!$||!($ instanceof HTMLInputElement||$ instanceof HTMLTextAreaElement||$ instanceof HTMLSelectElement))return;if($ instanceof HTMLInputElement&&$.type==="password")return;this.#G.set($,this.getElementValue($));let R=$.closest("form");if(R&&!this.#E.has(R))this.#E.set(R,Date.now())}getElementValue(x){if(x instanceof HTMLSelectElement)return x.value;if(x instanceof HTMLInputElement||x instanceof HTMLTextAreaElement)return x.value;return""}handleInputBlur(x){let $=x.target;if(!$||!($ instanceof HTMLInputElement||$ instanceof HTMLTextAreaElement||$ instanceof HTMLSelectElement))return;if($ instanceof HTMLInputElement&&$.type==="password")return;let R=this.#G.get($)||"",_=this.getElementValue($);if(_!==R&&_.trim()!==""){let J=$ instanceof HTMLSelectElement?"select":$.type,W=this.findFormElementLabel($)||this.getInputIdentifier($);if(!W)return;this.trackCustomEvent("input_fill",{input_type:J,label:W,form_name:this.getFormName($)})}this.#G.delete($)}getFormName(x){let $=x.closest("form");if(!$)return;return $.getAttribute("name")||$.getAttribute("id")||$.getAttribute("aria-label")||void 0}getInputIdentifier(x){return x.getAttribute("name")||x.getAttribute("id")||x.getAttribute("placeholder")||null}findFormElementLabel(x){let $=x.getAttribute("aria-label");if($)return $;if(x.id){let _=document.querySelector(`label[for="${x.id}"]`);if(_&&_.textContent)return _.textContent.trim()}let R=x.closest("label");if(R&&R.textContent){let _=R.textContent.trim(),J=x instanceof HTMLSelectElement?x.selectedOptions[0]?.text:x.value;return J?_.replace(J,"").trim():_}return null}handleFormChange(x){let $=x.target;if(!$||!($ instanceof HTMLSelectElement||$ instanceof HTMLInputElement))return;if($ instanceof HTMLSelectElement)this.trackSelectChange($);else if($ instanceof HTMLInputElement){if($.type==="radio")this.trackRadioChange($);else if($.type==="checkbox")this.trackCheckboxChange($);else if($.type==="range")this.trackRangeChange($)}}trackSelectChange(x){let $=this.findFormElementLabel(x)||this.getInputIdentifier(x);if(!$)return;let R=x.selectedOptions[0];this.trackCustomEvent("input_select",{input_type:"select",label:$,option_label:R?.text||"",form_name:this.getFormName(x)})}trackRadioChange(x){let $=this.findFormElementLabel(x)||this.getInputIdentifier(x);if(!$)return;let R=x.name||$;this.trackCustomEvent("input_radio",{input_type:"radio",group:R,label:$,form_name:this.getFormName(x)})}trackCheckboxChange(x){let $=this.findFormElementLabel(x)||this.getInputIdentifier(x);if(!$)return;this.trackCustomEvent("input_checkbox",{input_type:"checkbox",label:$,checked:x.checked,form_name:this.getFormName(x)})}trackRangeChange(x){let $=this.findFormElementLabel(x)||this.getInputIdentifier(x);if(!$)return;this.trackCustomEvent("input_range",{input_type:"range",label:$,form_name:this.getFormName(x)})}handleFormSubmit(x){let $=x.target;if(!$||!($ instanceof HTMLFormElement))return;let R=this.#E.get($)||Date.now(),_=Date.now()-R,J=this.getFormName($)||"unknown";this.trackCustomEvent("form_submit",{form_name:J,time_to_complete:_}),this.#E.delete($)}setupVideoTracking(){document.addEventListener("play",this.#w,!0),document.addEventListener("pause",this.#D,!0),document.addEventListener("ended",this.#P,!0)}getVideoMetadata(x){return{duration:Math.round(x.duration),current_time:Math.round(x.currentTime),src:x.currentSrc||x.src||void 0,title:x.title||void 0,video_id:x.id||void 0}}handleVideoPlay(x){let $=x.target;if(!$||!($ instanceof HTMLVideoElement))return;this.#K.set($,Date.now()),this.trackCustomEvent("video_play",this.getVideoMetadata($))}handleVideoPause(x){let $=x.target;if(!$||!($ instanceof HTMLVideoElement))return;let R=this.#K.get($),_=this.getVideoMetadata($);if(R)_.watch_time=Math.round((Date.now()-R)/1000),this.#K.delete($);this.trackCustomEvent("video_pause",_)}handleVideoEnd(x){let $=x.target;if(!$||!($ instanceof HTMLVideoElement))return;let R=this.#K.get($),_=this.getVideoMetadata($);if(R)_.watch_time=Math.round((Date.now()-R)/1000),this.#K.delete($);this.trackCustomEvent("video_complete",_)}setupPageTimeTracking(){document.addEventListener("visibilitychange",this.#L),window.addEventListener("beforeunload",this.#b)}handleVisibilityChange(){let x=Date.now(),$=this.#z;if(this.#z=!document.hidden,$)this.#Q+=x-this.#q;this.#q=x,this.trackCustomEvent("page_visibility",{visible:this.#z,total_time:Math.round((x-this.#g)/1000),visible_time:Math.round(this.#Q/1000)})}handleBeforeUnload(){let x=Date.now();if(this.#z)this.#Q+=x-this.#q;this.trackCustomEvent("page_exit",{total_time:Math.round((x-this.#g)/1000),visible_time:Math.round(this.#Q/1000)},{immediate:!0})}setupInactivityTracking(){["mousemove","mousedown","keydown","touchstart","scroll"].forEach((x)=>{document.addEventListener(x,this.#F,!0)}),this.checkInactivity()}handleUserActivity(){if(this.#C=Date.now(),this.#O&&this.#B)this.#O=!1,this.trackCustomEvent("user_active",{inactive_duration:Math.round((Date.now()-this.#B)/1000),url:window.location.href,title:document.title}),this.#B=null;this.checkInactivity()}checkInactivity(){if(this.#M)window.clearTimeout(this.#M);this.#M=window.setTimeout(()=>{let x=Date.now();if(x-this.#C>=this.#u&&!this.#O)this.#O=!0,this.#B=x,this.trackCustomEvent("user_inactive")},this.#u)}installErrorHandler(){if(this.#N||typeof window==="undefined")return;window.addEventListener("error",this.#y),window.addEventListener("unhandledrejection",this.#U),this.#N=!0}uninstallErrorHandler(){if(!this.#N||typeof window==="undefined")return;window.removeEventListener("error",this.#y),window.removeEventListener("unhandledrejection",this.#U),this.#N=!1}isErrorHandlerInstalled(){return this.#N}handleError(x){if(x instanceof ErrorEvent)this.trackError(new Error(x.message),{filename:x.filename,lineno:x.lineno,colno:x.colno,source:"window.onerror"})}handleRejection(x){let $=x.reason instanceof Error?x.reason:new Error(String(x.reason||"Unhandled Promise rejection"));this.trackError($,{source:"unhandledrejection"})}trackError(x,$={},R={immediate:!1}){let{filename:_,lineno:J,colno:W,source:K,url:Y,referrer:Z,userAgent:X,...q}=$,B={name:"error",message:x.message,stack:x.stack,source:K||"client",lineno:J,colno:W,filename:_,properties:q,timestamp:new Date().toISOString(),url:Y||window.location.href,referrer:Z||document.referrer,title:document.title};if(R.immediate)this.#R.publish([B]);else this.#R.track(B)}}class z{namespace;constructor({namespace:x}){this.namespace=x}getItem(x){let $=localStorage.getItem(`${this.namespace}.${x}`)||"";try{return JSON.parse($)}catch(R){return null}}setItem(x,$){localStorage.setItem(`${this.namespace}.${x}`,JSON.stringify($))}removeItem(x){localStorage.removeItem(`${this.namespace}.${x}`)}}var f=new z({namespace:"userpath"});function G(x,$,R={}){let{days:_=7,path:J="/",domain:W,secure:K,sameSite:Y="strict"}=R,Z=_?new Date(Date.now()+_*86400000).toUTCString():"",X=`${encodeURIComponent(x)}=${encodeURIComponent($)}`;if(Z)X+=`; expires=${Z}`;if(J)X+=`; path=${J}`;if(W)X+=`; domain=${W}`;if(K)X+="; secure";if(Y)X+=`; samesite=${Y}`;document.cookie=X}function g(x){let $=encodeURIComponent(x)+"=",R=document.cookie.split(";");for(let _=0;_<R.length;_++){let J=R[_];while(J.charAt(0)===" ")J=J.substring(1,J.length);if(J.indexOf($)===0)return decodeURIComponent(J.substring($.length,J.length))}return null}class O{#R;#x=[];#$=null;#_;#j;#W="";#X=null;#J=null;#Y=5000;constructor(x,$){this.#R=x,this.#_=$?.userId,this.#j=$?.appId||"",this.#Y=$?.flushIntervalMs||j.flushIntervalMs||5000}init(x){this.#$=x,this.setup()}destroy(){if(this.#X)window.clearInterval(this.#X);if(this.#J)window.clearTimeout(this.#J);if(this.#x.length>0)this.flush()}setup(){if(typeof window==="undefined")return;this.#W=window.location.href,this.#$.trackPageView(),this.setupHistoryChange(),this.#X=window.setInterval(()=>this.flush(),this.#Y)}setupHistoryChange(){if(!this.#$)return;let{pushState:x,replaceState:$}=history;history.pushState=(...R)=>{x.apply(history,R),this.trackPageViewIfUrlChanged()},history.replaceState=(...R)=>{$.apply(history,R),this.trackPageViewIfUrlChanged()},window.addEventListener("popstate",()=>{this.trackPageViewIfUrlChanged()})}trackPageViewIfUrlChanged(){let x=window.location.href;if(x!==this.#W)this.#W=x,this.#$.trackPageView()}getUserId(){return this.#_}setUserId(x){this.#_=x}getAppId(){return this.#j}setAppId(x){this.#j=x}track(x){if(this.#x.push(x),this.#x.length>=20){this.flush();return}if(!this.#J)this.#J=window.setTimeout(()=>{this.flush(),this.#J=null},this.#Y)}async identify(x){this.setUserId(x.id),await fetch(this.#R.buildUrl("identify"),{method:"POST",headers:{"Content-Type":"application/json","X-UserPath-App":this.#j,"X-UserPath-Session":this.getSessionId(),...this.#_&&{"X-UserPath-User":this.#_}},body:JSON.stringify(x),keepalive:!0})}deduplicateEvents(x){let $=new Set;return x.filter((R)=>{let _=`${R.name}-${R.timestamp}`;if($.has(_))return!1;return $.add(_),!0})}flush(){if(this.#x.length===0)return;if(this.#J)window.clearTimeout(this.#J),this.#J=null;let x=[...this.#x];this.#x=[];let $=this.deduplicateEvents(x);this.publish($).catch((R)=>{this.#x=[...$,...this.#x].slice(0,100),console.error("Error sending analytics data:",R)})}async publish(x){return fetch(this.#R.buildUrl("log"),{method:"POST",headers:{"Content-Type":"application/json","X-UserPath-App":this.#j,"X-UserPath-Session":this.getSessionId(),...this.#_&&{"X-UserPath-User":this.#_}},body:JSON.stringify({userAgent:navigator.userAgent,screenWidth:window.screen.width,screenHeight:window.screen.height,language:navigator.language,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone,events:x.map(($)=>{if(!$.timestamp)$.timestamp=new Date().toISOString();return{...$,name:$.name||$.type,properties:$.properties||{}}})}),credentials:"include",keepalive:!0})}getSessionId(){let x=g("userpath.session_id");if(!x)x=this.generateId(),G("userpath.session_id",x,{days:365});return x}generateId(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(x){let $=Math.random()*16|0;return(x==="x"?$:$&3|8).toString(16)})}}class L{#R;#x;#$;#_=j.version;constructor(x){if(this.#R=new M(x),this.#x=new O(this.#R,x),this.#$=new Q(this.#x,x),this.#_=x.version||this.#_,this.#x.init(this.#$),this.#x.setAppId(x.appId),x.autoTrack?.errors!==void 0?x.autoTrack.errors:!0)this.installErrorHandler();console.debug(`UserPath v${N.version} initialized`)}getAppId(){return this.#x.getAppId()}setAppId(x){this.#x.setAppId(x)}installErrorHandler(){this.#$.installErrorHandler()}uninstallErrorHandler(){this.#$.uninstallErrorHandler()}trackError(x,$={}){if(!this.#$.isErrorHandlerInstalled()&&$.source!=="manual")return;this.#$.trackError(x,$)}setUserId(x){this.#x.setUserId(x)}getUserId(){return this.#x.getUserId()}async identify(x){await this.#x.identify(x)}track(x,$){if(x==="page_view")this.#$.trackPageView();else if(x==="event")this.#$.trackCustomEvent($.name,$.properties||{});else if(x==="purchase")this.#$.trackPurchase({productId:$.properties?.product_id||"",price:$.price,currency:$.currency,properties:$.properties})}}export{L as UserPath,j as DEFAULT_CONFIG};
@@ -0,0 +1 @@
1
+ var M={async fetch(g){let x=g.method,z=new URL(g.url),G=z.pathname,I=`${z.origin}${G}`,A=new Headers;for(let[f,j]of g.headers.entries())if(!f.toLowerCase().startsWith("access-control-"))A.set(f,j);let J=await g.json().catch(()=>null),D={method:x,headers:A};if(!["GET","HEAD","OPTIONS"].includes(x))D.body=JSON.stringify(J);let c=await fetch(I,D);if(c.headers.get("content-type")?.includes("application/json"))return await c.json();else{let f=new Headers;for(let[F,K]of c.headers.entries())if(!F.toLowerCase().startsWith("access-control-"))f.set(F,K);let j=await c.text();return new Response(j,{status:c.status,statusText:c.statusText,headers:f})}}};export{M as userpath};
@@ -0,0 +1,12 @@
1
+ import { Config } from '../schemas/config';
2
+ /**
3
+ * Base API client for UserPath SDK
4
+ */
5
+ export declare class ApiClient {
6
+ serverUrl: string;
7
+ appId: string;
8
+ version: number;
9
+ constructor(config: Config);
10
+ buildUrl(path: string): string;
11
+ }
12
+ //# sourceMappingURL=api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../../../../src/base/api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAkB,MAAM,mBAAmB,CAAC;AAE3D;;GAEG;AACH,qBAAa,SAAS;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IAEd,OAAO,EAAE,MAAM,CAAC;gBAEX,MAAM,EAAE,MAAM;IAM1B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;CAG9B"}