humanbehavior-js 0.1.2 → 0.1.4
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 +235 -37
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.js +235 -37
- package/dist/esm/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 +29 -0
- package/package.json +1 -1
- package/src/api.ts +84 -21
- package/src/tracker.ts +203 -20
package/dist/types/index.d.ts
CHANGED
|
@@ -119,6 +119,14 @@ declare class HumanBehaviorTracker {
|
|
|
119
119
|
ingestionUrl?: string;
|
|
120
120
|
logLevel?: 'none' | 'error' | 'warn' | 'info' | 'debug';
|
|
121
121
|
redactFields?: string[];
|
|
122
|
+
enableAutomaticTracking?: boolean;
|
|
123
|
+
automaticTrackingOptions?: {
|
|
124
|
+
trackButtons?: boolean;
|
|
125
|
+
trackLinks?: boolean;
|
|
126
|
+
trackForms?: boolean;
|
|
127
|
+
includeText?: boolean;
|
|
128
|
+
includeClasses?: boolean;
|
|
129
|
+
};
|
|
122
130
|
}): HumanBehaviorTracker;
|
|
123
131
|
constructor(apiKey: string | undefined, ingestionUrl?: string);
|
|
124
132
|
private init;
|
|
@@ -139,6 +147,22 @@ declare class HumanBehaviorTracker {
|
|
|
139
147
|
* Track a custom event (PostHog-style)
|
|
140
148
|
*/
|
|
141
149
|
customEvent(eventName: string, properties?: Record<string, any>): Promise<void>;
|
|
150
|
+
/**
|
|
151
|
+
* Setup automatic tracking for buttons, links, and forms
|
|
152
|
+
*/
|
|
153
|
+
private setupAutomaticTracking;
|
|
154
|
+
/**
|
|
155
|
+
* Setup automatic button tracking
|
|
156
|
+
*/
|
|
157
|
+
private setupAutomaticButtonTracking;
|
|
158
|
+
/**
|
|
159
|
+
* Setup automatic link tracking
|
|
160
|
+
*/
|
|
161
|
+
private setupAutomaticLinkTracking;
|
|
162
|
+
/**
|
|
163
|
+
* Setup automatic form tracking
|
|
164
|
+
*/
|
|
165
|
+
private setupAutomaticFormTracking;
|
|
142
166
|
/**
|
|
143
167
|
* Cleanup navigation tracking
|
|
144
168
|
*/
|
|
@@ -252,6 +276,11 @@ declare class HumanBehaviorAPI {
|
|
|
252
276
|
sendUserData(userId: string, userData: Record<string, any>, sessionId: string): Promise<any>;
|
|
253
277
|
sendUserAuth(userId: string, userData: Record<string, any>, sessionId: string, authFields: string[]): Promise<any>;
|
|
254
278
|
sendBeaconEvents(events: any[], sessionId: string): void;
|
|
279
|
+
sendCustomEvent(sessionId: string, eventName: string, eventProperties?: Record<string, any>): Promise<any>;
|
|
280
|
+
sendCustomEventBatch(sessionId: string, events: Array<{
|
|
281
|
+
eventName: string;
|
|
282
|
+
eventProperties?: Record<string, any>;
|
|
283
|
+
}>): Promise<any>;
|
|
255
284
|
}
|
|
256
285
|
|
|
257
286
|
declare enum LogLevel {
|
package/package.json
CHANGED
package/src/api.ts
CHANGED
|
@@ -41,29 +41,41 @@ export class HumanBehaviorAPI {
|
|
|
41
41
|
referrer = document.referrer;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
method: 'POST',
|
|
46
|
-
headers: {
|
|
47
|
-
'Content-Type': 'application/json',
|
|
48
|
-
'Authorization': `Bearer ${this.apiKey}`,
|
|
49
|
-
'Referer': referrer || ''
|
|
50
|
-
},
|
|
51
|
-
body: JSON.stringify({
|
|
52
|
-
sessionId: sessionId,
|
|
53
|
-
endUserId: userId,
|
|
54
|
-
entryURL: entryURL,
|
|
55
|
-
referrer: referrer
|
|
56
|
-
})
|
|
57
|
-
});
|
|
44
|
+
console.log('API init called with:', { sessionId, userId, entryURL, referrer, baseUrl: this.baseUrl });
|
|
58
45
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
46
|
+
try {
|
|
47
|
+
const response = await fetch(`${this.baseUrl}/api/ingestion/init`, {
|
|
48
|
+
method: 'POST',
|
|
49
|
+
headers: {
|
|
50
|
+
'Content-Type': 'application/json',
|
|
51
|
+
'Authorization': `Bearer ${this.apiKey}`,
|
|
52
|
+
'Referer': referrer || ''
|
|
53
|
+
},
|
|
54
|
+
body: JSON.stringify({
|
|
55
|
+
sessionId: sessionId,
|
|
56
|
+
endUserId: userId,
|
|
57
|
+
entryURL: entryURL,
|
|
58
|
+
referrer: referrer
|
|
59
|
+
})
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
console.log('API init response status:', response.status);
|
|
63
|
+
|
|
64
|
+
if (!response.ok) {
|
|
65
|
+
const errorText = await response.text();
|
|
66
|
+
console.error('API init failed:', response.status, errorText);
|
|
67
|
+
throw new Error(`Failed to initialize ingestion: ${response.statusText} - ${errorText}`);
|
|
68
|
+
}
|
|
62
69
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
70
|
+
const responseJson = await response.json();
|
|
71
|
+
console.log('API init success:', responseJson);
|
|
72
|
+
return {
|
|
73
|
+
sessionId: responseJson.sessionId,
|
|
74
|
+
endUserId: responseJson.endUserId
|
|
75
|
+
}
|
|
76
|
+
} catch (error) {
|
|
77
|
+
console.error('API init error:', error);
|
|
78
|
+
throw error;
|
|
67
79
|
}
|
|
68
80
|
}
|
|
69
81
|
|
|
@@ -219,4 +231,55 @@ export class HumanBehaviorAPI {
|
|
|
219
231
|
data
|
|
220
232
|
);
|
|
221
233
|
}
|
|
234
|
+
|
|
235
|
+
async sendCustomEvent(sessionId: string, eventName: string, eventProperties?: Record<string, any>) {
|
|
236
|
+
try {
|
|
237
|
+
const response = await fetch(`${this.baseUrl}/api/ingestion/customEvent`, {
|
|
238
|
+
method: 'POST',
|
|
239
|
+
headers: {
|
|
240
|
+
'Content-Type': 'application/json',
|
|
241
|
+
'Authorization': `Bearer ${this.apiKey}`
|
|
242
|
+
},
|
|
243
|
+
body: JSON.stringify({
|
|
244
|
+
sessionId: sessionId,
|
|
245
|
+
eventName: eventName,
|
|
246
|
+
eventProperties: eventProperties || {}
|
|
247
|
+
})
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
if (!response.ok) {
|
|
251
|
+
throw new Error(`Failed to send custom event: ${response.statusText}`);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return await response.json();
|
|
255
|
+
} catch (error) {
|
|
256
|
+
logError('Error sending custom event:', error);
|
|
257
|
+
throw error;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async sendCustomEventBatch(sessionId: string, events: Array<{ eventName: string; eventProperties?: Record<string, any> }>) {
|
|
262
|
+
try {
|
|
263
|
+
const response = await fetch(`${this.baseUrl}/api/ingestion/customEvent/batch`, {
|
|
264
|
+
method: 'POST',
|
|
265
|
+
headers: {
|
|
266
|
+
'Content-Type': 'application/json',
|
|
267
|
+
'Authorization': `Bearer ${this.apiKey}`
|
|
268
|
+
},
|
|
269
|
+
body: JSON.stringify({
|
|
270
|
+
sessionId: sessionId,
|
|
271
|
+
events: events
|
|
272
|
+
})
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
if (!response.ok) {
|
|
276
|
+
throw new Error(`Failed to send custom event batch: ${response.statusText}`);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return await response.json();
|
|
280
|
+
} catch (error) {
|
|
281
|
+
logError('Error sending custom event batch:', error);
|
|
282
|
+
throw error;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
222
285
|
}
|
package/src/tracker.ts
CHANGED
|
@@ -58,6 +58,14 @@ export class HumanBehaviorTracker {
|
|
|
58
58
|
ingestionUrl?: string;
|
|
59
59
|
logLevel?: 'none' | 'error' | 'warn' | 'info' | 'debug';
|
|
60
60
|
redactFields?: string[];
|
|
61
|
+
enableAutomaticTracking?: boolean;
|
|
62
|
+
automaticTrackingOptions?: {
|
|
63
|
+
trackButtons?: boolean;
|
|
64
|
+
trackLinks?: boolean;
|
|
65
|
+
trackForms?: boolean;
|
|
66
|
+
includeText?: boolean;
|
|
67
|
+
includeClasses?: boolean;
|
|
68
|
+
};
|
|
61
69
|
}): HumanBehaviorTracker {
|
|
62
70
|
// Return existing instance if already initialized
|
|
63
71
|
if (isBrowser && window.__humanBehaviorGlobalTracker) {
|
|
@@ -78,6 +86,11 @@ export class HumanBehaviorTracker {
|
|
|
78
86
|
tracker.setRedactedFields(options.redactFields);
|
|
79
87
|
}
|
|
80
88
|
|
|
89
|
+
// Setup automatic tracking if enabled
|
|
90
|
+
if (options?.enableAutomaticTracking !== false) {
|
|
91
|
+
tracker.setupAutomaticTracking(options?.automaticTrackingOptions);
|
|
92
|
+
}
|
|
93
|
+
|
|
81
94
|
// Test connection (non-blocking)
|
|
82
95
|
if (isBrowser) {
|
|
83
96
|
const testUrl = tracker.api['baseUrl'] + '/api/health';
|
|
@@ -101,7 +114,8 @@ export class HumanBehaviorTracker {
|
|
|
101
114
|
}
|
|
102
115
|
|
|
103
116
|
// Initialize API
|
|
104
|
-
const defaultIngestionUrl = 'http://3.137.217.33:3000'; // AWS Development Server
|
|
117
|
+
//const defaultIngestionUrl = 'http://3.137.217.33:3000'; // AWS Development Server
|
|
118
|
+
const defaultIngestionUrl = 'http://ingestion-server-alb-1823866402.us-east-2.elb.amazonaws.com'; // ALB
|
|
105
119
|
this.api = new HumanBehaviorAPI({
|
|
106
120
|
apiKey: apiKey,
|
|
107
121
|
ingestionUrl: ingestionUrl || defaultIngestionUrl
|
|
@@ -330,32 +344,201 @@ export class HumanBehaviorTracker {
|
|
|
330
344
|
if (!this.initialized) return;
|
|
331
345
|
|
|
332
346
|
try {
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
properties: properties || {},
|
|
336
|
-
timestamp: new Date().toISOString(),
|
|
337
|
-
url: window.location.href,
|
|
338
|
-
pathname: window.location.pathname
|
|
339
|
-
};
|
|
340
|
-
|
|
341
|
-
// Add custom event to the main event stream
|
|
342
|
-
await this.addEvent({
|
|
343
|
-
type: 5, // Custom event type
|
|
344
|
-
data: {
|
|
345
|
-
payload: {
|
|
346
|
-
eventType: 'custom',
|
|
347
|
-
...customEventData
|
|
348
|
-
}
|
|
349
|
-
},
|
|
350
|
-
timestamp: Date.now()
|
|
351
|
-
});
|
|
347
|
+
// Send custom event directly to the API
|
|
348
|
+
await this.api.sendCustomEvent(this.sessionId, eventName, properties);
|
|
352
349
|
|
|
353
350
|
logDebug(`Custom event tracked: ${eventName}`, properties);
|
|
354
351
|
} catch (error) {
|
|
355
352
|
logError('Failed to track custom event:', error);
|
|
353
|
+
|
|
354
|
+
// Fallback: add to event stream if direct API call fails
|
|
355
|
+
try {
|
|
356
|
+
const customEventData = {
|
|
357
|
+
eventName: eventName,
|
|
358
|
+
properties: properties || {},
|
|
359
|
+
timestamp: new Date().toISOString(),
|
|
360
|
+
url: window.location.href,
|
|
361
|
+
pathname: window.location.pathname
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
await this.addEvent({
|
|
365
|
+
type: 5, // Custom event type
|
|
366
|
+
data: {
|
|
367
|
+
payload: {
|
|
368
|
+
eventType: 'custom',
|
|
369
|
+
...customEventData
|
|
370
|
+
}
|
|
371
|
+
},
|
|
372
|
+
timestamp: Date.now()
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
logDebug(`Custom event added to event stream as fallback: ${eventName}`);
|
|
376
|
+
} catch (fallbackError) {
|
|
377
|
+
logError('Failed to add custom event to event stream as fallback:', fallbackError);
|
|
378
|
+
}
|
|
356
379
|
}
|
|
357
380
|
}
|
|
358
381
|
|
|
382
|
+
/**
|
|
383
|
+
* Setup automatic tracking for buttons, links, and forms
|
|
384
|
+
*/
|
|
385
|
+
private setupAutomaticTracking(options?: {
|
|
386
|
+
trackButtons?: boolean;
|
|
387
|
+
trackLinks?: boolean;
|
|
388
|
+
trackForms?: boolean;
|
|
389
|
+
includeText?: boolean;
|
|
390
|
+
includeClasses?: boolean;
|
|
391
|
+
}): void {
|
|
392
|
+
if (!isBrowser) return;
|
|
393
|
+
|
|
394
|
+
const config = {
|
|
395
|
+
trackButtons: options?.trackButtons !== false,
|
|
396
|
+
trackLinks: options?.trackLinks !== false,
|
|
397
|
+
trackForms: options?.trackForms !== false,
|
|
398
|
+
includeText: options?.includeText !== false,
|
|
399
|
+
includeClasses: options?.includeClasses || false
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
logDebug('Setting up automatic tracking with config:', config);
|
|
403
|
+
|
|
404
|
+
// Setup button tracking
|
|
405
|
+
if (config.trackButtons) {
|
|
406
|
+
this.setupAutomaticButtonTracking(config);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Setup link tracking
|
|
410
|
+
if (config.trackLinks) {
|
|
411
|
+
this.setupAutomaticLinkTracking(config);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Setup form tracking
|
|
415
|
+
if (config.trackForms) {
|
|
416
|
+
this.setupAutomaticFormTracking(config);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Setup automatic button tracking
|
|
422
|
+
*/
|
|
423
|
+
private setupAutomaticButtonTracking(config: {
|
|
424
|
+
includeText?: boolean;
|
|
425
|
+
includeClasses?: boolean;
|
|
426
|
+
}): void {
|
|
427
|
+
document.addEventListener('click', async (event) => {
|
|
428
|
+
const target = event.target as HTMLElement;
|
|
429
|
+
|
|
430
|
+
// Track button clicks
|
|
431
|
+
if (target.tagName === 'BUTTON' || target.closest('button')) {
|
|
432
|
+
const button = target.tagName === 'BUTTON'
|
|
433
|
+
? target as HTMLButtonElement
|
|
434
|
+
: target.closest('button') as HTMLButtonElement;
|
|
435
|
+
|
|
436
|
+
const properties: Record<string, any> = {
|
|
437
|
+
buttonId: button.id || null,
|
|
438
|
+
buttonType: button.type || 'button',
|
|
439
|
+
page: window.location.pathname,
|
|
440
|
+
timestamp: Date.now()
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
if (config.includeText) {
|
|
444
|
+
properties.buttonText = button.textContent?.trim() || null;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
if (config.includeClasses) {
|
|
448
|
+
properties.buttonClass = button.className || null;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Remove null values
|
|
452
|
+
Object.keys(properties).forEach(key => {
|
|
453
|
+
if (properties[key] === null) {
|
|
454
|
+
delete properties[key];
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
await this.customEvent('button_clicked', properties);
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Setup automatic link tracking
|
|
465
|
+
*/
|
|
466
|
+
private setupAutomaticLinkTracking(config: {
|
|
467
|
+
includeText?: boolean;
|
|
468
|
+
includeClasses?: boolean;
|
|
469
|
+
}): void {
|
|
470
|
+
document.addEventListener('click', async (event) => {
|
|
471
|
+
const target = event.target as HTMLElement;
|
|
472
|
+
|
|
473
|
+
// Track link clicks
|
|
474
|
+
if (target.tagName === 'A' || target.closest('a')) {
|
|
475
|
+
const link = target.tagName === 'A'
|
|
476
|
+
? target as HTMLAnchorElement
|
|
477
|
+
: target.closest('a') as HTMLAnchorElement;
|
|
478
|
+
|
|
479
|
+
const properties: Record<string, any> = {
|
|
480
|
+
linkUrl: link.href || null,
|
|
481
|
+
linkId: link.id || null,
|
|
482
|
+
linkTarget: link.target || null,
|
|
483
|
+
page: window.location.pathname,
|
|
484
|
+
timestamp: Date.now()
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
if (config.includeText) {
|
|
488
|
+
properties.linkText = link.textContent?.trim() || null;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
if (config.includeClasses) {
|
|
492
|
+
properties.linkClass = link.className || null;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// Remove null values
|
|
496
|
+
Object.keys(properties).forEach(key => {
|
|
497
|
+
if (properties[key] === null) {
|
|
498
|
+
delete properties[key];
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
await this.customEvent('link_clicked', properties);
|
|
503
|
+
}
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* Setup automatic form tracking
|
|
509
|
+
*/
|
|
510
|
+
private setupAutomaticFormTracking(config: {
|
|
511
|
+
includeText?: boolean;
|
|
512
|
+
includeClasses?: boolean;
|
|
513
|
+
}): void {
|
|
514
|
+
document.addEventListener('submit', async (event) => {
|
|
515
|
+
const form = event.target as HTMLFormElement;
|
|
516
|
+
const formData = new FormData(form);
|
|
517
|
+
|
|
518
|
+
const properties: Record<string, any> = {
|
|
519
|
+
formId: form.id || null,
|
|
520
|
+
formAction: form.action || null,
|
|
521
|
+
formMethod: form.method || 'get',
|
|
522
|
+
fields: Array.from(formData.keys()),
|
|
523
|
+
page: window.location.pathname,
|
|
524
|
+
timestamp: Date.now()
|
|
525
|
+
};
|
|
526
|
+
|
|
527
|
+
if (config.includeClasses) {
|
|
528
|
+
properties.formClass = form.className || null;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Remove null values
|
|
532
|
+
Object.keys(properties).forEach(key => {
|
|
533
|
+
if (properties[key] === null) {
|
|
534
|
+
delete properties[key];
|
|
535
|
+
}
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
await this.customEvent('form_submitted', properties);
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
|
|
359
542
|
/**
|
|
360
543
|
* Cleanup navigation tracking
|
|
361
544
|
*/
|