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/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';
@@ -10,6 +11,7 @@ const isBrowser = typeof window !== 'undefined';
10
11
  declare global {
11
12
  interface Window {
12
13
  HumanBehaviorTracker: typeof HumanBehaviorTracker;
14
+ __humanBehaviorGlobalTracker?: HumanBehaviorTracker;
13
15
  }
14
16
  }
15
17
 
@@ -19,37 +21,87 @@ export class HumanBehaviorTracker {
19
21
  private rejectedEvents: any[] = [];
20
22
  private isProcessingRejectedEvents: boolean = false;
21
23
 
22
- private sessionId: string;
24
+ private sessionId!: string;
23
25
  private userProperties: Record<string, any> = {};
24
26
  private isProcessing: boolean = false;
25
27
  private flushInterval: number | null = null;
26
28
  private readonly FLUSH_INTERVAL_MS = 5000; // Flush every 5 seconds
27
- private api: HumanBehaviorAPI;
29
+ private api!: HumanBehaviorAPI;
28
30
  private endUserId: string | null = null;
29
- private apiKey: string;
31
+ private apiKey!: string;
30
32
  private initialized: boolean = false;
31
33
  public initializationPromise: Promise<void> | null = null;
32
- private redactionManager: RedactionManager;
34
+ private redactionManager!: RedactionManager;
35
+
36
+ // Console tracking properties
37
+ private originalConsole: {
38
+ log: typeof console.log;
39
+ warn: typeof console.warn;
40
+ error: typeof console.error;
41
+ } | null = null;
42
+ private consoleTrackingEnabled: boolean = false;
43
+
44
+ // Navigation tracking properties
45
+ public navigationTrackingEnabled: boolean = false;
46
+ private currentUrl: string = '';
47
+ private previousUrl: string = '';
48
+ private originalPushState: typeof history.pushState | null = null;
49
+ private originalReplaceState: typeof history.replaceState | null = null;
50
+ private navigationListeners: Array<() => void> = [];
51
+ private _connectionBlocked: boolean = false;
52
+
53
+ /**
54
+ * Initialize the HumanBehavior tracker
55
+ * This is the main entry point - call this once per page
56
+ */
57
+ public static init(apiKey: string, options?: {
58
+ ingestionUrl?: string;
59
+ logLevel?: 'none' | 'error' | 'warn' | 'info' | 'debug';
60
+ redactFields?: string[];
61
+ }): HumanBehaviorTracker {
62
+ // Return existing instance if already initialized
63
+ if (isBrowser && window.__humanBehaviorGlobalTracker) {
64
+ logDebug('Tracker already initialized, returning existing instance');
65
+ return window.__humanBehaviorGlobalTracker;
66
+ }
67
+
68
+ // Configure logging if specified
69
+ if (options?.logLevel) {
70
+ this.configureLogging({ level: options.logLevel });
71
+ }
72
+
73
+ // Create new tracker instance
74
+ const tracker = new HumanBehaviorTracker(apiKey, options?.ingestionUrl);
75
+
76
+ // Set redacted fields if specified
77
+ if (options?.redactFields) {
78
+ tracker.setRedactedFields(options.redactFields);
79
+ }
80
+
81
+ // Test connection (non-blocking)
82
+ if (isBrowser) {
83
+ const testUrl = tracker.api['baseUrl'] + '/api/ingestion/health';
84
+ fetch(testUrl, { method: 'HEAD' })
85
+ .then(() => logDebug('Connection test successful'))
86
+ .catch((error) => {
87
+ logWarn('Connection test failed - ad blocker may be active:', error.message);
88
+ tracker._connectionBlocked = true;
89
+ });
90
+ }
91
+
92
+ // Start tracking
93
+ tracker.start();
94
+
95
+ return tracker;
96
+ }
33
97
 
34
98
  constructor(apiKey: string | undefined, ingestionUrl?: string) {
35
99
  if (!apiKey) {
36
100
  throw new Error('Human Behavior API Key is required');
37
101
  }
38
102
 
39
- // ========================================
40
- // DEVELOPER: Choose your ingestion server
41
- // ========================================
42
- // Uncomment ONE of the following lines to select your server:
43
-
44
- // AWS Development Server
45
- const defaultIngestionUrl = 'http://3.137.217.33:3000';
46
-
47
- // Vercel Production Server
48
- // const defaultIngestionUrl = 'https://ingestion-server.vercel.app';
49
-
50
- // Local Development Server
51
- // const defaultIngestionUrl = 'http://localhost:3000';
52
-
103
+ // Initialize API
104
+ const defaultIngestionUrl = 'http://3.137.217.33:3000'; // AWS Development Server
53
105
  this.api = new HumanBehaviorAPI({
54
106
  apiKey: apiKey,
55
107
  ingestionUrl: ingestionUrl || defaultIngestionUrl
@@ -57,45 +109,64 @@ export class HumanBehaviorTracker {
57
109
  this.apiKey = apiKey;
58
110
  this.redactionManager = new RedactionManager();
59
111
 
60
- // Check for existing session ID and last activity time in localStorage
61
- const existingSessionId = isBrowser ? localStorage.getItem('human_behavior_session_id') : null;
62
- const lastActivity = isBrowser ? localStorage.getItem('human_behavior_last_activity') : null;
63
-
64
- // If we have a last activity time, check if it's within 30 minutes
65
- const thirtyMinutesAgo = Date.now() - (30 * 60 * 1000);
66
- const shouldUseExistingSession = lastActivity && parseInt(lastActivity) > thirtyMinutesAgo;
67
- this.sessionId = (existingSessionId && shouldUseExistingSession) ? existingSessionId : uuidv1();
68
-
69
- // Store the session ID if it's new
70
- if ((!existingSessionId || !shouldUseExistingSession) && isBrowser) {
71
- localStorage.setItem('human_behavior_session_id', this.sessionId);
112
+ // Handle session restoration
113
+ if (isBrowser) {
114
+ const existingSessionId = localStorage.getItem('human_behavior_session_id');
115
+ const lastActivity = localStorage.getItem('human_behavior_last_activity');
116
+ const thirtyMinutesAgo = Date.now() - (30 * 60 * 1000);
117
+
118
+ if (existingSessionId && lastActivity && parseInt(lastActivity) > thirtyMinutesAgo) {
119
+ this.sessionId = existingSessionId;
120
+ logDebug(`Reusing existing session: ${this.sessionId}`);
121
+ } else {
122
+ this.sessionId = uuidv1();
123
+ logDebug(`Creating new session: ${this.sessionId}`);
124
+ localStorage.setItem('human_behavior_session_id', this.sessionId);
125
+ }
126
+
127
+ this.currentUrl = window.location.href;
128
+ window.__humanBehaviorGlobalTracker = this;
129
+ } else {
130
+ this.sessionId = uuidv1();
72
131
  }
73
132
 
74
- // Start initialization immediately
133
+ // Start initialization
75
134
  this.initializationPromise = this.init();
76
135
  }
77
136
 
78
137
  private async init(): Promise<void> {
79
138
  try {
80
139
  const userId = this.getCookie(`human_behavior_end_user_id_${this.apiKey}`);
140
+ logDebug(`Initializing with sessionId: ${this.sessionId}, userId: ${userId}`);
141
+
81
142
  const { sessionId, endUserId } = await this.api.init(this.sessionId, userId);
82
- this.sessionId = sessionId;
143
+
144
+ // Check if server returned a different session ID
145
+ if (sessionId !== this.sessionId) {
146
+ logDebug(`Server returned different sessionId: ${sessionId} (client had: ${this.sessionId})`);
147
+ this.sessionId = sessionId;
148
+ // Update localStorage with server's session ID
149
+ if (isBrowser) {
150
+ localStorage.setItem('human_behavior_session_id', this.sessionId);
151
+ }
152
+ }
153
+
83
154
  this.endUserId = endUserId;
84
155
  this.setCookie(`human_behavior_end_user_id_${this.apiKey}`, endUserId, 365);
85
156
 
86
157
  // Only setup browser-specific handlers when in browser environment
87
158
  if (isBrowser) {
88
159
  this.setupPageUnloadHandler();
89
- this.start();
160
+ this.setupNavigationTracking();
90
161
  this.processRejectedEvents();
91
162
  } else {
92
- console.warn('HumanBehaviorTracker initialized in a non-browser environment. Session tracking is disabled.');
163
+ logWarn('HumanBehaviorTracker initialized in a non-browser environment. Session tracking is disabled.');
93
164
  }
94
165
 
95
166
  this.initialized = true;
96
- console.log('HumanBehaviorTracker initialized');
167
+ logInfo(`HumanBehaviorTracker initialized with sessionId: ${this.sessionId}, endUserId: ${endUserId}`);
97
168
  } catch (error) {
98
- console.error('Failed to initialize HumanBehaviorTracker:', error);
169
+ logError('Failed to initialize HumanBehaviorTracker:', error);
99
170
  throw error;
100
171
  }
101
172
  }
@@ -107,52 +178,342 @@ export class HumanBehaviorTracker {
107
178
  await this.initializationPromise;
108
179
  }
109
180
 
181
+ /**
182
+ * Setup navigation event tracking for SPA navigation
183
+ */
184
+ private setupNavigationTracking(): void {
185
+ if (!isBrowser || this.navigationTrackingEnabled) return;
186
+
187
+ this.navigationTrackingEnabled = true;
188
+ logDebug('Setting up navigation tracking');
189
+
190
+ // Store original history methods
191
+ this.originalPushState = history.pushState;
192
+ this.originalReplaceState = history.replaceState;
193
+
194
+ // Override pushState to capture programmatic navigation
195
+ history.pushState = (...args) => {
196
+ this.previousUrl = this.currentUrl;
197
+ this.currentUrl = window.location.href;
198
+
199
+ // Call original method
200
+ this.originalPushState!.apply(history, args);
201
+
202
+ // Track navigation event
203
+ this.trackNavigationEvent('pushState', this.previousUrl, this.currentUrl);
204
+ };
205
+
206
+ // Override replaceState to capture programmatic navigation
207
+ history.replaceState = (...args) => {
208
+ this.previousUrl = this.currentUrl;
209
+ this.currentUrl = window.location.href;
210
+
211
+ // Call original method
212
+ this.originalReplaceState!.apply(history, args);
213
+
214
+ // Track navigation event
215
+ this.trackNavigationEvent('replaceState', this.previousUrl, this.currentUrl);
216
+ };
217
+
218
+ // Listen for popstate events (back/forward navigation)
219
+ const popstateListener = () => {
220
+ this.previousUrl = this.currentUrl;
221
+ this.currentUrl = window.location.href;
222
+ this.trackNavigationEvent('popstate', this.previousUrl, this.currentUrl);
223
+ };
224
+
225
+ window.addEventListener('popstate', popstateListener);
226
+ this.navigationListeners.push(() => {
227
+ window.removeEventListener('popstate', popstateListener);
228
+ });
229
+
230
+ // Listen for hashchange events
231
+ const hashchangeListener = () => {
232
+ this.previousUrl = this.currentUrl;
233
+ this.currentUrl = window.location.href;
234
+ this.trackNavigationEvent('hashchange', this.previousUrl, this.currentUrl);
235
+ };
236
+
237
+ window.addEventListener('hashchange', hashchangeListener);
238
+ this.navigationListeners.push(() => {
239
+ window.removeEventListener('hashchange', hashchangeListener);
240
+ });
241
+
242
+ // Track initial page load
243
+ this.trackNavigationEvent('pageLoad', '', this.currentUrl);
244
+ }
245
+
246
+ /**
247
+ * Track navigation events and send custom events
248
+ */
249
+ public async trackNavigationEvent(type: string, fromUrl: string, toUrl: string): Promise<void> {
250
+ if (!this.initialized) return;
251
+
252
+ try {
253
+ const navigationData = {
254
+ type: type,
255
+ from: fromUrl,
256
+ to: toUrl,
257
+ timestamp: new Date().toISOString(),
258
+ pathname: window.location.pathname,
259
+ search: window.location.search,
260
+ hash: window.location.hash,
261
+ referrer: document.referrer
262
+ };
263
+
264
+ // Add navigation event to the main event stream
265
+ await this.addEvent({
266
+ type: 5, // Custom event type
267
+ data: {
268
+ payload: {
269
+ eventType: 'navigation',
270
+ ...navigationData
271
+ }
272
+ },
273
+ timestamp: Date.now()
274
+ });
275
+
276
+ logDebug(`Navigation tracked: ${type} from ${fromUrl} to ${toUrl}`);
277
+ } catch (error) {
278
+ logError('Failed to track navigation event:', error);
279
+ }
280
+ }
281
+
282
+ /**
283
+ * Track a page view event (PostHog-style)
284
+ */
285
+ public async trackPageView(url?: string): Promise<void> {
286
+ if (!this.initialized) return;
287
+
288
+ try {
289
+ const pageViewData = {
290
+ url: url || window.location.href,
291
+ pathname: window.location.pathname,
292
+ search: window.location.search,
293
+ hash: window.location.hash,
294
+ referrer: document.referrer,
295
+ timestamp: new Date().toISOString()
296
+ };
297
+
298
+ // Add pageview event to the main event stream
299
+ await this.addEvent({
300
+ type: 5, // Custom event type
301
+ data: {
302
+ payload: {
303
+ eventType: 'pageview',
304
+ ...pageViewData
305
+ }
306
+ },
307
+ timestamp: Date.now()
308
+ });
309
+
310
+ logDebug(`Pageview tracked: ${pageViewData.url}`);
311
+ } catch (error) {
312
+ logError('Failed to track pageview event:', error);
313
+ }
314
+ }
315
+
316
+ /**
317
+ * Track a custom event (PostHog-style)
318
+ */
319
+ public async customEvent(eventName: string, properties?: Record<string, any>): Promise<void> {
320
+ if (!this.initialized) return;
321
+
322
+ try {
323
+ const customEventData = {
324
+ eventName: eventName,
325
+ properties: properties || {},
326
+ timestamp: new Date().toISOString(),
327
+ url: window.location.href,
328
+ pathname: window.location.pathname
329
+ };
330
+
331
+ // Add custom event to the main event stream
332
+ await this.addEvent({
333
+ type: 5, // Custom event type
334
+ data: {
335
+ payload: {
336
+ eventType: 'custom',
337
+ ...customEventData
338
+ }
339
+ },
340
+ timestamp: Date.now()
341
+ });
342
+
343
+ logDebug(`Custom event tracked: ${eventName}`, properties);
344
+ } catch (error) {
345
+ logError('Failed to track custom event:', error);
346
+ }
347
+ }
348
+
349
+ /**
350
+ * Cleanup navigation tracking
351
+ */
352
+ private cleanupNavigationTracking(): void {
353
+ if (!this.navigationTrackingEnabled) return;
354
+
355
+ // Restore original history methods
356
+ if (this.originalPushState) {
357
+ history.pushState = this.originalPushState;
358
+ }
359
+ if (this.originalReplaceState) {
360
+ history.replaceState = this.originalReplaceState;
361
+ }
362
+
363
+ // Remove event listeners
364
+ this.navigationListeners.forEach(cleanup => cleanup());
365
+ this.navigationListeners = [];
366
+
367
+ this.navigationTrackingEnabled = false;
368
+ logDebug('Navigation tracking cleaned up');
369
+ }
370
+
110
371
  public static logToStorage(message: string) {
372
+ logInfo(message);
373
+ }
374
+
375
+ /**
376
+ * Configure logging behavior for the SDK
377
+ * @param config Logger configuration options
378
+ */
379
+ public static configureLogging(config: { level?: 'none' | 'error' | 'warn' | 'info' | 'debug', enableConsole?: boolean, enableStorage?: boolean }) {
380
+ const levelMap = {
381
+ 'none': 0,
382
+ 'error': 1,
383
+ 'warn': 2,
384
+ 'info': 3,
385
+ 'debug': 4
386
+ };
387
+
388
+ logger.setConfig({
389
+ level: levelMap[config.level || 'error'],
390
+ enableConsole: config.enableConsole !== false,
391
+ enableStorage: config.enableStorage || false
392
+ });
393
+ }
394
+
395
+ /**
396
+ * Enable console event tracking
397
+ */
398
+ public enableConsoleTracking(): void {
399
+ if (!isBrowser || this.consoleTrackingEnabled) return;
400
+
401
+ // Store original console methods
402
+ this.originalConsole = {
403
+ log: console.log,
404
+ warn: console.warn,
405
+ error: console.error
406
+ };
407
+
408
+
409
+
410
+ // Override console methods to capture ALL console output (including logger output)
411
+ console.log = (...args) => {
412
+ this.trackConsoleEvent('log', args);
413
+ this.originalConsole!.log(...args);
414
+ };
415
+
416
+ console.warn = (...args) => {
417
+ this.trackConsoleEvent('warn', args);
418
+ this.originalConsole!.warn(...args);
419
+ };
420
+
421
+ console.error = (...args) => {
422
+ this.trackConsoleEvent('error', args);
423
+ this.originalConsole!.error(...args);
424
+ };
425
+
426
+ this.consoleTrackingEnabled = true;
427
+ logDebug('Console tracking enabled');
428
+ }
429
+
430
+ /**
431
+ * Disable console event tracking
432
+ */
433
+ public disableConsoleTracking(): void {
434
+ if (!isBrowser || !this.consoleTrackingEnabled) return;
435
+
436
+ // Restore original console methods
437
+ if (this.originalConsole) {
438
+ console.log = this.originalConsole.log;
439
+ console.warn = this.originalConsole.warn;
440
+ console.error = this.originalConsole.error;
441
+ }
442
+
443
+ this.consoleTrackingEnabled = false;
444
+ logDebug('Console tracking disabled');
445
+ }
446
+
447
+ private trackConsoleEvent(level: 'log' | 'warn' | 'error', args: any[]): void {
448
+ if (!this.initialized) return;
449
+
111
450
  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);
451
+ const consoleData = {
452
+ level: level,
453
+ message: args.map(arg =>
454
+ typeof arg === 'object' ? JSON.stringify(arg) : String(arg)
455
+ ).join(' '),
456
+ timestamp: new Date().toISOString(),
457
+ url: window.location.href
458
+ };
459
+
460
+ // Add console event to the main event stream
461
+ this.addEvent({
462
+ type: 5, // Custom event type
463
+ data: {
464
+ payload: {
465
+ eventType: 'console',
466
+ ...consoleData
467
+ }
468
+ },
469
+ timestamp: Date.now()
470
+ }).catch(error => {
471
+ logError('Failed to track console event:', error);
472
+ });
473
+ } catch (error) {
474
+ logError('Error in trackConsoleEvent:', error);
117
475
  }
118
476
  }
119
477
 
120
478
  private setupPageUnloadHandler() {
121
479
  if (!isBrowser) return;
122
480
 
123
- console.log('Setting up page unload handler');
481
+ logDebug('Setting up page unload handler');
124
482
 
125
483
  // Handle visibility changes for sending events
126
484
  window.addEventListener('visibilitychange', () => {
127
485
  // Only send events when page becomes hidden
128
486
  if (document.visibilityState === 'hidden') {
129
- console.log('Page hidden - sending pending events');
487
+ logDebug('Page hidden - sending pending events');
130
488
  this.api.sendBeaconEvents(this.eventIngestionQueue, this.sessionId);
131
489
  }
132
490
  });
133
491
 
134
492
  // Handle actual page unload/close
135
493
  window.addEventListener('beforeunload', () => {
136
- // Update last activity time
137
- localStorage.setItem('human_behavior_last_activity', Date.now().toString());
138
-
139
494
  // Send final events
140
495
  this.api.sendBeaconEvents(this.eventIngestionQueue, this.sessionId);
141
496
  });
142
497
 
143
- // Update activity timestamp periodically
144
- setInterval(() => {
498
+ // Update activity timestamp on user interaction (not on page load)
499
+ const updateActivity = () => {
145
500
  localStorage.setItem('human_behavior_last_activity', Date.now().toString());
146
- }, 60000); // Update every minute
501
+ };
502
+
503
+ // Listen for user interactions to update activity timestamp
504
+ window.addEventListener('click', updateActivity);
505
+ window.addEventListener('keydown', updateActivity);
506
+ window.addEventListener('scroll', updateActivity);
507
+ window.addEventListener('mousemove', updateActivity);
147
508
  }
148
509
 
149
510
  public viewLogs() {
150
511
  try {
151
- const logs = JSON.parse(localStorage.getItem('human_behavior_logs') || '[]');
512
+ const logs = logger.getLogs();
152
513
  console.log('HumanBehavior Logs:', logs);
153
- localStorage.removeItem('human_behavior_logs'); // Clear logs after viewing
514
+ logger.clearLogs(); // Clear logs after viewing
154
515
  } catch (e) {
155
- console.error('Failed to read logs:', e);
516
+ logError('Failed to read logs:', e);
156
517
  }
157
518
  }
158
519
 
@@ -180,11 +541,6 @@ export class HumanBehaviorTracker {
180
541
  await this.api.sendUserAuth(this.endUserId, this.userProperties, this.sessionId, authFields);
181
542
  }
182
543
 
183
- public async customEvent(eventName: string, eventProperties: Record<string, any> = {}) {
184
- await this.ensureInitialized();
185
- this.api.sendBeaconCustomEvent(eventName, eventProperties, this.sessionId);
186
- }
187
-
188
544
  public async start() {
189
545
  await this.ensureInitialized();
190
546
  if (!isBrowser) return;
@@ -194,6 +550,9 @@ export class HumanBehaviorTracker {
194
550
  this.flush();
195
551
  }, this.FLUSH_INTERVAL_MS);
196
552
 
553
+ // Enable console tracking
554
+ this.enableConsoleTracking();
555
+
197
556
  // Start recording with redaction enabled
198
557
  rrweb.record({
199
558
  emit: (event) => {
@@ -216,6 +575,12 @@ export class HumanBehaviorTracker {
216
575
  clearInterval(this.flushInterval);
217
576
  this.flushInterval = null;
218
577
  }
578
+
579
+ // Disable console tracking
580
+ this.disableConsoleTracking();
581
+
582
+ // Cleanup navigation tracking
583
+ this.cleanupNavigationTracking();
219
584
  }
220
585
 
221
586
  public async addEvent(event: any) {
@@ -250,7 +615,7 @@ export class HumanBehaviorTracker {
250
615
  this.rejectedEvents = [];
251
616
  this.sessionId = newSessionId;
252
617
  } catch (error) {
253
- console.error('Failed to process rejected events:', error);
618
+ logError('Failed to process rejected events:', error);
254
619
  } finally {
255
620
  this.isProcessingRejectedEvents = false;
256
621
  }
@@ -270,15 +635,22 @@ export class HumanBehaviorTracker {
270
635
  this.queueSizeBytes = 0;
271
636
 
272
637
  if (eventsToProcess.length > 0) {
273
- console.log('Flushing events:', eventsToProcess);
638
+ logDebug('Flushing events:', eventsToProcess);
274
639
  try {
275
640
  await this.api.sendEvents(eventsToProcess, this.sessionId, this.endUserId!);
276
641
  } catch (error: any) {
277
642
  // If we get a 400 error, store events for retry
278
643
  if (error.message?.includes('ERROR: Session already completed')) {
279
- console.log('Session expired, storing events for retry');
644
+ logInfo('Session expired, storing events for retry');
280
645
  this.rejectedEvents.push(...eventsToProcess);
281
646
  this.processRejectedEvents();
647
+ } else if (error.message?.includes('ERR_BLOCKED_BY_CLIENT') ||
648
+ error.message?.includes('Failed to fetch') ||
649
+ error.message?.includes('NetworkError')) {
650
+ // Handle ad blocker or network issues gracefully
651
+ logWarn('Request blocked by ad blocker or network issue, storing events for retry');
652
+ this.rejectedEvents.push(...eventsToProcess);
653
+ // Don't process rejected events immediately to avoid spam
282
654
  } else {
283
655
  throw error;
284
656
  }
@@ -317,7 +689,7 @@ export class HumanBehaviorTracker {
317
689
  public async redact(options?: RedactionOptions): Promise<void> {
318
690
  await this.ensureInitialized();
319
691
  if (!isBrowser) {
320
- console.warn('Redaction is only available in browser environments');
692
+ logWarn('Redaction is only available in browser environments');
321
693
  return;
322
694
  }
323
695
 
@@ -331,7 +703,7 @@ export class HumanBehaviorTracker {
331
703
  */
332
704
  public setRedactedFields(fields: string[]): void {
333
705
  if (!isBrowser) {
334
- console.warn('Redaction is only available in browser environments');
706
+ logWarn('Redaction is only available in browser environments');
335
707
  return;
336
708
  }
337
709
 
@@ -351,6 +723,70 @@ export class HumanBehaviorTracker {
351
723
  public getRedactedFields(): string[] {
352
724
  return this.redactionManager.getSelectedFields();
353
725
  }
726
+
727
+ /**
728
+ * Get the current session ID
729
+ */
730
+ public getSessionId(): string {
731
+ return this.sessionId;
732
+ }
733
+
734
+ /**
735
+ * Get the current URL being tracked
736
+ */
737
+ public getCurrentUrl(): string {
738
+ return this.currentUrl;
739
+ }
740
+
741
+ /**
742
+ * Test if the tracker can reach the ingestion server
743
+ */
744
+ public async testConnection(): Promise<{ success: boolean; error?: string }> {
745
+ try {
746
+ await this.api.init(this.sessionId, this.endUserId);
747
+ return { success: true };
748
+ } catch (error: any) {
749
+ return {
750
+ success: false,
751
+ error: error.message || 'Unknown error'
752
+ };
753
+ }
754
+ }
755
+
756
+ /**
757
+ * Get connection status and recommendations
758
+ */
759
+ public getConnectionStatus(): {
760
+ blocked: boolean;
761
+ recommendations: string[]
762
+ } {
763
+ const recommendations: string[] = [];
764
+ let blocked = false;
765
+
766
+ // Check if we have rejected events (might indicate blocking)
767
+ if (this.rejectedEvents.length > 0) {
768
+ blocked = true;
769
+ recommendations.push('Some requests may be blocked by ad blockers');
770
+ }
771
+
772
+ // Check if connection was blocked during initialization
773
+ if (this._connectionBlocked) {
774
+ blocked = true;
775
+ recommendations.push('Initial connection test failed - ad blocker may be active');
776
+ }
777
+
778
+ // Check if we're in a browser environment
779
+ if (typeof window === 'undefined') {
780
+ recommendations.push('Not running in browser environment');
781
+ }
782
+
783
+ // Check if navigator.sendBeacon is available
784
+ if (typeof navigator.sendBeacon === 'undefined') {
785
+ recommendations.push('sendBeacon not available, using fetch fallback');
786
+ }
787
+
788
+ return { blocked, recommendations };
789
+ }
354
790
  }
355
791
 
356
792
  // Only expose to window object in browser environments