@prosopo/procaptcha-frictionless 2.13.14 → 2.13.16
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 +4 -4
- package/.turbo/turbo-build$colon$tsc.log +24 -24
- package/.turbo/turbo-build.log +5 -5
- package/CHANGELOG.md +28 -0
- package/dist/ProcaptchaFrictionless.d.ts +1 -1
- package/dist/ProcaptchaFrictionless.d.ts.map +1 -1
- package/dist/ProcaptchaFrictionless.js +19 -6
- package/dist/ProcaptchaFrictionless.js.map +1 -1
- package/dist/cjs/ProcaptchaFrictionless.cjs +18 -5
- package/dist/cjs/sessionInvalidatedRecovery.cjs +21 -10
- package/dist/sessionInvalidatedRecovery.d.ts +1 -0
- package/dist/sessionInvalidatedRecovery.d.ts.map +1 -1
- package/dist/sessionInvalidatedRecovery.js +21 -11
- package/dist/sessionInvalidatedRecovery.js.map +1 -1
- package/dist/tests/sessionInvalidatedRecovery.test.js +19 -1
- package/dist/tests/sessionInvalidatedRecovery.test.js.map +1 -1
- package/package.json +11 -11
- package/src/ProcaptchaFrictionless.tsx +36 -1
- package/src/sessionInvalidatedRecovery.ts +21 -9
- package/src/tests/sessionInvalidatedRecovery.test.ts +24 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
type RetryCoords,
|
|
36
36
|
consumeRetryMountProps,
|
|
37
37
|
handleSessionInvalidated,
|
|
38
|
+
normaliseRetryCoords,
|
|
38
39
|
} from "./sessionInvalidatedRecovery.js";
|
|
39
40
|
|
|
40
41
|
// Each session uses exactly one solver — chosen by the /frictionless response.
|
|
@@ -105,6 +106,16 @@ export const ProcaptchaFrictionless = ({
|
|
|
105
106
|
// After we've retried once, a second NO_SESSION_FOUND falls back to the
|
|
106
107
|
// inner widget's own `frictionlessState.restart()` path.
|
|
107
108
|
const sessionInvalidatedFiredRef = useRef(false);
|
|
109
|
+
// Bumped on every mount so the replacement widget gets a fresh React
|
|
110
|
+
// `key`. Without it a re-render for the same captcha type reconciles onto
|
|
111
|
+
// the existing element, and the inner widget keeps the manager it built on
|
|
112
|
+
// first mount — still closed over the sessionId we've just replaced.
|
|
113
|
+
const mountCountRef = useRef(0);
|
|
114
|
+
// Set when the next mount must open its challenge without waiting for a
|
|
115
|
+
// checkbox click. Held in a ref rather than passed down through `start()`
|
|
116
|
+
// because `providerRetry` re-invokes `start` with no arguments, which would
|
|
117
|
+
// otherwise drop the flag on the first provider retry.
|
|
118
|
+
const nextMountAutoStartRef = useRef(false);
|
|
108
119
|
|
|
109
120
|
useEffect(() => {
|
|
110
121
|
if (!config.language) return;
|
|
@@ -215,6 +226,19 @@ export const ProcaptchaFrictionless = ({
|
|
|
215
226
|
void start();
|
|
216
227
|
};
|
|
217
228
|
|
|
229
|
+
// The user pressed reload on the challenge. The provider consumed this
|
|
230
|
+
// session when it issued the challenge, so there is no way to ask it
|
|
231
|
+
// for another one — mint a new session by re-running frictionless and
|
|
232
|
+
// re-mount the widget with `autoStart`, which is what makes a new
|
|
233
|
+
// challenge appear instead of the modal simply closing. Not one-shot:
|
|
234
|
+
// the user may keep asking for a different challenge.
|
|
235
|
+
const onReload = (x?: number, y?: number) => {
|
|
236
|
+
pendingRetryCoordsRef.current = normaliseRetryCoords(x, y);
|
|
237
|
+
nextMountAutoStartRef.current = true;
|
|
238
|
+
resetState(0);
|
|
239
|
+
void start();
|
|
240
|
+
};
|
|
241
|
+
|
|
218
242
|
// Consume any pending retry coords now — the resumed widget owns them
|
|
219
243
|
// for exactly one auto-fired `manager.start(x, y)`. Cleared so a
|
|
220
244
|
// subsequent escalation/re-render doesn't accidentally re-inject.
|
|
@@ -222,14 +246,22 @@ export const ProcaptchaFrictionless = ({
|
|
|
222
246
|
// over pending retry coords when both are present, because escalation
|
|
223
247
|
// is the current transition and the pending retry belongs to a prior
|
|
224
248
|
// widget instance that never got to consume them.
|
|
249
|
+
const forcedAutoStart = nextMountAutoStartRef.current;
|
|
250
|
+
nextMountAutoStartRef.current = false;
|
|
225
251
|
const { autoStart: resumedAutoStart, startCoords: retryStartCoords } =
|
|
226
|
-
consumeRetryMountProps(
|
|
252
|
+
consumeRetryMountProps(
|
|
253
|
+
pendingRetryCoordsRef,
|
|
254
|
+
autoStart || forcedAutoStart,
|
|
255
|
+
);
|
|
227
256
|
const startCoords = escalationCoords ?? retryStartCoords;
|
|
257
|
+
mountCountRef.current += 1;
|
|
258
|
+
const mountKey = mountCountRef.current;
|
|
228
259
|
|
|
229
260
|
if (captchaType === CaptchaType.image) {
|
|
230
261
|
const Procaptcha = await ProcaptchaLoader();
|
|
231
262
|
setComponentToRender(
|
|
232
263
|
<Procaptcha
|
|
264
|
+
key={mountKey}
|
|
233
265
|
config={config}
|
|
234
266
|
callbacks={callbacks}
|
|
235
267
|
frictionlessState={frictionlessState}
|
|
@@ -237,12 +269,14 @@ export const ProcaptchaFrictionless = ({
|
|
|
237
269
|
autoStart={resumedAutoStart}
|
|
238
270
|
startCoords={startCoords}
|
|
239
271
|
onSessionInvalidated={onSessionInvalidated}
|
|
272
|
+
onReload={onReload}
|
|
240
273
|
/>,
|
|
241
274
|
);
|
|
242
275
|
} else if (captchaType === CaptchaType.puzzle) {
|
|
243
276
|
const ProcaptchaPuzzle = await ProcaptchaPuzzleLoader();
|
|
244
277
|
setComponentToRender(
|
|
245
278
|
<ProcaptchaPuzzle
|
|
279
|
+
key={mountKey}
|
|
246
280
|
config={config}
|
|
247
281
|
callbacks={callbacks}
|
|
248
282
|
frictionlessState={frictionlessState}
|
|
@@ -256,6 +290,7 @@ export const ProcaptchaFrictionless = ({
|
|
|
256
290
|
const ProcaptchaPow = await ProcaptchaPowLoader();
|
|
257
291
|
setComponentToRender(
|
|
258
292
|
<ProcaptchaPow
|
|
293
|
+
key={mountKey}
|
|
259
294
|
config={config}
|
|
260
295
|
callbacks={callbacks}
|
|
261
296
|
frictionlessState={frictionlessState}
|
|
@@ -26,13 +26,10 @@ export type RetryCoords = { x: number; y: number };
|
|
|
26
26
|
export type MutableRef<T> = { current: T };
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* the passed refs to record the one-shot fire + pending coords.
|
|
29
|
+
* The checkbox click position a re-mounted widget should start from, or
|
|
30
|
+
* `null` when there isn't a real one to carry over.
|
|
32
31
|
*
|
|
33
|
-
* -
|
|
34
|
-
* persistently broken session doesn't loop.
|
|
35
|
-
* - Coords are recorded only for a real trusted checkbox click. A partial
|
|
32
|
+
* - Coords are kept only for a real trusted checkbox click. A partial
|
|
36
33
|
* pair (only x or only y numeric) is treated as "no coords" so we
|
|
37
34
|
* never accidentally embed `NaN` into the solution salt.
|
|
38
35
|
* - `(0, 0)` is treated as "no coords" too — that's what the widgets
|
|
@@ -42,6 +39,23 @@ export type MutableRef<T> = { current: T };
|
|
|
42
39
|
* is identical; we discard the pair here so future readers can tell
|
|
43
40
|
* the two apart.
|
|
44
41
|
*/
|
|
42
|
+
export const normaliseRetryCoords = (
|
|
43
|
+
x: number | undefined,
|
|
44
|
+
y: number | undefined,
|
|
45
|
+
): RetryCoords | null => {
|
|
46
|
+
if (typeof x !== "number" || typeof y !== "number") return null;
|
|
47
|
+
if (x === 0 && y === 0) return null;
|
|
48
|
+
return { x, y };
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Semantics of the outer recovery handler. Returns whether the caller
|
|
53
|
+
* should proceed to re-run the frictionless flow (`start()`), and mutates
|
|
54
|
+
* the passed refs to record the one-shot fire + pending coords.
|
|
55
|
+
*
|
|
56
|
+
* Second calls are ignored (one-shot per outer widget lifetime) so a
|
|
57
|
+
* persistently broken session doesn't loop.
|
|
58
|
+
*/
|
|
45
59
|
export const handleSessionInvalidated = (
|
|
46
60
|
x: number | undefined,
|
|
47
61
|
y: number | undefined,
|
|
@@ -50,9 +64,7 @@ export const handleSessionInvalidated = (
|
|
|
50
64
|
): { shouldRestart: boolean } => {
|
|
51
65
|
if (firedRef.current) return { shouldRestart: false };
|
|
52
66
|
firedRef.current = true;
|
|
53
|
-
|
|
54
|
-
const isRealClick = bothNumeric && (x !== 0 || y !== 0);
|
|
55
|
-
pendingCoordsRef.current = isRealClick ? { x, y } : null;
|
|
67
|
+
pendingCoordsRef.current = normaliseRetryCoords(x, y);
|
|
56
68
|
return { shouldRestart: true };
|
|
57
69
|
};
|
|
58
70
|
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
type RetryCoords,
|
|
19
19
|
consumeRetryMountProps,
|
|
20
20
|
handleSessionInvalidated,
|
|
21
|
+
normaliseRetryCoords,
|
|
21
22
|
} from "../sessionInvalidatedRecovery.js";
|
|
22
23
|
|
|
23
24
|
const ref = <T>(initial: T): MutableRef<T> => ({ current: initial });
|
|
@@ -127,3 +128,26 @@ describe("consumeRetryMountProps", () => {
|
|
|
127
128
|
expect(mount).toEqual({ autoStart: false, startCoords: undefined });
|
|
128
129
|
});
|
|
129
130
|
});
|
|
131
|
+
|
|
132
|
+
describe("normaliseRetryCoords", () => {
|
|
133
|
+
it("keeps a real click", () => {
|
|
134
|
+
expect(normaliseRetryCoords(120, 340)).toEqual({ x: 120, y: 340 });
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("keeps a click that sits on one axis", () => {
|
|
138
|
+
expect(normaliseRetryCoords(0, 340)).toEqual({ x: 0, y: 340 });
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("drops (0, 0) — the autoStart / untrusted-event default, not a click", () => {
|
|
142
|
+
expect(normaliseRetryCoords(0, 0)).toBeNull();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("drops a half-pair rather than letting NaN reach the salt", () => {
|
|
146
|
+
expect(normaliseRetryCoords(120, undefined)).toBeNull();
|
|
147
|
+
expect(normaliseRetryCoords(undefined, 340)).toBeNull();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("drops a missing pair", () => {
|
|
151
|
+
expect(normaliseRetryCoords(undefined, undefined)).toBeNull();
|
|
152
|
+
});
|
|
153
|
+
});
|