humanbehavior-js 0.1.8 → 0.2.0
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/.tsbuildinfo +1 -0
- package/dist/cjs/index.js +145 -71
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/react/index.js +492 -26
- package/dist/cjs/react/index.js.map +1 -1
- package/dist/esm/index.js +145 -72
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/react/index.js +474 -10
- package/dist/esm/react/index.js.map +1 -1
- package/dist/index.min.js +1 -15
- package/dist/index.min.js.map +1 -1
- package/dist/types/index.d.ts +23 -8
- package/dist/types/react/index.d.ts +18 -3
- package/package.json +33 -10
- package/readme.md +2 -0
- package/src/api.ts +75 -16
- package/src/index.ts +2 -2
- package/src/react/index.tsx +49 -4
- package/src/tracker.ts +81 -51
- package/rollup.config.js +0 -106
- package/simple-demo.html +0 -26
- package/simple-spa.html +0 -594
- package/tsconfig.json +0 -24
package/dist/types/index.d.ts
CHANGED
|
@@ -89,8 +89,6 @@ declare global {
|
|
|
89
89
|
declare class HumanBehaviorTracker {
|
|
90
90
|
private eventIngestionQueue;
|
|
91
91
|
private queueSizeBytes;
|
|
92
|
-
private rejectedEvents;
|
|
93
|
-
private isProcessingRejectedEvents;
|
|
94
92
|
private sessionId;
|
|
95
93
|
private userProperties;
|
|
96
94
|
private isProcessing;
|
|
@@ -188,19 +186,31 @@ declare class HumanBehaviorTracker {
|
|
|
188
186
|
private trackConsoleEvent;
|
|
189
187
|
private setupPageUnloadHandler;
|
|
190
188
|
viewLogs(): void;
|
|
191
|
-
addUserInfo(
|
|
189
|
+
addUserInfo({ userId, userProperties }: {
|
|
190
|
+
userId: string;
|
|
191
|
+
userProperties: Record<string, any>;
|
|
192
|
+
}): Promise<void>;
|
|
192
193
|
/**
|
|
193
|
-
*
|
|
194
|
+
* Add user identification information to the tracker
|
|
194
195
|
* @param authFields Array of field names to check for existing users (e.g., ['email', 'phoneNumber'])
|
|
195
196
|
*/
|
|
196
|
-
auth
|
|
197
|
+
private auth;
|
|
197
198
|
start(): Promise<void>;
|
|
198
199
|
stop(): Promise<void>;
|
|
199
200
|
addEvent(event: any): Promise<void>;
|
|
200
|
-
private processRejectedEvents;
|
|
201
201
|
private flush;
|
|
202
202
|
private setCookie;
|
|
203
203
|
getCookie(name: string): string | null;
|
|
204
|
+
/**
|
|
205
|
+
* Delete a cookie by setting its expiration date to the past
|
|
206
|
+
* @param name The name of the cookie to delete
|
|
207
|
+
*/
|
|
208
|
+
private deleteCookie;
|
|
209
|
+
/**
|
|
210
|
+
* Log out the current user by clearing all user-related data
|
|
211
|
+
* This will delete the user ID cookie, clear localStorage, and reset user properties
|
|
212
|
+
*/
|
|
213
|
+
logout(): void;
|
|
204
214
|
/**
|
|
205
215
|
* Start redaction functionality for sensitive input fields
|
|
206
216
|
* @param options Optional configuration for redaction behavior
|
|
@@ -260,6 +270,7 @@ declare class HumanBehaviorTracker {
|
|
|
260
270
|
declare const MAX_CHUNK_SIZE_BYTES: number;
|
|
261
271
|
declare function isChunkSizeExceeded(currentChunk: any[], newEvent: any, sessionId: string): boolean;
|
|
262
272
|
declare function validateSingleEventSize(event: any, sessionId: string): void;
|
|
273
|
+
declare function splitLargeEvent(event: any, sessionId: string): any[];
|
|
263
274
|
declare class HumanBehaviorAPI {
|
|
264
275
|
private apiKey;
|
|
265
276
|
private baseUrl;
|
|
@@ -272,7 +283,7 @@ declare class HumanBehaviorAPI {
|
|
|
272
283
|
endUserId: any;
|
|
273
284
|
}>;
|
|
274
285
|
sendEvents(events: any[], sessionId: string, userId: string): Promise<void>;
|
|
275
|
-
sendEventsChunked(events: any[], sessionId: string): Promise<any[]>;
|
|
286
|
+
sendEventsChunked(events: any[], sessionId: string, userId?: string): Promise<any[]>;
|
|
276
287
|
sendUserData(userId: string, userData: Record<string, any>, sessionId: string): Promise<any>;
|
|
277
288
|
sendUserAuth(userId: string, userData: Record<string, any>, sessionId: string, authFields: string[]): Promise<any>;
|
|
278
289
|
sendBeaconEvents(events: any[], sessionId: string): boolean;
|
|
@@ -316,5 +327,9 @@ declare const logWarn: (message: string, ...args: any[]) => void;
|
|
|
316
327
|
declare const logInfo: (message: string, ...args: any[]) => void;
|
|
317
328
|
declare const logDebug: (message: string, ...args: any[]) => void;
|
|
318
329
|
|
|
319
|
-
|
|
330
|
+
/**
|
|
331
|
+
* Main entry point for the HumanBehavior SDK
|
|
332
|
+
*/
|
|
333
|
+
|
|
334
|
+
export { HumanBehaviorAPI, HumanBehaviorTracker, LogLevel, MAX_CHUNK_SIZE_BYTES, RedactionManager, HumanBehaviorTracker as default, isChunkSizeExceeded, logDebug, logError, logInfo, logWarn, logger, redactionManager, splitLargeEvent, validateSingleEventSize };
|
|
320
335
|
export type { LoggerConfig, RedactionOptions };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
2
3
|
import { HumanBehaviorTracker } from '..';
|
|
3
4
|
export { HumanBehaviorTracker } from '..';
|
|
4
5
|
|
|
@@ -12,7 +13,21 @@ interface HumanBehaviorProviderProps {
|
|
|
12
13
|
redactFields?: string[];
|
|
13
14
|
};
|
|
14
15
|
}
|
|
15
|
-
declare const HumanBehaviorProvider: ({ apiKey, client, children, options }: HumanBehaviorProviderProps) =>
|
|
16
|
+
declare const HumanBehaviorProvider: ({ apiKey, client, children, options }: HumanBehaviorProviderProps) => react_jsx_runtime.JSX.Element;
|
|
16
17
|
declare const useHumanBehavior: () => HumanBehaviorTracker;
|
|
18
|
+
declare const useRedaction: () => {
|
|
19
|
+
setRedactedFields: (fields: string[]) => void;
|
|
20
|
+
isRedactionActive: () => boolean;
|
|
21
|
+
getRedactedFields: () => string[];
|
|
22
|
+
};
|
|
23
|
+
declare const useUserTracking: () => {
|
|
24
|
+
addUserInfo: (userId: string, userProperties: Record<string, any>) => Promise<{
|
|
25
|
+
success: boolean;
|
|
26
|
+
error?: undefined;
|
|
27
|
+
} | {
|
|
28
|
+
success: boolean;
|
|
29
|
+
error: unknown;
|
|
30
|
+
}>;
|
|
31
|
+
};
|
|
17
32
|
|
|
18
|
-
export { HumanBehaviorProvider, useHumanBehavior };
|
|
33
|
+
export { HumanBehaviorProvider, useHumanBehavior, useRedaction, useUserTracking };
|
package/package.json
CHANGED
|
@@ -1,36 +1,50 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "humanbehavior-js",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "SDK for HumanBehavior session and event recording",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/cjs/index.js",
|
|
7
7
|
"module": "./dist/esm/index.js",
|
|
8
8
|
"types": "./dist/types/index.d.ts",
|
|
9
9
|
"unpkg": "dist/index.min.js",
|
|
10
|
+
"jsdelivr": "dist/index.min.js",
|
|
11
|
+
"homepage": "https://documentation.humanbehavior.co",
|
|
10
12
|
"exports": {
|
|
11
13
|
".": {
|
|
12
14
|
"import": "./dist/esm/index.js",
|
|
13
15
|
"require": "./dist/cjs/index.js",
|
|
14
|
-
"types": "./dist/types/index.d.ts"
|
|
16
|
+
"types": "./dist/types/index.d.ts",
|
|
17
|
+
"default": "./dist/esm/index.js"
|
|
15
18
|
},
|
|
16
19
|
"./react": {
|
|
17
20
|
"import": "./dist/esm/react/index.js",
|
|
18
21
|
"require": "./dist/cjs/react/index.js",
|
|
19
|
-
"types": "./dist/types/react/index.d.ts"
|
|
20
|
-
|
|
22
|
+
"types": "./dist/types/react/index.d.ts",
|
|
23
|
+
"default": "./dist/esm/react/index.js"
|
|
24
|
+
},
|
|
25
|
+
"./package.json": "./package.json"
|
|
21
26
|
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist/**/*",
|
|
29
|
+
"src/**/*",
|
|
30
|
+
"*.d.ts"
|
|
31
|
+
],
|
|
22
32
|
"scripts": {
|
|
23
33
|
"build": "rollup -c",
|
|
24
34
|
"build:dev": "rollup -c --environment NODE_ENV:development",
|
|
25
35
|
"prepare": "npm run build",
|
|
26
|
-
"test": "
|
|
36
|
+
"test": "node scripts/test-bundlers.js",
|
|
37
|
+
"test:bundlers": "node scripts/test-bundlers.js",
|
|
38
|
+
"test:framework": "node scripts/test-framework-compatibility.js",
|
|
39
|
+
"test:all": "npm run test:bundlers && npm run test:framework"
|
|
27
40
|
},
|
|
28
41
|
"author": "Chirag Kawediya, Skyler Ji, Amogh Chaturvedi",
|
|
29
42
|
"license": "ISC",
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"react": ">=16.8.0",
|
|
45
|
+
"react-dom": ">=16.8.0"
|
|
46
|
+
},
|
|
30
47
|
"dependencies": {
|
|
31
|
-
"@types/react": "^19.0.12",
|
|
32
|
-
"humanbehavior-js": "^0.0.9",
|
|
33
|
-
"react": "^19.0.0",
|
|
34
48
|
"rrweb": "^2.0.0-alpha.4",
|
|
35
49
|
"uuid": "^11.1.0"
|
|
36
50
|
},
|
|
@@ -40,6 +54,8 @@
|
|
|
40
54
|
"@rollup/plugin-terser": "^0.4.4",
|
|
41
55
|
"@rollup/plugin-typescript": "^12.1.2",
|
|
42
56
|
"@types/node": "^24.0.3",
|
|
57
|
+
"@types/react": "^19.0.12",
|
|
58
|
+
"@types/react-dom": "^19.0.0",
|
|
43
59
|
"rollup": "^4.36.0",
|
|
44
60
|
"rollup-plugin-dts": "^6.2.1",
|
|
45
61
|
"tslib": "^2.8.1",
|
|
@@ -56,6 +72,13 @@
|
|
|
56
72
|
"session-recording",
|
|
57
73
|
"analytics",
|
|
58
74
|
"rrweb",
|
|
59
|
-
"koalaware"
|
|
60
|
-
|
|
75
|
+
"koalaware",
|
|
76
|
+
"webpack",
|
|
77
|
+
"esbuild",
|
|
78
|
+
"vite",
|
|
79
|
+
"rollup"
|
|
80
|
+
],
|
|
81
|
+
"engines": {
|
|
82
|
+
"node": ">=14.0.0"
|
|
83
|
+
}
|
|
61
84
|
}
|
package/readme.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
A simplified session recording and analytics SDK that maintains session continuity across page navigations.
|
|
4
4
|
|
|
5
|
+
📖 **[Full Documentation](https://documentation.humanbehavior.co)** - Complete API reference, examples, and integration guides
|
|
6
|
+
|
|
5
7
|
## Quick Start
|
|
6
8
|
|
|
7
9
|
### Single Page Application (Recommended)
|
package/src/api.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { logError, logInfo } from './utils/logger';
|
|
1
|
+
import { logError, logInfo, logDebug } from './utils/logger';
|
|
2
2
|
|
|
3
|
-
export const MAX_CHUNK_SIZE_BYTES = 1024 * 1024
|
|
3
|
+
export const MAX_CHUNK_SIZE_BYTES = 1024 * 1024; // 1MB chunk size - more conservative
|
|
4
4
|
|
|
5
5
|
export function isChunkSizeExceeded(currentChunk: any[], newEvent: any, sessionId: string): boolean {
|
|
6
6
|
const nextChunkSize = new TextEncoder().encode(JSON.stringify({
|
|
@@ -18,10 +18,62 @@ export function validateSingleEventSize(event: any, sessionId: string): void {
|
|
|
18
18
|
})).length;
|
|
19
19
|
|
|
20
20
|
if (singleEventSize > MAX_CHUNK_SIZE_BYTES) {
|
|
21
|
-
|
|
21
|
+
// Instead of throwing, log a warning and suggest reducing event size
|
|
22
|
+
console.warn(`Single event size (${singleEventSize} bytes) exceeds maximum chunk size (${MAX_CHUNK_SIZE_BYTES} bytes). Consider reducing event data size.`);
|
|
22
23
|
}
|
|
23
24
|
}
|
|
24
25
|
|
|
26
|
+
export function splitLargeEvent(event: any, sessionId: string): any[] {
|
|
27
|
+
const eventSize = new TextEncoder().encode(JSON.stringify({
|
|
28
|
+
sessionId,
|
|
29
|
+
events: [event]
|
|
30
|
+
})).length;
|
|
31
|
+
|
|
32
|
+
if (eventSize <= MAX_CHUNK_SIZE_BYTES) {
|
|
33
|
+
return [event];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// If event is too large, try to split it by removing large properties
|
|
37
|
+
const simplifiedEvent = { ...event };
|
|
38
|
+
|
|
39
|
+
// Remove potentially large properties
|
|
40
|
+
const largeProperties = ['screenshot', 'html', 'dom', 'fullText', 'innerHTML', 'outerHTML'];
|
|
41
|
+
largeProperties.forEach(prop => {
|
|
42
|
+
if (simplifiedEvent[prop]) {
|
|
43
|
+
delete simplifiedEvent[prop];
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Check if simplified event is now small enough
|
|
48
|
+
const simplifiedSize = new TextEncoder().encode(JSON.stringify({
|
|
49
|
+
sessionId,
|
|
50
|
+
events: [simplifiedEvent]
|
|
51
|
+
})).length;
|
|
52
|
+
|
|
53
|
+
if (simplifiedSize <= MAX_CHUNK_SIZE_BYTES) {
|
|
54
|
+
return [simplifiedEvent];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// If still too large, create a minimal event
|
|
58
|
+
const minimalEvent = {
|
|
59
|
+
type: event.type,
|
|
60
|
+
timestamp: event.timestamp,
|
|
61
|
+
url: event.url,
|
|
62
|
+
pathname: event.pathname,
|
|
63
|
+
// Keep only essential properties
|
|
64
|
+
...Object.fromEntries(
|
|
65
|
+
Object.entries(event).filter(([key, value]) =>
|
|
66
|
+
!largeProperties.includes(key) &&
|
|
67
|
+
typeof value !== 'object' &&
|
|
68
|
+
typeof value !== 'string' ||
|
|
69
|
+
(typeof value === 'string' && value.length < 1000)
|
|
70
|
+
)
|
|
71
|
+
)
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
return [minimalEvent];
|
|
75
|
+
}
|
|
76
|
+
|
|
25
77
|
export class HumanBehaviorAPI {
|
|
26
78
|
private apiKey: string;
|
|
27
79
|
private baseUrl: string;
|
|
@@ -98,7 +150,7 @@ export class HumanBehaviorAPI {
|
|
|
98
150
|
}
|
|
99
151
|
}
|
|
100
152
|
|
|
101
|
-
async sendEventsChunked(events: any[], sessionId: string) {
|
|
153
|
+
async sendEventsChunked(events: any[], sessionId: string, userId?: string) {
|
|
102
154
|
try {
|
|
103
155
|
const results = [];
|
|
104
156
|
let currentChunk: any[] = [];
|
|
@@ -115,7 +167,8 @@ export class HumanBehaviorAPI {
|
|
|
115
167
|
},
|
|
116
168
|
body: JSON.stringify({
|
|
117
169
|
sessionId,
|
|
118
|
-
events: currentChunk
|
|
170
|
+
events: currentChunk,
|
|
171
|
+
endUserId: userId
|
|
119
172
|
})
|
|
120
173
|
});
|
|
121
174
|
|
|
@@ -127,11 +180,11 @@ export class HumanBehaviorAPI {
|
|
|
127
180
|
currentChunk = [];
|
|
128
181
|
}
|
|
129
182
|
|
|
130
|
-
//
|
|
131
|
-
|
|
183
|
+
// Handle large events by splitting them
|
|
184
|
+
const splitEvents = splitLargeEvent(event, sessionId);
|
|
132
185
|
|
|
133
|
-
// Start new chunk with
|
|
134
|
-
currentChunk =
|
|
186
|
+
// Start new chunk with the split events
|
|
187
|
+
currentChunk = splitEvents;
|
|
135
188
|
} else {
|
|
136
189
|
// Add event to current chunk
|
|
137
190
|
currentChunk.push(event);
|
|
@@ -148,7 +201,8 @@ export class HumanBehaviorAPI {
|
|
|
148
201
|
},
|
|
149
202
|
body: JSON.stringify({
|
|
150
203
|
sessionId,
|
|
151
|
-
events: currentChunk
|
|
204
|
+
events: currentChunk,
|
|
205
|
+
endUserId: userId
|
|
152
206
|
})
|
|
153
207
|
});
|
|
154
208
|
|
|
@@ -224,7 +278,8 @@ export class HumanBehaviorAPI {
|
|
|
224
278
|
const payload = {
|
|
225
279
|
sessionId: sessionId,
|
|
226
280
|
events: events,
|
|
227
|
-
endUserId: null // Beacon doesn't have user context
|
|
281
|
+
endUserId: null, // Beacon doesn't have user context
|
|
282
|
+
apiKey: this.apiKey // Include API key in body since beacon can't use headers
|
|
228
283
|
};
|
|
229
284
|
|
|
230
285
|
// Convert to Blob for sendBeacon
|
|
@@ -241,6 +296,7 @@ export class HumanBehaviorAPI {
|
|
|
241
296
|
}
|
|
242
297
|
|
|
243
298
|
async sendCustomEvent(sessionId: string, eventName: string, eventProperties?: Record<string, any>) {
|
|
299
|
+
logInfo('[SDK] Sending custom event', { sessionId, eventName, eventProperties });
|
|
244
300
|
try {
|
|
245
301
|
const response = await fetch(`${this.baseUrl}/api/ingestion/customEvent`, {
|
|
246
302
|
method: 'POST',
|
|
@@ -254,14 +310,17 @@ export class HumanBehaviorAPI {
|
|
|
254
310
|
eventProperties: eventProperties || {}
|
|
255
311
|
})
|
|
256
312
|
});
|
|
257
|
-
|
|
313
|
+
logInfo('[SDK] Custom event response', { status: response.status, statusText: response.statusText });
|
|
258
314
|
if (!response.ok) {
|
|
259
|
-
|
|
315
|
+
const errorText = await response.text();
|
|
316
|
+
logError('[SDK] Failed to send custom event', { status: response.status, statusText: response.statusText, errorText });
|
|
317
|
+
throw new Error(`Failed to send custom event: ${response.status} ${response.statusText} - ${errorText}`);
|
|
260
318
|
}
|
|
261
|
-
|
|
262
|
-
|
|
319
|
+
const json = await response.json();
|
|
320
|
+
logDebug('[SDK] Custom event success', json);
|
|
321
|
+
return json;
|
|
263
322
|
} catch (error) {
|
|
264
|
-
logError('Error sending custom event
|
|
323
|
+
logError('[SDK] Error sending custom event', error, { sessionId, eventName, eventProperties });
|
|
265
324
|
throw error;
|
|
266
325
|
}
|
|
267
326
|
}
|
package/src/index.ts
CHANGED
|
@@ -16,8 +16,8 @@ export * from './redact';
|
|
|
16
16
|
// Export logger functionality
|
|
17
17
|
export * from './utils/logger';
|
|
18
18
|
|
|
19
|
-
//
|
|
20
|
-
export
|
|
19
|
+
// Export the tracker as the default export
|
|
20
|
+
export default HumanBehaviorTracker;
|
|
21
21
|
|
|
22
22
|
// For UMD builds, expose the main class globally
|
|
23
23
|
if (typeof window !== 'undefined') {
|
package/src/react/index.tsx
CHANGED
|
@@ -114,21 +114,24 @@ export const HumanBehaviorProvider = ({ apiKey, client, children, options }: Hum
|
|
|
114
114
|
|
|
115
115
|
// Wait for initialization to complete
|
|
116
116
|
tracker.initializationPromise?.then(async () => {
|
|
117
|
-
// Start the tracker to enable event flushing and recording
|
|
118
117
|
await tracker.start();
|
|
119
118
|
setIsInitialized(true);
|
|
120
119
|
|
|
121
120
|
// Process any queued events
|
|
122
121
|
const currentQueue = eventQueueRef.current;
|
|
123
122
|
if (currentQueue.length > 0) {
|
|
124
|
-
|
|
123
|
+
for (const event of currentQueue) {
|
|
125
124
|
if (event.type === 'identify') {
|
|
126
125
|
logDebug('Processing queued identify event', event.userProperties);
|
|
127
|
-
|
|
126
|
+
try {
|
|
127
|
+
await tracker.addUserInfo(event.userProperties);
|
|
128
|
+
} catch (error) {
|
|
129
|
+
logError('Failed to process queued user info:', error);
|
|
130
|
+
}
|
|
128
131
|
} else {
|
|
129
132
|
tracker.addEvent(event);
|
|
130
133
|
}
|
|
131
|
-
}
|
|
134
|
+
}
|
|
132
135
|
setEventQueue([]); // Clear the queue
|
|
133
136
|
}
|
|
134
137
|
}).catch(error => {
|
|
@@ -201,6 +204,48 @@ export const useHumanBehavior = () => {
|
|
|
201
204
|
return tracker;
|
|
202
205
|
};
|
|
203
206
|
|
|
207
|
+
// Custom hook for managing redaction fields dynamically
|
|
208
|
+
export const useRedaction = () => {
|
|
209
|
+
const tracker = useHumanBehavior();
|
|
210
|
+
|
|
211
|
+
const setRedactedFields = useCallback((fields: string[]) => {
|
|
212
|
+
tracker.setRedactedFields(fields);
|
|
213
|
+
}, [tracker]);
|
|
214
|
+
|
|
215
|
+
const isRedactionActive = useCallback(() => {
|
|
216
|
+
return tracker.isRedactionActive();
|
|
217
|
+
}, [tracker]);
|
|
218
|
+
|
|
219
|
+
const getRedactedFields = useCallback(() => {
|
|
220
|
+
return tracker.getRedactedFields();
|
|
221
|
+
}, [tracker]);
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
setRedactedFields,
|
|
225
|
+
isRedactionActive,
|
|
226
|
+
getRedactedFields
|
|
227
|
+
};
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
// Custom hook for managing user info
|
|
231
|
+
export const useUserTracking = () => {
|
|
232
|
+
const tracker = useHumanBehavior();
|
|
233
|
+
|
|
234
|
+
const addUserInfo = useCallback(async (userId: string, userProperties: Record<string, any>) => {
|
|
235
|
+
try {
|
|
236
|
+
await tracker.addUserInfo({ userId, userProperties });
|
|
237
|
+
return { success: true };
|
|
238
|
+
} catch (error) {
|
|
239
|
+
logError('Failed to add user info:', error);
|
|
240
|
+
return { success: false, error };
|
|
241
|
+
}
|
|
242
|
+
}, [tracker]);
|
|
243
|
+
|
|
244
|
+
return {
|
|
245
|
+
addUserInfo
|
|
246
|
+
};
|
|
247
|
+
};
|
|
248
|
+
|
|
204
249
|
// Automatic page tracking component (similar to PostHog's PostHogPageView)
|
|
205
250
|
function HumanBehaviorPageView() {
|
|
206
251
|
const tracker = useContext(HumanBehaviorContext);
|
package/src/tracker.ts
CHANGED
|
@@ -18,8 +18,6 @@ declare global {
|
|
|
18
18
|
export class HumanBehaviorTracker {
|
|
19
19
|
private eventIngestionQueue: any[] = [];
|
|
20
20
|
private queueSizeBytes: number = 0;
|
|
21
|
-
private rejectedEvents: any[] = [];
|
|
22
|
-
private isProcessingRejectedEvents: boolean = false;
|
|
23
21
|
|
|
24
22
|
private sessionId!: string;
|
|
25
23
|
private userProperties: Record<string, any> = {};
|
|
@@ -183,7 +181,6 @@ export class HumanBehaviorTracker {
|
|
|
183
181
|
if (isBrowser) {
|
|
184
182
|
this.setupPageUnloadHandler();
|
|
185
183
|
this.setupNavigationTracking();
|
|
186
|
-
this.processRejectedEvents();
|
|
187
184
|
} else {
|
|
188
185
|
logWarn('HumanBehaviorTracker initialized in a non-browser environment. Session tracking is disabled.');
|
|
189
186
|
}
|
|
@@ -349,10 +346,21 @@ export class HumanBehaviorTracker {
|
|
|
349
346
|
await this.api.sendCustomEvent(this.sessionId, eventName, properties);
|
|
350
347
|
|
|
351
348
|
logDebug(`Custom event tracked: ${eventName}`, properties);
|
|
352
|
-
} catch (error) {
|
|
349
|
+
} catch (error: any) {
|
|
353
350
|
logError('Failed to track custom event:', error);
|
|
354
351
|
|
|
355
|
-
//
|
|
352
|
+
// Handle specific error types - check for any custom event failure
|
|
353
|
+
if (error.message?.includes('500') ||
|
|
354
|
+
error.message?.includes('Internal Server Error') ||
|
|
355
|
+
error.message?.includes('Failed to send custom event')) {
|
|
356
|
+
logWarn('Custom event endpoint failed, using fallback');
|
|
357
|
+
} else if (error.message?.includes('ERR_BLOCKED_BY_CLIENT')) {
|
|
358
|
+
logWarn('Custom event request blocked by ad blocker, using fallback');
|
|
359
|
+
} else if (error.message?.includes('Failed to fetch')) {
|
|
360
|
+
logWarn('Custom event network error, using fallback');
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Always try fallback for any custom event error
|
|
356
364
|
try {
|
|
357
365
|
const customEventData = {
|
|
358
366
|
eventName: eventName,
|
|
@@ -709,30 +717,31 @@ export class HumanBehaviorTracker {
|
|
|
709
717
|
}
|
|
710
718
|
}
|
|
711
719
|
|
|
712
|
-
public async addUserInfo(
|
|
720
|
+
public async addUserInfo(
|
|
721
|
+
{ userId, userProperties }: { userId: string, userProperties: Record<string, any> }
|
|
722
|
+
) {
|
|
713
723
|
await this.ensureInitialized();
|
|
714
724
|
if (!this.endUserId) {
|
|
715
725
|
throw new Error('Cannot add user info before tracker initialization');
|
|
716
726
|
}
|
|
717
727
|
this.userProperties = userProperties;
|
|
718
728
|
await this.api.sendUserData(this.endUserId, userProperties, this.sessionId);
|
|
729
|
+
if (userId) {
|
|
730
|
+
this.endUserId = userId;
|
|
731
|
+
}
|
|
719
732
|
}
|
|
720
733
|
|
|
721
734
|
/**
|
|
722
|
-
*
|
|
735
|
+
* Add user identification information to the tracker
|
|
723
736
|
* @param authFields Array of field names to check for existing users (e.g., ['email', 'phoneNumber'])
|
|
724
737
|
*/
|
|
725
|
-
|
|
738
|
+
private async auth(id: string) {
|
|
726
739
|
await this.ensureInitialized();
|
|
727
740
|
if (!this.endUserId) {
|
|
728
741
|
throw new Error('Cannot authenticate before tracker initialization');
|
|
729
742
|
}
|
|
730
|
-
|
|
731
|
-
throw new Error('No user info available. Call addUserInfo() first.');
|
|
732
|
-
}
|
|
733
|
-
const response = await this.api.sendUserAuth(this.endUserId, this.userProperties, this.sessionId, authFields);
|
|
743
|
+
const response = await this.api.sendUserAuth(this.endUserId, { id }, this.sessionId, ["id"]);
|
|
734
744
|
if (response && response.userId && response.userId !== this.endUserId) {
|
|
735
|
-
// Update endUserId and cookie if backend returns a new userId
|
|
736
745
|
this.endUserId = response.userId;
|
|
737
746
|
this.setCookie(`human_behavior_end_user_id_${this.apiKey}`, response.userId, 365);
|
|
738
747
|
}
|
|
@@ -792,33 +801,6 @@ export class HumanBehaviorTracker {
|
|
|
792
801
|
this.queueSizeBytes += eventSize;
|
|
793
802
|
}
|
|
794
803
|
|
|
795
|
-
private async processRejectedEvents() {
|
|
796
|
-
if (this.isProcessingRejectedEvents || this.rejectedEvents.length === 0) return;
|
|
797
|
-
|
|
798
|
-
this.isProcessingRejectedEvents = true;
|
|
799
|
-
try {
|
|
800
|
-
// Create a new session ID for rejected events
|
|
801
|
-
const newSessionId = uuidv1();
|
|
802
|
-
if (isBrowser) {
|
|
803
|
-
localStorage.setItem('human_behavior_session_id', newSessionId);
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
// Try to send rejected events with new session ID using beacon
|
|
807
|
-
// sendBeacon returns true if the request was queued successfully
|
|
808
|
-
this.api.sendBeaconEvents(this.rejectedEvents, newSessionId);
|
|
809
|
-
|
|
810
|
-
// Clear rejected events and update session ID
|
|
811
|
-
// Note: We can't verify if the beacon data was actually sent,
|
|
812
|
-
// but we clear the events to prevent duplicate sending attempts
|
|
813
|
-
this.rejectedEvents = [];
|
|
814
|
-
this.sessionId = newSessionId;
|
|
815
|
-
} catch (error) {
|
|
816
|
-
logError('Failed to process rejected events:', error);
|
|
817
|
-
} finally {
|
|
818
|
-
this.isProcessingRejectedEvents = false;
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
|
|
822
804
|
private async flush() {
|
|
823
805
|
// Prevent concurrent flushes
|
|
824
806
|
if (this.isProcessing || !this.initialized) {
|
|
@@ -835,20 +817,18 @@ export class HumanBehaviorTracker {
|
|
|
835
817
|
if (eventsToProcess.length > 0) {
|
|
836
818
|
logDebug('Flushing events:', eventsToProcess);
|
|
837
819
|
try {
|
|
838
|
-
|
|
820
|
+
// Use chunked sending to handle large payloads
|
|
821
|
+
await this.api.sendEventsChunked(eventsToProcess, this.sessionId, this.endUserId!);
|
|
839
822
|
} catch (error: any) {
|
|
840
|
-
//
|
|
823
|
+
// Handle specific error types with graceful degradation
|
|
841
824
|
if (error.message?.includes('ERROR: Session already completed')) {
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
825
|
+
logWarn('Session expired, events will be lost');
|
|
826
|
+
} else if (error.message?.includes('413') || error.message?.includes('Content Too Large')) {
|
|
827
|
+
logWarn('Payload too large, events will be lost');
|
|
845
828
|
} else if (error.message?.includes('ERR_BLOCKED_BY_CLIENT') ||
|
|
846
829
|
error.message?.includes('Failed to fetch') ||
|
|
847
830
|
error.message?.includes('NetworkError')) {
|
|
848
|
-
|
|
849
|
-
logWarn('Request blocked by ad blocker or network issue, storing events for retry');
|
|
850
|
-
this.rejectedEvents.push(...eventsToProcess);
|
|
851
|
-
// Don't process rejected events immediately to avoid spam
|
|
831
|
+
logWarn('Request blocked by ad blocker or network issue, events will be lost');
|
|
852
832
|
} else {
|
|
853
833
|
throw error;
|
|
854
834
|
}
|
|
@@ -924,6 +904,56 @@ export class HumanBehaviorTracker {
|
|
|
924
904
|
}
|
|
925
905
|
}
|
|
926
906
|
|
|
907
|
+
/**
|
|
908
|
+
* Delete a cookie by setting its expiration date to the past
|
|
909
|
+
* @param name The name of the cookie to delete
|
|
910
|
+
*/
|
|
911
|
+
private deleteCookie(name: string) {
|
|
912
|
+
if (!isBrowser) return;
|
|
913
|
+
|
|
914
|
+
try {
|
|
915
|
+
// Delete cookie by setting expiration to past
|
|
916
|
+
document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; SameSite=Lax`;
|
|
917
|
+
logDebug(`Deleted cookie: ${name}`);
|
|
918
|
+
} catch (error) {
|
|
919
|
+
logError(`Failed to delete cookie: ${name}`, error);
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
// Also remove from localStorage
|
|
923
|
+
try {
|
|
924
|
+
localStorage.removeItem(name);
|
|
925
|
+
logDebug(`Removed from localStorage: ${name}`);
|
|
926
|
+
} catch (error) {
|
|
927
|
+
logError(`Failed to remove from localStorage: ${name}`, error);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
/**
|
|
932
|
+
* Log out the current user by clearing all user-related data
|
|
933
|
+
* This will delete the user ID cookie, clear localStorage, and reset user properties
|
|
934
|
+
*/
|
|
935
|
+
public logout(): void {
|
|
936
|
+
if (!isBrowser) return;
|
|
937
|
+
|
|
938
|
+
try {
|
|
939
|
+
// Clear user ID cookie and localStorage
|
|
940
|
+
const userIdCookieName = `human_behavior_end_user_id_${this.apiKey}`;
|
|
941
|
+
this.deleteCookie(userIdCookieName);
|
|
942
|
+
|
|
943
|
+
// Clear session data from localStorage
|
|
944
|
+
localStorage.removeItem('human_behavior_session_id');
|
|
945
|
+
localStorage.removeItem('human_behavior_last_activity');
|
|
946
|
+
|
|
947
|
+
// Reset user-related properties
|
|
948
|
+
this.endUserId = null;
|
|
949
|
+
this.userProperties = {};
|
|
950
|
+
|
|
951
|
+
logInfo('User logged out - cleared all user data and cookies');
|
|
952
|
+
} catch (error) {
|
|
953
|
+
logError('Error during logout:', error);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
|
|
927
957
|
/**
|
|
928
958
|
* Start redaction functionality for sensitive input fields
|
|
929
959
|
* @param options Optional configuration for redaction behavior
|
|
@@ -1005,8 +1035,8 @@ export class HumanBehaviorTracker {
|
|
|
1005
1035
|
const recommendations: string[] = [];
|
|
1006
1036
|
let blocked = false;
|
|
1007
1037
|
|
|
1008
|
-
// Check if we have
|
|
1009
|
-
if (this.
|
|
1038
|
+
// Check if we have queued events (might indicate blocking)
|
|
1039
|
+
if (this.eventIngestionQueue.length > 0) {
|
|
1010
1040
|
blocked = true;
|
|
1011
1041
|
recommendations.push('Some requests may be blocked by ad blockers');
|
|
1012
1042
|
}
|