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