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.
@@ -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.4",
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
@@ -41,29 +41,41 @@ export class HumanBehaviorAPI {
41
41
  referrer = document.referrer;
42
42
  }
43
43
 
44
- const response = await fetch(`${this.baseUrl}/api/ingestion/init`, {
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
- if (!response.ok) {
60
- throw new Error(`Failed to initialize ingestion: ${response.statusText}`);
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
- const responseJson = await response.json();
64
- return {
65
- sessionId: responseJson.sessionId,
66
- endUserId: responseJson.endUserId
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
- 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
- });
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
  */