humanbehavior-js 0.4.15 → 0.4.17

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.
Files changed (89) hide show
  1. package/dist/cjs/wizard/index.cjs +6 -8
  2. package/dist/cjs/wizard/index.cjs.map +1 -1
  3. package/dist/cli/ai-auto-install.js +6 -8
  4. package/dist/cli/ai-auto-install.js.map +1 -1
  5. package/dist/esm/wizard/index.js +6 -8
  6. package/dist/esm/wizard/index.js.map +1 -1
  7. package/package/WIZARD_USAGE_GUIDE.md +381 -0
  8. package/package/canvas-recording-demo.html +143 -0
  9. package/package/clean-console-demo.html +39 -0
  10. package/package/dist/cjs/angular/index.cjs +14354 -0
  11. package/package/dist/cjs/angular/index.cjs.map +1 -0
  12. package/package/dist/cjs/index.cjs +14323 -0
  13. package/package/dist/cjs/index.cjs.map +1 -0
  14. package/package/dist/cjs/install-wizard.cjs +1530 -0
  15. package/package/dist/cjs/install-wizard.cjs.map +1 -0
  16. package/package/dist/cjs/react/index.cjs +14478 -0
  17. package/package/dist/cjs/react/index.cjs.map +1 -0
  18. package/package/dist/cjs/remix/index.cjs +14452 -0
  19. package/package/dist/cjs/remix/index.cjs.map +1 -0
  20. package/package/dist/cjs/svelte/index.cjs +14308 -0
  21. package/package/dist/cjs/svelte/index.cjs.map +1 -0
  22. package/package/dist/cjs/vue/index.cjs +14317 -0
  23. package/package/dist/cjs/vue/index.cjs.map +1 -0
  24. package/package/dist/cjs/wizard/index.cjs +3446 -0
  25. package/package/dist/cjs/wizard/index.cjs.map +1 -0
  26. package/package/dist/cli/ai-auto-install.cjs +57161 -0
  27. package/package/dist/cli/ai-auto-install.cjs.map +1 -0
  28. package/package/dist/cli/ai-auto-install.js +1969 -0
  29. package/package/dist/cli/ai-auto-install.js.map +1 -0
  30. package/package/dist/cli/auto-install.cjs +56352 -0
  31. package/package/dist/cli/auto-install.cjs.map +1 -0
  32. package/package/dist/cli/auto-install.js +1957 -0
  33. package/package/dist/cli/auto-install.js.map +1 -0
  34. package/package/dist/esm/angular/index.js +14350 -0
  35. package/package/dist/esm/angular/index.js.map +1 -0
  36. package/package/dist/esm/index.js +14309 -0
  37. package/package/dist/esm/index.js.map +1 -0
  38. package/package/dist/esm/install-wizard.js +1507 -0
  39. package/package/dist/esm/install-wizard.js.map +1 -0
  40. package/package/dist/esm/react/index.js +14472 -0
  41. package/package/dist/esm/react/index.js.map +1 -0
  42. package/package/dist/esm/remix/index.js +14448 -0
  43. package/package/dist/esm/remix/index.js.map +1 -0
  44. package/package/dist/esm/svelte/index.js +14306 -0
  45. package/package/dist/esm/svelte/index.js.map +1 -0
  46. package/package/dist/esm/vue/index.js +14315 -0
  47. package/package/dist/esm/vue/index.js.map +1 -0
  48. package/package/dist/esm/wizard/index.js +3415 -0
  49. package/package/dist/esm/wizard/index.js.map +1 -0
  50. package/package/dist/index.min.js +2 -0
  51. package/package/dist/index.min.js.map +1 -0
  52. package/package/dist/types/angular/index.d.ts +267 -0
  53. package/package/dist/types/index.d.ts +373 -0
  54. package/package/dist/types/install-wizard.d.ts +156 -0
  55. package/package/dist/types/react/index.d.ts +255 -0
  56. package/package/dist/types/remix/index.d.ts +246 -0
  57. package/package/dist/types/svelte/index.d.ts +232 -0
  58. package/package/dist/types/vue/index.d.ts +15 -0
  59. package/package/dist/types/wizard/index.d.ts +523 -0
  60. package/package/package.json +105 -0
  61. package/package/readme.md +281 -0
  62. package/package/rollup.config.js +422 -0
  63. package/package/simple-demo.html +26 -0
  64. package/package/simple-spa.html +838 -0
  65. package/package/src/angular/index.ts +79 -0
  66. package/package/src/api.ts +376 -0
  67. package/package/src/index.ts +28 -0
  68. package/package/src/react/AutoInstallWizard.tsx +557 -0
  69. package/package/src/react/browser.ts +8 -0
  70. package/package/src/react/index.tsx +308 -0
  71. package/package/src/redact.ts +521 -0
  72. package/package/src/remix/index.ts +16 -0
  73. package/package/src/svelte/index.ts +14 -0
  74. package/package/src/tracker.ts +1319 -0
  75. package/package/src/types/clack.d.ts +31 -0
  76. package/package/src/utils/logger.ts +144 -0
  77. package/package/src/vue/index.ts +29 -0
  78. package/package/src/wizard/README.md +114 -0
  79. package/package/src/wizard/ai/ai-install-wizard.ts +897 -0
  80. package/package/src/wizard/ai/manual-framework-wizard.ts +238 -0
  81. package/package/src/wizard/cli/ai-auto-install.ts +243 -0
  82. package/package/src/wizard/cli/auto-install.ts +224 -0
  83. package/package/src/wizard/core/install-wizard.ts +1744 -0
  84. package/package/src/wizard/index.ts +23 -0
  85. package/package/src/wizard/services/centralized-ai-service.ts +668 -0
  86. package/package/src/wizard/services/remote-ai-service.ts +240 -0
  87. package/package/tsconfig.json +24 -0
  88. package/package.json +1 -1
  89. package/src/wizard/cli/ai-auto-install.ts +4 -6
@@ -0,0 +1,521 @@
1
+ // Redaction functionality for sensitive input fields
2
+ // This module provides methods to configure rrweb's built-in masking
3
+ // Uses CSS selectors and classes for reliable redaction without event corruption
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 redact
14
+ }
15
+
16
+ export class RedactionManager {
17
+ private redactedText: string = '[REDACTED]';
18
+ private userSelectedFields: Set<string> = new Set(); // User-selected fields to redact
19
+ private excludeSelectors: string[] = [
20
+ '[data-no-redact="true"]',
21
+ '.human-behavior-no-redact'
22
+ ];
23
+
24
+ constructor(options?: RedactionOptions) {
25
+ if (options?.redactedText) {
26
+ this.redactedText = options.redactedText;
27
+ }
28
+ if (options?.excludeSelectors) {
29
+ this.excludeSelectors = [...this.excludeSelectors, ...options.excludeSelectors];
30
+ }
31
+ if (options?.userFields) {
32
+ this.setFieldsToRedact(options.userFields);
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Set specific fields to be redacted using CSS selectors
38
+ * These selectors are used to configure rrweb's built-in masking
39
+ * @param fields Array of CSS selectors for fields to redact
40
+ */
41
+ public setFieldsToRedact(fields: string[]): void {
42
+ this.userSelectedFields.clear();
43
+ fields.forEach(field => this.userSelectedFields.add(field));
44
+
45
+ if (fields.length > 0) {
46
+ logDebug(`Redaction: Active for ${fields.length} field(s):`, fields);
47
+
48
+ // Debug: Check if elements exist
49
+ fields.forEach(selector => {
50
+ const elements = document.querySelectorAll(selector);
51
+ logDebug(`Redaction: Found ${elements.length} element(s) for selector '${selector}'`);
52
+ elements.forEach((el, index) => {
53
+ logDebug(`Redaction: Element ${index} for '${selector}':`, el);
54
+ });
55
+ });
56
+ } else {
57
+ logDebug('Redaction: Disabled - no fields selected');
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Check if redaction is currently active (has fields selected)
63
+ */
64
+ public isActive(): boolean {
65
+ return this.userSelectedFields.size > 0;
66
+ }
67
+
68
+ /**
69
+ * Get the currently selected fields for redaction
70
+ */
71
+ public getSelectedFields(): string[] {
72
+ return Array.from(this.userSelectedFields);
73
+ }
74
+
75
+ /**
76
+ * Process an event and redact sensitive data if needed
77
+ * NOTE: This method is no longer used - events are handled directly by rrweb
78
+ * Kept for backward compatibility but not called in the current implementation
79
+ */
80
+ public processEvent(event: any): any {
81
+ // Only process if we have fields selected for redaction
82
+ if (this.userSelectedFields.size === 0) {
83
+ return event;
84
+ }
85
+
86
+ // Clone the event to avoid modifying the original
87
+ const processedEvent = JSON.parse(JSON.stringify(event));
88
+
89
+ // Handle different event types
90
+ if (processedEvent.type === 3) { // IncrementalSnapshot
91
+ if (processedEvent.data.source === 5) { // Input event
92
+ const shouldRedact = this.isFieldSelected(processedEvent.data);
93
+ if (shouldRedact) {
94
+ logDebug('Redaction: Processing input event for redaction');
95
+ this.redactInputEvent(processedEvent.data);
96
+ }
97
+ }
98
+ // Also check for other sources that might contain text changes
99
+ else if (processedEvent.data.source === 0) { // DOM mutations
100
+ this.redactDOMEvent(processedEvent.data);
101
+ }
102
+ // Handle other sources that might contain text
103
+ else if (processedEvent.data.source === 2) { // Mouse/Touch interaction
104
+ this.redactMouseEvent(processedEvent.data);
105
+ }
106
+ }
107
+ else if (processedEvent.type === 2) { // FullSnapshot
108
+ this.redactFullSnapshot(processedEvent.data);
109
+ }
110
+
111
+ return processedEvent;
112
+ }
113
+
114
+ /**
115
+ * Redact sensitive data in input events
116
+ */
117
+ private redactInputEvent(inputData: any): void {
118
+ // Check if this input event is from a field we want to redact
119
+ if (!this.isFieldSelected(inputData)) {
120
+ return;
121
+ }
122
+
123
+ logDebug('Redaction: Redacting input event with text:', inputData.text);
124
+
125
+ // Redact all text-related properties that could contain input data
126
+ const textProperties = ['text', 'value', 'content', 'data', 'input', 'textContent'];
127
+ textProperties.forEach(prop => {
128
+ if (inputData[prop] !== undefined && typeof inputData[prop] === 'string') {
129
+ inputData[prop] = this.redactedText;
130
+ logDebug(`Redaction: Redacted property '${prop}'`);
131
+ }
132
+ });
133
+
134
+ // Also check for any other string properties that might contain input data
135
+ Object.keys(inputData).forEach(key => {
136
+ if (typeof inputData[key] === 'string' && inputData[key].length > 0) {
137
+ inputData[key] = this.redactedText;
138
+ logDebug(`Redaction: Redacted additional property '${key}'`);
139
+ }
140
+ });
141
+
142
+ // Handle nested objects that might contain text data
143
+ if (inputData.attributes && typeof inputData.attributes === 'object') {
144
+ if (inputData.attributes.value && typeof inputData.attributes.value === 'string') {
145
+ inputData.attributes.value = this.redactedText;
146
+ logDebug('Redaction: Redacted nested value attribute');
147
+ }
148
+ }
149
+
150
+ logDebug('Redaction: Input event redaction complete');
151
+ }
152
+
153
+ /**
154
+ * Redact sensitive data in DOM mutation events
155
+ */
156
+ private redactDOMEvent(domData: any): void {
157
+ // Check for text changes in DOM mutations
158
+ if (domData.texts && Array.isArray(domData.texts)) {
159
+ domData.texts.forEach((textChange: any) => {
160
+ if (textChange.text && typeof textChange.text === 'string' &&
161
+ this.shouldRedactDOMChange(textChange)) {
162
+ textChange.text = this.redactedText;
163
+ }
164
+ });
165
+ }
166
+
167
+ // Also check for attribute changes that might contain input data
168
+ if (domData.attributes && Array.isArray(domData.attributes)) {
169
+ domData.attributes.forEach((attrChange: any) => {
170
+ if (attrChange.attributes && attrChange.attributes.value &&
171
+ typeof attrChange.attributes.value === 'string' &&
172
+ this.shouldRedactDOMChange(attrChange)) {
173
+ attrChange.attributes.value = this.redactedText;
174
+ }
175
+ });
176
+ }
177
+
178
+ // Check for any other properties that might contain text data
179
+ if (domData.adds && Array.isArray(domData.adds)) {
180
+ domData.adds.forEach((add: any) => {
181
+ if (add.node && add.node.textContent && typeof add.node.textContent === 'string' &&
182
+ this.shouldRedactDOMChange(add)) {
183
+ add.node.textContent = this.redactedText;
184
+ }
185
+ });
186
+ }
187
+ }
188
+
189
+ /**
190
+ * Check if a DOM change should be redacted based on its ID
191
+ */
192
+ private shouldRedactDOMChange(changeData: any): boolean {
193
+ if (!isBrowser) return false;
194
+
195
+ try {
196
+ // Check if this change has an ID that we can use to find the element
197
+ const elementId = changeData.id;
198
+ if (elementId !== undefined) {
199
+ // Try to find the element by data-rrweb-id attribute
200
+ let element = document.querySelector(`[data-rrweb-id="${elementId}"]`) as HTMLElement;
201
+
202
+ if (element) {
203
+ return this.shouldRedactElement(element);
204
+ }
205
+ }
206
+
207
+ // Also check for nodeId which is another way rrweb identifies elements
208
+ const nodeId = changeData.nodeId;
209
+ if (nodeId !== undefined) {
210
+ const element = document.querySelector(`[data-rrweb-id="${nodeId}"]`) as HTMLElement;
211
+ if (element) {
212
+ return this.shouldRedactElement(element);
213
+ }
214
+ }
215
+
216
+ return false;
217
+ } catch (e) {
218
+ logWarn('Error checking if DOM change should be redacted:', e);
219
+ return false;
220
+ }
221
+ }
222
+
223
+ /**
224
+ * Redact sensitive data in mouse/touch interaction events
225
+ */
226
+ private redactMouseEvent(mouseData: any): void {
227
+ // Mouse events typically don't contain text data, but check for any text properties
228
+ if (mouseData.text && typeof mouseData.text === 'string' &&
229
+ this.isFieldSelected(mouseData)) {
230
+ mouseData.text = this.redactedText;
231
+ }
232
+ }
233
+
234
+ /**
235
+ * Redact sensitive data in full snapshot events
236
+ */
237
+ private redactFullSnapshot(snapshotData: any): void {
238
+ if (snapshotData.node && snapshotData.node.type === 2) { // Element node
239
+ this.redactNode(snapshotData.node);
240
+ }
241
+ }
242
+
243
+ /**
244
+ * Recursively redact sensitive data in DOM nodes
245
+ */
246
+ private redactNode(node: any): void {
247
+ if (!node) return;
248
+
249
+ // Check if this node should be redacted
250
+ if (node.type === 2 && node.tagName &&
251
+ (node.tagName.toLowerCase() === 'input' || node.tagName.toLowerCase() === 'textarea')) {
252
+
253
+ // Check if this input/textarea should be redacted
254
+ if (this.shouldRedactNode(node)) {
255
+ // Redact value attribute
256
+ if (node.attributes && node.attributes.value) {
257
+ node.attributes.value = this.redactedText;
258
+ }
259
+ // Redact text content
260
+ if (node.textContent) {
261
+ node.textContent = this.redactedText;
262
+ }
263
+ }
264
+ }
265
+
266
+ // Recursively process child nodes
267
+ if (node.childNodes && Array.isArray(node.childNodes)) {
268
+ node.childNodes.forEach((childNode: any) => {
269
+ this.redactNode(childNode);
270
+ });
271
+ }
272
+ }
273
+
274
+ /**
275
+ * Check if a node should be redacted based on its attributes
276
+ */
277
+ private shouldRedactNode(node: any): boolean {
278
+ if (!node.attributes) return false;
279
+
280
+ // Check if any of our selectors would match this node
281
+ for (const selector of this.userSelectedFields) {
282
+ if (this.selectorMatchesNode(selector, node)) {
283
+ return true;
284
+ }
285
+ }
286
+
287
+ return false;
288
+ }
289
+
290
+ /**
291
+ * Check if a CSS selector would match a node based on its attributes
292
+ */
293
+ private selectorMatchesNode(selector: string, node: any): boolean {
294
+ if (!node.attributes) return false;
295
+
296
+ // Create a temporary element to test the selector
297
+ try {
298
+ const tempElement = document.createElement(node.tagName || 'div');
299
+
300
+ // Copy attributes from the node to the temp element
301
+ if (node.attributes) {
302
+ Object.keys(node.attributes).forEach(key => {
303
+ tempElement.setAttribute(key, node.attributes[key]);
304
+ });
305
+ }
306
+
307
+ // Test if the selector matches this element
308
+ return tempElement.matches(selector);
309
+ } catch (e) {
310
+ // If matches() is not supported or fails, fall back to basic attribute checking
311
+ return this.basicSelectorMatch(selector, node);
312
+ }
313
+ }
314
+
315
+ /**
316
+ * Basic selector matching for environments where matches() is not available
317
+ */
318
+ private basicSelectorMatch(selector: string, node: any): boolean {
319
+ if (!node.attributes) return false;
320
+
321
+ // Handle simple selectors like 'input[type="password"]'
322
+ if (selector.includes('input[type=')) {
323
+ const typeMatch = selector.match(/input\[type="([^"]+)"\]/);
324
+ if (typeMatch && node.tagName === 'input' && node.attributes.type === typeMatch[1]) {
325
+ return true;
326
+ }
327
+ }
328
+
329
+ // Handle ID selectors like '#email'
330
+ if (selector.startsWith('#')) {
331
+ const id = selector.substring(1);
332
+ return node.attributes.id === id;
333
+ }
334
+
335
+ // Handle class selectors like '.sensitive-field'
336
+ if (selector.startsWith('.')) {
337
+ const className = selector.substring(1);
338
+ return node.attributes.class && node.attributes.class.includes(className);
339
+ }
340
+
341
+ // Handle tag selectors like 'input'
342
+ if (!selector.includes('[') && !selector.includes('.')) {
343
+ return node.tagName && node.tagName.toLowerCase() === selector.toLowerCase();
344
+ }
345
+
346
+ return false;
347
+ }
348
+
349
+ /**
350
+ * Check if an event is from a field that should be redacted
351
+ */
352
+ private isFieldSelected(eventData: any): boolean {
353
+ if (!isBrowser) return false;
354
+
355
+ try {
356
+ // For input events (source 5), we need to determine if this is a sensitive field
357
+ if (eventData.source === 5) { // Input event
358
+ const elementId = eventData.id;
359
+ if (elementId !== undefined) {
360
+ // Try to find the element by data-rrweb-id attribute
361
+ let element = document.querySelector(`[data-rrweb-id="${elementId}"]`) as HTMLElement;
362
+
363
+ if (element) {
364
+ return this.shouldRedactElement(element);
365
+ }
366
+
367
+ // Fallback: Try to find by nodeId if available
368
+ if (eventData.nodeId !== undefined) {
369
+ element = document.querySelector(`[data-rrweb-id="${eventData.nodeId}"]`) as HTMLElement;
370
+ if (element) {
371
+ return this.shouldRedactElement(element);
372
+ }
373
+ }
374
+
375
+ // More aggressive approach: Check all elements that match our selectors
376
+ // and see if any of them are currently focused or have the same ID
377
+ for (const selector of this.userSelectedFields) {
378
+ const matchingElements = document.querySelectorAll(selector);
379
+ if (matchingElements.length > 0) {
380
+ // Check if any of these elements are currently focused
381
+ for (const el of matchingElements) {
382
+ if (el === document.activeElement) {
383
+ logDebug('Redaction: Found focused element matching selector:', selector);
384
+ return true;
385
+ }
386
+ }
387
+ }
388
+ }
389
+
390
+ // If we still can't find it, try a more direct approach
391
+ // Look for any input element that might be the active one
392
+ const activeElement = document.activeElement;
393
+ if (activeElement && this.shouldRedactElement(activeElement as HTMLElement)) {
394
+ logDebug('Redaction: Active element should be redacted');
395
+ return true;
396
+ }
397
+
398
+ return false;
399
+ }
400
+ }
401
+
402
+ // For other event types, try to find the element
403
+ const elementId = eventData.id;
404
+ if (elementId !== undefined) {
405
+ // First try to find by data-rrweb-id attribute
406
+ let element = document.querySelector(`[data-rrweb-id="${elementId}"]`) as HTMLElement;
407
+
408
+ if (element) {
409
+ return this.shouldRedactElement(element);
410
+ }
411
+ }
412
+
413
+ // Also check for nodeId which is another way rrweb identifies elements
414
+ const nodeId = eventData.nodeId;
415
+ if (nodeId !== undefined) {
416
+ const element = document.querySelector(`[data-rrweb-id="${nodeId}"]`) as HTMLElement;
417
+ if (element) {
418
+ return this.shouldRedactElement(element);
419
+ }
420
+ }
421
+
422
+ // For DOM mutations, check if the target element should be redacted
423
+ if (eventData.target && eventData.target.id) {
424
+ const element = document.querySelector(`[data-rrweb-id="${eventData.target.id}"]`) as HTMLElement;
425
+ if (element) {
426
+ return this.shouldRedactElement(element);
427
+ }
428
+ }
429
+
430
+ return false;
431
+ } catch (e) {
432
+ logWarn('Error checking if field should be redacted:', e);
433
+ return false;
434
+ }
435
+ }
436
+
437
+ /**
438
+ * Get CSS selectors for rrweb masking configuration
439
+ * Used to configure rrweb's maskTextSelector option
440
+ */
441
+ public getMaskTextSelector(): string | null {
442
+ if (this.userSelectedFields.size === 0) {
443
+ return null;
444
+ }
445
+ return Array.from(this.userSelectedFields).join(',');
446
+ }
447
+
448
+ /**
449
+ * Check if an element should be redacted (for rrweb maskTextFn/maskInputFn)
450
+ */
451
+ public shouldRedactElement(element: HTMLElement): boolean {
452
+ if (this.userSelectedFields.size === 0) {
453
+ return false;
454
+ }
455
+
456
+ // Check if any selector matches this element
457
+ for (const selector of this.userSelectedFields) {
458
+ try {
459
+ if (element.matches(selector)) {
460
+ return true;
461
+ }
462
+ } catch (e) {
463
+ // Invalid selector, skip
464
+ logWarn(`Invalid selector: ${selector}`);
465
+ }
466
+ }
467
+ return false;
468
+ }
469
+
470
+ /**
471
+ * Apply rrweb masking classes to DOM elements
472
+ * Adds 'rr-mask' class to elements that should be redacted
473
+ * This enables rrweb's built-in masking functionality
474
+ */
475
+ public applyRedactionClasses(): void {
476
+ if (this.userSelectedFields.size === 0) {
477
+ return;
478
+ }
479
+
480
+ // Remove existing redaction classes
481
+ document.querySelectorAll('.rr-mask').forEach(element => {
482
+ element.classList.remove('rr-mask');
483
+ });
484
+
485
+ // Add redaction classes to matching elements
486
+ this.userSelectedFields.forEach(selector => {
487
+ try {
488
+ const elements = document.querySelectorAll(selector);
489
+ elements.forEach(element => {
490
+ element.classList.add('rr-mask');
491
+ });
492
+ logDebug(`Applied rr-mask class to ${elements.length} element(s) for selector: ${selector}`);
493
+ } catch (e) {
494
+ logWarn(`Invalid selector: ${selector}`);
495
+ }
496
+ });
497
+ }
498
+
499
+ /**
500
+ * Get the original value of a redacted element (for debugging)
501
+ */
502
+ public getOriginalValue(element: HTMLElement): string | undefined {
503
+ if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) {
504
+ return element.value;
505
+ }
506
+ return undefined;
507
+ }
508
+
509
+ /**
510
+ * Check if an element is currently being redacted
511
+ */
512
+ public isElementRedacted(element: HTMLElement): boolean {
513
+ return this.shouldRedactElement(element);
514
+ }
515
+ }
516
+
517
+ // Export a default instance
518
+ export const redactionManager = new RedactionManager();
519
+
520
+ // Export the class for custom instances
521
+ export default RedactionManager;
@@ -0,0 +1,16 @@
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';
@@ -0,0 +1,14 @@
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 PostHog-style protection
11
+ }) => {
12
+ return HumanBehaviorTracker.init(apiKey, options);
13
+ }
14
+ };