humanbehavior-js 0.0.8 → 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/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,6 +31,20 @@ export class HumanBehaviorTracker {
30
31
  private initialized: boolean = false;
31
32
  public initializationPromise: Promise<void> | null = null;
32
33
  private redactionManager: RedactionManager;
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;
33
48
 
34
49
  constructor(apiKey: string | undefined, ingestionUrl?: string) {
35
50
  if (!apiKey) {
@@ -89,13 +104,13 @@ export class HumanBehaviorTracker {
89
104
  this.start();
90
105
  this.processRejectedEvents();
91
106
  } else {
92
- 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.');
93
108
  }
94
109
 
95
110
  this.initialized = true;
96
- console.log('HumanBehaviorTracker initialized');
111
+ logInfo('HumanBehaviorTracker initialized');
97
112
  } catch (error) {
98
- console.error('Failed to initialize HumanBehaviorTracker:', error);
113
+ logError('Failed to initialize HumanBehaviorTracker:', error);
99
114
  throw error;
100
115
  }
101
116
  }
@@ -108,25 +123,123 @@ export class HumanBehaviorTracker {
108
123
  }
109
124
 
110
125
  public static logToStorage(message: string) {
111
- try {
112
- const logs = JSON.parse(localStorage.getItem('human_behavior_logs') || '[]');
113
- logs.push(`${new Date().toISOString()}: ${message}`);
114
- localStorage.setItem('human_behavior_logs', JSON.stringify(logs));
115
- } catch (e) {
116
- console.error('Failed to log to storage:', e);
117
- }
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);
118
231
  }
119
232
 
120
233
  private setupPageUnloadHandler() {
121
234
  if (!isBrowser) return;
122
235
 
123
- console.log('Setting up page unload handler');
236
+ logDebug('Setting up page unload handler');
124
237
 
125
238
  // Handle visibility changes for sending events
126
239
  window.addEventListener('visibilitychange', () => {
127
240
  // Only send events when page becomes hidden
128
241
  if (document.visibilityState === 'hidden') {
129
- console.log('Page hidden - sending pending events');
242
+ logDebug('Page hidden - sending pending events');
130
243
  this.api.sendBeaconEvents(this.eventIngestionQueue, this.sessionId);
131
244
  }
132
245
  });
@@ -148,11 +261,11 @@ export class HumanBehaviorTracker {
148
261
 
149
262
  public viewLogs() {
150
263
  try {
151
- const logs = JSON.parse(localStorage.getItem('human_behavior_logs') || '[]');
264
+ const logs = logger.getLogs();
152
265
  console.log('HumanBehavior Logs:', logs);
153
- localStorage.removeItem('human_behavior_logs'); // Clear logs after viewing
266
+ logger.clearLogs(); // Clear logs after viewing
154
267
  } catch (e) {
155
- console.error('Failed to read logs:', e);
268
+ logError('Failed to read logs:', e);
156
269
  }
157
270
  }
158
271
 
@@ -194,6 +307,9 @@ export class HumanBehaviorTracker {
194
307
  this.flush();
195
308
  }, this.FLUSH_INTERVAL_MS);
196
309
 
310
+ // Enable console tracking
311
+ this.enableConsoleTracking();
312
+
197
313
  // Start recording with redaction enabled
198
314
  rrweb.record({
199
315
  emit: (event) => {
@@ -216,6 +332,9 @@ export class HumanBehaviorTracker {
216
332
  clearInterval(this.flushInterval);
217
333
  this.flushInterval = null;
218
334
  }
335
+
336
+ // Disable console tracking
337
+ this.disableConsoleTracking();
219
338
  }
220
339
 
221
340
  public async addEvent(event: any) {
@@ -250,7 +369,7 @@ export class HumanBehaviorTracker {
250
369
  this.rejectedEvents = [];
251
370
  this.sessionId = newSessionId;
252
371
  } catch (error) {
253
- console.error('Failed to process rejected events:', error);
372
+ logError('Failed to process rejected events:', error);
254
373
  } finally {
255
374
  this.isProcessingRejectedEvents = false;
256
375
  }
@@ -270,13 +389,13 @@ export class HumanBehaviorTracker {
270
389
  this.queueSizeBytes = 0;
271
390
 
272
391
  if (eventsToProcess.length > 0) {
273
- console.log('Flushing events:', eventsToProcess);
392
+ logDebug('Flushing events:', eventsToProcess);
274
393
  try {
275
394
  await this.api.sendEvents(eventsToProcess, this.sessionId, this.endUserId!);
276
395
  } catch (error: any) {
277
396
  // If we get a 400 error, store events for retry
278
397
  if (error.message?.includes('ERROR: Session already completed')) {
279
- console.log('Session expired, storing events for retry');
398
+ logInfo('Session expired, storing events for retry');
280
399
  this.rejectedEvents.push(...eventsToProcess);
281
400
  this.processRejectedEvents();
282
401
  } else {
@@ -317,7 +436,7 @@ export class HumanBehaviorTracker {
317
436
  public async redact(options?: RedactionOptions): Promise<void> {
318
437
  await this.ensureInitialized();
319
438
  if (!isBrowser) {
320
- console.warn('Redaction is only available in browser environments');
439
+ logWarn('Redaction is only available in browser environments');
321
440
  return;
322
441
  }
323
442
 
@@ -331,7 +450,7 @@ export class HumanBehaviorTracker {
331
450
  */
332
451
  public setRedactedFields(fields: string[]): void {
333
452
  if (!isBrowser) {
334
- console.warn('Redaction is only available in browser environments');
453
+ logWarn('Redaction is only available in browser environments');
335
454
  return;
336
455
  }
337
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);