humanbehavior-js 0.4.15 → 0.4.17

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.
Files changed (89) hide show
  1. package/dist/cjs/wizard/index.cjs +6 -8
  2. package/dist/cjs/wizard/index.cjs.map +1 -1
  3. package/dist/cli/ai-auto-install.js +6 -8
  4. package/dist/cli/ai-auto-install.js.map +1 -1
  5. package/dist/esm/wizard/index.js +6 -8
  6. package/dist/esm/wizard/index.js.map +1 -1
  7. package/package/WIZARD_USAGE_GUIDE.md +381 -0
  8. package/package/canvas-recording-demo.html +143 -0
  9. package/package/clean-console-demo.html +39 -0
  10. package/package/dist/cjs/angular/index.cjs +14354 -0
  11. package/package/dist/cjs/angular/index.cjs.map +1 -0
  12. package/package/dist/cjs/index.cjs +14323 -0
  13. package/package/dist/cjs/index.cjs.map +1 -0
  14. package/package/dist/cjs/install-wizard.cjs +1530 -0
  15. package/package/dist/cjs/install-wizard.cjs.map +1 -0
  16. package/package/dist/cjs/react/index.cjs +14478 -0
  17. package/package/dist/cjs/react/index.cjs.map +1 -0
  18. package/package/dist/cjs/remix/index.cjs +14452 -0
  19. package/package/dist/cjs/remix/index.cjs.map +1 -0
  20. package/package/dist/cjs/svelte/index.cjs +14308 -0
  21. package/package/dist/cjs/svelte/index.cjs.map +1 -0
  22. package/package/dist/cjs/vue/index.cjs +14317 -0
  23. package/package/dist/cjs/vue/index.cjs.map +1 -0
  24. package/package/dist/cjs/wizard/index.cjs +3446 -0
  25. package/package/dist/cjs/wizard/index.cjs.map +1 -0
  26. package/package/dist/cli/ai-auto-install.cjs +57161 -0
  27. package/package/dist/cli/ai-auto-install.cjs.map +1 -0
  28. package/package/dist/cli/ai-auto-install.js +1969 -0
  29. package/package/dist/cli/ai-auto-install.js.map +1 -0
  30. package/package/dist/cli/auto-install.cjs +56352 -0
  31. package/package/dist/cli/auto-install.cjs.map +1 -0
  32. package/package/dist/cli/auto-install.js +1957 -0
  33. package/package/dist/cli/auto-install.js.map +1 -0
  34. package/package/dist/esm/angular/index.js +14350 -0
  35. package/package/dist/esm/angular/index.js.map +1 -0
  36. package/package/dist/esm/index.js +14309 -0
  37. package/package/dist/esm/index.js.map +1 -0
  38. package/package/dist/esm/install-wizard.js +1507 -0
  39. package/package/dist/esm/install-wizard.js.map +1 -0
  40. package/package/dist/esm/react/index.js +14472 -0
  41. package/package/dist/esm/react/index.js.map +1 -0
  42. package/package/dist/esm/remix/index.js +14448 -0
  43. package/package/dist/esm/remix/index.js.map +1 -0
  44. package/package/dist/esm/svelte/index.js +14306 -0
  45. package/package/dist/esm/svelte/index.js.map +1 -0
  46. package/package/dist/esm/vue/index.js +14315 -0
  47. package/package/dist/esm/vue/index.js.map +1 -0
  48. package/package/dist/esm/wizard/index.js +3415 -0
  49. package/package/dist/esm/wizard/index.js.map +1 -0
  50. package/package/dist/index.min.js +2 -0
  51. package/package/dist/index.min.js.map +1 -0
  52. package/package/dist/types/angular/index.d.ts +267 -0
  53. package/package/dist/types/index.d.ts +373 -0
  54. package/package/dist/types/install-wizard.d.ts +156 -0
  55. package/package/dist/types/react/index.d.ts +255 -0
  56. package/package/dist/types/remix/index.d.ts +246 -0
  57. package/package/dist/types/svelte/index.d.ts +232 -0
  58. package/package/dist/types/vue/index.d.ts +15 -0
  59. package/package/dist/types/wizard/index.d.ts +523 -0
  60. package/package/package.json +105 -0
  61. package/package/readme.md +281 -0
  62. package/package/rollup.config.js +422 -0
  63. package/package/simple-demo.html +26 -0
  64. package/package/simple-spa.html +838 -0
  65. package/package/src/angular/index.ts +79 -0
  66. package/package/src/api.ts +376 -0
  67. package/package/src/index.ts +28 -0
  68. package/package/src/react/AutoInstallWizard.tsx +557 -0
  69. package/package/src/react/browser.ts +8 -0
  70. package/package/src/react/index.tsx +308 -0
  71. package/package/src/redact.ts +521 -0
  72. package/package/src/remix/index.ts +16 -0
  73. package/package/src/svelte/index.ts +14 -0
  74. package/package/src/tracker.ts +1319 -0
  75. package/package/src/types/clack.d.ts +31 -0
  76. package/package/src/utils/logger.ts +144 -0
  77. package/package/src/vue/index.ts +29 -0
  78. package/package/src/wizard/README.md +114 -0
  79. package/package/src/wizard/ai/ai-install-wizard.ts +897 -0
  80. package/package/src/wizard/ai/manual-framework-wizard.ts +238 -0
  81. package/package/src/wizard/cli/ai-auto-install.ts +243 -0
  82. package/package/src/wizard/cli/auto-install.ts +224 -0
  83. package/package/src/wizard/core/install-wizard.ts +1744 -0
  84. package/package/src/wizard/index.ts +23 -0
  85. package/package/src/wizard/services/centralized-ai-service.ts +668 -0
  86. package/package/src/wizard/services/remote-ai-service.ts +240 -0
  87. package/package/tsconfig.json +24 -0
  88. package/package.json +1 -1
  89. package/src/wizard/cli/ai-auto-install.ts +4 -6
@@ -0,0 +1,79 @@
1
+ import { HumanBehaviorTracker } from '../index.js';
2
+
3
+ // Angular NgModule for legacy Angular applications
4
+ export class HumanBehaviorModule {
5
+ static forRoot(config: {
6
+ apiKey: string;
7
+ ingestionUrl?: string;
8
+ logLevel?: 'none' | 'error' | 'warn' | 'info' | 'debug';
9
+ redactFields?: string[];
10
+ suppressConsoleErrors?: boolean;
11
+ recordCanvas?: boolean; // Enable canvas recording with PostHog-style protection
12
+ }) {
13
+ return {
14
+ ngModule: HumanBehaviorModule,
15
+ providers: [
16
+ {
17
+ provide: 'HUMANBEHAVIOR_API_KEY',
18
+ useValue: config.apiKey
19
+ },
20
+ {
21
+ provide: HumanBehaviorTracker,
22
+ useFactory: (apiKey: string) => {
23
+ return HumanBehaviorTracker.init(apiKey, {
24
+ ingestionUrl: config.ingestionUrl,
25
+ logLevel: config.logLevel,
26
+ redactFields: config.redactFields,
27
+ suppressConsoleErrors: config.suppressConsoleErrors,
28
+ recordCanvas: config.recordCanvas, // Pass canvas recording option
29
+ });
30
+ },
31
+ deps: ['HUMANBEHAVIOR_API_KEY']
32
+ }
33
+ ]
34
+ };
35
+ }
36
+ }
37
+
38
+ // Angular service for dependency injection
39
+ export class HumanBehaviorService {
40
+ private tracker: HumanBehaviorTracker;
41
+
42
+ constructor(apiKey: string, options?: {
43
+ ingestionUrl?: string;
44
+ logLevel?: 'none' | 'error' | 'warn' | 'info' | 'debug';
45
+ redactFields?: string[];
46
+ suppressConsoleErrors?: boolean;
47
+ recordCanvas?: boolean; // Enable canvas recording with PostHog-style protection
48
+ }) {
49
+ this.tracker = HumanBehaviorTracker.init(apiKey, options);
50
+ }
51
+
52
+ // Expose core tracker methods
53
+ identifyUser(userProperties: Record<string, any>) {
54
+ return this.tracker.identifyUser({ userProperties });
55
+ }
56
+
57
+ getSessionId() {
58
+ return this.tracker.getSessionId();
59
+ }
60
+
61
+ setRedactedFields(fields: string[]) {
62
+ return this.tracker.setRedactedFields(fields);
63
+ }
64
+
65
+ getRedactedFields() {
66
+ return this.tracker.getRedactedFields();
67
+ }
68
+ }
69
+
70
+ // Helper function for standalone Angular initialization
71
+ export function initializeHumanBehavior(apiKey: string, options?: {
72
+ ingestionUrl?: string;
73
+ logLevel?: 'none' | 'error' | 'warn' | 'info' | 'debug';
74
+ redactFields?: string[];
75
+ suppressConsoleErrors?: boolean;
76
+ recordCanvas?: boolean; // Enable canvas recording with PostHog-style protection
77
+ }): HumanBehaviorTracker {
78
+ return HumanBehaviorTracker.init(apiKey, options);
79
+ }
@@ -0,0 +1,376 @@
1
+ import { logError, logInfo, logDebug, logWarn } from './utils/logger';
2
+
3
+ export const MAX_CHUNK_SIZE_BYTES = 1024 * 1024; // 1MB chunk size - more conservative
4
+
5
+ export function isChunkSizeExceeded(currentChunk: any[], newEvent: any, sessionId: string): boolean {
6
+ const nextChunkSize = new TextEncoder().encode(JSON.stringify({
7
+ sessionId,
8
+ events: [...currentChunk, newEvent]
9
+ })).length;
10
+
11
+ return nextChunkSize > MAX_CHUNK_SIZE_BYTES;
12
+ }
13
+
14
+ export function validateSingleEventSize(event: any, sessionId: string): void {
15
+ const singleEventSize = new TextEncoder().encode(JSON.stringify({
16
+ sessionId,
17
+ events: [event]
18
+ })).length;
19
+
20
+ if (singleEventSize > MAX_CHUNK_SIZE_BYTES) {
21
+ // Instead of throwing, log a warning and suggest reducing event size
22
+ logWarn(`Single event size (${singleEventSize} bytes) exceeds maximum chunk size (${MAX_CHUNK_SIZE_BYTES} bytes). Consider reducing event data size.`);
23
+ }
24
+ }
25
+
26
+
27
+
28
+
29
+
30
+ export function splitLargeEvent(event: any, sessionId: string): any[] {
31
+ // ✅ SIMPLE VALIDATION
32
+ if (!event || typeof event !== 'object') {
33
+ return [];
34
+ }
35
+
36
+ const eventSize = new TextEncoder().encode(JSON.stringify({
37
+ sessionId,
38
+ events: [event]
39
+ })).length;
40
+
41
+ if (eventSize <= MAX_CHUNK_SIZE_BYTES) {
42
+ return [event];
43
+ }
44
+
45
+ // If event is too large, try to split it by removing large properties
46
+ const simplifiedEvent = { ...event };
47
+
48
+ // Remove potentially large properties
49
+ const largeProperties = ['screenshot', 'html', 'dom', 'fullText', 'innerHTML', 'outerHTML'];
50
+ largeProperties.forEach(prop => {
51
+ if (simplifiedEvent[prop]) {
52
+ delete simplifiedEvent[prop];
53
+ }
54
+ });
55
+
56
+ // Check if simplified event is now small enough
57
+ const simplifiedSize = new TextEncoder().encode(JSON.stringify({
58
+ sessionId,
59
+ events: [simplifiedEvent]
60
+ })).length;
61
+
62
+ if (simplifiedSize <= MAX_CHUNK_SIZE_BYTES) {
63
+ return [simplifiedEvent];
64
+ }
65
+
66
+ // If still too large, create a minimal event
67
+ const minimalEvent = {
68
+ type: event.type,
69
+ timestamp: event.timestamp,
70
+ url: event.url,
71
+ pathname: event.pathname,
72
+ // Keep only essential properties
73
+ ...Object.fromEntries(
74
+ Object.entries(event).filter(([key, value]) =>
75
+ !largeProperties.includes(key) &&
76
+ typeof value !== 'object' &&
77
+ typeof value !== 'string' ||
78
+ (typeof value === 'string' && value.length < 1000)
79
+ )
80
+ )
81
+ };
82
+
83
+ return [minimalEvent];
84
+ }
85
+
86
+ export class HumanBehaviorAPI {
87
+ private apiKey: string;
88
+ private baseUrl: string;
89
+
90
+ constructor({ apiKey, ingestionUrl }: { apiKey: string, ingestionUrl: string }) {
91
+ this.apiKey = apiKey;
92
+ this.baseUrl = ingestionUrl;
93
+ }
94
+
95
+ public async init(sessionId: string, userId: string | null) {
96
+ // Get current page URL and referrer if in browser environment
97
+ let entryURL = null;
98
+ let referrer = null;
99
+
100
+ if (typeof window !== 'undefined') {
101
+ entryURL = window.location.href;
102
+ referrer = document.referrer;
103
+ }
104
+
105
+ logInfo('API init called with:', { sessionId, userId, entryURL, referrer, baseUrl: this.baseUrl });
106
+
107
+ try {
108
+ const response = await fetch(`${this.baseUrl}/api/ingestion/init`, {
109
+ method: 'POST',
110
+ headers: {
111
+ 'Content-Type': 'application/json',
112
+ 'Authorization': `Bearer ${this.apiKey}`,
113
+ 'Referer': referrer || ''
114
+ },
115
+ body: JSON.stringify({
116
+ sessionId: sessionId,
117
+ endUserId: userId,
118
+ entryURL: entryURL,
119
+ referrer: referrer
120
+ })
121
+ });
122
+
123
+ logInfo('API init response status:', response.status);
124
+
125
+ if (!response.ok) {
126
+ const errorText = await response.text();
127
+ logError('API init failed:', response.status, errorText);
128
+ throw new Error(`Failed to initialize ingestion: ${response.statusText} - ${errorText}`);
129
+ }
130
+
131
+ const responseJson = await response.json();
132
+ logInfo('API init success:', responseJson);
133
+ return {
134
+ sessionId: responseJson.sessionId,
135
+ endUserId: responseJson.endUserId
136
+ }
137
+ } catch (error) {
138
+ logError('API init error:', error);
139
+ throw error;
140
+ }
141
+ }
142
+
143
+ async sendEvents(events: any[], sessionId: string, userId: string) {
144
+ // ✅ SIMPLE VALIDATION FOR ALL EVENTS
145
+ const validEvents = events.filter(event => event && typeof event === 'object');
146
+
147
+ const response = await fetch(`${this.baseUrl}/api/ingestion/events`, {
148
+ method: 'POST',
149
+ headers: {
150
+ 'Content-Type': 'application/json',
151
+ 'Authorization': `Bearer ${this.apiKey}`
152
+ },
153
+ body: JSON.stringify({
154
+ sessionId,
155
+ events: validEvents,
156
+ endUserId: userId
157
+ })
158
+ });
159
+
160
+ if (!response.ok) {
161
+ throw new Error(`Failed to send events: ${response.statusText}`);
162
+ }
163
+ }
164
+
165
+ async sendEventsChunked(events: any[], sessionId: string, userId?: string) {
166
+ try {
167
+ const results = [];
168
+ let currentChunk: any[] = [];
169
+
170
+ for (const event of events) {
171
+ // ✅ SIMPLE VALIDATION FOR ALL EVENTS
172
+ if (!event || typeof event !== 'object') {
173
+ continue;
174
+ }
175
+
176
+ if (isChunkSizeExceeded(currentChunk, event, sessionId)) {
177
+ // If current chunk is not empty, send it first
178
+ if (currentChunk.length > 0) {
179
+ const response = await fetch(`${this.baseUrl}/api/ingestion/events`, {
180
+ method: 'POST',
181
+ headers: {
182
+ 'Content-Type': 'application/json',
183
+ 'Authorization': `Bearer ${this.apiKey}`
184
+ },
185
+ body: JSON.stringify({
186
+ sessionId,
187
+ events: currentChunk,
188
+ endUserId: userId
189
+ })
190
+ });
191
+
192
+ if (!response.ok) {
193
+ throw new Error(`Failed to send events: ${response.statusText}`);
194
+ }
195
+
196
+ results.push(await response.json());
197
+ currentChunk = [];
198
+ }
199
+
200
+ // Handle large events by splitting them
201
+ const splitEvents = splitLargeEvent(event, sessionId);
202
+
203
+ // Start new chunk with the split events
204
+ currentChunk = splitEvents;
205
+ } else {
206
+ // Add event to current chunk
207
+ currentChunk.push(event);
208
+ }
209
+ }
210
+
211
+ // Send any remaining events
212
+ if (currentChunk.length > 0) {
213
+ const response = await fetch(`${this.baseUrl}/api/ingestion/events`, {
214
+ method: 'POST',
215
+ headers: {
216
+ 'Content-Type': 'application/json',
217
+ 'Authorization': `Bearer ${this.apiKey}`
218
+ },
219
+ body: JSON.stringify({
220
+ sessionId,
221
+ events: currentChunk,
222
+ endUserId: userId
223
+ })
224
+ });
225
+
226
+ if (!response.ok) {
227
+ throw new Error(`Failed to send events: ${response.statusText}`);
228
+ }
229
+
230
+ results.push(await response.json());
231
+ }
232
+
233
+ return results.flat();
234
+ } catch (error) {
235
+ logError('Error sending events:', error);
236
+ throw error;
237
+ }
238
+ }
239
+
240
+ async sendUserData(userId: string, userData: Record<string, any>, sessionId: string) {
241
+ try {
242
+ const payload = {
243
+ userId: userId,
244
+ userAttributes: userData,
245
+ sessionId: sessionId,
246
+ posthogName: userData.email || userData.name || null // Update user name with email
247
+ };
248
+
249
+ logDebug('Sending user data to server:', payload);
250
+
251
+ const response = await fetch(`${this.baseUrl}/api/ingestion/user`, {
252
+ method: 'POST',
253
+ headers: {
254
+ 'Content-Type': 'application/json',
255
+ 'Authorization': `Bearer ${this.apiKey}`
256
+ },
257
+ body: JSON.stringify(payload)
258
+ });
259
+
260
+ if (!response.ok) {
261
+ throw new Error(`Failed to send user data: ${response.statusText} with API key: ${this.apiKey}`);
262
+ }
263
+
264
+ const result = await response.json();
265
+ logDebug('Server response:', result);
266
+ return result;
267
+ } catch (error) {
268
+ logError('Error sending user data:', error);
269
+ throw error;
270
+ }
271
+ }
272
+
273
+ async sendUserAuth(userId: string, userData: Record<string, any>, sessionId: string, authFields: string[]) {
274
+ try {
275
+ const response = await fetch(`${this.baseUrl}/api/ingestion/user/auth`, {
276
+ method: 'POST',
277
+ headers: {
278
+ 'Content-Type': 'application/json',
279
+ 'Authorization': `Bearer ${this.apiKey}`
280
+ },
281
+ body: JSON.stringify({
282
+ userId: userId,
283
+ userAttributes: userData,
284
+ sessionId: sessionId,
285
+ authFields: authFields
286
+ })
287
+ });
288
+
289
+ if (!response.ok) {
290
+ throw new Error(`Failed to authenticate user: ${response.statusText} with API key: ${this.apiKey}`);
291
+ }
292
+ // Returns: { success: true, message: '...', userId: '...' }
293
+ return await response.json();
294
+ } catch (error) {
295
+ logError('Error authenticating user:', error);
296
+ throw error;
297
+ }
298
+ }
299
+
300
+ public sendBeaconEvents(events: any[], sessionId: string) {
301
+ // Create JSON payload that matches the server's expected format
302
+ const payload = {
303
+ sessionId: sessionId,
304
+ events: events,
305
+ endUserId: null, // Beacon doesn't have user context
306
+ apiKey: this.apiKey // Include API key in body since beacon can't use headers
307
+ };
308
+
309
+ // Convert to Blob for sendBeacon
310
+ const blob = new Blob([JSON.stringify(payload)], {
311
+ type: 'application/json'
312
+ });
313
+
314
+ const success = navigator.sendBeacon(
315
+ `${this.baseUrl}/api/ingestion/events`,
316
+ blob
317
+ );
318
+
319
+ return success;
320
+ }
321
+
322
+ async sendCustomEvent(sessionId: string, eventName: string, eventProperties?: Record<string, any>) {
323
+ logInfo('[SDK] Sending custom event', { sessionId, eventName, eventProperties });
324
+ try {
325
+ const response = await fetch(`${this.baseUrl}/api/ingestion/customEvent`, {
326
+ method: 'POST',
327
+ headers: {
328
+ 'Content-Type': 'application/json',
329
+ 'Authorization': `Bearer ${this.apiKey}`
330
+ },
331
+ body: JSON.stringify({
332
+ sessionId: sessionId,
333
+ eventName: eventName,
334
+ eventProperties: eventProperties || {}
335
+ })
336
+ });
337
+ logInfo('[SDK] Custom event response', { status: response.status, statusText: response.statusText });
338
+ if (!response.ok) {
339
+ const errorText = await response.text();
340
+ logError('[SDK] Failed to send custom event', { status: response.status, statusText: response.statusText, errorText });
341
+ throw new Error(`Failed to send custom event: ${response.status} ${response.statusText} - ${errorText}`);
342
+ }
343
+ const json = await response.json();
344
+ logDebug('[SDK] Custom event success', json);
345
+ return json;
346
+ } catch (error) {
347
+ logError('[SDK] Error sending custom event', error, { sessionId, eventName, eventProperties });
348
+ throw error;
349
+ }
350
+ }
351
+
352
+ async sendCustomEventBatch(sessionId: string, events: Array<{ eventName: string; eventProperties?: Record<string, any> }>) {
353
+ try {
354
+ const response = await fetch(`${this.baseUrl}/api/ingestion/customEvent/batch`, {
355
+ method: 'POST',
356
+ headers: {
357
+ 'Content-Type': 'application/json',
358
+ 'Authorization': `Bearer ${this.apiKey}`
359
+ },
360
+ body: JSON.stringify({
361
+ sessionId: sessionId,
362
+ events: events
363
+ })
364
+ });
365
+
366
+ if (!response.ok) {
367
+ throw new Error(`Failed to send custom event batch: ${response.statusText}`);
368
+ }
369
+
370
+ return await response.json();
371
+ } catch (error) {
372
+ logError('Error sending custom event batch:', error);
373
+ throw error;
374
+ }
375
+ }
376
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Main entry point for the HumanBehavior SDK
3
+ */
4
+
5
+ import { HumanBehaviorTracker } from './tracker';
6
+
7
+ // Export everything from the tracker module
8
+ export * from './tracker';
9
+
10
+ // Export everything from the API module
11
+ export * from './api';
12
+
13
+ // Export redaction functionality
14
+ export * from './redact';
15
+
16
+ // Export logger functionality
17
+ export * from './utils/logger';
18
+
19
+ // Installation wizard is exported separately to avoid Node.js dependencies in browser bundles
20
+ // Import from 'humanbehavior-js/install-wizard' for Node.js usage
21
+
22
+ // Note: Default export removed to avoid mixed export warnings
23
+ // Use: import { HumanBehaviorTracker } from 'humanbehavior-js'
24
+
25
+ // For UMD builds, expose the main class globally
26
+ if (typeof window !== 'undefined') {
27
+ (window as any).HumanBehaviorTracker = HumanBehaviorTracker;
28
+ }