humanbehavior-js 0.0.9 → 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/dist/cjs/index.js +345 -184
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/react/index.js +51 -77
- package/dist/cjs/react/index.js.map +1 -1
- package/dist/esm/index.js +345 -184
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/react/index.js +48 -77
- 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 -12
- package/dist/types/react/index.d.ts +8 -9
- package/package.json +2 -1
- package/readme.md +127 -105
- package/simple-spa.html +544 -0
- package/src/api.ts +4 -134
- package/src/react/index.tsx +61 -28
- package/src/tracker.ts +404 -87
package/src/tracker.ts
CHANGED
|
@@ -11,6 +11,7 @@ const isBrowser = typeof window !== 'undefined';
|
|
|
11
11
|
declare global {
|
|
12
12
|
interface Window {
|
|
13
13
|
HumanBehaviorTracker: typeof HumanBehaviorTracker;
|
|
14
|
+
__humanBehaviorGlobalTracker?: HumanBehaviorTracker;
|
|
14
15
|
}
|
|
15
16
|
}
|
|
16
17
|
|
|
@@ -20,17 +21,17 @@ export class HumanBehaviorTracker {
|
|
|
20
21
|
private rejectedEvents: any[] = [];
|
|
21
22
|
private isProcessingRejectedEvents: boolean = false;
|
|
22
23
|
|
|
23
|
-
private sessionId
|
|
24
|
+
private sessionId!: string;
|
|
24
25
|
private userProperties: Record<string, any> = {};
|
|
25
26
|
private isProcessing: boolean = false;
|
|
26
27
|
private flushInterval: number | null = null;
|
|
27
28
|
private readonly FLUSH_INTERVAL_MS = 5000; // Flush every 5 seconds
|
|
28
|
-
private api
|
|
29
|
+
private api!: HumanBehaviorAPI;
|
|
29
30
|
private endUserId: string | null = null;
|
|
30
|
-
private apiKey
|
|
31
|
+
private apiKey!: string;
|
|
31
32
|
private initialized: boolean = false;
|
|
32
33
|
public initializationPromise: Promise<void> | null = null;
|
|
33
|
-
private redactionManager
|
|
34
|
+
private redactionManager!: RedactionManager;
|
|
34
35
|
|
|
35
36
|
// Console tracking properties
|
|
36
37
|
private originalConsole: {
|
|
@@ -38,33 +39,69 @@ export class HumanBehaviorTracker {
|
|
|
38
39
|
warn: typeof console.warn;
|
|
39
40
|
error: typeof console.error;
|
|
40
41
|
} | 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
42
|
private consoleTrackingEnabled: boolean = false;
|
|
48
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
|
+
}
|
|
97
|
+
|
|
49
98
|
constructor(apiKey: string | undefined, ingestionUrl?: string) {
|
|
50
99
|
if (!apiKey) {
|
|
51
100
|
throw new Error('Human Behavior API Key is required');
|
|
52
101
|
}
|
|
53
102
|
|
|
54
|
-
//
|
|
55
|
-
|
|
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
|
-
|
|
103
|
+
// Initialize API
|
|
104
|
+
const defaultIngestionUrl = 'http://3.137.217.33:3000'; // AWS Development Server
|
|
68
105
|
this.api = new HumanBehaviorAPI({
|
|
69
106
|
apiKey: apiKey,
|
|
70
107
|
ingestionUrl: ingestionUrl || defaultIngestionUrl
|
|
@@ -72,43 +109,62 @@ export class HumanBehaviorTracker {
|
|
|
72
109
|
this.apiKey = apiKey;
|
|
73
110
|
this.redactionManager = new RedactionManager();
|
|
74
111
|
|
|
75
|
-
//
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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();
|
|
87
131
|
}
|
|
88
132
|
|
|
89
|
-
// Start initialization
|
|
133
|
+
// Start initialization
|
|
90
134
|
this.initializationPromise = this.init();
|
|
91
135
|
}
|
|
92
136
|
|
|
93
137
|
private async init(): Promise<void> {
|
|
94
138
|
try {
|
|
95
139
|
const userId = this.getCookie(`human_behavior_end_user_id_${this.apiKey}`);
|
|
140
|
+
logDebug(`Initializing with sessionId: ${this.sessionId}, userId: ${userId}`);
|
|
141
|
+
|
|
96
142
|
const { sessionId, endUserId } = await this.api.init(this.sessionId, userId);
|
|
97
|
-
|
|
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
|
+
|
|
98
154
|
this.endUserId = endUserId;
|
|
99
155
|
this.setCookie(`human_behavior_end_user_id_${this.apiKey}`, endUserId, 365);
|
|
100
156
|
|
|
101
157
|
// Only setup browser-specific handlers when in browser environment
|
|
102
158
|
if (isBrowser) {
|
|
103
159
|
this.setupPageUnloadHandler();
|
|
104
|
-
this.
|
|
160
|
+
this.setupNavigationTracking();
|
|
105
161
|
this.processRejectedEvents();
|
|
106
162
|
} else {
|
|
107
163
|
logWarn('HumanBehaviorTracker initialized in a non-browser environment. Session tracking is disabled.');
|
|
108
164
|
}
|
|
109
165
|
|
|
110
166
|
this.initialized = true;
|
|
111
|
-
logInfo(
|
|
167
|
+
logInfo(`HumanBehaviorTracker initialized with sessionId: ${this.sessionId}, endUserId: ${endUserId}`);
|
|
112
168
|
} catch (error) {
|
|
113
169
|
logError('Failed to initialize HumanBehaviorTracker:', error);
|
|
114
170
|
throw error;
|
|
@@ -122,6 +178,196 @@ export class HumanBehaviorTracker {
|
|
|
122
178
|
await this.initializationPromise;
|
|
123
179
|
}
|
|
124
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
|
+
|
|
125
371
|
public static logToStorage(message: string) {
|
|
126
372
|
logInfo(message);
|
|
127
373
|
}
|
|
@@ -159,13 +405,7 @@ export class HumanBehaviorTracker {
|
|
|
159
405
|
error: console.error
|
|
160
406
|
};
|
|
161
407
|
|
|
162
|
-
|
|
163
|
-
this.originalLogger = {
|
|
164
|
-
error: logError,
|
|
165
|
-
warn: logWarn,
|
|
166
|
-
info: logInfo,
|
|
167
|
-
debug: logDebug
|
|
168
|
-
};
|
|
408
|
+
|
|
169
409
|
|
|
170
410
|
// Override console methods to capture ALL console output (including logger output)
|
|
171
411
|
console.log = (...args) => {
|
|
@@ -184,50 +424,55 @@ export class HumanBehaviorTracker {
|
|
|
184
424
|
};
|
|
185
425
|
|
|
186
426
|
this.consoleTrackingEnabled = true;
|
|
187
|
-
|
|
427
|
+
logDebug('Console tracking enabled');
|
|
188
428
|
}
|
|
189
429
|
|
|
190
430
|
/**
|
|
191
431
|
* Disable console event tracking
|
|
192
432
|
*/
|
|
193
433
|
public disableConsoleTracking(): void {
|
|
194
|
-
if (!isBrowser || !this.consoleTrackingEnabled
|
|
434
|
+
if (!isBrowser || !this.consoleTrackingEnabled) return;
|
|
195
435
|
|
|
196
436
|
// Restore original console methods
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
437
|
+
if (this.originalConsole) {
|
|
438
|
+
console.log = this.originalConsole.log;
|
|
439
|
+
console.warn = this.originalConsole.warn;
|
|
440
|
+
console.error = this.originalConsole.error;
|
|
441
|
+
}
|
|
200
442
|
|
|
201
443
|
this.consoleTrackingEnabled = false;
|
|
202
|
-
|
|
203
|
-
this.originalLogger = null;
|
|
444
|
+
logDebug('Console tracking disabled');
|
|
204
445
|
}
|
|
205
446
|
|
|
206
|
-
/**
|
|
207
|
-
* Track console events
|
|
208
|
-
*/
|
|
209
447
|
private trackConsoleEvent(level: 'log' | 'warn' | 'error', args: any[]): void {
|
|
210
448
|
if (!this.initialized) return;
|
|
211
449
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
450
|
+
try {
|
|
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);
|
|
475
|
+
}
|
|
231
476
|
}
|
|
232
477
|
|
|
233
478
|
private setupPageUnloadHandler() {
|
|
@@ -246,17 +491,20 @@ export class HumanBehaviorTracker {
|
|
|
246
491
|
|
|
247
492
|
// Handle actual page unload/close
|
|
248
493
|
window.addEventListener('beforeunload', () => {
|
|
249
|
-
// Update last activity time
|
|
250
|
-
localStorage.setItem('human_behavior_last_activity', Date.now().toString());
|
|
251
|
-
|
|
252
494
|
// Send final events
|
|
253
495
|
this.api.sendBeaconEvents(this.eventIngestionQueue, this.sessionId);
|
|
254
496
|
});
|
|
255
497
|
|
|
256
|
-
// Update activity timestamp
|
|
257
|
-
|
|
498
|
+
// Update activity timestamp on user interaction (not on page load)
|
|
499
|
+
const updateActivity = () => {
|
|
258
500
|
localStorage.setItem('human_behavior_last_activity', Date.now().toString());
|
|
259
|
-
}
|
|
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);
|
|
260
508
|
}
|
|
261
509
|
|
|
262
510
|
public viewLogs() {
|
|
@@ -293,11 +541,6 @@ export class HumanBehaviorTracker {
|
|
|
293
541
|
await this.api.sendUserAuth(this.endUserId, this.userProperties, this.sessionId, authFields);
|
|
294
542
|
}
|
|
295
543
|
|
|
296
|
-
public async customEvent(eventName: string, eventProperties: Record<string, any> = {}) {
|
|
297
|
-
await this.ensureInitialized();
|
|
298
|
-
this.api.sendBeaconCustomEvent(eventName, eventProperties, this.sessionId);
|
|
299
|
-
}
|
|
300
|
-
|
|
301
544
|
public async start() {
|
|
302
545
|
await this.ensureInitialized();
|
|
303
546
|
if (!isBrowser) return;
|
|
@@ -335,6 +578,9 @@ export class HumanBehaviorTracker {
|
|
|
335
578
|
|
|
336
579
|
// Disable console tracking
|
|
337
580
|
this.disableConsoleTracking();
|
|
581
|
+
|
|
582
|
+
// Cleanup navigation tracking
|
|
583
|
+
this.cleanupNavigationTracking();
|
|
338
584
|
}
|
|
339
585
|
|
|
340
586
|
public async addEvent(event: any) {
|
|
@@ -398,6 +644,13 @@ export class HumanBehaviorTracker {
|
|
|
398
644
|
logInfo('Session expired, storing events for retry');
|
|
399
645
|
this.rejectedEvents.push(...eventsToProcess);
|
|
400
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
|
|
401
654
|
} else {
|
|
402
655
|
throw error;
|
|
403
656
|
}
|
|
@@ -470,6 +723,70 @@ export class HumanBehaviorTracker {
|
|
|
470
723
|
public getRedactedFields(): string[] {
|
|
471
724
|
return this.redactionManager.getSelectedFields();
|
|
472
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
|
+
}
|
|
473
790
|
}
|
|
474
791
|
|
|
475
792
|
// Only expose to window object in browser environments
|