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.
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "humanbehavior-js",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "SDK for HumanBehavior session and event recording",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/index.js",
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
- const customEventData = {
334
- eventName: eventName,
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
  */