@prosopo/procaptcha-react 0.2.2 → 0.2.5
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 +95 -49
- package/dist/cjs/components/CaptchaComponent.cjs +110 -132
- package/dist/cjs/components/CaptchaWidget.cjs +6 -7
- package/dist/cjs/components/Procaptcha.cjs +49 -15
- package/dist/cjs/components/theme.cjs +20 -2
- package/dist/components/CaptchaComponent.d.ts +2 -1
- package/dist/components/CaptchaComponent.d.ts.map +1 -1
- package/dist/components/CaptchaComponent.js +38 -54
- package/dist/components/CaptchaComponent.js.map +1 -1
- package/dist/components/CaptchaWidget.d.ts +1 -1
- package/dist/components/CaptchaWidget.d.ts.map +1 -1
- package/dist/components/CaptchaWidget.js +61 -63
- package/dist/components/CaptchaWidget.js.map +1 -1
- package/dist/components/ExtensionAccountSelect.d.ts.map +1 -1
- package/dist/components/ExtensionAccountSelect.js.map +1 -1
- package/dist/components/Procaptcha.d.ts.map +1 -1
- package/dist/components/Procaptcha.js +55 -39
- package/dist/components/Procaptcha.js.map +1 -1
- package/dist/components/theme.d.ts +2 -2
- package/dist/components/theme.d.ts.map +1 -1
- package/dist/components/theme.js +17 -1
- package/dist/components/theme.js.map +1 -1
- package/package.json +9 -5
package/README.md
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Prosopo Procaptcha React Component Library
|
|
2
2
|
|
|
3
3
|
React components for integrating the Prosopo [procaptcha](https://github.com/prosopo/procaptcha) into a React app.
|
|
4
4
|
|
|
5
|
-
Prosopo is a distributed human verification service that can be used to stop bots from interacting with
|
|
5
|
+
Prosopo is a distributed human verification service that can be used to stop bots from interacting with your apps.
|
|
6
|
+
Sign up to be a network [beta tester](https://prosopo.io/#signup).
|
|
6
7
|
|
|
7
8
|
## Installation
|
|
8
9
|
|
|
@@ -14,73 +15,118 @@ npm install @prosopo/procaptcha-react --save
|
|
|
14
15
|
|
|
15
16
|
## Basic Usage
|
|
16
17
|
|
|
17
|
-
See the [client example](https://github.com/prosopo/client-example) for a minimal example of these components being used
|
|
18
|
+
See the [client example](https://github.com/prosopo/client-example) for a minimal example of these components being used
|
|
19
|
+
in a frontend app.
|
|
18
20
|
|
|
19
|
-
```
|
|
20
|
-
<
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
<CaptchaContextManager.Provider value={manager}>
|
|
24
|
-
{showCaptchas &&
|
|
25
|
-
<CaptchaComponent {...{ clientInterface }} />}
|
|
26
|
-
</CaptchaContextManager.Provider>
|
|
27
|
-
|
|
28
|
-
{!showCaptchas &&
|
|
29
|
-
<Button onClick={showCaptchaClick} className={"iAmHumanButton"}>
|
|
30
|
-
<Typography className={"iAmHumanButtonLabel"}>
|
|
31
|
-
I am human
|
|
32
|
-
</Typography>
|
|
33
|
-
</Button>}
|
|
34
|
-
|
|
35
|
-
</Box>
|
|
21
|
+
```jsx
|
|
22
|
+
<Procaptcha config={config} callbacks={{ onAccountNotFound, onError, onHuman, onExpired }}/>
|
|
36
23
|
```
|
|
37
24
|
|
|
38
25
|
### Callbacks
|
|
39
|
-
`CaptchaEventCallbacks` are passed to the captcha component at creation.
|
|
40
26
|
|
|
41
|
-
|
|
42
|
-
const clientInterface = useCaptcha({ config }, { onAccountChange, onChange, onSubmit, onSolved, onCancel });
|
|
43
|
-
```
|
|
27
|
+
`ProcaptchaEvents` are passed to the captcha component at creation.
|
|
44
28
|
|
|
45
29
|
The captcha event callbacks are defined as follows:
|
|
46
30
|
|
|
47
31
|
```typescript
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
onChange?: (captchaSolution: number[][], index: number) => void;
|
|
59
|
-
onCancel?: () => void;
|
|
60
|
-
onSolved?: (result: TCaptchaSubmitResult, isHuman?: boolean) => void;
|
|
32
|
+
/**
|
|
33
|
+
* A list of all events which can occur during the Procaptcha process.
|
|
34
|
+
*/
|
|
35
|
+
export interface ProcaptchaEvents {
|
|
36
|
+
onError: (error: Error) => void
|
|
37
|
+
onAccountNotFound: (address: string) => void
|
|
38
|
+
onHuman: (output: ProcaptchaOutput) => void
|
|
39
|
+
onExtensionNotFound: () => void
|
|
40
|
+
onExpired: () => void
|
|
41
|
+
onFailed: () => void
|
|
61
42
|
}
|
|
62
43
|
```
|
|
63
44
|
|
|
64
|
-
|
|
45
|
+
### onHuman
|
|
46
|
+
|
|
47
|
+
The `onHuman` callback is called when the user has successfully completed the captcha challenge. The `ProcaptchaOutput`
|
|
48
|
+
object contains the following fields:
|
|
49
|
+
|
|
50
|
+
| Key | Type | Description |
|
|
51
|
+
|--------------|--------|-------------------------------------------------------------------------------------------------------------------------------|
|
|
52
|
+
| commitmentId | string | The commitment ID of the captcha challenge. This is used to verify the user's response on-chain. |
|
|
53
|
+
| providerUrl | string | The URL of the provider that the user used to solve the captcha challenge. |
|
|
54
|
+
| dapp | string | The SITE_KEY of your application / website |
|
|
55
|
+
| user | string | The user's account address |
|
|
56
|
+
| blockNumber | number | The block number of the captcha challenge. This is used to verify that the contacted provider was randomly selected on-chain. |
|
|
57
|
+
|
|
58
|
+
### onError
|
|
59
|
+
|
|
60
|
+
The `onError` callback is called when an error occurs during the captcha process. The `Error` object is a standard
|
|
61
|
+
JavaScript error.
|
|
65
62
|
|
|
66
|
-
|
|
63
|
+
### onAccountNotFound
|
|
67
64
|
|
|
68
|
-
|
|
65
|
+
The `onAccountNotFound` callback is called when the user's account is not found in the Procaptcha config in
|
|
66
|
+
the [`userAccountAddress`](https://github.com/prosopo/captcha/blob/0bb4850adfe2b995dc16f7dd18e6ea844a0b6997/packages/types/src/config/config.ts#L116) field.
|
|
69
67
|
|
|
70
|
-
|
|
68
|
+
### onExpired
|
|
71
69
|
|
|
72
|
-
|
|
70
|
+
The `onExpired` callback is called when the captcha challenge has expired. This can occur if the user takes too long to
|
|
71
|
+
complete the challenge.
|
|
73
72
|
|
|
74
|
-
|
|
73
|
+
### onFailed
|
|
75
74
|
|
|
76
|
-
|
|
75
|
+
The `onFailed` callback is called when the user has failed the captcha challenge. This can occur if the user answers the
|
|
76
|
+
challenge incorrectly.
|
|
77
77
|
|
|
78
|
-
|
|
78
|
+
## Add the Procaptcha Widget to your Web page using a React Component
|
|
79
|
+
|
|
80
|
+
You can see Procaptcha being used as a React component in
|
|
81
|
+
our [React Demo](https://github.com/prosopo/captcha/blob/main/demos/client-example/src/App.tsx).
|
|
82
|
+
|
|
83
|
+
The Procaptcha component is called as follows:
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
<Procaptcha
|
|
89
|
+
config={config}
|
|
90
|
+
callbacks={{onAccountNotFound, onError, onHuman, onExpired}}
|
|
91
|
+
/>
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
A config object is required and must contain your SITE_KEY. The callbacks are optional and can be used to handle the
|
|
95
|
+
various Procaptcha events. The following config demonstrates the `REACT_APP_DAPP_SITE_KEY` variable being pulled from
|
|
96
|
+
environment variables.
|
|
97
|
+
|
|
98
|
+
```tsx
|
|
99
|
+
const config: ProcaptchaConfigOptional = {
|
|
100
|
+
account: {
|
|
101
|
+
address: process.env.REACT_APP_DAPP_SITE_KEY || undefined,
|
|
102
|
+
},
|
|
103
|
+
web2: 'true',
|
|
104
|
+
dappName: 'client-example',
|
|
105
|
+
defaultEnvironment: 'rococo',
|
|
106
|
+
networks: {
|
|
107
|
+
rococo: {
|
|
108
|
+
endpoint: 'wss://rococo-contracts-rpc.polkadot.io:443',
|
|
109
|
+
contract: {
|
|
110
|
+
address: '5HiVWQhJrysNcFNEWf2crArKht16zrhro3FcekVWocyQjx5u',
|
|
111
|
+
name: 'prosopo',
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
solutionThreshold: 80,
|
|
116
|
+
}
|
|
117
|
+
```
|
|
79
118
|
|
|
80
|
-
|
|
119
|
+
### Config Options
|
|
81
120
|
|
|
82
|
-
|
|
121
|
+
| Key | Type | Description |
|
|
122
|
+
|--------------------|--------|-----------------------------------------------------------------------------------------|
|
|
123
|
+
| account | string | The SITE_KEY you received when you signed up |
|
|
124
|
+
| web2 | string | Set to `true` to enable web2 support |
|
|
125
|
+
| dappName | string | The name of your application / website |
|
|
126
|
+
| defaultEnvironment | string | The default environment to use - set to `rococo` |
|
|
127
|
+
| networks | object | The networks your application supports - copy paste this from the config above |
|
|
128
|
+
| solutionThreshold | number | The percentage of captcha that a user must have answered correctly to identify as human |
|
|
83
129
|
|
|
84
|
-
|
|
130
|
+
## Verify the User Response Server Side
|
|
85
131
|
|
|
86
|
-
|
|
132
|
+
Please see the main [README](https://github.com/prosopo/captcha/blob/main/README.md) for instructions on how to implement the server side of Procaptcha.
|
|
@@ -3,153 +3,131 @@ const jsxRuntime = require("react/jsx-runtime");
|
|
|
3
3
|
const material = require("@mui/material");
|
|
4
4
|
const CaptchaWidget = require("./CaptchaWidget.cjs");
|
|
5
5
|
const util = require("@prosopo/util");
|
|
6
|
+
const theme = require("./theme.cjs");
|
|
7
|
+
const react = require("react");
|
|
6
8
|
const common = require("@prosopo/common");
|
|
7
9
|
const index = require("../util/index.cjs");
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
+
const CaptchaComponent = ({
|
|
11
|
+
challenge,
|
|
12
|
+
index: index$1,
|
|
13
|
+
solutions,
|
|
14
|
+
onSubmit,
|
|
15
|
+
onCancel,
|
|
16
|
+
onClick,
|
|
17
|
+
onNext,
|
|
18
|
+
themeColor
|
|
19
|
+
}) => {
|
|
10
20
|
const { t } = common.useTranslation();
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
21
|
+
const captcha = challenge.captchas ? util.at(challenge.captchas, index$1) : null;
|
|
22
|
+
const solution = solutions ? util.at(solutions, index$1) : [];
|
|
23
|
+
const theme$1 = react.useMemo(() => themeColor === "light" ? theme.lightTheme : theme.darkTheme, [themeColor]);
|
|
24
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
15
25
|
material.Box,
|
|
16
26
|
{
|
|
17
27
|
sx: {
|
|
18
|
-
//
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
justifyContent: "center",
|
|
22
|
-
// fill entire screen
|
|
28
|
+
// introduce scroll bars when screen < minWidth of children
|
|
29
|
+
overflowX: "auto",
|
|
30
|
+
overflowY: "auto",
|
|
23
31
|
width: "100%",
|
|
24
|
-
|
|
32
|
+
maxWidth: "500px",
|
|
33
|
+
maxHeight: "100%"
|
|
25
34
|
},
|
|
26
|
-
children: /* @__PURE__ */ jsxRuntime.
|
|
35
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
27
36
|
material.Box,
|
|
28
37
|
{
|
|
38
|
+
bgcolor: theme$1.palette.background.default,
|
|
29
39
|
sx: {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
width: "100%",
|
|
34
|
-
// limit the popup width
|
|
35
|
-
maxWidth: "450px",
|
|
36
|
-
// maxHeight introduces vertical scroll bars if children content longer than window
|
|
37
|
-
maxHeight: "100%"
|
|
40
|
+
display: "flex",
|
|
41
|
+
flexDirection: "column",
|
|
42
|
+
minWidth: "300px"
|
|
38
43
|
},
|
|
39
|
-
children:
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
-
),
|
|
76
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
77
|
-
material.Typography,
|
|
78
|
-
{
|
|
79
|
-
px: 1,
|
|
80
|
-
sx: {
|
|
81
|
-
color: "#ffffff",
|
|
82
|
-
fontWeight: 700,
|
|
83
|
-
textTransform: "uppercase",
|
|
84
|
-
fontSize: theme.typography.h6.fontSize
|
|
85
|
-
},
|
|
86
|
-
children: `${util.at(props.challenge.captchas, props.index).captcha.target}`
|
|
87
|
-
}
|
|
88
|
-
)
|
|
89
|
-
]
|
|
90
|
-
}
|
|
91
|
-
),
|
|
92
|
-
/* @__PURE__ */ jsxRuntime.jsx(material.Box, { ...index({ dev: { cy: "captcha-" + props.index } }), children: /* @__PURE__ */ jsxRuntime.jsx(CaptchaWidget.CaptchaWidget, { challenge: captcha, solution, onClick }) }),
|
|
93
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
94
|
-
material.Box,
|
|
95
|
-
{
|
|
96
|
-
px: 2,
|
|
97
|
-
py: 1,
|
|
98
|
-
sx: {
|
|
99
|
-
display: "flex",
|
|
100
|
-
alignItems: "center",
|
|
101
|
-
justifyContent: "center",
|
|
102
|
-
width: "100%"
|
|
103
|
-
},
|
|
104
|
-
...index({ dev: { cy: "dots-captcha" } }),
|
|
105
|
-
children: challenge.captchas.map((_, i) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
106
|
-
material.Box,
|
|
107
|
-
{
|
|
108
|
-
sx: {
|
|
109
|
-
width: 7,
|
|
110
|
-
height: 7,
|
|
111
|
-
borderRadius: "50%",
|
|
112
|
-
border: "1px solid #CFCFCF"
|
|
113
|
-
},
|
|
114
|
-
mx: 0.5,
|
|
115
|
-
bgcolor: index$1 === i ? theme.palette.background.default : "#CFCFCF"
|
|
44
|
+
children: [
|
|
45
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
46
|
+
material.Box,
|
|
47
|
+
{
|
|
48
|
+
px: 2,
|
|
49
|
+
py: 3,
|
|
50
|
+
sx: {
|
|
51
|
+
display: "flex",
|
|
52
|
+
alignItems: "center",
|
|
53
|
+
width: "100%"
|
|
54
|
+
},
|
|
55
|
+
bgcolor: theme$1.palette.primary.main,
|
|
56
|
+
children: [
|
|
57
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
58
|
+
material.Typography,
|
|
59
|
+
{
|
|
60
|
+
sx: {
|
|
61
|
+
color: "#ffffff",
|
|
62
|
+
fontWeight: 700
|
|
63
|
+
},
|
|
64
|
+
children: [
|
|
65
|
+
t("WIDGET.SELECT_ALL"),
|
|
66
|
+
": "
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
),
|
|
70
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
71
|
+
material.Typography,
|
|
72
|
+
{
|
|
73
|
+
px: 1,
|
|
74
|
+
sx: {
|
|
75
|
+
color: "#ffffff",
|
|
76
|
+
fontWeight: 700,
|
|
77
|
+
textTransform: "capitalize",
|
|
78
|
+
fontSize: theme$1.typography.h6.fontSize
|
|
116
79
|
},
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
80
|
+
children: `${util.at(challenge.captchas, index$1).captcha.target}`
|
|
81
|
+
}
|
|
82
|
+
)
|
|
83
|
+
]
|
|
84
|
+
}
|
|
85
|
+
),
|
|
86
|
+
/* @__PURE__ */ jsxRuntime.jsx(material.Box, { ...index({ dev: { cy: "captcha-" + index$1 } }), children: captcha && /* @__PURE__ */ jsxRuntime.jsx(CaptchaWidget.CaptchaWidget, { challenge: captcha, solution, onClick }) }),
|
|
87
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
88
|
+
material.Box,
|
|
89
|
+
{
|
|
90
|
+
px: 2,
|
|
91
|
+
py: 1,
|
|
92
|
+
sx: {
|
|
93
|
+
display: "flex",
|
|
94
|
+
alignItems: "center",
|
|
95
|
+
justifyContent: "center",
|
|
96
|
+
width: "100%"
|
|
97
|
+
},
|
|
98
|
+
...index({ dev: { cy: "dots-captcha" } })
|
|
99
|
+
}
|
|
100
|
+
),
|
|
101
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
102
|
+
material.Box,
|
|
103
|
+
{
|
|
104
|
+
px: 2,
|
|
105
|
+
pt: 0,
|
|
106
|
+
pb: 2,
|
|
107
|
+
sx: {
|
|
108
|
+
display: "flex",
|
|
109
|
+
alignItems: "center",
|
|
110
|
+
justifyContent: "space-between"
|
|
111
|
+
},
|
|
112
|
+
children: [
|
|
113
|
+
/* @__PURE__ */ jsxRuntime.jsx(material.Button, { onClick: onCancel, variant: "text", children: t("WIDGET.CANCEL") }),
|
|
114
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
115
|
+
material.Button,
|
|
116
|
+
{
|
|
117
|
+
color: "primary",
|
|
118
|
+
onClick: index$1 < challenge.captchas.length - 1 ? onNext : onSubmit,
|
|
119
|
+
variant: "contained",
|
|
120
|
+
...index({ dev: { cy: "button-next" } }),
|
|
121
|
+
children: index$1 < challenge.captchas.length - 1 ? t("WIDGET.NEXT") : t("WIDGET.SUBMIT")
|
|
122
|
+
}
|
|
123
|
+
)
|
|
124
|
+
]
|
|
125
|
+
}
|
|
126
|
+
)
|
|
127
|
+
]
|
|
150
128
|
}
|
|
151
129
|
)
|
|
152
130
|
}
|
|
153
|
-
)
|
|
131
|
+
);
|
|
154
132
|
};
|
|
155
133
|
module.exports = CaptchaComponent;
|
|
@@ -13,12 +13,11 @@ const getHash = (item) => {
|
|
|
13
13
|
}
|
|
14
14
|
return item.hash;
|
|
15
15
|
};
|
|
16
|
-
|
|
17
|
-
const { challenge, solution, onClick } = props;
|
|
16
|
+
const CaptchaWidget = ({ challenge, solution, onClick }) => {
|
|
18
17
|
const items = challenge.captcha.items;
|
|
19
18
|
const theme = material.useTheme();
|
|
20
19
|
const isTouchDevice = "ontouchstart" in window;
|
|
21
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
20
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
22
21
|
material.Box,
|
|
23
22
|
{
|
|
24
23
|
component: "div",
|
|
@@ -55,13 +54,13 @@ function CaptchaWidget(props) {
|
|
|
55
54
|
onClick: isTouchDevice ? void 0 : () => onClick(hash),
|
|
56
55
|
onTouchStart: isTouchDevice ? () => onClick(hash) : void 0,
|
|
57
56
|
children: [
|
|
58
|
-
/* @__PURE__ */ jsxRuntime.jsx(material.Box, { sx: { border: 1, borderColor:
|
|
57
|
+
/* @__PURE__ */ jsxRuntime.jsx(material.Box, { sx: { border: 1, borderColor: theme.palette.grey[300] }, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
59
58
|
"img",
|
|
60
59
|
{
|
|
61
60
|
style: {
|
|
62
61
|
width: "100%",
|
|
63
62
|
// image should be full width / height of the item
|
|
64
|
-
backgroundColor:
|
|
63
|
+
backgroundColor: theme.palette.grey[300],
|
|
65
64
|
// colour of the bands when letterboxing and image
|
|
66
65
|
opacity: solution.includes(hash) && isTouchDevice ? "50%" : "100%",
|
|
67
66
|
// iphone workaround
|
|
@@ -136,6 +135,6 @@ function CaptchaWidget(props) {
|
|
|
136
135
|
);
|
|
137
136
|
})
|
|
138
137
|
}
|
|
139
|
-
)
|
|
140
|
-
}
|
|
138
|
+
);
|
|
139
|
+
};
|
|
141
140
|
exports.CaptchaWidget = CaptchaWidget;
|