humanbehavior-js 0.0.8 → 0.1.0

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
@@ -4053,6 +4053,123 @@ function v1Bytes(rnds, msecs, nsecs, clockseq, node, buf, offset = 0) {
4053
4053
  return buf;
4054
4054
  }
4055
4055
 
4056
+ exports.LogLevel = void 0;
4057
+ (function (LogLevel) {
4058
+ LogLevel[LogLevel["NONE"] = 0] = "NONE";
4059
+ LogLevel[LogLevel["ERROR"] = 1] = "ERROR";
4060
+ LogLevel[LogLevel["WARN"] = 2] = "WARN";
4061
+ LogLevel[LogLevel["INFO"] = 3] = "INFO";
4062
+ LogLevel[LogLevel["DEBUG"] = 4] = "DEBUG";
4063
+ })(exports.LogLevel || (exports.LogLevel = {}));
4064
+ class Logger {
4065
+ constructor(config) {
4066
+ this.config = {
4067
+ level: exports.LogLevel.ERROR, // Default to only errors in production
4068
+ enableConsole: true,
4069
+ enableStorage: false
4070
+ };
4071
+ this.isBrowser = typeof window !== 'undefined';
4072
+ if (config) {
4073
+ this.config = Object.assign(Object.assign({}, this.config), config);
4074
+ }
4075
+ }
4076
+ setConfig(config) {
4077
+ this.config = Object.assign(Object.assign({}, this.config), config);
4078
+ }
4079
+ shouldLog(level) {
4080
+ return level <= this.config.level;
4081
+ }
4082
+ formatMessage(level, message, ...args) {
4083
+ const timestamp = new Date().toISOString();
4084
+ return `[HumanBehavior ${level}] ${timestamp}: ${message}`;
4085
+ }
4086
+ error(message, ...args) {
4087
+ if (!this.shouldLog(exports.LogLevel.ERROR))
4088
+ return;
4089
+ const formattedMessage = this.formatMessage('ERROR', message);
4090
+ if (this.config.enableConsole) {
4091
+ console.error(formattedMessage, ...args);
4092
+ }
4093
+ if (this.config.enableStorage && this.isBrowser) {
4094
+ this.logToStorage(formattedMessage, args);
4095
+ }
4096
+ }
4097
+ warn(message, ...args) {
4098
+ if (!this.shouldLog(exports.LogLevel.WARN))
4099
+ return;
4100
+ const formattedMessage = this.formatMessage('WARN', message);
4101
+ if (this.config.enableConsole) {
4102
+ console.warn(formattedMessage, ...args);
4103
+ }
4104
+ if (this.config.enableStorage && this.isBrowser) {
4105
+ this.logToStorage(formattedMessage, args);
4106
+ }
4107
+ }
4108
+ info(message, ...args) {
4109
+ if (!this.shouldLog(exports.LogLevel.INFO))
4110
+ return;
4111
+ const formattedMessage = this.formatMessage('INFO', message);
4112
+ if (this.config.enableConsole) {
4113
+ console.log(formattedMessage, ...args);
4114
+ }
4115
+ if (this.config.enableStorage && this.isBrowser) {
4116
+ this.logToStorage(formattedMessage, args);
4117
+ }
4118
+ }
4119
+ debug(message, ...args) {
4120
+ if (!this.shouldLog(exports.LogLevel.DEBUG))
4121
+ return;
4122
+ const formattedMessage = this.formatMessage('DEBUG', message);
4123
+ if (this.config.enableConsole) {
4124
+ console.log(formattedMessage, ...args);
4125
+ }
4126
+ if (this.config.enableStorage && this.isBrowser) {
4127
+ this.logToStorage(formattedMessage, args);
4128
+ }
4129
+ }
4130
+ logToStorage(message, args) {
4131
+ try {
4132
+ const logs = JSON.parse(localStorage.getItem('human_behavior_logs') || '[]');
4133
+ const logEntry = {
4134
+ message,
4135
+ args: args.length > 0 ? args : undefined,
4136
+ timestamp: Date.now()
4137
+ };
4138
+ logs.push(logEntry);
4139
+ // Keep only last 1000 logs to prevent storage bloat
4140
+ if (logs.length > 1000) {
4141
+ logs.splice(0, logs.length - 1000);
4142
+ }
4143
+ localStorage.setItem('human_behavior_logs', JSON.stringify(logs));
4144
+ }
4145
+ catch (e) {
4146
+ // Silently fail if storage is not available
4147
+ }
4148
+ }
4149
+ getLogs() {
4150
+ if (!this.isBrowser)
4151
+ return [];
4152
+ try {
4153
+ return JSON.parse(localStorage.getItem('human_behavior_logs') || '[]');
4154
+ }
4155
+ catch (e) {
4156
+ return [];
4157
+ }
4158
+ }
4159
+ clearLogs() {
4160
+ if (this.isBrowser) {
4161
+ localStorage.removeItem('human_behavior_logs');
4162
+ }
4163
+ }
4164
+ }
4165
+ // Create singleton instance
4166
+ const logger = new Logger();
4167
+ // Export convenience methods
4168
+ const logError = (message, ...args) => logger.error(message, ...args);
4169
+ const logWarn = (message, ...args) => logger.warn(message, ...args);
4170
+ const logInfo = (message, ...args) => logger.info(message, ...args);
4171
+ const logDebug = (message, ...args) => logger.debug(message, ...args);
4172
+
4056
4173
  const MAX_CHUNK_SIZE_BYTES = 1024 * 1024 * 10; // 10MB chunk size
4057
4174
  function isChunkSizeExceeded(currentChunk, newEvent, sessionId) {
4058
4175
  const nextChunkSize = new TextEncoder().encode(JSON.stringify({
@@ -4184,7 +4301,7 @@ class HumanBehaviorAPI {
4184
4301
  return results.flat();
4185
4302
  }
4186
4303
  catch (error) {
4187
- console.error('Error sending events:', error);
4304
+ logError('Error sending events:', error);
4188
4305
  throw error;
4189
4306
  }
4190
4307
  });
@@ -4210,7 +4327,7 @@ class HumanBehaviorAPI {
4210
4327
  return yield response.json();
4211
4328
  }
4212
4329
  catch (error) {
4213
- console.error('Error sending user data:', error);
4330
+ logError('Error sending user data:', error);
4214
4331
  throw error;
4215
4332
  }
4216
4333
  });
@@ -4237,134 +4354,21 @@ class HumanBehaviorAPI {
4237
4354
  return yield response.json();
4238
4355
  }
4239
4356
  catch (error) {
4240
- console.error('Error authenticating user:', error);
4357
+ logError('Error authenticating user:', error);
4241
4358
  throw error;
4242
4359
  }
4243
4360
  });
4244
4361
  }
4245
- sendSessionComplete(sessionId) {
4246
- return __awaiter$1(this, void 0, void 0, function* () {
4247
- const response = yield fetch(`${this.baseUrl}/api/ingestion/sessionComplete`, {
4248
- method: 'POST',
4249
- headers: {
4250
- 'Content-Type': 'application/json',
4251
- 'Authorization': `Bearer ${this.apiKey}`
4252
- },
4253
- body: JSON.stringify({ sessionId })
4254
- });
4255
- if (!response.ok) {
4256
- throw new Error(`Failed to send session complete: ${response.statusText}`);
4257
- }
4258
- });
4259
- }
4260
- sendCustomEvent(eventName, eventProperties, sessionId) {
4261
- return __awaiter$1(this, void 0, void 0, function* () {
4262
- const maxRetries = 3;
4263
- let retryCount = 0;
4264
- while (retryCount < maxRetries) {
4265
- try {
4266
- const response = yield fetch(`${this.baseUrl}/api/ingestion/customEvent`, {
4267
- method: 'POST',
4268
- headers: {
4269
- 'Content-Type': 'application/json',
4270
- 'Authorization': `Bearer ${this.apiKey}`
4271
- },
4272
- body: JSON.stringify({
4273
- name: eventName,
4274
- properties: eventProperties,
4275
- sessionId: sessionId,
4276
- timestamp: new Date().toISOString()
4277
- })
4278
- });
4279
- if (!response.ok) {
4280
- throw new Error(`Failed to send custom event: ${response.statusText}`);
4281
- }
4282
- return yield response.json();
4283
- }
4284
- catch (error) {
4285
- retryCount++;
4286
- if (retryCount === maxRetries) {
4287
- console.error('Error sending custom event after max retries:', error);
4288
- throw error;
4289
- }
4290
- // Exponential backoff
4291
- yield new Promise(resolve => setTimeout(resolve, Math.pow(2, retryCount) * 1000));
4292
- }
4293
- }
4294
- });
4295
- }
4296
- sendCustomEvents(events, sessionId) {
4297
- return __awaiter$1(this, void 0, void 0, function* () {
4298
- const maxRetries = 3;
4299
- let retryCount = 0;
4300
- while (retryCount < maxRetries) {
4301
- try {
4302
- const response = yield fetch(`${this.baseUrl}/api/ingestion/customEvent/batch`, {
4303
- method: 'POST',
4304
- headers: {
4305
- 'Content-Type': 'application/json',
4306
- 'Authorization': `Bearer ${this.apiKey}`
4307
- },
4308
- body: JSON.stringify({
4309
- events: events.map(event => (Object.assign(Object.assign({}, event), { sessionId: sessionId })))
4310
- })
4311
- });
4312
- if (!response.ok) {
4313
- throw new Error(`Failed to send custom events: ${response.statusText}`);
4314
- }
4315
- return yield response.json();
4316
- }
4317
- catch (error) {
4318
- retryCount++;
4319
- if (retryCount === maxRetries) {
4320
- console.error('Error sending custom events after max retries:', error);
4321
- throw error;
4322
- }
4323
- // Exponential backoff
4324
- yield new Promise(resolve => setTimeout(resolve, Math.pow(2, retryCount) * 1000));
4325
- }
4326
- }
4327
- });
4328
- }
4329
- sendBeaconEvents(events, sessionId, isSessionComplete = false) {
4362
+ sendBeaconEvents(events, sessionId) {
4330
4363
  const data = new URLSearchParams();
4331
4364
  data.append('events', encodeURIComponent(JSON.stringify(events)));
4332
4365
  data.append('sessionId', encodeURIComponent(sessionId));
4333
4366
  data.append('timestamp', encodeURIComponent(Date.now().toString()));
4334
4367
  data.append('apiKey', encodeURIComponent(this.apiKey));
4335
- if (isSessionComplete) {
4336
- console.log('Session complete beacon sending');
4337
- localStorage.setItem('koalaware_session_complete', Date.now().toString());
4338
- data.append('sessionComplete', encodeURIComponent('true'));
4339
- }
4340
4368
  navigator.sendBeacon(`${this.baseUrl}/api/ingestion/events`, data);
4341
4369
  // KoalawareTracker.logToStorage(`Sending events beacon: ${this.baseUrl}/api/ingestion/events`);
4342
4370
  // KoalawareTracker.logToStorage(`Events beacon success: ${success}`);
4343
4371
  }
4344
- sendBeaconSessionComplete(sessionId) {
4345
- const data = new URLSearchParams();
4346
- data.append('sessionId', sessionId);
4347
- data.append('apiKey', this.apiKey);
4348
- data.append('sessionComplete', 'true');
4349
- navigator.sendBeacon(`${this.baseUrl}/api/ingestion/sessionComplete`, data);
4350
- // KoalawareTracker.logToStorage(`Sending completion beacon: ${this.baseUrl}/api/ingestion/sessionComplete`);
4351
- // KoalawareTracker.logToStorage(`Complete beacon success: ${success}`);
4352
- }
4353
- sendBeaconCustomEvent(eventName, eventProperties, sessionId) {
4354
- const data = new URLSearchParams();
4355
- data.append('name', encodeURIComponent(eventName));
4356
- data.append('properties', encodeURIComponent(JSON.stringify(eventProperties)));
4357
- data.append('sessionId', encodeURIComponent(sessionId));
4358
- data.append('timestamp', encodeURIComponent(new Date().toISOString()));
4359
- data.append('apiKey', encodeURIComponent(this.apiKey));
4360
- return navigator.sendBeacon(`${this.baseUrl}/api/ingestion/customEvent`, data);
4361
- }
4362
- sendBeaconCustomEvents(events, sessionId) {
4363
- const data = new URLSearchParams();
4364
- data.append('events', encodeURIComponent(JSON.stringify(events.map(event => (Object.assign(Object.assign({}, event), { sessionId: sessionId }))))));
4365
- data.append('apiKey', encodeURIComponent(this.apiKey));
4366
- return navigator.sendBeacon(`${this.baseUrl}/api/ingestion/customEvent/batch`, data);
4367
- }
4368
4372
  }
4369
4373
 
4370
4374
  // Redaction functionality for sensitive input fields
@@ -4397,18 +4401,18 @@ class RedactionManager {
4397
4401
  this.userSelectedFields.clear();
4398
4402
  fields.forEach(field => this.userSelectedFields.add(field));
4399
4403
  if (fields.length > 0) {
4400
- console.log(`Redaction: Active for ${fields.length} field(s):`, fields);
4404
+ logDebug(`Redaction: Active for ${fields.length} field(s):`, fields);
4401
4405
  // Debug: Check if elements exist
4402
4406
  fields.forEach(selector => {
4403
4407
  const elements = document.querySelectorAll(selector);
4404
- console.log(`Redaction: Found ${elements.length} element(s) for selector '${selector}'`);
4408
+ logDebug(`Redaction: Found ${elements.length} element(s) for selector '${selector}'`);
4405
4409
  elements.forEach((el, index) => {
4406
- console.log(`Redaction: Element ${index} for '${selector}':`, el);
4410
+ logDebug(`Redaction: Element ${index} for '${selector}':`, el);
4407
4411
  });
4408
4412
  });
4409
4413
  }
4410
4414
  else {
4411
- console.log('Redaction: Disabled - no fields selected');
4415
+ logDebug('Redaction: Disabled - no fields selected');
4412
4416
  }
4413
4417
  }
4414
4418
  /**
@@ -4438,7 +4442,7 @@ class RedactionManager {
4438
4442
  if (processedEvent.data.source === 5) { // Input event
4439
4443
  const shouldRedact = this.isFieldSelected(processedEvent.data);
4440
4444
  if (shouldRedact) {
4441
- console.log('Redaction: Processing input event for redaction');
4445
+ logDebug('Redaction: Processing input event for redaction');
4442
4446
  this.redactInputEvent(processedEvent.data);
4443
4447
  }
4444
4448
  }
@@ -4464,30 +4468,30 @@ class RedactionManager {
4464
4468
  if (!this.isFieldSelected(inputData)) {
4465
4469
  return;
4466
4470
  }
4467
- console.log('Redaction: Redacting input event with text:', inputData.text);
4471
+ logDebug('Redaction: Redacting input event with text:', inputData.text);
4468
4472
  // Redact all text-related properties that could contain input data
4469
4473
  const textProperties = ['text', 'value', 'content', 'data', 'input', 'textContent'];
4470
4474
  textProperties.forEach(prop => {
4471
4475
  if (inputData[prop] !== undefined && typeof inputData[prop] === 'string') {
4472
4476
  inputData[prop] = this.redactedText;
4473
- console.log(`Redaction: Redacted property '${prop}'`);
4477
+ logDebug(`Redaction: Redacted property '${prop}'`);
4474
4478
  }
4475
4479
  });
4476
4480
  // Also check for any other string properties that might contain input data
4477
4481
  Object.keys(inputData).forEach(key => {
4478
4482
  if (typeof inputData[key] === 'string' && inputData[key].length > 0) {
4479
4483
  inputData[key] = this.redactedText;
4480
- console.log(`Redaction: Redacted additional property '${key}'`);
4484
+ logDebug(`Redaction: Redacted additional property '${key}'`);
4481
4485
  }
4482
4486
  });
4483
4487
  // Handle nested objects that might contain text data
4484
4488
  if (inputData.attributes && typeof inputData.attributes === 'object') {
4485
4489
  if (inputData.attributes.value && typeof inputData.attributes.value === 'string') {
4486
4490
  inputData.attributes.value = this.redactedText;
4487
- console.log('Redaction: Redacted nested value attribute');
4491
+ logDebug('Redaction: Redacted nested value attribute');
4488
4492
  }
4489
4493
  }
4490
- console.log('Redaction: Input event redaction complete');
4494
+ logDebug('Redaction: Input event redaction complete');
4491
4495
  }
4492
4496
  /**
4493
4497
  * Redact sensitive data in DOM mutation events
@@ -4549,7 +4553,7 @@ class RedactionManager {
4549
4553
  return false;
4550
4554
  }
4551
4555
  catch (e) {
4552
- console.warn('Error checking if DOM change should be redacted:', e);
4556
+ logWarn('Error checking if DOM change should be redacted:', e);
4553
4557
  return false;
4554
4558
  }
4555
4559
  }
@@ -4696,7 +4700,7 @@ class RedactionManager {
4696
4700
  // Check if any of these elements are currently focused
4697
4701
  for (const el of matchingElements) {
4698
4702
  if (el === document.activeElement) {
4699
- console.log('Redaction: Found focused element matching selector:', selector);
4703
+ logDebug('Redaction: Found focused element matching selector:', selector);
4700
4704
  return true;
4701
4705
  }
4702
4706
  }
@@ -4706,7 +4710,7 @@ class RedactionManager {
4706
4710
  // Look for any input element that might be the active one
4707
4711
  const activeElement = document.activeElement;
4708
4712
  if (activeElement && this.shouldRedactElement(activeElement)) {
4709
- console.log('Redaction: Active element should be redacted');
4713
+ logDebug('Redaction: Active element should be redacted');
4710
4714
  return true;
4711
4715
  }
4712
4716
  return false;
@@ -4739,7 +4743,7 @@ class RedactionManager {
4739
4743
  return false;
4740
4744
  }
4741
4745
  catch (e) {
4742
- console.warn('Error checking if field should be redacted:', e);
4746
+ logWarn('Error checking if field should be redacted:', e);
4743
4747
  return false;
4744
4748
  }
4745
4749
  }
@@ -4783,6 +4787,40 @@ const redactionManager = new RedactionManager();
4783
4787
  // Check if we're in a browser environment
4784
4788
  const isBrowser = typeof window !== 'undefined';
4785
4789
  class HumanBehaviorTracker {
4790
+ /**
4791
+ * Initialize the HumanBehavior tracker
4792
+ * This is the main entry point - call this once per page
4793
+ */
4794
+ static init(apiKey, options) {
4795
+ // Return existing instance if already initialized
4796
+ if (isBrowser && window.__humanBehaviorGlobalTracker) {
4797
+ logDebug('Tracker already initialized, returning existing instance');
4798
+ return window.__humanBehaviorGlobalTracker;
4799
+ }
4800
+ // Configure logging if specified
4801
+ if (options === null || options === void 0 ? void 0 : options.logLevel) {
4802
+ this.configureLogging({ level: options.logLevel });
4803
+ }
4804
+ // Create new tracker instance
4805
+ const tracker = new HumanBehaviorTracker(apiKey, options === null || options === void 0 ? void 0 : options.ingestionUrl);
4806
+ // Set redacted fields if specified
4807
+ if (options === null || options === void 0 ? void 0 : options.redactFields) {
4808
+ tracker.setRedactedFields(options.redactFields);
4809
+ }
4810
+ // Test connection (non-blocking)
4811
+ if (isBrowser) {
4812
+ const testUrl = tracker.api['baseUrl'] + '/api/ingestion/health';
4813
+ fetch(testUrl, { method: 'HEAD' })
4814
+ .then(() => logDebug('Connection test successful'))
4815
+ .catch((error) => {
4816
+ logWarn('Connection test failed - ad blocker may be active:', error.message);
4817
+ tracker._connectionBlocked = true;
4818
+ });
4819
+ }
4820
+ // Start tracking
4821
+ tracker.start();
4822
+ return tracker;
4823
+ }
4786
4824
  constructor(apiKey, ingestionUrl) {
4787
4825
  this.eventIngestionQueue = [];
4788
4826
  this.queueSizeBytes = 0;
@@ -4795,61 +4833,82 @@ class HumanBehaviorTracker {
4795
4833
  this.endUserId = null;
4796
4834
  this.initialized = false;
4797
4835
  this.initializationPromise = null;
4836
+ // Console tracking properties
4837
+ this.originalConsole = null;
4838
+ this.consoleTrackingEnabled = false;
4839
+ // Navigation tracking properties
4840
+ this.navigationTrackingEnabled = false;
4841
+ this.currentUrl = '';
4842
+ this.previousUrl = '';
4843
+ this.originalPushState = null;
4844
+ this.originalReplaceState = null;
4845
+ this.navigationListeners = [];
4846
+ this._connectionBlocked = false;
4798
4847
  if (!apiKey) {
4799
4848
  throw new Error('Human Behavior API Key is required');
4800
4849
  }
4801
- // ========================================
4802
- // DEVELOPER: Choose your ingestion server
4803
- // ========================================
4804
- // Uncomment ONE of the following lines to select your server:
4805
- // AWS Development Server
4806
- const defaultIngestionUrl = 'http://3.137.217.33:3000';
4807
- // Vercel Production Server
4808
- // const defaultIngestionUrl = 'https://ingestion-server.vercel.app';
4809
- // Local Development Server
4810
- // const defaultIngestionUrl = 'http://localhost:3000';
4850
+ // Initialize API
4851
+ const defaultIngestionUrl = 'http://3.137.217.33:3000'; // AWS Development Server
4811
4852
  this.api = new HumanBehaviorAPI({
4812
4853
  apiKey: apiKey,
4813
4854
  ingestionUrl: ingestionUrl || defaultIngestionUrl
4814
4855
  });
4815
4856
  this.apiKey = apiKey;
4816
4857
  this.redactionManager = new RedactionManager();
4817
- // Check for existing session ID and last activity time in localStorage
4818
- const existingSessionId = isBrowser ? localStorage.getItem('human_behavior_session_id') : null;
4819
- const lastActivity = isBrowser ? localStorage.getItem('human_behavior_last_activity') : null;
4820
- // If we have a last activity time, check if it's within 30 minutes
4821
- const thirtyMinutesAgo = Date.now() - (30 * 60 * 1000);
4822
- const shouldUseExistingSession = lastActivity && parseInt(lastActivity) > thirtyMinutesAgo;
4823
- this.sessionId = (existingSessionId && shouldUseExistingSession) ? existingSessionId : v1();
4824
- // Store the session ID if it's new
4825
- if ((!existingSessionId || !shouldUseExistingSession) && isBrowser) {
4826
- localStorage.setItem('human_behavior_session_id', this.sessionId);
4858
+ // Handle session restoration
4859
+ if (isBrowser) {
4860
+ const existingSessionId = localStorage.getItem('human_behavior_session_id');
4861
+ const lastActivity = localStorage.getItem('human_behavior_last_activity');
4862
+ const thirtyMinutesAgo = Date.now() - (30 * 60 * 1000);
4863
+ if (existingSessionId && lastActivity && parseInt(lastActivity) > thirtyMinutesAgo) {
4864
+ this.sessionId = existingSessionId;
4865
+ logDebug(`Reusing existing session: ${this.sessionId}`);
4866
+ }
4867
+ else {
4868
+ this.sessionId = v1();
4869
+ logDebug(`Creating new session: ${this.sessionId}`);
4870
+ localStorage.setItem('human_behavior_session_id', this.sessionId);
4871
+ }
4872
+ this.currentUrl = window.location.href;
4873
+ window.__humanBehaviorGlobalTracker = this;
4874
+ }
4875
+ else {
4876
+ this.sessionId = v1();
4827
4877
  }
4828
- // Start initialization immediately
4878
+ // Start initialization
4829
4879
  this.initializationPromise = this.init();
4830
4880
  }
4831
4881
  init() {
4832
4882
  return __awaiter$1(this, void 0, void 0, function* () {
4833
4883
  try {
4834
4884
  const userId = this.getCookie(`human_behavior_end_user_id_${this.apiKey}`);
4885
+ logDebug(`Initializing with sessionId: ${this.sessionId}, userId: ${userId}`);
4835
4886
  const { sessionId, endUserId } = yield this.api.init(this.sessionId, userId);
4836
- this.sessionId = sessionId;
4887
+ // Check if server returned a different session ID
4888
+ if (sessionId !== this.sessionId) {
4889
+ logDebug(`Server returned different sessionId: ${sessionId} (client had: ${this.sessionId})`);
4890
+ this.sessionId = sessionId;
4891
+ // Update localStorage with server's session ID
4892
+ if (isBrowser) {
4893
+ localStorage.setItem('human_behavior_session_id', this.sessionId);
4894
+ }
4895
+ }
4837
4896
  this.endUserId = endUserId;
4838
4897
  this.setCookie(`human_behavior_end_user_id_${this.apiKey}`, endUserId, 365);
4839
4898
  // Only setup browser-specific handlers when in browser environment
4840
4899
  if (isBrowser) {
4841
4900
  this.setupPageUnloadHandler();
4842
- this.start();
4901
+ this.setupNavigationTracking();
4843
4902
  this.processRejectedEvents();
4844
4903
  }
4845
4904
  else {
4846
- console.warn('HumanBehaviorTracker initialized in a non-browser environment. Session tracking is disabled.');
4905
+ logWarn('HumanBehaviorTracker initialized in a non-browser environment. Session tracking is disabled.');
4847
4906
  }
4848
4907
  this.initialized = true;
4849
- console.log('HumanBehaviorTracker initialized');
4908
+ logInfo(`HumanBehaviorTracker initialized with sessionId: ${this.sessionId}, endUserId: ${endUserId}`);
4850
4909
  }
4851
4910
  catch (error) {
4852
- console.error('Failed to initialize HumanBehaviorTracker:', error);
4911
+ logError('Failed to initialize HumanBehaviorTracker:', error);
4853
4912
  throw error;
4854
4913
  }
4855
4914
  });
@@ -4862,48 +4921,295 @@ class HumanBehaviorTracker {
4862
4921
  yield this.initializationPromise;
4863
4922
  });
4864
4923
  }
4924
+ /**
4925
+ * Setup navigation event tracking for SPA navigation
4926
+ */
4927
+ setupNavigationTracking() {
4928
+ if (!isBrowser || this.navigationTrackingEnabled)
4929
+ return;
4930
+ this.navigationTrackingEnabled = true;
4931
+ logDebug('Setting up navigation tracking');
4932
+ // Store original history methods
4933
+ this.originalPushState = history.pushState;
4934
+ this.originalReplaceState = history.replaceState;
4935
+ // Override pushState to capture programmatic navigation
4936
+ history.pushState = (...args) => {
4937
+ this.previousUrl = this.currentUrl;
4938
+ this.currentUrl = window.location.href;
4939
+ // Call original method
4940
+ this.originalPushState.apply(history, args);
4941
+ // Track navigation event
4942
+ this.trackNavigationEvent('pushState', this.previousUrl, this.currentUrl);
4943
+ };
4944
+ // Override replaceState to capture programmatic navigation
4945
+ history.replaceState = (...args) => {
4946
+ this.previousUrl = this.currentUrl;
4947
+ this.currentUrl = window.location.href;
4948
+ // Call original method
4949
+ this.originalReplaceState.apply(history, args);
4950
+ // Track navigation event
4951
+ this.trackNavigationEvent('replaceState', this.previousUrl, this.currentUrl);
4952
+ };
4953
+ // Listen for popstate events (back/forward navigation)
4954
+ const popstateListener = () => {
4955
+ this.previousUrl = this.currentUrl;
4956
+ this.currentUrl = window.location.href;
4957
+ this.trackNavigationEvent('popstate', this.previousUrl, this.currentUrl);
4958
+ };
4959
+ window.addEventListener('popstate', popstateListener);
4960
+ this.navigationListeners.push(() => {
4961
+ window.removeEventListener('popstate', popstateListener);
4962
+ });
4963
+ // Listen for hashchange events
4964
+ const hashchangeListener = () => {
4965
+ this.previousUrl = this.currentUrl;
4966
+ this.currentUrl = window.location.href;
4967
+ this.trackNavigationEvent('hashchange', this.previousUrl, this.currentUrl);
4968
+ };
4969
+ window.addEventListener('hashchange', hashchangeListener);
4970
+ this.navigationListeners.push(() => {
4971
+ window.removeEventListener('hashchange', hashchangeListener);
4972
+ });
4973
+ // Track initial page load
4974
+ this.trackNavigationEvent('pageLoad', '', this.currentUrl);
4975
+ }
4976
+ /**
4977
+ * Track navigation events and send custom events
4978
+ */
4979
+ trackNavigationEvent(type, fromUrl, toUrl) {
4980
+ return __awaiter$1(this, void 0, void 0, function* () {
4981
+ if (!this.initialized)
4982
+ return;
4983
+ try {
4984
+ const navigationData = {
4985
+ type: type,
4986
+ from: fromUrl,
4987
+ to: toUrl,
4988
+ timestamp: new Date().toISOString(),
4989
+ pathname: window.location.pathname,
4990
+ search: window.location.search,
4991
+ hash: window.location.hash,
4992
+ referrer: document.referrer
4993
+ };
4994
+ // Add navigation event to the main event stream
4995
+ yield this.addEvent({
4996
+ type: 5, // Custom event type
4997
+ data: {
4998
+ payload: Object.assign({ eventType: 'navigation' }, navigationData)
4999
+ },
5000
+ timestamp: Date.now()
5001
+ });
5002
+ logDebug(`Navigation tracked: ${type} from ${fromUrl} to ${toUrl}`);
5003
+ }
5004
+ catch (error) {
5005
+ logError('Failed to track navigation event:', error);
5006
+ }
5007
+ });
5008
+ }
5009
+ /**
5010
+ * Track a page view event (PostHog-style)
5011
+ */
5012
+ trackPageView(url) {
5013
+ return __awaiter$1(this, void 0, void 0, function* () {
5014
+ if (!this.initialized)
5015
+ return;
5016
+ try {
5017
+ const pageViewData = {
5018
+ url: url || window.location.href,
5019
+ pathname: window.location.pathname,
5020
+ search: window.location.search,
5021
+ hash: window.location.hash,
5022
+ referrer: document.referrer,
5023
+ timestamp: new Date().toISOString()
5024
+ };
5025
+ // Add pageview event to the main event stream
5026
+ yield this.addEvent({
5027
+ type: 5, // Custom event type
5028
+ data: {
5029
+ payload: Object.assign({ eventType: 'pageview' }, pageViewData)
5030
+ },
5031
+ timestamp: Date.now()
5032
+ });
5033
+ logDebug(`Pageview tracked: ${pageViewData.url}`);
5034
+ }
5035
+ catch (error) {
5036
+ logError('Failed to track pageview event:', error);
5037
+ }
5038
+ });
5039
+ }
5040
+ /**
5041
+ * Track a custom event (PostHog-style)
5042
+ */
5043
+ customEvent(eventName, properties) {
5044
+ return __awaiter$1(this, void 0, void 0, function* () {
5045
+ if (!this.initialized)
5046
+ return;
5047
+ try {
5048
+ const customEventData = {
5049
+ eventName: eventName,
5050
+ properties: properties || {},
5051
+ timestamp: new Date().toISOString(),
5052
+ url: window.location.href,
5053
+ pathname: window.location.pathname
5054
+ };
5055
+ // Add custom event to the main event stream
5056
+ yield this.addEvent({
5057
+ type: 5, // Custom event type
5058
+ data: {
5059
+ payload: Object.assign({ eventType: 'custom' }, customEventData)
5060
+ },
5061
+ timestamp: Date.now()
5062
+ });
5063
+ logDebug(`Custom event tracked: ${eventName}`, properties);
5064
+ }
5065
+ catch (error) {
5066
+ logError('Failed to track custom event:', error);
5067
+ }
5068
+ });
5069
+ }
5070
+ /**
5071
+ * Cleanup navigation tracking
5072
+ */
5073
+ cleanupNavigationTracking() {
5074
+ if (!this.navigationTrackingEnabled)
5075
+ return;
5076
+ // Restore original history methods
5077
+ if (this.originalPushState) {
5078
+ history.pushState = this.originalPushState;
5079
+ }
5080
+ if (this.originalReplaceState) {
5081
+ history.replaceState = this.originalReplaceState;
5082
+ }
5083
+ // Remove event listeners
5084
+ this.navigationListeners.forEach(cleanup => cleanup());
5085
+ this.navigationListeners = [];
5086
+ this.navigationTrackingEnabled = false;
5087
+ logDebug('Navigation tracking cleaned up');
5088
+ }
4865
5089
  static logToStorage(message) {
5090
+ logInfo(message);
5091
+ }
5092
+ /**
5093
+ * Configure logging behavior for the SDK
5094
+ * @param config Logger configuration options
5095
+ */
5096
+ static configureLogging(config) {
5097
+ const levelMap = {
5098
+ 'none': 0,
5099
+ 'error': 1,
5100
+ 'warn': 2,
5101
+ 'info': 3,
5102
+ 'debug': 4
5103
+ };
5104
+ logger.setConfig({
5105
+ level: levelMap[config.level || 'error'],
5106
+ enableConsole: config.enableConsole !== false,
5107
+ enableStorage: config.enableStorage || false
5108
+ });
5109
+ }
5110
+ /**
5111
+ * Enable console event tracking
5112
+ */
5113
+ enableConsoleTracking() {
5114
+ if (!isBrowser || this.consoleTrackingEnabled)
5115
+ return;
5116
+ // Store original console methods
5117
+ this.originalConsole = {
5118
+ log: console.log,
5119
+ warn: console.warn,
5120
+ error: console.error
5121
+ };
5122
+ // Override console methods to capture ALL console output (including logger output)
5123
+ console.log = (...args) => {
5124
+ this.trackConsoleEvent('log', args);
5125
+ this.originalConsole.log(...args);
5126
+ };
5127
+ console.warn = (...args) => {
5128
+ this.trackConsoleEvent('warn', args);
5129
+ this.originalConsole.warn(...args);
5130
+ };
5131
+ console.error = (...args) => {
5132
+ this.trackConsoleEvent('error', args);
5133
+ this.originalConsole.error(...args);
5134
+ };
5135
+ this.consoleTrackingEnabled = true;
5136
+ logDebug('Console tracking enabled');
5137
+ }
5138
+ /**
5139
+ * Disable console event tracking
5140
+ */
5141
+ disableConsoleTracking() {
5142
+ if (!isBrowser || !this.consoleTrackingEnabled)
5143
+ return;
5144
+ // Restore original console methods
5145
+ if (this.originalConsole) {
5146
+ console.log = this.originalConsole.log;
5147
+ console.warn = this.originalConsole.warn;
5148
+ console.error = this.originalConsole.error;
5149
+ }
5150
+ this.consoleTrackingEnabled = false;
5151
+ logDebug('Console tracking disabled');
5152
+ }
5153
+ trackConsoleEvent(level, args) {
5154
+ if (!this.initialized)
5155
+ return;
4866
5156
  try {
4867
- const logs = JSON.parse(localStorage.getItem('human_behavior_logs') || '[]');
4868
- logs.push(`${new Date().toISOString()}: ${message}`);
4869
- localStorage.setItem('human_behavior_logs', JSON.stringify(logs));
5157
+ const consoleData = {
5158
+ level: level,
5159
+ message: args.map(arg => typeof arg === 'object' ? JSON.stringify(arg) : String(arg)).join(' '),
5160
+ timestamp: new Date().toISOString(),
5161
+ url: window.location.href
5162
+ };
5163
+ // Add console event to the main event stream
5164
+ this.addEvent({
5165
+ type: 5, // Custom event type
5166
+ data: {
5167
+ payload: Object.assign({ eventType: 'console' }, consoleData)
5168
+ },
5169
+ timestamp: Date.now()
5170
+ }).catch(error => {
5171
+ logError('Failed to track console event:', error);
5172
+ });
4870
5173
  }
4871
- catch (e) {
4872
- console.error('Failed to log to storage:', e);
5174
+ catch (error) {
5175
+ logError('Error in trackConsoleEvent:', error);
4873
5176
  }
4874
5177
  }
4875
5178
  setupPageUnloadHandler() {
4876
5179
  if (!isBrowser)
4877
5180
  return;
4878
- console.log('Setting up page unload handler');
5181
+ logDebug('Setting up page unload handler');
4879
5182
  // Handle visibility changes for sending events
4880
5183
  window.addEventListener('visibilitychange', () => {
4881
5184
  // Only send events when page becomes hidden
4882
5185
  if (document.visibilityState === 'hidden') {
4883
- console.log('Page hidden - sending pending events');
5186
+ logDebug('Page hidden - sending pending events');
4884
5187
  this.api.sendBeaconEvents(this.eventIngestionQueue, this.sessionId);
4885
5188
  }
4886
5189
  });
4887
5190
  // Handle actual page unload/close
4888
5191
  window.addEventListener('beforeunload', () => {
4889
- // Update last activity time
4890
- localStorage.setItem('human_behavior_last_activity', Date.now().toString());
4891
5192
  // Send final events
4892
5193
  this.api.sendBeaconEvents(this.eventIngestionQueue, this.sessionId);
4893
5194
  });
4894
- // Update activity timestamp periodically
4895
- setInterval(() => {
5195
+ // Update activity timestamp on user interaction (not on page load)
5196
+ const updateActivity = () => {
4896
5197
  localStorage.setItem('human_behavior_last_activity', Date.now().toString());
4897
- }, 60000); // Update every minute
5198
+ };
5199
+ // Listen for user interactions to update activity timestamp
5200
+ window.addEventListener('click', updateActivity);
5201
+ window.addEventListener('keydown', updateActivity);
5202
+ window.addEventListener('scroll', updateActivity);
5203
+ window.addEventListener('mousemove', updateActivity);
4898
5204
  }
4899
5205
  viewLogs() {
4900
5206
  try {
4901
- const logs = JSON.parse(localStorage.getItem('human_behavior_logs') || '[]');
5207
+ const logs = logger.getLogs();
4902
5208
  console.log('HumanBehavior Logs:', logs);
4903
- localStorage.removeItem('human_behavior_logs'); // Clear logs after viewing
5209
+ logger.clearLogs(); // Clear logs after viewing
4904
5210
  }
4905
5211
  catch (e) {
4906
- console.error('Failed to read logs:', e);
5212
+ logError('Failed to read logs:', e);
4907
5213
  }
4908
5214
  }
4909
5215
  addUserInfo(userProperties) {
@@ -4932,12 +5238,6 @@ class HumanBehaviorTracker {
4932
5238
  yield this.api.sendUserAuth(this.endUserId, this.userProperties, this.sessionId, authFields);
4933
5239
  });
4934
5240
  }
4935
- customEvent(eventName_1) {
4936
- return __awaiter$1(this, arguments, void 0, function* (eventName, eventProperties = {}) {
4937
- yield this.ensureInitialized();
4938
- this.api.sendBeaconCustomEvent(eventName, eventProperties, this.sessionId);
4939
- });
4940
- }
4941
5241
  start() {
4942
5242
  return __awaiter$1(this, void 0, void 0, function* () {
4943
5243
  yield this.ensureInitialized();
@@ -4947,6 +5247,8 @@ class HumanBehaviorTracker {
4947
5247
  this.flushInterval = window.setInterval(() => {
4948
5248
  this.flush();
4949
5249
  }, this.FLUSH_INTERVAL_MS);
5250
+ // Enable console tracking
5251
+ this.enableConsoleTracking();
4950
5252
  // Start recording with redaction enabled
4951
5253
  record({
4952
5254
  emit: (event) => {
@@ -4970,6 +5272,10 @@ class HumanBehaviorTracker {
4970
5272
  clearInterval(this.flushInterval);
4971
5273
  this.flushInterval = null;
4972
5274
  }
5275
+ // Disable console tracking
5276
+ this.disableConsoleTracking();
5277
+ // Cleanup navigation tracking
5278
+ this.cleanupNavigationTracking();
4973
5279
  });
4974
5280
  }
4975
5281
  addEvent(event) {
@@ -5003,7 +5309,7 @@ class HumanBehaviorTracker {
5003
5309
  this.sessionId = newSessionId;
5004
5310
  }
5005
5311
  catch (error) {
5006
- console.error('Failed to process rejected events:', error);
5312
+ logError('Failed to process rejected events:', error);
5007
5313
  }
5008
5314
  finally {
5009
5315
  this.isProcessingRejectedEvents = false;
@@ -5012,7 +5318,7 @@ class HumanBehaviorTracker {
5012
5318
  }
5013
5319
  flush() {
5014
5320
  return __awaiter$1(this, void 0, void 0, function* () {
5015
- var _a;
5321
+ var _a, _b, _c, _d;
5016
5322
  // Prevent concurrent flushes
5017
5323
  if (this.isProcessing || !this.initialized) {
5018
5324
  return;
@@ -5024,17 +5330,25 @@ class HumanBehaviorTracker {
5024
5330
  this.eventIngestionQueue = [];
5025
5331
  this.queueSizeBytes = 0;
5026
5332
  if (eventsToProcess.length > 0) {
5027
- console.log('Flushing events:', eventsToProcess);
5333
+ logDebug('Flushing events:', eventsToProcess);
5028
5334
  try {
5029
5335
  yield this.api.sendEvents(eventsToProcess, this.sessionId, this.endUserId);
5030
5336
  }
5031
5337
  catch (error) {
5032
5338
  // If we get a 400 error, store events for retry
5033
5339
  if ((_a = error.message) === null || _a === void 0 ? void 0 : _a.includes('ERROR: Session already completed')) {
5034
- console.log('Session expired, storing events for retry');
5340
+ logInfo('Session expired, storing events for retry');
5035
5341
  this.rejectedEvents.push(...eventsToProcess);
5036
5342
  this.processRejectedEvents();
5037
5343
  }
5344
+ else if (((_b = error.message) === null || _b === void 0 ? void 0 : _b.includes('ERR_BLOCKED_BY_CLIENT')) ||
5345
+ ((_c = error.message) === null || _c === void 0 ? void 0 : _c.includes('Failed to fetch')) ||
5346
+ ((_d = error.message) === null || _d === void 0 ? void 0 : _d.includes('NetworkError'))) {
5347
+ // Handle ad blocker or network issues gracefully
5348
+ logWarn('Request blocked by ad blocker or network issue, storing events for retry');
5349
+ this.rejectedEvents.push(...eventsToProcess);
5350
+ // Don't process rejected events immediately to avoid spam
5351
+ }
5038
5352
  else {
5039
5353
  throw error;
5040
5354
  }
@@ -5077,7 +5391,7 @@ class HumanBehaviorTracker {
5077
5391
  return __awaiter$1(this, void 0, void 0, function* () {
5078
5392
  yield this.ensureInitialized();
5079
5393
  if (!isBrowser) {
5080
- console.warn('Redaction is only available in browser environments');
5394
+ logWarn('Redaction is only available in browser environments');
5081
5395
  return;
5082
5396
  }
5083
5397
  // Create a new redaction manager with the provided options
@@ -5090,7 +5404,7 @@ class HumanBehaviorTracker {
5090
5404
  */
5091
5405
  setRedactedFields(fields) {
5092
5406
  if (!isBrowser) {
5093
- console.warn('Redaction is only available in browser environments');
5407
+ logWarn('Redaction is only available in browser environments');
5094
5408
  return;
5095
5409
  }
5096
5410
  this.redactionManager.setFieldsToRedact(fields);
@@ -5107,6 +5421,61 @@ class HumanBehaviorTracker {
5107
5421
  getRedactedFields() {
5108
5422
  return this.redactionManager.getSelectedFields();
5109
5423
  }
5424
+ /**
5425
+ * Get the current session ID
5426
+ */
5427
+ getSessionId() {
5428
+ return this.sessionId;
5429
+ }
5430
+ /**
5431
+ * Get the current URL being tracked
5432
+ */
5433
+ getCurrentUrl() {
5434
+ return this.currentUrl;
5435
+ }
5436
+ /**
5437
+ * Test if the tracker can reach the ingestion server
5438
+ */
5439
+ testConnection() {
5440
+ return __awaiter$1(this, void 0, void 0, function* () {
5441
+ try {
5442
+ yield this.api.init(this.sessionId, this.endUserId);
5443
+ return { success: true };
5444
+ }
5445
+ catch (error) {
5446
+ return {
5447
+ success: false,
5448
+ error: error.message || 'Unknown error'
5449
+ };
5450
+ }
5451
+ });
5452
+ }
5453
+ /**
5454
+ * Get connection status and recommendations
5455
+ */
5456
+ getConnectionStatus() {
5457
+ const recommendations = [];
5458
+ let blocked = false;
5459
+ // Check if we have rejected events (might indicate blocking)
5460
+ if (this.rejectedEvents.length > 0) {
5461
+ blocked = true;
5462
+ recommendations.push('Some requests may be blocked by ad blockers');
5463
+ }
5464
+ // Check if connection was blocked during initialization
5465
+ if (this._connectionBlocked) {
5466
+ blocked = true;
5467
+ recommendations.push('Initial connection test failed - ad blocker may be active');
5468
+ }
5469
+ // Check if we're in a browser environment
5470
+ if (typeof window === 'undefined') {
5471
+ recommendations.push('Not running in browser environment');
5472
+ }
5473
+ // Check if navigator.sendBeacon is available
5474
+ if (typeof navigator.sendBeacon === 'undefined') {
5475
+ recommendations.push('sendBeacon not available, using fetch fallback');
5476
+ }
5477
+ return { blocked, recommendations };
5478
+ }
5110
5479
  }
5111
5480
  // Only expose to window object in browser environments
5112
5481
  if (isBrowser) {
@@ -5127,6 +5496,11 @@ exports.MAX_CHUNK_SIZE_BYTES = MAX_CHUNK_SIZE_BYTES;
5127
5496
  exports.RedactionManager = RedactionManager;
5128
5497
  exports.default = HumanBehaviorTracker;
5129
5498
  exports.isChunkSizeExceeded = isChunkSizeExceeded;
5499
+ exports.logDebug = logDebug;
5500
+ exports.logError = logError;
5501
+ exports.logInfo = logInfo;
5502
+ exports.logWarn = logWarn;
5503
+ exports.logger = logger;
5130
5504
  exports.redactionManager = redactionManager;
5131
5505
  exports.validateSingleEventSize = validateSingleEventSize;
5132
5506
  //# sourceMappingURL=index.js.map