humanbehavior-js 0.0.9 → 0.1.1
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 +441 -201
- 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 +441 -201
- 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 +75 -13
- package/dist/types/react/index.d.ts +8 -9
- package/package.json +2 -1
- package/readme.md +127 -105
- package/simple-spa.html +594 -0
- package/src/api.ts +2 -142
- package/src/react/index.tsx +61 -28
- package/src/tracker.ts +507 -102
package/dist/esm/index.js
CHANGED
|
@@ -4347,6 +4347,7 @@ class HumanBehaviorAPI {
|
|
|
4347
4347
|
if (!response.ok) {
|
|
4348
4348
|
throw new Error(`Failed to authenticate user: ${response.statusText} with API key: ${this.apiKey}`);
|
|
4349
4349
|
}
|
|
4350
|
+
// Returns: { success: true, message: '...', userId: '...' }
|
|
4350
4351
|
return yield response.json();
|
|
4351
4352
|
}
|
|
4352
4353
|
catch (error) {
|
|
@@ -4355,128 +4356,13 @@ class HumanBehaviorAPI {
|
|
|
4355
4356
|
}
|
|
4356
4357
|
});
|
|
4357
4358
|
}
|
|
4358
|
-
|
|
4359
|
-
return __awaiter$1(this, void 0, void 0, function* () {
|
|
4360
|
-
const response = yield fetch(`${this.baseUrl}/api/ingestion/sessionComplete`, {
|
|
4361
|
-
method: 'POST',
|
|
4362
|
-
headers: {
|
|
4363
|
-
'Content-Type': 'application/json',
|
|
4364
|
-
'Authorization': `Bearer ${this.apiKey}`
|
|
4365
|
-
},
|
|
4366
|
-
body: JSON.stringify({ sessionId })
|
|
4367
|
-
});
|
|
4368
|
-
if (!response.ok) {
|
|
4369
|
-
throw new Error(`Failed to send session complete: ${response.statusText}`);
|
|
4370
|
-
}
|
|
4371
|
-
});
|
|
4372
|
-
}
|
|
4373
|
-
sendCustomEvent(eventName, eventProperties, sessionId) {
|
|
4374
|
-
return __awaiter$1(this, void 0, void 0, function* () {
|
|
4375
|
-
const maxRetries = 3;
|
|
4376
|
-
let retryCount = 0;
|
|
4377
|
-
while (retryCount < maxRetries) {
|
|
4378
|
-
try {
|
|
4379
|
-
const response = yield fetch(`${this.baseUrl}/api/ingestion/customEvent`, {
|
|
4380
|
-
method: 'POST',
|
|
4381
|
-
headers: {
|
|
4382
|
-
'Content-Type': 'application/json',
|
|
4383
|
-
'Authorization': `Bearer ${this.apiKey}`
|
|
4384
|
-
},
|
|
4385
|
-
body: JSON.stringify({
|
|
4386
|
-
name: eventName,
|
|
4387
|
-
properties: eventProperties,
|
|
4388
|
-
sessionId: sessionId,
|
|
4389
|
-
timestamp: new Date().toISOString()
|
|
4390
|
-
})
|
|
4391
|
-
});
|
|
4392
|
-
if (!response.ok) {
|
|
4393
|
-
throw new Error(`Failed to send custom event: ${response.statusText}`);
|
|
4394
|
-
}
|
|
4395
|
-
return yield response.json();
|
|
4396
|
-
}
|
|
4397
|
-
catch (error) {
|
|
4398
|
-
retryCount++;
|
|
4399
|
-
if (retryCount === maxRetries) {
|
|
4400
|
-
logError('Error sending custom event after max retries:', error);
|
|
4401
|
-
throw error;
|
|
4402
|
-
}
|
|
4403
|
-
// Exponential backoff
|
|
4404
|
-
yield new Promise(resolve => setTimeout(resolve, Math.pow(2, retryCount) * 1000));
|
|
4405
|
-
}
|
|
4406
|
-
}
|
|
4407
|
-
});
|
|
4408
|
-
}
|
|
4409
|
-
sendCustomEvents(events, sessionId) {
|
|
4410
|
-
return __awaiter$1(this, void 0, void 0, function* () {
|
|
4411
|
-
const maxRetries = 3;
|
|
4412
|
-
let retryCount = 0;
|
|
4413
|
-
while (retryCount < maxRetries) {
|
|
4414
|
-
try {
|
|
4415
|
-
const response = yield fetch(`${this.baseUrl}/api/ingestion/customEvent/batch`, {
|
|
4416
|
-
method: 'POST',
|
|
4417
|
-
headers: {
|
|
4418
|
-
'Content-Type': 'application/json',
|
|
4419
|
-
'Authorization': `Bearer ${this.apiKey}`
|
|
4420
|
-
},
|
|
4421
|
-
body: JSON.stringify({
|
|
4422
|
-
events: events.map(event => (Object.assign(Object.assign({}, event), { sessionId: sessionId })))
|
|
4423
|
-
})
|
|
4424
|
-
});
|
|
4425
|
-
if (!response.ok) {
|
|
4426
|
-
throw new Error(`Failed to send custom events: ${response.statusText}`);
|
|
4427
|
-
}
|
|
4428
|
-
return yield response.json();
|
|
4429
|
-
}
|
|
4430
|
-
catch (error) {
|
|
4431
|
-
retryCount++;
|
|
4432
|
-
if (retryCount === maxRetries) {
|
|
4433
|
-
logError('Error sending custom events after max retries:', error);
|
|
4434
|
-
throw error;
|
|
4435
|
-
}
|
|
4436
|
-
// Exponential backoff
|
|
4437
|
-
yield new Promise(resolve => setTimeout(resolve, Math.pow(2, retryCount) * 1000));
|
|
4438
|
-
}
|
|
4439
|
-
}
|
|
4440
|
-
});
|
|
4441
|
-
}
|
|
4442
|
-
sendBeaconEvents(events, sessionId, isSessionComplete = false) {
|
|
4359
|
+
sendBeaconEvents(events, sessionId) {
|
|
4443
4360
|
const data = new URLSearchParams();
|
|
4444
4361
|
data.append('events', encodeURIComponent(JSON.stringify(events)));
|
|
4445
4362
|
data.append('sessionId', encodeURIComponent(sessionId));
|
|
4446
4363
|
data.append('timestamp', encodeURIComponent(Date.now().toString()));
|
|
4447
4364
|
data.append('apiKey', encodeURIComponent(this.apiKey));
|
|
4448
|
-
if (isSessionComplete) {
|
|
4449
|
-
logInfo('Session complete beacon sending');
|
|
4450
|
-
localStorage.setItem('koalaware_session_complete', Date.now().toString());
|
|
4451
|
-
data.append('sessionComplete', encodeURIComponent('true'));
|
|
4452
|
-
}
|
|
4453
4365
|
navigator.sendBeacon(`${this.baseUrl}/api/ingestion/events`, data);
|
|
4454
|
-
// KoalawareTracker.logToStorage(`Sending events beacon: ${this.baseUrl}/api/ingestion/events`);
|
|
4455
|
-
// KoalawareTracker.logToStorage(`Events beacon success: ${success}`);
|
|
4456
|
-
}
|
|
4457
|
-
sendBeaconSessionComplete(sessionId) {
|
|
4458
|
-
const data = new URLSearchParams();
|
|
4459
|
-
data.append('sessionId', sessionId);
|
|
4460
|
-
data.append('apiKey', this.apiKey);
|
|
4461
|
-
data.append('sessionComplete', 'true');
|
|
4462
|
-
navigator.sendBeacon(`${this.baseUrl}/api/ingestion/sessionComplete`, data);
|
|
4463
|
-
// KoalawareTracker.logToStorage(`Sending completion beacon: ${this.baseUrl}/api/ingestion/sessionComplete`);
|
|
4464
|
-
// KoalawareTracker.logToStorage(`Complete beacon success: ${success}`);
|
|
4465
|
-
}
|
|
4466
|
-
sendBeaconCustomEvent(eventName, eventProperties, sessionId) {
|
|
4467
|
-
const data = new URLSearchParams();
|
|
4468
|
-
data.append('name', encodeURIComponent(eventName));
|
|
4469
|
-
data.append('properties', encodeURIComponent(JSON.stringify(eventProperties)));
|
|
4470
|
-
data.append('sessionId', encodeURIComponent(sessionId));
|
|
4471
|
-
data.append('timestamp', encodeURIComponent(new Date().toISOString()));
|
|
4472
|
-
data.append('apiKey', encodeURIComponent(this.apiKey));
|
|
4473
|
-
return navigator.sendBeacon(`${this.baseUrl}/api/ingestion/customEvent`, data);
|
|
4474
|
-
}
|
|
4475
|
-
sendBeaconCustomEvents(events, sessionId) {
|
|
4476
|
-
const data = new URLSearchParams();
|
|
4477
|
-
data.append('events', encodeURIComponent(JSON.stringify(events.map(event => (Object.assign(Object.assign({}, event), { sessionId: sessionId }))))));
|
|
4478
|
-
data.append('apiKey', encodeURIComponent(this.apiKey));
|
|
4479
|
-
return navigator.sendBeacon(`${this.baseUrl}/api/ingestion/customEvent/batch`, data);
|
|
4480
4366
|
}
|
|
4481
4367
|
}
|
|
4482
4368
|
|
|
@@ -4896,6 +4782,40 @@ const redactionManager = new RedactionManager();
|
|
|
4896
4782
|
// Check if we're in a browser environment
|
|
4897
4783
|
const isBrowser = typeof window !== 'undefined';
|
|
4898
4784
|
class HumanBehaviorTracker {
|
|
4785
|
+
/**
|
|
4786
|
+
* Initialize the HumanBehavior tracker
|
|
4787
|
+
* This is the main entry point - call this once per page
|
|
4788
|
+
*/
|
|
4789
|
+
static init(apiKey, options) {
|
|
4790
|
+
// Return existing instance if already initialized
|
|
4791
|
+
if (isBrowser && window.__humanBehaviorGlobalTracker) {
|
|
4792
|
+
logDebug('Tracker already initialized, returning existing instance');
|
|
4793
|
+
return window.__humanBehaviorGlobalTracker;
|
|
4794
|
+
}
|
|
4795
|
+
// Configure logging if specified
|
|
4796
|
+
if (options === null || options === void 0 ? void 0 : options.logLevel) {
|
|
4797
|
+
this.configureLogging({ level: options.logLevel });
|
|
4798
|
+
}
|
|
4799
|
+
// Create new tracker instance
|
|
4800
|
+
const tracker = new HumanBehaviorTracker(apiKey, options === null || options === void 0 ? void 0 : options.ingestionUrl);
|
|
4801
|
+
// Set redacted fields if specified
|
|
4802
|
+
if (options === null || options === void 0 ? void 0 : options.redactFields) {
|
|
4803
|
+
tracker.setRedactedFields(options.redactFields);
|
|
4804
|
+
}
|
|
4805
|
+
// Test connection (non-blocking)
|
|
4806
|
+
if (isBrowser) {
|
|
4807
|
+
const testUrl = tracker.api['baseUrl'] + '/api/health';
|
|
4808
|
+
fetch(testUrl, { method: 'HEAD' })
|
|
4809
|
+
.then(() => logDebug('Connection test successful'))
|
|
4810
|
+
.catch((error) => {
|
|
4811
|
+
logWarn('Connection test failed - ad blocker may be active:', error.message);
|
|
4812
|
+
tracker._connectionBlocked = true;
|
|
4813
|
+
});
|
|
4814
|
+
}
|
|
4815
|
+
// Start tracking
|
|
4816
|
+
tracker.start();
|
|
4817
|
+
return tracker;
|
|
4818
|
+
}
|
|
4899
4819
|
constructor(apiKey, ingestionUrl) {
|
|
4900
4820
|
this.eventIngestionQueue = [];
|
|
4901
4821
|
this.queueSizeBytes = 0;
|
|
@@ -4910,60 +4830,87 @@ class HumanBehaviorTracker {
|
|
|
4910
4830
|
this.initializationPromise = null;
|
|
4911
4831
|
// Console tracking properties
|
|
4912
4832
|
this.originalConsole = null;
|
|
4913
|
-
this.originalLogger = null;
|
|
4914
4833
|
this.consoleTrackingEnabled = false;
|
|
4834
|
+
// Navigation tracking properties
|
|
4835
|
+
this.navigationTrackingEnabled = false;
|
|
4836
|
+
this.currentUrl = '';
|
|
4837
|
+
this.previousUrl = '';
|
|
4838
|
+
this.originalPushState = null;
|
|
4839
|
+
this.originalReplaceState = null;
|
|
4840
|
+
this.navigationListeners = [];
|
|
4841
|
+
this._connectionBlocked = false;
|
|
4915
4842
|
if (!apiKey) {
|
|
4916
4843
|
throw new Error('Human Behavior API Key is required');
|
|
4917
4844
|
}
|
|
4918
|
-
//
|
|
4919
|
-
|
|
4920
|
-
// ========================================
|
|
4921
|
-
// Uncomment ONE of the following lines to select your server:
|
|
4922
|
-
// AWS Development Server
|
|
4923
|
-
const defaultIngestionUrl = 'http://3.137.217.33:3000';
|
|
4924
|
-
// Vercel Production Server
|
|
4925
|
-
// const defaultIngestionUrl = 'https://ingestion-server.vercel.app';
|
|
4926
|
-
// Local Development Server
|
|
4927
|
-
// const defaultIngestionUrl = 'http://localhost:3000';
|
|
4845
|
+
// Initialize API
|
|
4846
|
+
const defaultIngestionUrl = 'http://3.137.217.33:3000'; // AWS Development Server
|
|
4928
4847
|
this.api = new HumanBehaviorAPI({
|
|
4929
4848
|
apiKey: apiKey,
|
|
4930
4849
|
ingestionUrl: ingestionUrl || defaultIngestionUrl
|
|
4931
4850
|
});
|
|
4932
4851
|
this.apiKey = apiKey;
|
|
4933
4852
|
this.redactionManager = new RedactionManager();
|
|
4934
|
-
//
|
|
4935
|
-
|
|
4936
|
-
|
|
4937
|
-
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
|
|
4853
|
+
// Handle session restoration with improved continuity
|
|
4854
|
+
if (isBrowser) {
|
|
4855
|
+
const existingSessionId = localStorage.getItem('human_behavior_session_id');
|
|
4856
|
+
const lastActivity = localStorage.getItem('human_behavior_last_activity');
|
|
4857
|
+
const thirtyMinutesAgo = Date.now() - (30 * 60 * 1000);
|
|
4858
|
+
// Check if we have an existing session that's still within the activity window
|
|
4859
|
+
if (existingSessionId && lastActivity && parseInt(lastActivity) > thirtyMinutesAgo) {
|
|
4860
|
+
this.sessionId = existingSessionId;
|
|
4861
|
+
logDebug(`Reusing existing session: ${this.sessionId}`);
|
|
4862
|
+
// Update activity timestamp to extend the session window
|
|
4863
|
+
localStorage.setItem('human_behavior_last_activity', Date.now().toString());
|
|
4864
|
+
}
|
|
4865
|
+
else {
|
|
4866
|
+
// Clear old session data if it's expired
|
|
4867
|
+
if (existingSessionId) {
|
|
4868
|
+
logDebug(`Session expired, clearing old session: ${existingSessionId}`);
|
|
4869
|
+
localStorage.removeItem('human_behavior_session_id');
|
|
4870
|
+
localStorage.removeItem('human_behavior_last_activity');
|
|
4871
|
+
}
|
|
4872
|
+
this.sessionId = v1();
|
|
4873
|
+
logDebug(`Creating new session: ${this.sessionId}`);
|
|
4874
|
+
localStorage.setItem('human_behavior_session_id', this.sessionId);
|
|
4875
|
+
localStorage.setItem('human_behavior_last_activity', Date.now().toString());
|
|
4876
|
+
}
|
|
4877
|
+
this.currentUrl = window.location.href;
|
|
4878
|
+
window.__humanBehaviorGlobalTracker = this;
|
|
4879
|
+
}
|
|
4880
|
+
else {
|
|
4881
|
+
this.sessionId = v1();
|
|
4944
4882
|
}
|
|
4945
|
-
// Start initialization
|
|
4883
|
+
// Start initialization
|
|
4946
4884
|
this.initializationPromise = this.init();
|
|
4947
4885
|
}
|
|
4948
4886
|
init() {
|
|
4949
4887
|
return __awaiter$1(this, void 0, void 0, function* () {
|
|
4950
4888
|
try {
|
|
4951
4889
|
const userId = this.getCookie(`human_behavior_end_user_id_${this.apiKey}`);
|
|
4890
|
+
logDebug(`Initializing with sessionId: ${this.sessionId}, userId: ${userId}`);
|
|
4952
4891
|
const { sessionId, endUserId } = yield this.api.init(this.sessionId, userId);
|
|
4953
|
-
|
|
4892
|
+
// Check if server returned a different session ID (for session continuity)
|
|
4893
|
+
if (sessionId !== this.sessionId) {
|
|
4894
|
+
logDebug(`Server returned different sessionId: ${sessionId} (client had: ${this.sessionId})`);
|
|
4895
|
+
this.sessionId = sessionId;
|
|
4896
|
+
// Update localStorage with server's session ID for continuity
|
|
4897
|
+
if (isBrowser) {
|
|
4898
|
+
localStorage.setItem('human_behavior_session_id', this.sessionId);
|
|
4899
|
+
}
|
|
4900
|
+
}
|
|
4954
4901
|
this.endUserId = endUserId;
|
|
4955
4902
|
this.setCookie(`human_behavior_end_user_id_${this.apiKey}`, endUserId, 365);
|
|
4956
4903
|
// Only setup browser-specific handlers when in browser environment
|
|
4957
4904
|
if (isBrowser) {
|
|
4958
4905
|
this.setupPageUnloadHandler();
|
|
4959
|
-
this.
|
|
4906
|
+
this.setupNavigationTracking();
|
|
4960
4907
|
this.processRejectedEvents();
|
|
4961
4908
|
}
|
|
4962
4909
|
else {
|
|
4963
4910
|
logWarn('HumanBehaviorTracker initialized in a non-browser environment. Session tracking is disabled.');
|
|
4964
4911
|
}
|
|
4965
4912
|
this.initialized = true;
|
|
4966
|
-
logInfo(
|
|
4913
|
+
logInfo(`HumanBehaviorTracker initialized with sessionId: ${this.sessionId}, endUserId: ${endUserId}`);
|
|
4967
4914
|
}
|
|
4968
4915
|
catch (error) {
|
|
4969
4916
|
logError('Failed to initialize HumanBehaviorTracker:', error);
|
|
@@ -4979,6 +4926,171 @@ class HumanBehaviorTracker {
|
|
|
4979
4926
|
yield this.initializationPromise;
|
|
4980
4927
|
});
|
|
4981
4928
|
}
|
|
4929
|
+
/**
|
|
4930
|
+
* Setup navigation event tracking for SPA navigation
|
|
4931
|
+
*/
|
|
4932
|
+
setupNavigationTracking() {
|
|
4933
|
+
if (!isBrowser || this.navigationTrackingEnabled)
|
|
4934
|
+
return;
|
|
4935
|
+
this.navigationTrackingEnabled = true;
|
|
4936
|
+
logDebug('Setting up navigation tracking');
|
|
4937
|
+
// Store original history methods
|
|
4938
|
+
this.originalPushState = history.pushState;
|
|
4939
|
+
this.originalReplaceState = history.replaceState;
|
|
4940
|
+
// Override pushState to capture programmatic navigation
|
|
4941
|
+
history.pushState = (...args) => {
|
|
4942
|
+
this.previousUrl = this.currentUrl;
|
|
4943
|
+
this.currentUrl = window.location.href;
|
|
4944
|
+
// Call original method
|
|
4945
|
+
this.originalPushState.apply(history, args);
|
|
4946
|
+
// Track navigation event
|
|
4947
|
+
this.trackNavigationEvent('pushState', this.previousUrl, this.currentUrl);
|
|
4948
|
+
};
|
|
4949
|
+
// Override replaceState to capture programmatic navigation
|
|
4950
|
+
history.replaceState = (...args) => {
|
|
4951
|
+
this.previousUrl = this.currentUrl;
|
|
4952
|
+
this.currentUrl = window.location.href;
|
|
4953
|
+
// Call original method
|
|
4954
|
+
this.originalReplaceState.apply(history, args);
|
|
4955
|
+
// Track navigation event
|
|
4956
|
+
this.trackNavigationEvent('replaceState', this.previousUrl, this.currentUrl);
|
|
4957
|
+
};
|
|
4958
|
+
// Listen for popstate events (back/forward navigation)
|
|
4959
|
+
const popstateListener = () => {
|
|
4960
|
+
this.previousUrl = this.currentUrl;
|
|
4961
|
+
this.currentUrl = window.location.href;
|
|
4962
|
+
this.trackNavigationEvent('popstate', this.previousUrl, this.currentUrl);
|
|
4963
|
+
};
|
|
4964
|
+
window.addEventListener('popstate', popstateListener);
|
|
4965
|
+
this.navigationListeners.push(() => {
|
|
4966
|
+
window.removeEventListener('popstate', popstateListener);
|
|
4967
|
+
});
|
|
4968
|
+
// Listen for hashchange events
|
|
4969
|
+
const hashchangeListener = () => {
|
|
4970
|
+
this.previousUrl = this.currentUrl;
|
|
4971
|
+
this.currentUrl = window.location.href;
|
|
4972
|
+
this.trackNavigationEvent('hashchange', this.previousUrl, this.currentUrl);
|
|
4973
|
+
};
|
|
4974
|
+
window.addEventListener('hashchange', hashchangeListener);
|
|
4975
|
+
this.navigationListeners.push(() => {
|
|
4976
|
+
window.removeEventListener('hashchange', hashchangeListener);
|
|
4977
|
+
});
|
|
4978
|
+
// Track initial page load
|
|
4979
|
+
this.trackNavigationEvent('pageLoad', '', this.currentUrl);
|
|
4980
|
+
}
|
|
4981
|
+
/**
|
|
4982
|
+
* Track navigation events and send custom events
|
|
4983
|
+
*/
|
|
4984
|
+
trackNavigationEvent(type, fromUrl, toUrl) {
|
|
4985
|
+
return __awaiter$1(this, void 0, void 0, function* () {
|
|
4986
|
+
if (!this.initialized)
|
|
4987
|
+
return;
|
|
4988
|
+
try {
|
|
4989
|
+
const navigationData = {
|
|
4990
|
+
type: type,
|
|
4991
|
+
from: fromUrl,
|
|
4992
|
+
to: toUrl,
|
|
4993
|
+
timestamp: new Date().toISOString(),
|
|
4994
|
+
pathname: window.location.pathname,
|
|
4995
|
+
search: window.location.search,
|
|
4996
|
+
hash: window.location.hash,
|
|
4997
|
+
referrer: document.referrer
|
|
4998
|
+
};
|
|
4999
|
+
// Add navigation event to the main event stream
|
|
5000
|
+
yield this.addEvent({
|
|
5001
|
+
type: 5, // Custom event type
|
|
5002
|
+
data: {
|
|
5003
|
+
payload: Object.assign({ eventType: 'navigation' }, navigationData)
|
|
5004
|
+
},
|
|
5005
|
+
timestamp: Date.now()
|
|
5006
|
+
});
|
|
5007
|
+
logDebug(`Navigation tracked: ${type} from ${fromUrl} to ${toUrl}`);
|
|
5008
|
+
}
|
|
5009
|
+
catch (error) {
|
|
5010
|
+
logError('Failed to track navigation event:', error);
|
|
5011
|
+
}
|
|
5012
|
+
});
|
|
5013
|
+
}
|
|
5014
|
+
/**
|
|
5015
|
+
* Track a page view event (PostHog-style)
|
|
5016
|
+
*/
|
|
5017
|
+
trackPageView(url) {
|
|
5018
|
+
return __awaiter$1(this, void 0, void 0, function* () {
|
|
5019
|
+
if (!this.initialized)
|
|
5020
|
+
return;
|
|
5021
|
+
try {
|
|
5022
|
+
const pageViewData = {
|
|
5023
|
+
url: url || window.location.href,
|
|
5024
|
+
pathname: window.location.pathname,
|
|
5025
|
+
search: window.location.search,
|
|
5026
|
+
hash: window.location.hash,
|
|
5027
|
+
referrer: document.referrer,
|
|
5028
|
+
timestamp: new Date().toISOString()
|
|
5029
|
+
};
|
|
5030
|
+
// Add pageview event to the main event stream
|
|
5031
|
+
yield this.addEvent({
|
|
5032
|
+
type: 5, // Custom event type
|
|
5033
|
+
data: {
|
|
5034
|
+
payload: Object.assign({ eventType: 'pageview' }, pageViewData)
|
|
5035
|
+
},
|
|
5036
|
+
timestamp: Date.now()
|
|
5037
|
+
});
|
|
5038
|
+
logDebug(`Pageview tracked: ${pageViewData.url}`);
|
|
5039
|
+
}
|
|
5040
|
+
catch (error) {
|
|
5041
|
+
logError('Failed to track pageview event:', error);
|
|
5042
|
+
}
|
|
5043
|
+
});
|
|
5044
|
+
}
|
|
5045
|
+
/**
|
|
5046
|
+
* Track a custom event (PostHog-style)
|
|
5047
|
+
*/
|
|
5048
|
+
customEvent(eventName, properties) {
|
|
5049
|
+
return __awaiter$1(this, void 0, void 0, function* () {
|
|
5050
|
+
if (!this.initialized)
|
|
5051
|
+
return;
|
|
5052
|
+
try {
|
|
5053
|
+
const customEventData = {
|
|
5054
|
+
eventName: eventName,
|
|
5055
|
+
properties: properties || {},
|
|
5056
|
+
timestamp: new Date().toISOString(),
|
|
5057
|
+
url: window.location.href,
|
|
5058
|
+
pathname: window.location.pathname
|
|
5059
|
+
};
|
|
5060
|
+
// Add custom event to the main event stream
|
|
5061
|
+
yield this.addEvent({
|
|
5062
|
+
type: 5, // Custom event type
|
|
5063
|
+
data: {
|
|
5064
|
+
payload: Object.assign({ eventType: 'custom' }, customEventData)
|
|
5065
|
+
},
|
|
5066
|
+
timestamp: Date.now()
|
|
5067
|
+
});
|
|
5068
|
+
logDebug(`Custom event tracked: ${eventName}`, properties);
|
|
5069
|
+
}
|
|
5070
|
+
catch (error) {
|
|
5071
|
+
logError('Failed to track custom event:', error);
|
|
5072
|
+
}
|
|
5073
|
+
});
|
|
5074
|
+
}
|
|
5075
|
+
/**
|
|
5076
|
+
* Cleanup navigation tracking
|
|
5077
|
+
*/
|
|
5078
|
+
cleanupNavigationTracking() {
|
|
5079
|
+
if (!this.navigationTrackingEnabled)
|
|
5080
|
+
return;
|
|
5081
|
+
// Restore original history methods
|
|
5082
|
+
if (this.originalPushState) {
|
|
5083
|
+
history.pushState = this.originalPushState;
|
|
5084
|
+
}
|
|
5085
|
+
if (this.originalReplaceState) {
|
|
5086
|
+
history.replaceState = this.originalReplaceState;
|
|
5087
|
+
}
|
|
5088
|
+
// Remove event listeners
|
|
5089
|
+
this.navigationListeners.forEach(cleanup => cleanup());
|
|
5090
|
+
this.navigationListeners = [];
|
|
5091
|
+
this.navigationTrackingEnabled = false;
|
|
5092
|
+
logDebug('Navigation tracking cleaned up');
|
|
5093
|
+
}
|
|
4982
5094
|
static logToStorage(message) {
|
|
4983
5095
|
logInfo(message);
|
|
4984
5096
|
}
|
|
@@ -5012,13 +5124,6 @@ class HumanBehaviorTracker {
|
|
|
5012
5124
|
warn: console.warn,
|
|
5013
5125
|
error: console.error
|
|
5014
5126
|
};
|
|
5015
|
-
// Store original logger methods
|
|
5016
|
-
this.originalLogger = {
|
|
5017
|
-
error: logError,
|
|
5018
|
-
warn: logWarn,
|
|
5019
|
-
info: logInfo,
|
|
5020
|
-
debug: logDebug
|
|
5021
|
-
};
|
|
5022
5127
|
// Override console methods to capture ALL console output (including logger output)
|
|
5023
5128
|
console.log = (...args) => {
|
|
5024
5129
|
this.trackConsoleEvent('log', args);
|
|
@@ -5033,44 +5138,47 @@ class HumanBehaviorTracker {
|
|
|
5033
5138
|
this.originalConsole.error(...args);
|
|
5034
5139
|
};
|
|
5035
5140
|
this.consoleTrackingEnabled = true;
|
|
5036
|
-
|
|
5141
|
+
logDebug('Console tracking enabled');
|
|
5037
5142
|
}
|
|
5038
5143
|
/**
|
|
5039
5144
|
* Disable console event tracking
|
|
5040
5145
|
*/
|
|
5041
5146
|
disableConsoleTracking() {
|
|
5042
|
-
if (!isBrowser || !this.consoleTrackingEnabled
|
|
5147
|
+
if (!isBrowser || !this.consoleTrackingEnabled)
|
|
5043
5148
|
return;
|
|
5044
5149
|
// Restore original console methods
|
|
5045
|
-
|
|
5046
|
-
|
|
5047
|
-
|
|
5150
|
+
if (this.originalConsole) {
|
|
5151
|
+
console.log = this.originalConsole.log;
|
|
5152
|
+
console.warn = this.originalConsole.warn;
|
|
5153
|
+
console.error = this.originalConsole.error;
|
|
5154
|
+
}
|
|
5048
5155
|
this.consoleTrackingEnabled = false;
|
|
5049
|
-
|
|
5050
|
-
this.originalLogger = null;
|
|
5156
|
+
logDebug('Console tracking disabled');
|
|
5051
5157
|
}
|
|
5052
|
-
/**
|
|
5053
|
-
* Track console events
|
|
5054
|
-
*/
|
|
5055
5158
|
trackConsoleEvent(level, args) {
|
|
5056
5159
|
if (!this.initialized)
|
|
5057
5160
|
return;
|
|
5058
|
-
|
|
5059
|
-
|
|
5060
|
-
|
|
5061
|
-
|
|
5062
|
-
|
|
5063
|
-
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
|
|
5067
|
-
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
|
|
5071
|
-
|
|
5072
|
-
|
|
5073
|
-
|
|
5161
|
+
try {
|
|
5162
|
+
const consoleData = {
|
|
5163
|
+
level: level,
|
|
5164
|
+
message: args.map(arg => typeof arg === 'object' ? JSON.stringify(arg) : String(arg)).join(' '),
|
|
5165
|
+
timestamp: new Date().toISOString(),
|
|
5166
|
+
url: window.location.href
|
|
5167
|
+
};
|
|
5168
|
+
// Add console event to the main event stream
|
|
5169
|
+
this.addEvent({
|
|
5170
|
+
type: 5, // Custom event type
|
|
5171
|
+
data: {
|
|
5172
|
+
payload: Object.assign({ eventType: 'console' }, consoleData)
|
|
5173
|
+
},
|
|
5174
|
+
timestamp: Date.now()
|
|
5175
|
+
}).catch(error => {
|
|
5176
|
+
logError('Failed to track console event:', error);
|
|
5177
|
+
});
|
|
5178
|
+
}
|
|
5179
|
+
catch (error) {
|
|
5180
|
+
logError('Error in trackConsoleEvent:', error);
|
|
5181
|
+
}
|
|
5074
5182
|
}
|
|
5075
5183
|
setupPageUnloadHandler() {
|
|
5076
5184
|
if (!isBrowser)
|
|
@@ -5086,15 +5194,18 @@ class HumanBehaviorTracker {
|
|
|
5086
5194
|
});
|
|
5087
5195
|
// Handle actual page unload/close
|
|
5088
5196
|
window.addEventListener('beforeunload', () => {
|
|
5089
|
-
// Update last activity time
|
|
5090
|
-
localStorage.setItem('human_behavior_last_activity', Date.now().toString());
|
|
5091
5197
|
// Send final events
|
|
5092
5198
|
this.api.sendBeaconEvents(this.eventIngestionQueue, this.sessionId);
|
|
5093
5199
|
});
|
|
5094
|
-
// Update activity timestamp
|
|
5095
|
-
|
|
5200
|
+
// Update activity timestamp on user interaction (not on page load)
|
|
5201
|
+
const updateActivity = () => {
|
|
5096
5202
|
localStorage.setItem('human_behavior_last_activity', Date.now().toString());
|
|
5097
|
-
}
|
|
5203
|
+
};
|
|
5204
|
+
// Listen for user interactions to update activity timestamp
|
|
5205
|
+
window.addEventListener('click', updateActivity);
|
|
5206
|
+
window.addEventListener('keydown', updateActivity);
|
|
5207
|
+
window.addEventListener('scroll', updateActivity);
|
|
5208
|
+
window.addEventListener('mousemove', updateActivity);
|
|
5098
5209
|
}
|
|
5099
5210
|
viewLogs() {
|
|
5100
5211
|
try {
|
|
@@ -5129,13 +5240,12 @@ class HumanBehaviorTracker {
|
|
|
5129
5240
|
if (!this.userProperties || Object.keys(this.userProperties).length === 0) {
|
|
5130
5241
|
throw new Error('No user info available. Call addUserInfo() first.');
|
|
5131
5242
|
}
|
|
5132
|
-
yield this.api.sendUserAuth(this.endUserId, this.userProperties, this.sessionId, authFields);
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
this.api.sendBeaconCustomEvent(eventName, eventProperties, this.sessionId);
|
|
5243
|
+
const response = yield this.api.sendUserAuth(this.endUserId, this.userProperties, this.sessionId, authFields);
|
|
5244
|
+
if (response && response.userId && response.userId !== this.endUserId) {
|
|
5245
|
+
// Update endUserId and cookie if backend returns a new userId
|
|
5246
|
+
this.endUserId = response.userId;
|
|
5247
|
+
this.setCookie(`human_behavior_end_user_id_${this.apiKey}`, response.userId, 365);
|
|
5248
|
+
}
|
|
5139
5249
|
});
|
|
5140
5250
|
}
|
|
5141
5251
|
start() {
|
|
@@ -5174,6 +5284,8 @@ class HumanBehaviorTracker {
|
|
|
5174
5284
|
}
|
|
5175
5285
|
// Disable console tracking
|
|
5176
5286
|
this.disableConsoleTracking();
|
|
5287
|
+
// Cleanup navigation tracking
|
|
5288
|
+
this.cleanupNavigationTracking();
|
|
5177
5289
|
});
|
|
5178
5290
|
}
|
|
5179
5291
|
addEvent(event) {
|
|
@@ -5216,7 +5328,7 @@ class HumanBehaviorTracker {
|
|
|
5216
5328
|
}
|
|
5217
5329
|
flush() {
|
|
5218
5330
|
return __awaiter$1(this, void 0, void 0, function* () {
|
|
5219
|
-
var _a;
|
|
5331
|
+
var _a, _b, _c, _d;
|
|
5220
5332
|
// Prevent concurrent flushes
|
|
5221
5333
|
if (this.isProcessing || !this.initialized) {
|
|
5222
5334
|
return;
|
|
@@ -5239,6 +5351,14 @@ class HumanBehaviorTracker {
|
|
|
5239
5351
|
this.rejectedEvents.push(...eventsToProcess);
|
|
5240
5352
|
this.processRejectedEvents();
|
|
5241
5353
|
}
|
|
5354
|
+
else if (((_b = error.message) === null || _b === void 0 ? void 0 : _b.includes('ERR_BLOCKED_BY_CLIENT')) ||
|
|
5355
|
+
((_c = error.message) === null || _c === void 0 ? void 0 : _c.includes('Failed to fetch')) ||
|
|
5356
|
+
((_d = error.message) === null || _d === void 0 ? void 0 : _d.includes('NetworkError'))) {
|
|
5357
|
+
// Handle ad blocker or network issues gracefully
|
|
5358
|
+
logWarn('Request blocked by ad blocker or network issue, storing events for retry');
|
|
5359
|
+
this.rejectedEvents.push(...eventsToProcess);
|
|
5360
|
+
// Don't process rejected events immediately to avoid spam
|
|
5361
|
+
}
|
|
5242
5362
|
else {
|
|
5243
5363
|
throw error;
|
|
5244
5364
|
}
|
|
@@ -5250,28 +5370,70 @@ class HumanBehaviorTracker {
|
|
|
5250
5370
|
}
|
|
5251
5371
|
});
|
|
5252
5372
|
}
|
|
5253
|
-
// Add helper methods for cookie management
|
|
5373
|
+
// Add helper methods for cookie management with localStorage fallback
|
|
5254
5374
|
setCookie(name, value, daysToExpire) {
|
|
5255
5375
|
if (!isBrowser)
|
|
5256
5376
|
return;
|
|
5257
|
-
|
|
5258
|
-
|
|
5259
|
-
|
|
5260
|
-
|
|
5377
|
+
try {
|
|
5378
|
+
// Try to set cookie first
|
|
5379
|
+
const date = new Date();
|
|
5380
|
+
date.setTime(date.getTime() + (daysToExpire * 24 * 60 * 60 * 1000));
|
|
5381
|
+
const expires = `expires=${date.toUTCString()}`;
|
|
5382
|
+
document.cookie = `${name}=${value};${expires};path=/;SameSite=Lax`;
|
|
5383
|
+
// Also store in localStorage as backup
|
|
5384
|
+
localStorage.setItem(name, value);
|
|
5385
|
+
logDebug(`Set cookie and localStorage: ${name}`);
|
|
5386
|
+
}
|
|
5387
|
+
catch (error) {
|
|
5388
|
+
// If cookie fails, use localStorage only
|
|
5389
|
+
try {
|
|
5390
|
+
localStorage.setItem(name, value);
|
|
5391
|
+
logDebug(`Cookie blocked, using localStorage: ${name}`);
|
|
5392
|
+
}
|
|
5393
|
+
catch (localStorageError) {
|
|
5394
|
+
logError('Failed to store user ID in both cookie and localStorage:', localStorageError);
|
|
5395
|
+
}
|
|
5396
|
+
}
|
|
5261
5397
|
}
|
|
5262
5398
|
getCookie(name) {
|
|
5263
5399
|
if (!isBrowser)
|
|
5264
5400
|
return null;
|
|
5265
|
-
|
|
5266
|
-
|
|
5267
|
-
|
|
5268
|
-
|
|
5269
|
-
|
|
5270
|
-
c =
|
|
5271
|
-
|
|
5272
|
-
|
|
5401
|
+
try {
|
|
5402
|
+
// Try to get from cookie first
|
|
5403
|
+
const nameEQ = name + "=";
|
|
5404
|
+
const ca = document.cookie.split(';');
|
|
5405
|
+
for (let i = 0; i < ca.length; i++) {
|
|
5406
|
+
let c = ca[i];
|
|
5407
|
+
while (c.charAt(0) === ' ')
|
|
5408
|
+
c = c.substring(1, c.length);
|
|
5409
|
+
if (c.indexOf(nameEQ) === 0) {
|
|
5410
|
+
const cookieValue = c.substring(nameEQ.length, c.length);
|
|
5411
|
+
logDebug(`Found cookie: ${name}`);
|
|
5412
|
+
return cookieValue;
|
|
5413
|
+
}
|
|
5414
|
+
}
|
|
5415
|
+
// If cookie not found, try localStorage
|
|
5416
|
+
const localStorageValue = localStorage.getItem(name);
|
|
5417
|
+
if (localStorageValue) {
|
|
5418
|
+
logDebug(`Cookie not found, using localStorage: ${name}`);
|
|
5419
|
+
return localStorageValue;
|
|
5420
|
+
}
|
|
5421
|
+
return null;
|
|
5422
|
+
}
|
|
5423
|
+
catch (error) {
|
|
5424
|
+
// If cookie access fails, try localStorage
|
|
5425
|
+
try {
|
|
5426
|
+
const localStorageValue = localStorage.getItem(name);
|
|
5427
|
+
if (localStorageValue) {
|
|
5428
|
+
logDebug(`Cookie access failed, using localStorage: ${name}`);
|
|
5429
|
+
return localStorageValue;
|
|
5430
|
+
}
|
|
5431
|
+
}
|
|
5432
|
+
catch (localStorageError) {
|
|
5433
|
+
logError('Failed to access both cookie and localStorage:', localStorageError);
|
|
5434
|
+
}
|
|
5435
|
+
return null;
|
|
5273
5436
|
}
|
|
5274
|
-
return null;
|
|
5275
5437
|
}
|
|
5276
5438
|
/**
|
|
5277
5439
|
* Start redaction functionality for sensitive input fields
|
|
@@ -5311,6 +5473,84 @@ class HumanBehaviorTracker {
|
|
|
5311
5473
|
getRedactedFields() {
|
|
5312
5474
|
return this.redactionManager.getSelectedFields();
|
|
5313
5475
|
}
|
|
5476
|
+
/**
|
|
5477
|
+
* Get the current session ID
|
|
5478
|
+
*/
|
|
5479
|
+
getSessionId() {
|
|
5480
|
+
return this.sessionId;
|
|
5481
|
+
}
|
|
5482
|
+
/**
|
|
5483
|
+
* Get the current URL being tracked
|
|
5484
|
+
*/
|
|
5485
|
+
getCurrentUrl() {
|
|
5486
|
+
return this.currentUrl;
|
|
5487
|
+
}
|
|
5488
|
+
/**
|
|
5489
|
+
* Test if the tracker can reach the ingestion server
|
|
5490
|
+
*/
|
|
5491
|
+
testConnection() {
|
|
5492
|
+
return __awaiter$1(this, void 0, void 0, function* () {
|
|
5493
|
+
try {
|
|
5494
|
+
yield this.api.init(this.sessionId, this.endUserId);
|
|
5495
|
+
return { success: true };
|
|
5496
|
+
}
|
|
5497
|
+
catch (error) {
|
|
5498
|
+
return {
|
|
5499
|
+
success: false,
|
|
5500
|
+
error: error.message || 'Unknown error'
|
|
5501
|
+
};
|
|
5502
|
+
}
|
|
5503
|
+
});
|
|
5504
|
+
}
|
|
5505
|
+
/**
|
|
5506
|
+
* Get connection status and recommendations
|
|
5507
|
+
*/
|
|
5508
|
+
getConnectionStatus() {
|
|
5509
|
+
const recommendations = [];
|
|
5510
|
+
let blocked = false;
|
|
5511
|
+
// Check if we have rejected events (might indicate blocking)
|
|
5512
|
+
if (this.rejectedEvents.length > 0) {
|
|
5513
|
+
blocked = true;
|
|
5514
|
+
recommendations.push('Some requests may be blocked by ad blockers');
|
|
5515
|
+
}
|
|
5516
|
+
// Check if connection was blocked during initialization
|
|
5517
|
+
if (this._connectionBlocked) {
|
|
5518
|
+
blocked = true;
|
|
5519
|
+
recommendations.push('Initial connection test failed - ad blocker may be active');
|
|
5520
|
+
}
|
|
5521
|
+
// Check if we're in a browser environment
|
|
5522
|
+
if (typeof window === 'undefined') {
|
|
5523
|
+
recommendations.push('Not running in browser environment');
|
|
5524
|
+
}
|
|
5525
|
+
// Check if navigator.sendBeacon is available
|
|
5526
|
+
if (typeof navigator.sendBeacon === 'undefined') {
|
|
5527
|
+
recommendations.push('sendBeacon not available, using fetch fallback');
|
|
5528
|
+
}
|
|
5529
|
+
return { blocked, recommendations };
|
|
5530
|
+
}
|
|
5531
|
+
/**
|
|
5532
|
+
* Check if the current user is a preexisting user
|
|
5533
|
+
* Returns true if the user has an existing endUserId cookie from a previous session
|
|
5534
|
+
*/
|
|
5535
|
+
isPreexistingUser() {
|
|
5536
|
+
if (!isBrowser) {
|
|
5537
|
+
return false;
|
|
5538
|
+
}
|
|
5539
|
+
// Check if there's an existing endUserId cookie for this API key
|
|
5540
|
+
const existingEndUserId = this.getCookie(`human_behavior_end_user_id_${this.apiKey}`);
|
|
5541
|
+
return existingEndUserId !== null && existingEndUserId !== this.endUserId;
|
|
5542
|
+
}
|
|
5543
|
+
/**
|
|
5544
|
+
* Get user information including whether they are preexisting
|
|
5545
|
+
*/
|
|
5546
|
+
getUserInfo() {
|
|
5547
|
+
return {
|
|
5548
|
+
endUserId: this.endUserId,
|
|
5549
|
+
sessionId: this.sessionId,
|
|
5550
|
+
isPreexistingUser: this.isPreexistingUser(),
|
|
5551
|
+
initialized: this.initialized
|
|
5552
|
+
};
|
|
5553
|
+
}
|
|
5314
5554
|
}
|
|
5315
5555
|
// Only expose to window object in browser environments
|
|
5316
5556
|
if (isBrowser) {
|