humanbehavior-js 0.0.9 → 0.1.1
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/index.js +441 -201
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/react/index.js +51 -77
- package/dist/cjs/react/index.js.map +1 -1
- package/dist/esm/index.js +441 -201
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/react/index.js +48 -77
- package/dist/esm/react/index.js.map +1 -1
- package/dist/index.min.js +2 -2
- package/dist/index.min.js.map +1 -1
- package/dist/types/index.d.ts +75 -13
- package/dist/types/react/index.d.ts +8 -9
- package/package.json +2 -1
- package/readme.md +127 -105
- package/simple-spa.html +594 -0
- package/src/api.ts +2 -142
- package/src/react/index.tsx +61 -28
- package/src/tracker.ts +507 -102
package/src/api.ts
CHANGED
|
@@ -199,7 +199,7 @@ export class HumanBehaviorAPI {
|
|
|
199
199
|
if (!response.ok) {
|
|
200
200
|
throw new Error(`Failed to authenticate user: ${response.statusText} with API key: ${this.apiKey}`);
|
|
201
201
|
}
|
|
202
|
-
|
|
202
|
+
// Returns: { success: true, message: '...', userId: '...' }
|
|
203
203
|
return await response.json();
|
|
204
204
|
} catch (error) {
|
|
205
205
|
logError('Error authenticating user:', error);
|
|
@@ -207,156 +207,16 @@ export class HumanBehaviorAPI {
|
|
|
207
207
|
}
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
-
|
|
211
|
-
const response = await fetch(`${this.baseUrl}/api/ingestion/sessionComplete`, {
|
|
212
|
-
method: 'POST',
|
|
213
|
-
headers: {
|
|
214
|
-
'Content-Type': 'application/json',
|
|
215
|
-
'Authorization': `Bearer ${this.apiKey}`
|
|
216
|
-
},
|
|
217
|
-
body: JSON.stringify({ sessionId })
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
if (!response.ok) {
|
|
221
|
-
throw new Error(`Failed to send session complete: ${response.statusText}`);
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
async sendCustomEvent(eventName: string, eventProperties: Record<string, any>, sessionId: string) {
|
|
226
|
-
const maxRetries = 3;
|
|
227
|
-
let retryCount = 0;
|
|
228
|
-
|
|
229
|
-
while (retryCount < maxRetries) {
|
|
230
|
-
try {
|
|
231
|
-
const response = await fetch(`${this.baseUrl}/api/ingestion/customEvent`, {
|
|
232
|
-
method: 'POST',
|
|
233
|
-
headers: {
|
|
234
|
-
'Content-Type': 'application/json',
|
|
235
|
-
'Authorization': `Bearer ${this.apiKey}`
|
|
236
|
-
},
|
|
237
|
-
body: JSON.stringify({
|
|
238
|
-
name: eventName,
|
|
239
|
-
properties: eventProperties,
|
|
240
|
-
sessionId: sessionId,
|
|
241
|
-
timestamp: new Date().toISOString()
|
|
242
|
-
})
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
if (!response.ok) {
|
|
246
|
-
throw new Error(`Failed to send custom event: ${response.statusText}`);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
return await response.json();
|
|
250
|
-
} catch (error) {
|
|
251
|
-
retryCount++;
|
|
252
|
-
if (retryCount === maxRetries) {
|
|
253
|
-
logError('Error sending custom event after max retries:', error);
|
|
254
|
-
throw error;
|
|
255
|
-
}
|
|
256
|
-
// Exponential backoff
|
|
257
|
-
await new Promise(resolve => setTimeout(resolve, Math.pow(2, retryCount) * 1000));
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
async sendCustomEvents(events: any[], sessionId: string) {
|
|
263
|
-
const maxRetries = 3;
|
|
264
|
-
let retryCount = 0;
|
|
265
|
-
|
|
266
|
-
while (retryCount < maxRetries) {
|
|
267
|
-
try {
|
|
268
|
-
const response = await fetch(`${this.baseUrl}/api/ingestion/customEvent/batch`, {
|
|
269
|
-
method: 'POST',
|
|
270
|
-
headers: {
|
|
271
|
-
'Content-Type': 'application/json',
|
|
272
|
-
'Authorization': `Bearer ${this.apiKey}`
|
|
273
|
-
},
|
|
274
|
-
body: JSON.stringify({
|
|
275
|
-
events: events.map(event => ({
|
|
276
|
-
...event,
|
|
277
|
-
sessionId: sessionId
|
|
278
|
-
}))
|
|
279
|
-
})
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
if (!response.ok) {
|
|
283
|
-
throw new Error(`Failed to send custom events: ${response.statusText}`);
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
return await response.json();
|
|
287
|
-
} catch (error) {
|
|
288
|
-
retryCount++;
|
|
289
|
-
if (retryCount === maxRetries) {
|
|
290
|
-
logError('Error sending custom events after max retries:', error);
|
|
291
|
-
throw error;
|
|
292
|
-
}
|
|
293
|
-
// Exponential backoff
|
|
294
|
-
await new Promise(resolve => setTimeout(resolve, Math.pow(2, retryCount) * 1000));
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
public sendBeaconEvents(events: any[], sessionId: string, isSessionComplete: boolean = false) {
|
|
210
|
+
public sendBeaconEvents(events: any[], sessionId: string) {
|
|
300
211
|
const data = new URLSearchParams()
|
|
301
212
|
data.append('events', encodeURIComponent(JSON.stringify(events)))
|
|
302
213
|
data.append('sessionId', encodeURIComponent(sessionId))
|
|
303
214
|
data.append('timestamp', encodeURIComponent(Date.now().toString()))
|
|
304
215
|
data.append('apiKey', encodeURIComponent(this.apiKey))
|
|
305
|
-
if (isSessionComplete) {
|
|
306
|
-
logInfo('Session complete beacon sending');
|
|
307
|
-
localStorage.setItem('koalaware_session_complete', Date.now().toString());
|
|
308
|
-
data.append('sessionComplete', encodeURIComponent('true'))
|
|
309
|
-
}
|
|
310
216
|
|
|
311
217
|
const success = navigator.sendBeacon(
|
|
312
218
|
`${this.baseUrl}/api/ingestion/events`,
|
|
313
219
|
data
|
|
314
220
|
);
|
|
315
|
-
|
|
316
|
-
// KoalawareTracker.logToStorage(`Sending events beacon: ${this.baseUrl}/api/ingestion/events`);
|
|
317
|
-
// KoalawareTracker.logToStorage(`Events beacon success: ${success}`);
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
public sendBeaconSessionComplete(sessionId: string) {
|
|
321
|
-
const data = new URLSearchParams()
|
|
322
|
-
data.append('sessionId', sessionId)
|
|
323
|
-
data.append('apiKey', this.apiKey)
|
|
324
|
-
data.append('sessionComplete', 'true')
|
|
325
|
-
|
|
326
|
-
const success = navigator.sendBeacon(
|
|
327
|
-
`${this.baseUrl}/api/ingestion/sessionComplete`,
|
|
328
|
-
data
|
|
329
|
-
);
|
|
330
|
-
|
|
331
|
-
// KoalawareTracker.logToStorage(`Sending completion beacon: ${this.baseUrl}/api/ingestion/sessionComplete`);
|
|
332
|
-
// KoalawareTracker.logToStorage(`Complete beacon success: ${success}`);
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
public sendBeaconCustomEvent(eventName: string, eventProperties: Record<string, any>, sessionId: string) {
|
|
336
|
-
const data = new URLSearchParams()
|
|
337
|
-
data.append('name', encodeURIComponent(eventName))
|
|
338
|
-
data.append('properties', encodeURIComponent(JSON.stringify(eventProperties)))
|
|
339
|
-
data.append('sessionId', encodeURIComponent(sessionId))
|
|
340
|
-
data.append('timestamp', encodeURIComponent(new Date().toISOString()))
|
|
341
|
-
data.append('apiKey', encodeURIComponent(this.apiKey))
|
|
342
|
-
|
|
343
|
-
return navigator.sendBeacon(
|
|
344
|
-
`${this.baseUrl}/api/ingestion/customEvent`,
|
|
345
|
-
data
|
|
346
|
-
);
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
public sendBeaconCustomEvents(events: any[], sessionId: string) {
|
|
350
|
-
const data = new URLSearchParams()
|
|
351
|
-
data.append('events', encodeURIComponent(JSON.stringify(events.map(event => ({
|
|
352
|
-
...event,
|
|
353
|
-
sessionId: sessionId
|
|
354
|
-
})))))
|
|
355
|
-
data.append('apiKey', encodeURIComponent(this.apiKey))
|
|
356
|
-
|
|
357
|
-
return navigator.sendBeacon(
|
|
358
|
-
`${this.baseUrl}/api/ingestion/customEvent/batch`,
|
|
359
|
-
data
|
|
360
|
-
);
|
|
361
221
|
}
|
|
362
222
|
}
|
package/src/react/index.tsx
CHANGED
|
@@ -24,6 +24,11 @@ interface HumanBehaviorProviderProps {
|
|
|
24
24
|
apiKey?: string;
|
|
25
25
|
client?: HumanBehaviorTracker;
|
|
26
26
|
children: ReactNode;
|
|
27
|
+
options?: {
|
|
28
|
+
ingestionUrl?: string;
|
|
29
|
+
logLevel?: 'none' | 'error' | 'warn' | 'info' | 'debug';
|
|
30
|
+
redactFields?: string[];
|
|
31
|
+
};
|
|
27
32
|
}
|
|
28
33
|
|
|
29
34
|
// Default context to prevent unnecessary re-renders
|
|
@@ -38,9 +43,9 @@ const defaultContext: HumanBehaviorContextType = {
|
|
|
38
43
|
}
|
|
39
44
|
};
|
|
40
45
|
|
|
41
|
-
const HumanBehaviorContext = createContext<
|
|
46
|
+
const HumanBehaviorContext = createContext<HumanBehaviorTracker | null>(null);
|
|
42
47
|
|
|
43
|
-
export const HumanBehaviorProvider = ({ apiKey, client, children }: HumanBehaviorProviderProps) => {
|
|
48
|
+
export const HumanBehaviorProvider = ({ apiKey, client, children, options }: HumanBehaviorProviderProps) => {
|
|
44
49
|
const [humanBehavior, setHumanBehavior] = useState<HumanBehaviorTracker | null>(client || null);
|
|
45
50
|
const [eventQueue, setEventQueue] = useState<any[]>([]);
|
|
46
51
|
const [isMounted, setIsMounted] = useState(false);
|
|
@@ -132,15 +137,15 @@ export const HumanBehaviorProvider = ({ apiKey, client, children }: HumanBehavio
|
|
|
132
137
|
// Memoized context value to prevent unnecessary re-renders
|
|
133
138
|
const contextValue = useMemo(() => {
|
|
134
139
|
if (!isMounted) {
|
|
135
|
-
return
|
|
140
|
+
return null;
|
|
136
141
|
}
|
|
137
142
|
|
|
138
143
|
if (!isInitialized) {
|
|
139
|
-
return
|
|
144
|
+
return null;
|
|
140
145
|
}
|
|
141
146
|
|
|
142
|
-
return
|
|
143
|
-
}, [isMounted, isInitialized, humanBehavior
|
|
147
|
+
return humanBehavior;
|
|
148
|
+
}, [isMounted, isInitialized, humanBehavior]);
|
|
144
149
|
|
|
145
150
|
// If not in browser, render children without context
|
|
146
151
|
if (!(isBrowser())) {
|
|
@@ -149,6 +154,7 @@ export const HumanBehaviorProvider = ({ apiKey, client, children }: HumanBehavio
|
|
|
149
154
|
|
|
150
155
|
return (
|
|
151
156
|
<HumanBehaviorContext.Provider value={contextValue}>
|
|
157
|
+
<HumanBehaviorPageView />
|
|
152
158
|
{children}
|
|
153
159
|
</HumanBehaviorContext.Provider>
|
|
154
160
|
);
|
|
@@ -185,28 +191,55 @@ const createQueuingImplementation = (queueEvent: (event: any) => void): HumanBeh
|
|
|
185
191
|
}
|
|
186
192
|
});
|
|
187
193
|
|
|
188
|
-
export const useHumanBehavior = ()
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
if (context === null) {
|
|
193
|
-
throw new Error("useHumanBehavior must be used within a HumanBehaviorProvider");
|
|
194
|
+
export const useHumanBehavior = () => {
|
|
195
|
+
const tracker = useContext(HumanBehaviorContext);
|
|
196
|
+
if (!tracker) {
|
|
197
|
+
throw new Error('useHumanBehavior must be used within a HumanBehaviorProvider');
|
|
194
198
|
}
|
|
199
|
+
return tracker;
|
|
200
|
+
};
|
|
195
201
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
return serverSideImplementation;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// If we have an initialized tracker, return it as the interface
|
|
203
|
-
if (context.humanBehavior) {
|
|
204
|
-
return context.humanBehavior;
|
|
205
|
-
}
|
|
202
|
+
// Automatic page tracking component (similar to PostHog's PostHogPageView)
|
|
203
|
+
function HumanBehaviorPageView() {
|
|
204
|
+
const tracker = useContext(HumanBehaviorContext);
|
|
206
205
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
206
|
+
useEffect(() => {
|
|
207
|
+
if (tracker && typeof window !== 'undefined') {
|
|
208
|
+
// Track initial page load
|
|
209
|
+
tracker.trackPageView();
|
|
210
|
+
|
|
211
|
+
// Listen for route changes (for SPAs)
|
|
212
|
+
const handleRouteChange = () => {
|
|
213
|
+
tracker.trackPageView();
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
// Listen for popstate (back/forward navigation)
|
|
217
|
+
window.addEventListener('popstate', handleRouteChange);
|
|
218
|
+
|
|
219
|
+
// Listen for pushstate/replacestate (programmatic navigation)
|
|
220
|
+
const originalPushState = history.pushState;
|
|
221
|
+
const originalReplaceState = history.replaceState;
|
|
222
|
+
|
|
223
|
+
history.pushState = function(...args) {
|
|
224
|
+
originalPushState.apply(this, args);
|
|
225
|
+
handleRouteChange();
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
history.replaceState = function(...args) {
|
|
229
|
+
originalReplaceState.apply(this, args);
|
|
230
|
+
handleRouteChange();
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
return () => {
|
|
234
|
+
window.removeEventListener('popstate', handleRouteChange);
|
|
235
|
+
history.pushState = originalPushState;
|
|
236
|
+
history.replaceState = originalReplaceState;
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
}, [tracker]);
|
|
240
|
+
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Export the tracker class for direct use
|
|
245
|
+
export { HumanBehaviorTracker };
|