humanbehavior-js 0.4.16 → 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.
- package/dist/cjs/wizard/index.cjs +6 -8
- package/dist/cjs/wizard/index.cjs.map +1 -1
- package/dist/cli/ai-auto-install.js +6 -8
- package/dist/cli/ai-auto-install.js.map +1 -1
- package/dist/esm/wizard/index.js +6 -8
- package/dist/esm/wizard/index.js.map +1 -1
- package/package/WIZARD_USAGE_GUIDE.md +381 -0
- package/package/canvas-recording-demo.html +143 -0
- package/package/clean-console-demo.html +39 -0
- package/package/dist/cjs/angular/index.cjs +14354 -0
- package/package/dist/cjs/angular/index.cjs.map +1 -0
- package/package/dist/cjs/index.cjs +14323 -0
- package/package/dist/cjs/index.cjs.map +1 -0
- package/package/dist/cjs/install-wizard.cjs +1530 -0
- package/package/dist/cjs/install-wizard.cjs.map +1 -0
- package/package/dist/cjs/react/index.cjs +14478 -0
- package/package/dist/cjs/react/index.cjs.map +1 -0
- package/package/dist/cjs/remix/index.cjs +14452 -0
- package/package/dist/cjs/remix/index.cjs.map +1 -0
- package/package/dist/cjs/svelte/index.cjs +14308 -0
- package/package/dist/cjs/svelte/index.cjs.map +1 -0
- package/package/dist/cjs/vue/index.cjs +14317 -0
- package/package/dist/cjs/vue/index.cjs.map +1 -0
- package/package/dist/cjs/wizard/index.cjs +3446 -0
- package/package/dist/cjs/wizard/index.cjs.map +1 -0
- package/package/dist/cli/ai-auto-install.cjs +57161 -0
- package/package/dist/cli/ai-auto-install.cjs.map +1 -0
- package/package/dist/cli/ai-auto-install.js +1969 -0
- package/package/dist/cli/ai-auto-install.js.map +1 -0
- package/package/dist/cli/auto-install.cjs +56352 -0
- package/package/dist/cli/auto-install.cjs.map +1 -0
- package/package/dist/cli/auto-install.js +1957 -0
- package/package/dist/cli/auto-install.js.map +1 -0
- package/package/dist/esm/angular/index.js +14350 -0
- package/package/dist/esm/angular/index.js.map +1 -0
- package/package/dist/esm/index.js +14309 -0
- package/package/dist/esm/index.js.map +1 -0
- package/package/dist/esm/install-wizard.js +1507 -0
- package/package/dist/esm/install-wizard.js.map +1 -0
- package/package/dist/esm/react/index.js +14472 -0
- package/package/dist/esm/react/index.js.map +1 -0
- package/package/dist/esm/remix/index.js +14448 -0
- package/package/dist/esm/remix/index.js.map +1 -0
- package/package/dist/esm/svelte/index.js +14306 -0
- package/package/dist/esm/svelte/index.js.map +1 -0
- package/package/dist/esm/vue/index.js +14315 -0
- package/package/dist/esm/vue/index.js.map +1 -0
- package/package/dist/esm/wizard/index.js +3415 -0
- package/package/dist/esm/wizard/index.js.map +1 -0
- package/package/dist/index.min.js +2 -0
- package/package/dist/index.min.js.map +1 -0
- package/package/dist/types/angular/index.d.ts +267 -0
- package/package/dist/types/index.d.ts +373 -0
- package/package/dist/types/install-wizard.d.ts +156 -0
- package/package/dist/types/react/index.d.ts +255 -0
- package/package/dist/types/remix/index.d.ts +246 -0
- package/package/dist/types/svelte/index.d.ts +232 -0
- package/package/dist/types/vue/index.d.ts +15 -0
- package/package/dist/types/wizard/index.d.ts +523 -0
- package/package/package.json +105 -0
- package/package/readme.md +281 -0
- package/package/rollup.config.js +422 -0
- package/package/simple-demo.html +26 -0
- package/package/simple-spa.html +838 -0
- package/package/src/angular/index.ts +79 -0
- package/package/src/api.ts +376 -0
- package/package/src/index.ts +28 -0
- package/package/src/react/AutoInstallWizard.tsx +557 -0
- package/package/src/react/browser.ts +8 -0
- package/package/src/react/index.tsx +308 -0
- package/package/src/redact.ts +521 -0
- package/package/src/remix/index.ts +16 -0
- package/package/src/svelte/index.ts +14 -0
- package/package/src/tracker.ts +1319 -0
- package/package/src/types/clack.d.ts +31 -0
- package/package/src/utils/logger.ts +144 -0
- package/package/src/vue/index.ts +29 -0
- package/package/src/wizard/README.md +114 -0
- package/package/src/wizard/ai/ai-install-wizard.ts +897 -0
- package/package/src/wizard/ai/manual-framework-wizard.ts +238 -0
- package/package/src/wizard/cli/ai-auto-install.ts +243 -0
- package/package/src/wizard/cli/auto-install.ts +224 -0
- package/package/src/wizard/core/install-wizard.ts +1744 -0
- package/package/src/wizard/index.ts +23 -0
- package/package/src/wizard/services/centralized-ai-service.ts +668 -0
- package/package/src/wizard/services/remote-ai-service.ts +240 -0
- package/package/tsconfig.json +24 -0
- package/package.json +1 -1
- 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
|
+
}
|