humanbehavior-js 0.0.7 → 0.0.9

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/src/redact.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  // Redaction functionality for sensitive input fields
2
2
  // This module provides methods to redact sensitive input fields in event recordings
3
3
 
4
+ import { logDebug, logWarn } from './utils/logger';
5
+
4
6
  // Check if we're in a browser environment
5
7
  const isBrowser = typeof window !== 'undefined';
6
8
 
@@ -39,18 +41,18 @@ export class RedactionManager {
39
41
  fields.forEach(field => this.userSelectedFields.add(field));
40
42
 
41
43
  if (fields.length > 0) {
42
- console.log(`Redaction: Active for ${fields.length} field(s):`, fields);
44
+ logDebug(`Redaction: Active for ${fields.length} field(s):`, fields);
43
45
 
44
46
  // Debug: Check if elements exist
45
47
  fields.forEach(selector => {
46
48
  const elements = document.querySelectorAll(selector);
47
- console.log(`Redaction: Found ${elements.length} element(s) for selector '${selector}'`);
49
+ logDebug(`Redaction: Found ${elements.length} element(s) for selector '${selector}'`);
48
50
  elements.forEach((el, index) => {
49
- console.log(`Redaction: Element ${index} for '${selector}':`, el);
51
+ logDebug(`Redaction: Element ${index} for '${selector}':`, el);
50
52
  });
51
53
  });
52
54
  } else {
53
- console.log('Redaction: Disabled - no fields selected');
55
+ logDebug('Redaction: Disabled - no fields selected');
54
56
  }
55
57
  }
56
58
 
@@ -85,7 +87,7 @@ export class RedactionManager {
85
87
  if (processedEvent.data.source === 5) { // Input event
86
88
  const shouldRedact = this.isFieldSelected(processedEvent.data);
87
89
  if (shouldRedact) {
88
- console.log('Redaction: Processing input event for redaction');
90
+ logDebug('Redaction: Processing input event for redaction');
89
91
  this.redactInputEvent(processedEvent.data);
90
92
  }
91
93
  }
@@ -114,14 +116,14 @@ export class RedactionManager {
114
116
  return;
115
117
  }
116
118
 
117
- console.log('Redaction: Redacting input event with text:', inputData.text);
119
+ logDebug('Redaction: Redacting input event with text:', inputData.text);
118
120
 
119
121
  // Redact all text-related properties that could contain input data
120
122
  const textProperties = ['text', 'value', 'content', 'data', 'input', 'textContent'];
121
123
  textProperties.forEach(prop => {
122
124
  if (inputData[prop] !== undefined && typeof inputData[prop] === 'string') {
123
125
  inputData[prop] = this.redactedText;
124
- console.log(`Redaction: Redacted property '${prop}'`);
126
+ logDebug(`Redaction: Redacted property '${prop}'`);
125
127
  }
126
128
  });
127
129
 
@@ -129,7 +131,7 @@ export class RedactionManager {
129
131
  Object.keys(inputData).forEach(key => {
130
132
  if (typeof inputData[key] === 'string' && inputData[key].length > 0) {
131
133
  inputData[key] = this.redactedText;
132
- console.log(`Redaction: Redacted additional property '${key}'`);
134
+ logDebug(`Redaction: Redacted additional property '${key}'`);
133
135
  }
134
136
  });
135
137
 
@@ -137,11 +139,11 @@ export class RedactionManager {
137
139
  if (inputData.attributes && typeof inputData.attributes === 'object') {
138
140
  if (inputData.attributes.value && typeof inputData.attributes.value === 'string') {
139
141
  inputData.attributes.value = this.redactedText;
140
- console.log('Redaction: Redacted nested value attribute');
142
+ logDebug('Redaction: Redacted nested value attribute');
141
143
  }
142
144
  }
143
145
 
144
- console.log('Redaction: Input event redaction complete');
146
+ logDebug('Redaction: Input event redaction complete');
145
147
  }
146
148
 
147
149
  /**
@@ -209,7 +211,7 @@ export class RedactionManager {
209
211
 
210
212
  return false;
211
213
  } catch (e) {
212
- console.warn('Error checking if DOM change should be redacted:', e);
214
+ logWarn('Error checking if DOM change should be redacted:', e);
213
215
  return false;
214
216
  }
215
217
  }
@@ -373,10 +375,10 @@ export class RedactionManager {
373
375
  if (matchingElements.length > 0) {
374
376
  // Check if any of these elements are currently focused
375
377
  for (const el of matchingElements) {
376
- if (el === document.activeElement) {
377
- console.log('Redaction: Found focused element matching selector:', selector);
378
- return true;
379
- }
378
+ if (el === document.activeElement) {
379
+ logDebug('Redaction: Found focused element matching selector:', selector);
380
+ return true;
381
+ }
380
382
  }
381
383
  }
382
384
  }
@@ -385,7 +387,7 @@ export class RedactionManager {
385
387
  // Look for any input element that might be the active one
386
388
  const activeElement = document.activeElement;
387
389
  if (activeElement && this.shouldRedactElement(activeElement as HTMLElement)) {
388
- console.log('Redaction: Active element should be redacted');
390
+ logDebug('Redaction: Active element should be redacted');
389
391
  return true;
390
392
  }
391
393
 
@@ -423,7 +425,7 @@ export class RedactionManager {
423
425
 
424
426
  return false;
425
427
  } catch (e) {
426
- console.warn('Error checking if field should be redacted:', e);
428
+ logWarn('Error checking if field should be redacted:', e);
427
429
  return false;
428
430
  }
429
431
  }
package/src/tracker.ts CHANGED
@@ -2,6 +2,7 @@ import * as rrweb from 'rrweb';
2
2
  import { v1 as uuidv1 } from 'uuid';
3
3
  import { HumanBehaviorAPI } from './api';
4
4
  import { RedactionManager, RedactionOptions } from './redact';
5
+ import { logger, logError, logWarn, logInfo, logDebug } from './utils/logger';
5
6
 
6
7
  // Check if we're in a browser environment
7
8
  const isBrowser = typeof window !== 'undefined';
@@ -30,17 +31,43 @@ export class HumanBehaviorTracker {
30
31
  private initialized: boolean = false;
31
32
  public initializationPromise: Promise<void> | null = null;
32
33
  private redactionManager: RedactionManager;
33
-
34
- constructor(apiKey: string | undefined, ingestionUrl: string | undefined) {
34
+
35
+ // Console tracking properties
36
+ private originalConsole: {
37
+ log: typeof console.log;
38
+ warn: typeof console.warn;
39
+ error: typeof console.error;
40
+ } | null = null;
41
+ private originalLogger: {
42
+ error: typeof logError;
43
+ warn: typeof logWarn;
44
+ info: typeof logInfo;
45
+ debug: typeof logDebug;
46
+ } | null = null;
47
+ private consoleTrackingEnabled: boolean = false;
48
+
49
+ constructor(apiKey: string | undefined, ingestionUrl?: string) {
35
50
  if (!apiKey) {
36
51
  throw new Error('Human Behavior API Key is required');
37
52
  }
38
- if (!ingestionUrl) {
39
- throw new Error('Human Behavior Ingestion URL is required');
40
- }
53
+
54
+ // ========================================
55
+ // DEVELOPER: Choose your ingestion server
56
+ // ========================================
57
+ // Uncomment ONE of the following lines to select your server:
58
+
59
+ // AWS Development Server
60
+ const defaultIngestionUrl = 'http://3.137.217.33:3000';
61
+
62
+ // Vercel Production Server
63
+ // const defaultIngestionUrl = 'https://ingestion-server.vercel.app';
64
+
65
+ // Local Development Server
66
+ // const defaultIngestionUrl = 'http://localhost:3000';
67
+
41
68
  this.api = new HumanBehaviorAPI({
42
69
  apiKey: apiKey,
43
- ingestionUrl: ingestionUrl
70
+ ingestionUrl: ingestionUrl || defaultIngestionUrl
44
71
  });
45
72
  this.apiKey = apiKey;
46
73
  this.redactionManager = new RedactionManager();
@@ -77,13 +104,13 @@ export class HumanBehaviorTracker {
77
104
  this.start();
78
105
  this.processRejectedEvents();
79
106
  } else {
80
- console.warn('HumanBehaviorTracker initialized in a non-browser environment. Session tracking is disabled.');
107
+ logWarn('HumanBehaviorTracker initialized in a non-browser environment. Session tracking is disabled.');
81
108
  }
82
109
 
83
110
  this.initialized = true;
84
- console.log('HumanBehaviorTracker initialized');
111
+ logInfo('HumanBehaviorTracker initialized');
85
112
  } catch (error) {
86
- console.error('Failed to initialize HumanBehaviorTracker:', error);
113
+ logError('Failed to initialize HumanBehaviorTracker:', error);
87
114
  throw error;
88
115
  }
89
116
  }
@@ -96,25 +123,123 @@ export class HumanBehaviorTracker {
96
123
  }
97
124
 
98
125
  public static logToStorage(message: string) {
99
- try {
100
- const logs = JSON.parse(localStorage.getItem('human_behavior_logs') || '[]');
101
- logs.push(`${new Date().toISOString()}: ${message}`);
102
- localStorage.setItem('human_behavior_logs', JSON.stringify(logs));
103
- } catch (e) {
104
- console.error('Failed to log to storage:', e);
105
- }
126
+ logInfo(message);
127
+ }
128
+
129
+ /**
130
+ * Configure logging behavior for the SDK
131
+ * @param config Logger configuration options
132
+ */
133
+ public static configureLogging(config: { level?: 'none' | 'error' | 'warn' | 'info' | 'debug', enableConsole?: boolean, enableStorage?: boolean }) {
134
+ const levelMap = {
135
+ 'none': 0,
136
+ 'error': 1,
137
+ 'warn': 2,
138
+ 'info': 3,
139
+ 'debug': 4
140
+ };
141
+
142
+ logger.setConfig({
143
+ level: levelMap[config.level || 'error'],
144
+ enableConsole: config.enableConsole !== false,
145
+ enableStorage: config.enableStorage || false
146
+ });
147
+ }
148
+
149
+ /**
150
+ * Enable console event tracking
151
+ */
152
+ public enableConsoleTracking(): void {
153
+ if (!isBrowser || this.consoleTrackingEnabled) return;
154
+
155
+ // Store original console methods
156
+ this.originalConsole = {
157
+ log: console.log,
158
+ warn: console.warn,
159
+ error: console.error
160
+ };
161
+
162
+ // Store original logger methods
163
+ this.originalLogger = {
164
+ error: logError,
165
+ warn: logWarn,
166
+ info: logInfo,
167
+ debug: logDebug
168
+ };
169
+
170
+ // Override console methods to capture ALL console output (including logger output)
171
+ console.log = (...args) => {
172
+ this.trackConsoleEvent('log', args);
173
+ this.originalConsole!.log(...args);
174
+ };
175
+
176
+ console.warn = (...args) => {
177
+ this.trackConsoleEvent('warn', args);
178
+ this.originalConsole!.warn(...args);
179
+ };
180
+
181
+ console.error = (...args) => {
182
+ this.trackConsoleEvent('error', args);
183
+ this.originalConsole!.error(...args);
184
+ };
185
+
186
+ this.consoleTrackingEnabled = true;
187
+ this.originalLogger!.debug('Console tracking enabled');
188
+ }
189
+
190
+ /**
191
+ * Disable console event tracking
192
+ */
193
+ public disableConsoleTracking(): void {
194
+ if (!isBrowser || !this.consoleTrackingEnabled || !this.originalConsole) return;
195
+
196
+ // Restore original console methods
197
+ console.log = this.originalConsole.log;
198
+ console.warn = this.originalConsole.warn;
199
+ console.error = this.originalConsole.error;
200
+
201
+ this.consoleTrackingEnabled = false;
202
+ this.originalConsole = null;
203
+ this.originalLogger = null;
204
+ }
205
+
206
+ /**
207
+ * Track console events
208
+ */
209
+ private trackConsoleEvent(level: 'log' | 'warn' | 'error', args: any[]): void {
210
+ if (!this.initialized) return;
211
+
212
+ const consoleEvent = {
213
+ type: 5, // Custom event type
214
+ data: {
215
+ payload: {
216
+ type: 'console',
217
+ level: level,
218
+ message: args.map(arg =>
219
+ typeof arg === 'string' ? arg :
220
+ typeof arg === 'object' ? JSON.stringify(arg) :
221
+ String(arg)
222
+ ).join(' '),
223
+ timestamp: Date.now(),
224
+ url: window.location.href
225
+ }
226
+ },
227
+ timestamp: Date.now()
228
+ };
229
+
230
+ this.addEvent(consoleEvent);
106
231
  }
107
232
 
108
233
  private setupPageUnloadHandler() {
109
234
  if (!isBrowser) return;
110
235
 
111
- console.log('Setting up page unload handler');
236
+ logDebug('Setting up page unload handler');
112
237
 
113
238
  // Handle visibility changes for sending events
114
239
  window.addEventListener('visibilitychange', () => {
115
240
  // Only send events when page becomes hidden
116
241
  if (document.visibilityState === 'hidden') {
117
- console.log('Page hidden - sending pending events');
242
+ logDebug('Page hidden - sending pending events');
118
243
  this.api.sendBeaconEvents(this.eventIngestionQueue, this.sessionId);
119
244
  }
120
245
  });
@@ -136,11 +261,11 @@ export class HumanBehaviorTracker {
136
261
 
137
262
  public viewLogs() {
138
263
  try {
139
- const logs = JSON.parse(localStorage.getItem('human_behavior_logs') || '[]');
264
+ const logs = logger.getLogs();
140
265
  console.log('HumanBehavior Logs:', logs);
141
- localStorage.removeItem('human_behavior_logs'); // Clear logs after viewing
266
+ logger.clearLogs(); // Clear logs after viewing
142
267
  } catch (e) {
143
- console.error('Failed to read logs:', e);
268
+ logError('Failed to read logs:', e);
144
269
  }
145
270
  }
146
271
 
@@ -182,6 +307,9 @@ export class HumanBehaviorTracker {
182
307
  this.flush();
183
308
  }, this.FLUSH_INTERVAL_MS);
184
309
 
310
+ // Enable console tracking
311
+ this.enableConsoleTracking();
312
+
185
313
  // Start recording with redaction enabled
186
314
  rrweb.record({
187
315
  emit: (event) => {
@@ -204,6 +332,9 @@ export class HumanBehaviorTracker {
204
332
  clearInterval(this.flushInterval);
205
333
  this.flushInterval = null;
206
334
  }
335
+
336
+ // Disable console tracking
337
+ this.disableConsoleTracking();
207
338
  }
208
339
 
209
340
  public async addEvent(event: any) {
@@ -238,7 +369,7 @@ export class HumanBehaviorTracker {
238
369
  this.rejectedEvents = [];
239
370
  this.sessionId = newSessionId;
240
371
  } catch (error) {
241
- console.error('Failed to process rejected events:', error);
372
+ logError('Failed to process rejected events:', error);
242
373
  } finally {
243
374
  this.isProcessingRejectedEvents = false;
244
375
  }
@@ -258,13 +389,13 @@ export class HumanBehaviorTracker {
258
389
  this.queueSizeBytes = 0;
259
390
 
260
391
  if (eventsToProcess.length > 0) {
261
- console.log('Flushing events:', eventsToProcess);
392
+ logDebug('Flushing events:', eventsToProcess);
262
393
  try {
263
394
  await this.api.sendEvents(eventsToProcess, this.sessionId, this.endUserId!);
264
395
  } catch (error: any) {
265
396
  // If we get a 400 error, store events for retry
266
397
  if (error.message?.includes('ERROR: Session already completed')) {
267
- console.log('Session expired, storing events for retry');
398
+ logInfo('Session expired, storing events for retry');
268
399
  this.rejectedEvents.push(...eventsToProcess);
269
400
  this.processRejectedEvents();
270
401
  } else {
@@ -305,7 +436,7 @@ export class HumanBehaviorTracker {
305
436
  public async redact(options?: RedactionOptions): Promise<void> {
306
437
  await this.ensureInitialized();
307
438
  if (!isBrowser) {
308
- console.warn('Redaction is only available in browser environments');
439
+ logWarn('Redaction is only available in browser environments');
309
440
  return;
310
441
  }
311
442
 
@@ -319,7 +450,7 @@ export class HumanBehaviorTracker {
319
450
  */
320
451
  public setRedactedFields(fields: string[]): void {
321
452
  if (!isBrowser) {
322
- console.warn('Redaction is only available in browser environments');
453
+ logWarn('Redaction is only available in browser environments');
323
454
  return;
324
455
  }
325
456
 
@@ -0,0 +1,144 @@
1
+ export enum LogLevel {
2
+ NONE = 0,
3
+ ERROR = 1,
4
+ WARN = 2,
5
+ INFO = 3,
6
+ DEBUG = 4
7
+ }
8
+
9
+ export interface LoggerConfig {
10
+ level: LogLevel;
11
+ enableConsole: boolean;
12
+ enableStorage: boolean;
13
+ }
14
+
15
+ class Logger {
16
+ private config: LoggerConfig = {
17
+ level: LogLevel.ERROR, // Default to only errors in production
18
+ enableConsole: true,
19
+ enableStorage: false
20
+ };
21
+
22
+ private isBrowser = typeof window !== 'undefined';
23
+
24
+ constructor(config?: Partial<LoggerConfig>) {
25
+ if (config) {
26
+ this.config = { ...this.config, ...config };
27
+ }
28
+ }
29
+
30
+ setConfig(config: Partial<LoggerConfig>): void {
31
+ this.config = { ...this.config, ...config };
32
+ }
33
+
34
+ private shouldLog(level: LogLevel): boolean {
35
+ return level <= this.config.level;
36
+ }
37
+
38
+ private formatMessage(level: string, message: string, ...args: any[]): string {
39
+ const timestamp = new Date().toISOString();
40
+ return `[HumanBehavior ${level}] ${timestamp}: ${message}`;
41
+ }
42
+
43
+ error(message: string, ...args: any[]): void {
44
+ if (!this.shouldLog(LogLevel.ERROR)) return;
45
+
46
+ const formattedMessage = this.formatMessage('ERROR', message);
47
+
48
+ if (this.config.enableConsole) {
49
+ console.error(formattedMessage, ...args);
50
+ }
51
+
52
+ if (this.config.enableStorage && this.isBrowser) {
53
+ this.logToStorage(formattedMessage, args);
54
+ }
55
+ }
56
+
57
+ warn(message: string, ...args: any[]): void {
58
+ if (!this.shouldLog(LogLevel.WARN)) return;
59
+
60
+ const formattedMessage = this.formatMessage('WARN', message);
61
+
62
+ if (this.config.enableConsole) {
63
+ console.warn(formattedMessage, ...args);
64
+ }
65
+
66
+ if (this.config.enableStorage && this.isBrowser) {
67
+ this.logToStorage(formattedMessage, args);
68
+ }
69
+ }
70
+
71
+ info(message: string, ...args: any[]): void {
72
+ if (!this.shouldLog(LogLevel.INFO)) return;
73
+
74
+ const formattedMessage = this.formatMessage('INFO', message);
75
+
76
+ if (this.config.enableConsole) {
77
+ console.log(formattedMessage, ...args);
78
+ }
79
+
80
+ if (this.config.enableStorage && this.isBrowser) {
81
+ this.logToStorage(formattedMessage, args);
82
+ }
83
+ }
84
+
85
+ debug(message: string, ...args: any[]): void {
86
+ if (!this.shouldLog(LogLevel.DEBUG)) return;
87
+
88
+ const formattedMessage = this.formatMessage('DEBUG', message);
89
+
90
+ if (this.config.enableConsole) {
91
+ console.log(formattedMessage, ...args);
92
+ }
93
+
94
+ if (this.config.enableStorage && this.isBrowser) {
95
+ this.logToStorage(formattedMessage, args);
96
+ }
97
+ }
98
+
99
+ private logToStorage(message: string, args: any[]): void {
100
+ try {
101
+ const logs = JSON.parse(localStorage.getItem('human_behavior_logs') || '[]');
102
+ const logEntry = {
103
+ message,
104
+ args: args.length > 0 ? args : undefined,
105
+ timestamp: Date.now()
106
+ };
107
+ logs.push(logEntry);
108
+
109
+ // Keep only last 1000 logs to prevent storage bloat
110
+ if (logs.length > 1000) {
111
+ logs.splice(0, logs.length - 1000);
112
+ }
113
+
114
+ localStorage.setItem('human_behavior_logs', JSON.stringify(logs));
115
+ } catch (e) {
116
+ // Silently fail if storage is not available
117
+ }
118
+ }
119
+
120
+ getLogs(): any[] {
121
+ if (!this.isBrowser) return [];
122
+
123
+ try {
124
+ return JSON.parse(localStorage.getItem('human_behavior_logs') || '[]');
125
+ } catch (e) {
126
+ return [];
127
+ }
128
+ }
129
+
130
+ clearLogs(): void {
131
+ if (this.isBrowser) {
132
+ localStorage.removeItem('human_behavior_logs');
133
+ }
134
+ }
135
+ }
136
+
137
+ // Create singleton instance
138
+ export const logger = new Logger();
139
+
140
+ // Export convenience methods
141
+ export const logError = (message: string, ...args: any[]) => logger.error(message, ...args);
142
+ export const logWarn = (message: string, ...args: any[]) => logger.warn(message, ...args);
143
+ export const logInfo = (message: string, ...args: any[]) => logger.info(message, ...args);
144
+ export const logDebug = (message: string, ...args: any[]) => logger.debug(message, ...args);