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,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$2 = typeof window !== 'undefined';
12891
+ const isBrowser$3 = 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$2)
13050
+ if (!isBrowser$3)
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$2)
13193
+ if (!isBrowser$3)
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$2 = typeof window !== 'undefined';
13350
+ /**
13351
+ * Detect device type based on user agent and screen size
13352
+ */
13353
+ function detectDeviceType() {
13354
+ if (!isBrowser$2)
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$2)
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$2)
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$2) {
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$2) {
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$2)
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$2)
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$1 = typeof window !== 'undefined';
13168
13825
  class HumanBehaviorTracker {
@@ -13174,7 +13831,7 @@ class HumanBehaviorTracker {
13174
13831
  var _a;
13175
13832
  // ✅ SUPPRESS COMMON RRWEB ERRORS FOR CLEAN CONSOLE
13176
13833
  if (isBrowser$1 && (options === null || options === void 0 ? void 0 : options.suppressConsoleErrors) !== false) {
13177
- // Suppress canvas security errors
13834
+ // Suppress canvas security errors and network errors
13178
13835
  const originalConsoleError = console.error;
13179
13836
  console.error = (...args) => {
13180
13837
  const message = args.join(' ');
@@ -13185,8 +13842,14 @@ class HumanBehaviorTracker {
13185
13842
  message.includes('CORS') ||
13186
13843
  message.includes('Access-Control-Allow-Origin') ||
13187
13844
  message.includes('Failed to load resource') ||
13188
- message.includes('net::ERR_BLOCKED_BY_CLIENT')) {
13189
- // Silently suppress these common rrweb errors
13845
+ message.includes('net::ERR_BLOCKED_BY_CLIENT') ||
13846
+ message.includes('NetworkError when attempting to fetch resource') ||
13847
+ message.includes('Failed to fetch') ||
13848
+ message.includes('TypeError: NetworkError') ||
13849
+ message.includes('HumanBehavior ERROR') ||
13850
+ message.includes('Failed to track custom event') ||
13851
+ message.includes('Error sending custom event')) {
13852
+ // Silently suppress these common errors
13190
13853
  return;
13191
13854
  }
13192
13855
  originalConsoleError.apply(console, args);
@@ -13200,8 +13863,12 @@ class HumanBehaviorTracker {
13200
13863
  message.includes('CORS') ||
13201
13864
  message.includes('Access-Control-Allow-Origin') ||
13202
13865
  message.includes('Failed to load resource') ||
13203
- message.includes('net::ERR_BLOCKED_BY_CLIENT')) {
13204
- // Silently suppress these common rrweb warnings
13866
+ message.includes('net::ERR_BLOCKED_BY_CLIENT') ||
13867
+ message.includes('NetworkError when attempting to fetch resource') ||
13868
+ message.includes('Failed to fetch') ||
13869
+ message.includes('Custom event network error') ||
13870
+ message.includes('Request blocked by ad blocker')) {
13871
+ // Silently suppress these common warnings
13205
13872
  return;
13206
13873
  }
13207
13874
  originalConsoleWarn.apply(console, args);
@@ -13213,7 +13880,9 @@ class HumanBehaviorTracker {
13213
13880
  message.includes('Tainted canvases') ||
13214
13881
  message.includes('toDataURL') ||
13215
13882
  message.includes('Cross-Origin') ||
13216
- message.includes('CORS')) {
13883
+ message.includes('CORS') ||
13884
+ message.includes('NetworkError') ||
13885
+ message.includes('Failed to fetch')) {
13217
13886
  event.preventDefault();
13218
13887
  return false;
13219
13888
  }
@@ -13229,7 +13898,10 @@ class HumanBehaviorTracker {
13229
13898
  this.configureLogging({ level: options.logLevel });
13230
13899
  }
13231
13900
  // Create new tracker instance
13232
- const tracker = new HumanBehaviorTracker(apiKey, options === null || options === void 0 ? void 0 : options.ingestionUrl);
13901
+ const tracker = new HumanBehaviorTracker(apiKey, options === null || options === void 0 ? void 0 : options.ingestionUrl, {
13902
+ enableAutomaticProperties: options === null || options === void 0 ? void 0 : options.enableAutomaticProperties,
13903
+ propertyDenylist: options === null || options === void 0 ? void 0 : options.propertyDenylist
13904
+ });
13233
13905
  // Store canvas recording preference
13234
13906
  tracker.recordCanvas = (_a = options === null || options === void 0 ? void 0 : options.recordCanvas) !== null && _a !== void 0 ? _a : false;
13235
13907
  // Set redacted fields if specified
@@ -13246,7 +13918,7 @@ class HumanBehaviorTracker {
13246
13918
  tracker.start();
13247
13919
  return tracker;
13248
13920
  }
13249
- constructor(apiKey, ingestionUrl) {
13921
+ constructor(apiKey, ingestionUrl, options) {
13250
13922
  this.eventIngestionQueue = [];
13251
13923
  this.userProperties = {};
13252
13924
  this.isProcessing = false;
@@ -13284,6 +13956,11 @@ class HumanBehaviorTracker {
13284
13956
  });
13285
13957
  this.apiKey = apiKey;
13286
13958
  this.redactionManager = new RedactionManager();
13959
+ // Initialize property manager
13960
+ this.propertyManager = new PropertyManager({
13961
+ enableAutomaticProperties: (options === null || options === void 0 ? void 0 : options.enableAutomaticProperties) !== false,
13962
+ propertyDenylist: (options === null || options === void 0 ? void 0 : options.propertyDenylist) || []
13963
+ });
13287
13964
  // Handle session restoration with improved continuity
13288
13965
  if (isBrowser$1) {
13289
13966
  const existingSessionId = localStorage.getItem(`human_behavior_session_id_${this.apiKey}`);
@@ -13322,7 +13999,28 @@ class HumanBehaviorTracker {
13322
13999
  try {
13323
14000
  const userId = this.getCookie(`human_behavior_end_user_id_${this.apiKey}`);
13324
14001
  logDebug(`Initializing with sessionId: ${this.sessionId}, userId: ${userId}`);
13325
- const { sessionId, endUserId } = yield this.api.init(this.sessionId, userId);
14002
+ // Get automatic properties for init
14003
+ const automaticProperties = this.propertyManager.getAutomaticProperties();
14004
+ // Create a custom init request with automatic properties
14005
+ const initResponse = yield fetch(`${this.api['baseUrl']}/api/ingestion/init`, {
14006
+ method: 'POST',
14007
+ headers: {
14008
+ 'Content-Type': 'application/json',
14009
+ 'Authorization': `Bearer ${this.apiKey}`,
14010
+ 'Referer': document.referrer || ''
14011
+ },
14012
+ body: JSON.stringify({
14013
+ sessionId: this.sessionId,
14014
+ endUserId: userId,
14015
+ entryURL: window.location.href,
14016
+ referrer: document.referrer,
14017
+ automaticProperties: automaticProperties
14018
+ })
14019
+ });
14020
+ if (!initResponse.ok) {
14021
+ throw new Error(`Failed to initialize: ${initResponse.statusText}`);
14022
+ }
14023
+ const { sessionId, endUserId } = yield initResponse.json();
13326
14024
  // Check if server returned a different session ID (for session continuity)
13327
14025
  if (sessionId !== this.sessionId) {
13328
14026
  logDebug(`Server returned different sessionId: ${sessionId} (client had: ${this.sessionId})`);
@@ -13334,6 +14032,10 @@ class HumanBehaviorTracker {
13334
14032
  }
13335
14033
  this.endUserId = endUserId;
13336
14034
  this.setCookie(`human_behavior_end_user_id_${this.apiKey}`, endUserId, 365);
14035
+ // Send IP information after successful initialization
14036
+ this.api.sendIPInfo(this.sessionId).catch(error => {
14037
+ logWarn('Failed to send IP info:', error);
14038
+ });
13337
14039
  // Only setup browser-specific handlers when in browser environment
13338
14040
  if (isBrowser$1) {
13339
14041
  this.setupPageUnloadHandler();
@@ -13454,6 +14156,8 @@ class HumanBehaviorTracker {
13454
14156
  return __awaiter(this, void 0, void 0, function* () {
13455
14157
  if (!this.initialized)
13456
14158
  return;
14159
+ // Update automatic properties for new page
14160
+ this.propertyManager.updateAutomaticProperties();
13457
14161
  try {
13458
14162
  const pageViewData = {
13459
14163
  url: url || window.location.href,
@@ -13463,11 +14167,13 @@ class HumanBehaviorTracker {
13463
14167
  referrer: document.referrer,
13464
14168
  timestamp: new Date().toISOString()
13465
14169
  };
14170
+ // Get enhanced properties with automatic properties
14171
+ const enhancedProperties = this.propertyManager.getEventProperties(pageViewData);
13466
14172
  // Add pageview event to the main event stream
13467
14173
  yield this.addEvent({
13468
14174
  type: 5, // Custom event type
13469
14175
  data: {
13470
- payload: Object.assign({ eventType: 'pageview' }, pageViewData)
14176
+ payload: Object.assign({ eventType: 'pageview' }, enhancedProperties)
13471
14177
  },
13472
14178
  timestamp: Date.now()
13473
14179
  });
@@ -13483,10 +14189,12 @@ class HumanBehaviorTracker {
13483
14189
  var _a, _b, _c, _d, _e;
13484
14190
  if (!this.initialized)
13485
14191
  return;
14192
+ // Get enhanced properties with automatic properties
14193
+ const enhancedProperties = this.propertyManager.getEventProperties(properties);
13486
14194
  try {
13487
14195
  // Send custom event directly to the API
13488
- yield this.api.sendCustomEvent(this.sessionId, eventName, properties);
13489
- logDebug(`Custom event tracked: ${eventName}`, properties);
14196
+ yield this.api.sendCustomEvent(this.sessionId, eventName, enhancedProperties);
14197
+ logDebug(`Custom event tracked: ${eventName}`, enhancedProperties);
13490
14198
  }
13491
14199
  catch (error) {
13492
14200
  logError('Failed to track custom event:', error);
@@ -13506,7 +14214,7 @@ class HumanBehaviorTracker {
13506
14214
  try {
13507
14215
  const customEventData = {
13508
14216
  eventName: eventName,
13509
- properties: properties || {},
14217
+ properties: enhancedProperties || {},
13510
14218
  timestamp: new Date().toISOString(),
13511
14219
  url: window.location.href,
13512
14220
  pathname: window.location.pathname
@@ -13806,8 +14514,25 @@ class HumanBehaviorTracker {
13806
14514
  // Store user properties
13807
14515
  this.userProperties = userProperties;
13808
14516
  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);
14517
+ // Get automatic properties and send with user data
14518
+ const automaticProperties = this.propertyManager.getAutomaticProperties();
14519
+ // Create a custom user request with automatic properties
14520
+ const userResponse = yield fetch(`${this.api['baseUrl']}/api/ingestion/user`, {
14521
+ method: 'POST',
14522
+ headers: {
14523
+ 'Content-Type': 'application/json',
14524
+ 'Authorization': `Bearer ${this.apiKey}`
14525
+ },
14526
+ body: JSON.stringify({
14527
+ userId: originalEndUserId,
14528
+ userAttributes: userProperties,
14529
+ sessionId: this.sessionId,
14530
+ automaticProperties: automaticProperties
14531
+ })
14532
+ });
14533
+ if (!userResponse.ok) {
14534
+ throw new Error(`Failed to identify user: ${userResponse.statusText}`);
14535
+ }
13811
14536
  // Don't update endUserId - keep it as the original UUID
13812
14537
  return originalEndUserId || '';
13813
14538
  });
@@ -13855,7 +14580,7 @@ class HumanBehaviorTracker {
13855
14580
  collectFonts: false, // Disable font collection to reduce errors
13856
14581
  inlineStylesheet: true, // Keep styles for proper session replay
13857
14582
  recordCrossOriginIframes: false, // Prevent cross-origin iframe errors
13858
- // ✅ CANVAS RECORDING - PostHog-style protection against overwhelm
14583
+ // ✅ CANVAS RECORDING - protection against overwhelm
13859
14584
  recordCanvas: this.recordCanvas, // Opt-in only
13860
14585
  sampling: this.recordCanvas ? { canvas: 4 } : undefined, // 4 FPS throttle
13861
14586
  dataURLOptions: this.recordCanvas ? {
@@ -14279,6 +15004,79 @@ class HumanBehaviorTracker {
14279
15004
  initialized: this.initialized
14280
15005
  };
14281
15006
  }
15007
+ // ===== PROPERTY MANAGEMENT METHODS =====
15008
+ /**
15009
+ * Set a session property that will be included in all events for this session
15010
+ */
15011
+ setSessionProperty(key, value) {
15012
+ this.propertyManager.setSessionProperty(key, value);
15013
+ }
15014
+ /**
15015
+ * Set multiple session properties
15016
+ */
15017
+ setSessionProperties(properties) {
15018
+ this.propertyManager.setSessionProperties(properties);
15019
+ }
15020
+ /**
15021
+ * Get a session property
15022
+ */
15023
+ getSessionProperty(key) {
15024
+ return this.propertyManager.getSessionProperty(key);
15025
+ }
15026
+ /**
15027
+ * Remove a session property
15028
+ */
15029
+ removeSessionProperty(key) {
15030
+ this.propertyManager.removeSessionProperty(key);
15031
+ }
15032
+ /**
15033
+ * Set a user property that will be included in all events
15034
+ */
15035
+ setUserProperty(key, value) {
15036
+ this.propertyManager.setUserProperty(key, value);
15037
+ }
15038
+ /**
15039
+ * Set multiple user properties
15040
+ */
15041
+ setUserProperties(properties) {
15042
+ this.propertyManager.setUserProperties(properties);
15043
+ }
15044
+ /**
15045
+ * Get a user property
15046
+ */
15047
+ getUserProperty(key) {
15048
+ return this.propertyManager.getUserProperty(key);
15049
+ }
15050
+ /**
15051
+ * Remove a user property
15052
+ */
15053
+ removeUserProperty(key) {
15054
+ this.propertyManager.removeUserProperty(key);
15055
+ }
15056
+ /**
15057
+ * Set a property only if it hasn't been set before
15058
+ */
15059
+ setOnce(key, value, scope = 'user') {
15060
+ this.propertyManager.setOnce(key, value, scope);
15061
+ }
15062
+ /**
15063
+ * Clear all session properties
15064
+ */
15065
+ clearSessionProperties() {
15066
+ this.propertyManager.clearSessionProperties();
15067
+ }
15068
+ /**
15069
+ * Clear all user properties
15070
+ */
15071
+ clearUserProperties() {
15072
+ this.propertyManager.clearUserProperties();
15073
+ }
15074
+ /**
15075
+ * Get all properties for debugging
15076
+ */
15077
+ getAllProperties() {
15078
+ return this.propertyManager.getAllProperties();
15079
+ }
14282
15080
  }
14283
15081
  // Only expose to window object in browser environments
14284
15082
  if (isBrowser$1) {
@@ -14399,7 +15197,7 @@ const useHumanBehavior = () => {
14399
15197
  }
14400
15198
  return tracker;
14401
15199
  };
14402
- // Automatic page tracking component (similar to PostHog's PostHogPageView)
15200
+ // Automatic page tracking component
14403
15201
  function HumanBehaviorPageView() {
14404
15202
  const tracker = useContext(HumanBehaviorContext);
14405
15203
  useEffect(() => {