humanbehavior-js 0.4.20 → 0.4.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/cjs/angular/index.cjs +799 -13
  2. package/dist/cjs/angular/index.cjs.map +1 -1
  3. package/dist/cjs/index.cjs +815 -13
  4. package/dist/cjs/index.cjs.map +1 -1
  5. package/dist/cjs/react/index.cjs +800 -14
  6. package/dist/cjs/react/index.cjs.map +1 -1
  7. package/dist/cjs/remix/index.cjs +800 -14
  8. package/dist/cjs/remix/index.cjs.map +1 -1
  9. package/dist/cjs/svelte/index.cjs +799 -13
  10. package/dist/cjs/svelte/index.cjs.map +1 -1
  11. package/dist/cjs/vue/index.cjs +799 -13
  12. package/dist/cjs/vue/index.cjs.map +1 -1
  13. package/dist/esm/angular/index.js +799 -13
  14. package/dist/esm/angular/index.js.map +1 -1
  15. package/dist/esm/index.js +807 -14
  16. package/dist/esm/index.js.map +1 -1
  17. package/dist/esm/react/index.js +800 -14
  18. package/dist/esm/react/index.js.map +1 -1
  19. package/dist/esm/remix/index.js +800 -14
  20. package/dist/esm/remix/index.js.map +1 -1
  21. package/dist/esm/svelte/index.js +799 -13
  22. package/dist/esm/svelte/index.js.map +1 -1
  23. package/dist/esm/vue/index.js +799 -13
  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 +175 -11
  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 {
@@ -13227,7 +13884,10 @@ class HumanBehaviorTracker {
13227
13884
  this.configureLogging({ level: options.logLevel });
13228
13885
  }
13229
13886
  // Create new tracker instance
13230
- const tracker = new HumanBehaviorTracker(apiKey, options === null || options === void 0 ? void 0 : options.ingestionUrl);
13887
+ const tracker = new HumanBehaviorTracker(apiKey, options === null || options === void 0 ? void 0 : options.ingestionUrl, {
13888
+ enableAutomaticProperties: options === null || options === void 0 ? void 0 : options.enableAutomaticProperties,
13889
+ propertyDenylist: options === null || options === void 0 ? void 0 : options.propertyDenylist
13890
+ });
13231
13891
  // Store canvas recording preference
13232
13892
  tracker.recordCanvas = (_a = options === null || options === void 0 ? void 0 : options.recordCanvas) !== null && _a !== void 0 ? _a : false;
13233
13893
  // Set redacted fields if specified
@@ -13244,7 +13904,7 @@ class HumanBehaviorTracker {
13244
13904
  tracker.start();
13245
13905
  return tracker;
13246
13906
  }
13247
- constructor(apiKey, ingestionUrl) {
13907
+ constructor(apiKey, ingestionUrl, options) {
13248
13908
  this.eventIngestionQueue = [];
13249
13909
  this.userProperties = {};
13250
13910
  this.isProcessing = false;
@@ -13282,6 +13942,11 @@ class HumanBehaviorTracker {
13282
13942
  });
13283
13943
  this.apiKey = apiKey;
13284
13944
  this.redactionManager = new RedactionManager();
13945
+ // Initialize property manager
13946
+ this.propertyManager = new PropertyManager({
13947
+ enableAutomaticProperties: (options === null || options === void 0 ? void 0 : options.enableAutomaticProperties) !== false,
13948
+ propertyDenylist: (options === null || options === void 0 ? void 0 : options.propertyDenylist) || []
13949
+ });
13285
13950
  // Handle session restoration with improved continuity
13286
13951
  if (isBrowser) {
13287
13952
  const existingSessionId = localStorage.getItem(`human_behavior_session_id_${this.apiKey}`);
@@ -13320,7 +13985,28 @@ class HumanBehaviorTracker {
13320
13985
  try {
13321
13986
  const userId = this.getCookie(`human_behavior_end_user_id_${this.apiKey}`);
13322
13987
  logDebug(`Initializing with sessionId: ${this.sessionId}, userId: ${userId}`);
13323
- const { sessionId, endUserId } = yield this.api.init(this.sessionId, userId);
13988
+ // Get automatic properties for init
13989
+ const automaticProperties = this.propertyManager.getAutomaticProperties();
13990
+ // Create a custom init request with automatic properties
13991
+ const initResponse = yield fetch(`${this.api['baseUrl']}/api/ingestion/init`, {
13992
+ method: 'POST',
13993
+ headers: {
13994
+ 'Content-Type': 'application/json',
13995
+ 'Authorization': `Bearer ${this.apiKey}`,
13996
+ 'Referer': document.referrer || ''
13997
+ },
13998
+ body: JSON.stringify({
13999
+ sessionId: this.sessionId,
14000
+ endUserId: userId,
14001
+ entryURL: window.location.href,
14002
+ referrer: document.referrer,
14003
+ automaticProperties: automaticProperties
14004
+ })
14005
+ });
14006
+ if (!initResponse.ok) {
14007
+ throw new Error(`Failed to initialize: ${initResponse.statusText}`);
14008
+ }
14009
+ const { sessionId, endUserId } = yield initResponse.json();
13324
14010
  // Check if server returned a different session ID (for session continuity)
13325
14011
  if (sessionId !== this.sessionId) {
13326
14012
  logDebug(`Server returned different sessionId: ${sessionId} (client had: ${this.sessionId})`);
@@ -13332,6 +14018,10 @@ class HumanBehaviorTracker {
13332
14018
  }
13333
14019
  this.endUserId = endUserId;
13334
14020
  this.setCookie(`human_behavior_end_user_id_${this.apiKey}`, endUserId, 365);
14021
+ // Send IP information after successful initialization
14022
+ this.api.sendIPInfo(this.sessionId).catch(error => {
14023
+ logWarn('Failed to send IP info:', error);
14024
+ });
13335
14025
  // Only setup browser-specific handlers when in browser environment
13336
14026
  if (isBrowser) {
13337
14027
  this.setupPageUnloadHandler();
@@ -13452,6 +14142,8 @@ class HumanBehaviorTracker {
13452
14142
  return __awaiter(this, void 0, void 0, function* () {
13453
14143
  if (!this.initialized)
13454
14144
  return;
14145
+ // Update automatic properties for new page
14146
+ this.propertyManager.updateAutomaticProperties();
13455
14147
  try {
13456
14148
  const pageViewData = {
13457
14149
  url: url || window.location.href,
@@ -13461,11 +14153,13 @@ class HumanBehaviorTracker {
13461
14153
  referrer: document.referrer,
13462
14154
  timestamp: new Date().toISOString()
13463
14155
  };
14156
+ // Get enhanced properties with automatic properties
14157
+ const enhancedProperties = this.propertyManager.getEventProperties(pageViewData);
13464
14158
  // Add pageview event to the main event stream
13465
14159
  yield this.addEvent({
13466
14160
  type: 5, // Custom event type
13467
14161
  data: {
13468
- payload: Object.assign({ eventType: 'pageview' }, pageViewData)
14162
+ payload: Object.assign({ eventType: 'pageview' }, enhancedProperties)
13469
14163
  },
13470
14164
  timestamp: Date.now()
13471
14165
  });
@@ -13481,10 +14175,12 @@ class HumanBehaviorTracker {
13481
14175
  var _a, _b, _c, _d, _e;
13482
14176
  if (!this.initialized)
13483
14177
  return;
14178
+ // Get enhanced properties with automatic properties
14179
+ const enhancedProperties = this.propertyManager.getEventProperties(properties);
13484
14180
  try {
13485
14181
  // Send custom event directly to the API
13486
- yield this.api.sendCustomEvent(this.sessionId, eventName, properties);
13487
- logDebug(`Custom event tracked: ${eventName}`, properties);
14182
+ yield this.api.sendCustomEvent(this.sessionId, eventName, enhancedProperties);
14183
+ logDebug(`Custom event tracked: ${eventName}`, enhancedProperties);
13488
14184
  }
13489
14185
  catch (error) {
13490
14186
  logError('Failed to track custom event:', error);
@@ -13504,7 +14200,7 @@ class HumanBehaviorTracker {
13504
14200
  try {
13505
14201
  const customEventData = {
13506
14202
  eventName: eventName,
13507
- properties: properties || {},
14203
+ properties: enhancedProperties || {},
13508
14204
  timestamp: new Date().toISOString(),
13509
14205
  url: window.location.href,
13510
14206
  pathname: window.location.pathname
@@ -13804,8 +14500,25 @@ class HumanBehaviorTracker {
13804
14500
  // Store user properties
13805
14501
  this.userProperties = userProperties;
13806
14502
  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);
14503
+ // Get automatic properties and send with user data
14504
+ const automaticProperties = this.propertyManager.getAutomaticProperties();
14505
+ // Create a custom user request with automatic properties
14506
+ const userResponse = yield fetch(`${this.api['baseUrl']}/api/ingestion/user`, {
14507
+ method: 'POST',
14508
+ headers: {
14509
+ 'Content-Type': 'application/json',
14510
+ 'Authorization': `Bearer ${this.apiKey}`
14511
+ },
14512
+ body: JSON.stringify({
14513
+ userId: originalEndUserId,
14514
+ userAttributes: userProperties,
14515
+ sessionId: this.sessionId,
14516
+ automaticProperties: automaticProperties
14517
+ })
14518
+ });
14519
+ if (!userResponse.ok) {
14520
+ throw new Error(`Failed to identify user: ${userResponse.statusText}`);
14521
+ }
13809
14522
  // Don't update endUserId - keep it as the original UUID
13810
14523
  return originalEndUserId || '';
13811
14524
  });
@@ -13853,7 +14566,7 @@ class HumanBehaviorTracker {
13853
14566
  collectFonts: false, // Disable font collection to reduce errors
13854
14567
  inlineStylesheet: true, // Keep styles for proper session replay
13855
14568
  recordCrossOriginIframes: false, // Prevent cross-origin iframe errors
13856
- // ✅ CANVAS RECORDING - PostHog-style protection against overwhelm
14569
+ // ✅ CANVAS RECORDING - protection against overwhelm
13857
14570
  recordCanvas: this.recordCanvas, // Opt-in only
13858
14571
  sampling: this.recordCanvas ? { canvas: 4 } : undefined, // 4 FPS throttle
13859
14572
  dataURLOptions: this.recordCanvas ? {
@@ -14277,6 +14990,79 @@ class HumanBehaviorTracker {
14277
14990
  initialized: this.initialized
14278
14991
  };
14279
14992
  }
14993
+ // ===== PROPERTY MANAGEMENT METHODS =====
14994
+ /**
14995
+ * Set a session property that will be included in all events for this session
14996
+ */
14997
+ setSessionProperty(key, value) {
14998
+ this.propertyManager.setSessionProperty(key, value);
14999
+ }
15000
+ /**
15001
+ * Set multiple session properties
15002
+ */
15003
+ setSessionProperties(properties) {
15004
+ this.propertyManager.setSessionProperties(properties);
15005
+ }
15006
+ /**
15007
+ * Get a session property
15008
+ */
15009
+ getSessionProperty(key) {
15010
+ return this.propertyManager.getSessionProperty(key);
15011
+ }
15012
+ /**
15013
+ * Remove a session property
15014
+ */
15015
+ removeSessionProperty(key) {
15016
+ this.propertyManager.removeSessionProperty(key);
15017
+ }
15018
+ /**
15019
+ * Set a user property that will be included in all events
15020
+ */
15021
+ setUserProperty(key, value) {
15022
+ this.propertyManager.setUserProperty(key, value);
15023
+ }
15024
+ /**
15025
+ * Set multiple user properties
15026
+ */
15027
+ setUserProperties(properties) {
15028
+ this.propertyManager.setUserProperties(properties);
15029
+ }
15030
+ /**
15031
+ * Get a user property
15032
+ */
15033
+ getUserProperty(key) {
15034
+ return this.propertyManager.getUserProperty(key);
15035
+ }
15036
+ /**
15037
+ * Remove a user property
15038
+ */
15039
+ removeUserProperty(key) {
15040
+ this.propertyManager.removeUserProperty(key);
15041
+ }
15042
+ /**
15043
+ * Set a property only if it hasn't been set before
15044
+ */
15045
+ setOnce(key, value, scope = 'user') {
15046
+ this.propertyManager.setOnce(key, value, scope);
15047
+ }
15048
+ /**
15049
+ * Clear all session properties
15050
+ */
15051
+ clearSessionProperties() {
15052
+ this.propertyManager.clearSessionProperties();
15053
+ }
15054
+ /**
15055
+ * Clear all user properties
15056
+ */
15057
+ clearUserProperties() {
15058
+ this.propertyManager.clearUserProperties();
15059
+ }
15060
+ /**
15061
+ * Get all properties for debugging
15062
+ */
15063
+ getAllProperties() {
15064
+ return this.propertyManager.getAllProperties();
15065
+ }
14280
15066
  }
14281
15067
  // Only expose to window object in browser environments
14282
15068
  if (isBrowser) {