humanbehavior-js 0.2.1 → 0.2.2

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.
Files changed (31) hide show
  1. package/.rollup.cache/Users/hudsonch/Desktop/HumanBehaviorInternship/humanbehavior-js/dist/api.js +312 -0
  2. package/.rollup.cache/Users/hudsonch/Desktop/HumanBehaviorInternship/humanbehavior-js/dist/api.js.map +1 -0
  3. package/.rollup.cache/Users/hudsonch/Desktop/HumanBehaviorInternship/humanbehavior-js/dist/index.js +19 -0
  4. package/.rollup.cache/Users/hudsonch/Desktop/HumanBehaviorInternship/humanbehavior-js/dist/index.js.map +1 -0
  5. package/.rollup.cache/Users/hudsonch/Desktop/HumanBehaviorInternship/humanbehavior-js/dist/react/index.js +222 -0
  6. package/.rollup.cache/Users/hudsonch/Desktop/HumanBehaviorInternship/humanbehavior-js/dist/react/index.js.map +1 -0
  7. package/.rollup.cache/Users/hudsonch/Desktop/HumanBehaviorInternship/humanbehavior-js/dist/redact.js +416 -0
  8. package/.rollup.cache/Users/hudsonch/Desktop/HumanBehaviorInternship/humanbehavior-js/dist/redact.js.map +1 -0
  9. package/.rollup.cache/Users/hudsonch/Desktop/HumanBehaviorInternship/humanbehavior-js/dist/tracker.js +950 -0
  10. package/.rollup.cache/Users/hudsonch/Desktop/HumanBehaviorInternship/humanbehavior-js/dist/tracker.js.map +1 -0
  11. package/.rollup.cache/Users/hudsonch/Desktop/HumanBehaviorInternship/humanbehavior-js/dist/utils/logger.js +117 -0
  12. package/.rollup.cache/Users/hudsonch/Desktop/HumanBehaviorInternship/humanbehavior-js/dist/utils/logger.js.map +1 -0
  13. package/dist/cjs/index.js +2 -6
  14. package/dist/cjs/index.js.map +1 -1
  15. package/dist/cjs/react/index.js +26 -451
  16. package/dist/cjs/react/index.js.map +1 -1
  17. package/dist/esm/index.js +2 -6
  18. package/dist/esm/index.js.map +1 -1
  19. package/dist/esm/react/index.js +5 -430
  20. package/dist/esm/react/index.js.map +1 -1
  21. package/dist/index.min.js +15 -1
  22. package/dist/index.min.js.map +1 -1
  23. package/dist/types/index.d.ts +0 -4
  24. package/dist/types/react/index.d.ts +2 -3
  25. package/package.json +10 -32
  26. package/rollup.config.js +106 -0
  27. package/simple-demo.html +26 -0
  28. package/simple-spa.html +658 -0
  29. package/src/index.ts +2 -2
  30. package/tsconfig.json +24 -0
  31. /package/{dist → .rollup.cache/Users/hudsonch/Desktop/HumanBehaviorInternship/humanbehavior-js/dist}/.tsbuildinfo +0 -0
@@ -0,0 +1,950 @@
1
+ import { __awaiter } from "tslib";
2
+ import * as rrweb from 'rrweb';
3
+ import { v1 as uuidv1 } from 'uuid';
4
+ import { HumanBehaviorAPI } from './api';
5
+ import { RedactionManager } from './redact';
6
+ import { logger, logError, logWarn, logInfo, logDebug } from './utils/logger';
7
+ // Check if we're in a browser environment
8
+ const isBrowser = typeof window !== 'undefined';
9
+ export class HumanBehaviorTracker {
10
+ /**
11
+ * Initialize the HumanBehavior tracker
12
+ * This is the main entry point - call this once per page
13
+ */
14
+ static init(apiKey, options) {
15
+ // Return existing instance if already initialized
16
+ if (isBrowser && window.__humanBehaviorGlobalTracker) {
17
+ logDebug('Tracker already initialized, returning existing instance');
18
+ return window.__humanBehaviorGlobalTracker;
19
+ }
20
+ // Configure logging if specified
21
+ if (options === null || options === void 0 ? void 0 : options.logLevel) {
22
+ this.configureLogging({ level: options.logLevel });
23
+ }
24
+ // Create new tracker instance
25
+ const tracker = new HumanBehaviorTracker(apiKey, options === null || options === void 0 ? void 0 : options.ingestionUrl);
26
+ // Set redacted fields if specified
27
+ if (options === null || options === void 0 ? void 0 : options.redactFields) {
28
+ tracker.setRedactedFields(options.redactFields);
29
+ }
30
+ // Setup automatic tracking if enabled
31
+ if ((options === null || options === void 0 ? void 0 : options.enableAutomaticTracking) !== false) {
32
+ tracker.setupAutomaticTracking(options === null || options === void 0 ? void 0 : options.automaticTrackingOptions);
33
+ }
34
+ // Test connection (non-blocking)
35
+ if (isBrowser) {
36
+ const testUrl = tracker.api['baseUrl'] + '/api/health';
37
+ fetch(testUrl, { method: 'HEAD' })
38
+ .then(() => logDebug('Connection test successful'))
39
+ .catch((error) => {
40
+ logWarn('Connection test failed - ad blocker may be active:', error.message);
41
+ tracker._connectionBlocked = true;
42
+ });
43
+ }
44
+ // Start tracking
45
+ tracker.start();
46
+ return tracker;
47
+ }
48
+ constructor(apiKey, ingestionUrl) {
49
+ this.eventIngestionQueue = [];
50
+ this.queueSizeBytes = 0;
51
+ this.userProperties = {};
52
+ this.isProcessing = false;
53
+ this.flushInterval = null;
54
+ this.FLUSH_INTERVAL_MS = 5000; // Flush every 5 seconds
55
+ this.endUserId = null;
56
+ this.initialized = false;
57
+ this.initializationPromise = null;
58
+ // Console tracking properties
59
+ this.originalConsole = null;
60
+ this.consoleTrackingEnabled = false;
61
+ // Navigation tracking properties
62
+ this.navigationTrackingEnabled = false;
63
+ this.currentUrl = '';
64
+ this.previousUrl = '';
65
+ this.originalPushState = null;
66
+ this.originalReplaceState = null;
67
+ this.navigationListeners = [];
68
+ this._connectionBlocked = false;
69
+ if (!apiKey) {
70
+ throw new Error('Human Behavior API Key is required');
71
+ }
72
+ // Initialize API
73
+ //const defaultIngestionUrl = 'http://3.137.217.33:3000'; // AWS Development Server
74
+ //const defaultIngestionUrl = 'http://ingestion-server-alb-1823866402.us-east-2.elb.amazonaws.com'; // ALB
75
+ const defaultIngestionUrl = 'https://ingest.humanbehavior.co'; // HTTPS ALB
76
+ this.api = new HumanBehaviorAPI({
77
+ apiKey: apiKey,
78
+ ingestionUrl: ingestionUrl || defaultIngestionUrl
79
+ });
80
+ this.apiKey = apiKey;
81
+ this.redactionManager = new RedactionManager();
82
+ // Handle session restoration with improved continuity
83
+ if (isBrowser) {
84
+ const existingSessionId = localStorage.getItem('human_behavior_session_id');
85
+ const lastActivity = localStorage.getItem('human_behavior_last_activity');
86
+ const thirtyMinutesAgo = Date.now() - (30 * 60 * 1000);
87
+ // Check if we have an existing session that's still within the activity window
88
+ if (existingSessionId && lastActivity && parseInt(lastActivity) > thirtyMinutesAgo) {
89
+ this.sessionId = existingSessionId;
90
+ logDebug(`Reusing existing session: ${this.sessionId}`);
91
+ // Update activity timestamp to extend the session window
92
+ localStorage.setItem('human_behavior_last_activity', Date.now().toString());
93
+ }
94
+ else {
95
+ // Clear old session data if it's expired
96
+ if (existingSessionId) {
97
+ logDebug(`Session expired, clearing old session: ${existingSessionId}`);
98
+ localStorage.removeItem('human_behavior_session_id');
99
+ localStorage.removeItem('human_behavior_last_activity');
100
+ }
101
+ this.sessionId = uuidv1();
102
+ logDebug(`Creating new session: ${this.sessionId}`);
103
+ localStorage.setItem('human_behavior_session_id', this.sessionId);
104
+ localStorage.setItem('human_behavior_last_activity', Date.now().toString());
105
+ }
106
+ this.currentUrl = window.location.href;
107
+ window.__humanBehaviorGlobalTracker = this;
108
+ }
109
+ else {
110
+ this.sessionId = uuidv1();
111
+ }
112
+ // Start initialization
113
+ this.initializationPromise = this.init();
114
+ }
115
+ init() {
116
+ return __awaiter(this, void 0, void 0, function* () {
117
+ try {
118
+ const userId = this.getCookie(`human_behavior_end_user_id_${this.apiKey}`);
119
+ logDebug(`Initializing with sessionId: ${this.sessionId}, userId: ${userId}`);
120
+ const { sessionId, endUserId } = yield this.api.init(this.sessionId, userId);
121
+ // Check if server returned a different session ID (for session continuity)
122
+ if (sessionId !== this.sessionId) {
123
+ logDebug(`Server returned different sessionId: ${sessionId} (client had: ${this.sessionId})`);
124
+ this.sessionId = sessionId;
125
+ // Update localStorage with server's session ID for continuity
126
+ if (isBrowser) {
127
+ localStorage.setItem('human_behavior_session_id', this.sessionId);
128
+ }
129
+ }
130
+ this.endUserId = endUserId;
131
+ this.setCookie(`human_behavior_end_user_id_${this.apiKey}`, endUserId, 365);
132
+ // Only setup browser-specific handlers when in browser environment
133
+ if (isBrowser) {
134
+ this.setupPageUnloadHandler();
135
+ this.setupNavigationTracking();
136
+ }
137
+ else {
138
+ logWarn('HumanBehaviorTracker initialized in a non-browser environment. Session tracking is disabled.');
139
+ }
140
+ this.initialized = true;
141
+ logInfo(`HumanBehaviorTracker initialized with sessionId: ${this.sessionId}, endUserId: ${endUserId}`);
142
+ }
143
+ catch (error) {
144
+ logError('Failed to initialize HumanBehaviorTracker:', error);
145
+ throw error;
146
+ }
147
+ });
148
+ }
149
+ ensureInitialized() {
150
+ return __awaiter(this, void 0, void 0, function* () {
151
+ if (!this.initializationPromise) {
152
+ throw new Error('HumanBehaviorTracker initialization failed');
153
+ }
154
+ yield this.initializationPromise;
155
+ });
156
+ }
157
+ /**
158
+ * Setup navigation event tracking for SPA navigation
159
+ */
160
+ setupNavigationTracking() {
161
+ if (!isBrowser || this.navigationTrackingEnabled)
162
+ return;
163
+ this.navigationTrackingEnabled = true;
164
+ logDebug('Setting up navigation tracking');
165
+ // Store original history methods
166
+ this.originalPushState = history.pushState;
167
+ this.originalReplaceState = history.replaceState;
168
+ // Override pushState to capture programmatic navigation
169
+ history.pushState = (...args) => {
170
+ this.previousUrl = this.currentUrl;
171
+ this.currentUrl = window.location.href;
172
+ // Call original method
173
+ this.originalPushState.apply(history, args);
174
+ // Track navigation event
175
+ this.trackNavigationEvent('pushState', this.previousUrl, this.currentUrl);
176
+ };
177
+ // Override replaceState to capture programmatic navigation
178
+ history.replaceState = (...args) => {
179
+ this.previousUrl = this.currentUrl;
180
+ this.currentUrl = window.location.href;
181
+ // Call original method
182
+ this.originalReplaceState.apply(history, args);
183
+ // Track navigation event
184
+ this.trackNavigationEvent('replaceState', this.previousUrl, this.currentUrl);
185
+ };
186
+ // Listen for popstate events (back/forward navigation)
187
+ const popstateListener = () => {
188
+ this.previousUrl = this.currentUrl;
189
+ this.currentUrl = window.location.href;
190
+ this.trackNavigationEvent('popstate', this.previousUrl, this.currentUrl);
191
+ };
192
+ window.addEventListener('popstate', popstateListener);
193
+ this.navigationListeners.push(() => {
194
+ window.removeEventListener('popstate', popstateListener);
195
+ });
196
+ // Listen for hashchange events
197
+ const hashchangeListener = () => {
198
+ this.previousUrl = this.currentUrl;
199
+ this.currentUrl = window.location.href;
200
+ this.trackNavigationEvent('hashchange', this.previousUrl, this.currentUrl);
201
+ };
202
+ window.addEventListener('hashchange', hashchangeListener);
203
+ this.navigationListeners.push(() => {
204
+ window.removeEventListener('hashchange', hashchangeListener);
205
+ });
206
+ // Track initial page load
207
+ this.trackNavigationEvent('pageLoad', '', this.currentUrl);
208
+ }
209
+ /**
210
+ * Track navigation events and send custom events
211
+ */
212
+ trackNavigationEvent(type, fromUrl, toUrl) {
213
+ return __awaiter(this, void 0, void 0, function* () {
214
+ if (!this.initialized)
215
+ return;
216
+ try {
217
+ const navigationData = {
218
+ type: type,
219
+ from: fromUrl,
220
+ to: toUrl,
221
+ timestamp: new Date().toISOString(),
222
+ pathname: window.location.pathname,
223
+ search: window.location.search,
224
+ hash: window.location.hash,
225
+ referrer: document.referrer
226
+ };
227
+ // Add navigation event to the main event stream
228
+ yield this.addEvent({
229
+ type: 5, // Custom event type
230
+ data: {
231
+ payload: Object.assign({ eventType: 'navigation' }, navigationData)
232
+ },
233
+ timestamp: Date.now()
234
+ });
235
+ logDebug(`Navigation tracked: ${type} from ${fromUrl} to ${toUrl}`);
236
+ }
237
+ catch (error) {
238
+ logError('Failed to track navigation event:', error);
239
+ }
240
+ });
241
+ }
242
+ /**
243
+ * Track a page view event (PostHog-style)
244
+ */
245
+ trackPageView(url) {
246
+ return __awaiter(this, void 0, void 0, function* () {
247
+ if (!this.initialized)
248
+ return;
249
+ try {
250
+ const pageViewData = {
251
+ url: url || window.location.href,
252
+ pathname: window.location.pathname,
253
+ search: window.location.search,
254
+ hash: window.location.hash,
255
+ referrer: document.referrer,
256
+ timestamp: new Date().toISOString()
257
+ };
258
+ // Add pageview event to the main event stream
259
+ yield this.addEvent({
260
+ type: 5, // Custom event type
261
+ data: {
262
+ payload: Object.assign({ eventType: 'pageview' }, pageViewData)
263
+ },
264
+ timestamp: Date.now()
265
+ });
266
+ logDebug(`Pageview tracked: ${pageViewData.url}`);
267
+ }
268
+ catch (error) {
269
+ logError('Failed to track pageview event:', error);
270
+ }
271
+ });
272
+ }
273
+ /**
274
+ * Track a custom event (PostHog-style)
275
+ */
276
+ customEvent(eventName, properties) {
277
+ return __awaiter(this, void 0, void 0, function* () {
278
+ var _a, _b, _c, _d, _e;
279
+ if (!this.initialized)
280
+ return;
281
+ try {
282
+ // Send custom event directly to the API
283
+ yield this.api.sendCustomEvent(this.sessionId, eventName, properties);
284
+ logDebug(`Custom event tracked: ${eventName}`, properties);
285
+ }
286
+ catch (error) {
287
+ logError('Failed to track custom event:', error);
288
+ // Handle specific error types - check for any custom event failure
289
+ if (((_a = error.message) === null || _a === void 0 ? void 0 : _a.includes('500')) ||
290
+ ((_b = error.message) === null || _b === void 0 ? void 0 : _b.includes('Internal Server Error')) ||
291
+ ((_c = error.message) === null || _c === void 0 ? void 0 : _c.includes('Failed to send custom event'))) {
292
+ logWarn('Custom event endpoint failed, using fallback');
293
+ }
294
+ else if ((_d = error.message) === null || _d === void 0 ? void 0 : _d.includes('ERR_BLOCKED_BY_CLIENT')) {
295
+ logWarn('Custom event request blocked by ad blocker, using fallback');
296
+ }
297
+ else if ((_e = error.message) === null || _e === void 0 ? void 0 : _e.includes('Failed to fetch')) {
298
+ logWarn('Custom event network error, using fallback');
299
+ }
300
+ // Always try fallback for any custom event error
301
+ try {
302
+ const customEventData = {
303
+ eventName: eventName,
304
+ properties: properties || {},
305
+ timestamp: new Date().toISOString(),
306
+ url: window.location.href,
307
+ pathname: window.location.pathname
308
+ };
309
+ yield this.addEvent({
310
+ type: 5, // Custom event type
311
+ data: {
312
+ payload: Object.assign({ eventType: 'custom' }, customEventData)
313
+ },
314
+ timestamp: Date.now()
315
+ });
316
+ logDebug(`Custom event added to event stream as fallback: ${eventName}`);
317
+ }
318
+ catch (fallbackError) {
319
+ logError('Failed to add custom event to event stream as fallback:', fallbackError);
320
+ }
321
+ }
322
+ });
323
+ }
324
+ /**
325
+ * Setup automatic tracking for buttons, links, and forms
326
+ */
327
+ setupAutomaticTracking(options) {
328
+ if (!isBrowser)
329
+ return;
330
+ const config = {
331
+ trackButtons: (options === null || options === void 0 ? void 0 : options.trackButtons) !== false,
332
+ trackLinks: (options === null || options === void 0 ? void 0 : options.trackLinks) !== false,
333
+ trackForms: (options === null || options === void 0 ? void 0 : options.trackForms) !== false,
334
+ includeText: (options === null || options === void 0 ? void 0 : options.includeText) !== false,
335
+ includeClasses: (options === null || options === void 0 ? void 0 : options.includeClasses) || false
336
+ };
337
+ logDebug('Setting up automatic tracking with config:', config);
338
+ // Setup button tracking
339
+ if (config.trackButtons) {
340
+ this.setupAutomaticButtonTracking(config);
341
+ }
342
+ // Setup link tracking
343
+ if (config.trackLinks) {
344
+ this.setupAutomaticLinkTracking(config);
345
+ }
346
+ // Setup form tracking
347
+ if (config.trackForms) {
348
+ this.setupAutomaticFormTracking(config);
349
+ }
350
+ }
351
+ /**
352
+ * Setup automatic button tracking
353
+ */
354
+ setupAutomaticButtonTracking(config) {
355
+ document.addEventListener('click', (event) => __awaiter(this, void 0, void 0, function* () {
356
+ var _a;
357
+ const target = event.target;
358
+ // Track button clicks
359
+ if (target.tagName === 'BUTTON' || target.closest('button')) {
360
+ const button = target.tagName === 'BUTTON'
361
+ ? target
362
+ : target.closest('button');
363
+ const properties = {
364
+ buttonId: button.id || null,
365
+ buttonType: button.type || 'button',
366
+ page: window.location.pathname,
367
+ timestamp: Date.now()
368
+ };
369
+ if (config.includeText) {
370
+ properties.buttonText = ((_a = button.textContent) === null || _a === void 0 ? void 0 : _a.trim()) || null;
371
+ }
372
+ if (config.includeClasses) {
373
+ properties.buttonClass = button.className || null;
374
+ }
375
+ // Remove null values
376
+ Object.keys(properties).forEach(key => {
377
+ if (properties[key] === null) {
378
+ delete properties[key];
379
+ }
380
+ });
381
+ yield this.customEvent('button_clicked', properties);
382
+ }
383
+ }));
384
+ }
385
+ /**
386
+ * Setup automatic link tracking
387
+ */
388
+ setupAutomaticLinkTracking(config) {
389
+ document.addEventListener('click', (event) => __awaiter(this, void 0, void 0, function* () {
390
+ var _a;
391
+ const target = event.target;
392
+ // Track link clicks
393
+ if (target.tagName === 'A' || target.closest('a')) {
394
+ const link = target.tagName === 'A'
395
+ ? target
396
+ : target.closest('a');
397
+ const properties = {
398
+ linkUrl: link.href || null,
399
+ linkId: link.id || null,
400
+ linkTarget: link.target || null,
401
+ page: window.location.pathname,
402
+ timestamp: Date.now()
403
+ };
404
+ if (config.includeText) {
405
+ properties.linkText = ((_a = link.textContent) === null || _a === void 0 ? void 0 : _a.trim()) || null;
406
+ }
407
+ if (config.includeClasses) {
408
+ properties.linkClass = link.className || null;
409
+ }
410
+ // Remove null values
411
+ Object.keys(properties).forEach(key => {
412
+ if (properties[key] === null) {
413
+ delete properties[key];
414
+ }
415
+ });
416
+ yield this.customEvent('link_clicked', properties);
417
+ }
418
+ }));
419
+ }
420
+ /**
421
+ * Setup automatic form tracking
422
+ */
423
+ setupAutomaticFormTracking(config) {
424
+ document.addEventListener('submit', (event) => __awaiter(this, void 0, void 0, function* () {
425
+ const form = event.target;
426
+ const formData = new FormData(form);
427
+ const properties = {
428
+ formId: form.id || null,
429
+ formAction: form.action || null,
430
+ formMethod: form.method || 'get',
431
+ fields: Array.from(formData.keys()),
432
+ page: window.location.pathname,
433
+ timestamp: Date.now()
434
+ };
435
+ if (config.includeClasses) {
436
+ properties.formClass = form.className || null;
437
+ }
438
+ // Remove null values
439
+ Object.keys(properties).forEach(key => {
440
+ if (properties[key] === null) {
441
+ delete properties[key];
442
+ }
443
+ });
444
+ yield this.customEvent('form_submitted', properties);
445
+ }));
446
+ }
447
+ /**
448
+ * Cleanup navigation tracking
449
+ */
450
+ cleanupNavigationTracking() {
451
+ if (!this.navigationTrackingEnabled)
452
+ return;
453
+ // Restore original history methods
454
+ if (this.originalPushState) {
455
+ history.pushState = this.originalPushState;
456
+ }
457
+ if (this.originalReplaceState) {
458
+ history.replaceState = this.originalReplaceState;
459
+ }
460
+ // Remove event listeners
461
+ this.navigationListeners.forEach(cleanup => cleanup());
462
+ this.navigationListeners = [];
463
+ this.navigationTrackingEnabled = false;
464
+ logDebug('Navigation tracking cleaned up');
465
+ }
466
+ static logToStorage(message) {
467
+ logInfo(message);
468
+ }
469
+ /**
470
+ * Configure logging behavior for the SDK
471
+ * @param config Logger configuration options
472
+ */
473
+ static configureLogging(config) {
474
+ const levelMap = {
475
+ 'none': 0,
476
+ 'error': 1,
477
+ 'warn': 2,
478
+ 'info': 3,
479
+ 'debug': 4
480
+ };
481
+ logger.setConfig({
482
+ level: levelMap[config.level || 'error'],
483
+ enableConsole: config.enableConsole !== false,
484
+ enableStorage: config.enableStorage || false
485
+ });
486
+ }
487
+ /**
488
+ * Enable console event tracking
489
+ */
490
+ enableConsoleTracking() {
491
+ if (!isBrowser || this.consoleTrackingEnabled)
492
+ return;
493
+ // Store original console methods
494
+ this.originalConsole = {
495
+ log: console.log,
496
+ warn: console.warn,
497
+ error: console.error
498
+ };
499
+ // Override console methods to capture ALL console output (including logger output)
500
+ console.log = (...args) => {
501
+ this.trackConsoleEvent('log', args);
502
+ this.originalConsole.log(...args);
503
+ };
504
+ console.warn = (...args) => {
505
+ this.trackConsoleEvent('warn', args);
506
+ this.originalConsole.warn(...args);
507
+ };
508
+ console.error = (...args) => {
509
+ this.trackConsoleEvent('error', args);
510
+ this.originalConsole.error(...args);
511
+ };
512
+ this.consoleTrackingEnabled = true;
513
+ logDebug('Console tracking enabled');
514
+ }
515
+ /**
516
+ * Disable console event tracking
517
+ */
518
+ disableConsoleTracking() {
519
+ if (!isBrowser || !this.consoleTrackingEnabled)
520
+ return;
521
+ // Restore original console methods
522
+ if (this.originalConsole) {
523
+ console.log = this.originalConsole.log;
524
+ console.warn = this.originalConsole.warn;
525
+ console.error = this.originalConsole.error;
526
+ }
527
+ this.consoleTrackingEnabled = false;
528
+ logDebug('Console tracking disabled');
529
+ }
530
+ trackConsoleEvent(level, args) {
531
+ if (!this.initialized)
532
+ return;
533
+ try {
534
+ const consoleData = {
535
+ level: level,
536
+ message: args.map(arg => typeof arg === 'object' ? JSON.stringify(arg) : String(arg)).join(' '),
537
+ timestamp: new Date().toISOString(),
538
+ url: window.location.href
539
+ };
540
+ // Add console event to the main event stream
541
+ this.addEvent({
542
+ type: 5, // Custom event type
543
+ data: {
544
+ payload: Object.assign({ eventType: 'console' }, consoleData)
545
+ },
546
+ timestamp: Date.now()
547
+ }).catch(error => {
548
+ logError('Failed to track console event:', error);
549
+ });
550
+ }
551
+ catch (error) {
552
+ logError('Error in trackConsoleEvent:', error);
553
+ }
554
+ }
555
+ setupPageUnloadHandler() {
556
+ if (!isBrowser)
557
+ return;
558
+ logDebug('Setting up page unload handler');
559
+ // Handle visibility changes for sending events
560
+ window.addEventListener('visibilitychange', () => {
561
+ // Only send events when page becomes hidden
562
+ if (document.visibilityState === 'hidden') {
563
+ logDebug('Page hidden - sending pending events');
564
+ this.api.sendBeaconEvents(this.eventIngestionQueue, this.sessionId);
565
+ }
566
+ });
567
+ // Handle actual page unload/close
568
+ window.addEventListener('beforeunload', () => {
569
+ // Send final events
570
+ this.api.sendBeaconEvents(this.eventIngestionQueue, this.sessionId);
571
+ });
572
+ // Update activity timestamp on user interaction (not on page load)
573
+ const updateActivity = () => {
574
+ localStorage.setItem('human_behavior_last_activity', Date.now().toString());
575
+ };
576
+ // Listen for user interactions to update activity timestamp
577
+ window.addEventListener('click', updateActivity);
578
+ window.addEventListener('keydown', updateActivity);
579
+ window.addEventListener('scroll', updateActivity);
580
+ window.addEventListener('mousemove', updateActivity);
581
+ }
582
+ viewLogs() {
583
+ try {
584
+ const logs = logger.getLogs();
585
+ console.log('HumanBehavior Logs:', logs);
586
+ logger.clearLogs(); // Clear logs after viewing
587
+ }
588
+ catch (e) {
589
+ logError('Failed to read logs:', e);
590
+ }
591
+ }
592
+ addUserInfo(_a) {
593
+ return __awaiter(this, arguments, void 0, function* ({ userId, userProperties }) {
594
+ yield this.ensureInitialized();
595
+ if (!this.endUserId) {
596
+ throw new Error('Cannot add user info before tracker initialization');
597
+ }
598
+ this.userProperties = userProperties;
599
+ yield this.api.sendUserData(this.endUserId, userProperties, this.sessionId);
600
+ if (userId) {
601
+ this.endUserId = userId;
602
+ }
603
+ });
604
+ }
605
+ /**
606
+ * Add user identification information to the tracker
607
+ * @param authFields Array of field names to check for existing users (e.g., ['email', 'phoneNumber'])
608
+ */
609
+ auth(id) {
610
+ return __awaiter(this, void 0, void 0, function* () {
611
+ yield this.ensureInitialized();
612
+ if (!this.endUserId) {
613
+ throw new Error('Cannot authenticate before tracker initialization');
614
+ }
615
+ const response = yield this.api.sendUserAuth(this.endUserId, { id }, this.sessionId, ["id"]);
616
+ if (response && response.userId && response.userId !== this.endUserId) {
617
+ this.endUserId = response.userId;
618
+ this.setCookie(`human_behavior_end_user_id_${this.apiKey}`, response.userId, 365);
619
+ }
620
+ });
621
+ }
622
+ start() {
623
+ return __awaiter(this, void 0, void 0, function* () {
624
+ yield this.ensureInitialized();
625
+ if (!isBrowser)
626
+ return;
627
+ // Start periodic flushing
628
+ this.flushInterval = window.setInterval(() => {
629
+ this.flush();
630
+ }, this.FLUSH_INTERVAL_MS);
631
+ // Enable console tracking
632
+ this.enableConsoleTracking();
633
+ // Start recording with redaction enabled
634
+ rrweb.record({
635
+ emit: (event) => {
636
+ this.addEvent(event);
637
+ },
638
+ inlineStylesheet: true,
639
+ recordCanvas: true,
640
+ collectFonts: true,
641
+ inlineImages: true,
642
+ blockClass: 'rr-block',
643
+ ignoreClass: 'rr-ignore',
644
+ maskTextClass: 'rr-ignore'
645
+ });
646
+ });
647
+ }
648
+ stop() {
649
+ return __awaiter(this, void 0, void 0, function* () {
650
+ yield this.ensureInitialized();
651
+ if (!isBrowser)
652
+ return;
653
+ if (this.flushInterval) {
654
+ clearInterval(this.flushInterval);
655
+ this.flushInterval = null;
656
+ }
657
+ // Disable console tracking
658
+ this.disableConsoleTracking();
659
+ // Cleanup navigation tracking
660
+ this.cleanupNavigationTracking();
661
+ });
662
+ }
663
+ addEvent(event) {
664
+ return __awaiter(this, void 0, void 0, function* () {
665
+ yield this.ensureInitialized();
666
+ // Process event through redaction manager if active
667
+ const processedEvent = this.redactionManager.processEvent(event);
668
+ const eventSize = new TextEncoder().encode(JSON.stringify(processedEvent)).length;
669
+ this.eventIngestionQueue.push(processedEvent);
670
+ this.queueSizeBytes += eventSize;
671
+ });
672
+ }
673
+ flush() {
674
+ return __awaiter(this, void 0, void 0, function* () {
675
+ var _a, _b, _c, _d, _e, _f;
676
+ // Prevent concurrent flushes
677
+ if (this.isProcessing || !this.initialized) {
678
+ return;
679
+ }
680
+ this.isProcessing = true;
681
+ try {
682
+ // Swap the current queue with an empty one atomically
683
+ const eventsToProcess = this.eventIngestionQueue;
684
+ this.eventIngestionQueue = [];
685
+ this.queueSizeBytes = 0;
686
+ if (eventsToProcess.length > 0) {
687
+ logDebug('Flushing events:', eventsToProcess);
688
+ try {
689
+ // Use chunked sending to handle large payloads
690
+ yield this.api.sendEventsChunked(eventsToProcess, this.sessionId, this.endUserId);
691
+ }
692
+ catch (error) {
693
+ // Handle specific error types with graceful degradation
694
+ if ((_a = error.message) === null || _a === void 0 ? void 0 : _a.includes('ERROR: Session already completed')) {
695
+ logWarn('Session expired, events will be lost');
696
+ }
697
+ else if (((_b = error.message) === null || _b === void 0 ? void 0 : _b.includes('413')) || ((_c = error.message) === null || _c === void 0 ? void 0 : _c.includes('Content Too Large'))) {
698
+ logWarn('Payload too large, events will be lost');
699
+ }
700
+ else if (((_d = error.message) === null || _d === void 0 ? void 0 : _d.includes('ERR_BLOCKED_BY_CLIENT')) ||
701
+ ((_e = error.message) === null || _e === void 0 ? void 0 : _e.includes('Failed to fetch')) ||
702
+ ((_f = error.message) === null || _f === void 0 ? void 0 : _f.includes('NetworkError'))) {
703
+ logWarn('Request blocked by ad blocker or network issue, events will be lost');
704
+ }
705
+ else {
706
+ throw error;
707
+ }
708
+ }
709
+ }
710
+ }
711
+ finally {
712
+ this.isProcessing = false;
713
+ }
714
+ });
715
+ }
716
+ // Add helper methods for cookie management with localStorage fallback
717
+ setCookie(name, value, daysToExpire) {
718
+ if (!isBrowser)
719
+ return;
720
+ try {
721
+ // Try to set cookie first
722
+ const date = new Date();
723
+ date.setTime(date.getTime() + (daysToExpire * 24 * 60 * 60 * 1000));
724
+ const expires = `expires=${date.toUTCString()}`;
725
+ document.cookie = `${name}=${value};${expires};path=/;SameSite=Lax`;
726
+ // Also store in localStorage as backup
727
+ localStorage.setItem(name, value);
728
+ logDebug(`Set cookie and localStorage: ${name}`);
729
+ }
730
+ catch (error) {
731
+ // If cookie fails, use localStorage only
732
+ try {
733
+ localStorage.setItem(name, value);
734
+ logDebug(`Cookie blocked, using localStorage: ${name}`);
735
+ }
736
+ catch (localStorageError) {
737
+ logError('Failed to store user ID in both cookie and localStorage:', localStorageError);
738
+ }
739
+ }
740
+ }
741
+ getCookie(name) {
742
+ if (!isBrowser)
743
+ return null;
744
+ try {
745
+ // Try to get from cookie first
746
+ const nameEQ = name + "=";
747
+ const ca = document.cookie.split(';');
748
+ for (let i = 0; i < ca.length; i++) {
749
+ let c = ca[i];
750
+ while (c.charAt(0) === ' ')
751
+ c = c.substring(1, c.length);
752
+ if (c.indexOf(nameEQ) === 0) {
753
+ const cookieValue = c.substring(nameEQ.length, c.length);
754
+ logDebug(`Found cookie: ${name}`);
755
+ return cookieValue;
756
+ }
757
+ }
758
+ // If cookie not found, try localStorage
759
+ const localStorageValue = localStorage.getItem(name);
760
+ if (localStorageValue) {
761
+ logDebug(`Cookie not found, using localStorage: ${name}`);
762
+ return localStorageValue;
763
+ }
764
+ return null;
765
+ }
766
+ catch (error) {
767
+ // If cookie access fails, try localStorage
768
+ try {
769
+ const localStorageValue = localStorage.getItem(name);
770
+ if (localStorageValue) {
771
+ logDebug(`Cookie access failed, using localStorage: ${name}`);
772
+ return localStorageValue;
773
+ }
774
+ }
775
+ catch (localStorageError) {
776
+ logError('Failed to access both cookie and localStorage:', localStorageError);
777
+ }
778
+ return null;
779
+ }
780
+ }
781
+ /**
782
+ * Delete a cookie by setting its expiration date to the past
783
+ * @param name The name of the cookie to delete
784
+ */
785
+ deleteCookie(name) {
786
+ if (!isBrowser)
787
+ return;
788
+ try {
789
+ // Delete cookie by setting expiration to past
790
+ document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; SameSite=Lax`;
791
+ logDebug(`Deleted cookie: ${name}`);
792
+ }
793
+ catch (error) {
794
+ logError(`Failed to delete cookie: ${name}`, error);
795
+ }
796
+ // Also remove from localStorage
797
+ try {
798
+ localStorage.removeItem(name);
799
+ logDebug(`Removed from localStorage: ${name}`);
800
+ }
801
+ catch (error) {
802
+ logError(`Failed to remove from localStorage: ${name}`, error);
803
+ }
804
+ }
805
+ /**
806
+ * Log out the current user by clearing all user-related data
807
+ * This will delete the user ID cookie, clear localStorage, and reset user properties
808
+ */
809
+ logout() {
810
+ if (!isBrowser)
811
+ return;
812
+ try {
813
+ // Clear user ID cookie and localStorage
814
+ const userIdCookieName = `human_behavior_end_user_id_${this.apiKey}`;
815
+ this.deleteCookie(userIdCookieName);
816
+ // Clear session data from localStorage
817
+ localStorage.removeItem('human_behavior_session_id');
818
+ localStorage.removeItem('human_behavior_last_activity');
819
+ // Reset user-related properties
820
+ this.endUserId = null;
821
+ this.userProperties = {};
822
+ logInfo('User logged out - cleared all user data and cookies');
823
+ }
824
+ catch (error) {
825
+ logError('Error during logout:', error);
826
+ }
827
+ }
828
+ /**
829
+ * Start redaction functionality for sensitive input fields
830
+ * @param options Optional configuration for redaction behavior
831
+ */
832
+ redact(options) {
833
+ return __awaiter(this, void 0, void 0, function* () {
834
+ yield this.ensureInitialized();
835
+ if (!isBrowser) {
836
+ logWarn('Redaction is only available in browser environments');
837
+ return;
838
+ }
839
+ // Create a new redaction manager with the provided options
840
+ this.redactionManager = new RedactionManager(options);
841
+ });
842
+ }
843
+ /**
844
+ * Set specific fields to be redacted during session recording
845
+ * @param fields Array of CSS selectors for fields to redact (e.g., ['input[type="password"]', '#email-field'])
846
+ */
847
+ setRedactedFields(fields) {
848
+ if (!isBrowser) {
849
+ logWarn('Redaction is only available in browser environments');
850
+ return;
851
+ }
852
+ this.redactionManager.setFieldsToRedact(fields);
853
+ }
854
+ /**
855
+ * Check if redaction is currently active
856
+ */
857
+ isRedactionActive() {
858
+ return this.redactionManager.isActive();
859
+ }
860
+ /**
861
+ * Get the currently selected fields for redaction
862
+ */
863
+ getRedactedFields() {
864
+ return this.redactionManager.getSelectedFields();
865
+ }
866
+ /**
867
+ * Get the current session ID
868
+ */
869
+ getSessionId() {
870
+ return this.sessionId;
871
+ }
872
+ /**
873
+ * Get the current URL being tracked
874
+ */
875
+ getCurrentUrl() {
876
+ return this.currentUrl;
877
+ }
878
+ /**
879
+ * Test if the tracker can reach the ingestion server
880
+ */
881
+ testConnection() {
882
+ return __awaiter(this, void 0, void 0, function* () {
883
+ try {
884
+ yield this.api.init(this.sessionId, this.endUserId);
885
+ return { success: true };
886
+ }
887
+ catch (error) {
888
+ return {
889
+ success: false,
890
+ error: error.message || 'Unknown error'
891
+ };
892
+ }
893
+ });
894
+ }
895
+ /**
896
+ * Get connection status and recommendations
897
+ */
898
+ getConnectionStatus() {
899
+ const recommendations = [];
900
+ let blocked = false;
901
+ // Check if we have queued events (might indicate blocking)
902
+ if (this.eventIngestionQueue.length > 0) {
903
+ blocked = true;
904
+ recommendations.push('Some requests may be blocked by ad blockers');
905
+ }
906
+ // Check if connection was blocked during initialization
907
+ if (this._connectionBlocked) {
908
+ blocked = true;
909
+ recommendations.push('Initial connection test failed - ad blocker may be active');
910
+ }
911
+ // Check if we're in a browser environment
912
+ if (typeof window === 'undefined') {
913
+ recommendations.push('Not running in browser environment');
914
+ }
915
+ // Check if navigator.sendBeacon is available
916
+ if (typeof navigator.sendBeacon === 'undefined') {
917
+ recommendations.push('sendBeacon not available, using fetch fallback');
918
+ }
919
+ return { blocked, recommendations };
920
+ }
921
+ /**
922
+ * Check if the current user is a preexisting user
923
+ * Returns true if the user has an existing endUserId cookie from a previous session
924
+ */
925
+ isPreexistingUser() {
926
+ if (!isBrowser) {
927
+ return false;
928
+ }
929
+ // Check if there's an existing endUserId cookie for this API key
930
+ const existingEndUserId = this.getCookie(`human_behavior_end_user_id_${this.apiKey}`);
931
+ return existingEndUserId !== null && existingEndUserId !== this.endUserId;
932
+ }
933
+ /**
934
+ * Get user information including whether they are preexisting
935
+ */
936
+ getUserInfo() {
937
+ return {
938
+ endUserId: this.endUserId,
939
+ sessionId: this.sessionId,
940
+ isPreexistingUser: this.isPreexistingUser(),
941
+ initialized: this.initialized
942
+ };
943
+ }
944
+ }
945
+ // Only expose to window object in browser environments
946
+ if (isBrowser) {
947
+ window.HumanBehaviorTracker = HumanBehaviorTracker;
948
+ }
949
+ export default HumanBehaviorTracker;
950
+ //# sourceMappingURL=tracker.js.map