humanbehavior-js 0.4.19 → 0.4.21

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 (66) hide show
  1. package/dist/cjs/angular/index.cjs +799 -13
  2. package/dist/cjs/angular/index.cjs.map +1 -1
  3. package/dist/cjs/index.cjs +815 -13
  4. package/dist/cjs/index.cjs.map +1 -1
  5. package/dist/cjs/install-wizard.cjs +56 -10
  6. package/dist/cjs/install-wizard.cjs.map +1 -1
  7. package/dist/cjs/react/index.cjs +800 -14
  8. package/dist/cjs/react/index.cjs.map +1 -1
  9. package/dist/cjs/remix/index.cjs +800 -14
  10. package/dist/cjs/remix/index.cjs.map +1 -1
  11. package/dist/cjs/svelte/index.cjs +799 -13
  12. package/dist/cjs/svelte/index.cjs.map +1 -1
  13. package/dist/cjs/vue/index.cjs +799 -13
  14. package/dist/cjs/vue/index.cjs.map +1 -1
  15. package/dist/cjs/wizard/index.cjs +56 -10
  16. package/dist/cjs/wizard/index.cjs.map +1 -1
  17. package/dist/cli/ai-auto-install.js +56 -10
  18. package/dist/cli/ai-auto-install.js.map +1 -1
  19. package/dist/cli/auto-install.js +56 -10
  20. package/dist/cli/auto-install.js.map +1 -1
  21. package/dist/esm/angular/index.js +799 -13
  22. package/dist/esm/angular/index.js.map +1 -1
  23. package/dist/esm/index.js +807 -14
  24. package/dist/esm/index.js.map +1 -1
  25. package/dist/esm/install-wizard.js +56 -10
  26. package/dist/esm/install-wizard.js.map +1 -1
  27. package/dist/esm/react/index.js +800 -14
  28. package/dist/esm/react/index.js.map +1 -1
  29. package/dist/esm/remix/index.js +800 -14
  30. package/dist/esm/remix/index.js.map +1 -1
  31. package/dist/esm/svelte/index.js +799 -13
  32. package/dist/esm/svelte/index.js.map +1 -1
  33. package/dist/esm/vue/index.js +799 -13
  34. package/dist/esm/vue/index.js.map +1 -1
  35. package/dist/esm/wizard/index.js +56 -10
  36. package/dist/esm/wizard/index.js.map +1 -1
  37. package/dist/index.min.js +1 -1
  38. package/dist/index.min.js.map +1 -1
  39. package/dist/types/angular/index.d.ts +60 -1
  40. package/dist/types/index.d.ts +258 -3
  41. package/dist/types/react/index.d.ts +60 -1
  42. package/dist/types/remix/index.d.ts +60 -1
  43. package/dist/types/svelte/index.d.ts +60 -1
  44. package/package/canvas-recording-demo.html +1 -1
  45. package/package/simple-spa.html +1 -1
  46. package/package/src/angular/index.ts +3 -3
  47. package/package/src/react/index.tsx +2 -2
  48. package/package/src/svelte/index.ts +1 -1
  49. package/package/src/tracker.ts +2 -2
  50. package/package/src/vue/index.ts +1 -1
  51. package/package.json +1 -1
  52. package/simple-spa.html +164 -2
  53. package/src/angular/index.ts +3 -3
  54. package/src/api.ts +40 -0
  55. package/src/index.ts +7 -0
  56. package/src/react/index.tsx +2 -2
  57. package/src/svelte/index.ts +1 -1
  58. package/src/tracker.ts +175 -11
  59. package/src/utils/ip-detector.ts +158 -0
  60. package/src/utils/property-detector.ts +345 -0
  61. package/src/utils/property-manager.ts +274 -0
  62. package/src/vue/index.ts +1 -1
  63. package/src/wizard/core/install-wizard.ts +60 -10
  64. package/canvas-recording-demo.html +0 -143
  65. package/clean-console-demo.html +0 -39
  66. package/simple-demo.html +0 -26
@@ -12391,6 +12391,145 @@ const logWarn = (message, ...args) => logger.warn(message, ...args);
12391
12391
  const logInfo = (message, ...args) => logger.info(message, ...args);
12392
12392
  const logDebug = (message, ...args) => logger.debug(message, ...args);
12393
12393
 
12394
+ /**
12395
+ * IP Address Detection Utility
12396
+ * Attempts to get the client's public IP address using multiple methods
12397
+ */
12398
+ /**
12399
+ * Get IP address using STUN server (most reliable)
12400
+ */
12401
+ function getIPFromSTUN() {
12402
+ return __awaiter(this, void 0, void 0, function* () {
12403
+ try {
12404
+ const pc = new RTCPeerConnection({
12405
+ iceServers: [
12406
+ { urls: 'stun:stun.l.google.com:19302' },
12407
+ { urls: 'stun:stun1.l.google.com:19302' },
12408
+ { urls: 'stun:stun2.l.google.com:19302' }
12409
+ ]
12410
+ });
12411
+ return new Promise((resolve) => {
12412
+ const timeout = setTimeout(() => {
12413
+ pc.close();
12414
+ resolve(null);
12415
+ }, 5000);
12416
+ pc.createDataChannel('');
12417
+ pc.createOffer()
12418
+ .then(offer => pc.setLocalDescription(offer))
12419
+ .catch(() => resolve(null));
12420
+ pc.onicecandidate = (event) => {
12421
+ if (event.candidate) {
12422
+ const candidate = event.candidate.candidate;
12423
+ const match = candidate.match(/([0-9]{1,3}(\.[0-9]{1,3}){3})/);
12424
+ if (match) {
12425
+ clearTimeout(timeout);
12426
+ pc.close();
12427
+ resolve(match[1]);
12428
+ }
12429
+ }
12430
+ };
12431
+ });
12432
+ }
12433
+ catch (error) {
12434
+ return null;
12435
+ }
12436
+ });
12437
+ }
12438
+ /**
12439
+ * Get IP address using public IP service (fallback)
12440
+ */
12441
+ function getIPFromPublicService() {
12442
+ return __awaiter(this, void 0, void 0, function* () {
12443
+ try {
12444
+ const response = yield fetch('https://api.ipify.org?format=json', {
12445
+ method: 'GET'
12446
+ });
12447
+ if (response.ok) {
12448
+ const data = yield response.json();
12449
+ return data.ip;
12450
+ }
12451
+ }
12452
+ catch (error) {
12453
+ // Try alternative service
12454
+ try {
12455
+ const response = yield fetch('https://httpbin.org/ip', {
12456
+ method: 'GET'
12457
+ });
12458
+ if (response.ok) {
12459
+ const data = yield response.json();
12460
+ return data.origin;
12461
+ }
12462
+ }
12463
+ catch (fallbackError) {
12464
+ // Last resort
12465
+ try {
12466
+ const response = yield fetch('https://api.myip.com', {
12467
+ method: 'GET'
12468
+ });
12469
+ if (response.ok) {
12470
+ const data = yield response.json();
12471
+ return data.ip;
12472
+ }
12473
+ }
12474
+ catch (lastError) {
12475
+ return null;
12476
+ }
12477
+ }
12478
+ }
12479
+ return null;
12480
+ });
12481
+ }
12482
+ /**
12483
+ * Get client's public IP address
12484
+ * Tries STUN first (most reliable), then falls back to public services
12485
+ */
12486
+ function getClientIP() {
12487
+ return __awaiter(this, void 0, void 0, function* () {
12488
+ const startTime = Date.now();
12489
+ // Try STUN first (most reliable and privacy-friendly)
12490
+ const stunIP = yield getIPFromSTUN();
12491
+ if (stunIP) {
12492
+ return {
12493
+ ip: stunIP,
12494
+ method: 'stun',
12495
+ timestamp: startTime
12496
+ };
12497
+ }
12498
+ // Fallback to public IP service
12499
+ const publicIP = yield getIPFromPublicService();
12500
+ if (publicIP) {
12501
+ return {
12502
+ ip: publicIP,
12503
+ method: 'public-service',
12504
+ timestamp: startTime
12505
+ };
12506
+ }
12507
+ return null;
12508
+ });
12509
+ }
12510
+ /**
12511
+ * Get IP address with caching to avoid repeated requests
12512
+ */
12513
+ let cachedIP = null;
12514
+ let cacheTimestamp = 0;
12515
+ const CACHE_DURATION = 5 * 60 * 1000; // 5 minutes
12516
+ function getCachedIP() {
12517
+ return __awaiter(this, void 0, void 0, function* () {
12518
+ const now = Date.now();
12519
+ // Return cached IP if still valid
12520
+ if (cachedIP && (now - cacheTimestamp) < CACHE_DURATION) {
12521
+ return cachedIP;
12522
+ }
12523
+ // Get fresh IP
12524
+ const ipInfo = yield getClientIP();
12525
+ if (ipInfo) {
12526
+ cachedIP = ipInfo;
12527
+ cacheTimestamp = now;
12528
+ }
12529
+ return ipInfo;
12530
+ });
12531
+ }
12532
+
12394
12533
  const MAX_CHUNK_SIZE_BYTES = 1024 * 1024; // 1MB chunk size - more conservative
12395
12534
  function isChunkSizeExceeded(currentChunk, newEvent, sessionId) {
12396
12535
  const nextChunkSize = new TextEncoder().encode(JSON.stringify({
@@ -12484,6 +12623,45 @@ class HumanBehaviorAPI {
12484
12623
  }
12485
12624
  });
12486
12625
  }
12626
+ /**
12627
+ * Send IP address information to the server
12628
+ * This is called after successful initialization
12629
+ */
12630
+ sendIPInfo(sessionId) {
12631
+ return __awaiter(this, void 0, void 0, function* () {
12632
+ try {
12633
+ const ipInfo = yield getCachedIP();
12634
+ if (!ipInfo) {
12635
+ logWarn('No IP address available to send');
12636
+ return;
12637
+ }
12638
+ logDebug('Sending IP info:', ipInfo);
12639
+ const response = yield fetch(`${this.baseUrl}/api/ingestion/ip-info`, {
12640
+ method: 'POST',
12641
+ headers: {
12642
+ 'Content-Type': 'application/json',
12643
+ 'Authorization': `Bearer ${this.apiKey}`
12644
+ },
12645
+ body: JSON.stringify({
12646
+ sessionId: sessionId,
12647
+ clientIP: ipInfo.ip,
12648
+ ipDetectionMethod: ipInfo.method,
12649
+ timestamp: ipInfo.timestamp
12650
+ })
12651
+ });
12652
+ if (!response.ok) {
12653
+ const errorText = yield response.text();
12654
+ logWarn('Failed to send IP info:', response.status, errorText);
12655
+ }
12656
+ else {
12657
+ logDebug('IP info sent successfully');
12658
+ }
12659
+ }
12660
+ catch (error) {
12661
+ logWarn('Error sending IP info:', error);
12662
+ }
12663
+ });
12664
+ }
12487
12665
  sendEvents(events, sessionId, userId) {
12488
12666
  return __awaiter(this, void 0, void 0, function* () {
12489
12667
  // ✅ SIMPLE VALIDATION FOR ALL EVENTS
@@ -12710,7 +12888,7 @@ class HumanBehaviorAPI {
12710
12888
  // This module provides methods to configure rrweb's built-in masking
12711
12889
  // Uses CSS selectors and classes for reliable redaction without event corruption
12712
12890
  // Check if we're in a browser environment
12713
- const isBrowser$1 = typeof window !== 'undefined';
12891
+ const isBrowser$2 = typeof window !== 'undefined';
12714
12892
  class RedactionManager {
12715
12893
  constructor(options) {
12716
12894
  this.redactedText = '[REDACTED]';
@@ -12869,7 +13047,7 @@ class RedactionManager {
12869
13047
  * Check if a DOM change should be redacted based on its ID
12870
13048
  */
12871
13049
  shouldRedactDOMChange(changeData) {
12872
- if (!isBrowser$1)
13050
+ if (!isBrowser$2)
12873
13051
  return false;
12874
13052
  try {
12875
13053
  // Check if this change has an ID that we can use to find the element
@@ -13012,7 +13190,7 @@ class RedactionManager {
13012
13190
  * Check if an event is from a field that should be redacted
13013
13191
  */
13014
13192
  isFieldSelected(eventData) {
13015
- if (!isBrowser$1)
13193
+ if (!isBrowser$2)
13016
13194
  return false;
13017
13195
  try {
13018
13196
  // For input events (source 5), we need to determine if this is a sensitive field
@@ -13163,6 +13341,485 @@ class RedactionManager {
13163
13341
  // Export a default instance
13164
13342
  new RedactionManager();
13165
13343
 
13344
+ /**
13345
+ * Automatic Property Detection for HumanBehavior SDK
13346
+ * Captures device type, location, and initial referrer information
13347
+ */
13348
+ // Check if we're in a browser environment
13349
+ const isBrowser$1 = typeof window !== 'undefined';
13350
+ /**
13351
+ * Detect device type based on user agent and screen size
13352
+ */
13353
+ function detectDeviceType() {
13354
+ if (!isBrowser$1)
13355
+ return 'unknown';
13356
+ const userAgent = navigator.userAgent.toLowerCase();
13357
+ const screenWidth = window.screen.width;
13358
+ const screenHeight = window.screen.height;
13359
+ // Mobile detection
13360
+ if (/mobile|android|iphone|ipad|ipod|blackberry|windows phone/i.test(userAgent)) {
13361
+ if (/ipad/i.test(userAgent) || (screenWidth >= 768 && screenHeight >= 1024)) {
13362
+ return 'tablet';
13363
+ }
13364
+ return 'mobile';
13365
+ }
13366
+ // Desktop detection
13367
+ if (/windows|macintosh|linux/i.test(userAgent)) {
13368
+ return 'desktop';
13369
+ }
13370
+ return 'unknown';
13371
+ }
13372
+ /**
13373
+ * Extract browser information from user agent
13374
+ */
13375
+ function detectBrowser() {
13376
+ if (!isBrowser$1)
13377
+ return { browser: 'unknown', browser_version: 'unknown' };
13378
+ const userAgent = navigator.userAgent;
13379
+ // Chrome
13380
+ if (/chrome/i.test(userAgent) && !/edge/i.test(userAgent)) {
13381
+ const match = userAgent.match(/chrome\/(\d+)/i);
13382
+ return {
13383
+ browser: 'chrome',
13384
+ browser_version: match ? match[1] : 'unknown'
13385
+ };
13386
+ }
13387
+ // Firefox
13388
+ if (/firefox/i.test(userAgent)) {
13389
+ const match = userAgent.match(/firefox\/(\d+)/i);
13390
+ return {
13391
+ browser: 'firefox',
13392
+ browser_version: match ? match[1] : 'unknown'
13393
+ };
13394
+ }
13395
+ // Safari
13396
+ if (/safari/i.test(userAgent) && !/chrome/i.test(userAgent)) {
13397
+ const match = userAgent.match(/version\/(\d+)/i);
13398
+ return {
13399
+ browser: 'safari',
13400
+ browser_version: match ? match[1] : 'unknown'
13401
+ };
13402
+ }
13403
+ // Edge
13404
+ if (/edge/i.test(userAgent)) {
13405
+ const match = userAgent.match(/edge\/(\d+)/i);
13406
+ return {
13407
+ browser: 'edge',
13408
+ browser_version: match ? match[1] : 'unknown'
13409
+ };
13410
+ }
13411
+ // Internet Explorer
13412
+ if (/msie|trident/i.test(userAgent)) {
13413
+ const match = userAgent.match(/msie (\d+)/i) || userAgent.match(/rv:(\d+)/i);
13414
+ return {
13415
+ browser: 'ie',
13416
+ browser_version: match ? match[1] : 'unknown'
13417
+ };
13418
+ }
13419
+ return { browser: 'unknown', browser_version: 'unknown' };
13420
+ }
13421
+ /**
13422
+ * Extract operating system information from user agent
13423
+ */
13424
+ function detectOS() {
13425
+ if (!isBrowser$1)
13426
+ return { os: 'unknown', os_version: 'unknown' };
13427
+ const userAgent = navigator.userAgent;
13428
+ // Windows
13429
+ if (/windows/i.test(userAgent)) {
13430
+ const match = userAgent.match(/windows nt (\d+\.\d+)/i);
13431
+ let version = 'unknown';
13432
+ if (match) {
13433
+ const versionNum = parseFloat(match[1]);
13434
+ if (versionNum === 10.0)
13435
+ version = '10';
13436
+ else if (versionNum === 6.3)
13437
+ version = '8.1';
13438
+ else if (versionNum === 6.2)
13439
+ version = '8';
13440
+ else if (versionNum === 6.1)
13441
+ version = '7';
13442
+ else
13443
+ version = match[1];
13444
+ }
13445
+ return { os: 'windows', os_version: version };
13446
+ }
13447
+ // macOS
13448
+ if (/macintosh|mac os x/i.test(userAgent)) {
13449
+ const match = userAgent.match(/mac os x (\d+[._]\d+)/i);
13450
+ return {
13451
+ os: 'macos',
13452
+ os_version: match ? match[1].replace('_', '.') : 'unknown'
13453
+ };
13454
+ }
13455
+ // iOS
13456
+ if (/iphone|ipad|ipod/i.test(userAgent)) {
13457
+ const match = userAgent.match(/os (\d+[._]\d+)/i);
13458
+ return {
13459
+ os: 'ios',
13460
+ os_version: match ? match[1].replace('_', '.') : 'unknown'
13461
+ };
13462
+ }
13463
+ // Android
13464
+ if (/android/i.test(userAgent)) {
13465
+ const match = userAgent.match(/android (\d+\.\d+)/i);
13466
+ return {
13467
+ os: 'android',
13468
+ os_version: match ? match[1] : 'unknown'
13469
+ };
13470
+ }
13471
+ // Linux
13472
+ if (/linux/i.test(userAgent)) {
13473
+ return { os: 'linux', os_version: 'unknown' };
13474
+ }
13475
+ return { os: 'unknown', os_version: 'unknown' };
13476
+ }
13477
+ /**
13478
+ * Extract UTM parameters from URL
13479
+ */
13480
+ function extractUTMParams(url) {
13481
+ const urlObj = new URL(url);
13482
+ const utmParams = {};
13483
+ const utmKeys = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content'];
13484
+ utmKeys.forEach(key => {
13485
+ const value = urlObj.searchParams.get(key);
13486
+ if (value) {
13487
+ utmParams[key] = value;
13488
+ }
13489
+ });
13490
+ return utmParams;
13491
+ }
13492
+ /**
13493
+ * Extract domain from URL
13494
+ */
13495
+ function extractDomain(url) {
13496
+ try {
13497
+ const urlObj = new URL(url);
13498
+ return urlObj.hostname;
13499
+ }
13500
+ catch (_a) {
13501
+ return '';
13502
+ }
13503
+ }
13504
+ /**
13505
+ * Get device information
13506
+ */
13507
+ function getDeviceInfo() {
13508
+ if (!isBrowser$1) {
13509
+ return {
13510
+ device_type: 'unknown',
13511
+ browser: 'unknown',
13512
+ browser_version: 'unknown',
13513
+ os: 'unknown',
13514
+ os_version: 'unknown',
13515
+ screen_resolution: 'unknown',
13516
+ viewport_size: 'unknown',
13517
+ color_depth: 0,
13518
+ timezone: 'unknown',
13519
+ language: 'unknown',
13520
+ languages: []
13521
+ };
13522
+ }
13523
+ const { browser, browser_version } = detectBrowser();
13524
+ const { os, os_version } = detectOS();
13525
+ return {
13526
+ device_type: detectDeviceType(),
13527
+ browser,
13528
+ browser_version,
13529
+ os,
13530
+ os_version,
13531
+ screen_resolution: `${window.screen.width}x${window.screen.height}`,
13532
+ viewport_size: `${window.innerWidth}x${window.innerHeight}`,
13533
+ color_depth: window.screen.colorDepth,
13534
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
13535
+ language: navigator.language,
13536
+ languages: [...(navigator.languages || [navigator.language])],
13537
+ raw_user_agent: navigator.userAgent
13538
+ };
13539
+ }
13540
+ /**
13541
+ * Get location information
13542
+ */
13543
+ function getLocationInfo() {
13544
+ if (!isBrowser$1) {
13545
+ return {
13546
+ current_url: '',
13547
+ pathname: '',
13548
+ search: '',
13549
+ hash: '',
13550
+ title: '',
13551
+ referrer: '',
13552
+ referrer_domain: '',
13553
+ initial_referrer: '',
13554
+ initial_referrer_domain: ''
13555
+ };
13556
+ }
13557
+ const currentUrl = window.location.href;
13558
+ const referrer = document.referrer;
13559
+ const utmParams = extractUTMParams(currentUrl);
13560
+ return Object.assign({ current_url: currentUrl, pathname: window.location.pathname, search: window.location.search, hash: window.location.hash, title: document.title, referrer, referrer_domain: extractDomain(referrer), initial_referrer: referrer, initial_referrer_domain: extractDomain(referrer), initial_host: window.location.hostname }, utmParams);
13561
+ }
13562
+ /**
13563
+ * Get all automatic properties
13564
+ */
13565
+ function getAutomaticProperties() {
13566
+ return Object.assign(Object.assign({}, getDeviceInfo()), getLocationInfo());
13567
+ }
13568
+ /**
13569
+ * Get initial properties that should be captured once per session
13570
+ */
13571
+ function getInitialProperties() {
13572
+ if (!isBrowser$1)
13573
+ return {};
13574
+ const locationInfo = getLocationInfo();
13575
+ return {
13576
+ initial_referrer: locationInfo.initial_referrer,
13577
+ initial_referrer_domain: locationInfo.initial_referrer_domain,
13578
+ initial_url: locationInfo.current_url,
13579
+ initial_pathname: locationInfo.pathname,
13580
+ initial_utm_source: locationInfo.utm_source,
13581
+ initial_utm_medium: locationInfo.utm_medium,
13582
+ initial_utm_campaign: locationInfo.utm_campaign,
13583
+ initial_utm_term: locationInfo.utm_term,
13584
+ initial_utm_content: locationInfo.utm_content
13585
+ };
13586
+ }
13587
+ /**
13588
+ * Get current page properties (changes with navigation)
13589
+ */
13590
+ function getCurrentPageProperties() {
13591
+ if (!isBrowser$1)
13592
+ return {};
13593
+ const locationInfo = getLocationInfo();
13594
+ return {
13595
+ current_url: locationInfo.current_url,
13596
+ pathname: locationInfo.pathname,
13597
+ search: locationInfo.search,
13598
+ hash: locationInfo.hash,
13599
+ title: locationInfo.title,
13600
+ referrer: locationInfo.referrer,
13601
+ referrer_domain: locationInfo.referrer_domain,
13602
+ utm_source: locationInfo.utm_source,
13603
+ utm_medium: locationInfo.utm_medium,
13604
+ utm_campaign: locationInfo.utm_campaign,
13605
+ utm_term: locationInfo.utm_term,
13606
+ utm_content: locationInfo.utm_content
13607
+ };
13608
+ }
13609
+
13610
+ /**
13611
+ * Property Manager for HumanBehavior SDK
13612
+ * Handles automatic properties, session properties, and user properties
13613
+ */
13614
+ class PropertyManager {
13615
+ constructor(config = {}) {
13616
+ this.sessionProperties = {};
13617
+ this.userProperties = {};
13618
+ this.initialProperties = {};
13619
+ this.isInitialized = false;
13620
+ this.config = Object.assign({ enableAutomaticProperties: true, enableSessionProperties: true, enableUserProperties: true, propertyDenylist: [] }, config);
13621
+ this.automaticProperties = getAutomaticProperties();
13622
+ this.initialize();
13623
+ }
13624
+ /**
13625
+ * Initialize the property manager
13626
+ */
13627
+ initialize() {
13628
+ if (this.isInitialized)
13629
+ return;
13630
+ // Capture initial properties once
13631
+ this.initialProperties = getInitialProperties();
13632
+ // Load session properties from sessionStorage
13633
+ this.loadSessionProperties();
13634
+ this.isInitialized = true;
13635
+ }
13636
+ /**
13637
+ * Get all properties for an event
13638
+ */
13639
+ getEventProperties(eventProperties = {}) {
13640
+ const properties = Object.assign({}, eventProperties);
13641
+ // Add automatic properties
13642
+ if (this.config.enableAutomaticProperties) {
13643
+ Object.assign(properties, this.getAutomaticProperties());
13644
+ }
13645
+ // Add session properties
13646
+ if (this.config.enableSessionProperties) {
13647
+ Object.assign(properties, this.sessionProperties);
13648
+ }
13649
+ // Add user properties
13650
+ if (this.config.enableUserProperties) {
13651
+ Object.assign(properties, this.userProperties);
13652
+ }
13653
+ // Add initial properties (only once per session)
13654
+ if (!this.sessionProperties['$initial_properties_captured']) {
13655
+ Object.assign(properties, this.initialProperties);
13656
+ this.setSessionProperty('$initial_properties_captured', true);
13657
+ }
13658
+ // Apply denylist
13659
+ this.applyDenylist(properties);
13660
+ return properties;
13661
+ }
13662
+ /**
13663
+ * Get automatic properties
13664
+ */
13665
+ getAutomaticProperties() {
13666
+ return Object.assign(Object.assign({}, this.automaticProperties), getCurrentPageProperties() // Always get fresh page properties
13667
+ );
13668
+ }
13669
+ /**
13670
+ * Get automatic properties with GeoIP data merged in
13671
+ */
13672
+ getAutomaticPropertiesWithGeoIP(geoIPProperties = {}) {
13673
+ return Object.assign(Object.assign(Object.assign({}, this.automaticProperties), getCurrentPageProperties()), geoIPProperties);
13674
+ }
13675
+ /**
13676
+ * Set a session property
13677
+ */
13678
+ setSessionProperty(key, value) {
13679
+ this.sessionProperties[key] = value;
13680
+ this.saveSessionProperties();
13681
+ }
13682
+ /**
13683
+ * Set multiple session properties
13684
+ */
13685
+ setSessionProperties(properties) {
13686
+ Object.assign(this.sessionProperties, properties);
13687
+ this.saveSessionProperties();
13688
+ }
13689
+ /**
13690
+ * Get a session property
13691
+ */
13692
+ getSessionProperty(key) {
13693
+ return this.sessionProperties[key];
13694
+ }
13695
+ /**
13696
+ * Remove a session property
13697
+ */
13698
+ removeSessionProperty(key) {
13699
+ delete this.sessionProperties[key];
13700
+ this.saveSessionProperties();
13701
+ }
13702
+ /**
13703
+ * Set a user property
13704
+ */
13705
+ setUserProperty(key, value) {
13706
+ this.userProperties[key] = value;
13707
+ }
13708
+ /**
13709
+ * Set multiple user properties
13710
+ */
13711
+ setUserProperties(properties) {
13712
+ Object.assign(this.userProperties, properties);
13713
+ }
13714
+ /**
13715
+ * Get a user property
13716
+ */
13717
+ getUserProperty(key) {
13718
+ return this.userProperties[key];
13719
+ }
13720
+ /**
13721
+ * Remove a user property
13722
+ */
13723
+ removeUserProperty(key) {
13724
+ delete this.userProperties[key];
13725
+ }
13726
+ /**
13727
+ * Set a property only if it hasn't been set before
13728
+ */
13729
+ setOnce(key, value, scope = 'user') {
13730
+ if (scope === 'session') {
13731
+ if (!(key in this.sessionProperties)) {
13732
+ this.setSessionProperty(key, value);
13733
+ }
13734
+ }
13735
+ else {
13736
+ if (!(key in this.userProperties)) {
13737
+ this.setUserProperty(key, value);
13738
+ }
13739
+ }
13740
+ }
13741
+ /**
13742
+ * Clear all session properties
13743
+ */
13744
+ clearSessionProperties() {
13745
+ this.sessionProperties = {};
13746
+ this.saveSessionProperties();
13747
+ }
13748
+ /**
13749
+ * Clear all user properties
13750
+ */
13751
+ clearUserProperties() {
13752
+ this.userProperties = {};
13753
+ }
13754
+ /**
13755
+ * Reset all properties
13756
+ */
13757
+ reset() {
13758
+ this.clearSessionProperties();
13759
+ this.clearUserProperties();
13760
+ this.initialProperties = {};
13761
+ this.isInitialized = false;
13762
+ this.initialize();
13763
+ }
13764
+ /**
13765
+ * Load session properties from sessionStorage
13766
+ */
13767
+ loadSessionProperties() {
13768
+ if (typeof sessionStorage === 'undefined')
13769
+ return;
13770
+ try {
13771
+ const stored = sessionStorage.getItem('hb_session_properties');
13772
+ if (stored) {
13773
+ this.sessionProperties = JSON.parse(stored);
13774
+ }
13775
+ }
13776
+ catch (error) {
13777
+ console.warn('Failed to load session properties:', error);
13778
+ }
13779
+ }
13780
+ /**
13781
+ * Save session properties to sessionStorage
13782
+ */
13783
+ saveSessionProperties() {
13784
+ if (typeof sessionStorage === 'undefined')
13785
+ return;
13786
+ try {
13787
+ sessionStorage.setItem('hb_session_properties', JSON.stringify(this.sessionProperties));
13788
+ }
13789
+ catch (error) {
13790
+ console.warn('Failed to save session properties:', error);
13791
+ }
13792
+ }
13793
+ /**
13794
+ * Apply property denylist
13795
+ */
13796
+ applyDenylist(properties) {
13797
+ if (!this.config.propertyDenylist || this.config.propertyDenylist.length === 0) {
13798
+ return;
13799
+ }
13800
+ this.config.propertyDenylist.forEach(deniedKey => {
13801
+ delete properties[deniedKey];
13802
+ });
13803
+ }
13804
+ /**
13805
+ * Update automatic properties (call when page changes)
13806
+ */
13807
+ updateAutomaticProperties() {
13808
+ this.automaticProperties = getAutomaticProperties();
13809
+ }
13810
+ /**
13811
+ * Get all properties for debugging
13812
+ */
13813
+ getAllProperties() {
13814
+ return {
13815
+ automatic: this.getAutomaticProperties(),
13816
+ session: Object.assign({}, this.sessionProperties),
13817
+ user: Object.assign({}, this.userProperties),
13818
+ initial: Object.assign({}, this.initialProperties)
13819
+ };
13820
+ }
13821
+ }
13822
+
13166
13823
  // Check if we're in a browser environment
13167
13824
  const isBrowser = typeof window !== 'undefined';
13168
13825
  class HumanBehaviorTracker {
@@ -13229,7 +13886,10 @@ class HumanBehaviorTracker {
13229
13886
  this.configureLogging({ level: options.logLevel });
13230
13887
  }
13231
13888
  // Create new tracker instance
13232
- const tracker = new HumanBehaviorTracker(apiKey, options === null || options === void 0 ? void 0 : options.ingestionUrl);
13889
+ const tracker = new HumanBehaviorTracker(apiKey, options === null || options === void 0 ? void 0 : options.ingestionUrl, {
13890
+ enableAutomaticProperties: options === null || options === void 0 ? void 0 : options.enableAutomaticProperties,
13891
+ propertyDenylist: options === null || options === void 0 ? void 0 : options.propertyDenylist
13892
+ });
13233
13893
  // Store canvas recording preference
13234
13894
  tracker.recordCanvas = (_a = options === null || options === void 0 ? void 0 : options.recordCanvas) !== null && _a !== void 0 ? _a : false;
13235
13895
  // Set redacted fields if specified
@@ -13246,7 +13906,7 @@ class HumanBehaviorTracker {
13246
13906
  tracker.start();
13247
13907
  return tracker;
13248
13908
  }
13249
- constructor(apiKey, ingestionUrl) {
13909
+ constructor(apiKey, ingestionUrl, options) {
13250
13910
  this.eventIngestionQueue = [];
13251
13911
  this.userProperties = {};
13252
13912
  this.isProcessing = false;
@@ -13284,6 +13944,11 @@ class HumanBehaviorTracker {
13284
13944
  });
13285
13945
  this.apiKey = apiKey;
13286
13946
  this.redactionManager = new RedactionManager();
13947
+ // Initialize property manager
13948
+ this.propertyManager = new PropertyManager({
13949
+ enableAutomaticProperties: (options === null || options === void 0 ? void 0 : options.enableAutomaticProperties) !== false,
13950
+ propertyDenylist: (options === null || options === void 0 ? void 0 : options.propertyDenylist) || []
13951
+ });
13287
13952
  // Handle session restoration with improved continuity
13288
13953
  if (isBrowser) {
13289
13954
  const existingSessionId = localStorage.getItem(`human_behavior_session_id_${this.apiKey}`);
@@ -13322,7 +13987,28 @@ class HumanBehaviorTracker {
13322
13987
  try {
13323
13988
  const userId = this.getCookie(`human_behavior_end_user_id_${this.apiKey}`);
13324
13989
  logDebug(`Initializing with sessionId: ${this.sessionId}, userId: ${userId}`);
13325
- const { sessionId, endUserId } = yield this.api.init(this.sessionId, userId);
13990
+ // Get automatic properties for init
13991
+ const automaticProperties = this.propertyManager.getAutomaticProperties();
13992
+ // Create a custom init request with automatic properties
13993
+ const initResponse = yield fetch(`${this.api['baseUrl']}/api/ingestion/init`, {
13994
+ method: 'POST',
13995
+ headers: {
13996
+ 'Content-Type': 'application/json',
13997
+ 'Authorization': `Bearer ${this.apiKey}`,
13998
+ 'Referer': document.referrer || ''
13999
+ },
14000
+ body: JSON.stringify({
14001
+ sessionId: this.sessionId,
14002
+ endUserId: userId,
14003
+ entryURL: window.location.href,
14004
+ referrer: document.referrer,
14005
+ automaticProperties: automaticProperties
14006
+ })
14007
+ });
14008
+ if (!initResponse.ok) {
14009
+ throw new Error(`Failed to initialize: ${initResponse.statusText}`);
14010
+ }
14011
+ const { sessionId, endUserId } = yield initResponse.json();
13326
14012
  // Check if server returned a different session ID (for session continuity)
13327
14013
  if (sessionId !== this.sessionId) {
13328
14014
  logDebug(`Server returned different sessionId: ${sessionId} (client had: ${this.sessionId})`);
@@ -13334,6 +14020,10 @@ class HumanBehaviorTracker {
13334
14020
  }
13335
14021
  this.endUserId = endUserId;
13336
14022
  this.setCookie(`human_behavior_end_user_id_${this.apiKey}`, endUserId, 365);
14023
+ // Send IP information after successful initialization
14024
+ this.api.sendIPInfo(this.sessionId).catch(error => {
14025
+ logWarn('Failed to send IP info:', error);
14026
+ });
13337
14027
  // Only setup browser-specific handlers when in browser environment
13338
14028
  if (isBrowser) {
13339
14029
  this.setupPageUnloadHandler();
@@ -13454,6 +14144,8 @@ class HumanBehaviorTracker {
13454
14144
  return __awaiter(this, void 0, void 0, function* () {
13455
14145
  if (!this.initialized)
13456
14146
  return;
14147
+ // Update automatic properties for new page
14148
+ this.propertyManager.updateAutomaticProperties();
13457
14149
  try {
13458
14150
  const pageViewData = {
13459
14151
  url: url || window.location.href,
@@ -13463,11 +14155,13 @@ class HumanBehaviorTracker {
13463
14155
  referrer: document.referrer,
13464
14156
  timestamp: new Date().toISOString()
13465
14157
  };
14158
+ // Get enhanced properties with automatic properties
14159
+ const enhancedProperties = this.propertyManager.getEventProperties(pageViewData);
13466
14160
  // Add pageview event to the main event stream
13467
14161
  yield this.addEvent({
13468
14162
  type: 5, // Custom event type
13469
14163
  data: {
13470
- payload: Object.assign({ eventType: 'pageview' }, pageViewData)
14164
+ payload: Object.assign({ eventType: 'pageview' }, enhancedProperties)
13471
14165
  },
13472
14166
  timestamp: Date.now()
13473
14167
  });
@@ -13483,10 +14177,12 @@ class HumanBehaviorTracker {
13483
14177
  var _a, _b, _c, _d, _e;
13484
14178
  if (!this.initialized)
13485
14179
  return;
14180
+ // Get enhanced properties with automatic properties
14181
+ const enhancedProperties = this.propertyManager.getEventProperties(properties);
13486
14182
  try {
13487
14183
  // Send custom event directly to the API
13488
- yield this.api.sendCustomEvent(this.sessionId, eventName, properties);
13489
- logDebug(`Custom event tracked: ${eventName}`, properties);
14184
+ yield this.api.sendCustomEvent(this.sessionId, eventName, enhancedProperties);
14185
+ logDebug(`Custom event tracked: ${eventName}`, enhancedProperties);
13490
14186
  }
13491
14187
  catch (error) {
13492
14188
  logError('Failed to track custom event:', error);
@@ -13506,7 +14202,7 @@ class HumanBehaviorTracker {
13506
14202
  try {
13507
14203
  const customEventData = {
13508
14204
  eventName: eventName,
13509
- properties: properties || {},
14205
+ properties: enhancedProperties || {},
13510
14206
  timestamp: new Date().toISOString(),
13511
14207
  url: window.location.href,
13512
14208
  pathname: window.location.pathname
@@ -13806,8 +14502,25 @@ class HumanBehaviorTracker {
13806
14502
  // Store user properties
13807
14503
  this.userProperties = userProperties;
13808
14504
  logDebug('Identifying user:', { userProperties, originalEndUserId, sessionId: this.sessionId });
13809
- // Send user data with the original endUserId
13810
- yield this.api.sendUserData(originalEndUserId, userProperties, this.sessionId);
14505
+ // Get automatic properties and send with user data
14506
+ const automaticProperties = this.propertyManager.getAutomaticProperties();
14507
+ // Create a custom user request with automatic properties
14508
+ const userResponse = yield fetch(`${this.api['baseUrl']}/api/ingestion/user`, {
14509
+ method: 'POST',
14510
+ headers: {
14511
+ 'Content-Type': 'application/json',
14512
+ 'Authorization': `Bearer ${this.apiKey}`
14513
+ },
14514
+ body: JSON.stringify({
14515
+ userId: originalEndUserId,
14516
+ userAttributes: userProperties,
14517
+ sessionId: this.sessionId,
14518
+ automaticProperties: automaticProperties
14519
+ })
14520
+ });
14521
+ if (!userResponse.ok) {
14522
+ throw new Error(`Failed to identify user: ${userResponse.statusText}`);
14523
+ }
13811
14524
  // Don't update endUserId - keep it as the original UUID
13812
14525
  return originalEndUserId || '';
13813
14526
  });
@@ -13855,7 +14568,7 @@ class HumanBehaviorTracker {
13855
14568
  collectFonts: false, // Disable font collection to reduce errors
13856
14569
  inlineStylesheet: true, // Keep styles for proper session replay
13857
14570
  recordCrossOriginIframes: false, // Prevent cross-origin iframe errors
13858
- // ✅ CANVAS RECORDING - PostHog-style protection against overwhelm
14571
+ // ✅ CANVAS RECORDING - protection against overwhelm
13859
14572
  recordCanvas: this.recordCanvas, // Opt-in only
13860
14573
  sampling: this.recordCanvas ? { canvas: 4 } : undefined, // 4 FPS throttle
13861
14574
  dataURLOptions: this.recordCanvas ? {
@@ -14279,6 +14992,79 @@ class HumanBehaviorTracker {
14279
14992
  initialized: this.initialized
14280
14993
  };
14281
14994
  }
14995
+ // ===== PROPERTY MANAGEMENT METHODS =====
14996
+ /**
14997
+ * Set a session property that will be included in all events for this session
14998
+ */
14999
+ setSessionProperty(key, value) {
15000
+ this.propertyManager.setSessionProperty(key, value);
15001
+ }
15002
+ /**
15003
+ * Set multiple session properties
15004
+ */
15005
+ setSessionProperties(properties) {
15006
+ this.propertyManager.setSessionProperties(properties);
15007
+ }
15008
+ /**
15009
+ * Get a session property
15010
+ */
15011
+ getSessionProperty(key) {
15012
+ return this.propertyManager.getSessionProperty(key);
15013
+ }
15014
+ /**
15015
+ * Remove a session property
15016
+ */
15017
+ removeSessionProperty(key) {
15018
+ this.propertyManager.removeSessionProperty(key);
15019
+ }
15020
+ /**
15021
+ * Set a user property that will be included in all events
15022
+ */
15023
+ setUserProperty(key, value) {
15024
+ this.propertyManager.setUserProperty(key, value);
15025
+ }
15026
+ /**
15027
+ * Set multiple user properties
15028
+ */
15029
+ setUserProperties(properties) {
15030
+ this.propertyManager.setUserProperties(properties);
15031
+ }
15032
+ /**
15033
+ * Get a user property
15034
+ */
15035
+ getUserProperty(key) {
15036
+ return this.propertyManager.getUserProperty(key);
15037
+ }
15038
+ /**
15039
+ * Remove a user property
15040
+ */
15041
+ removeUserProperty(key) {
15042
+ this.propertyManager.removeUserProperty(key);
15043
+ }
15044
+ /**
15045
+ * Set a property only if it hasn't been set before
15046
+ */
15047
+ setOnce(key, value, scope = 'user') {
15048
+ this.propertyManager.setOnce(key, value, scope);
15049
+ }
15050
+ /**
15051
+ * Clear all session properties
15052
+ */
15053
+ clearSessionProperties() {
15054
+ this.propertyManager.clearSessionProperties();
15055
+ }
15056
+ /**
15057
+ * Clear all user properties
15058
+ */
15059
+ clearUserProperties() {
15060
+ this.propertyManager.clearUserProperties();
15061
+ }
15062
+ /**
15063
+ * Get all properties for debugging
15064
+ */
15065
+ getAllProperties() {
15066
+ return this.propertyManager.getAllProperties();
15067
+ }
14282
15068
  }
14283
15069
  // Only expose to window object in browser environments
14284
15070
  if (isBrowser) {