humanbehavior-js 0.0.9 → 0.1.1

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.
package/dist/esm/index.js CHANGED
@@ -4347,6 +4347,7 @@ class HumanBehaviorAPI {
4347
4347
  if (!response.ok) {
4348
4348
  throw new Error(`Failed to authenticate user: ${response.statusText} with API key: ${this.apiKey}`);
4349
4349
  }
4350
+ // Returns: { success: true, message: '...', userId: '...' }
4350
4351
  return yield response.json();
4351
4352
  }
4352
4353
  catch (error) {
@@ -4355,128 +4356,13 @@ class HumanBehaviorAPI {
4355
4356
  }
4356
4357
  });
4357
4358
  }
4358
- sendSessionComplete(sessionId) {
4359
- return __awaiter$1(this, void 0, void 0, function* () {
4360
- const response = yield fetch(`${this.baseUrl}/api/ingestion/sessionComplete`, {
4361
- method: 'POST',
4362
- headers: {
4363
- 'Content-Type': 'application/json',
4364
- 'Authorization': `Bearer ${this.apiKey}`
4365
- },
4366
- body: JSON.stringify({ sessionId })
4367
- });
4368
- if (!response.ok) {
4369
- throw new Error(`Failed to send session complete: ${response.statusText}`);
4370
- }
4371
- });
4372
- }
4373
- sendCustomEvent(eventName, eventProperties, sessionId) {
4374
- return __awaiter$1(this, void 0, void 0, function* () {
4375
- const maxRetries = 3;
4376
- let retryCount = 0;
4377
- while (retryCount < maxRetries) {
4378
- try {
4379
- const response = yield fetch(`${this.baseUrl}/api/ingestion/customEvent`, {
4380
- method: 'POST',
4381
- headers: {
4382
- 'Content-Type': 'application/json',
4383
- 'Authorization': `Bearer ${this.apiKey}`
4384
- },
4385
- body: JSON.stringify({
4386
- name: eventName,
4387
- properties: eventProperties,
4388
- sessionId: sessionId,
4389
- timestamp: new Date().toISOString()
4390
- })
4391
- });
4392
- if (!response.ok) {
4393
- throw new Error(`Failed to send custom event: ${response.statusText}`);
4394
- }
4395
- return yield response.json();
4396
- }
4397
- catch (error) {
4398
- retryCount++;
4399
- if (retryCount === maxRetries) {
4400
- logError('Error sending custom event after max retries:', error);
4401
- throw error;
4402
- }
4403
- // Exponential backoff
4404
- yield new Promise(resolve => setTimeout(resolve, Math.pow(2, retryCount) * 1000));
4405
- }
4406
- }
4407
- });
4408
- }
4409
- sendCustomEvents(events, sessionId) {
4410
- return __awaiter$1(this, void 0, void 0, function* () {
4411
- const maxRetries = 3;
4412
- let retryCount = 0;
4413
- while (retryCount < maxRetries) {
4414
- try {
4415
- const response = yield fetch(`${this.baseUrl}/api/ingestion/customEvent/batch`, {
4416
- method: 'POST',
4417
- headers: {
4418
- 'Content-Type': 'application/json',
4419
- 'Authorization': `Bearer ${this.apiKey}`
4420
- },
4421
- body: JSON.stringify({
4422
- events: events.map(event => (Object.assign(Object.assign({}, event), { sessionId: sessionId })))
4423
- })
4424
- });
4425
- if (!response.ok) {
4426
- throw new Error(`Failed to send custom events: ${response.statusText}`);
4427
- }
4428
- return yield response.json();
4429
- }
4430
- catch (error) {
4431
- retryCount++;
4432
- if (retryCount === maxRetries) {
4433
- logError('Error sending custom events after max retries:', error);
4434
- throw error;
4435
- }
4436
- // Exponential backoff
4437
- yield new Promise(resolve => setTimeout(resolve, Math.pow(2, retryCount) * 1000));
4438
- }
4439
- }
4440
- });
4441
- }
4442
- sendBeaconEvents(events, sessionId, isSessionComplete = false) {
4359
+ sendBeaconEvents(events, sessionId) {
4443
4360
  const data = new URLSearchParams();
4444
4361
  data.append('events', encodeURIComponent(JSON.stringify(events)));
4445
4362
  data.append('sessionId', encodeURIComponent(sessionId));
4446
4363
  data.append('timestamp', encodeURIComponent(Date.now().toString()));
4447
4364
  data.append('apiKey', encodeURIComponent(this.apiKey));
4448
- if (isSessionComplete) {
4449
- logInfo('Session complete beacon sending');
4450
- localStorage.setItem('koalaware_session_complete', Date.now().toString());
4451
- data.append('sessionComplete', encodeURIComponent('true'));
4452
- }
4453
4365
  navigator.sendBeacon(`${this.baseUrl}/api/ingestion/events`, data);
4454
- // KoalawareTracker.logToStorage(`Sending events beacon: ${this.baseUrl}/api/ingestion/events`);
4455
- // KoalawareTracker.logToStorage(`Events beacon success: ${success}`);
4456
- }
4457
- sendBeaconSessionComplete(sessionId) {
4458
- const data = new URLSearchParams();
4459
- data.append('sessionId', sessionId);
4460
- data.append('apiKey', this.apiKey);
4461
- data.append('sessionComplete', 'true');
4462
- navigator.sendBeacon(`${this.baseUrl}/api/ingestion/sessionComplete`, data);
4463
- // KoalawareTracker.logToStorage(`Sending completion beacon: ${this.baseUrl}/api/ingestion/sessionComplete`);
4464
- // KoalawareTracker.logToStorage(`Complete beacon success: ${success}`);
4465
- }
4466
- sendBeaconCustomEvent(eventName, eventProperties, sessionId) {
4467
- const data = new URLSearchParams();
4468
- data.append('name', encodeURIComponent(eventName));
4469
- data.append('properties', encodeURIComponent(JSON.stringify(eventProperties)));
4470
- data.append('sessionId', encodeURIComponent(sessionId));
4471
- data.append('timestamp', encodeURIComponent(new Date().toISOString()));
4472
- data.append('apiKey', encodeURIComponent(this.apiKey));
4473
- return navigator.sendBeacon(`${this.baseUrl}/api/ingestion/customEvent`, data);
4474
- }
4475
- sendBeaconCustomEvents(events, sessionId) {
4476
- const data = new URLSearchParams();
4477
- data.append('events', encodeURIComponent(JSON.stringify(events.map(event => (Object.assign(Object.assign({}, event), { sessionId: sessionId }))))));
4478
- data.append('apiKey', encodeURIComponent(this.apiKey));
4479
- return navigator.sendBeacon(`${this.baseUrl}/api/ingestion/customEvent/batch`, data);
4480
4366
  }
4481
4367
  }
4482
4368
 
@@ -4896,6 +4782,40 @@ const redactionManager = new RedactionManager();
4896
4782
  // Check if we're in a browser environment
4897
4783
  const isBrowser = typeof window !== 'undefined';
4898
4784
  class HumanBehaviorTracker {
4785
+ /**
4786
+ * Initialize the HumanBehavior tracker
4787
+ * This is the main entry point - call this once per page
4788
+ */
4789
+ static init(apiKey, options) {
4790
+ // Return existing instance if already initialized
4791
+ if (isBrowser && window.__humanBehaviorGlobalTracker) {
4792
+ logDebug('Tracker already initialized, returning existing instance');
4793
+ return window.__humanBehaviorGlobalTracker;
4794
+ }
4795
+ // Configure logging if specified
4796
+ if (options === null || options === void 0 ? void 0 : options.logLevel) {
4797
+ this.configureLogging({ level: options.logLevel });
4798
+ }
4799
+ // Create new tracker instance
4800
+ const tracker = new HumanBehaviorTracker(apiKey, options === null || options === void 0 ? void 0 : options.ingestionUrl);
4801
+ // Set redacted fields if specified
4802
+ if (options === null || options === void 0 ? void 0 : options.redactFields) {
4803
+ tracker.setRedactedFields(options.redactFields);
4804
+ }
4805
+ // Test connection (non-blocking)
4806
+ if (isBrowser) {
4807
+ const testUrl = tracker.api['baseUrl'] + '/api/health';
4808
+ fetch(testUrl, { method: 'HEAD' })
4809
+ .then(() => logDebug('Connection test successful'))
4810
+ .catch((error) => {
4811
+ logWarn('Connection test failed - ad blocker may be active:', error.message);
4812
+ tracker._connectionBlocked = true;
4813
+ });
4814
+ }
4815
+ // Start tracking
4816
+ tracker.start();
4817
+ return tracker;
4818
+ }
4899
4819
  constructor(apiKey, ingestionUrl) {
4900
4820
  this.eventIngestionQueue = [];
4901
4821
  this.queueSizeBytes = 0;
@@ -4910,60 +4830,87 @@ class HumanBehaviorTracker {
4910
4830
  this.initializationPromise = null;
4911
4831
  // Console tracking properties
4912
4832
  this.originalConsole = null;
4913
- this.originalLogger = null;
4914
4833
  this.consoleTrackingEnabled = false;
4834
+ // Navigation tracking properties
4835
+ this.navigationTrackingEnabled = false;
4836
+ this.currentUrl = '';
4837
+ this.previousUrl = '';
4838
+ this.originalPushState = null;
4839
+ this.originalReplaceState = null;
4840
+ this.navigationListeners = [];
4841
+ this._connectionBlocked = false;
4915
4842
  if (!apiKey) {
4916
4843
  throw new Error('Human Behavior API Key is required');
4917
4844
  }
4918
- // ========================================
4919
- // DEVELOPER: Choose your ingestion server
4920
- // ========================================
4921
- // Uncomment ONE of the following lines to select your server:
4922
- // AWS Development Server
4923
- const defaultIngestionUrl = 'http://3.137.217.33:3000';
4924
- // Vercel Production Server
4925
- // const defaultIngestionUrl = 'https://ingestion-server.vercel.app';
4926
- // Local Development Server
4927
- // const defaultIngestionUrl = 'http://localhost:3000';
4845
+ // Initialize API
4846
+ const defaultIngestionUrl = 'http://3.137.217.33:3000'; // AWS Development Server
4928
4847
  this.api = new HumanBehaviorAPI({
4929
4848
  apiKey: apiKey,
4930
4849
  ingestionUrl: ingestionUrl || defaultIngestionUrl
4931
4850
  });
4932
4851
  this.apiKey = apiKey;
4933
4852
  this.redactionManager = new RedactionManager();
4934
- // Check for existing session ID and last activity time in localStorage
4935
- const existingSessionId = isBrowser ? localStorage.getItem('human_behavior_session_id') : null;
4936
- const lastActivity = isBrowser ? localStorage.getItem('human_behavior_last_activity') : null;
4937
- // If we have a last activity time, check if it's within 30 minutes
4938
- const thirtyMinutesAgo = Date.now() - (30 * 60 * 1000);
4939
- const shouldUseExistingSession = lastActivity && parseInt(lastActivity) > thirtyMinutesAgo;
4940
- this.sessionId = (existingSessionId && shouldUseExistingSession) ? existingSessionId : v1();
4941
- // Store the session ID if it's new
4942
- if ((!existingSessionId || !shouldUseExistingSession) && isBrowser) {
4943
- localStorage.setItem('human_behavior_session_id', this.sessionId);
4853
+ // Handle session restoration with improved continuity
4854
+ if (isBrowser) {
4855
+ const existingSessionId = localStorage.getItem('human_behavior_session_id');
4856
+ const lastActivity = localStorage.getItem('human_behavior_last_activity');
4857
+ const thirtyMinutesAgo = Date.now() - (30 * 60 * 1000);
4858
+ // Check if we have an existing session that's still within the activity window
4859
+ if (existingSessionId && lastActivity && parseInt(lastActivity) > thirtyMinutesAgo) {
4860
+ this.sessionId = existingSessionId;
4861
+ logDebug(`Reusing existing session: ${this.sessionId}`);
4862
+ // Update activity timestamp to extend the session window
4863
+ localStorage.setItem('human_behavior_last_activity', Date.now().toString());
4864
+ }
4865
+ else {
4866
+ // Clear old session data if it's expired
4867
+ if (existingSessionId) {
4868
+ logDebug(`Session expired, clearing old session: ${existingSessionId}`);
4869
+ localStorage.removeItem('human_behavior_session_id');
4870
+ localStorage.removeItem('human_behavior_last_activity');
4871
+ }
4872
+ this.sessionId = v1();
4873
+ logDebug(`Creating new session: ${this.sessionId}`);
4874
+ localStorage.setItem('human_behavior_session_id', this.sessionId);
4875
+ localStorage.setItem('human_behavior_last_activity', Date.now().toString());
4876
+ }
4877
+ this.currentUrl = window.location.href;
4878
+ window.__humanBehaviorGlobalTracker = this;
4879
+ }
4880
+ else {
4881
+ this.sessionId = v1();
4944
4882
  }
4945
- // Start initialization immediately
4883
+ // Start initialization
4946
4884
  this.initializationPromise = this.init();
4947
4885
  }
4948
4886
  init() {
4949
4887
  return __awaiter$1(this, void 0, void 0, function* () {
4950
4888
  try {
4951
4889
  const userId = this.getCookie(`human_behavior_end_user_id_${this.apiKey}`);
4890
+ logDebug(`Initializing with sessionId: ${this.sessionId}, userId: ${userId}`);
4952
4891
  const { sessionId, endUserId } = yield this.api.init(this.sessionId, userId);
4953
- this.sessionId = sessionId;
4892
+ // Check if server returned a different session ID (for session continuity)
4893
+ if (sessionId !== this.sessionId) {
4894
+ logDebug(`Server returned different sessionId: ${sessionId} (client had: ${this.sessionId})`);
4895
+ this.sessionId = sessionId;
4896
+ // Update localStorage with server's session ID for continuity
4897
+ if (isBrowser) {
4898
+ localStorage.setItem('human_behavior_session_id', this.sessionId);
4899
+ }
4900
+ }
4954
4901
  this.endUserId = endUserId;
4955
4902
  this.setCookie(`human_behavior_end_user_id_${this.apiKey}`, endUserId, 365);
4956
4903
  // Only setup browser-specific handlers when in browser environment
4957
4904
  if (isBrowser) {
4958
4905
  this.setupPageUnloadHandler();
4959
- this.start();
4906
+ this.setupNavigationTracking();
4960
4907
  this.processRejectedEvents();
4961
4908
  }
4962
4909
  else {
4963
4910
  logWarn('HumanBehaviorTracker initialized in a non-browser environment. Session tracking is disabled.');
4964
4911
  }
4965
4912
  this.initialized = true;
4966
- logInfo('HumanBehaviorTracker initialized');
4913
+ logInfo(`HumanBehaviorTracker initialized with sessionId: ${this.sessionId}, endUserId: ${endUserId}`);
4967
4914
  }
4968
4915
  catch (error) {
4969
4916
  logError('Failed to initialize HumanBehaviorTracker:', error);
@@ -4979,6 +4926,171 @@ class HumanBehaviorTracker {
4979
4926
  yield this.initializationPromise;
4980
4927
  });
4981
4928
  }
4929
+ /**
4930
+ * Setup navigation event tracking for SPA navigation
4931
+ */
4932
+ setupNavigationTracking() {
4933
+ if (!isBrowser || this.navigationTrackingEnabled)
4934
+ return;
4935
+ this.navigationTrackingEnabled = true;
4936
+ logDebug('Setting up navigation tracking');
4937
+ // Store original history methods
4938
+ this.originalPushState = history.pushState;
4939
+ this.originalReplaceState = history.replaceState;
4940
+ // Override pushState to capture programmatic navigation
4941
+ history.pushState = (...args) => {
4942
+ this.previousUrl = this.currentUrl;
4943
+ this.currentUrl = window.location.href;
4944
+ // Call original method
4945
+ this.originalPushState.apply(history, args);
4946
+ // Track navigation event
4947
+ this.trackNavigationEvent('pushState', this.previousUrl, this.currentUrl);
4948
+ };
4949
+ // Override replaceState to capture programmatic navigation
4950
+ history.replaceState = (...args) => {
4951
+ this.previousUrl = this.currentUrl;
4952
+ this.currentUrl = window.location.href;
4953
+ // Call original method
4954
+ this.originalReplaceState.apply(history, args);
4955
+ // Track navigation event
4956
+ this.trackNavigationEvent('replaceState', this.previousUrl, this.currentUrl);
4957
+ };
4958
+ // Listen for popstate events (back/forward navigation)
4959
+ const popstateListener = () => {
4960
+ this.previousUrl = this.currentUrl;
4961
+ this.currentUrl = window.location.href;
4962
+ this.trackNavigationEvent('popstate', this.previousUrl, this.currentUrl);
4963
+ };
4964
+ window.addEventListener('popstate', popstateListener);
4965
+ this.navigationListeners.push(() => {
4966
+ window.removeEventListener('popstate', popstateListener);
4967
+ });
4968
+ // Listen for hashchange events
4969
+ const hashchangeListener = () => {
4970
+ this.previousUrl = this.currentUrl;
4971
+ this.currentUrl = window.location.href;
4972
+ this.trackNavigationEvent('hashchange', this.previousUrl, this.currentUrl);
4973
+ };
4974
+ window.addEventListener('hashchange', hashchangeListener);
4975
+ this.navigationListeners.push(() => {
4976
+ window.removeEventListener('hashchange', hashchangeListener);
4977
+ });
4978
+ // Track initial page load
4979
+ this.trackNavigationEvent('pageLoad', '', this.currentUrl);
4980
+ }
4981
+ /**
4982
+ * Track navigation events and send custom events
4983
+ */
4984
+ trackNavigationEvent(type, fromUrl, toUrl) {
4985
+ return __awaiter$1(this, void 0, void 0, function* () {
4986
+ if (!this.initialized)
4987
+ return;
4988
+ try {
4989
+ const navigationData = {
4990
+ type: type,
4991
+ from: fromUrl,
4992
+ to: toUrl,
4993
+ timestamp: new Date().toISOString(),
4994
+ pathname: window.location.pathname,
4995
+ search: window.location.search,
4996
+ hash: window.location.hash,
4997
+ referrer: document.referrer
4998
+ };
4999
+ // Add navigation event to the main event stream
5000
+ yield this.addEvent({
5001
+ type: 5, // Custom event type
5002
+ data: {
5003
+ payload: Object.assign({ eventType: 'navigation' }, navigationData)
5004
+ },
5005
+ timestamp: Date.now()
5006
+ });
5007
+ logDebug(`Navigation tracked: ${type} from ${fromUrl} to ${toUrl}`);
5008
+ }
5009
+ catch (error) {
5010
+ logError('Failed to track navigation event:', error);
5011
+ }
5012
+ });
5013
+ }
5014
+ /**
5015
+ * Track a page view event (PostHog-style)
5016
+ */
5017
+ trackPageView(url) {
5018
+ return __awaiter$1(this, void 0, void 0, function* () {
5019
+ if (!this.initialized)
5020
+ return;
5021
+ try {
5022
+ const pageViewData = {
5023
+ url: url || window.location.href,
5024
+ pathname: window.location.pathname,
5025
+ search: window.location.search,
5026
+ hash: window.location.hash,
5027
+ referrer: document.referrer,
5028
+ timestamp: new Date().toISOString()
5029
+ };
5030
+ // Add pageview event to the main event stream
5031
+ yield this.addEvent({
5032
+ type: 5, // Custom event type
5033
+ data: {
5034
+ payload: Object.assign({ eventType: 'pageview' }, pageViewData)
5035
+ },
5036
+ timestamp: Date.now()
5037
+ });
5038
+ logDebug(`Pageview tracked: ${pageViewData.url}`);
5039
+ }
5040
+ catch (error) {
5041
+ logError('Failed to track pageview event:', error);
5042
+ }
5043
+ });
5044
+ }
5045
+ /**
5046
+ * Track a custom event (PostHog-style)
5047
+ */
5048
+ customEvent(eventName, properties) {
5049
+ return __awaiter$1(this, void 0, void 0, function* () {
5050
+ if (!this.initialized)
5051
+ return;
5052
+ try {
5053
+ const customEventData = {
5054
+ eventName: eventName,
5055
+ properties: properties || {},
5056
+ timestamp: new Date().toISOString(),
5057
+ url: window.location.href,
5058
+ pathname: window.location.pathname
5059
+ };
5060
+ // Add custom event to the main event stream
5061
+ yield this.addEvent({
5062
+ type: 5, // Custom event type
5063
+ data: {
5064
+ payload: Object.assign({ eventType: 'custom' }, customEventData)
5065
+ },
5066
+ timestamp: Date.now()
5067
+ });
5068
+ logDebug(`Custom event tracked: ${eventName}`, properties);
5069
+ }
5070
+ catch (error) {
5071
+ logError('Failed to track custom event:', error);
5072
+ }
5073
+ });
5074
+ }
5075
+ /**
5076
+ * Cleanup navigation tracking
5077
+ */
5078
+ cleanupNavigationTracking() {
5079
+ if (!this.navigationTrackingEnabled)
5080
+ return;
5081
+ // Restore original history methods
5082
+ if (this.originalPushState) {
5083
+ history.pushState = this.originalPushState;
5084
+ }
5085
+ if (this.originalReplaceState) {
5086
+ history.replaceState = this.originalReplaceState;
5087
+ }
5088
+ // Remove event listeners
5089
+ this.navigationListeners.forEach(cleanup => cleanup());
5090
+ this.navigationListeners = [];
5091
+ this.navigationTrackingEnabled = false;
5092
+ logDebug('Navigation tracking cleaned up');
5093
+ }
4982
5094
  static logToStorage(message) {
4983
5095
  logInfo(message);
4984
5096
  }
@@ -5012,13 +5124,6 @@ class HumanBehaviorTracker {
5012
5124
  warn: console.warn,
5013
5125
  error: console.error
5014
5126
  };
5015
- // Store original logger methods
5016
- this.originalLogger = {
5017
- error: logError,
5018
- warn: logWarn,
5019
- info: logInfo,
5020
- debug: logDebug
5021
- };
5022
5127
  // Override console methods to capture ALL console output (including logger output)
5023
5128
  console.log = (...args) => {
5024
5129
  this.trackConsoleEvent('log', args);
@@ -5033,44 +5138,47 @@ class HumanBehaviorTracker {
5033
5138
  this.originalConsole.error(...args);
5034
5139
  };
5035
5140
  this.consoleTrackingEnabled = true;
5036
- this.originalLogger.debug('Console tracking enabled');
5141
+ logDebug('Console tracking enabled');
5037
5142
  }
5038
5143
  /**
5039
5144
  * Disable console event tracking
5040
5145
  */
5041
5146
  disableConsoleTracking() {
5042
- if (!isBrowser || !this.consoleTrackingEnabled || !this.originalConsole)
5147
+ if (!isBrowser || !this.consoleTrackingEnabled)
5043
5148
  return;
5044
5149
  // Restore original console methods
5045
- console.log = this.originalConsole.log;
5046
- console.warn = this.originalConsole.warn;
5047
- console.error = this.originalConsole.error;
5150
+ if (this.originalConsole) {
5151
+ console.log = this.originalConsole.log;
5152
+ console.warn = this.originalConsole.warn;
5153
+ console.error = this.originalConsole.error;
5154
+ }
5048
5155
  this.consoleTrackingEnabled = false;
5049
- this.originalConsole = null;
5050
- this.originalLogger = null;
5156
+ logDebug('Console tracking disabled');
5051
5157
  }
5052
- /**
5053
- * Track console events
5054
- */
5055
5158
  trackConsoleEvent(level, args) {
5056
5159
  if (!this.initialized)
5057
5160
  return;
5058
- const consoleEvent = {
5059
- type: 5, // Custom event type
5060
- data: {
5061
- payload: {
5062
- type: 'console',
5063
- level: level,
5064
- message: args.map(arg => typeof arg === 'string' ? arg :
5065
- typeof arg === 'object' ? JSON.stringify(arg) :
5066
- String(arg)).join(' '),
5067
- timestamp: Date.now(),
5068
- url: window.location.href
5069
- }
5070
- },
5071
- timestamp: Date.now()
5072
- };
5073
- this.addEvent(consoleEvent);
5161
+ try {
5162
+ const consoleData = {
5163
+ level: level,
5164
+ message: args.map(arg => typeof arg === 'object' ? JSON.stringify(arg) : String(arg)).join(' '),
5165
+ timestamp: new Date().toISOString(),
5166
+ url: window.location.href
5167
+ };
5168
+ // Add console event to the main event stream
5169
+ this.addEvent({
5170
+ type: 5, // Custom event type
5171
+ data: {
5172
+ payload: Object.assign({ eventType: 'console' }, consoleData)
5173
+ },
5174
+ timestamp: Date.now()
5175
+ }).catch(error => {
5176
+ logError('Failed to track console event:', error);
5177
+ });
5178
+ }
5179
+ catch (error) {
5180
+ logError('Error in trackConsoleEvent:', error);
5181
+ }
5074
5182
  }
5075
5183
  setupPageUnloadHandler() {
5076
5184
  if (!isBrowser)
@@ -5086,15 +5194,18 @@ class HumanBehaviorTracker {
5086
5194
  });
5087
5195
  // Handle actual page unload/close
5088
5196
  window.addEventListener('beforeunload', () => {
5089
- // Update last activity time
5090
- localStorage.setItem('human_behavior_last_activity', Date.now().toString());
5091
5197
  // Send final events
5092
5198
  this.api.sendBeaconEvents(this.eventIngestionQueue, this.sessionId);
5093
5199
  });
5094
- // Update activity timestamp periodically
5095
- setInterval(() => {
5200
+ // Update activity timestamp on user interaction (not on page load)
5201
+ const updateActivity = () => {
5096
5202
  localStorage.setItem('human_behavior_last_activity', Date.now().toString());
5097
- }, 60000); // Update every minute
5203
+ };
5204
+ // Listen for user interactions to update activity timestamp
5205
+ window.addEventListener('click', updateActivity);
5206
+ window.addEventListener('keydown', updateActivity);
5207
+ window.addEventListener('scroll', updateActivity);
5208
+ window.addEventListener('mousemove', updateActivity);
5098
5209
  }
5099
5210
  viewLogs() {
5100
5211
  try {
@@ -5129,13 +5240,12 @@ class HumanBehaviorTracker {
5129
5240
  if (!this.userProperties || Object.keys(this.userProperties).length === 0) {
5130
5241
  throw new Error('No user info available. Call addUserInfo() first.');
5131
5242
  }
5132
- yield this.api.sendUserAuth(this.endUserId, this.userProperties, this.sessionId, authFields);
5133
- });
5134
- }
5135
- customEvent(eventName_1) {
5136
- return __awaiter$1(this, arguments, void 0, function* (eventName, eventProperties = {}) {
5137
- yield this.ensureInitialized();
5138
- this.api.sendBeaconCustomEvent(eventName, eventProperties, this.sessionId);
5243
+ const response = yield this.api.sendUserAuth(this.endUserId, this.userProperties, this.sessionId, authFields);
5244
+ if (response && response.userId && response.userId !== this.endUserId) {
5245
+ // Update endUserId and cookie if backend returns a new userId
5246
+ this.endUserId = response.userId;
5247
+ this.setCookie(`human_behavior_end_user_id_${this.apiKey}`, response.userId, 365);
5248
+ }
5139
5249
  });
5140
5250
  }
5141
5251
  start() {
@@ -5174,6 +5284,8 @@ class HumanBehaviorTracker {
5174
5284
  }
5175
5285
  // Disable console tracking
5176
5286
  this.disableConsoleTracking();
5287
+ // Cleanup navigation tracking
5288
+ this.cleanupNavigationTracking();
5177
5289
  });
5178
5290
  }
5179
5291
  addEvent(event) {
@@ -5216,7 +5328,7 @@ class HumanBehaviorTracker {
5216
5328
  }
5217
5329
  flush() {
5218
5330
  return __awaiter$1(this, void 0, void 0, function* () {
5219
- var _a;
5331
+ var _a, _b, _c, _d;
5220
5332
  // Prevent concurrent flushes
5221
5333
  if (this.isProcessing || !this.initialized) {
5222
5334
  return;
@@ -5239,6 +5351,14 @@ class HumanBehaviorTracker {
5239
5351
  this.rejectedEvents.push(...eventsToProcess);
5240
5352
  this.processRejectedEvents();
5241
5353
  }
5354
+ else if (((_b = error.message) === null || _b === void 0 ? void 0 : _b.includes('ERR_BLOCKED_BY_CLIENT')) ||
5355
+ ((_c = error.message) === null || _c === void 0 ? void 0 : _c.includes('Failed to fetch')) ||
5356
+ ((_d = error.message) === null || _d === void 0 ? void 0 : _d.includes('NetworkError'))) {
5357
+ // Handle ad blocker or network issues gracefully
5358
+ logWarn('Request blocked by ad blocker or network issue, storing events for retry');
5359
+ this.rejectedEvents.push(...eventsToProcess);
5360
+ // Don't process rejected events immediately to avoid spam
5361
+ }
5242
5362
  else {
5243
5363
  throw error;
5244
5364
  }
@@ -5250,28 +5370,70 @@ class HumanBehaviorTracker {
5250
5370
  }
5251
5371
  });
5252
5372
  }
5253
- // Add helper methods for cookie management
5373
+ // Add helper methods for cookie management with localStorage fallback
5254
5374
  setCookie(name, value, daysToExpire) {
5255
5375
  if (!isBrowser)
5256
5376
  return;
5257
- const date = new Date();
5258
- date.setTime(date.getTime() + (daysToExpire * 24 * 60 * 60 * 1000));
5259
- const expires = `expires=${date.toUTCString()}`;
5260
- document.cookie = `${name}=${value};${expires};path=/;SameSite=Lax`;
5377
+ try {
5378
+ // Try to set cookie first
5379
+ const date = new Date();
5380
+ date.setTime(date.getTime() + (daysToExpire * 24 * 60 * 60 * 1000));
5381
+ const expires = `expires=${date.toUTCString()}`;
5382
+ document.cookie = `${name}=${value};${expires};path=/;SameSite=Lax`;
5383
+ // Also store in localStorage as backup
5384
+ localStorage.setItem(name, value);
5385
+ logDebug(`Set cookie and localStorage: ${name}`);
5386
+ }
5387
+ catch (error) {
5388
+ // If cookie fails, use localStorage only
5389
+ try {
5390
+ localStorage.setItem(name, value);
5391
+ logDebug(`Cookie blocked, using localStorage: ${name}`);
5392
+ }
5393
+ catch (localStorageError) {
5394
+ logError('Failed to store user ID in both cookie and localStorage:', localStorageError);
5395
+ }
5396
+ }
5261
5397
  }
5262
5398
  getCookie(name) {
5263
5399
  if (!isBrowser)
5264
5400
  return null;
5265
- const nameEQ = name + "=";
5266
- const ca = document.cookie.split(';');
5267
- for (let i = 0; i < ca.length; i++) {
5268
- let c = ca[i];
5269
- while (c.charAt(0) === ' ')
5270
- c = c.substring(1, c.length);
5271
- if (c.indexOf(nameEQ) === 0)
5272
- return c.substring(nameEQ.length, c.length);
5401
+ try {
5402
+ // Try to get from cookie first
5403
+ const nameEQ = name + "=";
5404
+ const ca = document.cookie.split(';');
5405
+ for (let i = 0; i < ca.length; i++) {
5406
+ let c = ca[i];
5407
+ while (c.charAt(0) === ' ')
5408
+ c = c.substring(1, c.length);
5409
+ if (c.indexOf(nameEQ) === 0) {
5410
+ const cookieValue = c.substring(nameEQ.length, c.length);
5411
+ logDebug(`Found cookie: ${name}`);
5412
+ return cookieValue;
5413
+ }
5414
+ }
5415
+ // If cookie not found, try localStorage
5416
+ const localStorageValue = localStorage.getItem(name);
5417
+ if (localStorageValue) {
5418
+ logDebug(`Cookie not found, using localStorage: ${name}`);
5419
+ return localStorageValue;
5420
+ }
5421
+ return null;
5422
+ }
5423
+ catch (error) {
5424
+ // If cookie access fails, try localStorage
5425
+ try {
5426
+ const localStorageValue = localStorage.getItem(name);
5427
+ if (localStorageValue) {
5428
+ logDebug(`Cookie access failed, using localStorage: ${name}`);
5429
+ return localStorageValue;
5430
+ }
5431
+ }
5432
+ catch (localStorageError) {
5433
+ logError('Failed to access both cookie and localStorage:', localStorageError);
5434
+ }
5435
+ return null;
5273
5436
  }
5274
- return null;
5275
5437
  }
5276
5438
  /**
5277
5439
  * Start redaction functionality for sensitive input fields
@@ -5311,6 +5473,84 @@ class HumanBehaviorTracker {
5311
5473
  getRedactedFields() {
5312
5474
  return this.redactionManager.getSelectedFields();
5313
5475
  }
5476
+ /**
5477
+ * Get the current session ID
5478
+ */
5479
+ getSessionId() {
5480
+ return this.sessionId;
5481
+ }
5482
+ /**
5483
+ * Get the current URL being tracked
5484
+ */
5485
+ getCurrentUrl() {
5486
+ return this.currentUrl;
5487
+ }
5488
+ /**
5489
+ * Test if the tracker can reach the ingestion server
5490
+ */
5491
+ testConnection() {
5492
+ return __awaiter$1(this, void 0, void 0, function* () {
5493
+ try {
5494
+ yield this.api.init(this.sessionId, this.endUserId);
5495
+ return { success: true };
5496
+ }
5497
+ catch (error) {
5498
+ return {
5499
+ success: false,
5500
+ error: error.message || 'Unknown error'
5501
+ };
5502
+ }
5503
+ });
5504
+ }
5505
+ /**
5506
+ * Get connection status and recommendations
5507
+ */
5508
+ getConnectionStatus() {
5509
+ const recommendations = [];
5510
+ let blocked = false;
5511
+ // Check if we have rejected events (might indicate blocking)
5512
+ if (this.rejectedEvents.length > 0) {
5513
+ blocked = true;
5514
+ recommendations.push('Some requests may be blocked by ad blockers');
5515
+ }
5516
+ // Check if connection was blocked during initialization
5517
+ if (this._connectionBlocked) {
5518
+ blocked = true;
5519
+ recommendations.push('Initial connection test failed - ad blocker may be active');
5520
+ }
5521
+ // Check if we're in a browser environment
5522
+ if (typeof window === 'undefined') {
5523
+ recommendations.push('Not running in browser environment');
5524
+ }
5525
+ // Check if navigator.sendBeacon is available
5526
+ if (typeof navigator.sendBeacon === 'undefined') {
5527
+ recommendations.push('sendBeacon not available, using fetch fallback');
5528
+ }
5529
+ return { blocked, recommendations };
5530
+ }
5531
+ /**
5532
+ * Check if the current user is a preexisting user
5533
+ * Returns true if the user has an existing endUserId cookie from a previous session
5534
+ */
5535
+ isPreexistingUser() {
5536
+ if (!isBrowser) {
5537
+ return false;
5538
+ }
5539
+ // Check if there's an existing endUserId cookie for this API key
5540
+ const existingEndUserId = this.getCookie(`human_behavior_end_user_id_${this.apiKey}`);
5541
+ return existingEndUserId !== null && existingEndUserId !== this.endUserId;
5542
+ }
5543
+ /**
5544
+ * Get user information including whether they are preexisting
5545
+ */
5546
+ getUserInfo() {
5547
+ return {
5548
+ endUserId: this.endUserId,
5549
+ sessionId: this.sessionId,
5550
+ isPreexistingUser: this.isPreexistingUser(),
5551
+ initialized: this.initialized
5552
+ };
5553
+ }
5314
5554
  }
5315
5555
  // Only expose to window object in browser environments
5316
5556
  if (isBrowser) {