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/dist/cjs/index.js +277 -47
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/react/index.js +188 -51
- package/dist/cjs/react/index.js.map +1 -1
- package/dist/esm/index.js +273 -48
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/react/index.js +189 -52
- package/dist/esm/react/index.js.map +1 -1
- package/dist/index.min.js +2 -2
- package/dist/index.min.js.map +1 -1
- package/dist/types/index.d.ts +60 -3
- package/package.json +2 -2
- package/readme.md +116 -28
- package/src/api.ts +22 -8
- package/src/index.ts +3 -0
- package/src/react/index.tsx +85 -62
- package/src/redact.ts +19 -17
- package/src/tracker.ts +157 -26
- package/src/utils/logger.ts +144 -0
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
|
-
|
|
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
|
-
|
|
49
|
+
logDebug(`Redaction: Found ${elements.length} element(s) for selector '${selector}'`);
|
|
48
50
|
elements.forEach((el, index) => {
|
|
49
|
-
|
|
51
|
+
logDebug(`Redaction: Element ${index} for '${selector}':`, el);
|
|
50
52
|
});
|
|
51
53
|
});
|
|
52
54
|
} else {
|
|
53
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
142
|
+
logDebug('Redaction: Redacted nested value attribute');
|
|
141
143
|
}
|
|
142
144
|
}
|
|
143
145
|
|
|
144
|
-
|
|
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
|
-
|
|
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
|
-
|
|
377
|
-
|
|
378
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
39
|
-
|
|
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
|
-
|
|
107
|
+
logWarn('HumanBehaviorTracker initialized in a non-browser environment. Session tracking is disabled.');
|
|
81
108
|
}
|
|
82
109
|
|
|
83
110
|
this.initialized = true;
|
|
84
|
-
|
|
111
|
+
logInfo('HumanBehaviorTracker initialized');
|
|
85
112
|
} catch (error) {
|
|
86
|
-
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
264
|
+
const logs = logger.getLogs();
|
|
140
265
|
console.log('HumanBehavior Logs:', logs);
|
|
141
|
-
|
|
266
|
+
logger.clearLogs(); // Clear logs after viewing
|
|
142
267
|
} catch (e) {
|
|
143
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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);
|