humanbehavior-js 0.4.28 → 0.5.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/README.md +151 -0
- package/package.json +114 -79
- package/packages/angular/dist/index.d.ts +46 -0
- package/packages/angular/dist/index.d.ts.map +1 -0
- package/packages/angular/dist/index.js +2 -0
- package/packages/angular/dist/index.js.map +1 -0
- package/packages/angular/dist/index.mjs +2 -0
- package/packages/angular/dist/index.mjs.map +1 -0
- package/packages/browser/dist/index.d.ts +5 -0
- package/packages/browser/dist/index.d.ts.map +1 -0
- package/packages/browser/dist/index.iife.js +12095 -0
- package/packages/browser/dist/index.iife.js.map +1 -0
- package/packages/browser/dist/index.js +2 -0
- package/packages/browser/dist/index.js.map +1 -0
- package/packages/browser/dist/index.min.js +2 -0
- package/packages/browser/dist/index.min.js.map +1 -0
- package/packages/browser/dist/index.mjs +2 -0
- package/packages/browser/dist/index.mjs.map +1 -0
- package/packages/react/dist/browser.d.ts +2 -0
- package/packages/react/dist/browser.d.ts.map +1 -0
- package/packages/react/dist/index.d.ts +48 -0
- package/packages/react/dist/index.d.ts.map +1 -0
- package/packages/react/dist/index.js +2 -0
- package/packages/react/dist/index.js.map +1 -0
- package/packages/react/dist/index.mjs +2 -0
- package/packages/react/dist/index.mjs.map +1 -0
- package/packages/remix/dist/index.d.ts +8 -0
- package/packages/remix/dist/index.d.ts.map +1 -0
- package/packages/remix/dist/index.js +2 -0
- package/packages/remix/dist/index.js.map +1 -0
- package/packages/remix/dist/index.mjs +2 -0
- package/packages/remix/dist/index.mjs.map +1 -0
- package/packages/svelte/dist/index.d.ts +11 -0
- package/packages/svelte/dist/index.d.ts.map +1 -0
- package/packages/svelte/dist/index.js +2 -0
- package/packages/svelte/dist/index.js.map +1 -0
- package/packages/svelte/dist/index.mjs +2 -0
- package/packages/svelte/dist/index.mjs.map +1 -0
- package/{dist/types/vue → packages/vue/dist}/index.d.ts +4 -5
- package/packages/vue/dist/index.d.ts.map +1 -0
- package/packages/vue/dist/index.js +2 -0
- package/packages/vue/dist/index.js.map +1 -0
- package/packages/vue/dist/index.mjs +2 -0
- package/packages/vue/dist/index.mjs.map +1 -0
- package/packages/wizard/dist/ai/ai-install-wizard.d.ts +145 -0
- package/packages/wizard/dist/ai/ai-install-wizard.d.ts.map +1 -0
- package/packages/wizard/dist/ai/manual-framework-wizard.d.ts +52 -0
- package/packages/wizard/dist/ai/manual-framework-wizard.d.ts.map +1 -0
- package/packages/wizard/dist/cli/ai-auto-install.d.ts +27 -0
- package/packages/wizard/dist/cli/ai-auto-install.d.ts.map +1 -0
- package/{dist → packages/wizard/dist}/cli/ai-auto-install.js +821 -905
- package/packages/wizard/dist/cli/ai-auto-install.js.map +1 -0
- package/packages/wizard/dist/cli/auto-install.d.ts +26 -0
- package/packages/wizard/dist/cli/auto-install.d.ts.map +1 -0
- package/{dist → packages/wizard/dist}/cli/auto-install.js +821 -905
- package/packages/wizard/dist/cli/auto-install.js.map +1 -0
- package/{dist/types → packages/wizard/dist/core}/install-wizard.d.ts +6 -8
- package/packages/wizard/dist/core/install-wizard.d.ts.map +1 -0
- package/packages/wizard/dist/index.d.ts +18 -0
- package/packages/wizard/dist/index.d.ts.map +1 -0
- package/packages/wizard/dist/index.js +2 -0
- package/packages/wizard/dist/index.js.map +1 -0
- package/packages/wizard/dist/index.mjs +2 -0
- package/packages/wizard/dist/index.mjs.map +1 -0
- package/packages/wizard/dist/services/centralized-ai-service.d.ts +159 -0
- package/packages/wizard/dist/services/centralized-ai-service.d.ts.map +1 -0
- package/packages/wizard/dist/services/remote-ai-service.d.ts +58 -0
- package/packages/wizard/dist/services/remote-ai-service.d.ts.map +1 -0
- package/WIZARD_USAGE_GUIDE.md +0 -381
- package/dist/cjs/angular/index.cjs +0 -14979
- package/dist/cjs/angular/index.cjs.map +0 -1
- package/dist/cjs/index.cjs +0 -14964
- package/dist/cjs/index.cjs.map +0 -1
- package/dist/cjs/install-wizard.cjs +0 -1576
- package/dist/cjs/install-wizard.cjs.map +0 -1
- package/dist/cjs/react/index.cjs +0 -15103
- package/dist/cjs/react/index.cjs.map +0 -1
- package/dist/cjs/remix/index.cjs +0 -15077
- package/dist/cjs/remix/index.cjs.map +0 -1
- package/dist/cjs/svelte/index.cjs +0 -14933
- package/dist/cjs/svelte/index.cjs.map +0 -1
- package/dist/cjs/vue/index.cjs +0 -14942
- package/dist/cjs/vue/index.cjs.map +0 -1
- package/dist/cjs/wizard/index.cjs +0 -3490
- package/dist/cjs/wizard/index.cjs.map +0 -1
- package/dist/cli/ai-auto-install.js.map +0 -1
- package/dist/cli/auto-install.js.map +0 -1
- package/dist/esm/angular/index.js +0 -14975
- package/dist/esm/angular/index.js.map +0 -1
- package/dist/esm/index.js +0 -14941
- package/dist/esm/index.js.map +0 -1
- package/dist/esm/install-wizard.js +0 -1553
- package/dist/esm/install-wizard.js.map +0 -1
- package/dist/esm/react/index.js +0 -15097
- package/dist/esm/react/index.js.map +0 -1
- package/dist/esm/remix/index.js +0 -15073
- package/dist/esm/remix/index.js.map +0 -1
- package/dist/esm/svelte/index.js +0 -14931
- package/dist/esm/svelte/index.js.map +0 -1
- package/dist/esm/vue/index.js +0 -14940
- package/dist/esm/vue/index.js.map +0 -1
- package/dist/esm/wizard/index.js +0 -3459
- package/dist/esm/wizard/index.js.map +0 -1
- package/dist/index.min.js +0 -2
- package/dist/index.min.js.map +0 -1
- package/dist/types/angular/index.d.ts +0 -357
- package/dist/types/index.d.ts +0 -644
- package/dist/types/react/index.d.ts +0 -345
- package/dist/types/remix/index.d.ts +0 -336
- package/dist/types/svelte/index.d.ts +0 -322
- package/dist/types/wizard/index.d.ts +0 -523
- package/readme.md +0 -335
- package/rollup.config.js +0 -422
- package/simple-spa.html +0 -1000
- package/src/angular/index.ts +0 -79
- package/src/api.ts +0 -416
- package/src/index.ts +0 -35
- package/src/react/AutoInstallWizard.tsx +0 -557
- package/src/react/browser.ts +0 -8
- package/src/react/index.tsx +0 -308
- package/src/redact.ts +0 -327
- package/src/remix/index.ts +0 -16
- package/src/svelte/index.ts +0 -14
- package/src/tracker.ts +0 -1587
- package/src/types/clack.d.ts +0 -31
- package/src/utils/ip-detector.ts +0 -158
- package/src/utils/logger.ts +0 -144
- package/src/utils/property-detector.ts +0 -345
- package/src/utils/property-manager.ts +0 -274
- package/src/vue/index.ts +0 -29
- package/src/wizard/README.md +0 -114
- package/src/wizard/ai/ai-install-wizard.ts +0 -897
- package/src/wizard/ai/manual-framework-wizard.ts +0 -238
- package/src/wizard/cli/ai-auto-install.ts +0 -241
- package/src/wizard/cli/auto-install.ts +0 -224
- package/src/wizard/core/install-wizard.ts +0 -1794
- package/src/wizard/index.ts +0 -23
- package/src/wizard/services/centralized-ai-service.ts +0 -668
- package/src/wizard/services/remote-ai-service.ts +0 -240
- package/tsconfig.json +0 -24
package/src/react/index.tsx
DELETED
|
@@ -1,308 +0,0 @@
|
|
|
1
|
-
import React, { useEffect, useState, createContext, useContext, ReactNode, useCallback, useMemo, useRef } from "react";
|
|
2
|
-
import { HumanBehaviorTracker, logError, logWarn, logDebug } from "./browser";
|
|
3
|
-
|
|
4
|
-
// Check if we're in a browser environment
|
|
5
|
-
const isBrowser = () => typeof window !== 'undefined';
|
|
6
|
-
|
|
7
|
-
// Define the public interface that components will interact with
|
|
8
|
-
interface HumanBehaviorInterface {
|
|
9
|
-
addEvent: (event: any) => void;
|
|
10
|
-
identifyUser: ({ userProperties }: { userProperties: Record<string, any> }) => Promise<string>;
|
|
11
|
-
start: () => void;
|
|
12
|
-
stop: () => void;
|
|
13
|
-
logout: () => void;
|
|
14
|
-
viewLogs: () => void;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
interface HumanBehaviorContextType {
|
|
18
|
-
humanBehavior: HumanBehaviorTracker | null;
|
|
19
|
-
queueEvent: (event: any) => void;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
interface HumanBehaviorProviderProps {
|
|
23
|
-
// Either provide an apiKey to create a new client, or provide an existing client
|
|
24
|
-
apiKey?: string;
|
|
25
|
-
client?: HumanBehaviorTracker;
|
|
26
|
-
children: ReactNode;
|
|
27
|
-
options?: {
|
|
28
|
-
ingestionUrl?: string;
|
|
29
|
-
logLevel?: 'none' | 'error' | 'warn' | 'info' | 'debug';
|
|
30
|
-
redactFields?: string[];
|
|
31
|
-
suppressConsoleErrors?: boolean;
|
|
32
|
-
recordCanvas?: boolean; // Enable canvas recording with protection
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Default context to prevent unnecessary re-renders
|
|
37
|
-
const defaultContext: HumanBehaviorContextType = {
|
|
38
|
-
humanBehavior: null,
|
|
39
|
-
queueEvent: (event: any) => {
|
|
40
|
-
// In server-side, just no-op
|
|
41
|
-
if (typeof window === 'undefined') {
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
logWarn('HumanBehavior not initialized yet, event queued:', event);
|
|
45
|
-
}
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
const HumanBehaviorContext = createContext<HumanBehaviorTracker | null>(null);
|
|
49
|
-
|
|
50
|
-
export const HumanBehaviorProvider = ({ apiKey, client, children, options }: HumanBehaviorProviderProps) => {
|
|
51
|
-
const [humanBehavior, setHumanBehavior] = useState<HumanBehaviorTracker | null>(client || null);
|
|
52
|
-
const [eventQueue, setEventQueue] = useState<any[]>([]);
|
|
53
|
-
const [isMounted, setIsMounted] = useState(false);
|
|
54
|
-
const [isInitialized, setIsInitialized] = useState(false);
|
|
55
|
-
|
|
56
|
-
// Use refs to avoid dependency issues in useEffect
|
|
57
|
-
const apiKeyRef = useRef(apiKey);
|
|
58
|
-
const clientRef = useRef(client);
|
|
59
|
-
const eventQueueRef = useRef(eventQueue);
|
|
60
|
-
|
|
61
|
-
// Update refs when props change
|
|
62
|
-
useEffect(() => {
|
|
63
|
-
apiKeyRef.current = apiKey;
|
|
64
|
-
clientRef.current = client;
|
|
65
|
-
}, [apiKey, client]);
|
|
66
|
-
|
|
67
|
-
// Update eventQueue ref when queue changes
|
|
68
|
-
useEffect(() => {
|
|
69
|
-
eventQueueRef.current = eventQueue;
|
|
70
|
-
}, [eventQueue]);
|
|
71
|
-
|
|
72
|
-
// Memoized queueEvent function to prevent unnecessary re-renders
|
|
73
|
-
const queueEvent = useCallback((event: any) => {
|
|
74
|
-
setEventQueue(prev => [...prev, event]);
|
|
75
|
-
}, []);
|
|
76
|
-
|
|
77
|
-
// Handle mounting state
|
|
78
|
-
useEffect(() => {
|
|
79
|
-
setIsMounted(true);
|
|
80
|
-
}, []);
|
|
81
|
-
|
|
82
|
-
useEffect(() => {
|
|
83
|
-
// Only run in browser
|
|
84
|
-
if (!(isBrowser())) {
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Skip if not mounted yet (handles Next.js hydration)
|
|
89
|
-
if (!isMounted) {
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// If client is provided, use that
|
|
94
|
-
if (clientRef.current) {
|
|
95
|
-
setHumanBehavior(clientRef.current);
|
|
96
|
-
setIsInitialized(true);
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// If no client is provided, apiKey is required
|
|
101
|
-
if (!apiKeyRef.current || apiKeyRef.current.trim() === '') {
|
|
102
|
-
logError("An apiKey is required when no client is provided");
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if (humanBehavior !== null) {
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Create new tracker instance with the validated apiKey and options
|
|
111
|
-
const tracker = HumanBehaviorTracker.init(
|
|
112
|
-
apiKeyRef.current.trim(),
|
|
113
|
-
{
|
|
114
|
-
ingestionUrl: options?.ingestionUrl,
|
|
115
|
-
logLevel: options?.logLevel,
|
|
116
|
-
redactFields: options?.redactFields,
|
|
117
|
-
suppressConsoleErrors: options?.suppressConsoleErrors,
|
|
118
|
-
recordCanvas: options?.recordCanvas, // Pass canvas recording option
|
|
119
|
-
}
|
|
120
|
-
);
|
|
121
|
-
|
|
122
|
-
setHumanBehavior(tracker);
|
|
123
|
-
|
|
124
|
-
// Wait for initialization to complete
|
|
125
|
-
tracker.initializationPromise?.then(async () => {
|
|
126
|
-
await tracker.start();
|
|
127
|
-
setIsInitialized(true);
|
|
128
|
-
|
|
129
|
-
// Process any queued events
|
|
130
|
-
const currentQueue = eventQueueRef.current;
|
|
131
|
-
if (currentQueue.length > 0) {
|
|
132
|
-
for (const event of currentQueue) {
|
|
133
|
-
if (event.type === 'identify') {
|
|
134
|
-
logDebug('Processing queued identify event', event);
|
|
135
|
-
try {
|
|
136
|
-
await tracker.identifyUser({ userProperties: event.userProperties });
|
|
137
|
-
} catch (error) {
|
|
138
|
-
logError('Failed to process queued user info:', error);
|
|
139
|
-
}
|
|
140
|
-
} else {
|
|
141
|
-
tracker.addEvent(event);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
setEventQueue([]); // Clear the queue
|
|
145
|
-
}
|
|
146
|
-
}).catch(error => {
|
|
147
|
-
logError('Failed to initialize HumanBehaviorTracker:', error);
|
|
148
|
-
});
|
|
149
|
-
}, [isMounted, humanBehavior]); // Removed apiKey, client, eventQueue from dependencies
|
|
150
|
-
|
|
151
|
-
// Memoized context value to prevent unnecessary re-renders
|
|
152
|
-
const contextValue = useMemo(() => {
|
|
153
|
-
if (!isMounted) {
|
|
154
|
-
return null;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
if (!isInitialized) {
|
|
158
|
-
return null;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
return humanBehavior;
|
|
162
|
-
}, [isMounted, isInitialized, humanBehavior]);
|
|
163
|
-
|
|
164
|
-
// If not in browser, render children without context
|
|
165
|
-
if (!(isBrowser())) {
|
|
166
|
-
return <>{children}</>;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
return (
|
|
170
|
-
<HumanBehaviorContext.Provider value={contextValue}>
|
|
171
|
-
<HumanBehaviorPageView />
|
|
172
|
-
{children}
|
|
173
|
-
</HumanBehaviorContext.Provider>
|
|
174
|
-
);
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
// Default implementation for when tracker is not available
|
|
178
|
-
const defaultImplementation: HumanBehaviorInterface = {
|
|
179
|
-
addEvent: () => {},
|
|
180
|
-
identifyUser: async ({ userProperties }: { userProperties: Record<string, any> }) => {
|
|
181
|
-
return '';
|
|
182
|
-
},
|
|
183
|
-
start: () => {},
|
|
184
|
-
stop: () => {},
|
|
185
|
-
logout: () => {},
|
|
186
|
-
viewLogs: () => {},
|
|
187
|
-
};
|
|
188
|
-
|
|
189
|
-
// Memoized queuing implementation for initialization period
|
|
190
|
-
const createQueuingImplementation = (queueEvent: (event: any) => void): HumanBehaviorInterface => ({
|
|
191
|
-
addEvent: (event: any) => {
|
|
192
|
-
queueEvent(event);
|
|
193
|
-
},
|
|
194
|
-
identifyUser: async ({ userProperties }: { userProperties: Record<string, any> }) => {
|
|
195
|
-
queueEvent({
|
|
196
|
-
type: 'identify',
|
|
197
|
-
userProperties,
|
|
198
|
-
});
|
|
199
|
-
return ''; // Return empty string to match interface
|
|
200
|
-
},
|
|
201
|
-
start: () => {
|
|
202
|
-
// Start will be called automatically when initialized
|
|
203
|
-
},
|
|
204
|
-
stop: () => {
|
|
205
|
-
// Stop is a no-op when not initialized
|
|
206
|
-
},
|
|
207
|
-
logout: () => {
|
|
208
|
-
// Logout is a no-op when not initialized
|
|
209
|
-
},
|
|
210
|
-
viewLogs: () => {
|
|
211
|
-
logWarn('Logs are not available until HumanBehaviorTracker is initialized');
|
|
212
|
-
}
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
export const useHumanBehavior = () => {
|
|
216
|
-
const tracker = useContext(HumanBehaviorContext);
|
|
217
|
-
if (!tracker) {
|
|
218
|
-
throw new Error('useHumanBehavior must be used within a HumanBehaviorProvider');
|
|
219
|
-
}
|
|
220
|
-
return tracker;
|
|
221
|
-
};
|
|
222
|
-
|
|
223
|
-
// Custom hook for managing unredaction fields dynamically
|
|
224
|
-
export const useRedaction = () => {
|
|
225
|
-
const tracker = useHumanBehavior();
|
|
226
|
-
|
|
227
|
-
const setUnredactedFields = useCallback((fields: string[]) => {
|
|
228
|
-
tracker.setUnredactedFields(fields);
|
|
229
|
-
}, [tracker]);
|
|
230
|
-
|
|
231
|
-
const hasUnredactedFields = useCallback(() => {
|
|
232
|
-
return tracker.hasUnredactedFields();
|
|
233
|
-
}, [tracker]);
|
|
234
|
-
|
|
235
|
-
const getUnredactedFields = useCallback(() => {
|
|
236
|
-
return tracker.getUnredactedFields();
|
|
237
|
-
}, [tracker]);
|
|
238
|
-
|
|
239
|
-
return {
|
|
240
|
-
setUnredactedFields,
|
|
241
|
-
hasUnredactedFields,
|
|
242
|
-
getUnredactedFields
|
|
243
|
-
};
|
|
244
|
-
};
|
|
245
|
-
|
|
246
|
-
// Custom hook for managing user info
|
|
247
|
-
export const useUserTracking = () => {
|
|
248
|
-
const tracker = useHumanBehavior();
|
|
249
|
-
|
|
250
|
-
const identifyUser = useCallback(async ({ userProperties }: { userProperties: Record<string, any> }) => {
|
|
251
|
-
try {
|
|
252
|
-
await tracker.identifyUser({ userProperties });
|
|
253
|
-
return { success: true };
|
|
254
|
-
} catch (error) {
|
|
255
|
-
logError('Failed to identify user:', error);
|
|
256
|
-
return { success: false, error };
|
|
257
|
-
}
|
|
258
|
-
}, [tracker]);
|
|
259
|
-
|
|
260
|
-
return {
|
|
261
|
-
identifyUser
|
|
262
|
-
};
|
|
263
|
-
};
|
|
264
|
-
|
|
265
|
-
// Automatic page tracking component
|
|
266
|
-
function HumanBehaviorPageView() {
|
|
267
|
-
const tracker = useContext(HumanBehaviorContext);
|
|
268
|
-
|
|
269
|
-
useEffect(() => {
|
|
270
|
-
if (tracker && typeof window !== 'undefined') {
|
|
271
|
-
// Track initial page load
|
|
272
|
-
tracker.trackPageView();
|
|
273
|
-
|
|
274
|
-
// Listen for route changes (for SPAs)
|
|
275
|
-
const handleRouteChange = () => {
|
|
276
|
-
tracker.trackPageView();
|
|
277
|
-
};
|
|
278
|
-
|
|
279
|
-
// Listen for popstate (back/forward navigation)
|
|
280
|
-
window.addEventListener('popstate', handleRouteChange);
|
|
281
|
-
|
|
282
|
-
// Listen for pushstate/replacestate (programmatic navigation)
|
|
283
|
-
const originalPushState = history.pushState;
|
|
284
|
-
const originalReplaceState = history.replaceState;
|
|
285
|
-
|
|
286
|
-
history.pushState = function(...args) {
|
|
287
|
-
originalPushState.apply(this, args);
|
|
288
|
-
handleRouteChange();
|
|
289
|
-
};
|
|
290
|
-
|
|
291
|
-
history.replaceState = function(...args) {
|
|
292
|
-
originalReplaceState.apply(this, args);
|
|
293
|
-
handleRouteChange();
|
|
294
|
-
};
|
|
295
|
-
|
|
296
|
-
return () => {
|
|
297
|
-
window.removeEventListener('popstate', handleRouteChange);
|
|
298
|
-
history.pushState = originalPushState;
|
|
299
|
-
history.replaceState = originalReplaceState;
|
|
300
|
-
};
|
|
301
|
-
}
|
|
302
|
-
}, [tracker]);
|
|
303
|
-
|
|
304
|
-
return null;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
// Export the tracker class for direct use
|
|
308
|
-
export { HumanBehaviorTracker };
|
package/src/redact.ts
DELETED
|
@@ -1,327 +0,0 @@
|
|
|
1
|
-
// Simplified redaction functionality for HumanBehavior SDK
|
|
2
|
-
// Since rrweb auto-redacts all input fields by default, this module only handles
|
|
3
|
-
// selectively unredacting specific fields (except passwords which remain protected)
|
|
4
|
-
|
|
5
|
-
import { logDebug, logWarn } from './utils/logger';
|
|
6
|
-
|
|
7
|
-
// Check if we're in a browser environment
|
|
8
|
-
const isBrowser = typeof window !== 'undefined';
|
|
9
|
-
|
|
10
|
-
export interface RedactionOptions {
|
|
11
|
-
redactedText?: string;
|
|
12
|
-
excludeSelectors?: string[];
|
|
13
|
-
userFields?: string[]; // Fields that the user wants to unredact (legacy)
|
|
14
|
-
redactionStrategy?: {
|
|
15
|
-
mode: 'privacy-first' | 'visibility-first';
|
|
16
|
-
unredactFields?: string[]; // Fields to make visible (when mode: 'privacy-first')
|
|
17
|
-
redactFields?: string[]; // Fields to hide (when mode: 'visibility-first')
|
|
18
|
-
};
|
|
19
|
-
legacyRedactFields?: string[]; // For backward compatibility
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export class RedactionManager {
|
|
23
|
-
private redactedText: string = '[REDACTED]';
|
|
24
|
-
private unredactedFields: Set<string> = new Set(); // Fields that user wants to unredact
|
|
25
|
-
private redactedFields: Set<string> = new Set(); // Fields that user wants to redact
|
|
26
|
-
private redactionMode: 'privacy-first' | 'visibility-first' = 'privacy-first';
|
|
27
|
-
private excludeSelectors: string[] = [
|
|
28
|
-
'[data-no-redact="true"]',
|
|
29
|
-
'.human-behavior-no-redact'
|
|
30
|
-
];
|
|
31
|
-
|
|
32
|
-
constructor(options?: RedactionOptions) {
|
|
33
|
-
if (options?.redactedText) {
|
|
34
|
-
this.redactedText = options.redactedText;
|
|
35
|
-
}
|
|
36
|
-
if (options?.excludeSelectors) {
|
|
37
|
-
this.excludeSelectors = [...this.excludeSelectors, ...options.excludeSelectors];
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Handle new redaction strategy
|
|
41
|
-
if (options?.redactionStrategy) {
|
|
42
|
-
this.redactionMode = options.redactionStrategy.mode;
|
|
43
|
-
|
|
44
|
-
if (this.redactionMode === 'privacy-first') {
|
|
45
|
-
// Privacy-first: everything redacted by default, unredact specific fields
|
|
46
|
-
if (options.redactionStrategy.unredactFields) {
|
|
47
|
-
this.setFieldsToUnredact(options.redactionStrategy.unredactFields);
|
|
48
|
-
}
|
|
49
|
-
} else {
|
|
50
|
-
// Visibility-first: everything visible by default, redact specific fields
|
|
51
|
-
if (options.redactionStrategy.redactFields) {
|
|
52
|
-
this.setFieldsToRedact(options.redactionStrategy.redactFields);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Handle legacy redactFields (backward compatibility)
|
|
58
|
-
if (options?.legacyRedactFields) {
|
|
59
|
-
this.setFieldsToUnredact(options.legacyRedactFields);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Handle legacy userFields
|
|
63
|
-
if (options?.userFields) {
|
|
64
|
-
this.setFieldsToUnredact(options.userFields);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Set specific fields to be redacted (for visibility-first mode)
|
|
70
|
-
* @param fields Array of CSS selectors for fields to redact
|
|
71
|
-
*/
|
|
72
|
-
public setFieldsToRedact(fields: string[]): void {
|
|
73
|
-
this.redactedFields.clear();
|
|
74
|
-
|
|
75
|
-
// Always include password fields in redacted list
|
|
76
|
-
const passwordFields = [
|
|
77
|
-
'input[type="password"]',
|
|
78
|
-
'input[type="password" i]',
|
|
79
|
-
'[type="password"]',
|
|
80
|
-
'[type="password" i]'
|
|
81
|
-
];
|
|
82
|
-
|
|
83
|
-
// Add password fields and user-specified fields
|
|
84
|
-
[...passwordFields, ...fields].forEach(field => {
|
|
85
|
-
this.redactedFields.add(field);
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
if (this.redactedFields.size > 0) {
|
|
89
|
-
logDebug(`Redaction: Active for ${this.redactedFields.size} field(s):`, Array.from(this.redactedFields));
|
|
90
|
-
} else {
|
|
91
|
-
logDebug('Redaction: No fields to redact');
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
this.applyRedactionClasses();
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Set specific fields to be unredacted (everything else stays redacted by rrweb)
|
|
99
|
-
* @param fields Array of CSS selectors for fields to unredact
|
|
100
|
-
*/
|
|
101
|
-
public setFieldsToUnredact(fields: string[]): void {
|
|
102
|
-
this.unredactedFields.clear();
|
|
103
|
-
|
|
104
|
-
// Filter out password fields (they cannot be unredacted)
|
|
105
|
-
const validFields = fields.filter(field => {
|
|
106
|
-
const isPasswordField = this.isPasswordSelector(field);
|
|
107
|
-
if (isPasswordField) {
|
|
108
|
-
logWarn(`Cannot unredact password field: ${field} - Password fields are always protected`);
|
|
109
|
-
return false;
|
|
110
|
-
}
|
|
111
|
-
return true;
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
validFields.forEach(field => this.unredactedFields.add(field));
|
|
115
|
-
|
|
116
|
-
if (validFields.length > 0) {
|
|
117
|
-
logDebug(`Unredaction: Active for ${validFields.length} field(s):`, validFields);
|
|
118
|
-
} else {
|
|
119
|
-
logDebug('Unredaction: No valid fields to unredact');
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
this.applyUnredactionClasses();
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Remove specific fields from unredaction (they become redacted again)
|
|
127
|
-
* @param fields Array of CSS selectors for fields to redact
|
|
128
|
-
*/
|
|
129
|
-
public redactFields(fields: string[]): void {
|
|
130
|
-
fields.forEach(field => {
|
|
131
|
-
this.unredactedFields.delete(field);
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
if (this.unredactedFields.size > 0) {
|
|
135
|
-
logDebug(`Unredaction: Removed ${fields.length} field(s), ${this.unredactedFields.size} remaining:`, Array.from(this.unredactedFields));
|
|
136
|
-
} else {
|
|
137
|
-
logDebug('Unredaction: All fields redacted');
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
this.applyUnredactionClasses();
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Clear all unredacted fields (everything becomes redacted again)
|
|
145
|
-
*/
|
|
146
|
-
public clearUnredactedFields(): void {
|
|
147
|
-
this.unredactedFields.clear();
|
|
148
|
-
logDebug('Unredaction: All fields cleared, everything redacted');
|
|
149
|
-
|
|
150
|
-
this.removeUnredactionClasses();
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Check if any fields are currently unredacted
|
|
155
|
-
*/
|
|
156
|
-
public hasUnredactedFields(): boolean {
|
|
157
|
-
return this.unredactedFields.size > 0;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Get the current redaction mode
|
|
162
|
-
*/
|
|
163
|
-
public getRedactionMode(): 'privacy-first' | 'visibility-first' {
|
|
164
|
-
return this.redactionMode;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Get the currently unredacted fields
|
|
169
|
-
*/
|
|
170
|
-
public getUnredactedFields(): string[] {
|
|
171
|
-
return Array.from(this.unredactedFields);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Get CSS selectors for rrweb masking configuration
|
|
176
|
-
* Returns null if no fields are unredacted (everything stays redacted)
|
|
177
|
-
*/
|
|
178
|
-
public getMaskTextSelector(): string | null {
|
|
179
|
-
if (this.redactionMode === 'privacy-first') {
|
|
180
|
-
// Privacy-first: mask everything except unredacted fields
|
|
181
|
-
if (this.unredactedFields.size === 0) {
|
|
182
|
-
return null; // Everything stays redacted
|
|
183
|
-
}
|
|
184
|
-
return Array.from(this.unredactedFields).join(',');
|
|
185
|
-
} else {
|
|
186
|
-
// Visibility-first: mask only redacted fields
|
|
187
|
-
if (this.redactedFields.size === 0) {
|
|
188
|
-
return null; // Nothing to redact
|
|
189
|
-
}
|
|
190
|
-
return Array.from(this.redactedFields).join(',');
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Apply redaction classes to DOM elements (for visibility-first mode)
|
|
196
|
-
* Adds 'rr-mask' class to elements that should be redacted
|
|
197
|
-
*/
|
|
198
|
-
public applyRedactionClasses(): void {
|
|
199
|
-
if (this.redactedFields.size === 0) {
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// Add 'rr-mask' class to redacted elements
|
|
204
|
-
this.redactedFields.forEach(selector => {
|
|
205
|
-
try {
|
|
206
|
-
const elements = document.querySelectorAll(selector);
|
|
207
|
-
elements.forEach(element => {
|
|
208
|
-
element.classList.add('rr-mask');
|
|
209
|
-
});
|
|
210
|
-
logDebug(`Added rr-mask class to ${elements.length} element(s) for selector: ${selector}`);
|
|
211
|
-
} catch (e) {
|
|
212
|
-
logWarn(`Invalid selector: ${selector}`);
|
|
213
|
-
}
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* Apply unredaction classes to DOM elements
|
|
219
|
-
* Removes 'rr-mask' class from elements that should be unredacted
|
|
220
|
-
*/
|
|
221
|
-
public applyUnredactionClasses(): void {
|
|
222
|
-
if (this.unredactedFields.size === 0) {
|
|
223
|
-
return;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// Remove 'rr-mask' class from unredacted elements
|
|
227
|
-
this.unredactedFields.forEach(selector => {
|
|
228
|
-
try {
|
|
229
|
-
const elements = document.querySelectorAll(selector);
|
|
230
|
-
elements.forEach(element => {
|
|
231
|
-
element.classList.remove('rr-mask');
|
|
232
|
-
});
|
|
233
|
-
logDebug(`Removed rr-mask class from ${elements.length} element(s) for selector: ${selector}`);
|
|
234
|
-
} catch (e) {
|
|
235
|
-
logWarn(`Invalid selector: ${selector}`);
|
|
236
|
-
}
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* Remove all unredaction classes from DOM elements
|
|
242
|
-
*/
|
|
243
|
-
public removeUnredactionClasses(): void {
|
|
244
|
-
// Note: This doesn't add 'rr-mask' classes back - rrweb handles that automatically
|
|
245
|
-
logDebug('Unredaction classes removed');
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
* Check if a selector represents a password field
|
|
250
|
-
*/
|
|
251
|
-
private isPasswordSelector(selector: string): boolean {
|
|
252
|
-
const passwordPatterns = [
|
|
253
|
-
'input[type="password"]',
|
|
254
|
-
'input[type="password" i]',
|
|
255
|
-
'[type="password"]',
|
|
256
|
-
'[type="password" i]'
|
|
257
|
-
];
|
|
258
|
-
|
|
259
|
-
return passwordPatterns.some(pattern =>
|
|
260
|
-
selector.toLowerCase().includes(pattern.toLowerCase().replace(/[\[\]]/g, ''))
|
|
261
|
-
);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
/**
|
|
265
|
-
* Get the original value of an element (for debugging)
|
|
266
|
-
*/
|
|
267
|
-
public getOriginalValue(element: HTMLElement): string | undefined {
|
|
268
|
-
if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) {
|
|
269
|
-
return element.value;
|
|
270
|
-
}
|
|
271
|
-
return undefined;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
/**
|
|
275
|
-
* Check if an element is currently unredacted
|
|
276
|
-
*/
|
|
277
|
-
public isElementUnredacted(element: HTMLElement): boolean {
|
|
278
|
-
return this.shouldUnredactElement(element);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
/**
|
|
282
|
-
* Check if an element should be unredacted
|
|
283
|
-
*/
|
|
284
|
-
public shouldUnredactElement(element: HTMLElement): boolean {
|
|
285
|
-
if (this.redactionMode === 'privacy-first') {
|
|
286
|
-
// Privacy-first: check if element is in unredacted fields
|
|
287
|
-
if (this.unredactedFields.size === 0) {
|
|
288
|
-
return false; // Nothing unredacted
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// Check if any selector matches this element
|
|
292
|
-
for (const selector of this.unredactedFields) {
|
|
293
|
-
try {
|
|
294
|
-
if (element.matches(selector)) {
|
|
295
|
-
return true;
|
|
296
|
-
}
|
|
297
|
-
} catch (e) {
|
|
298
|
-
logWarn(`Invalid selector: ${selector}`);
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
return false;
|
|
302
|
-
} else {
|
|
303
|
-
// Visibility-first: check if element is NOT in redacted fields
|
|
304
|
-
if (this.redactedFields.size === 0) {
|
|
305
|
-
return true; // Nothing redacted, everything visible
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
// Check if any selector matches this element
|
|
309
|
-
for (const selector of this.redactedFields) {
|
|
310
|
-
try {
|
|
311
|
-
if (element.matches(selector)) {
|
|
312
|
-
return false; // Element is redacted
|
|
313
|
-
}
|
|
314
|
-
} catch (e) {
|
|
315
|
-
logWarn(`Invalid selector: ${selector}`);
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
return true; // Element is not redacted
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
// Export a default instance
|
|
324
|
-
export const redactionManager = new RedactionManager();
|
|
325
|
-
|
|
326
|
-
// Export the class for custom instances
|
|
327
|
-
export default RedactionManager;
|
package/src/remix/index.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { HumanBehaviorTracker } from '../index.js';
|
|
2
|
-
import type { LoaderFunctionArgs } from '@remix-run/node';
|
|
3
|
-
|
|
4
|
-
// Remix-specific loader helper
|
|
5
|
-
export function createHumanBehaviorLoader() {
|
|
6
|
-
return async ({ request }: LoaderFunctionArgs) => {
|
|
7
|
-
return {
|
|
8
|
-
ENV: {
|
|
9
|
-
HUMANBEHAVIOR_API_KEY: process.env.HUMANBEHAVIOR_API_KEY,
|
|
10
|
-
},
|
|
11
|
-
};
|
|
12
|
-
};
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// Re-export React components for convenience
|
|
16
|
-
export { HumanBehaviorProvider, useHumanBehavior } from '../react/index.js';
|
package/src/svelte/index.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { HumanBehaviorTracker } from '../index.js';
|
|
2
|
-
|
|
3
|
-
// Create a Svelte store-like interface for HumanBehavior
|
|
4
|
-
export const humanBehaviorStore = {
|
|
5
|
-
init: (apiKey: string, options?: {
|
|
6
|
-
ingestionUrl?: string;
|
|
7
|
-
logLevel?: 'none' | 'error' | 'warn' | 'info' | 'debug';
|
|
8
|
-
redactFields?: string[];
|
|
9
|
-
suppressConsoleErrors?: boolean;
|
|
10
|
-
recordCanvas?: boolean; // Enable canvas recording with protection
|
|
11
|
-
}) => {
|
|
12
|
-
return HumanBehaviorTracker.init(apiKey, options);
|
|
13
|
-
}
|
|
14
|
-
};
|