@tsdevstack/react-bot-detection 0.1.4

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.
@@ -0,0 +1,140 @@
1
+ "use client";
2
+ import { useCallback, useEffect, useRef, useState } from "react";
3
+ function useBotDetection() {
4
+ const [botScore, setBotScore] = useState(0);
5
+ const [detectionReasons, setDetectionReasons] = useState([]);
6
+ const [formStartTime] = useState(Date.now());
7
+ const mouseMovements = useRef([]);
8
+ const [hasMouseMovement, setHasMouseMovement] = useState(false);
9
+ const typingPatterns = useRef([]);
10
+ const lastKeyTime = useRef(0);
11
+ const [focusEvents, setFocusEvents] = useState(0);
12
+ const trackMouseMovement = useCallback((e)=>{
13
+ const movement = {
14
+ x: e.clientX,
15
+ y: e.clientY,
16
+ timestamp: Date.now()
17
+ };
18
+ mouseMovements.current.push(movement);
19
+ setHasMouseMovement(true);
20
+ if (mouseMovements.current.length > 50) mouseMovements.current.shift();
21
+ }, []);
22
+ const trackKeyPress = useCallback((e)=>{
23
+ const now = Date.now();
24
+ const timeSinceLastKey = now - lastKeyTime.current;
25
+ const pattern = {
26
+ key: e.key,
27
+ timestamp: now,
28
+ timeSinceLastKey
29
+ };
30
+ typingPatterns.current.push(pattern);
31
+ lastKeyTime.current = now;
32
+ if (typingPatterns.current.length > 100) typingPatterns.current.shift();
33
+ }, []);
34
+ useEffect(()=>{
35
+ document.addEventListener('mousemove', trackMouseMovement);
36
+ document.addEventListener('keydown', trackKeyPress);
37
+ return ()=>{
38
+ document.removeEventListener('mousemove', trackMouseMovement);
39
+ document.removeEventListener('keydown', trackKeyPress);
40
+ };
41
+ }, [
42
+ trackMouseMovement,
43
+ trackKeyPress
44
+ ]);
45
+ const analyzeBehavior = useCallback(()=>{
46
+ let score = 0;
47
+ const reasons = [];
48
+ if (!hasMouseMovement || mouseMovements.current.length < 5) {
49
+ score += 30;
50
+ reasons.push('No natural mouse movement detected');
51
+ } else {
52
+ const movements = mouseMovements.current.slice(-20);
53
+ const variations = movements.map((m, i)=>{
54
+ if (0 === i) return 0;
55
+ const prev = movements[i - 1];
56
+ return Math.abs(m.x - prev.x) + Math.abs(m.y - prev.y);
57
+ });
58
+ const avgVariation = variations.reduce((a, b)=>a + b, 0) / variations.length;
59
+ if (avgVariation < 5) {
60
+ score += 20;
61
+ reasons.push('Unnatural mouse movement patterns');
62
+ }
63
+ }
64
+ if (typingPatterns.current.length > 10) {
65
+ const intervals = typingPatterns.current.slice(-20).map((p)=>p.timeSinceLastKey).filter((t)=>t > 0 && t < 1000);
66
+ if (intervals.length > 5) {
67
+ const avgInterval = intervals.reduce((a, b)=>a + b, 0) / intervals.length;
68
+ const variance = intervals.reduce((acc, interval)=>acc + Math.pow(interval - avgInterval, 2), 0) / intervals.length;
69
+ if (variance < 100) {
70
+ score += 15;
71
+ reasons.push('Unnaturally consistent typing pattern');
72
+ }
73
+ if (avgInterval < 50) {
74
+ score += 20;
75
+ reasons.push('Superhuman typing speed detected');
76
+ }
77
+ }
78
+ }
79
+ const currentTime = Date.now();
80
+ const totalTime = currentTime - formStartTime;
81
+ if (totalTime < 2000) {
82
+ score += 40;
83
+ reasons.push('Form filled too quickly');
84
+ }
85
+ if (focusEvents < 2) {
86
+ score += 15;
87
+ reasons.push('Insufficient focus interactions');
88
+ }
89
+ if ("u" > typeof navigator && true === navigator.webdriver) {
90
+ score += 25;
91
+ reasons.push('Automated browser detected');
92
+ }
93
+ if ("u" > typeof window && (0 === window.outerWidth || 0 === window.outerHeight)) {
94
+ score += 35;
95
+ reasons.push('Headless browser indicators');
96
+ }
97
+ return {
98
+ score,
99
+ reasons,
100
+ isBot: score >= 50,
101
+ stats: {
102
+ mouseMovements: mouseMovements.current.length,
103
+ typingEvents: typingPatterns.current.length,
104
+ focusEvents,
105
+ timeSpent: Date.now() - formStartTime
106
+ },
107
+ honeypotTriggered: false
108
+ };
109
+ }, [
110
+ hasMouseMovement,
111
+ focusEvents,
112
+ formStartTime
113
+ ]);
114
+ const handleFieldFocus = useCallback(()=>{
115
+ setFocusEvents((prev)=>prev + 1);
116
+ }, []);
117
+ const handleFormSubmit = useCallback(()=>{
118
+ const result = analyzeBehavior();
119
+ setBotScore(result.score);
120
+ setDetectionReasons(result.reasons);
121
+ return result;
122
+ }, [
123
+ analyzeBehavior
124
+ ]);
125
+ return {
126
+ botScore,
127
+ detectionReasons,
128
+ isBot: botScore >= 50,
129
+ analyzeBehavior,
130
+ handleFieldFocus,
131
+ handleFormSubmit,
132
+ stats: {
133
+ mouseMovements: mouseMovements.current.length,
134
+ typingEvents: typingPatterns.current.length,
135
+ focusEvents,
136
+ timeSpent: Date.now() - formStartTime
137
+ }
138
+ };
139
+ }
140
+ export { useBotDetection };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,384 @@
1
+ "use client";
2
+ import { afterEach, beforeEach, describe, expect, it, rs } from "@rstest/core";
3
+ import { act, renderHook } from "@testing-library/react";
4
+ import { useBotDetection } from "./use-bot-detection.js";
5
+ describe('useBotDetection', ()=>{
6
+ beforeEach(()=>{
7
+ rs.useFakeTimers();
8
+ });
9
+ afterEach(()=>{
10
+ rs.useRealTimers();
11
+ });
12
+ describe('initial state', ()=>{
13
+ it('should return initial bot score of 0', ()=>{
14
+ const { result } = renderHook(()=>useBotDetection());
15
+ expect(result.current.botScore).toBe(0);
16
+ });
17
+ it('should return empty detection reasons', ()=>{
18
+ const { result } = renderHook(()=>useBotDetection());
19
+ expect(result.current.detectionReasons).toEqual([]);
20
+ });
21
+ it('should not flag as bot initially', ()=>{
22
+ const { result } = renderHook(()=>useBotDetection());
23
+ expect(result.current.isBot).toBe(false);
24
+ });
25
+ it('should return initial stats', ()=>{
26
+ const { result } = renderHook(()=>useBotDetection());
27
+ expect(result.current.stats.focusEvents).toBe(0);
28
+ });
29
+ });
30
+ describe('handleFieldFocus', ()=>{
31
+ it('should increment focus events', ()=>{
32
+ const { result } = renderHook(()=>useBotDetection());
33
+ act(()=>{
34
+ result.current.handleFieldFocus();
35
+ });
36
+ expect(result.current.stats.focusEvents).toBe(1);
37
+ });
38
+ it('should accumulate focus events', ()=>{
39
+ const { result } = renderHook(()=>useBotDetection());
40
+ act(()=>{
41
+ result.current.handleFieldFocus();
42
+ result.current.handleFieldFocus();
43
+ result.current.handleFieldFocus();
44
+ });
45
+ expect(result.current.stats.focusEvents).toBe(3);
46
+ });
47
+ });
48
+ describe('analyzeBehavior', ()=>{
49
+ it('should detect fast form submission', ()=>{
50
+ const { result } = renderHook(()=>useBotDetection());
51
+ const analysisResult = result.current.analyzeBehavior();
52
+ expect(analysisResult.reasons).toContain('Form filled too quickly');
53
+ expect(analysisResult.score).toBeGreaterThanOrEqual(40);
54
+ });
55
+ it('should detect insufficient focus interactions', ()=>{
56
+ const { result } = renderHook(()=>useBotDetection());
57
+ rs.advanceTimersByTime(3000);
58
+ const analysisResult = result.current.analyzeBehavior();
59
+ expect(analysisResult.reasons).toContain('Insufficient focus interactions');
60
+ });
61
+ it('should detect no mouse movement', ()=>{
62
+ const { result } = renderHook(()=>useBotDetection());
63
+ rs.advanceTimersByTime(3000);
64
+ const analysisResult = result.current.analyzeBehavior();
65
+ expect(analysisResult.reasons).toContain('No natural mouse movement detected');
66
+ });
67
+ it('should return isBot true when score >= 50', ()=>{
68
+ const { result } = renderHook(()=>useBotDetection());
69
+ const analysisResult = result.current.analyzeBehavior();
70
+ expect(analysisResult.isBot).toBe(true);
71
+ expect(analysisResult.score).toBeGreaterThanOrEqual(50);
72
+ });
73
+ });
74
+ describe('handleFormSubmit', ()=>{
75
+ it('should update botScore state', ()=>{
76
+ const { result } = renderHook(()=>useBotDetection());
77
+ act(()=>{
78
+ result.current.handleFormSubmit();
79
+ });
80
+ expect(result.current.botScore).toBeGreaterThan(0);
81
+ });
82
+ it('should update detectionReasons state', ()=>{
83
+ const { result } = renderHook(()=>useBotDetection());
84
+ act(()=>{
85
+ result.current.handleFormSubmit();
86
+ });
87
+ expect(result.current.detectionReasons.length).toBeGreaterThan(0);
88
+ });
89
+ it('should return analysis result', ()=>{
90
+ const { result } = renderHook(()=>useBotDetection());
91
+ let submitResult;
92
+ act(()=>{
93
+ submitResult = result.current.handleFormSubmit();
94
+ });
95
+ expect(submitResult).toHaveProperty('score');
96
+ expect(submitResult).toHaveProperty('reasons');
97
+ expect(submitResult).toHaveProperty('isBot');
98
+ expect(submitResult).toHaveProperty('stats');
99
+ });
100
+ });
101
+ describe('event listeners', ()=>{
102
+ it('should set up mousemove listener', ()=>{
103
+ const addEventListenerSpy = rs.spyOn(document, 'addEventListener');
104
+ renderHook(()=>useBotDetection());
105
+ expect(addEventListenerSpy).toHaveBeenCalledWith('mousemove', expect.any(Function));
106
+ addEventListenerSpy.mockRestore();
107
+ });
108
+ it('should set up keydown listener', ()=>{
109
+ const addEventListenerSpy = rs.spyOn(document, 'addEventListener');
110
+ renderHook(()=>useBotDetection());
111
+ expect(addEventListenerSpy).toHaveBeenCalledWith('keydown', expect.any(Function));
112
+ addEventListenerSpy.mockRestore();
113
+ });
114
+ it('should clean up event listeners on unmount', ()=>{
115
+ const removeEventListenerSpy = rs.spyOn(document, 'removeEventListener');
116
+ const { unmount } = renderHook(()=>useBotDetection());
117
+ unmount();
118
+ expect(removeEventListenerSpy).toHaveBeenCalledWith('mousemove', expect.any(Function));
119
+ expect(removeEventListenerSpy).toHaveBeenCalledWith('keydown', expect.any(Function));
120
+ removeEventListenerSpy.mockRestore();
121
+ });
122
+ });
123
+ describe('mouse movement tracking', ()=>{
124
+ it('should track mouse movements and update stats', ()=>{
125
+ const { result } = renderHook(()=>useBotDetection());
126
+ for(let i = 0; i < 10; i++)act(()=>{
127
+ document.dispatchEvent(new MouseEvent('mousemove', {
128
+ clientX: 50 * i,
129
+ clientY: 30 * i
130
+ }));
131
+ });
132
+ const analysisResult = result.current.analyzeBehavior();
133
+ expect(analysisResult.stats.mouseMovements).toBe(10);
134
+ });
135
+ it('should not flag bot when sufficient natural mouse movement', ()=>{
136
+ const { result } = renderHook(()=>useBotDetection());
137
+ for(let i = 0; i < 10; i++)act(()=>{
138
+ document.dispatchEvent(new MouseEvent('mousemove', {
139
+ clientX: 50 * i + 20 * Math.random(),
140
+ clientY: 30 * i + 20 * Math.random()
141
+ }));
142
+ });
143
+ rs.advanceTimersByTime(3000);
144
+ act(()=>{
145
+ result.current.handleFieldFocus();
146
+ result.current.handleFieldFocus();
147
+ });
148
+ const analysisResult = result.current.analyzeBehavior();
149
+ expect(analysisResult.reasons).not.toContain('No natural mouse movement detected');
150
+ });
151
+ it('should detect unnatural mouse patterns with low variation', ()=>{
152
+ const { result } = renderHook(()=>useBotDetection());
153
+ for(let i = 0; i < 10; i++)act(()=>{
154
+ document.dispatchEvent(new MouseEvent('mousemove', {
155
+ clientX: 100 + i,
156
+ clientY: 100 + i
157
+ }));
158
+ });
159
+ rs.advanceTimersByTime(3000);
160
+ const analysisResult = result.current.analyzeBehavior();
161
+ expect(analysisResult.reasons).toContain('Unnatural mouse movement patterns');
162
+ });
163
+ it('should cap mouse movements at 50', ()=>{
164
+ const { result } = renderHook(()=>useBotDetection());
165
+ for(let i = 0; i < 60; i++)act(()=>{
166
+ document.dispatchEvent(new MouseEvent('mousemove', {
167
+ clientX: 10 * i,
168
+ clientY: 10 * i
169
+ }));
170
+ });
171
+ const analysisResult = result.current.analyzeBehavior();
172
+ expect(analysisResult.stats.mouseMovements).toBe(50);
173
+ });
174
+ });
175
+ describe('typing pattern tracking', ()=>{
176
+ it('should track keystrokes and update stats', ()=>{
177
+ const { result } = renderHook(()=>useBotDetection());
178
+ for(let i = 0; i < 15; i++){
179
+ act(()=>{
180
+ document.dispatchEvent(new KeyboardEvent('keydown', {
181
+ key: 'a'
182
+ }));
183
+ });
184
+ rs.advanceTimersByTime(100);
185
+ }
186
+ const analysisResult = result.current.analyzeBehavior();
187
+ expect(analysisResult.stats.typingEvents).toBe(15);
188
+ });
189
+ it('should detect unnaturally consistent typing', ()=>{
190
+ const { result } = renderHook(()=>useBotDetection());
191
+ rs.advanceTimersByTime(3000);
192
+ for(let i = 0; i < 20; i++){
193
+ act(()=>{
194
+ document.dispatchEvent(new KeyboardEvent('keydown', {
195
+ key: 'a'
196
+ }));
197
+ });
198
+ rs.advanceTimersByTime(50);
199
+ }
200
+ for(let i = 0; i < 10; i++)act(()=>{
201
+ document.dispatchEvent(new MouseEvent('mousemove', {
202
+ clientX: 50 * i + 30 * Math.random(),
203
+ clientY: 30 * i + 30 * Math.random()
204
+ }));
205
+ });
206
+ act(()=>{
207
+ result.current.handleFieldFocus();
208
+ result.current.handleFieldFocus();
209
+ });
210
+ const analysisResult = result.current.analyzeBehavior();
211
+ expect(analysisResult.reasons).toContain('Unnaturally consistent typing pattern');
212
+ });
213
+ it('should detect superhuman typing speed', ()=>{
214
+ const { result } = renderHook(()=>useBotDetection());
215
+ rs.advanceTimersByTime(3000);
216
+ for(let i = 0; i < 20; i++){
217
+ act(()=>{
218
+ document.dispatchEvent(new KeyboardEvent('keydown', {
219
+ key: 'a'
220
+ }));
221
+ });
222
+ rs.advanceTimersByTime(30);
223
+ }
224
+ for(let i = 0; i < 10; i++)act(()=>{
225
+ document.dispatchEvent(new MouseEvent('mousemove', {
226
+ clientX: 50 * i + 30 * Math.random(),
227
+ clientY: 30 * i + 30 * Math.random()
228
+ }));
229
+ });
230
+ act(()=>{
231
+ result.current.handleFieldFocus();
232
+ result.current.handleFieldFocus();
233
+ });
234
+ const analysisResult = result.current.analyzeBehavior();
235
+ expect(analysisResult.reasons).toContain('Superhuman typing speed detected');
236
+ });
237
+ it('should cap typing patterns at 100', ()=>{
238
+ const { result } = renderHook(()=>useBotDetection());
239
+ for(let i = 0; i < 110; i++){
240
+ act(()=>{
241
+ document.dispatchEvent(new KeyboardEvent('keydown', {
242
+ key: 'a'
243
+ }));
244
+ });
245
+ rs.advanceTimersByTime(100);
246
+ }
247
+ const analysisResult = result.current.analyzeBehavior();
248
+ expect(analysisResult.stats.typingEvents).toBe(100);
249
+ });
250
+ it('should not flag typing when natural variation exists', ()=>{
251
+ const { result } = renderHook(()=>useBotDetection());
252
+ rs.advanceTimersByTime(3000);
253
+ const intervals = [
254
+ 120,
255
+ 80,
256
+ 150,
257
+ 90,
258
+ 200,
259
+ 100,
260
+ 180,
261
+ 70,
262
+ 130,
263
+ 110,
264
+ 160,
265
+ 95
266
+ ];
267
+ for (const interval of intervals){
268
+ act(()=>{
269
+ document.dispatchEvent(new KeyboardEvent('keydown', {
270
+ key: 'a'
271
+ }));
272
+ });
273
+ rs.advanceTimersByTime(interval);
274
+ }
275
+ for(let i = 0; i < 10; i++)act(()=>{
276
+ document.dispatchEvent(new MouseEvent('mousemove', {
277
+ clientX: 50 * i + 30 * Math.random(),
278
+ clientY: 30 * i + 30 * Math.random()
279
+ }));
280
+ });
281
+ act(()=>{
282
+ result.current.handleFieldFocus();
283
+ result.current.handleFieldFocus();
284
+ });
285
+ const analysisResult = result.current.analyzeBehavior();
286
+ expect(analysisResult.reasons).not.toContain('Unnaturally consistent typing pattern');
287
+ expect(analysisResult.reasons).not.toContain('Superhuman typing speed detected');
288
+ });
289
+ });
290
+ describe('browser detection', ()=>{
291
+ it('should detect webdriver automation', ()=>{
292
+ const { result } = renderHook(()=>useBotDetection());
293
+ const originalNavigator = globalThis.navigator;
294
+ Object.defineProperty(globalThis, 'navigator', {
295
+ value: {
296
+ ...originalNavigator,
297
+ webdriver: true
298
+ },
299
+ writable: true,
300
+ configurable: true
301
+ });
302
+ rs.advanceTimersByTime(3000);
303
+ const analysisResult = result.current.analyzeBehavior();
304
+ expect(analysisResult.reasons).toContain('Automated browser detected');
305
+ expect(analysisResult.score).toBeGreaterThanOrEqual(25);
306
+ Object.defineProperty(globalThis, 'navigator', {
307
+ value: originalNavigator,
308
+ writable: true,
309
+ configurable: true
310
+ });
311
+ });
312
+ it('should detect headless browser indicators', ()=>{
313
+ const { result } = renderHook(()=>useBotDetection());
314
+ const originalOuterWidth = window.outerWidth;
315
+ const originalOuterHeight = window.outerHeight;
316
+ Object.defineProperty(window, 'outerWidth', {
317
+ value: 0,
318
+ writable: true,
319
+ configurable: true
320
+ });
321
+ Object.defineProperty(window, 'outerHeight', {
322
+ value: 0,
323
+ writable: true,
324
+ configurable: true
325
+ });
326
+ rs.advanceTimersByTime(3000);
327
+ const analysisResult = result.current.analyzeBehavior();
328
+ expect(analysisResult.reasons).toContain('Headless browser indicators');
329
+ expect(analysisResult.score).toBeGreaterThanOrEqual(35);
330
+ Object.defineProperty(window, 'outerWidth', {
331
+ value: originalOuterWidth,
332
+ writable: true,
333
+ configurable: true
334
+ });
335
+ Object.defineProperty(window, 'outerHeight', {
336
+ value: originalOuterHeight,
337
+ writable: true,
338
+ configurable: true
339
+ });
340
+ });
341
+ });
342
+ describe('legitimate user behavior', ()=>{
343
+ it('should pass legitimate user with all natural behaviors', ()=>{
344
+ const { result } = renderHook(()=>useBotDetection());
345
+ rs.advanceTimersByTime(5000);
346
+ for(let i = 0; i < 15; i++)act(()=>{
347
+ document.dispatchEvent(new MouseEvent('mousemove', {
348
+ clientX: 40 * i + 50 * Math.random(),
349
+ clientY: 25 * i + 40 * Math.random()
350
+ }));
351
+ });
352
+ const intervals = [
353
+ 120,
354
+ 80,
355
+ 150,
356
+ 90,
357
+ 200,
358
+ 100,
359
+ 180,
360
+ 70,
361
+ 130,
362
+ 110,
363
+ 160,
364
+ 95
365
+ ];
366
+ for (const interval of intervals){
367
+ act(()=>{
368
+ document.dispatchEvent(new KeyboardEvent('keydown', {
369
+ key: 'a'
370
+ }));
371
+ });
372
+ rs.advanceTimersByTime(interval);
373
+ }
374
+ act(()=>{
375
+ result.current.handleFieldFocus();
376
+ result.current.handleFieldFocus();
377
+ result.current.handleFieldFocus();
378
+ });
379
+ const analysisResult = result.current.analyzeBehavior();
380
+ expect(analysisResult.isBot).toBe(false);
381
+ expect(analysisResult.score).toBeLessThan(50);
382
+ });
383
+ });
384
+ });
@@ -0,0 +1,32 @@
1
+ import React from 'react';
2
+ export interface UseHoneypotReturn {
3
+ /** Whether a bot has been detected via honeypot */
4
+ isBotDetected: boolean;
5
+ /** Score contribution from honeypot (50 if triggered) */
6
+ botScore: number;
7
+ /** Callback to mark bot as detected */
8
+ handleBotDetected: () => void;
9
+ /** Reset detection state */
10
+ resetDetection: () => void;
11
+ /** Pre-configured Honeypot component */
12
+ HoneypotComponent: () => React.ReactElement;
13
+ }
14
+ /**
15
+ * Hook for honeypot-based bot detection.
16
+ *
17
+ * Provides a hidden form field that bots typically fill out,
18
+ * allowing detection of automated submissions.
19
+ *
20
+ * @example
21
+ * ```tsx
22
+ * const { HoneypotComponent, isBotDetected } = useHoneypot();
23
+ *
24
+ * return (
25
+ * <form>
26
+ * <HoneypotComponent />
27
+ * {isBotDetected && <p>Bot detected!</p>}
28
+ * </form>
29
+ * );
30
+ * ```
31
+ */
32
+ export declare function useHoneypot(): UseHoneypotReturn;
@@ -0,0 +1,29 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { useCallback, useState } from "react";
4
+ import { Honeypot } from "../components/honeypot.js";
5
+ function useHoneypot() {
6
+ const [isBotDetected, setIsBotDetected] = useState(false);
7
+ const [botScore, setBotScore] = useState(0);
8
+ const handleBotDetected = useCallback(()=>{
9
+ setIsBotDetected(true);
10
+ setBotScore((prev)=>prev + 50);
11
+ }, []);
12
+ const resetDetection = useCallback(()=>{
13
+ setIsBotDetected(false);
14
+ setBotScore(0);
15
+ }, []);
16
+ const HoneypotComponent = useCallback(()=>/*#__PURE__*/ jsx(Honeypot, {
17
+ onBotDetected: handleBotDetected
18
+ }), [
19
+ handleBotDetected
20
+ ]);
21
+ return {
22
+ isBotDetected,
23
+ botScore,
24
+ handleBotDetected,
25
+ resetDetection,
26
+ HoneypotComponent
27
+ };
28
+ }
29
+ export { useHoneypot };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,78 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { describe, expect, it } from "@rstest/core";
4
+ import { act, render, renderHook } from "@testing-library/react";
5
+ import { useHoneypot } from "./use-honeypot.js";
6
+ describe('useHoneypot', ()=>{
7
+ describe('initial state', ()=>{
8
+ it('should not detect bot initially', ()=>{
9
+ const { result } = renderHook(()=>useHoneypot());
10
+ expect(result.current.isBotDetected).toBe(false);
11
+ });
12
+ it('should have initial bot score of 0', ()=>{
13
+ const { result } = renderHook(()=>useHoneypot());
14
+ expect(result.current.botScore).toBe(0);
15
+ });
16
+ });
17
+ describe('handleBotDetected', ()=>{
18
+ it('should set isBotDetected to true', ()=>{
19
+ const { result } = renderHook(()=>useHoneypot());
20
+ act(()=>{
21
+ result.current.handleBotDetected();
22
+ });
23
+ expect(result.current.isBotDetected).toBe(true);
24
+ });
25
+ it('should add 50 to bot score', ()=>{
26
+ const { result } = renderHook(()=>useHoneypot());
27
+ act(()=>{
28
+ result.current.handleBotDetected();
29
+ });
30
+ expect(result.current.botScore).toBe(50);
31
+ });
32
+ it('should accumulate bot score on multiple calls', ()=>{
33
+ const { result } = renderHook(()=>useHoneypot());
34
+ act(()=>{
35
+ result.current.handleBotDetected();
36
+ result.current.handleBotDetected();
37
+ });
38
+ expect(result.current.botScore).toBe(100);
39
+ });
40
+ });
41
+ describe('resetDetection', ()=>{
42
+ it('should reset isBotDetected to false', ()=>{
43
+ const { result } = renderHook(()=>useHoneypot());
44
+ act(()=>{
45
+ result.current.handleBotDetected();
46
+ });
47
+ expect(result.current.isBotDetected).toBe(true);
48
+ act(()=>{
49
+ result.current.resetDetection();
50
+ });
51
+ expect(result.current.isBotDetected).toBe(false);
52
+ });
53
+ it('should reset bot score to 0', ()=>{
54
+ const { result } = renderHook(()=>useHoneypot());
55
+ act(()=>{
56
+ result.current.handleBotDetected();
57
+ });
58
+ expect(result.current.botScore).toBe(50);
59
+ act(()=>{
60
+ result.current.resetDetection();
61
+ });
62
+ expect(result.current.botScore).toBe(0);
63
+ });
64
+ });
65
+ describe('HoneypotComponent', ()=>{
66
+ it('should render a component', ()=>{
67
+ const { result } = renderHook(()=>useHoneypot());
68
+ const { container } = render(/*#__PURE__*/ jsx(result.current.HoneypotComponent, {}));
69
+ expect(container.firstChild).not.toBeNull();
70
+ });
71
+ it('should render honeypot fields with aria-hidden', ()=>{
72
+ const { result } = renderHook(()=>useHoneypot());
73
+ render(/*#__PURE__*/ jsx(result.current.HoneypotComponent, {}));
74
+ const hiddenWrapper = document.querySelector('[aria-hidden="true"]');
75
+ expect(hiddenWrapper).not.toBeNull();
76
+ });
77
+ });
78
+ });
@@ -0,0 +1,9 @@
1
+ export type { BotDetectionResult, BotDetectionStats, ButtonComponentProps, } from './types';
2
+ export { useBotDetection } from './hooks';
3
+ export type { UseBotDetectionReturn } from './hooks';
4
+ export { useHoneypot } from './hooks';
5
+ export type { UseHoneypotReturn } from './hooks';
6
+ export { Honeypot } from './components';
7
+ export type { HoneypotProps } from './components';
8
+ export { BotProtectedForm } from './components';
9
+ export type { BotProtectedFormProps } from './components';
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ import { useBotDetection, useHoneypot } from "./hooks/index.js";
2
+ import { BotProtectedForm, Honeypot } from "./components/index.js";
3
+ export { BotProtectedForm, Honeypot, useBotDetection, useHoneypot };