@prosopo/procaptcha-puzzle 2.10.8 → 2.10.9
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 +53 -0
- package/.turbo/turbo-build$colon$tsc.log +56 -0
- package/.turbo/turbo-build.log +57 -57
- package/CHANGELOG.md +8 -0
- package/dist/cjs/components/ProcaptchaPuzzle.cjs +18 -0
- package/dist/cjs/components/ProcaptchaWidget.cjs +191 -0
- package/dist/cjs/components/PuzzleCanvas.cjs +275 -0
- package/dist/cjs/index.cjs +5 -0
- package/dist/cjs/services/Manager.cjs +286 -0
- package/dist/components/ProcaptchaPuzzle.js +17 -5
- package/dist/components/ProcaptchaWidget.js +181 -144
- package/dist/components/PuzzleCanvas.js +258 -189
- package/dist/index.js +5 -3
- package/dist/services/Manager.js +270 -253
- package/package.json +4 -4
- package/src/components/ProcaptchaPuzzle.tsx +38 -0
- package/src/components/ProcaptchaWidget.tsx +261 -0
- package/src/components/PuzzleCanvas.tsx +336 -0
- package/src/index.ts +15 -0
- package/src/services/Manager.ts +454 -0
- package/tsconfig.cjs.json +44 -0
- package/tsconfig.json +45 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/tsconfig.types.json +9 -0
- package/.turbo/turbo-typecheck.log +0 -4
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const jsxRuntime = require("@emotion/react/jsx-runtime");
|
|
4
|
+
const react = require("react");
|
|
5
|
+
const CONTAINER_WIDTH = 300;
|
|
6
|
+
const CONTAINER_HEIGHT = 200;
|
|
7
|
+
const TARGET_SIZE = 30;
|
|
8
|
+
const PIECE_SIZE = 24;
|
|
9
|
+
const SHAKE_KEYFRAMES = `
|
|
10
|
+
@keyframes prosopo-puzzle-shake {
|
|
11
|
+
0%, 100% { transform: translateX(0); }
|
|
12
|
+
10%, 30%, 50%, 70%, 90% { transform: translateX(-4px); }
|
|
13
|
+
20%, 40%, 60%, 80% { transform: translateX(4px); }
|
|
14
|
+
}
|
|
15
|
+
`;
|
|
16
|
+
const PuzzleCanvas = ({
|
|
17
|
+
originX,
|
|
18
|
+
originY,
|
|
19
|
+
targetX,
|
|
20
|
+
targetY,
|
|
21
|
+
onComplete,
|
|
22
|
+
showRetry,
|
|
23
|
+
submitting
|
|
24
|
+
}) => {
|
|
25
|
+
const [posX, setPosX] = react.useState(originX);
|
|
26
|
+
const [posY, setPosY] = react.useState(originY);
|
|
27
|
+
const isDragging = react.useRef(false);
|
|
28
|
+
const puzzleEvents = react.useRef([]);
|
|
29
|
+
const containerRef = react.useRef(null);
|
|
30
|
+
const offsetRef = react.useRef({ x: 0, y: 0 });
|
|
31
|
+
const [visible, setVisible] = react.useState(false);
|
|
32
|
+
const [shaking, setShaking] = react.useState(false);
|
|
33
|
+
react.useEffect(() => {
|
|
34
|
+
setPosX(originX);
|
|
35
|
+
setPosY(originY);
|
|
36
|
+
}, [originX, originY]);
|
|
37
|
+
react.useEffect(() => {
|
|
38
|
+
const frame = requestAnimationFrame(() => setVisible(true));
|
|
39
|
+
return () => cancelAnimationFrame(frame);
|
|
40
|
+
}, []);
|
|
41
|
+
react.useEffect(() => {
|
|
42
|
+
if (showRetry) {
|
|
43
|
+
setShaking(true);
|
|
44
|
+
const timer = setTimeout(() => setShaking(false), 500);
|
|
45
|
+
return () => clearTimeout(timer);
|
|
46
|
+
}
|
|
47
|
+
return () => {
|
|
48
|
+
};
|
|
49
|
+
}, [showRetry]);
|
|
50
|
+
const clamp = react.useCallback(
|
|
51
|
+
(value, min, max) => {
|
|
52
|
+
return Math.max(min, Math.min(max, value));
|
|
53
|
+
},
|
|
54
|
+
[]
|
|
55
|
+
);
|
|
56
|
+
const getContainerOffset = react.useCallback(() => {
|
|
57
|
+
if (containerRef.current) {
|
|
58
|
+
const rect = containerRef.current.getBoundingClientRect();
|
|
59
|
+
return { x: rect.left, y: rect.top };
|
|
60
|
+
}
|
|
61
|
+
return { x: 0, y: 0 };
|
|
62
|
+
}, []);
|
|
63
|
+
const handleMoveEvent = react.useCallback(
|
|
64
|
+
(clientX, clientY) => {
|
|
65
|
+
if (!isDragging.current) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const containerOffset = getContainerOffset();
|
|
69
|
+
const newX = clamp(
|
|
70
|
+
clientX - containerOffset.x - offsetRef.current.x,
|
|
71
|
+
0,
|
|
72
|
+
CONTAINER_WIDTH
|
|
73
|
+
);
|
|
74
|
+
const newY = clamp(
|
|
75
|
+
clientY - containerOffset.y - offsetRef.current.y,
|
|
76
|
+
0,
|
|
77
|
+
CONTAINER_HEIGHT
|
|
78
|
+
);
|
|
79
|
+
setPosX(newX);
|
|
80
|
+
setPosY(newY);
|
|
81
|
+
puzzleEvents.current.push({ x: newX, y: newY, t: Date.now() });
|
|
82
|
+
},
|
|
83
|
+
[clamp, getContainerOffset]
|
|
84
|
+
);
|
|
85
|
+
const handleEndEvent = react.useCallback(() => {
|
|
86
|
+
if (!isDragging.current) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
isDragging.current = false;
|
|
90
|
+
const currentEvents = [...puzzleEvents.current];
|
|
91
|
+
const lastEvent = currentEvents[currentEvents.length - 1];
|
|
92
|
+
const finalX = lastEvent ? lastEvent.x : originX;
|
|
93
|
+
const finalY = lastEvent ? lastEvent.y : originY;
|
|
94
|
+
onComplete(finalX, finalY, currentEvents);
|
|
95
|
+
}, [onComplete, originX, originY]);
|
|
96
|
+
const handleMouseMove = react.useCallback(
|
|
97
|
+
(event) => {
|
|
98
|
+
handleMoveEvent(event.clientX, event.clientY);
|
|
99
|
+
},
|
|
100
|
+
[handleMoveEvent]
|
|
101
|
+
);
|
|
102
|
+
const handleTouchMove = react.useCallback(
|
|
103
|
+
(event) => {
|
|
104
|
+
const touch = event.touches[0];
|
|
105
|
+
if (touch) {
|
|
106
|
+
handleMoveEvent(touch.clientX, touch.clientY);
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
[handleMoveEvent]
|
|
110
|
+
);
|
|
111
|
+
const handleMouseUp = react.useCallback(() => {
|
|
112
|
+
handleEndEvent();
|
|
113
|
+
}, [handleEndEvent]);
|
|
114
|
+
const handleTouchEnd = react.useCallback(() => {
|
|
115
|
+
handleEndEvent();
|
|
116
|
+
}, [handleEndEvent]);
|
|
117
|
+
react.useEffect(() => {
|
|
118
|
+
document.addEventListener("mousemove", handleMouseMove);
|
|
119
|
+
document.addEventListener("mouseup", handleMouseUp);
|
|
120
|
+
document.addEventListener("touchmove", handleTouchMove);
|
|
121
|
+
document.addEventListener("touchend", handleTouchEnd);
|
|
122
|
+
return () => {
|
|
123
|
+
document.removeEventListener("mousemove", handleMouseMove);
|
|
124
|
+
document.removeEventListener("mouseup", handleMouseUp);
|
|
125
|
+
document.removeEventListener("touchmove", handleTouchMove);
|
|
126
|
+
document.removeEventListener("touchend", handleTouchEnd);
|
|
127
|
+
};
|
|
128
|
+
}, [handleMouseMove, handleMouseUp, handleTouchMove, handleTouchEnd]);
|
|
129
|
+
const handlePieceMouseDown = react.useCallback(
|
|
130
|
+
(event) => {
|
|
131
|
+
if (submitting) return;
|
|
132
|
+
isDragging.current = true;
|
|
133
|
+
puzzleEvents.current = [];
|
|
134
|
+
const containerOffset = getContainerOffset();
|
|
135
|
+
offsetRef.current = {
|
|
136
|
+
x: event.clientX - containerOffset.x - posX,
|
|
137
|
+
y: event.clientY - containerOffset.y - posY
|
|
138
|
+
};
|
|
139
|
+
},
|
|
140
|
+
[getContainerOffset, posX, posY, submitting]
|
|
141
|
+
);
|
|
142
|
+
const handlePieceTouchStart = react.useCallback(
|
|
143
|
+
(event) => {
|
|
144
|
+
if (submitting) return;
|
|
145
|
+
const touch = event.touches[0];
|
|
146
|
+
if (touch) {
|
|
147
|
+
isDragging.current = true;
|
|
148
|
+
puzzleEvents.current = [];
|
|
149
|
+
const containerOffset = getContainerOffset();
|
|
150
|
+
offsetRef.current = {
|
|
151
|
+
x: touch.clientX - containerOffset.x - posX,
|
|
152
|
+
y: touch.clientY - containerOffset.y - posY
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
[getContainerOffset, posX, posY, submitting]
|
|
157
|
+
);
|
|
158
|
+
const instructionText = showRetry ? "Not quite — try again" : "Drag the piece to the target";
|
|
159
|
+
const headerBorderColor = showRetry ? "rgba(220, 53, 69, 0.4)" : "transparent";
|
|
160
|
+
const headerTextColor = showRetry ? "#dc3545" : "#333";
|
|
161
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
162
|
+
"div",
|
|
163
|
+
{
|
|
164
|
+
style: {
|
|
165
|
+
position: "fixed",
|
|
166
|
+
inset: 0,
|
|
167
|
+
zIndex: 2147483646,
|
|
168
|
+
display: "flex",
|
|
169
|
+
alignItems: "center",
|
|
170
|
+
justifyContent: "center",
|
|
171
|
+
backgroundColor: visible ? "rgba(0, 0, 0, 0.4)" : "rgba(0, 0, 0, 0)",
|
|
172
|
+
transition: "background-color 0.3s ease"
|
|
173
|
+
},
|
|
174
|
+
children: [
|
|
175
|
+
/* @__PURE__ */ jsxRuntime.jsx("style", { children: SHAKE_KEYFRAMES }),
|
|
176
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
177
|
+
"div",
|
|
178
|
+
{
|
|
179
|
+
style: {
|
|
180
|
+
display: "flex",
|
|
181
|
+
flexDirection: "column",
|
|
182
|
+
alignItems: "center",
|
|
183
|
+
gap: "0",
|
|
184
|
+
zIndex: 2147483647,
|
|
185
|
+
opacity: visible ? 1 : 0,
|
|
186
|
+
transform: visible ? "scale(1)" : "scale(0.9)",
|
|
187
|
+
transition: "opacity 0.3s ease, transform 0.3s ease",
|
|
188
|
+
animation: shaking ? "prosopo-puzzle-shake 0.5s ease" : "none"
|
|
189
|
+
},
|
|
190
|
+
children: [
|
|
191
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
192
|
+
"div",
|
|
193
|
+
{
|
|
194
|
+
style: {
|
|
195
|
+
backgroundColor: "#fff",
|
|
196
|
+
borderRadius: "8px 8px 0 0",
|
|
197
|
+
padding: "12px 20px",
|
|
198
|
+
width: `${CONTAINER_WIDTH}px`,
|
|
199
|
+
boxSizing: "border-box",
|
|
200
|
+
textAlign: "center",
|
|
201
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
202
|
+
fontSize: "14px",
|
|
203
|
+
fontWeight: 500,
|
|
204
|
+
color: headerTextColor,
|
|
205
|
+
borderBottom: `2px solid ${headerBorderColor}`,
|
|
206
|
+
boxShadow: "0px 11px 15px -7px rgba(0,0,0,0.2), 0px 24px 38px 3px rgba(0,0,0,0.14), 0px 9px 46px 8px rgba(0,0,0,0.12)",
|
|
207
|
+
transition: "color 0.3s ease, border-color 0.3s ease"
|
|
208
|
+
},
|
|
209
|
+
children: instructionText
|
|
210
|
+
}
|
|
211
|
+
),
|
|
212
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
213
|
+
"div",
|
|
214
|
+
{
|
|
215
|
+
ref: containerRef,
|
|
216
|
+
style: {
|
|
217
|
+
position: "relative",
|
|
218
|
+
width: `${CONTAINER_WIDTH}px`,
|
|
219
|
+
height: `${CONTAINER_HEIGHT}px`,
|
|
220
|
+
background: "linear-gradient(135deg, #e8eaf6 0%, #c5cae9 50%, #e8eaf6 100%)",
|
|
221
|
+
borderRadius: "0 0 8px 8px",
|
|
222
|
+
overflow: "hidden",
|
|
223
|
+
userSelect: "none",
|
|
224
|
+
boxShadow: "0px 11px 15px -7px rgba(0,0,0,0.2), 0px 24px 38px 3px rgba(0,0,0,0.14), 0px 9px 46px 8px rgba(0,0,0,0.12)",
|
|
225
|
+
opacity: submitting ? 0.6 : 1,
|
|
226
|
+
pointerEvents: submitting ? "none" : "auto",
|
|
227
|
+
transition: "opacity 0.2s ease"
|
|
228
|
+
},
|
|
229
|
+
children: [
|
|
230
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
231
|
+
"div",
|
|
232
|
+
{
|
|
233
|
+
style: {
|
|
234
|
+
position: "absolute",
|
|
235
|
+
left: `${targetX - TARGET_SIZE / 2}px`,
|
|
236
|
+
top: `${targetY - TARGET_SIZE / 2}px`,
|
|
237
|
+
width: `${TARGET_SIZE}px`,
|
|
238
|
+
height: `${TARGET_SIZE}px`,
|
|
239
|
+
borderRadius: "50%",
|
|
240
|
+
border: "2px dashed rgba(74, 144, 217, 0.6)",
|
|
241
|
+
backgroundColor: "rgba(74, 144, 217, 0.08)",
|
|
242
|
+
boxSizing: "border-box"
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
),
|
|
246
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
247
|
+
"div",
|
|
248
|
+
{
|
|
249
|
+
onMouseDown: handlePieceMouseDown,
|
|
250
|
+
onTouchStart: handlePieceTouchStart,
|
|
251
|
+
style: {
|
|
252
|
+
position: "absolute",
|
|
253
|
+
left: `${posX - PIECE_SIZE / 2}px`,
|
|
254
|
+
top: `${posY - PIECE_SIZE / 2}px`,
|
|
255
|
+
width: `${PIECE_SIZE}px`,
|
|
256
|
+
height: `${PIECE_SIZE}px`,
|
|
257
|
+
borderRadius: "50%",
|
|
258
|
+
background: "radial-gradient(circle at 40% 40%, #6ab0ff, #4a90d9)",
|
|
259
|
+
cursor: submitting ? "default" : isDragging.current ? "grabbing" : "grab",
|
|
260
|
+
boxShadow: isDragging.current ? "0 4px 12px rgba(74, 144, 217, 0.5)" : "0 2px 6px rgba(74, 144, 217, 0.3)",
|
|
261
|
+
transition: isDragging.current ? "none" : "box-shadow 0.2s ease, left 0.3s ease, top 0.3s ease"
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
)
|
|
265
|
+
]
|
|
266
|
+
}
|
|
267
|
+
)
|
|
268
|
+
]
|
|
269
|
+
}
|
|
270
|
+
)
|
|
271
|
+
]
|
|
272
|
+
}
|
|
273
|
+
);
|
|
274
|
+
};
|
|
275
|
+
exports.PuzzleCanvas = PuzzleCanvas;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
require("./components/ProcaptchaWidget.cjs");
|
|
4
|
+
const ProcaptchaPuzzle = require("./components/ProcaptchaPuzzle.cjs");
|
|
5
|
+
exports.ProcaptchaPuzzle = ProcaptchaPuzzle.ProcaptchaPuzzle;
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const string = require("@polkadot/util/string");
|
|
4
|
+
const api = require("@prosopo/api");
|
|
5
|
+
const common = require("@prosopo/common");
|
|
6
|
+
const procaptchaCommon = require("@prosopo/procaptcha-common");
|
|
7
|
+
const types = require("@prosopo/types");
|
|
8
|
+
const util = require("@prosopo/util");
|
|
9
|
+
const utilCrypto = require("@prosopo/util-crypto");
|
|
10
|
+
const Manager = (configInput, state, onStateUpdate, callbacks, frictionlessState, getHoneypotValue) => {
|
|
11
|
+
const events = procaptchaCommon.getDefaultEvents(callbacks);
|
|
12
|
+
let storedChallengeResponse;
|
|
13
|
+
let storedProviderApi;
|
|
14
|
+
let storedProviderUrl;
|
|
15
|
+
let storedUser;
|
|
16
|
+
let storedClickX;
|
|
17
|
+
let storedClickY;
|
|
18
|
+
const defaultState = () => {
|
|
19
|
+
return {
|
|
20
|
+
// note order matters! see buildUpdateState. These fields are set in order, so disable modal first, then set loading to false, etc.
|
|
21
|
+
showModal: false,
|
|
22
|
+
loading: false,
|
|
23
|
+
index: 0,
|
|
24
|
+
challenge: void 0,
|
|
25
|
+
solutions: void 0,
|
|
26
|
+
isHuman: false,
|
|
27
|
+
captchaApi: void 0,
|
|
28
|
+
account: void 0
|
|
29
|
+
// don't handle timeout here, this should be handled by the state management
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
const clearTimeout = () => {
|
|
33
|
+
window.clearTimeout(Number(state.timeout));
|
|
34
|
+
updateState({ timeout: void 0 });
|
|
35
|
+
};
|
|
36
|
+
const onFailed = () => {
|
|
37
|
+
updateState({
|
|
38
|
+
isHuman: false,
|
|
39
|
+
loading: false
|
|
40
|
+
});
|
|
41
|
+
events.onFailed();
|
|
42
|
+
resetState(frictionlessState?.restart);
|
|
43
|
+
};
|
|
44
|
+
const clearSuccessfulChallengeTimeout = () => {
|
|
45
|
+
window.clearTimeout(Number(state.successfullChallengeTimeout));
|
|
46
|
+
updateState({ successfullChallengeTimeout: void 0 });
|
|
47
|
+
};
|
|
48
|
+
const getConfig = () => {
|
|
49
|
+
const config = {
|
|
50
|
+
userAccountAddress: configInput.userAccountAddress || "",
|
|
51
|
+
...configInput
|
|
52
|
+
};
|
|
53
|
+
if (state.account) {
|
|
54
|
+
config.userAccountAddress = state.account.account.address;
|
|
55
|
+
}
|
|
56
|
+
return types.ProcaptchaConfigSchema.parse(config);
|
|
57
|
+
};
|
|
58
|
+
const getAccount = () => {
|
|
59
|
+
if (!state.account) {
|
|
60
|
+
throw new common.ProsopoEnvError("GENERAL.ACCOUNT_NOT_FOUND", {
|
|
61
|
+
context: { error: "Account not loaded" }
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
const account = state.account;
|
|
65
|
+
return { account };
|
|
66
|
+
};
|
|
67
|
+
const getDappAccount = () => {
|
|
68
|
+
if (!state.dappAccount) {
|
|
69
|
+
throw new common.ProsopoEnvError("GENERAL.SITE_KEY_MISSING");
|
|
70
|
+
}
|
|
71
|
+
const dappAccount = state.dappAccount;
|
|
72
|
+
return dappAccount;
|
|
73
|
+
};
|
|
74
|
+
const updateState = procaptchaCommon.buildUpdateState(state, onStateUpdate);
|
|
75
|
+
const resetState = (frictionlessRestart) => {
|
|
76
|
+
clearTimeout();
|
|
77
|
+
clearSuccessfulChallengeTimeout();
|
|
78
|
+
updateState(defaultState());
|
|
79
|
+
events.onReset();
|
|
80
|
+
if (frictionlessRestart) {
|
|
81
|
+
frictionlessRestart();
|
|
82
|
+
}
|
|
83
|
+
storedChallengeResponse = void 0;
|
|
84
|
+
storedProviderApi = void 0;
|
|
85
|
+
storedProviderUrl = void 0;
|
|
86
|
+
storedUser = void 0;
|
|
87
|
+
storedClickX = void 0;
|
|
88
|
+
storedClickY = void 0;
|
|
89
|
+
};
|
|
90
|
+
const setValidChallengeTimeout = () => {
|
|
91
|
+
const timeMillis = getConfig().captchas.puzzle.solutionTimeout;
|
|
92
|
+
const successfullChallengeTimeout = setTimeout(() => {
|
|
93
|
+
updateState({ isHuman: false });
|
|
94
|
+
events.onExpired();
|
|
95
|
+
resetState(frictionlessState?.restart);
|
|
96
|
+
}, timeMillis);
|
|
97
|
+
updateState({ successfullChallengeTimeout });
|
|
98
|
+
};
|
|
99
|
+
const start = async (x = 0, y = 0) => {
|
|
100
|
+
storedClickX = x;
|
|
101
|
+
storedClickY = y;
|
|
102
|
+
await procaptchaCommon.providerRetry(
|
|
103
|
+
async () => {
|
|
104
|
+
if (state.loading) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (state.isHuman) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
resetState();
|
|
111
|
+
updateState({
|
|
112
|
+
loading: true
|
|
113
|
+
});
|
|
114
|
+
updateState({ attemptCount: state.attemptCount + 1 });
|
|
115
|
+
const config = getConfig();
|
|
116
|
+
const selectAccount = async () => {
|
|
117
|
+
if (frictionlessState) {
|
|
118
|
+
return frictionlessState.userAccount;
|
|
119
|
+
}
|
|
120
|
+
const ext = new (await procaptchaCommon.ExtensionLoader(config.web2))();
|
|
121
|
+
return ext.getAccount(config);
|
|
122
|
+
};
|
|
123
|
+
const user = await selectAccount();
|
|
124
|
+
const userAccount = user.account.address;
|
|
125
|
+
updateState({
|
|
126
|
+
account: { account: { address: userAccount } }
|
|
127
|
+
});
|
|
128
|
+
updateState({ dappAccount: config.account.address });
|
|
129
|
+
await util.sleep(100);
|
|
130
|
+
if (!config.web2 && !config.userAccountAddress) {
|
|
131
|
+
throw new common.ProsopoEnvError("GENERAL.ACCOUNT_NOT_FOUND", {
|
|
132
|
+
context: {
|
|
133
|
+
error: "Account address has not been set for web3 mode"
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
let getRandomProviderResponse = void 0;
|
|
138
|
+
if (frictionlessState?.provider) {
|
|
139
|
+
getRandomProviderResponse = frictionlessState.provider;
|
|
140
|
+
} else {
|
|
141
|
+
getRandomProviderResponse = await procaptchaCommon.getProcaptchaRandomActiveProvider(
|
|
142
|
+
getConfig().defaultEnvironment
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
const providerUrl = getRandomProviderResponse.provider.url;
|
|
146
|
+
const providerApi = new api.ProviderApi(providerUrl, getDappAccount());
|
|
147
|
+
const simdReadingsOnChallenge = frictionlessState?.getSimdReadings ? await frictionlessState.getSimdReadings(0) : void 0;
|
|
148
|
+
const challenge = await providerApi.getPuzzleCaptchaChallenge(
|
|
149
|
+
userAccount,
|
|
150
|
+
getDappAccount(),
|
|
151
|
+
frictionlessState?.sessionId,
|
|
152
|
+
simdReadingsOnChallenge
|
|
153
|
+
);
|
|
154
|
+
if (challenge.error) {
|
|
155
|
+
updateState({
|
|
156
|
+
loading: false,
|
|
157
|
+
error: {
|
|
158
|
+
message: challenge.error.message,
|
|
159
|
+
key: challenge.error.key || "API.UNKNOWN_ERROR"
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
storedChallengeResponse = challenge;
|
|
165
|
+
storedProviderApi = providerApi;
|
|
166
|
+
storedProviderUrl = providerUrl;
|
|
167
|
+
storedUser = user;
|
|
168
|
+
updateState({
|
|
169
|
+
loading: false
|
|
170
|
+
});
|
|
171
|
+
},
|
|
172
|
+
async () => {
|
|
173
|
+
await start();
|
|
174
|
+
},
|
|
175
|
+
() => {
|
|
176
|
+
resetState();
|
|
177
|
+
},
|
|
178
|
+
state.attemptCount,
|
|
179
|
+
3
|
|
180
|
+
);
|
|
181
|
+
return storedChallengeResponse;
|
|
182
|
+
};
|
|
183
|
+
const submitSolution = async (finalX, finalY, puzzleEvents) => {
|
|
184
|
+
if (!storedChallengeResponse || !storedProviderApi || !storedProviderUrl || !storedUser) {
|
|
185
|
+
throw new common.ProsopoEnvError("GENERAL.ACCOUNT_NOT_FOUND", {
|
|
186
|
+
context: { error: "No challenge data available. Call start() first." }
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
updateState({ loading: true });
|
|
190
|
+
try {
|
|
191
|
+
const challenge = storedChallengeResponse;
|
|
192
|
+
const providerApi = storedProviderApi;
|
|
193
|
+
const providerUrl = storedProviderUrl;
|
|
194
|
+
const user = storedUser;
|
|
195
|
+
const config = getConfig();
|
|
196
|
+
const signer = user.extension?.signer;
|
|
197
|
+
if (!signer || !signer.signRaw) {
|
|
198
|
+
throw new common.ProsopoEnvError("GENERAL.CANT_FIND_KEYRINGPAIR", {
|
|
199
|
+
context: {
|
|
200
|
+
error: "Signer is not defined, cannot sign message to prove account ownership"
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
const userTimestampSignature = await signer.signRaw({
|
|
205
|
+
address: user.account.address,
|
|
206
|
+
data: string.stringToHex(challenge[types.ApiParams.timestamp].toString()),
|
|
207
|
+
type: "bytes"
|
|
208
|
+
});
|
|
209
|
+
let encryptedBehavioralData;
|
|
210
|
+
if (frictionlessState?.encryptBehavioralData && (frictionlessState?.behaviorCollector1 || frictionlessState?.behaviorCollector2 || frictionlessState?.behaviorCollector3)) {
|
|
211
|
+
try {
|
|
212
|
+
const behavioralData = {
|
|
213
|
+
collector1: frictionlessState.behaviorCollector1?.getData() || [],
|
|
214
|
+
collector2: frictionlessState.behaviorCollector2?.getData() || [],
|
|
215
|
+
collector3: frictionlessState.behaviorCollector3?.getData() || [],
|
|
216
|
+
deviceCapability: frictionlessState.deviceCapability || "unknown"
|
|
217
|
+
};
|
|
218
|
+
const dataToEncrypt = frictionlessState.packBehavioralData ? frictionlessState.packBehavioralData(behavioralData) : behavioralData;
|
|
219
|
+
encryptedBehavioralData = await frictionlessState.encryptBehavioralData(
|
|
220
|
+
JSON.stringify(dataToEncrypt)
|
|
221
|
+
);
|
|
222
|
+
} catch {
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
let salt;
|
|
226
|
+
if (storedClickX !== void 0 && storedClickY !== void 0) {
|
|
227
|
+
const coords = [storedClickX, storedClickY];
|
|
228
|
+
const randomSalt = utilCrypto.randomAsHex(
|
|
229
|
+
coords.map((coord) => coord.toString(16).length + 4).reduce((acc, curr) => acc + curr, 0)
|
|
230
|
+
);
|
|
231
|
+
salt = util.embedData(randomSalt, coords);
|
|
232
|
+
}
|
|
233
|
+
const simdReadings = frictionlessState?.getSimdReadings ? await frictionlessState.getSimdReadings() : void 0;
|
|
234
|
+
const hpValue = getHoneypotValue?.();
|
|
235
|
+
const clientMetaData = hpValue ? { hp: hpValue } : void 0;
|
|
236
|
+
const verifiedSolution = await providerApi.submitPuzzleCaptchaSolution(
|
|
237
|
+
challenge,
|
|
238
|
+
getAccount().account.account.address,
|
|
239
|
+
getDappAccount(),
|
|
240
|
+
finalX,
|
|
241
|
+
finalY,
|
|
242
|
+
puzzleEvents,
|
|
243
|
+
userTimestampSignature.signature.toString(),
|
|
244
|
+
config.captchas.puzzle.verifiedTimeout,
|
|
245
|
+
encryptedBehavioralData,
|
|
246
|
+
salt,
|
|
247
|
+
simdReadings,
|
|
248
|
+
clientMetaData
|
|
249
|
+
);
|
|
250
|
+
if (verifiedSolution[types.ApiParams.verified]) {
|
|
251
|
+
updateState({
|
|
252
|
+
isHuman: true,
|
|
253
|
+
loading: false
|
|
254
|
+
});
|
|
255
|
+
events.onHuman(
|
|
256
|
+
types.encodeProcaptchaOutput({
|
|
257
|
+
[types.ApiParams.providerUrl]: providerUrl,
|
|
258
|
+
[types.ApiParams.user]: getAccount().account.account.address,
|
|
259
|
+
[types.ApiParams.dapp]: getDappAccount(),
|
|
260
|
+
[types.ApiParams.challenge]: challenge.challenge,
|
|
261
|
+
[types.ApiParams.timestamp]: challenge.timestamp,
|
|
262
|
+
[types.ApiParams.signature]: {
|
|
263
|
+
[types.ApiParams.provider]: challenge.signature.provider,
|
|
264
|
+
[types.ApiParams.user]: {
|
|
265
|
+
[types.ApiParams.timestamp]: userTimestampSignature.signature.toString()
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
})
|
|
269
|
+
);
|
|
270
|
+
setValidChallengeTimeout();
|
|
271
|
+
return true;
|
|
272
|
+
}
|
|
273
|
+
onFailed();
|
|
274
|
+
return false;
|
|
275
|
+
} catch (error) {
|
|
276
|
+
updateState({ loading: false });
|
|
277
|
+
throw error;
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
return {
|
|
281
|
+
start,
|
|
282
|
+
submitSolution,
|
|
283
|
+
resetState
|
|
284
|
+
};
|
|
285
|
+
};
|
|
286
|
+
exports.Manager = Manager;
|
|
@@ -1,5 +1,17 @@
|
|
|
1
|
-
import { jsx
|
|
2
|
-
import {
|
|
3
|
-
const ProcaptchaWidget = lazy(
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import { jsx } from "@emotion/react/jsx-runtime";
|
|
2
|
+
import { lazy, Suspense } from "react";
|
|
3
|
+
const ProcaptchaWidget = lazy(
|
|
4
|
+
async () => import("./ProcaptchaWidget.js")
|
|
5
|
+
);
|
|
6
|
+
const ProcaptchaPuzzle = (props) => /* @__PURE__ */ jsx(Suspense, { children: /* @__PURE__ */ jsx(
|
|
7
|
+
ProcaptchaWidget,
|
|
8
|
+
{
|
|
9
|
+
config: props.config,
|
|
10
|
+
callbacks: props.callbacks,
|
|
11
|
+
frictionlessState: props.frictionlessState,
|
|
12
|
+
i18n: props.i18n
|
|
13
|
+
}
|
|
14
|
+
) });
|
|
15
|
+
export {
|
|
16
|
+
ProcaptchaPuzzle
|
|
17
|
+
};
|