humanbehavior-js 0.0.9 → 0.1.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/cjs/index.js +345 -184
- 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 +345 -184
- 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 +60 -12
- package/dist/types/react/index.d.ts +8 -9
- package/package.json +2 -1
- package/readme.md +127 -105
- package/simple-spa.html +544 -0
- package/src/api.ts +4 -134
- package/src/react/index.tsx +61 -28
- package/src/tracker.ts +404 -87
package/src/api.ts
CHANGED
|
@@ -207,106 +207,17 @@ export class HumanBehaviorAPI {
|
|
|
207
207
|
}
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
-
async sendSessionComplete(sessionId: string) {
|
|
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
210
|
|
|
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
211
|
|
|
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
212
|
|
|
299
|
-
|
|
213
|
+
|
|
214
|
+
public sendBeaconEvents(events: any[], sessionId: string) {
|
|
300
215
|
const data = new URLSearchParams()
|
|
301
216
|
data.append('events', encodeURIComponent(JSON.stringify(events)))
|
|
302
217
|
data.append('sessionId', encodeURIComponent(sessionId))
|
|
303
218
|
data.append('timestamp', encodeURIComponent(Date.now().toString()))
|
|
304
219
|
data.append('apiKey', encodeURIComponent(this.apiKey))
|
|
305
|
-
|
|
306
|
-
logInfo('Session complete beacon sending');
|
|
307
|
-
localStorage.setItem('koalaware_session_complete', Date.now().toString());
|
|
308
|
-
data.append('sessionComplete', encodeURIComponent('true'))
|
|
309
|
-
}
|
|
220
|
+
|
|
310
221
|
|
|
311
222
|
const success = navigator.sendBeacon(
|
|
312
223
|
`${this.baseUrl}/api/ingestion/events`,
|
|
@@ -316,47 +227,6 @@ export class HumanBehaviorAPI {
|
|
|
316
227
|
// KoalawareTracker.logToStorage(`Sending events beacon: ${this.baseUrl}/api/ingestion/events`);
|
|
317
228
|
// KoalawareTracker.logToStorage(`Events beacon success: ${success}`);
|
|
318
229
|
}
|
|
230
|
+
|
|
319
231
|
|
|
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
|
-
}
|
|
362
232
|
}
|
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 };
|