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