humanbehavior-js 0.1.2 → 0.1.3
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 +201 -15
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.js +201 -15
- 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 +51 -0
- package/src/tracker.ts +201 -19
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
|
@@ -219,4 +219,55 @@ export class HumanBehaviorAPI {
|
|
|
219
219
|
data
|
|
220
220
|
);
|
|
221
221
|
}
|
|
222
|
+
|
|
223
|
+
async sendCustomEvent(sessionId: string, eventName: string, eventProperties?: Record<string, any>) {
|
|
224
|
+
try {
|
|
225
|
+
const response = await fetch(`${this.baseUrl}/api/ingestion/customEvent`, {
|
|
226
|
+
method: 'POST',
|
|
227
|
+
headers: {
|
|
228
|
+
'Content-Type': 'application/json',
|
|
229
|
+
'Authorization': `Bearer ${this.apiKey}`
|
|
230
|
+
},
|
|
231
|
+
body: JSON.stringify({
|
|
232
|
+
sessionId: sessionId,
|
|
233
|
+
eventName: eventName,
|
|
234
|
+
eventProperties: eventProperties || {}
|
|
235
|
+
})
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
if (!response.ok) {
|
|
239
|
+
throw new Error(`Failed to send custom event: ${response.statusText}`);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return await response.json();
|
|
243
|
+
} catch (error) {
|
|
244
|
+
logError('Error sending custom event:', error);
|
|
245
|
+
throw error;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async sendCustomEventBatch(sessionId: string, events: Array<{ eventName: string; eventProperties?: Record<string, any> }>) {
|
|
250
|
+
try {
|
|
251
|
+
const response = await fetch(`${this.baseUrl}/api/ingestion/customEvent/batch`, {
|
|
252
|
+
method: 'POST',
|
|
253
|
+
headers: {
|
|
254
|
+
'Content-Type': 'application/json',
|
|
255
|
+
'Authorization': `Bearer ${this.apiKey}`
|
|
256
|
+
},
|
|
257
|
+
body: JSON.stringify({
|
|
258
|
+
sessionId: sessionId,
|
|
259
|
+
events: events
|
|
260
|
+
})
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
if (!response.ok) {
|
|
264
|
+
throw new Error(`Failed to send custom event batch: ${response.statusText}`);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return await response.json();
|
|
268
|
+
} catch (error) {
|
|
269
|
+
logError('Error sending custom event batch:', error);
|
|
270
|
+
throw error;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
222
273
|
}
|
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';
|
|
@@ -330,32 +343,201 @@ export class HumanBehaviorTracker {
|
|
|
330
343
|
if (!this.initialized) return;
|
|
331
344
|
|
|
332
345
|
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
|
-
});
|
|
346
|
+
// Send custom event directly to the API
|
|
347
|
+
await this.api.sendCustomEvent(this.sessionId, eventName, properties);
|
|
352
348
|
|
|
353
349
|
logDebug(`Custom event tracked: ${eventName}`, properties);
|
|
354
350
|
} catch (error) {
|
|
355
351
|
logError('Failed to track custom event:', error);
|
|
352
|
+
|
|
353
|
+
// Fallback: add to event stream if direct API call fails
|
|
354
|
+
try {
|
|
355
|
+
const customEventData = {
|
|
356
|
+
eventName: eventName,
|
|
357
|
+
properties: properties || {},
|
|
358
|
+
timestamp: new Date().toISOString(),
|
|
359
|
+
url: window.location.href,
|
|
360
|
+
pathname: window.location.pathname
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
await this.addEvent({
|
|
364
|
+
type: 5, // Custom event type
|
|
365
|
+
data: {
|
|
366
|
+
payload: {
|
|
367
|
+
eventType: 'custom',
|
|
368
|
+
...customEventData
|
|
369
|
+
}
|
|
370
|
+
},
|
|
371
|
+
timestamp: Date.now()
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
logDebug(`Custom event added to event stream as fallback: ${eventName}`);
|
|
375
|
+
} catch (fallbackError) {
|
|
376
|
+
logError('Failed to add custom event to event stream as fallback:', fallbackError);
|
|
377
|
+
}
|
|
356
378
|
}
|
|
357
379
|
}
|
|
358
380
|
|
|
381
|
+
/**
|
|
382
|
+
* Setup automatic tracking for buttons, links, and forms
|
|
383
|
+
*/
|
|
384
|
+
private setupAutomaticTracking(options?: {
|
|
385
|
+
trackButtons?: boolean;
|
|
386
|
+
trackLinks?: boolean;
|
|
387
|
+
trackForms?: boolean;
|
|
388
|
+
includeText?: boolean;
|
|
389
|
+
includeClasses?: boolean;
|
|
390
|
+
}): void {
|
|
391
|
+
if (!isBrowser) return;
|
|
392
|
+
|
|
393
|
+
const config = {
|
|
394
|
+
trackButtons: options?.trackButtons !== false,
|
|
395
|
+
trackLinks: options?.trackLinks !== false,
|
|
396
|
+
trackForms: options?.trackForms !== false,
|
|
397
|
+
includeText: options?.includeText !== false,
|
|
398
|
+
includeClasses: options?.includeClasses || false
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
logDebug('Setting up automatic tracking with config:', config);
|
|
402
|
+
|
|
403
|
+
// Setup button tracking
|
|
404
|
+
if (config.trackButtons) {
|
|
405
|
+
this.setupAutomaticButtonTracking(config);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Setup link tracking
|
|
409
|
+
if (config.trackLinks) {
|
|
410
|
+
this.setupAutomaticLinkTracking(config);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// Setup form tracking
|
|
414
|
+
if (config.trackForms) {
|
|
415
|
+
this.setupAutomaticFormTracking(config);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Setup automatic button tracking
|
|
421
|
+
*/
|
|
422
|
+
private setupAutomaticButtonTracking(config: {
|
|
423
|
+
includeText?: boolean;
|
|
424
|
+
includeClasses?: boolean;
|
|
425
|
+
}): void {
|
|
426
|
+
document.addEventListener('click', async (event) => {
|
|
427
|
+
const target = event.target as HTMLElement;
|
|
428
|
+
|
|
429
|
+
// Track button clicks
|
|
430
|
+
if (target.tagName === 'BUTTON' || target.closest('button')) {
|
|
431
|
+
const button = target.tagName === 'BUTTON'
|
|
432
|
+
? target as HTMLButtonElement
|
|
433
|
+
: target.closest('button') as HTMLButtonElement;
|
|
434
|
+
|
|
435
|
+
const properties: Record<string, any> = {
|
|
436
|
+
buttonId: button.id || null,
|
|
437
|
+
buttonType: button.type || 'button',
|
|
438
|
+
page: window.location.pathname,
|
|
439
|
+
timestamp: Date.now()
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
if (config.includeText) {
|
|
443
|
+
properties.buttonText = button.textContent?.trim() || null;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
if (config.includeClasses) {
|
|
447
|
+
properties.buttonClass = button.className || null;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Remove null values
|
|
451
|
+
Object.keys(properties).forEach(key => {
|
|
452
|
+
if (properties[key] === null) {
|
|
453
|
+
delete properties[key];
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
await this.customEvent('button_clicked', properties);
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Setup automatic link tracking
|
|
464
|
+
*/
|
|
465
|
+
private setupAutomaticLinkTracking(config: {
|
|
466
|
+
includeText?: boolean;
|
|
467
|
+
includeClasses?: boolean;
|
|
468
|
+
}): void {
|
|
469
|
+
document.addEventListener('click', async (event) => {
|
|
470
|
+
const target = event.target as HTMLElement;
|
|
471
|
+
|
|
472
|
+
// Track link clicks
|
|
473
|
+
if (target.tagName === 'A' || target.closest('a')) {
|
|
474
|
+
const link = target.tagName === 'A'
|
|
475
|
+
? target as HTMLAnchorElement
|
|
476
|
+
: target.closest('a') as HTMLAnchorElement;
|
|
477
|
+
|
|
478
|
+
const properties: Record<string, any> = {
|
|
479
|
+
linkUrl: link.href || null,
|
|
480
|
+
linkId: link.id || null,
|
|
481
|
+
linkTarget: link.target || null,
|
|
482
|
+
page: window.location.pathname,
|
|
483
|
+
timestamp: Date.now()
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
if (config.includeText) {
|
|
487
|
+
properties.linkText = link.textContent?.trim() || null;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
if (config.includeClasses) {
|
|
491
|
+
properties.linkClass = link.className || null;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Remove null values
|
|
495
|
+
Object.keys(properties).forEach(key => {
|
|
496
|
+
if (properties[key] === null) {
|
|
497
|
+
delete properties[key];
|
|
498
|
+
}
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
await this.customEvent('link_clicked', properties);
|
|
502
|
+
}
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Setup automatic form tracking
|
|
508
|
+
*/
|
|
509
|
+
private setupAutomaticFormTracking(config: {
|
|
510
|
+
includeText?: boolean;
|
|
511
|
+
includeClasses?: boolean;
|
|
512
|
+
}): void {
|
|
513
|
+
document.addEventListener('submit', async (event) => {
|
|
514
|
+
const form = event.target as HTMLFormElement;
|
|
515
|
+
const formData = new FormData(form);
|
|
516
|
+
|
|
517
|
+
const properties: Record<string, any> = {
|
|
518
|
+
formId: form.id || null,
|
|
519
|
+
formAction: form.action || null,
|
|
520
|
+
formMethod: form.method || 'get',
|
|
521
|
+
fields: Array.from(formData.keys()),
|
|
522
|
+
page: window.location.pathname,
|
|
523
|
+
timestamp: Date.now()
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
if (config.includeClasses) {
|
|
527
|
+
properties.formClass = form.className || null;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Remove null values
|
|
531
|
+
Object.keys(properties).forEach(key => {
|
|
532
|
+
if (properties[key] === null) {
|
|
533
|
+
delete properties[key];
|
|
534
|
+
}
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
await this.customEvent('form_submitted', properties);
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
|
|
359
541
|
/**
|
|
360
542
|
* Cleanup navigation tracking
|
|
361
543
|
*/
|