@ingram-tech/bot-protection 0.2.0 → 0.3.1

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 CHANGED
@@ -59,6 +59,51 @@ export async function POST(request: Request) {
59
59
  `verifyHuman` also accepts a plain object (`{ formData: { ...fields } }`) and a
60
60
  `timing` window (`{ minMs, maxMs }`). Pass `botid: false` to skip layer 3.
61
61
 
62
+ ### Client forms (JSON POST)
63
+
64
+ For client components that POST JSON instead of a server-rendered `<form>`, use
65
+ the `/react` hook. It fetches the token from your route's `GET` on mount and
66
+ hands you the fields to merge into the body. The route's `GET` returns the
67
+ token; its `POST` verifies:
68
+
69
+ ```tsx
70
+ "use client";
71
+ import { HoneypotInput, useBotProtection } from "@ingram-tech/bot-protection/react";
72
+
73
+ export function ContactForm() {
74
+ const { honeypotRef, botFields } = useBotProtection("/api/contact");
75
+
76
+ async function onSubmit(values: FormValues) {
77
+ await fetch("/api/contact", {
78
+ method: "POST",
79
+ headers: { "content-type": "application/json" },
80
+ body: JSON.stringify({ ...values, ...botFields() }),
81
+ });
82
+ }
83
+ return (
84
+ <form onSubmit={/* ... */}>
85
+ {/* ...your real fields... */}
86
+ <HoneypotInput inputRef={honeypotRef} />
87
+ </form>
88
+ );
89
+ }
90
+ ```
91
+
92
+ ```ts
93
+ // app/api/contact/route.ts
94
+ import { createFormToken, verifyHuman } from "@ingram-tech/bot-protection";
95
+
96
+ export const GET = () => Response.json({ token: createFormToken() });
97
+
98
+ export async function POST(request: Request) {
99
+ const body = await request.json();
100
+ const result = await verifyHuman({ formData: body });
101
+ if (!result.ok) return Response.json({ ok: true }); // silently drop
102
+ // ...send the email / save the lead...
103
+ return Response.json({ ok: true });
104
+ }
105
+ ```
106
+
62
107
  The honeypot field defaults to a name browsers and password managers won't
63
108
  autofill (filling it would falsely flag real users). If that default collides
64
109
  with a real field in your form, override it on both sides — they must match:
@@ -1,5 +1,5 @@
1
1
  export declare const HoneypotField: ({ token, field, }: {
2
2
  token: string;
3
3
  field?: string;
4
- }) => import("react/jsx-runtime").JSX.Element;
4
+ }) => import("react").JSX.Element;
5
5
  //# sourceMappingURL=honeypot.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"honeypot.d.ts","sourceRoot":"","sources":["../src/honeypot.tsx"],"names":[],"mappings":"AA2BA,eAAO,MAAM,aAAa,GAAI,mBAG3B;IACF,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CACf,4CAmBA,CAAC"}
1
+ {"version":3,"file":"honeypot.d.ts","sourceRoot":"","sources":["../src/honeypot.tsx"],"names":[],"mappings":"AA2BA,eAAO,MAAM,aAAa,GAAI,mBAG3B;IACF,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CACf,gCAoBA,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"honeypot.js","sourceRoot":"","sources":["../src/honeypot.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAEvD;;;;;;;;;;;;;GAaG;AAEH,MAAM,MAAM,GAAkB;IAC7B,QAAQ,EAAE,UAAU;IACpB,IAAI,EAAE,SAAS;IACf,GAAG,EAAE,SAAS;IACd,KAAK,EAAE,KAAK;IACZ,MAAM,EAAE,KAAK;IACb,QAAQ,EAAE,QAAQ;CAClB,CAAC;AAEF,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,EAC7B,KAAK,EACL,KAAK,GAAG,cAAc,GAItB,EAAE,EAAE,CAAC,CACL,8BAAiB,MAAM,EAAC,KAAK,EAAE,MAAM,aACpC,sDAEC,gBACC,IAAI,EAAC,MAAM,EACX,IAAI,EAAE,KAAK,EACX,QAAQ,EAAE,CAAC,CAAC,EACZ,YAAY,EAAC,KAAK,EAClB,YAAY,EAAC,EAAE,2CAID,MAAM,oBACL,OAAO,GACrB,IACK,EACR,gBAAO,IAAI,EAAC,QAAQ,EAAC,IAAI,EAAE,WAAW,EAAE,YAAY,EAAE,KAAK,GAAI,IAC1D,CACN,CAAC"}
1
+ {"version":3,"file":"honeypot.js","sourceRoot":"","sources":["../src/honeypot.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAEvD;;;;;;;;;;;;;GAaG;AAEH,MAAM,MAAM,GAAkB;IAC7B,QAAQ,EAAE,UAAU;IACpB,IAAI,EAAE,SAAS;IACf,GAAG,EAAE,SAAS;IACd,KAAK,EAAE,KAAK;IACZ,MAAM,EAAE,KAAK;IACb,QAAQ,EAAE,QAAQ;CAClB,CAAC;AAEF,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,EAC7B,KAAK,EACL,KAAK,GAAG,cAAc,GAItB,EAAE,EAAE,CAAC,CACL,8BAAiB,MAAM,EAAC,KAAK,EAAE,MAAM,aACpC,sDAGC,gBACC,IAAI,EAAC,MAAM,EACX,IAAI,EAAE,KAAK,EACX,QAAQ,EAAE,CAAC,CAAC,EACZ,YAAY,EAAC,KAAK,EAClB,YAAY,EAAC,EAAE,2CAID,MAAM,oBACL,OAAO,GACrB,IACK,EACR,gBAAO,IAAI,EAAC,QAAQ,EAAC,IAAI,EAAE,WAAW,EAAE,YAAY,EAAE,KAAK,GAAI,IAC1D,CACN,CAAC"}
@@ -0,0 +1,31 @@
1
+ import { type RefObject } from "react";
2
+ /**
3
+ * Client-side bot protection for forms that POST JSON to your own route.
4
+ *
5
+ * Fetches a signed timing token from `tokenEndpoint` on mount — the route's GET
6
+ * handler should return `{ token: createFormToken() }` — then exposes a honeypot
7
+ * ref plus a `botFields()` helper returning the fields to spread into the
8
+ * request body. The matching POST handler calls `verifyHuman({ formData: body })`.
9
+ *
10
+ * Pair with {@link HoneypotInput}, which renders the trap and wires the ref.
11
+ *
12
+ * @param tokenEndpoint - GET route that mints the token (e.g. "/api/contact").
13
+ *
14
+ * @example
15
+ * const { honeypotRef, botFields } = useBotProtection("/api/contact");
16
+ * // ...in the form: <HoneypotInput inputRef={honeypotRef} />
17
+ * // ...on submit: body: JSON.stringify({ ...values, ...botFields() })
18
+ */
19
+ export declare function useBotProtection(tokenEndpoint: string): {
20
+ honeypotRef: RefObject<HTMLInputElement | null>;
21
+ botFields: () => Record<string, string>;
22
+ };
23
+ /**
24
+ * Visually-hidden honeypot input for {@link useBotProtection}. Real users never
25
+ * see or fill it (off-screen, aria-hidden, tabIndex -1, autofill opted out);
26
+ * bots that fill every field give themselves away.
27
+ */
28
+ export declare function HoneypotInput({ inputRef, }: {
29
+ inputRef: RefObject<HTMLInputElement | null>;
30
+ }): import("react").JSX.Element;
31
+ //# sourceMappingURL=react.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,SAAS,EAA+B,MAAM,OAAO,CAAC;AAGpE;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAAC,aAAa,EAAE,MAAM;;qBAW/B,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;EAM5C;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,EAC7B,QAAQ,GACR,EAAE;IACF,QAAQ,EAAE,SAAS,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAC;CAC7C,+BAuBA"}
package/dist/react.js ADDED
@@ -0,0 +1,51 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { useEffect, useRef, useState } from "react";
4
+ import { HONEYPOT_FIELD, TOKEN_FIELD } from "./fields";
5
+ /**
6
+ * Client-side bot protection for forms that POST JSON to your own route.
7
+ *
8
+ * Fetches a signed timing token from `tokenEndpoint` on mount — the route's GET
9
+ * handler should return `{ token: createFormToken() }` — then exposes a honeypot
10
+ * ref plus a `botFields()` helper returning the fields to spread into the
11
+ * request body. The matching POST handler calls `verifyHuman({ formData: body })`.
12
+ *
13
+ * Pair with {@link HoneypotInput}, which renders the trap and wires the ref.
14
+ *
15
+ * @param tokenEndpoint - GET route that mints the token (e.g. "/api/contact").
16
+ *
17
+ * @example
18
+ * const { honeypotRef, botFields } = useBotProtection("/api/contact");
19
+ * // ...in the form: <HoneypotInput inputRef={honeypotRef} />
20
+ * // ...on submit: body: JSON.stringify({ ...values, ...botFields() })
21
+ */
22
+ export function useBotProtection(tokenEndpoint) {
23
+ const [token, setToken] = useState("");
24
+ const honeypotRef = useRef(null);
25
+ useEffect(() => {
26
+ fetch(tokenEndpoint)
27
+ .then((res) => res.json())
28
+ .then((data) => setToken(data.token ?? ""))
29
+ .catch(() => { });
30
+ }, [tokenEndpoint]);
31
+ const botFields = () => ({
32
+ [HONEYPOT_FIELD]: honeypotRef.current?.value ?? "",
33
+ [TOKEN_FIELD]: token,
34
+ });
35
+ return { honeypotRef, botFields };
36
+ }
37
+ /**
38
+ * Visually-hidden honeypot input for {@link useBotProtection}. Real users never
39
+ * see or fill it (off-screen, aria-hidden, tabIndex -1, autofill opted out);
40
+ * bots that fill every field give themselves away.
41
+ */
42
+ export function HoneypotInput({ inputRef, }) {
43
+ return (_jsx("input", { ref: inputRef, type: "text", name: HONEYPOT_FIELD, tabIndex: -1, autoComplete: "off", "aria-hidden": "true", defaultValue: "", "data-1p-ignore": true, "data-lpignore": "true", "data-form-type": "other", style: {
44
+ position: "absolute",
45
+ left: "-9999px",
46
+ width: "1px",
47
+ height: "1px",
48
+ overflow: "hidden",
49
+ } }));
50
+ }
51
+ //# sourceMappingURL=react.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react.js","sourceRoot":"","sources":["../src/react.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAAkB,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACpE,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAEvD;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,gBAAgB,CAAC,aAAqB;IACrD,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC;IACvC,MAAM,WAAW,GAAG,MAAM,CAAmB,IAAI,CAAC,CAAC;IAEnD,SAAS,CAAC,GAAG,EAAE;QACd,KAAK,CAAC,aAAa,CAAC;aAClB,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;aACzB,IAAI,CAAC,CAAC,IAAwB,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;aAC9D,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IACnB,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC;IAEpB,MAAM,SAAS,GAAG,GAA2B,EAAE,CAAC,CAAC;QAChD,CAAC,cAAc,CAAC,EAAE,WAAW,CAAC,OAAO,EAAE,KAAK,IAAI,EAAE;QAClD,CAAC,WAAW,CAAC,EAAE,KAAK;KACpB,CAAC,CAAC;IAEH,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,CAAC;AACnC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,EAC7B,QAAQ,GAGR;IACA,OAAO,CACN,gBACC,GAAG,EAAE,QAAQ,EACb,IAAI,EAAC,MAAM,EACX,IAAI,EAAE,cAAc,EACpB,QAAQ,EAAE,CAAC,CAAC,EACZ,YAAY,EAAC,KAAK,iBACN,MAAM,EAClB,YAAY,EAAC,EAAE,2CAGD,MAAM,oBACL,OAAO,EACtB,KAAK,EAAE;YACN,QAAQ,EAAE,UAAU;YACpB,IAAI,EAAE,SAAS;YACf,KAAK,EAAE,KAAK;YACZ,MAAM,EAAE,KAAK;YACb,QAAQ,EAAE,QAAQ;SAClB,GACA,CACF,CAAC;AACH,CAAC"}
package/dist/token.d.ts CHANGED
@@ -7,7 +7,7 @@ export declare const createFormToken: () => string;
7
7
  export interface TokenCheck {
8
8
  /** Reject submissions faster than this (default 2000ms — bots are instant). */
9
9
  minMs?: number;
10
- /** Reject tokens older than this (default 1h — stale/replayed forms). */
10
+ /** Reject tokens older than this (default 1h — bounds the replay window). */
11
11
  maxMs?: number;
12
12
  }
13
13
  export interface TokenResult {
@@ -1 +1 @@
1
- {"version":3,"file":"token.d.ts","sourceRoot":"","sources":["../src/token.ts"],"names":[],"mappings":"AAYA;;;;GAIG;AACH,eAAO,MAAM,eAAe,QAAO,MASlC,CAAC;AAEF,MAAM,WAAW,UAAU;IAC1B,+EAA+E;IAC/E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yEAAyE;IACzE,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,WAAW;IAC3B,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,6EAA6E;AAC7E,eAAO,MAAM,eAAe,GAC3B,OAAO,MAAM,GAAG,SAAS,GAAG,IAAI,EAChC,mBAA0C,UAAe,KACvD,WAqBF,CAAC"}
1
+ {"version":3,"file":"token.d.ts","sourceRoot":"","sources":["../src/token.ts"],"names":[],"mappings":"AAmBA;;;;GAIG;AACH,eAAO,MAAM,eAAe,QAAO,MASlC,CAAC;AAEF,MAAM,WAAW,UAAU;IAC1B,+EAA+E;IAC/E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6EAA6E;IAC7E,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,WAAW;IAC3B,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,6EAA6E;AAC7E,eAAO,MAAM,eAAe,GAC3B,OAAO,MAAM,GAAG,SAAS,GAAG,IAAI,EAChC,mBAA0C,UAAe,KACvD,WAqBF,CAAC"}
package/dist/token.js CHANGED
@@ -4,6 +4,13 @@ import { getSecret, isConfigured } from "./keys";
4
4
  * Signed timing token: `<timestamp>.<hmac>`. The timestamp records when the
5
5
  * form was rendered; the HMAC (keyed by BOT_PROTECTION_SECRET) prevents a bot
6
6
  * from forging an older timestamp to defeat the "submitted too fast" check.
7
+ *
8
+ * Scope: this is a *timing window* gate, not a per-submission nonce. A token is
9
+ * valid for any number of submissions between `minMs` and `maxMs` after it was
10
+ * minted, so it does not by itself prevent replay within that window — it raises
11
+ * the cost (a bot must fetch a real form and wait) and composes with the
12
+ * honeypot and Vercel BotID layers. If you need true single-use semantics, bind
13
+ * the token to a server-stored, one-time value (e.g. a session/CSRF nonce).
7
14
  */
8
15
  const sign = (ts, secret) => createHmac("sha256", secret).update(ts).digest("hex");
9
16
  /**
package/dist/token.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"token.js","sourceRoot":"","sources":["../src/token.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAEjD;;;;GAIG;AAEH,MAAM,IAAI,GAAG,CAAC,EAAU,EAAE,MAAc,EAAU,EAAE,CACnD,UAAU,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAEvD;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,GAAW,EAAE;IAC3C,IAAI,CAAC,YAAY,EAAE,EAAE,CAAC;QACrB,OAAO,CAAC,IAAI,CACX,qFAAqF,CACrF,CAAC;QACF,OAAO,EAAE,CAAC;IACX,CAAC;IACD,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC;IACjC,OAAO,GAAG,EAAE,IAAI,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC;AACzC,CAAC,CAAC;AAcF,6EAA6E;AAC7E,MAAM,CAAC,MAAM,eAAe,GAAG,CAC9B,KAAgC,EAChC,EAAE,KAAK,GAAG,IAAI,EAAE,KAAK,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,KAAiB,EAAE,EAC3C,EAAE;IAChB,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC;IAE1D,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC/B,IAAI,GAAG,IAAI,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,iBAAiB,EAAE,CAAC;IAC9D,MAAM,EAAE,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAC/B,MAAM,GAAG,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;IAEjC,MAAM,QAAQ,GAAG,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC;IACvC,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC3B,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAChC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM,IAAI,CAAC,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;QACrD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC;IAC/C,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC;IACxC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC;IAC7E,IAAI,OAAO,GAAG,KAAK;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;IAC9D,IAAI,OAAO,GAAG,KAAK;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;IAE7D,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;AACrB,CAAC,CAAC"}
1
+ {"version":3,"file":"token.js","sourceRoot":"","sources":["../src/token.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAEjD;;;;;;;;;;;GAWG;AAEH,MAAM,IAAI,GAAG,CAAC,EAAU,EAAE,MAAc,EAAU,EAAE,CACnD,UAAU,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAEvD;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,GAAW,EAAE;IAC3C,IAAI,CAAC,YAAY,EAAE,EAAE,CAAC;QACrB,OAAO,CAAC,IAAI,CACX,qFAAqF,CACrF,CAAC;QACF,OAAO,EAAE,CAAC;IACX,CAAC;IACD,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC;IACjC,OAAO,GAAG,EAAE,IAAI,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC;AACzC,CAAC,CAAC;AAcF,6EAA6E;AAC7E,MAAM,CAAC,MAAM,eAAe,GAAG,CAC9B,KAAgC,EAChC,EAAE,KAAK,GAAG,IAAI,EAAE,KAAK,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,KAAiB,EAAE,EAC3C,EAAE;IAChB,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC;IAE1D,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC/B,IAAI,GAAG,IAAI,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,iBAAiB,EAAE,CAAC;IAC9D,MAAM,EAAE,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAC/B,MAAM,GAAG,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;IAEjC,MAAM,QAAQ,GAAG,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC;IACvC,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC3B,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAChC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM,IAAI,CAAC,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;QACrD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC;IAC/C,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC;IACxC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC;IAC7E,IAAI,OAAO,GAAG,KAAK;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;IAC9D,IAAI,OAAO,GAAG,KAAK;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;IAE7D,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;AACrB,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ingram-tech/bot-protection",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "Invisible, layered bot protection for forms — honeypot + signed timing token + Vercel BotID.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -24,6 +24,10 @@
24
24
  "types": "./dist/honeypot.d.ts",
25
25
  "import": "./dist/honeypot.js"
26
26
  },
27
+ "./react": {
28
+ "types": "./dist/react.d.ts",
29
+ "import": "./dist/react.js"
30
+ },
27
31
  "./fields": {
28
32
  "types": "./dist/fields.d.ts",
29
33
  "import": "./dist/fields.js"