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