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