@prosopo/procaptcha-common 2.9.23 → 2.10.18
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/.turbo/turbo-build$colon$cjs.log +22 -15
- package/.turbo/turbo-build$colon$tsc.log +22 -16
- package/.turbo/turbo-build.log +22 -16
- package/CHANGELOG.md +355 -0
- package/dist/cjs/elements/window.cjs +7 -0
- package/dist/cjs/index.cjs +5 -0
- package/dist/cjs/reactComponents/Checkbox.cjs +11 -7
- package/dist/cjs/reactComponents/Honeypot.cjs +67 -0
- package/dist/cjs/reactComponents/TestModeBanner.cjs +47 -0
- package/dist/elements/window.d.ts +1 -0
- package/dist/elements/window.d.ts.map +1 -1
- package/dist/elements/window.js +8 -1
- package/dist/elements/window.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/reactComponents/Checkbox.d.ts.map +1 -1
- package/dist/reactComponents/Checkbox.js +11 -7
- package/dist/reactComponents/Checkbox.js.map +1 -1
- package/dist/reactComponents/Honeypot.d.ts +6 -0
- package/dist/reactComponents/Honeypot.d.ts.map +1 -0
- package/dist/reactComponents/Honeypot.js +67 -0
- package/dist/reactComponents/Honeypot.js.map +1 -0
- package/dist/reactComponents/TestModeBanner.d.ts +7 -0
- package/dist/reactComponents/TestModeBanner.d.ts.map +1 -0
- package/dist/reactComponents/TestModeBanner.js +47 -0
- package/dist/reactComponents/TestModeBanner.js.map +1 -0
- package/dist/tests/window.test.js +22 -1
- package/dist/tests/window.test.js.map +1 -1
- package/package.json +11 -8
- package/src/callbacks/defaultCallbacks.ts +197 -0
- package/src/callbacks/defaultEvents.ts +21 -0
- package/src/elements/form.ts +41 -0
- package/src/elements/window.ts +39 -0
- package/src/extensionLoader.ts +17 -0
- package/src/index.ts +24 -0
- package/src/providers.ts +49 -0
- package/src/reactComponents/Checkbox.tsx +203 -0
- package/src/reactComponents/Honeypot.tsx +133 -0
- package/src/reactComponents/Reload.tsx +99 -0
- package/src/reactComponents/TestModeBanner.tsx +75 -0
- package/src/state/builder.ts +137 -0
- package/src/tests/defaultCallbacks.test.ts +372 -0
- package/src/tests/defaultEvents.test.ts +80 -0
- package/src/tests/extensionLoader.test.ts +41 -0
- package/src/tests/form.test.ts +154 -0
- package/src/tests/providers.test.ts +175 -0
- package/src/tests/state-builder.test.ts +264 -0
- package/src/tests/window.test.ts +137 -0
- package/tsconfig.cjs.json +32 -0
- package/tsconfig.json +33 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/tsconfig.types.json +9 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// Copyright 2021-2026 Prosopo (UK) Ltd.
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
type CSSProperties,
|
|
17
|
+
forwardRef,
|
|
18
|
+
useEffect,
|
|
19
|
+
useId,
|
|
20
|
+
useMemo,
|
|
21
|
+
useRef,
|
|
22
|
+
useState,
|
|
23
|
+
} from "react";
|
|
24
|
+
import { createPortal } from "react-dom";
|
|
25
|
+
|
|
26
|
+
interface HoneypotProps {
|
|
27
|
+
encodedQuestion: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const offscreenStyle: CSSProperties = {
|
|
31
|
+
position: "absolute",
|
|
32
|
+
left: "-9999px",
|
|
33
|
+
top: "-9999px",
|
|
34
|
+
width: "1px",
|
|
35
|
+
height: "1px",
|
|
36
|
+
overflow: "hidden",
|
|
37
|
+
opacity: 0,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Server wraps morse/semaphore in base64 (utf-8) for the wire. Strip the
|
|
41
|
+
// base64 layer here so the rendered label is the raw morse/semaphore an agent
|
|
42
|
+
// can recognise and engage with.
|
|
43
|
+
const decodeBase64Utf8 = (b64: string): string => {
|
|
44
|
+
const binary = atob(b64);
|
|
45
|
+
const bytes = Uint8Array.from(binary, (c) => c.charCodeAt(0));
|
|
46
|
+
return new TextDecoder().decode(bytes);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// Locate the dapp's enclosing <form> by stepping out of the widget's shadow
|
|
50
|
+
// root into light DOM, then walking up via closest(). Returns null when the
|
|
51
|
+
// widget isn't embedded inside a form.
|
|
52
|
+
const findAncestorForm = (anchor: Element): HTMLFormElement | null => {
|
|
53
|
+
const root = anchor.getRootNode();
|
|
54
|
+
const lightDomEntry = root instanceof ShadowRoot ? root.host : anchor;
|
|
55
|
+
return lightDomEntry instanceof Element
|
|
56
|
+
? lightDomEntry.closest("form")
|
|
57
|
+
: null;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// Honeypot must live in light DOM, not in the widget's shadow root: if it
|
|
61
|
+
// rendered there a bot would have to traverse `.shadowRoot` to reach it, and
|
|
62
|
+
// @prosopo/catcher patches that getter to detect (and restart on) automated
|
|
63
|
+
// access — wiping the value the bot just wrote before it can submit.
|
|
64
|
+
//
|
|
65
|
+
// Within light DOM we prefer the enclosing <form> so bots scraping
|
|
66
|
+
// `form.querySelectorAll('input')` discover the bait naturally; document.body
|
|
67
|
+
// is the fallback for widgets mounted outside any form.
|
|
68
|
+
//
|
|
69
|
+
// The decoded question is rendered as the input's <label>, not as its value.
|
|
70
|
+
// Naive form-fillers leave the empty input alone — no signal, no false
|
|
71
|
+
// positives. Agents that read the DOM as a prompt may decode the label and
|
|
72
|
+
// write an answer into the empty field; that response rides up as
|
|
73
|
+
// clientMetaData.hp.
|
|
74
|
+
export const Honeypot = forwardRef<HTMLInputElement, HoneypotProps>(
|
|
75
|
+
({ encodedQuestion }, ref) => {
|
|
76
|
+
const id = useId();
|
|
77
|
+
// Opaque non-existent form id. Setting `form="..."` on an input with a
|
|
78
|
+
// value that doesn't match any form's id disassociates the input from
|
|
79
|
+
// every form: the browser excludes it from the parent form's submission
|
|
80
|
+
// set and from `form.elements`, while leaving it discoverable via
|
|
81
|
+
// `form.querySelectorAll('input')`. Built from useId so it's unique per
|
|
82
|
+
// Honeypot instance and guaranteed not to collide with the input's own id.
|
|
83
|
+
const detachedFormId = `${id}-d`;
|
|
84
|
+
const question = useMemo(
|
|
85
|
+
() => decodeBase64Utf8(encodedQuestion),
|
|
86
|
+
[encodedQuestion],
|
|
87
|
+
);
|
|
88
|
+
const anchorRef = useRef<HTMLSpanElement>(null);
|
|
89
|
+
const [portalTarget, setPortalTarget] = useState<HTMLElement | null>(null);
|
|
90
|
+
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
const anchor = anchorRef.current;
|
|
93
|
+
if (!anchor) return;
|
|
94
|
+
setPortalTarget(findAncestorForm(anchor) ?? document.body);
|
|
95
|
+
}, []);
|
|
96
|
+
|
|
97
|
+
if (typeof document === "undefined") return null;
|
|
98
|
+
|
|
99
|
+
// Transient anchor while we resolve the portal target. Sits in the React
|
|
100
|
+
// tree (inside the shadow root) just long enough for the effect above to
|
|
101
|
+
// walk out to light DOM and find the form.
|
|
102
|
+
if (!portalTarget) {
|
|
103
|
+
return (
|
|
104
|
+
<span ref={anchorRef} aria-hidden="true" style={{ display: "none" }} />
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Input is nested inside the label (implicit association) instead of
|
|
109
|
+
// htmlFor-linked — biome's noLabelWithoutControl rule only recognises
|
|
110
|
+
// the descendant form of association at static-analysis time.
|
|
111
|
+
return createPortal(
|
|
112
|
+
<div aria-hidden="true" style={offscreenStyle}>
|
|
113
|
+
<label>
|
|
114
|
+
{question}
|
|
115
|
+
<input
|
|
116
|
+
ref={ref}
|
|
117
|
+
id={id}
|
|
118
|
+
form={detachedFormId}
|
|
119
|
+
type="text"
|
|
120
|
+
name="email_confirm"
|
|
121
|
+
defaultValue=""
|
|
122
|
+
tabIndex={-1}
|
|
123
|
+
autoComplete="off"
|
|
124
|
+
aria-hidden="true"
|
|
125
|
+
/>
|
|
126
|
+
</label>
|
|
127
|
+
</div>,
|
|
128
|
+
portalTarget,
|
|
129
|
+
);
|
|
130
|
+
},
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
Honeypot.displayName = "Honeypot";
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// Copyright 2021-2026 Prosopo (UK) Ltd.
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
|
|
15
|
+
import { darkTheme, lightTheme } from "@prosopo/widget-skeleton";
|
|
16
|
+
import { type ButtonHTMLAttributes, type FC, useMemo, useState } from "react";
|
|
17
|
+
|
|
18
|
+
interface ReloadButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
19
|
+
themeColor: "light" | "dark";
|
|
20
|
+
onReload: () => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const buttonStyleBase = {
|
|
24
|
+
border: "none",
|
|
25
|
+
paddingTop: "6px",
|
|
26
|
+
paddingBottom: "6px",
|
|
27
|
+
cursor: "pointer",
|
|
28
|
+
height: "39px",
|
|
29
|
+
width: "39px",
|
|
30
|
+
borderRadius: "50%",
|
|
31
|
+
display: "flex",
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const ReloadButton: FC<ReloadButtonProps> = ({
|
|
35
|
+
themeColor,
|
|
36
|
+
onReload,
|
|
37
|
+
}: ReloadButtonProps) => {
|
|
38
|
+
const theme = useMemo(
|
|
39
|
+
() => (themeColor === "light" ? lightTheme : darkTheme),
|
|
40
|
+
[themeColor],
|
|
41
|
+
);
|
|
42
|
+
const [hover, setHover] = useState(false);
|
|
43
|
+
const buttonStyle = useMemo(() => {
|
|
44
|
+
const baseStyle = {
|
|
45
|
+
...buttonStyleBase,
|
|
46
|
+
backgroundColor: theme.palette.background.default,
|
|
47
|
+
color: hover
|
|
48
|
+
? theme.palette.primary.contrastText
|
|
49
|
+
: theme.palette.background.contrastText,
|
|
50
|
+
border: `1px solid ${theme.palette.grey[500]}`,
|
|
51
|
+
borderRadius: "50%",
|
|
52
|
+
transition: "background-color 0.3s",
|
|
53
|
+
boxShadow: `0px 1px 3px 0px ${theme.palette.grey[500]}`,
|
|
54
|
+
justifyContent: "center",
|
|
55
|
+
alignItems: "center",
|
|
56
|
+
margin: "0 auto",
|
|
57
|
+
};
|
|
58
|
+
return {
|
|
59
|
+
...baseStyle,
|
|
60
|
+
backgroundColor: hover
|
|
61
|
+
? theme.palette.grey[700]
|
|
62
|
+
: theme.palette.background.default,
|
|
63
|
+
};
|
|
64
|
+
}, [hover, theme]);
|
|
65
|
+
return (
|
|
66
|
+
<button
|
|
67
|
+
className="reload-button"
|
|
68
|
+
aria-label="Reload"
|
|
69
|
+
type="button"
|
|
70
|
+
style={buttonStyle}
|
|
71
|
+
onMouseEnter={() => setHover(true)}
|
|
72
|
+
onMouseLeave={() => setHover(false)}
|
|
73
|
+
onClick={(e) => {
|
|
74
|
+
e.preventDefault();
|
|
75
|
+
onReload();
|
|
76
|
+
}}
|
|
77
|
+
>
|
|
78
|
+
<svg
|
|
79
|
+
width="16px"
|
|
80
|
+
height="16px"
|
|
81
|
+
viewBox="0 0 16 16"
|
|
82
|
+
version="1.1"
|
|
83
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
84
|
+
xmlnsXlink="http://www.w3.org/1999/xlink"
|
|
85
|
+
style={{ display: "flex" }}
|
|
86
|
+
>
|
|
87
|
+
<title>reload</title>
|
|
88
|
+
<path
|
|
89
|
+
shapeRendering="optimizeQuality"
|
|
90
|
+
fill={
|
|
91
|
+
hover ? theme.palette.primary.contrastText : theme.palette.grey[700]
|
|
92
|
+
}
|
|
93
|
+
transform={"scale(0.0416)"}
|
|
94
|
+
d="M234.666667,149.333333 L234.666667,106.666667 L314.564847,106.664112 C287.579138,67.9778918 242.745446,42.6666667 192,42.6666667 C109.525477,42.6666667 42.6666667,109.525477 42.6666667,192 C42.6666667,274.474523 109.525477,341.333333 192,341.333333 C268.201293,341.333333 331.072074,284.258623 340.195444,210.526102 L382.537159,215.817985 C370.807686,310.617565 289.973536,384 192,384 C85.961328,384 1.42108547e-14,298.038672 1.42108547e-14,192 C1.42108547e-14,85.961328 85.961328,1.42108547e-14 192,1.42108547e-14 C252.316171,1.42108547e-14 306.136355,27.8126321 341.335366,71.3127128 L341.333333,1.42108547e-14 L384,1.42108547e-14 L384,149.333333 L234.666667,149.333333 Z"
|
|
95
|
+
/>
|
|
96
|
+
</svg>
|
|
97
|
+
</button>
|
|
98
|
+
);
|
|
99
|
+
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// Copyright 2021-2026 Prosopo (UK) Ltd.
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
|
|
15
|
+
/** @jsxImportSource @emotion/react */
|
|
16
|
+
|
|
17
|
+
import { TestSiteKeyMode, getTestSiteKeyMode } from "@prosopo/types";
|
|
18
|
+
import { type FC, useEffect } from "react";
|
|
19
|
+
|
|
20
|
+
interface TestModeBannerProps {
|
|
21
|
+
siteKey: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Renders a prominent warning when the widget is configured with one of the
|
|
25
|
+
// reserved CI test site keys (always-pass / always-fail). Renders nothing for a
|
|
26
|
+
// normal site key. This is the user-facing safeguard that stops a test key from
|
|
27
|
+
// being shipped to production unnoticed.
|
|
28
|
+
export const TestModeBanner: FC<TestModeBannerProps> = ({
|
|
29
|
+
siteKey,
|
|
30
|
+
}: TestModeBannerProps) => {
|
|
31
|
+
const mode: TestSiteKeyMode | null = getTestSiteKeyMode(siteKey);
|
|
32
|
+
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if (mode !== null) {
|
|
35
|
+
console.warn(
|
|
36
|
+
`[Procaptcha] WARNING: site key "${siteKey}" is a TEST key that ALWAYS ${
|
|
37
|
+
mode === TestSiteKeyMode.Pass ? "PASSES" : "FAILS"
|
|
38
|
+
}. Never use it in production.`,
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
}, [mode, siteKey]);
|
|
42
|
+
|
|
43
|
+
if (mode === null) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const action =
|
|
48
|
+
mode === TestSiteKeyMode.Pass ? "ALWAYS PASSES" : "ALWAYS FAILS";
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
// biome-ignore lint/a11y/useSemanticElements: the "alert" role has no native HTML element equivalent
|
|
52
|
+
<div
|
|
53
|
+
role="alert"
|
|
54
|
+
data-cy="test-mode-banner"
|
|
55
|
+
css={{
|
|
56
|
+
width: "100%",
|
|
57
|
+
boxSizing: "border-box",
|
|
58
|
+
padding: "6px 10px",
|
|
59
|
+
backgroundColor: "#fff3cd",
|
|
60
|
+
color: "#664d03",
|
|
61
|
+
border: "1px solid #ffe69c",
|
|
62
|
+
borderRadius: "4px",
|
|
63
|
+
fontSize: "11px",
|
|
64
|
+
lineHeight: "1.3",
|
|
65
|
+
fontFamily:
|
|
66
|
+
"-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
|
|
67
|
+
textAlign: "center",
|
|
68
|
+
}}
|
|
69
|
+
>
|
|
70
|
+
{`⚠ Test mode: this site key ${action}. Do not use in production.`}
|
|
71
|
+
</div>
|
|
72
|
+
);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export default TestModeBanner;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// Copyright 2021-2026 Prosopo (UK) Ltd.
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
import type {
|
|
15
|
+
Account,
|
|
16
|
+
CaptchaResponseBody,
|
|
17
|
+
ProcaptchaApiInterface,
|
|
18
|
+
ProcaptchaState,
|
|
19
|
+
ProcaptchaStateUpdateFn,
|
|
20
|
+
TCaptchaSubmitResult,
|
|
21
|
+
} from "@prosopo/types";
|
|
22
|
+
|
|
23
|
+
type useRefType = <T>(defaultValue: T) => { current: T };
|
|
24
|
+
type useStateType = <T>(defaultValue: T) => [T, (value: T) => void];
|
|
25
|
+
|
|
26
|
+
export const buildUpdateState =
|
|
27
|
+
(state: ProcaptchaState, onStateUpdate: ProcaptchaStateUpdateFn) =>
|
|
28
|
+
(nextState: Partial<ProcaptchaState>) => {
|
|
29
|
+
// mutate the current state. Note that this is in order of properties in the nextState object.
|
|
30
|
+
// e.g. given {b: 2, c: 3, a: 1}, b will be set, then c, then a. This is because JS stores fields in insertion order by default, unless you override it with a class or such by changing the key enumeration order.
|
|
31
|
+
Object.assign(state, nextState);
|
|
32
|
+
// then call the update function for the frontend to do the same
|
|
33
|
+
onStateUpdate(nextState);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Wrap a ref to be the same format as useState.
|
|
38
|
+
* @param useRef the useRef function from react
|
|
39
|
+
* @param defaultValue the default value if the state is not already initialised
|
|
40
|
+
* @returns a ref in the same format as a state, e.g. [value, setValue]
|
|
41
|
+
*/
|
|
42
|
+
const useRefAsState = <T>(
|
|
43
|
+
useRef: useRefType,
|
|
44
|
+
defaultValue: T,
|
|
45
|
+
): [T, (value: T) => void] => {
|
|
46
|
+
const ref = useRef<T>(defaultValue);
|
|
47
|
+
const setter = (value: T) => {
|
|
48
|
+
ref.current = value;
|
|
49
|
+
};
|
|
50
|
+
const value: T = ref.current;
|
|
51
|
+
return [value, setter];
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const useProcaptcha = (
|
|
55
|
+
useState: useStateType,
|
|
56
|
+
useRef: useRefType,
|
|
57
|
+
): [ProcaptchaState, ProcaptchaStateUpdateFn] => {
|
|
58
|
+
const [isHuman, setIsHuman] = useState(false);
|
|
59
|
+
const [index, setIndex] = useState(0);
|
|
60
|
+
const [solutions, setSolutions] = useState(
|
|
61
|
+
[] as [string, number, number][][],
|
|
62
|
+
);
|
|
63
|
+
const [captchaApi, setCaptchaApi] = useRefAsState<
|
|
64
|
+
ProcaptchaApiInterface | undefined
|
|
65
|
+
>(useRef, undefined);
|
|
66
|
+
const [showModal, setShowModal] = useState(false);
|
|
67
|
+
const [challenge, setChallenge] = useState<CaptchaResponseBody | undefined>(
|
|
68
|
+
undefined,
|
|
69
|
+
);
|
|
70
|
+
const [loading, setLoading] = useState(false);
|
|
71
|
+
const [account, setAccount] = useState<Account | undefined>(undefined);
|
|
72
|
+
const [dappAccount, setDappAccount] = useState<string | undefined>(undefined);
|
|
73
|
+
const [submission, setSubmission] = useRefAsState<
|
|
74
|
+
TCaptchaSubmitResult | undefined
|
|
75
|
+
>(useRef, undefined);
|
|
76
|
+
const [timeout, setTimeout] = useRefAsState<NodeJS.Timeout | undefined>(
|
|
77
|
+
useRef,
|
|
78
|
+
undefined,
|
|
79
|
+
);
|
|
80
|
+
const [successfullChallengeTimeout, setSuccessfullChallengeTimeout] =
|
|
81
|
+
useRefAsState<NodeJS.Timeout | undefined>(useRef, undefined);
|
|
82
|
+
const [sendData, setSendData] = useState(false);
|
|
83
|
+
const [attemptCount, setAttemptCount] = useState(0);
|
|
84
|
+
const [error, setError] = useState<
|
|
85
|
+
{ message: string; key: string } | undefined
|
|
86
|
+
>(undefined);
|
|
87
|
+
const [sessionId, setSessionId] = useState<string | undefined>(undefined);
|
|
88
|
+
return [
|
|
89
|
+
// the state
|
|
90
|
+
{
|
|
91
|
+
isHuman,
|
|
92
|
+
index,
|
|
93
|
+
solutions,
|
|
94
|
+
captchaApi,
|
|
95
|
+
showModal,
|
|
96
|
+
challenge,
|
|
97
|
+
loading,
|
|
98
|
+
account,
|
|
99
|
+
dappAccount,
|
|
100
|
+
submission,
|
|
101
|
+
timeout,
|
|
102
|
+
successfullChallengeTimeout,
|
|
103
|
+
sendData,
|
|
104
|
+
attemptCount,
|
|
105
|
+
error,
|
|
106
|
+
sessionId,
|
|
107
|
+
},
|
|
108
|
+
// and method to update the state
|
|
109
|
+
(nextState: Partial<ProcaptchaState>) => {
|
|
110
|
+
if (nextState.account !== undefined) setAccount(nextState.account);
|
|
111
|
+
if (nextState.isHuman !== undefined) setIsHuman(nextState.isHuman);
|
|
112
|
+
if (nextState.index !== undefined) setIndex(nextState.index);
|
|
113
|
+
// force a copy of the array to ensure a re-render
|
|
114
|
+
// nutshell: react doesn't look inside an array for changes, hence changes to the array need to result in a fresh array
|
|
115
|
+
if (nextState.solutions !== undefined)
|
|
116
|
+
setSolutions(nextState.solutions.slice());
|
|
117
|
+
if (nextState.captchaApi !== undefined)
|
|
118
|
+
setCaptchaApi(nextState.captchaApi);
|
|
119
|
+
if (nextState.showModal !== undefined) setShowModal(nextState.showModal);
|
|
120
|
+
if (nextState.challenge !== undefined) setChallenge(nextState.challenge);
|
|
121
|
+
if (nextState.loading !== undefined) setLoading(nextState.loading);
|
|
122
|
+
if (nextState.showModal !== undefined) setShowModal(nextState.showModal);
|
|
123
|
+
if (nextState.dappAccount !== undefined)
|
|
124
|
+
setDappAccount(nextState.dappAccount);
|
|
125
|
+
if (nextState.submission !== undefined)
|
|
126
|
+
setSubmission(nextState.submission);
|
|
127
|
+
if (nextState.timeout !== undefined) setTimeout(nextState.timeout);
|
|
128
|
+
if (nextState.successfullChallengeTimeout !== undefined)
|
|
129
|
+
setSuccessfullChallengeTimeout(nextState.timeout);
|
|
130
|
+
if (nextState.sendData !== undefined) setSendData(nextState.sendData);
|
|
131
|
+
if (nextState.attemptCount !== undefined)
|
|
132
|
+
setAttemptCount(nextState.attemptCount);
|
|
133
|
+
if (nextState.error !== undefined) setError(nextState.error);
|
|
134
|
+
if (nextState.sessionId !== undefined) setSessionId(nextState.sessionId);
|
|
135
|
+
},
|
|
136
|
+
];
|
|
137
|
+
};
|