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
@@ -12393,6 +12393,145 @@ const logWarn = (message, ...args) => logger.warn(message, ...args);
12393
12393
  const logInfo = (message, ...args) => logger.info(message, ...args);
12394
12394
  const logDebug = (message, ...args) => logger.debug(message, ...args);
12395
12395
 
12396
+ /**
12397
+ * IP Address Detection Utility
12398
+ * Attempts to get the client's public IP address using multiple methods
12399
+ */
12400
+ /**
12401
+ * Get IP address using STUN server (most reliable)
12402
+ */
12403
+ function getIPFromSTUN() {
12404
+ return __awaiter(this, void 0, void 0, function* () {
12405
+ try {
12406
+ const pc = new RTCPeerConnection({
12407
+ iceServers: [
12408
+ { urls: 'stun:stun.l.google.com:19302' },
12409
+ { urls: 'stun:stun1.l.google.com:19302' },
12410
+ { urls: 'stun:stun2.l.google.com:19302' }
12411
+ ]
12412
+ });
12413
+ return new Promise((resolve) => {
12414
+ const timeout = setTimeout(() => {
12415
+ pc.close();
12416
+ resolve(null);
12417
+ }, 5000);
12418
+ pc.createDataChannel('');
12419
+ pc.createOffer()
12420
+ .then(offer => pc.setLocalDescription(offer))
12421
+ .catch(() => resolve(null));
12422
+ pc.onicecandidate = (event) => {
12423
+ if (event.candidate) {
12424
+ const candidate = event.candidate.candidate;
12425
+ const match = candidate.match(/([0-9]{1,3}(\.[0-9]{1,3}){3})/);
12426
+ if (match) {
12427
+ clearTimeout(timeout);
12428
+ pc.close();
12429
+ resolve(match[1]);
12430
+ }
12431
+ }
12432
+ };
12433
+ });
12434
+ }
12435
+ catch (error) {
12436
+ return null;
12437
+ }
12438
+ });
12439
+ }
12440
+ /**
12441
+ * Get IP address using public IP service (fallback)
12442
+ */
12443
+ function getIPFromPublicService() {
12444
+ return __awaiter(this, void 0, void 0, function* () {
12445
+ try {
12446
+ const response = yield fetch('https://api.ipify.org?format=json', {
12447
+ method: 'GET'
12448
+ });
12449
+ if (response.ok) {
12450
+ const data = yield response.json();
12451
+ return data.ip;
12452
+ }
12453
+ }
12454
+ catch (error) {
12455
+ // Try alternative service
12456
+ try {
12457
+ const response = yield fetch('https://httpbin.org/ip', {
12458
+ method: 'GET'
12459
+ });
12460
+ if (response.ok) {
12461
+ const data = yield response.json();
12462
+ return data.origin;
12463
+ }
12464
+ }
12465
+ catch (fallbackError) {
12466
+ // Last resort
12467
+ try {
12468
+ const response = yield fetch('https://api.myip.com', {
12469
+ method: 'GET'
12470
+ });
12471
+ if (response.ok) {
12472
+ const data = yield response.json();
12473
+ return data.ip;
12474
+ }
12475
+ }
12476
+ catch (lastError) {
12477
+ return null;
12478
+ }
12479
+ }
12480
+ }
12481
+ return null;
12482
+ });
12483
+ }
12484
+ /**
12485
+ * Get client's public IP address
12486
+ * Tries STUN first (most reliable), then falls back to public services
12487
+ */
12488
+ function getClientIP() {
12489
+ return __awaiter(this, void 0, void 0, function* () {
12490
+ const startTime = Date.now();
12491
+ // Try STUN first (most reliable and privacy-friendly)
12492
+ const stunIP = yield getIPFromSTUN();
12493
+ if (stunIP) {
12494
+ return {
12495
+ ip: stunIP,
12496
+ method: 'stun',
12497
+ timestamp: startTime
12498
+ };
12499
+ }
12500
+ // Fallback to public IP service
12501
+ const publicIP = yield getIPFromPublicService();
12502
+ if (publicIP) {
12503
+ return {
12504
+ ip: publicIP,
12505
+ method: 'public-service',
12506
+ timestamp: startTime
12507
+ };
12508
+ }
12509
+ return null;
12510
+ });
12511
+ }
12512
+ /**
12513
+ * Get IP address with caching to avoid repeated requests
12514
+ */
12515
+ let cachedIP = null;
12516
+ let cacheTimestamp = 0;
12517
+ const CACHE_DURATION = 5 * 60 * 1000; // 5 minutes
12518
+ function getCachedIP() {
12519
+ return __awaiter(this, void 0, void 0, function* () {
12520
+ const now = Date.now();
12521
+ // Return cached IP if still valid
12522
+ if (cachedIP && (now - cacheTimestamp) < CACHE_DURATION) {
12523
+ return cachedIP;
12524
+ }
12525
+ // Get fresh IP
12526
+ const ipInfo = yield getClientIP();
12527
+ if (ipInfo) {
12528
+ cachedIP = ipInfo;
12529
+ cacheTimestamp = now;
12530
+ }
12531
+ return ipInfo;
12532
+ });
12533
+ }
12534
+
12396
12535
  const MAX_CHUNK_SIZE_BYTES = 1024 * 1024; // 1MB chunk size - more conservative
12397
12536
  function isChunkSizeExceeded(currentChunk, newEvent, sessionId) {
12398
12537
  const nextChunkSize = new TextEncoder().encode(JSON.stringify({
@@ -12486,6 +12625,45 @@ class HumanBehaviorAPI {
12486
12625
  }
12487
12626
  });
12488
12627
  }
12628
+ /**
12629
+ * Send IP address information to the server
12630
+ * This is called after successful initialization
12631
+ */
12632
+ sendIPInfo(sessionId) {
12633
+ return __awaiter(this, void 0, void 0, function* () {
12634
+ try {
12635
+ const ipInfo = yield getCachedIP();
12636
+ if (!ipInfo) {
12637
+ logWarn('No IP address available to send');
12638
+ return;
12639
+ }
12640
+ logDebug('Sending IP info:', ipInfo);
12641
+ const response = yield fetch(`${this.baseUrl}/api/ingestion/ip-info`, {
12642
+ method: 'POST',
12643
+ headers: {
12644
+ 'Content-Type': 'application/json',
12645
+ 'Authorization': `Bearer ${this.apiKey}`
12646
+ },
12647
+ body: JSON.stringify({
12648
+ sessionId: sessionId,
12649
+ clientIP: ipInfo.ip,
12650
+ ipDetectionMethod: ipInfo.method,
12651
+ timestamp: ipInfo.timestamp
12652
+ })
12653
+ });
12654
+ if (!response.ok) {
12655
+ const errorText = yield response.text();
12656
+ logWarn('Failed to send IP info:', response.status, errorText);
12657
+ }
12658
+ else {
12659
+ logDebug('IP info sent successfully');
12660
+ }
12661
+ }
12662
+ catch (error) {
12663
+ logWarn('Error sending IP info:', error);
12664
+ }
12665
+ });
12666
+ }
12489
12667
  sendEvents(events, sessionId, userId) {
12490
12668
  return __awaiter(this, void 0, void 0, function* () {
12491
12669
  // ✅ SIMPLE VALIDATION FOR ALL EVENTS
@@ -12712,7 +12890,7 @@ class HumanBehaviorAPI {
12712
12890
  // This module provides methods to configure rrweb's built-in masking
12713
12891
  // Uses CSS selectors and classes for reliable redaction without event corruption
12714
12892
  // Check if we're in a browser environment
12715
- const isBrowser$2 = typeof window !== 'undefined';
12893
+ const isBrowser$3 = typeof window !== 'undefined';
12716
12894
  class RedactionManager {
12717
12895
  constructor(options) {
12718
12896
  this.redactedText = '[REDACTED]';
@@ -12871,7 +13049,7 @@ class RedactionManager {
12871
13049
  * Check if a DOM change should be redacted based on its ID
12872
13050
  */
12873
13051
  shouldRedactDOMChange(changeData) {
12874
- if (!isBrowser$2)
13052
+ if (!isBrowser$3)
12875
13053
  return false;
12876
13054
  try {
12877
13055
  // Check if this change has an ID that we can use to find the element
@@ -13014,7 +13192,7 @@ class RedactionManager {
13014
13192
  * Check if an event is from a field that should be redacted
13015
13193
  */
13016
13194
  isFieldSelected(eventData) {
13017
- if (!isBrowser$2)
13195
+ if (!isBrowser$3)
13018
13196
  return false;
13019
13197
  try {
13020
13198
  // For input events (source 5), we need to determine if this is a sensitive field
@@ -13165,6 +13343,485 @@ class RedactionManager {
13165
13343
  // Export a default instance
13166
13344
  new RedactionManager();
13167
13345
 
13346
+ /**
13347
+ * Automatic Property Detection for HumanBehavior SDK
13348
+ * Captures device type, location, and initial referrer information
13349
+ */
13350
+ // Check if we're in a browser environment
13351
+ const isBrowser$2 = typeof window !== 'undefined';
13352
+ /**
13353
+ * Detect device type based on user agent and screen size
13354
+ */
13355
+ function detectDeviceType() {
13356
+ if (!isBrowser$2)
13357
+ return 'unknown';
13358
+ const userAgent = navigator.userAgent.toLowerCase();
13359
+ const screenWidth = window.screen.width;
13360
+ const screenHeight = window.screen.height;
13361
+ // Mobile detection
13362
+ if (/mobile|android|iphone|ipad|ipod|blackberry|windows phone/i.test(userAgent)) {
13363
+ if (/ipad/i.test(userAgent) || (screenWidth >= 768 && screenHeight >= 1024)) {
13364
+ return 'tablet';
13365
+ }
13366
+ return 'mobile';
13367
+ }
13368
+ // Desktop detection
13369
+ if (/windows|macintosh|linux/i.test(userAgent)) {
13370
+ return 'desktop';
13371
+ }
13372
+ return 'unknown';
13373
+ }
13374
+ /**
13375
+ * Extract browser information from user agent
13376
+ */
13377
+ function detectBrowser() {
13378
+ if (!isBrowser$2)
13379
+ return { browser: 'unknown', browser_version: 'unknown' };
13380
+ const userAgent = navigator.userAgent;
13381
+ // Chrome
13382
+ if (/chrome/i.test(userAgent) && !/edge/i.test(userAgent)) {
13383
+ const match = userAgent.match(/chrome\/(\d+)/i);
13384
+ return {
13385
+ browser: 'chrome',
13386
+ browser_version: match ? match[1] : 'unknown'
13387
+ };
13388
+ }
13389
+ // Firefox
13390
+ if (/firefox/i.test(userAgent)) {
13391
+ const match = userAgent.match(/firefox\/(\d+)/i);
13392
+ return {
13393
+ browser: 'firefox',
13394
+ browser_version: match ? match[1] : 'unknown'
13395
+ };
13396
+ }
13397
+ // Safari
13398
+ if (/safari/i.test(userAgent) && !/chrome/i.test(userAgent)) {
13399
+ const match = userAgent.match(/version\/(\d+)/i);
13400
+ return {
13401
+ browser: 'safari',
13402
+ browser_version: match ? match[1] : 'unknown'
13403
+ };
13404
+ }
13405
+ // Edge
13406
+ if (/edge/i.test(userAgent)) {
13407
+ const match = userAgent.match(/edge\/(\d+)/i);
13408
+ return {
13409
+ browser: 'edge',
13410
+ browser_version: match ? match[1] : 'unknown'
13411
+ };
13412
+ }
13413
+ // Internet Explorer
13414
+ if (/msie|trident/i.test(userAgent)) {
13415
+ const match = userAgent.match(/msie (\d+)/i) || userAgent.match(/rv:(\d+)/i);
13416
+ return {
13417
+ browser: 'ie',
13418
+ browser_version: match ? match[1] : 'unknown'
13419
+ };
13420
+ }
13421
+ return { browser: 'unknown', browser_version: 'unknown' };
13422
+ }
13423
+ /**
13424
+ * Extract operating system information from user agent
13425
+ */
13426
+ function detectOS() {
13427
+ if (!isBrowser$2)
13428
+ return { os: 'unknown', os_version: 'unknown' };
13429
+ const userAgent = navigator.userAgent;
13430
+ // Windows
13431
+ if (/windows/i.test(userAgent)) {
13432
+ const match = userAgent.match(/windows nt (\d+\.\d+)/i);
13433
+ let version = 'unknown';
13434
+ if (match) {
13435
+ const versionNum = parseFloat(match[1]);
13436
+ if (versionNum === 10.0)
13437
+ version = '10';
13438
+ else if (versionNum === 6.3)
13439
+ version = '8.1';
13440
+ else if (versionNum === 6.2)
13441
+ version = '8';
13442
+ else if (versionNum === 6.1)
13443
+ version = '7';
13444
+ else
13445
+ version = match[1];
13446
+ }
13447
+ return { os: 'windows', os_version: version };
13448
+ }
13449
+ // macOS
13450
+ if (/macintosh|mac os x/i.test(userAgent)) {
13451
+ const match = userAgent.match(/mac os x (\d+[._]\d+)/i);
13452
+ return {
13453
+ os: 'macos',
13454
+ os_version: match ? match[1].replace('_', '.') : 'unknown'
13455
+ };
13456
+ }
13457
+ // iOS
13458
+ if (/iphone|ipad|ipod/i.test(userAgent)) {
13459
+ const match = userAgent.match(/os (\d+[._]\d+)/i);
13460
+ return {
13461
+ os: 'ios',
13462
+ os_version: match ? match[1].replace('_', '.') : 'unknown'
13463
+ };
13464
+ }
13465
+ // Android
13466
+ if (/android/i.test(userAgent)) {
13467
+ const match = userAgent.match(/android (\d+\.\d+)/i);
13468
+ return {
13469
+ os: 'android',
13470
+ os_version: match ? match[1] : 'unknown'
13471
+ };
13472
+ }
13473
+ // Linux
13474
+ if (/linux/i.test(userAgent)) {
13475
+ return { os: 'linux', os_version: 'unknown' };
13476
+ }
13477
+ return { os: 'unknown', os_version: 'unknown' };
13478
+ }
13479
+ /**
13480
+ * Extract UTM parameters from URL
13481
+ */
13482
+ function extractUTMParams(url) {
13483
+ const urlObj = new URL(url);
13484
+ const utmParams = {};
13485
+ const utmKeys = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content'];
13486
+ utmKeys.forEach(key => {
13487
+ const value = urlObj.searchParams.get(key);
13488
+ if (value) {
13489
+ utmParams[key] = value;
13490
+ }
13491
+ });
13492
+ return utmParams;
13493
+ }
13494
+ /**
13495
+ * Extract domain from URL
13496
+ */
13497
+ function extractDomain(url) {
13498
+ try {
13499
+ const urlObj = new URL(url);
13500
+ return urlObj.hostname;
13501
+ }
13502
+ catch (_a) {
13503
+ return '';
13504
+ }
13505
+ }
13506
+ /**
13507
+ * Get device information
13508
+ */
13509
+ function getDeviceInfo() {
13510
+ if (!isBrowser$2) {
13511
+ return {
13512
+ device_type: 'unknown',
13513
+ browser: 'unknown',
13514
+ browser_version: 'unknown',
13515
+ os: 'unknown',
13516
+ os_version: 'unknown',
13517
+ screen_resolution: 'unknown',
13518
+ viewport_size: 'unknown',
13519
+ color_depth: 0,
13520
+ timezone: 'unknown',
13521
+ language: 'unknown',
13522
+ languages: []
13523
+ };
13524
+ }
13525
+ const { browser, browser_version } = detectBrowser();
13526
+ const { os, os_version } = detectOS();
13527
+ return {
13528
+ device_type: detectDeviceType(),
13529
+ browser,
13530
+ browser_version,
13531
+ os,
13532
+ os_version,
13533
+ screen_resolution: `${window.screen.width}x${window.screen.height}`,
13534
+ viewport_size: `${window.innerWidth}x${window.innerHeight}`,
13535
+ color_depth: window.screen.colorDepth,
13536
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
13537
+ language: navigator.language,
13538
+ languages: [...(navigator.languages || [navigator.language])],
13539
+ raw_user_agent: navigator.userAgent
13540
+ };
13541
+ }
13542
+ /**
13543
+ * Get location information
13544
+ */
13545
+ function getLocationInfo() {
13546
+ if (!isBrowser$2) {
13547
+ return {
13548
+ current_url: '',
13549
+ pathname: '',
13550
+ search: '',
13551
+ hash: '',
13552
+ title: '',
13553
+ referrer: '',
13554
+ referrer_domain: '',
13555
+ initial_referrer: '',
13556
+ initial_referrer_domain: ''
13557
+ };
13558
+ }
13559
+ const currentUrl = window.location.href;
13560
+ const referrer = document.referrer;
13561
+ const utmParams = extractUTMParams(currentUrl);
13562
+ 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);
13563
+ }
13564
+ /**
13565
+ * Get all automatic properties
13566
+ */
13567
+ function getAutomaticProperties() {
13568
+ return Object.assign(Object.assign({}, getDeviceInfo()), getLocationInfo());
13569
+ }
13570
+ /**
13571
+ * Get initial properties that should be captured once per session
13572
+ */
13573
+ function getInitialProperties() {
13574
+ if (!isBrowser$2)
13575
+ return {};
13576
+ const locationInfo = getLocationInfo();
13577
+ return {
13578
+ initial_referrer: locationInfo.initial_referrer,
13579
+ initial_referrer_domain: locationInfo.initial_referrer_domain,
13580
+ initial_url: locationInfo.current_url,
13581
+ initial_pathname: locationInfo.pathname,
13582
+ initial_utm_source: locationInfo.utm_source,
13583
+ initial_utm_medium: locationInfo.utm_medium,
13584
+ initial_utm_campaign: locationInfo.utm_campaign,
13585
+ initial_utm_term: locationInfo.utm_term,
13586
+ initial_utm_content: locationInfo.utm_content
13587
+ };
13588
+ }
13589
+ /**
13590
+ * Get current page properties (changes with navigation)
13591
+ */
13592
+ function getCurrentPageProperties() {
13593
+ if (!isBrowser$2)
13594
+ return {};
13595
+ const locationInfo = getLocationInfo();
13596
+ return {
13597
+ current_url: locationInfo.current_url,
13598
+ pathname: locationInfo.pathname,
13599
+ search: locationInfo.search,
13600
+ hash: locationInfo.hash,
13601
+ title: locationInfo.title,
13602
+ referrer: locationInfo.referrer,
13603
+ referrer_domain: locationInfo.referrer_domain,
13604
+ utm_source: locationInfo.utm_source,
13605
+ utm_medium: locationInfo.utm_medium,
13606
+ utm_campaign: locationInfo.utm_campaign,
13607
+ utm_term: locationInfo.utm_term,
13608
+ utm_content: locationInfo.utm_content
13609
+ };
13610
+ }
13611
+
13612
+ /**
13613
+ * Property Manager for HumanBehavior SDK
13614
+ * Handles automatic properties, session properties, and user properties
13615
+ */
13616
+ class PropertyManager {
13617
+ constructor(config = {}) {
13618
+ this.sessionProperties = {};
13619
+ this.userProperties = {};
13620
+ this.initialProperties = {};
13621
+ this.isInitialized = false;
13622
+ this.config = Object.assign({ enableAutomaticProperties: true, enableSessionProperties: true, enableUserProperties: true, propertyDenylist: [] }, config);
13623
+ this.automaticProperties = getAutomaticProperties();
13624
+ this.initialize();
13625
+ }
13626
+ /**
13627
+ * Initialize the property manager
13628
+ */
13629
+ initialize() {
13630
+ if (this.isInitialized)
13631
+ return;
13632
+ // Capture initial properties once
13633
+ this.initialProperties = getInitialProperties();
13634
+ // Load session properties from sessionStorage
13635
+ this.loadSessionProperties();
13636
+ this.isInitialized = true;
13637
+ }
13638
+ /**
13639
+ * Get all properties for an event
13640
+ */
13641
+ getEventProperties(eventProperties = {}) {
13642
+ const properties = Object.assign({}, eventProperties);
13643
+ // Add automatic properties
13644
+ if (this.config.enableAutomaticProperties) {
13645
+ Object.assign(properties, this.getAutomaticProperties());
13646
+ }
13647
+ // Add session properties
13648
+ if (this.config.enableSessionProperties) {
13649
+ Object.assign(properties, this.sessionProperties);
13650
+ }
13651
+ // Add user properties
13652
+ if (this.config.enableUserProperties) {
13653
+ Object.assign(properties, this.userProperties);
13654
+ }
13655
+ // Add initial properties (only once per session)
13656
+ if (!this.sessionProperties['$initial_properties_captured']) {
13657
+ Object.assign(properties, this.initialProperties);
13658
+ this.setSessionProperty('$initial_properties_captured', true);
13659
+ }
13660
+ // Apply denylist
13661
+ this.applyDenylist(properties);
13662
+ return properties;
13663
+ }
13664
+ /**
13665
+ * Get automatic properties
13666
+ */
13667
+ getAutomaticProperties() {
13668
+ return Object.assign(Object.assign({}, this.automaticProperties), getCurrentPageProperties() // Always get fresh page properties
13669
+ );
13670
+ }
13671
+ /**
13672
+ * Get automatic properties with GeoIP data merged in
13673
+ */
13674
+ getAutomaticPropertiesWithGeoIP(geoIPProperties = {}) {
13675
+ return Object.assign(Object.assign(Object.assign({}, this.automaticProperties), getCurrentPageProperties()), geoIPProperties);
13676
+ }
13677
+ /**
13678
+ * Set a session property
13679
+ */
13680
+ setSessionProperty(key, value) {
13681
+ this.sessionProperties[key] = value;
13682
+ this.saveSessionProperties();
13683
+ }
13684
+ /**
13685
+ * Set multiple session properties
13686
+ */
13687
+ setSessionProperties(properties) {
13688
+ Object.assign(this.sessionProperties, properties);
13689
+ this.saveSessionProperties();
13690
+ }
13691
+ /**
13692
+ * Get a session property
13693
+ */
13694
+ getSessionProperty(key) {
13695
+ return this.sessionProperties[key];
13696
+ }
13697
+ /**
13698
+ * Remove a session property
13699
+ */
13700
+ removeSessionProperty(key) {
13701
+ delete this.sessionProperties[key];
13702
+ this.saveSessionProperties();
13703
+ }
13704
+ /**
13705
+ * Set a user property
13706
+ */
13707
+ setUserProperty(key, value) {
13708
+ this.userProperties[key] = value;
13709
+ }
13710
+ /**
13711
+ * Set multiple user properties
13712
+ */
13713
+ setUserProperties(properties) {
13714
+ Object.assign(this.userProperties, properties);
13715
+ }
13716
+ /**
13717
+ * Get a user property
13718
+ */
13719
+ getUserProperty(key) {
13720
+ return this.userProperties[key];
13721
+ }
13722
+ /**
13723
+ * Remove a user property
13724
+ */
13725
+ removeUserProperty(key) {
13726
+ delete this.userProperties[key];
13727
+ }
13728
+ /**
13729
+ * Set a property only if it hasn't been set before
13730
+ */
13731
+ setOnce(key, value, scope = 'user') {
13732
+ if (scope === 'session') {
13733
+ if (!(key in this.sessionProperties)) {
13734
+ this.setSessionProperty(key, value);
13735
+ }
13736
+ }
13737
+ else {
13738
+ if (!(key in this.userProperties)) {
13739
+ this.setUserProperty(key, value);
13740
+ }
13741
+ }
13742
+ }
13743
+ /**
13744
+ * Clear all session properties
13745
+ */
13746
+ clearSessionProperties() {
13747
+ this.sessionProperties = {};
13748
+ this.saveSessionProperties();
13749
+ }
13750
+ /**
13751
+ * Clear all user properties
13752
+ */
13753
+ clearUserProperties() {
13754
+ this.userProperties = {};
13755
+ }
13756
+ /**
13757
+ * Reset all properties
13758
+ */
13759
+ reset() {
13760
+ this.clearSessionProperties();
13761
+ this.clearUserProperties();
13762
+ this.initialProperties = {};
13763
+ this.isInitialized = false;
13764
+ this.initialize();
13765
+ }
13766
+ /**
13767
+ * Load session properties from sessionStorage
13768
+ */
13769
+ loadSessionProperties() {
13770
+ if (typeof sessionStorage === 'undefined')
13771
+ return;
13772
+ try {
13773
+ const stored = sessionStorage.getItem('hb_session_properties');
13774
+ if (stored) {
13775
+ this.sessionProperties = JSON.parse(stored);
13776
+ }
13777
+ }
13778
+ catch (error) {
13779
+ console.warn('Failed to load session properties:', error);
13780
+ }
13781
+ }
13782
+ /**
13783
+ * Save session properties to sessionStorage
13784
+ */
13785
+ saveSessionProperties() {
13786
+ if (typeof sessionStorage === 'undefined')
13787
+ return;
13788
+ try {
13789
+ sessionStorage.setItem('hb_session_properties', JSON.stringify(this.sessionProperties));
13790
+ }
13791
+ catch (error) {
13792
+ console.warn('Failed to save session properties:', error);
13793
+ }
13794
+ }
13795
+ /**
13796
+ * Apply property denylist
13797
+ */
13798
+ applyDenylist(properties) {
13799
+ if (!this.config.propertyDenylist || this.config.propertyDenylist.length === 0) {
13800
+ return;
13801
+ }
13802
+ this.config.propertyDenylist.forEach(deniedKey => {
13803
+ delete properties[deniedKey];
13804
+ });
13805
+ }
13806
+ /**
13807
+ * Update automatic properties (call when page changes)
13808
+ */
13809
+ updateAutomaticProperties() {
13810
+ this.automaticProperties = getAutomaticProperties();
13811
+ }
13812
+ /**
13813
+ * Get all properties for debugging
13814
+ */
13815
+ getAllProperties() {
13816
+ return {
13817
+ automatic: this.getAutomaticProperties(),
13818
+ session: Object.assign({}, this.sessionProperties),
13819
+ user: Object.assign({}, this.userProperties),
13820
+ initial: Object.assign({}, this.initialProperties)
13821
+ };
13822
+ }
13823
+ }
13824
+
13168
13825
  // Check if we're in a browser environment
13169
13826
  const isBrowser$1 = typeof window !== 'undefined';
13170
13827
  class HumanBehaviorTracker {
@@ -13176,7 +13833,7 @@ class HumanBehaviorTracker {
13176
13833
  var _a;
13177
13834
  // ✅ SUPPRESS COMMON RRWEB ERRORS FOR CLEAN CONSOLE
13178
13835
  if (isBrowser$1 && (options === null || options === void 0 ? void 0 : options.suppressConsoleErrors) !== false) {
13179
- // Suppress canvas security errors
13836
+ // Suppress canvas security errors and network errors
13180
13837
  const originalConsoleError = console.error;
13181
13838
  console.error = (...args) => {
13182
13839
  const message = args.join(' ');
@@ -13187,8 +13844,14 @@ class HumanBehaviorTracker {
13187
13844
  message.includes('CORS') ||
13188
13845
  message.includes('Access-Control-Allow-Origin') ||
13189
13846
  message.includes('Failed to load resource') ||
13190
- message.includes('net::ERR_BLOCKED_BY_CLIENT')) {
13191
- // Silently suppress these common rrweb errors
13847
+ message.includes('net::ERR_BLOCKED_BY_CLIENT') ||
13848
+ message.includes('NetworkError when attempting to fetch resource') ||
13849
+ message.includes('Failed to fetch') ||
13850
+ message.includes('TypeError: NetworkError') ||
13851
+ message.includes('HumanBehavior ERROR') ||
13852
+ message.includes('Failed to track custom event') ||
13853
+ message.includes('Error sending custom event')) {
13854
+ // Silently suppress these common errors
13192
13855
  return;
13193
13856
  }
13194
13857
  originalConsoleError.apply(console, args);
@@ -13202,8 +13865,12 @@ class HumanBehaviorTracker {
13202
13865
  message.includes('CORS') ||
13203
13866
  message.includes('Access-Control-Allow-Origin') ||
13204
13867
  message.includes('Failed to load resource') ||
13205
- message.includes('net::ERR_BLOCKED_BY_CLIENT')) {
13206
- // Silently suppress these common rrweb warnings
13868
+ message.includes('net::ERR_BLOCKED_BY_CLIENT') ||
13869
+ message.includes('NetworkError when attempting to fetch resource') ||
13870
+ message.includes('Failed to fetch') ||
13871
+ message.includes('Custom event network error') ||
13872
+ message.includes('Request blocked by ad blocker')) {
13873
+ // Silently suppress these common warnings
13207
13874
  return;
13208
13875
  }
13209
13876
  originalConsoleWarn.apply(console, args);
@@ -13215,7 +13882,9 @@ class HumanBehaviorTracker {
13215
13882
  message.includes('Tainted canvases') ||
13216
13883
  message.includes('toDataURL') ||
13217
13884
  message.includes('Cross-Origin') ||
13218
- message.includes('CORS')) {
13885
+ message.includes('CORS') ||
13886
+ message.includes('NetworkError') ||
13887
+ message.includes('Failed to fetch')) {
13219
13888
  event.preventDefault();
13220
13889
  return false;
13221
13890
  }
@@ -13231,7 +13900,10 @@ class HumanBehaviorTracker {
13231
13900
  this.configureLogging({ level: options.logLevel });
13232
13901
  }
13233
13902
  // Create new tracker instance
13234
- const tracker = new HumanBehaviorTracker(apiKey, options === null || options === void 0 ? void 0 : options.ingestionUrl);
13903
+ const tracker = new HumanBehaviorTracker(apiKey, options === null || options === void 0 ? void 0 : options.ingestionUrl, {
13904
+ enableAutomaticProperties: options === null || options === void 0 ? void 0 : options.enableAutomaticProperties,
13905
+ propertyDenylist: options === null || options === void 0 ? void 0 : options.propertyDenylist
13906
+ });
13235
13907
  // Store canvas recording preference
13236
13908
  tracker.recordCanvas = (_a = options === null || options === void 0 ? void 0 : options.recordCanvas) !== null && _a !== void 0 ? _a : false;
13237
13909
  // Set redacted fields if specified
@@ -13248,7 +13920,7 @@ class HumanBehaviorTracker {
13248
13920
  tracker.start();
13249
13921
  return tracker;
13250
13922
  }
13251
- constructor(apiKey, ingestionUrl) {
13923
+ constructor(apiKey, ingestionUrl, options) {
13252
13924
  this.eventIngestionQueue = [];
13253
13925
  this.userProperties = {};
13254
13926
  this.isProcessing = false;
@@ -13286,6 +13958,11 @@ class HumanBehaviorTracker {
13286
13958
  });
13287
13959
  this.apiKey = apiKey;
13288
13960
  this.redactionManager = new RedactionManager();
13961
+ // Initialize property manager
13962
+ this.propertyManager = new PropertyManager({
13963
+ enableAutomaticProperties: (options === null || options === void 0 ? void 0 : options.enableAutomaticProperties) !== false,
13964
+ propertyDenylist: (options === null || options === void 0 ? void 0 : options.propertyDenylist) || []
13965
+ });
13289
13966
  // Handle session restoration with improved continuity
13290
13967
  if (isBrowser$1) {
13291
13968
  const existingSessionId = localStorage.getItem(`human_behavior_session_id_${this.apiKey}`);
@@ -13324,7 +14001,28 @@ class HumanBehaviorTracker {
13324
14001
  try {
13325
14002
  const userId = this.getCookie(`human_behavior_end_user_id_${this.apiKey}`);
13326
14003
  logDebug(`Initializing with sessionId: ${this.sessionId}, userId: ${userId}`);
13327
- const { sessionId, endUserId } = yield this.api.init(this.sessionId, userId);
14004
+ // Get automatic properties for init
14005
+ const automaticProperties = this.propertyManager.getAutomaticProperties();
14006
+ // Create a custom init request with automatic properties
14007
+ const initResponse = yield fetch(`${this.api['baseUrl']}/api/ingestion/init`, {
14008
+ method: 'POST',
14009
+ headers: {
14010
+ 'Content-Type': 'application/json',
14011
+ 'Authorization': `Bearer ${this.apiKey}`,
14012
+ 'Referer': document.referrer || ''
14013
+ },
14014
+ body: JSON.stringify({
14015
+ sessionId: this.sessionId,
14016
+ endUserId: userId,
14017
+ entryURL: window.location.href,
14018
+ referrer: document.referrer,
14019
+ automaticProperties: automaticProperties
14020
+ })
14021
+ });
14022
+ if (!initResponse.ok) {
14023
+ throw new Error(`Failed to initialize: ${initResponse.statusText}`);
14024
+ }
14025
+ const { sessionId, endUserId } = yield initResponse.json();
13328
14026
  // Check if server returned a different session ID (for session continuity)
13329
14027
  if (sessionId !== this.sessionId) {
13330
14028
  logDebug(`Server returned different sessionId: ${sessionId} (client had: ${this.sessionId})`);
@@ -13336,6 +14034,10 @@ class HumanBehaviorTracker {
13336
14034
  }
13337
14035
  this.endUserId = endUserId;
13338
14036
  this.setCookie(`human_behavior_end_user_id_${this.apiKey}`, endUserId, 365);
14037
+ // Send IP information after successful initialization
14038
+ this.api.sendIPInfo(this.sessionId).catch(error => {
14039
+ logWarn('Failed to send IP info:', error);
14040
+ });
13339
14041
  // Only setup browser-specific handlers when in browser environment
13340
14042
  if (isBrowser$1) {
13341
14043
  this.setupPageUnloadHandler();
@@ -13456,6 +14158,8 @@ class HumanBehaviorTracker {
13456
14158
  return __awaiter(this, void 0, void 0, function* () {
13457
14159
  if (!this.initialized)
13458
14160
  return;
14161
+ // Update automatic properties for new page
14162
+ this.propertyManager.updateAutomaticProperties();
13459
14163
  try {
13460
14164
  const pageViewData = {
13461
14165
  url: url || window.location.href,
@@ -13465,11 +14169,13 @@ class HumanBehaviorTracker {
13465
14169
  referrer: document.referrer,
13466
14170
  timestamp: new Date().toISOString()
13467
14171
  };
14172
+ // Get enhanced properties with automatic properties
14173
+ const enhancedProperties = this.propertyManager.getEventProperties(pageViewData);
13468
14174
  // Add pageview event to the main event stream
13469
14175
  yield this.addEvent({
13470
14176
  type: 5, // Custom event type
13471
14177
  data: {
13472
- payload: Object.assign({ eventType: 'pageview' }, pageViewData)
14178
+ payload: Object.assign({ eventType: 'pageview' }, enhancedProperties)
13473
14179
  },
13474
14180
  timestamp: Date.now()
13475
14181
  });
@@ -13485,10 +14191,12 @@ class HumanBehaviorTracker {
13485
14191
  var _a, _b, _c, _d, _e;
13486
14192
  if (!this.initialized)
13487
14193
  return;
14194
+ // Get enhanced properties with automatic properties
14195
+ const enhancedProperties = this.propertyManager.getEventProperties(properties);
13488
14196
  try {
13489
14197
  // Send custom event directly to the API
13490
- yield this.api.sendCustomEvent(this.sessionId, eventName, properties);
13491
- logDebug(`Custom event tracked: ${eventName}`, properties);
14198
+ yield this.api.sendCustomEvent(this.sessionId, eventName, enhancedProperties);
14199
+ logDebug(`Custom event tracked: ${eventName}`, enhancedProperties);
13492
14200
  }
13493
14201
  catch (error) {
13494
14202
  logError('Failed to track custom event:', error);
@@ -13508,7 +14216,7 @@ class HumanBehaviorTracker {
13508
14216
  try {
13509
14217
  const customEventData = {
13510
14218
  eventName: eventName,
13511
- properties: properties || {},
14219
+ properties: enhancedProperties || {},
13512
14220
  timestamp: new Date().toISOString(),
13513
14221
  url: window.location.href,
13514
14222
  pathname: window.location.pathname
@@ -13808,8 +14516,25 @@ class HumanBehaviorTracker {
13808
14516
  // Store user properties
13809
14517
  this.userProperties = userProperties;
13810
14518
  logDebug('Identifying user:', { userProperties, originalEndUserId, sessionId: this.sessionId });
13811
- // Send user data with the original endUserId
13812
- yield this.api.sendUserData(originalEndUserId, userProperties, this.sessionId);
14519
+ // Get automatic properties and send with user data
14520
+ const automaticProperties = this.propertyManager.getAutomaticProperties();
14521
+ // Create a custom user request with automatic properties
14522
+ const userResponse = yield fetch(`${this.api['baseUrl']}/api/ingestion/user`, {
14523
+ method: 'POST',
14524
+ headers: {
14525
+ 'Content-Type': 'application/json',
14526
+ 'Authorization': `Bearer ${this.apiKey}`
14527
+ },
14528
+ body: JSON.stringify({
14529
+ userId: originalEndUserId,
14530
+ userAttributes: userProperties,
14531
+ sessionId: this.sessionId,
14532
+ automaticProperties: automaticProperties
14533
+ })
14534
+ });
14535
+ if (!userResponse.ok) {
14536
+ throw new Error(`Failed to identify user: ${userResponse.statusText}`);
14537
+ }
13813
14538
  // Don't update endUserId - keep it as the original UUID
13814
14539
  return originalEndUserId || '';
13815
14540
  });
@@ -13857,7 +14582,7 @@ class HumanBehaviorTracker {
13857
14582
  collectFonts: false, // Disable font collection to reduce errors
13858
14583
  inlineStylesheet: true, // Keep styles for proper session replay
13859
14584
  recordCrossOriginIframes: false, // Prevent cross-origin iframe errors
13860
- // ✅ CANVAS RECORDING - PostHog-style protection against overwhelm
14585
+ // ✅ CANVAS RECORDING - protection against overwhelm
13861
14586
  recordCanvas: this.recordCanvas, // Opt-in only
13862
14587
  sampling: this.recordCanvas ? { canvas: 4 } : undefined, // 4 FPS throttle
13863
14588
  dataURLOptions: this.recordCanvas ? {
@@ -14281,6 +15006,79 @@ class HumanBehaviorTracker {
14281
15006
  initialized: this.initialized
14282
15007
  };
14283
15008
  }
15009
+ // ===== PROPERTY MANAGEMENT METHODS =====
15010
+ /**
15011
+ * Set a session property that will be included in all events for this session
15012
+ */
15013
+ setSessionProperty(key, value) {
15014
+ this.propertyManager.setSessionProperty(key, value);
15015
+ }
15016
+ /**
15017
+ * Set multiple session properties
15018
+ */
15019
+ setSessionProperties(properties) {
15020
+ this.propertyManager.setSessionProperties(properties);
15021
+ }
15022
+ /**
15023
+ * Get a session property
15024
+ */
15025
+ getSessionProperty(key) {
15026
+ return this.propertyManager.getSessionProperty(key);
15027
+ }
15028
+ /**
15029
+ * Remove a session property
15030
+ */
15031
+ removeSessionProperty(key) {
15032
+ this.propertyManager.removeSessionProperty(key);
15033
+ }
15034
+ /**
15035
+ * Set a user property that will be included in all events
15036
+ */
15037
+ setUserProperty(key, value) {
15038
+ this.propertyManager.setUserProperty(key, value);
15039
+ }
15040
+ /**
15041
+ * Set multiple user properties
15042
+ */
15043
+ setUserProperties(properties) {
15044
+ this.propertyManager.setUserProperties(properties);
15045
+ }
15046
+ /**
15047
+ * Get a user property
15048
+ */
15049
+ getUserProperty(key) {
15050
+ return this.propertyManager.getUserProperty(key);
15051
+ }
15052
+ /**
15053
+ * Remove a user property
15054
+ */
15055
+ removeUserProperty(key) {
15056
+ this.propertyManager.removeUserProperty(key);
15057
+ }
15058
+ /**
15059
+ * Set a property only if it hasn't been set before
15060
+ */
15061
+ setOnce(key, value, scope = 'user') {
15062
+ this.propertyManager.setOnce(key, value, scope);
15063
+ }
15064
+ /**
15065
+ * Clear all session properties
15066
+ */
15067
+ clearSessionProperties() {
15068
+ this.propertyManager.clearSessionProperties();
15069
+ }
15070
+ /**
15071
+ * Clear all user properties
15072
+ */
15073
+ clearUserProperties() {
15074
+ this.propertyManager.clearUserProperties();
15075
+ }
15076
+ /**
15077
+ * Get all properties for debugging
15078
+ */
15079
+ getAllProperties() {
15080
+ return this.propertyManager.getAllProperties();
15081
+ }
14284
15082
  }
14285
15083
  // Only expose to window object in browser environments
14286
15084
  if (isBrowser$1) {
@@ -14436,7 +15234,7 @@ const useUserTracking = () => {
14436
15234
  identifyUser
14437
15235
  };
14438
15236
  };
14439
- // Automatic page tracking component (similar to PostHog's PostHogPageView)
15237
+ // Automatic page tracking component
14440
15238
  function HumanBehaviorPageView() {
14441
15239
  const tracker = React.useContext(HumanBehaviorContext);
14442
15240
  React.useEffect(() => {