humanbehavior-js 0.0.7 → 0.0.9

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.
@@ -101,10 +101,34 @@ declare class HumanBehaviorTracker {
101
101
  private initialized;
102
102
  initializationPromise: Promise<void> | null;
103
103
  private redactionManager;
104
- constructor(apiKey: string | undefined, ingestionUrl: string | undefined);
104
+ private originalConsole;
105
+ private originalLogger;
106
+ private consoleTrackingEnabled;
107
+ constructor(apiKey: string | undefined, ingestionUrl?: string);
105
108
  private init;
106
109
  private ensureInitialized;
107
110
  static logToStorage(message: string): void;
111
+ /**
112
+ * Configure logging behavior for the SDK
113
+ * @param config Logger configuration options
114
+ */
115
+ static configureLogging(config: {
116
+ level?: 'none' | 'error' | 'warn' | 'info' | 'debug';
117
+ enableConsole?: boolean;
118
+ enableStorage?: boolean;
119
+ }): void;
120
+ /**
121
+ * Enable console event tracking
122
+ */
123
+ enableConsoleTracking(): void;
124
+ /**
125
+ * Disable console event tracking
126
+ */
127
+ disableConsoleTracking(): void;
128
+ /**
129
+ * Track console events
130
+ */
131
+ private trackConsoleEvent;
108
132
  private setupPageUnloadHandler;
109
133
  viewLogs(): void;
110
134
  addUserInfo(userProperties: Record<string, any>): Promise<void>;
@@ -168,5 +192,38 @@ declare class HumanBehaviorAPI {
168
192
  sendBeaconCustomEvents(events: any[], sessionId: string): boolean;
169
193
  }
170
194
 
171
- export { HumanBehaviorAPI, HumanBehaviorTracker, MAX_CHUNK_SIZE_BYTES, RedactionManager, HumanBehaviorTracker as default, isChunkSizeExceeded, redactionManager, validateSingleEventSize };
172
- export type { RedactionOptions };
195
+ declare enum LogLevel {
196
+ NONE = 0,
197
+ ERROR = 1,
198
+ WARN = 2,
199
+ INFO = 3,
200
+ DEBUG = 4
201
+ }
202
+ interface LoggerConfig {
203
+ level: LogLevel;
204
+ enableConsole: boolean;
205
+ enableStorage: boolean;
206
+ }
207
+ declare class Logger {
208
+ private config;
209
+ private isBrowser;
210
+ constructor(config?: Partial<LoggerConfig>);
211
+ setConfig(config: Partial<LoggerConfig>): void;
212
+ private shouldLog;
213
+ private formatMessage;
214
+ error(message: string, ...args: any[]): void;
215
+ warn(message: string, ...args: any[]): void;
216
+ info(message: string, ...args: any[]): void;
217
+ debug(message: string, ...args: any[]): void;
218
+ private logToStorage;
219
+ getLogs(): any[];
220
+ clearLogs(): void;
221
+ }
222
+ declare const logger: Logger;
223
+ declare const logError: (message: string, ...args: any[]) => void;
224
+ declare const logWarn: (message: string, ...args: any[]) => void;
225
+ declare const logInfo: (message: string, ...args: any[]) => void;
226
+ declare const logDebug: (message: string, ...args: any[]) => void;
227
+
228
+ export { HumanBehaviorAPI, HumanBehaviorTracker, LogLevel, MAX_CHUNK_SIZE_BYTES, RedactionManager, HumanBehaviorTracker as default, isChunkSizeExceeded, logDebug, logError, logInfo, logWarn, logger, redactionManager, validateSingleEventSize };
229
+ export type { LoggerConfig, RedactionOptions };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "humanbehavior-js",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
4
4
  "description": "SDK for HumanBehavior session and event recording",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/index.js",
@@ -49,7 +49,7 @@
49
49
  },
50
50
  "repository": {
51
51
  "type": "git",
52
- "url": "https://github.com/humanbehavior-gh/HumanBehavior.git"
52
+ "url": "git+https://github.com/humanbehavior-gh/HumanBehavior.git"
53
53
  },
54
54
  "keywords": [
55
55
  "session-recording",
package/readme.md CHANGED
@@ -1,57 +1,145 @@
1
- To build, run `npm run build`
1
+ # HumanBehavior SDK
2
2
 
3
+ A JavaScript SDK for session recording and user behavior analytics.
3
4
 
4
- Usage:
5
+ ## Installation
5
6
 
6
- NextJS
7
+ ```bash
8
+ npm install humanbehavior-js
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### Vanilla JavaScript
14
+
15
+ ```html
16
+ <script src="https://unpkg.com/humanbehavior-js@0.0.7/dist/index.min.js"></script>
17
+ <script>
18
+ const tracker = new HumanBehaviorTracker("your-api-key-here");
19
+ tracker.start();
20
+
21
+ // Set up redaction for sensitive fields
22
+ tracker.setRedactedFields(['#password', 'input[type="password"]']);
23
+ </script>
24
+ ```
7
25
 
8
- Add the following to your `providers.tsx` file.
9
- ```ts
26
+ ### Next.js
27
+
28
+ Add the following to your `providers.tsx` file:
29
+
30
+ ```tsx
10
31
  "use client"
11
32
 
12
- import { KoalawareTracker } from "koalaware-js";
13
- import { KoalawareProvider as KoalawareProviderJS } from "koalaware-js/react";
33
+ import { HumanBehaviorTracker } from "humanbehavior-js";
34
+ import { HumanBehaviorProvider } from "humanbehavior-js/react";
14
35
  import { useEffect, useState } from "react";
15
36
 
16
- export function KoalawareProvider({ children }: { children: React.ReactNode }) {
17
- const [koalaware, setKoalaware] = useState<KoalawareTracker | null>(null);
37
+ export function HumanBehaviorProviderWrapper({ children }: { children: React.ReactNode }) {
38
+ const [tracker, setTracker] = useState<HumanBehaviorTracker | null>(null);
18
39
 
19
40
  useEffect(() => {
20
- const apiKey = "kw_1c8969a408ea2eb333fd174a7684c7c943e82ea1df8349b43640e4b608f35e68";
21
- const koalaware = new KoalawareTracker(apiKey);
22
- setKoalaware(koalaware);
41
+ const apiKey = process.env.NEXT_PUBLIC_HUMANBEHAVIOR_API_KEY;
42
+ if (apiKey) {
43
+ const tracker = new HumanBehaviorTracker(apiKey);
44
+ setTracker(tracker);
45
+ }
23
46
  }, []);
24
47
 
25
48
  return (
26
- <KoalawareProviderJS client={koalaware}>
49
+ <HumanBehaviorProvider client={tracker}>
50
+ {children}
51
+ </HumanBehaviorProvider>
52
+ )
53
+ }
54
+ ```
55
+
56
+ Or use the simpler approach with just an API key:
57
+
58
+ ```tsx
59
+ "use client"
60
+
61
+ import { HumanBehaviorProvider } from "humanbehavior-js/react";
62
+
63
+ export function HumanBehaviorProviderWrapper({ children }: { children: React.ReactNode }) {
64
+ return (
65
+ <HumanBehaviorProvider apiKey={process.env.NEXT_PUBLIC_HUMANBEHAVIOR_API_KEY}>
27
66
  {children}
28
- </KoalawareProviderJS>
67
+ </HumanBehaviorProvider>
29
68
  )
30
69
  }
31
70
  ```
32
71
 
33
- Next, add the provider to your root app layout.
34
- ```ts
72
+ Next, add the provider to your root app layout:
73
+
74
+ ```tsx
35
75
  export default async function RootLayout({
36
76
  children
37
77
  }: {
38
78
  children: React.ReactNode;
39
79
  }) {
40
- const session = await auth();
41
80
  return (
42
- <html lang='en' className={`${lato.className}`} suppressHydrationWarning>
43
- <body className={'overflow-hidden'}>
44
- <NextTopLoader showSpinner={false} />
45
- <KoalawareProvider>
46
- <NuqsAdapter>
47
- <Providers session={session}>
48
- <Toaster />
49
- {children}
50
- </Providers>
51
- </NuqsAdapter>
52
- </KoalawareProvider>
81
+ <html lang='en' suppressHydrationWarning>
82
+ <body>
83
+ <HumanBehaviorProviderWrapper>
84
+ {children}
85
+ </HumanBehaviorProviderWrapper>
53
86
  </body>
54
87
  </html>
55
88
  );
56
89
  }
90
+ ```
91
+
92
+ ### Using the Hook
93
+
94
+ In any component within the provider:
95
+
96
+ ```tsx
97
+ import { useHumanBehavior } from "humanbehavior-js/react";
98
+
99
+ export function MyComponent() {
100
+ const humanBehavior = useHumanBehavior();
101
+
102
+ const handleUserAction = async () => {
103
+ // Add user information
104
+ await humanBehavior.addUserInfo({
105
+ email: "user@example.com",
106
+ name: "John Doe"
107
+ });
108
+
109
+ // Track custom events
110
+ humanBehavior.addEvent({
111
+ type: "custom",
112
+ name: "button_clicked",
113
+ data: { buttonId: "submit" }
114
+ });
115
+ };
116
+
117
+ return <button onClick={handleUserAction}>Click me</button>;
118
+ }
119
+ ```
120
+
121
+ ## Environment Variables
122
+
123
+ For security, store your API key in environment variables:
124
+
125
+ ```bash
126
+ # .env.local
127
+ NEXT_PUBLIC_HUMANBEHAVIOR_API_KEY=your-api-key-here
128
+ NEXT_PUBLIC_INGESTION_URL=https://ingestion.humanbehavior.ai
129
+ ```
130
+
131
+ ## Features
132
+
133
+ - **Session Recording**: Automatic recording of user interactions
134
+ - **Data Redaction**: Protect sensitive information in recordings
135
+ - **User Identification**: Track users across sessions
136
+ - **Custom Events**: Send custom analytics events
137
+ - **React Integration**: Hooks and providers for React applications
138
+
139
+ ## Build
140
+
141
+ To build the SDK locally:
142
+
143
+ ```bash
144
+ npm run build
57
145
  ```
package/src/api.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { logError, logInfo } from './utils/logger';
2
+
1
3
  export const MAX_CHUNK_SIZE_BYTES = 1024 * 1024 * 10; // 10MB chunk size
2
4
 
3
5
  export function isChunkSizeExceeded(currentChunk: any[], newEvent: any, sessionId: string): boolean {
@@ -30,15 +32,27 @@ export class HumanBehaviorAPI {
30
32
  }
31
33
 
32
34
  public async init(sessionId: string, userId: string | null) {
35
+ // Get current page URL and referrer if in browser environment
36
+ let entryURL = null;
37
+ let referrer = null;
38
+
39
+ if (typeof window !== 'undefined') {
40
+ entryURL = window.location.href;
41
+ referrer = document.referrer;
42
+ }
43
+
33
44
  const response = await fetch(`${this.baseUrl}/api/ingestion/init`, {
34
45
  method: 'POST',
35
46
  headers: {
36
47
  'Content-Type': 'application/json',
37
- 'Authorization': `Bearer ${this.apiKey}`
48
+ 'Authorization': `Bearer ${this.apiKey}`,
49
+ 'Referer': referrer || ''
38
50
  },
39
51
  body: JSON.stringify({
40
52
  sessionId: sessionId,
41
- endUserId: userId
53
+ endUserId: userId,
54
+ entryURL: entryURL,
55
+ referrer: referrer
42
56
  })
43
57
  });
44
58
 
@@ -135,7 +149,7 @@ export class HumanBehaviorAPI {
135
149
 
136
150
  return results.flat();
137
151
  } catch (error) {
138
- console.error('Error sending events:', error);
152
+ logError('Error sending events:', error);
139
153
  throw error;
140
154
  }
141
155
  }
@@ -161,7 +175,7 @@ export class HumanBehaviorAPI {
161
175
 
162
176
  return await response.json();
163
177
  } catch (error) {
164
- console.error('Error sending user data:', error);
178
+ logError('Error sending user data:', error);
165
179
  throw error;
166
180
  }
167
181
  }
@@ -188,7 +202,7 @@ export class HumanBehaviorAPI {
188
202
 
189
203
  return await response.json();
190
204
  } catch (error) {
191
- console.error('Error authenticating user:', error);
205
+ logError('Error authenticating user:', error);
192
206
  throw error;
193
207
  }
194
208
  }
@@ -236,7 +250,7 @@ export class HumanBehaviorAPI {
236
250
  } catch (error) {
237
251
  retryCount++;
238
252
  if (retryCount === maxRetries) {
239
- console.error('Error sending custom event after max retries:', error);
253
+ logError('Error sending custom event after max retries:', error);
240
254
  throw error;
241
255
  }
242
256
  // Exponential backoff
@@ -273,7 +287,7 @@ export class HumanBehaviorAPI {
273
287
  } catch (error) {
274
288
  retryCount++;
275
289
  if (retryCount === maxRetries) {
276
- console.error('Error sending custom events after max retries:', error);
290
+ logError('Error sending custom events after max retries:', error);
277
291
  throw error;
278
292
  }
279
293
  // Exponential backoff
@@ -289,7 +303,7 @@ export class HumanBehaviorAPI {
289
303
  data.append('timestamp', encodeURIComponent(Date.now().toString()))
290
304
  data.append('apiKey', encodeURIComponent(this.apiKey))
291
305
  if (isSessionComplete) {
292
- console.log('Session complete beacon sending');
306
+ logInfo('Session complete beacon sending');
293
307
  localStorage.setItem('koalaware_session_complete', Date.now().toString());
294
308
  data.append('sessionComplete', encodeURIComponent('true'))
295
309
  }
package/src/index.ts CHANGED
@@ -13,6 +13,9 @@ export * from './api';
13
13
  // Export redaction functionality
14
14
  export * from './redact';
15
15
 
16
+ // Export logger functionality
17
+ export * from './utils/logger';
18
+
16
19
  // Also export the tracker as the default export
17
20
  export { HumanBehaviorTracker as default } from './tracker';
18
21
 
@@ -1,5 +1,6 @@
1
- import React, { useEffect, useState, createContext, useContext, ReactNode } from "react";
1
+ import React, { useEffect, useState, createContext, useContext, ReactNode, useCallback, useMemo, useRef } from "react";
2
2
  import { HumanBehaviorTracker } from "..";
3
+ import { logError, logWarn, logDebug } from "../utils/logger";
3
4
 
4
5
  // Check if we're in a browser environment
5
6
  const isBrowser = () => typeof window !== 'undefined';
@@ -25,6 +26,7 @@ interface HumanBehaviorProviderProps {
25
26
  children: ReactNode;
26
27
  }
27
28
 
29
+ // Default context to prevent unnecessary re-renders
28
30
  const defaultContext: HumanBehaviorContextType = {
29
31
  humanBehavior: null,
30
32
  queueEvent: (event: any) => {
@@ -32,7 +34,7 @@ const defaultContext: HumanBehaviorContextType = {
32
34
  if (typeof window === 'undefined') {
33
35
  return;
34
36
  }
35
- console.warn('HumanBehavior not initialized yet, event queued:', event);
37
+ logWarn('HumanBehavior not initialized yet, event queued:', event);
36
38
  }
37
39
  };
38
40
 
@@ -43,11 +45,27 @@ export const HumanBehaviorProvider = ({ apiKey, client, children }: HumanBehavio
43
45
  const [eventQueue, setEventQueue] = useState<any[]>([]);
44
46
  const [isMounted, setIsMounted] = useState(false);
45
47
  const [isInitialized, setIsInitialized] = useState(false);
48
+
49
+ // Use refs to avoid dependency issues in useEffect
50
+ const apiKeyRef = useRef(apiKey);
51
+ const clientRef = useRef(client);
52
+ const eventQueueRef = useRef(eventQueue);
53
+
54
+ // Update refs when props change
55
+ useEffect(() => {
56
+ apiKeyRef.current = apiKey;
57
+ clientRef.current = client;
58
+ }, [apiKey, client]);
59
+
60
+ // Update eventQueue ref when queue changes
61
+ useEffect(() => {
62
+ eventQueueRef.current = eventQueue;
63
+ }, [eventQueue]);
46
64
 
47
- // Function to queue events before initialization
48
- const queueEvent = (event: any) => {
65
+ // Memoized queueEvent function to prevent unnecessary re-renders
66
+ const queueEvent = useCallback((event: any) => {
49
67
  setEventQueue(prev => [...prev, event]);
50
- };
68
+ }, []);
51
69
 
52
70
  // Handle mounting state
53
71
  useEffect(() => {
@@ -66,15 +84,15 @@ export const HumanBehaviorProvider = ({ apiKey, client, children }: HumanBehavio
66
84
  }
67
85
 
68
86
  // If client is provided, use that
69
- if (client) {
70
- setHumanBehavior(client);
87
+ if (clientRef.current) {
88
+ setHumanBehavior(clientRef.current);
71
89
  setIsInitialized(true);
72
90
  return;
73
91
  }
74
92
 
75
93
  // If no client is provided, apiKey is required
76
- if (!apiKey || apiKey.trim() === '') {
77
- console.error("An apiKey is required when no client is provided");
94
+ if (!apiKeyRef.current || apiKeyRef.current.trim() === '') {
95
+ logError("An apiKey is required when no client is provided");
78
96
  return;
79
97
  }
80
98
 
@@ -83,7 +101,10 @@ export const HumanBehaviorProvider = ({ apiKey, client, children }: HumanBehavio
83
101
  }
84
102
 
85
103
  // Create new tracker instance with the validated apiKey
86
- const tracker = new HumanBehaviorTracker(apiKey.trim(), process.env.NEXT_PUBLIC_INGESTION_URL || 'https://ingestion.humanbehavior.ai');
104
+ const tracker = new HumanBehaviorTracker(
105
+ apiKeyRef.current.trim(),
106
+ process.env.NEXT_PUBLIC_INGESTION_URL || 'https://ingestion.humanbehavior.ai'
107
+ );
87
108
  setHumanBehavior(tracker);
88
109
 
89
110
  // Wait for initialization to complete
@@ -91,10 +112,11 @@ export const HumanBehaviorProvider = ({ apiKey, client, children }: HumanBehavio
91
112
  setIsInitialized(true);
92
113
 
93
114
  // Process any queued events
94
- if (eventQueue.length > 0) {
95
- eventQueue.forEach(event => {
115
+ const currentQueue = eventQueueRef.current;
116
+ if (currentQueue.length > 0) {
117
+ currentQueue.forEach(event => {
96
118
  if (event.type === 'identify') {
97
- console.log('Processing queued identify event', event.userProperties);
119
+ logDebug('Processing queued identify event', event.userProperties);
98
120
  tracker.addUserInfo(event.userProperties);
99
121
  } else {
100
122
  tracker.addEvent(event);
@@ -103,40 +125,66 @@ export const HumanBehaviorProvider = ({ apiKey, client, children }: HumanBehavio
103
125
  setEventQueue([]); // Clear the queue
104
126
  }
105
127
  }).catch(error => {
106
- console.error('Failed to initialize HumanBehaviorTracker:', error);
128
+ logError('Failed to initialize HumanBehaviorTracker:', error);
107
129
  });
108
- }, [apiKey, client, eventQueue, isMounted]);
130
+ }, [isMounted, humanBehavior]); // Removed apiKey, client, eventQueue from dependencies
131
+
132
+ // Memoized context value to prevent unnecessary re-renders
133
+ const contextValue = useMemo(() => {
134
+ if (!isMounted) {
135
+ return { humanBehavior: null, queueEvent };
136
+ }
137
+
138
+ if (!isInitialized) {
139
+ return { humanBehavior: null, queueEvent };
140
+ }
141
+
142
+ return { humanBehavior, queueEvent };
143
+ }, [isMounted, isInitialized, humanBehavior, queueEvent]);
109
144
 
110
145
  // If not in browser, render children without context
111
146
  if (!(isBrowser())) {
112
147
  return <>{children}</>;
113
148
  }
114
149
 
115
- // If not mounted yet, render children with queuing context
116
- if (!isMounted) {
117
- return (
118
- <HumanBehaviorContext.Provider value={{ humanBehavior: null, queueEvent }}>
119
- {children}
120
- </HumanBehaviorContext.Provider>
121
- );
122
- }
123
-
124
- // If not initialized yet, render children with queuing context
125
- if (!isInitialized) {
126
- return (
127
- <HumanBehaviorContext.Provider value={{ humanBehavior: null, queueEvent }}>
128
- {children}
129
- </HumanBehaviorContext.Provider>
130
- );
131
- }
132
-
133
150
  return (
134
- <HumanBehaviorContext.Provider value={{ humanBehavior, queueEvent }}>
151
+ <HumanBehaviorContext.Provider value={contextValue}>
135
152
  {children}
136
153
  </HumanBehaviorContext.Provider>
137
154
  );
138
155
  };
139
156
 
157
+ // No-op implementation for server-side
158
+ const serverSideImplementation: HumanBehaviorInterface = {
159
+ addEvent: () => {},
160
+ addUserInfo: async () => {},
161
+ start: () => {},
162
+ stop: () => {},
163
+ viewLogs: () => {},
164
+ };
165
+
166
+ // Memoized queuing implementation for initialization period
167
+ const createQueuingImplementation = (queueEvent: (event: any) => void): HumanBehaviorInterface => ({
168
+ addEvent: (event: any) => {
169
+ queueEvent(event);
170
+ },
171
+ addUserInfo: async (userProperties: Record<string, any>) => {
172
+ queueEvent({
173
+ type: 'identify',
174
+ userProperties,
175
+ });
176
+ },
177
+ start: () => {
178
+ // Start will be called automatically when initialized
179
+ },
180
+ stop: () => {
181
+ // Stop is a no-op when not initialized
182
+ },
183
+ viewLogs: () => {
184
+ logWarn('Logs are not available until HumanBehaviorTracker is initialized');
185
+ }
186
+ });
187
+
140
188
  export const useHumanBehavior = (): HumanBehaviorInterface => {
141
189
  const context = useContext(HumanBehaviorContext);
142
190
 
@@ -147,14 +195,8 @@ export const useHumanBehavior = (): HumanBehaviorInterface => {
147
195
 
148
196
  // If we're in the server-side, return a no-op implementation
149
197
  if (typeof window === 'undefined') {
150
- console.warn('HumanBehavior is being used before being initialized');
151
- return {
152
- addEvent: () => {},
153
- addUserInfo: async () => {},
154
- start: () => {},
155
- stop: () => {},
156
- viewLogs: () => {},
157
- };
198
+ logWarn('HumanBehavior is being used before being initialized');
199
+ return serverSideImplementation;
158
200
  }
159
201
 
160
202
  // If we have an initialized tracker, return it as the interface
@@ -166,24 +208,5 @@ export const useHumanBehavior = (): HumanBehaviorInterface => {
166
208
  // - context.humanBehavior is null
167
209
  // - context.queueEvent is available
168
210
  // - we need to queue all operations until initialization completes
169
- return {
170
- addEvent: (event: any) => {
171
- context.queueEvent(event);
172
- },
173
- addUserInfo: async (userProperties: Record<string, any>) => {
174
- context.queueEvent({
175
- type: 'identify',
176
- userProperties,
177
- });
178
- },
179
- start: () => {
180
- // Start will be called automatically when initialized
181
- },
182
- stop: () => {
183
- // Stop is a no-op when not initialized
184
- },
185
- viewLogs: () => {
186
- console.warn('Logs are not available until HumanBehaviorTracker is initialized');
187
- }
188
- };
211
+ return useMemo(() => createQueuingImplementation(context.queueEvent), [context.queueEvent]);
189
212
  };