humanbehavior-js 0.4.20 → 0.4.22

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 (53) hide show
  1. package/dist/cjs/angular/index.cjs +817 -19
  2. package/dist/cjs/angular/index.cjs.map +1 -1
  3. package/dist/cjs/index.cjs +833 -19
  4. package/dist/cjs/index.cjs.map +1 -1
  5. package/dist/cjs/react/index.cjs +818 -20
  6. package/dist/cjs/react/index.cjs.map +1 -1
  7. package/dist/cjs/remix/index.cjs +818 -20
  8. package/dist/cjs/remix/index.cjs.map +1 -1
  9. package/dist/cjs/svelte/index.cjs +817 -19
  10. package/dist/cjs/svelte/index.cjs.map +1 -1
  11. package/dist/cjs/vue/index.cjs +817 -19
  12. package/dist/cjs/vue/index.cjs.map +1 -1
  13. package/dist/esm/angular/index.js +817 -19
  14. package/dist/esm/angular/index.js.map +1 -1
  15. package/dist/esm/index.js +825 -20
  16. package/dist/esm/index.js.map +1 -1
  17. package/dist/esm/react/index.js +818 -20
  18. package/dist/esm/react/index.js.map +1 -1
  19. package/dist/esm/remix/index.js +818 -20
  20. package/dist/esm/remix/index.js.map +1 -1
  21. package/dist/esm/svelte/index.js +817 -19
  22. package/dist/esm/svelte/index.js.map +1 -1
  23. package/dist/esm/vue/index.js +817 -19
  24. package/dist/esm/vue/index.js.map +1 -1
  25. package/dist/index.min.js +1 -1
  26. package/dist/index.min.js.map +1 -1
  27. package/dist/types/angular/index.d.ts +60 -1
  28. package/dist/types/index.d.ts +258 -3
  29. package/dist/types/react/index.d.ts +60 -1
  30. package/dist/types/remix/index.d.ts +60 -1
  31. package/dist/types/svelte/index.d.ts +60 -1
  32. package/package/canvas-recording-demo.html +1 -1
  33. package/package/simple-spa.html +1 -1
  34. package/package/src/angular/index.ts +3 -3
  35. package/package/src/react/index.tsx +2 -2
  36. package/package/src/svelte/index.ts +1 -1
  37. package/package/src/tracker.ts +2 -2
  38. package/package/src/vue/index.ts +1 -1
  39. package/package.json +1 -1
  40. package/simple-spa.html +164 -2
  41. package/src/angular/index.ts +3 -3
  42. package/src/api.ts +40 -0
  43. package/src/index.ts +7 -0
  44. package/src/react/index.tsx +2 -2
  45. package/src/svelte/index.ts +1 -1
  46. package/src/tracker.ts +193 -17
  47. package/src/utils/ip-detector.ts +158 -0
  48. package/src/utils/property-detector.ts +345 -0
  49. package/src/utils/property-manager.ts +274 -0
  50. package/src/vue/index.ts +1 -1
  51. package/canvas-recording-demo.html +0 -143
  52. package/clean-console-demo.html +0 -39
  53. package/simple-demo.html +0 -26
@@ -12391,6 +12391,152 @@ 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
+ /**
12533
+ * Clear IP cache (useful for testing or when network changes)
12534
+ */
12535
+ function clearIPCache() {
12536
+ cachedIP = null;
12537
+ cacheTimestamp = 0;
12538
+ }
12539
+
12394
12540
  const MAX_CHUNK_SIZE_BYTES = 1024 * 1024; // 1MB chunk size - more conservative
12395
12541
  function isChunkSizeExceeded(currentChunk, newEvent, sessionId) {
12396
12542
  const nextChunkSize = new TextEncoder().encode(JSON.stringify({
@@ -12494,6 +12640,45 @@ class HumanBehaviorAPI {
12494
12640
  }
12495
12641
  });
12496
12642
  }
12643
+ /**
12644
+ * Send IP address information to the server
12645
+ * This is called after successful initialization
12646
+ */
12647
+ sendIPInfo(sessionId) {
12648
+ return __awaiter(this, void 0, void 0, function* () {
12649
+ try {
12650
+ const ipInfo = yield getCachedIP();
12651
+ if (!ipInfo) {
12652
+ logWarn('No IP address available to send');
12653
+ return;
12654
+ }
12655
+ logDebug('Sending IP info:', ipInfo);
12656
+ const response = yield fetch(`${this.baseUrl}/api/ingestion/ip-info`, {
12657
+ method: 'POST',
12658
+ headers: {
12659
+ 'Content-Type': 'application/json',
12660
+ 'Authorization': `Bearer ${this.apiKey}`
12661
+ },
12662
+ body: JSON.stringify({
12663
+ sessionId: sessionId,
12664
+ clientIP: ipInfo.ip,
12665
+ ipDetectionMethod: ipInfo.method,
12666
+ timestamp: ipInfo.timestamp
12667
+ })
12668
+ });
12669
+ if (!response.ok) {
12670
+ const errorText = yield response.text();
12671
+ logWarn('Failed to send IP info:', response.status, errorText);
12672
+ }
12673
+ else {
12674
+ logDebug('IP info sent successfully');
12675
+ }
12676
+ }
12677
+ catch (error) {
12678
+ logWarn('Error sending IP info:', error);
12679
+ }
12680
+ });
12681
+ }
12497
12682
  sendEvents(events, sessionId, userId) {
12498
12683
  return __awaiter(this, void 0, void 0, function* () {
12499
12684
  // ✅ SIMPLE VALIDATION FOR ALL EVENTS
@@ -12720,7 +12905,7 @@ class HumanBehaviorAPI {
12720
12905
  // This module provides methods to configure rrweb's built-in masking
12721
12906
  // Uses CSS selectors and classes for reliable redaction without event corruption
12722
12907
  // Check if we're in a browser environment
12723
- const isBrowser$1 = typeof window !== 'undefined';
12908
+ const isBrowser$2 = typeof window !== 'undefined';
12724
12909
  class RedactionManager {
12725
12910
  constructor(options) {
12726
12911
  this.redactedText = '[REDACTED]';
@@ -12879,7 +13064,7 @@ class RedactionManager {
12879
13064
  * Check if a DOM change should be redacted based on its ID
12880
13065
  */
12881
13066
  shouldRedactDOMChange(changeData) {
12882
- if (!isBrowser$1)
13067
+ if (!isBrowser$2)
12883
13068
  return false;
12884
13069
  try {
12885
13070
  // Check if this change has an ID that we can use to find the element
@@ -13022,7 +13207,7 @@ class RedactionManager {
13022
13207
  * Check if an event is from a field that should be redacted
13023
13208
  */
13024
13209
  isFieldSelected(eventData) {
13025
- if (!isBrowser$1)
13210
+ if (!isBrowser$2)
13026
13211
  return false;
13027
13212
  try {
13028
13213
  // For input events (source 5), we need to determine if this is a sensitive field
@@ -13173,6 +13358,485 @@ class RedactionManager {
13173
13358
  // Export a default instance
13174
13359
  const redactionManager = new RedactionManager();
13175
13360
 
13361
+ /**
13362
+ * Automatic Property Detection for HumanBehavior SDK
13363
+ * Captures device type, location, and initial referrer information
13364
+ */
13365
+ // Check if we're in a browser environment
13366
+ const isBrowser$1 = typeof window !== 'undefined';
13367
+ /**
13368
+ * Detect device type based on user agent and screen size
13369
+ */
13370
+ function detectDeviceType() {
13371
+ if (!isBrowser$1)
13372
+ return 'unknown';
13373
+ const userAgent = navigator.userAgent.toLowerCase();
13374
+ const screenWidth = window.screen.width;
13375
+ const screenHeight = window.screen.height;
13376
+ // Mobile detection
13377
+ if (/mobile|android|iphone|ipad|ipod|blackberry|windows phone/i.test(userAgent)) {
13378
+ if (/ipad/i.test(userAgent) || (screenWidth >= 768 && screenHeight >= 1024)) {
13379
+ return 'tablet';
13380
+ }
13381
+ return 'mobile';
13382
+ }
13383
+ // Desktop detection
13384
+ if (/windows|macintosh|linux/i.test(userAgent)) {
13385
+ return 'desktop';
13386
+ }
13387
+ return 'unknown';
13388
+ }
13389
+ /**
13390
+ * Extract browser information from user agent
13391
+ */
13392
+ function detectBrowser() {
13393
+ if (!isBrowser$1)
13394
+ return { browser: 'unknown', browser_version: 'unknown' };
13395
+ const userAgent = navigator.userAgent;
13396
+ // Chrome
13397
+ if (/chrome/i.test(userAgent) && !/edge/i.test(userAgent)) {
13398
+ const match = userAgent.match(/chrome\/(\d+)/i);
13399
+ return {
13400
+ browser: 'chrome',
13401
+ browser_version: match ? match[1] : 'unknown'
13402
+ };
13403
+ }
13404
+ // Firefox
13405
+ if (/firefox/i.test(userAgent)) {
13406
+ const match = userAgent.match(/firefox\/(\d+)/i);
13407
+ return {
13408
+ browser: 'firefox',
13409
+ browser_version: match ? match[1] : 'unknown'
13410
+ };
13411
+ }
13412
+ // Safari
13413
+ if (/safari/i.test(userAgent) && !/chrome/i.test(userAgent)) {
13414
+ const match = userAgent.match(/version\/(\d+)/i);
13415
+ return {
13416
+ browser: 'safari',
13417
+ browser_version: match ? match[1] : 'unknown'
13418
+ };
13419
+ }
13420
+ // Edge
13421
+ if (/edge/i.test(userAgent)) {
13422
+ const match = userAgent.match(/edge\/(\d+)/i);
13423
+ return {
13424
+ browser: 'edge',
13425
+ browser_version: match ? match[1] : 'unknown'
13426
+ };
13427
+ }
13428
+ // Internet Explorer
13429
+ if (/msie|trident/i.test(userAgent)) {
13430
+ const match = userAgent.match(/msie (\d+)/i) || userAgent.match(/rv:(\d+)/i);
13431
+ return {
13432
+ browser: 'ie',
13433
+ browser_version: match ? match[1] : 'unknown'
13434
+ };
13435
+ }
13436
+ return { browser: 'unknown', browser_version: 'unknown' };
13437
+ }
13438
+ /**
13439
+ * Extract operating system information from user agent
13440
+ */
13441
+ function detectOS() {
13442
+ if (!isBrowser$1)
13443
+ return { os: 'unknown', os_version: 'unknown' };
13444
+ const userAgent = navigator.userAgent;
13445
+ // Windows
13446
+ if (/windows/i.test(userAgent)) {
13447
+ const match = userAgent.match(/windows nt (\d+\.\d+)/i);
13448
+ let version = 'unknown';
13449
+ if (match) {
13450
+ const versionNum = parseFloat(match[1]);
13451
+ if (versionNum === 10.0)
13452
+ version = '10';
13453
+ else if (versionNum === 6.3)
13454
+ version = '8.1';
13455
+ else if (versionNum === 6.2)
13456
+ version = '8';
13457
+ else if (versionNum === 6.1)
13458
+ version = '7';
13459
+ else
13460
+ version = match[1];
13461
+ }
13462
+ return { os: 'windows', os_version: version };
13463
+ }
13464
+ // macOS
13465
+ if (/macintosh|mac os x/i.test(userAgent)) {
13466
+ const match = userAgent.match(/mac os x (\d+[._]\d+)/i);
13467
+ return {
13468
+ os: 'macos',
13469
+ os_version: match ? match[1].replace('_', '.') : 'unknown'
13470
+ };
13471
+ }
13472
+ // iOS
13473
+ if (/iphone|ipad|ipod/i.test(userAgent)) {
13474
+ const match = userAgent.match(/os (\d+[._]\d+)/i);
13475
+ return {
13476
+ os: 'ios',
13477
+ os_version: match ? match[1].replace('_', '.') : 'unknown'
13478
+ };
13479
+ }
13480
+ // Android
13481
+ if (/android/i.test(userAgent)) {
13482
+ const match = userAgent.match(/android (\d+\.\d+)/i);
13483
+ return {
13484
+ os: 'android',
13485
+ os_version: match ? match[1] : 'unknown'
13486
+ };
13487
+ }
13488
+ // Linux
13489
+ if (/linux/i.test(userAgent)) {
13490
+ return { os: 'linux', os_version: 'unknown' };
13491
+ }
13492
+ return { os: 'unknown', os_version: 'unknown' };
13493
+ }
13494
+ /**
13495
+ * Extract UTM parameters from URL
13496
+ */
13497
+ function extractUTMParams(url) {
13498
+ const urlObj = new URL(url);
13499
+ const utmParams = {};
13500
+ const utmKeys = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content'];
13501
+ utmKeys.forEach(key => {
13502
+ const value = urlObj.searchParams.get(key);
13503
+ if (value) {
13504
+ utmParams[key] = value;
13505
+ }
13506
+ });
13507
+ return utmParams;
13508
+ }
13509
+ /**
13510
+ * Extract domain from URL
13511
+ */
13512
+ function extractDomain(url) {
13513
+ try {
13514
+ const urlObj = new URL(url);
13515
+ return urlObj.hostname;
13516
+ }
13517
+ catch (_a) {
13518
+ return '';
13519
+ }
13520
+ }
13521
+ /**
13522
+ * Get device information
13523
+ */
13524
+ function getDeviceInfo() {
13525
+ if (!isBrowser$1) {
13526
+ return {
13527
+ device_type: 'unknown',
13528
+ browser: 'unknown',
13529
+ browser_version: 'unknown',
13530
+ os: 'unknown',
13531
+ os_version: 'unknown',
13532
+ screen_resolution: 'unknown',
13533
+ viewport_size: 'unknown',
13534
+ color_depth: 0,
13535
+ timezone: 'unknown',
13536
+ language: 'unknown',
13537
+ languages: []
13538
+ };
13539
+ }
13540
+ const { browser, browser_version } = detectBrowser();
13541
+ const { os, os_version } = detectOS();
13542
+ return {
13543
+ device_type: detectDeviceType(),
13544
+ browser,
13545
+ browser_version,
13546
+ os,
13547
+ os_version,
13548
+ screen_resolution: `${window.screen.width}x${window.screen.height}`,
13549
+ viewport_size: `${window.innerWidth}x${window.innerHeight}`,
13550
+ color_depth: window.screen.colorDepth,
13551
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
13552
+ language: navigator.language,
13553
+ languages: [...(navigator.languages || [navigator.language])],
13554
+ raw_user_agent: navigator.userAgent
13555
+ };
13556
+ }
13557
+ /**
13558
+ * Get location information
13559
+ */
13560
+ function getLocationInfo() {
13561
+ if (!isBrowser$1) {
13562
+ return {
13563
+ current_url: '',
13564
+ pathname: '',
13565
+ search: '',
13566
+ hash: '',
13567
+ title: '',
13568
+ referrer: '',
13569
+ referrer_domain: '',
13570
+ initial_referrer: '',
13571
+ initial_referrer_domain: ''
13572
+ };
13573
+ }
13574
+ const currentUrl = window.location.href;
13575
+ const referrer = document.referrer;
13576
+ const utmParams = extractUTMParams(currentUrl);
13577
+ 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);
13578
+ }
13579
+ /**
13580
+ * Get all automatic properties
13581
+ */
13582
+ function getAutomaticProperties() {
13583
+ return Object.assign(Object.assign({}, getDeviceInfo()), getLocationInfo());
13584
+ }
13585
+ /**
13586
+ * Get initial properties that should be captured once per session
13587
+ */
13588
+ function getInitialProperties() {
13589
+ if (!isBrowser$1)
13590
+ return {};
13591
+ const locationInfo = getLocationInfo();
13592
+ return {
13593
+ initial_referrer: locationInfo.initial_referrer,
13594
+ initial_referrer_domain: locationInfo.initial_referrer_domain,
13595
+ initial_url: locationInfo.current_url,
13596
+ initial_pathname: locationInfo.pathname,
13597
+ initial_utm_source: locationInfo.utm_source,
13598
+ initial_utm_medium: locationInfo.utm_medium,
13599
+ initial_utm_campaign: locationInfo.utm_campaign,
13600
+ initial_utm_term: locationInfo.utm_term,
13601
+ initial_utm_content: locationInfo.utm_content
13602
+ };
13603
+ }
13604
+ /**
13605
+ * Get current page properties (changes with navigation)
13606
+ */
13607
+ function getCurrentPageProperties() {
13608
+ if (!isBrowser$1)
13609
+ return {};
13610
+ const locationInfo = getLocationInfo();
13611
+ return {
13612
+ current_url: locationInfo.current_url,
13613
+ pathname: locationInfo.pathname,
13614
+ search: locationInfo.search,
13615
+ hash: locationInfo.hash,
13616
+ title: locationInfo.title,
13617
+ referrer: locationInfo.referrer,
13618
+ referrer_domain: locationInfo.referrer_domain,
13619
+ utm_source: locationInfo.utm_source,
13620
+ utm_medium: locationInfo.utm_medium,
13621
+ utm_campaign: locationInfo.utm_campaign,
13622
+ utm_term: locationInfo.utm_term,
13623
+ utm_content: locationInfo.utm_content
13624
+ };
13625
+ }
13626
+
13627
+ /**
13628
+ * Property Manager for HumanBehavior SDK
13629
+ * Handles automatic properties, session properties, and user properties
13630
+ */
13631
+ class PropertyManager {
13632
+ constructor(config = {}) {
13633
+ this.sessionProperties = {};
13634
+ this.userProperties = {};
13635
+ this.initialProperties = {};
13636
+ this.isInitialized = false;
13637
+ this.config = Object.assign({ enableAutomaticProperties: true, enableSessionProperties: true, enableUserProperties: true, propertyDenylist: [] }, config);
13638
+ this.automaticProperties = getAutomaticProperties();
13639
+ this.initialize();
13640
+ }
13641
+ /**
13642
+ * Initialize the property manager
13643
+ */
13644
+ initialize() {
13645
+ if (this.isInitialized)
13646
+ return;
13647
+ // Capture initial properties once
13648
+ this.initialProperties = getInitialProperties();
13649
+ // Load session properties from sessionStorage
13650
+ this.loadSessionProperties();
13651
+ this.isInitialized = true;
13652
+ }
13653
+ /**
13654
+ * Get all properties for an event
13655
+ */
13656
+ getEventProperties(eventProperties = {}) {
13657
+ const properties = Object.assign({}, eventProperties);
13658
+ // Add automatic properties
13659
+ if (this.config.enableAutomaticProperties) {
13660
+ Object.assign(properties, this.getAutomaticProperties());
13661
+ }
13662
+ // Add session properties
13663
+ if (this.config.enableSessionProperties) {
13664
+ Object.assign(properties, this.sessionProperties);
13665
+ }
13666
+ // Add user properties
13667
+ if (this.config.enableUserProperties) {
13668
+ Object.assign(properties, this.userProperties);
13669
+ }
13670
+ // Add initial properties (only once per session)
13671
+ if (!this.sessionProperties['$initial_properties_captured']) {
13672
+ Object.assign(properties, this.initialProperties);
13673
+ this.setSessionProperty('$initial_properties_captured', true);
13674
+ }
13675
+ // Apply denylist
13676
+ this.applyDenylist(properties);
13677
+ return properties;
13678
+ }
13679
+ /**
13680
+ * Get automatic properties
13681
+ */
13682
+ getAutomaticProperties() {
13683
+ return Object.assign(Object.assign({}, this.automaticProperties), getCurrentPageProperties() // Always get fresh page properties
13684
+ );
13685
+ }
13686
+ /**
13687
+ * Get automatic properties with GeoIP data merged in
13688
+ */
13689
+ getAutomaticPropertiesWithGeoIP(geoIPProperties = {}) {
13690
+ return Object.assign(Object.assign(Object.assign({}, this.automaticProperties), getCurrentPageProperties()), geoIPProperties);
13691
+ }
13692
+ /**
13693
+ * Set a session property
13694
+ */
13695
+ setSessionProperty(key, value) {
13696
+ this.sessionProperties[key] = value;
13697
+ this.saveSessionProperties();
13698
+ }
13699
+ /**
13700
+ * Set multiple session properties
13701
+ */
13702
+ setSessionProperties(properties) {
13703
+ Object.assign(this.sessionProperties, properties);
13704
+ this.saveSessionProperties();
13705
+ }
13706
+ /**
13707
+ * Get a session property
13708
+ */
13709
+ getSessionProperty(key) {
13710
+ return this.sessionProperties[key];
13711
+ }
13712
+ /**
13713
+ * Remove a session property
13714
+ */
13715
+ removeSessionProperty(key) {
13716
+ delete this.sessionProperties[key];
13717
+ this.saveSessionProperties();
13718
+ }
13719
+ /**
13720
+ * Set a user property
13721
+ */
13722
+ setUserProperty(key, value) {
13723
+ this.userProperties[key] = value;
13724
+ }
13725
+ /**
13726
+ * Set multiple user properties
13727
+ */
13728
+ setUserProperties(properties) {
13729
+ Object.assign(this.userProperties, properties);
13730
+ }
13731
+ /**
13732
+ * Get a user property
13733
+ */
13734
+ getUserProperty(key) {
13735
+ return this.userProperties[key];
13736
+ }
13737
+ /**
13738
+ * Remove a user property
13739
+ */
13740
+ removeUserProperty(key) {
13741
+ delete this.userProperties[key];
13742
+ }
13743
+ /**
13744
+ * Set a property only if it hasn't been set before
13745
+ */
13746
+ setOnce(key, value, scope = 'user') {
13747
+ if (scope === 'session') {
13748
+ if (!(key in this.sessionProperties)) {
13749
+ this.setSessionProperty(key, value);
13750
+ }
13751
+ }
13752
+ else {
13753
+ if (!(key in this.userProperties)) {
13754
+ this.setUserProperty(key, value);
13755
+ }
13756
+ }
13757
+ }
13758
+ /**
13759
+ * Clear all session properties
13760
+ */
13761
+ clearSessionProperties() {
13762
+ this.sessionProperties = {};
13763
+ this.saveSessionProperties();
13764
+ }
13765
+ /**
13766
+ * Clear all user properties
13767
+ */
13768
+ clearUserProperties() {
13769
+ this.userProperties = {};
13770
+ }
13771
+ /**
13772
+ * Reset all properties
13773
+ */
13774
+ reset() {
13775
+ this.clearSessionProperties();
13776
+ this.clearUserProperties();
13777
+ this.initialProperties = {};
13778
+ this.isInitialized = false;
13779
+ this.initialize();
13780
+ }
13781
+ /**
13782
+ * Load session properties from sessionStorage
13783
+ */
13784
+ loadSessionProperties() {
13785
+ if (typeof sessionStorage === 'undefined')
13786
+ return;
13787
+ try {
13788
+ const stored = sessionStorage.getItem('hb_session_properties');
13789
+ if (stored) {
13790
+ this.sessionProperties = JSON.parse(stored);
13791
+ }
13792
+ }
13793
+ catch (error) {
13794
+ console.warn('Failed to load session properties:', error);
13795
+ }
13796
+ }
13797
+ /**
13798
+ * Save session properties to sessionStorage
13799
+ */
13800
+ saveSessionProperties() {
13801
+ if (typeof sessionStorage === 'undefined')
13802
+ return;
13803
+ try {
13804
+ sessionStorage.setItem('hb_session_properties', JSON.stringify(this.sessionProperties));
13805
+ }
13806
+ catch (error) {
13807
+ console.warn('Failed to save session properties:', error);
13808
+ }
13809
+ }
13810
+ /**
13811
+ * Apply property denylist
13812
+ */
13813
+ applyDenylist(properties) {
13814
+ if (!this.config.propertyDenylist || this.config.propertyDenylist.length === 0) {
13815
+ return;
13816
+ }
13817
+ this.config.propertyDenylist.forEach(deniedKey => {
13818
+ delete properties[deniedKey];
13819
+ });
13820
+ }
13821
+ /**
13822
+ * Update automatic properties (call when page changes)
13823
+ */
13824
+ updateAutomaticProperties() {
13825
+ this.automaticProperties = getAutomaticProperties();
13826
+ }
13827
+ /**
13828
+ * Get all properties for debugging
13829
+ */
13830
+ getAllProperties() {
13831
+ return {
13832
+ automatic: this.getAutomaticProperties(),
13833
+ session: Object.assign({}, this.sessionProperties),
13834
+ user: Object.assign({}, this.userProperties),
13835
+ initial: Object.assign({}, this.initialProperties)
13836
+ };
13837
+ }
13838
+ }
13839
+
13176
13840
  // Check if we're in a browser environment
13177
13841
  const isBrowser = typeof window !== 'undefined';
13178
13842
  class HumanBehaviorTracker {
@@ -13184,7 +13848,7 @@ class HumanBehaviorTracker {
13184
13848
  var _a;
13185
13849
  // ✅ SUPPRESS COMMON RRWEB ERRORS FOR CLEAN CONSOLE
13186
13850
  if (isBrowser && (options === null || options === void 0 ? void 0 : options.suppressConsoleErrors) !== false) {
13187
- // Suppress canvas security errors
13851
+ // Suppress canvas security errors and network errors
13188
13852
  const originalConsoleError = console.error;
13189
13853
  console.error = (...args) => {
13190
13854
  const message = args.join(' ');
@@ -13195,8 +13859,14 @@ class HumanBehaviorTracker {
13195
13859
  message.includes('CORS') ||
13196
13860
  message.includes('Access-Control-Allow-Origin') ||
13197
13861
  message.includes('Failed to load resource') ||
13198
- message.includes('net::ERR_BLOCKED_BY_CLIENT')) {
13199
- // Silently suppress these common rrweb errors
13862
+ message.includes('net::ERR_BLOCKED_BY_CLIENT') ||
13863
+ message.includes('NetworkError when attempting to fetch resource') ||
13864
+ message.includes('Failed to fetch') ||
13865
+ message.includes('TypeError: NetworkError') ||
13866
+ message.includes('HumanBehavior ERROR') ||
13867
+ message.includes('Failed to track custom event') ||
13868
+ message.includes('Error sending custom event')) {
13869
+ // Silently suppress these common errors
13200
13870
  return;
13201
13871
  }
13202
13872
  originalConsoleError.apply(console, args);
@@ -13210,8 +13880,12 @@ class HumanBehaviorTracker {
13210
13880
  message.includes('CORS') ||
13211
13881
  message.includes('Access-Control-Allow-Origin') ||
13212
13882
  message.includes('Failed to load resource') ||
13213
- message.includes('net::ERR_BLOCKED_BY_CLIENT')) {
13214
- // Silently suppress these common rrweb warnings
13883
+ message.includes('net::ERR_BLOCKED_BY_CLIENT') ||
13884
+ message.includes('NetworkError when attempting to fetch resource') ||
13885
+ message.includes('Failed to fetch') ||
13886
+ message.includes('Custom event network error') ||
13887
+ message.includes('Request blocked by ad blocker')) {
13888
+ // Silently suppress these common warnings
13215
13889
  return;
13216
13890
  }
13217
13891
  originalConsoleWarn.apply(console, args);
@@ -13223,7 +13897,9 @@ class HumanBehaviorTracker {
13223
13897
  message.includes('Tainted canvases') ||
13224
13898
  message.includes('toDataURL') ||
13225
13899
  message.includes('Cross-Origin') ||
13226
- message.includes('CORS')) {
13900
+ message.includes('CORS') ||
13901
+ message.includes('NetworkError') ||
13902
+ message.includes('Failed to fetch')) {
13227
13903
  event.preventDefault();
13228
13904
  return false;
13229
13905
  }
@@ -13239,7 +13915,10 @@ class HumanBehaviorTracker {
13239
13915
  this.configureLogging({ level: options.logLevel });
13240
13916
  }
13241
13917
  // Create new tracker instance
13242
- const tracker = new HumanBehaviorTracker(apiKey, options === null || options === void 0 ? void 0 : options.ingestionUrl);
13918
+ const tracker = new HumanBehaviorTracker(apiKey, options === null || options === void 0 ? void 0 : options.ingestionUrl, {
13919
+ enableAutomaticProperties: options === null || options === void 0 ? void 0 : options.enableAutomaticProperties,
13920
+ propertyDenylist: options === null || options === void 0 ? void 0 : options.propertyDenylist
13921
+ });
13243
13922
  // Store canvas recording preference
13244
13923
  tracker.recordCanvas = (_a = options === null || options === void 0 ? void 0 : options.recordCanvas) !== null && _a !== void 0 ? _a : false;
13245
13924
  // Set redacted fields if specified
@@ -13256,7 +13935,7 @@ class HumanBehaviorTracker {
13256
13935
  tracker.start();
13257
13936
  return tracker;
13258
13937
  }
13259
- constructor(apiKey, ingestionUrl) {
13938
+ constructor(apiKey, ingestionUrl, options) {
13260
13939
  this.eventIngestionQueue = [];
13261
13940
  this.userProperties = {};
13262
13941
  this.isProcessing = false;
@@ -13294,6 +13973,11 @@ class HumanBehaviorTracker {
13294
13973
  });
13295
13974
  this.apiKey = apiKey;
13296
13975
  this.redactionManager = new RedactionManager();
13976
+ // Initialize property manager
13977
+ this.propertyManager = new PropertyManager({
13978
+ enableAutomaticProperties: (options === null || options === void 0 ? void 0 : options.enableAutomaticProperties) !== false,
13979
+ propertyDenylist: (options === null || options === void 0 ? void 0 : options.propertyDenylist) || []
13980
+ });
13297
13981
  // Handle session restoration with improved continuity
13298
13982
  if (isBrowser) {
13299
13983
  const existingSessionId = localStorage.getItem(`human_behavior_session_id_${this.apiKey}`);
@@ -13332,7 +14016,28 @@ class HumanBehaviorTracker {
13332
14016
  try {
13333
14017
  const userId = this.getCookie(`human_behavior_end_user_id_${this.apiKey}`);
13334
14018
  logDebug(`Initializing with sessionId: ${this.sessionId}, userId: ${userId}`);
13335
- const { sessionId, endUserId } = yield this.api.init(this.sessionId, userId);
14019
+ // Get automatic properties for init
14020
+ const automaticProperties = this.propertyManager.getAutomaticProperties();
14021
+ // Create a custom init request with automatic properties
14022
+ const initResponse = yield fetch(`${this.api['baseUrl']}/api/ingestion/init`, {
14023
+ method: 'POST',
14024
+ headers: {
14025
+ 'Content-Type': 'application/json',
14026
+ 'Authorization': `Bearer ${this.apiKey}`,
14027
+ 'Referer': document.referrer || ''
14028
+ },
14029
+ body: JSON.stringify({
14030
+ sessionId: this.sessionId,
14031
+ endUserId: userId,
14032
+ entryURL: window.location.href,
14033
+ referrer: document.referrer,
14034
+ automaticProperties: automaticProperties
14035
+ })
14036
+ });
14037
+ if (!initResponse.ok) {
14038
+ throw new Error(`Failed to initialize: ${initResponse.statusText}`);
14039
+ }
14040
+ const { sessionId, endUserId } = yield initResponse.json();
13336
14041
  // Check if server returned a different session ID (for session continuity)
13337
14042
  if (sessionId !== this.sessionId) {
13338
14043
  logDebug(`Server returned different sessionId: ${sessionId} (client had: ${this.sessionId})`);
@@ -13344,6 +14049,10 @@ class HumanBehaviorTracker {
13344
14049
  }
13345
14050
  this.endUserId = endUserId;
13346
14051
  this.setCookie(`human_behavior_end_user_id_${this.apiKey}`, endUserId, 365);
14052
+ // Send IP information after successful initialization
14053
+ this.api.sendIPInfo(this.sessionId).catch(error => {
14054
+ logWarn('Failed to send IP info:', error);
14055
+ });
13347
14056
  // Only setup browser-specific handlers when in browser environment
13348
14057
  if (isBrowser) {
13349
14058
  this.setupPageUnloadHandler();
@@ -13464,6 +14173,8 @@ class HumanBehaviorTracker {
13464
14173
  return __awaiter(this, void 0, void 0, function* () {
13465
14174
  if (!this.initialized)
13466
14175
  return;
14176
+ // Update automatic properties for new page
14177
+ this.propertyManager.updateAutomaticProperties();
13467
14178
  try {
13468
14179
  const pageViewData = {
13469
14180
  url: url || window.location.href,
@@ -13473,11 +14184,13 @@ class HumanBehaviorTracker {
13473
14184
  referrer: document.referrer,
13474
14185
  timestamp: new Date().toISOString()
13475
14186
  };
14187
+ // Get enhanced properties with automatic properties
14188
+ const enhancedProperties = this.propertyManager.getEventProperties(pageViewData);
13476
14189
  // Add pageview event to the main event stream
13477
14190
  yield this.addEvent({
13478
14191
  type: 5, // Custom event type
13479
14192
  data: {
13480
- payload: Object.assign({ eventType: 'pageview' }, pageViewData)
14193
+ payload: Object.assign({ eventType: 'pageview' }, enhancedProperties)
13481
14194
  },
13482
14195
  timestamp: Date.now()
13483
14196
  });
@@ -13493,10 +14206,12 @@ class HumanBehaviorTracker {
13493
14206
  var _a, _b, _c, _d, _e;
13494
14207
  if (!this.initialized)
13495
14208
  return;
14209
+ // Get enhanced properties with automatic properties
14210
+ const enhancedProperties = this.propertyManager.getEventProperties(properties);
13496
14211
  try {
13497
14212
  // Send custom event directly to the API
13498
- yield this.api.sendCustomEvent(this.sessionId, eventName, properties);
13499
- logDebug(`Custom event tracked: ${eventName}`, properties);
14213
+ yield this.api.sendCustomEvent(this.sessionId, eventName, enhancedProperties);
14214
+ logDebug(`Custom event tracked: ${eventName}`, enhancedProperties);
13500
14215
  }
13501
14216
  catch (error) {
13502
14217
  logError('Failed to track custom event:', error);
@@ -13516,7 +14231,7 @@ class HumanBehaviorTracker {
13516
14231
  try {
13517
14232
  const customEventData = {
13518
14233
  eventName: eventName,
13519
- properties: properties || {},
14234
+ properties: enhancedProperties || {},
13520
14235
  timestamp: new Date().toISOString(),
13521
14236
  url: window.location.href,
13522
14237
  pathname: window.location.pathname
@@ -13816,8 +14531,25 @@ class HumanBehaviorTracker {
13816
14531
  // Store user properties
13817
14532
  this.userProperties = userProperties;
13818
14533
  logDebug('Identifying user:', { userProperties, originalEndUserId, sessionId: this.sessionId });
13819
- // Send user data with the original endUserId
13820
- yield this.api.sendUserData(originalEndUserId, userProperties, this.sessionId);
14534
+ // Get automatic properties and send with user data
14535
+ const automaticProperties = this.propertyManager.getAutomaticProperties();
14536
+ // Create a custom user request with automatic properties
14537
+ const userResponse = yield fetch(`${this.api['baseUrl']}/api/ingestion/user`, {
14538
+ method: 'POST',
14539
+ headers: {
14540
+ 'Content-Type': 'application/json',
14541
+ 'Authorization': `Bearer ${this.apiKey}`
14542
+ },
14543
+ body: JSON.stringify({
14544
+ userId: originalEndUserId,
14545
+ userAttributes: userProperties,
14546
+ sessionId: this.sessionId,
14547
+ automaticProperties: automaticProperties
14548
+ })
14549
+ });
14550
+ if (!userResponse.ok) {
14551
+ throw new Error(`Failed to identify user: ${userResponse.statusText}`);
14552
+ }
13821
14553
  // Don't update endUserId - keep it as the original UUID
13822
14554
  return originalEndUserId || '';
13823
14555
  });
@@ -13865,7 +14597,7 @@ class HumanBehaviorTracker {
13865
14597
  collectFonts: false, // Disable font collection to reduce errors
13866
14598
  inlineStylesheet: true, // Keep styles for proper session replay
13867
14599
  recordCrossOriginIframes: false, // Prevent cross-origin iframe errors
13868
- // ✅ CANVAS RECORDING - PostHog-style protection against overwhelm
14600
+ // ✅ CANVAS RECORDING - protection against overwhelm
13869
14601
  recordCanvas: this.recordCanvas, // Opt-in only
13870
14602
  sampling: this.recordCanvas ? { canvas: 4 } : undefined, // 4 FPS throttle
13871
14603
  dataURLOptions: this.recordCanvas ? {
@@ -14289,6 +15021,79 @@ class HumanBehaviorTracker {
14289
15021
  initialized: this.initialized
14290
15022
  };
14291
15023
  }
15024
+ // ===== PROPERTY MANAGEMENT METHODS =====
15025
+ /**
15026
+ * Set a session property that will be included in all events for this session
15027
+ */
15028
+ setSessionProperty(key, value) {
15029
+ this.propertyManager.setSessionProperty(key, value);
15030
+ }
15031
+ /**
15032
+ * Set multiple session properties
15033
+ */
15034
+ setSessionProperties(properties) {
15035
+ this.propertyManager.setSessionProperties(properties);
15036
+ }
15037
+ /**
15038
+ * Get a session property
15039
+ */
15040
+ getSessionProperty(key) {
15041
+ return this.propertyManager.getSessionProperty(key);
15042
+ }
15043
+ /**
15044
+ * Remove a session property
15045
+ */
15046
+ removeSessionProperty(key) {
15047
+ this.propertyManager.removeSessionProperty(key);
15048
+ }
15049
+ /**
15050
+ * Set a user property that will be included in all events
15051
+ */
15052
+ setUserProperty(key, value) {
15053
+ this.propertyManager.setUserProperty(key, value);
15054
+ }
15055
+ /**
15056
+ * Set multiple user properties
15057
+ */
15058
+ setUserProperties(properties) {
15059
+ this.propertyManager.setUserProperties(properties);
15060
+ }
15061
+ /**
15062
+ * Get a user property
15063
+ */
15064
+ getUserProperty(key) {
15065
+ return this.propertyManager.getUserProperty(key);
15066
+ }
15067
+ /**
15068
+ * Remove a user property
15069
+ */
15070
+ removeUserProperty(key) {
15071
+ this.propertyManager.removeUserProperty(key);
15072
+ }
15073
+ /**
15074
+ * Set a property only if it hasn't been set before
15075
+ */
15076
+ setOnce(key, value, scope = 'user') {
15077
+ this.propertyManager.setOnce(key, value, scope);
15078
+ }
15079
+ /**
15080
+ * Clear all session properties
15081
+ */
15082
+ clearSessionProperties() {
15083
+ this.propertyManager.clearSessionProperties();
15084
+ }
15085
+ /**
15086
+ * Clear all user properties
15087
+ */
15088
+ clearUserProperties() {
15089
+ this.propertyManager.clearUserProperties();
15090
+ }
15091
+ /**
15092
+ * Get all properties for debugging
15093
+ */
15094
+ getAllProperties() {
15095
+ return this.propertyManager.getAllProperties();
15096
+ }
14292
15097
  }
14293
15098
  // Only expose to window object in browser environments
14294
15099
  if (isBrowser) {
@@ -14310,7 +15115,16 @@ if (typeof window !== 'undefined') {
14310
15115
  exports.HumanBehaviorAPI = HumanBehaviorAPI;
14311
15116
  exports.HumanBehaviorTracker = HumanBehaviorTracker;
14312
15117
  exports.MAX_CHUNK_SIZE_BYTES = MAX_CHUNK_SIZE_BYTES;
15118
+ exports.PropertyManager = PropertyManager;
14313
15119
  exports.RedactionManager = RedactionManager;
15120
+ exports.clearIPCache = clearIPCache;
15121
+ exports.getAutomaticProperties = getAutomaticProperties;
15122
+ exports.getCachedIP = getCachedIP;
15123
+ exports.getClientIP = getClientIP;
15124
+ exports.getCurrentPageProperties = getCurrentPageProperties;
15125
+ exports.getDeviceInfo = getDeviceInfo;
15126
+ exports.getInitialProperties = getInitialProperties;
15127
+ exports.getLocationInfo = getLocationInfo;
14314
15128
  exports.isChunkSizeExceeded = isChunkSizeExceeded;
14315
15129
  exports.logDebug = logDebug;
14316
15130
  exports.logError = logError;