humanbehavior-js 0.0.8 → 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
+ private originalConsole;
105
+ private originalLogger;
106
+ private consoleTrackingEnabled;
104
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.8",
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/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 {
@@ -147,7 +149,7 @@ export class HumanBehaviorAPI {
147
149
 
148
150
  return results.flat();
149
151
  } catch (error) {
150
- console.error('Error sending events:', error);
152
+ logError('Error sending events:', error);
151
153
  throw error;
152
154
  }
153
155
  }
@@ -173,7 +175,7 @@ export class HumanBehaviorAPI {
173
175
 
174
176
  return await response.json();
175
177
  } catch (error) {
176
- console.error('Error sending user data:', error);
178
+ logError('Error sending user data:', error);
177
179
  throw error;
178
180
  }
179
181
  }
@@ -200,7 +202,7 @@ export class HumanBehaviorAPI {
200
202
 
201
203
  return await response.json();
202
204
  } catch (error) {
203
- console.error('Error authenticating user:', error);
205
+ logError('Error authenticating user:', error);
204
206
  throw error;
205
207
  }
206
208
  }
@@ -248,7 +250,7 @@ export class HumanBehaviorAPI {
248
250
  } catch (error) {
249
251
  retryCount++;
250
252
  if (retryCount === maxRetries) {
251
- console.error('Error sending custom event after max retries:', error);
253
+ logError('Error sending custom event after max retries:', error);
252
254
  throw error;
253
255
  }
254
256
  // Exponential backoff
@@ -285,7 +287,7 @@ export class HumanBehaviorAPI {
285
287
  } catch (error) {
286
288
  retryCount++;
287
289
  if (retryCount === maxRetries) {
288
- console.error('Error sending custom events after max retries:', error);
290
+ logError('Error sending custom events after max retries:', error);
289
291
  throw error;
290
292
  }
291
293
  // Exponential backoff
@@ -301,7 +303,7 @@ export class HumanBehaviorAPI {
301
303
  data.append('timestamp', encodeURIComponent(Date.now().toString()))
302
304
  data.append('apiKey', encodeURIComponent(this.apiKey))
303
305
  if (isSessionComplete) {
304
- console.log('Session complete beacon sending');
306
+ logInfo('Session complete beacon sending');
305
307
  localStorage.setItem('koalaware_session_complete', Date.now().toString());
306
308
  data.append('sessionComplete', encodeURIComponent('true'))
307
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
  };
package/src/redact.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  // Redaction functionality for sensitive input fields
2
2
  // This module provides methods to redact sensitive input fields in event recordings
3
3
 
4
+ import { logDebug, logWarn } from './utils/logger';
5
+
4
6
  // Check if we're in a browser environment
5
7
  const isBrowser = typeof window !== 'undefined';
6
8
 
@@ -39,18 +41,18 @@ export class RedactionManager {
39
41
  fields.forEach(field => this.userSelectedFields.add(field));
40
42
 
41
43
  if (fields.length > 0) {
42
- console.log(`Redaction: Active for ${fields.length} field(s):`, fields);
44
+ logDebug(`Redaction: Active for ${fields.length} field(s):`, fields);
43
45
 
44
46
  // Debug: Check if elements exist
45
47
  fields.forEach(selector => {
46
48
  const elements = document.querySelectorAll(selector);
47
- console.log(`Redaction: Found ${elements.length} element(s) for selector '${selector}'`);
49
+ logDebug(`Redaction: Found ${elements.length} element(s) for selector '${selector}'`);
48
50
  elements.forEach((el, index) => {
49
- console.log(`Redaction: Element ${index} for '${selector}':`, el);
51
+ logDebug(`Redaction: Element ${index} for '${selector}':`, el);
50
52
  });
51
53
  });
52
54
  } else {
53
- console.log('Redaction: Disabled - no fields selected');
55
+ logDebug('Redaction: Disabled - no fields selected');
54
56
  }
55
57
  }
56
58
 
@@ -85,7 +87,7 @@ export class RedactionManager {
85
87
  if (processedEvent.data.source === 5) { // Input event
86
88
  const shouldRedact = this.isFieldSelected(processedEvent.data);
87
89
  if (shouldRedact) {
88
- console.log('Redaction: Processing input event for redaction');
90
+ logDebug('Redaction: Processing input event for redaction');
89
91
  this.redactInputEvent(processedEvent.data);
90
92
  }
91
93
  }
@@ -114,14 +116,14 @@ export class RedactionManager {
114
116
  return;
115
117
  }
116
118
 
117
- console.log('Redaction: Redacting input event with text:', inputData.text);
119
+ logDebug('Redaction: Redacting input event with text:', inputData.text);
118
120
 
119
121
  // Redact all text-related properties that could contain input data
120
122
  const textProperties = ['text', 'value', 'content', 'data', 'input', 'textContent'];
121
123
  textProperties.forEach(prop => {
122
124
  if (inputData[prop] !== undefined && typeof inputData[prop] === 'string') {
123
125
  inputData[prop] = this.redactedText;
124
- console.log(`Redaction: Redacted property '${prop}'`);
126
+ logDebug(`Redaction: Redacted property '${prop}'`);
125
127
  }
126
128
  });
127
129
 
@@ -129,7 +131,7 @@ export class RedactionManager {
129
131
  Object.keys(inputData).forEach(key => {
130
132
  if (typeof inputData[key] === 'string' && inputData[key].length > 0) {
131
133
  inputData[key] = this.redactedText;
132
- console.log(`Redaction: Redacted additional property '${key}'`);
134
+ logDebug(`Redaction: Redacted additional property '${key}'`);
133
135
  }
134
136
  });
135
137
 
@@ -137,11 +139,11 @@ export class RedactionManager {
137
139
  if (inputData.attributes && typeof inputData.attributes === 'object') {
138
140
  if (inputData.attributes.value && typeof inputData.attributes.value === 'string') {
139
141
  inputData.attributes.value = this.redactedText;
140
- console.log('Redaction: Redacted nested value attribute');
142
+ logDebug('Redaction: Redacted nested value attribute');
141
143
  }
142
144
  }
143
145
 
144
- console.log('Redaction: Input event redaction complete');
146
+ logDebug('Redaction: Input event redaction complete');
145
147
  }
146
148
 
147
149
  /**
@@ -209,7 +211,7 @@ export class RedactionManager {
209
211
 
210
212
  return false;
211
213
  } catch (e) {
212
- console.warn('Error checking if DOM change should be redacted:', e);
214
+ logWarn('Error checking if DOM change should be redacted:', e);
213
215
  return false;
214
216
  }
215
217
  }
@@ -373,10 +375,10 @@ export class RedactionManager {
373
375
  if (matchingElements.length > 0) {
374
376
  // Check if any of these elements are currently focused
375
377
  for (const el of matchingElements) {
376
- if (el === document.activeElement) {
377
- console.log('Redaction: Found focused element matching selector:', selector);
378
- return true;
379
- }
378
+ if (el === document.activeElement) {
379
+ logDebug('Redaction: Found focused element matching selector:', selector);
380
+ return true;
381
+ }
380
382
  }
381
383
  }
382
384
  }
@@ -385,7 +387,7 @@ export class RedactionManager {
385
387
  // Look for any input element that might be the active one
386
388
  const activeElement = document.activeElement;
387
389
  if (activeElement && this.shouldRedactElement(activeElement as HTMLElement)) {
388
- console.log('Redaction: Active element should be redacted');
390
+ logDebug('Redaction: Active element should be redacted');
389
391
  return true;
390
392
  }
391
393
 
@@ -423,7 +425,7 @@ export class RedactionManager {
423
425
 
424
426
  return false;
425
427
  } catch (e) {
426
- console.warn('Error checking if field should be redacted:', e);
428
+ logWarn('Error checking if field should be redacted:', e);
427
429
  return false;
428
430
  }
429
431
  }