@prosopo/procaptcha-puzzle 2.15.0 → 2.17.0
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 +5 -5
- package/.turbo/turbo-build$colon$tsc.log +18 -18
- package/.turbo/turbo-build.log +5 -5
- package/CHANGELOG.md +55 -0
- package/dist/cjs/components/procaptchaPuzzle.cjs +2 -17
- package/dist/cjs/components/procaptchaWidget.cjs +26 -19
- package/dist/cjs/components/puzzleCanvas.cjs +54 -3
- package/dist/cjs/services/Manager.cjs +12 -46
- package/dist/components/procaptchaPuzzle.d.ts +3 -3
- package/dist/components/procaptchaPuzzle.d.ts.map +1 -1
- package/dist/components/procaptchaPuzzle.js +2 -17
- package/dist/components/procaptchaPuzzle.js.map +1 -1
- package/dist/components/procaptchaWidget.d.ts.map +1 -1
- package/dist/components/procaptchaWidget.js +25 -18
- package/dist/components/procaptchaWidget.js.map +1 -1
- package/dist/components/puzzleCanvas.d.ts +4 -1
- package/dist/components/puzzleCanvas.d.ts.map +1 -1
- package/dist/components/puzzleCanvas.js +55 -4
- package/dist/components/puzzleCanvas.js.map +1 -1
- package/dist/services/Manager.d.ts +3 -1
- package/dist/services/Manager.d.ts.map +1 -1
- package/dist/services/Manager.js +13 -47
- package/dist/services/Manager.js.map +1 -1
- package/dist/tests/manager.unit.test.js +8 -1
- package/dist/tests/manager.unit.test.js.map +1 -1
- package/dist/tests/procaptchaWidget.unit.test.js +87 -2
- package/dist/tests/procaptchaWidget.unit.test.js.map +1 -1
- package/dist/tests/puzzleCanvas.unit.test.js +94 -0
- package/dist/tests/puzzleCanvas.unit.test.js.map +1 -1
- package/package.json +6 -6
|
@@ -43,10 +43,10 @@ Bundle build started
|
|
|
43
43
|
✓ 6 modules transformed.
|
|
44
44
|
rendering chunks...
|
|
45
45
|
computing gzip size...
|
|
46
|
+
dist/cjs/components/procaptchaPuzzle.cjs 0.47 kB │ gzip: 0.22 kB
|
|
46
47
|
dist/cjs/index.cjs 0.60 kB │ gzip: 0.21 kB
|
|
47
|
-
dist/cjs/
|
|
48
|
-
dist/cjs/components/procaptchaWidget.cjs 8.
|
|
49
|
-
dist/cjs/
|
|
50
|
-
dist/cjs/components/puzzleCanvas.cjs 14.77 kB │ gzip: 4.39 kB
|
|
48
|
+
dist/cjs/services/Manager.cjs 8.66 kB │ gzip: 2.45 kB
|
|
49
|
+
dist/cjs/components/procaptchaWidget.cjs 8.86 kB │ gzip: 2.40 kB
|
|
50
|
+
dist/cjs/components/puzzleCanvas.cjs 16.87 kB │ gzip: 4.79 kB
|
|
51
51
|
|
|
52
|
-
[32m✓ built in
|
|
52
|
+
[32m✓ built in 85ms[39m
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
$ tsc --build --verbose
|
|
2
|
-
|
|
2
|
+
11:33:49 AM - Projects in this build:
|
|
3
3
|
* ../../dev/config/tsconfig.json
|
|
4
4
|
* ../util/tsconfig.json
|
|
5
5
|
* ../locale/tsconfig.json
|
|
@@ -17,37 +17,37 @@ $ tsc --build --verbose
|
|
|
17
17
|
* ../procaptcha-common/tsconfig.json
|
|
18
18
|
* tsconfig.json
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
11:33:49 AM - Project '../../dev/config/tsconfig.json' is up to date because newest input '../../dev/config/src/webpack/webpack.config.ts' is older than output '../../dev/config/tsconfig.tsbuildinfo'
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
11:33:49 AM - Project '../util/tsconfig.json' is up to date because newest input '../util/src/url.ts' is older than output '../util/tsconfig.tsbuildinfo'
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
11:33:49 AM - Project '../locale/tsconfig.json' is up to date because newest input '../locale/src/translationKey.ts' is older than output '../locale/tsconfig.tsbuildinfo'
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
11:33:49 AM - Project '../util-crypto/tsconfig.json' is up to date because newest input '../util-crypto/src/types.ts' is older than output '../util-crypto/tsconfig.tsbuildinfo'
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
11:33:49 AM - Project '../types/tsconfig.json' is up to date because newest input '../types/src/types.test-d.ts' is older than output '../types/tsconfig.tsbuildinfo'
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
11:33:49 AM - Project '../api/tsconfig.json' is up to date because newest input '../api/src/tests/apiHarness.ts' is older than output '../api/tsconfig.tsbuildinfo'
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
11:33:49 AM - Project '../logger/tsconfig.json' is up to date because newest input '../logger/src/index.ts' is older than output '../logger/tsconfig.tsbuildinfo'
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
11:33:49 AM - Project '../common/tsconfig.json' is up to date because newest input '../common/src/error.ts' is older than output '../common/tsconfig.tsbuildinfo'
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
11:33:50 AM - Project '../load-balancer/tsconfig.json' is up to date because newest input '../load-balancer/src/balancer.ts' is older than output '../load-balancer/tsconfig.tsbuildinfo'
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
11:33:50 AM - Project '../widget-skeleton/tsconfig.json' is up to date because newest input '../widget-skeleton/src/webComponent/createWidget.ts' is older than output '../widget-skeleton/tsconfig.tsbuildinfo'
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
11:33:50 AM - Project '../fingerprintjs/tsconfig.json' is up to date because newest input '../fingerprintjs/src/utils/browser.ts' is older than output '../fingerprintjs/tsconfig.tsbuildinfo'
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
11:33:50 AM - Project '../fingerprint/tsconfig.json' is up to date because newest input '../fingerprint/src/index.ts' is older than output '../fingerprint/tsconfig.tsbuildinfo'
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
11:33:50 AM - Project '../keyring/tsconfig.json' is up to date because newest input '../keyring/src/pair/decode.ts' is older than output '../keyring/tsconfig.tsbuildinfo'
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
11:33:50 AM - Project '../account/tsconfig.json' is up to date because newest input '../account/src/custom.d.ts' is older than output '../account/tsconfig.tsbuildinfo'
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
11:33:50 AM - Project '../procaptcha-common/tsconfig.json' is up to date because newest input '../procaptcha-common/src/state/managerLifecycle.ts' is older than output '../procaptcha-common/tsconfig.tsbuildinfo'
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
11:33:50 AM - Project 'tsconfig.json' is out of date because output file 'tsconfig.tsbuildinfo' does not exist
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
11:33:50 AM - Building project '/home/runner/work/captcha/captcha/packages/procaptcha-puzzle/tsconfig.json'...
|
|
53
53
|
|
package/.turbo/turbo-build.log
CHANGED
|
@@ -45,10 +45,10 @@ Bundle build started
|
|
|
45
45
|
rendering chunks...
|
|
46
46
|
computing gzip size...
|
|
47
47
|
dist/_virtual/_rolldown/runtime.js 0.04 kB │ gzip: 0.06 kB
|
|
48
|
+
dist/components/procaptchaPuzzle.js 0.33 kB │ gzip: 0.19 kB
|
|
48
49
|
dist/index.js 0.38 kB │ gzip: 0.16 kB
|
|
49
|
-
dist/
|
|
50
|
-
dist/components/procaptchaWidget.js
|
|
51
|
-
dist/
|
|
52
|
-
dist/components/puzzleCanvas.js 14.04 kB │ gzip: 4.36 kB
|
|
50
|
+
dist/services/Manager.js 8.12 kB │ gzip: 2.41 kB
|
|
51
|
+
dist/components/procaptchaWidget.js 8.49 kB │ gzip: 2.40 kB
|
|
52
|
+
dist/components/puzzleCanvas.js 16.04 kB │ gzip: 4.78 kB
|
|
53
53
|
|
|
54
|
-
[32m✓ built in
|
|
54
|
+
[32m✓ built in 78ms[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,60 @@
|
|
|
1
1
|
# @prosopo/procaptcha-puzzle
|
|
2
2
|
|
|
3
|
+
## 2.17.0
|
|
4
|
+
### Minor Changes
|
|
5
|
+
|
|
6
|
+
- 4570692: Let a user swap the puzzle captcha for an image challenge, on sites where Prosopo has switched it on.
|
|
7
|
+
|
|
8
|
+
Sites get a new staff-only setting, `widgetFeatureFlags.puzzleImageSwitch`. It is off by default and has no stored default. When it is on and the site allows image challenges, the puzzle response says `imageSwitchAvailable: true`. The puzzle then shows a small grid icon next to its title, with a "Switch to an image challenge" tooltip on hover or keyboard focus. After two wrong answers or refreshes in a row, the icon fills with the primary colour and its tooltip shows without a hover, because a touch screen has no hover. The tooltip goes away once the user starts moving the piece.
|
|
9
|
+
|
|
10
|
+
Pressing it re-mints the session the same way refresh does, with `switchToImage: true`. The provider only honours that when the request is a refresh of a puzzle session on the same site and the switch is on. The new session is then an image challenge with reason `PUZZLE_USER_SWITCH`. Like the refresh limit, it only ever moves from puzzle to image, so a client that fakes the request can only make its own challenge harder.
|
|
11
|
+
|
|
12
|
+
Also fixes a wrong puzzle answer restarting the whole widget. The user was sent back to the checkbox instead of getting a new puzzle with "Not quite — try again", and a wasted `/frictionless` request went out. The puzzle now retries in place.
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- Updated dependencies [4570692]
|
|
17
|
+
- @prosopo/procaptcha-common@2.21.0
|
|
18
|
+
- @prosopo/api@4.6.0
|
|
19
|
+
- @prosopo/types@5.15.0
|
|
20
|
+
- @prosopo/locale@3.9.0
|
|
21
|
+
- @prosopo/common@3.1.64
|
|
22
|
+
|
|
23
|
+
## 2.16.0
|
|
24
|
+
### Minor Changes
|
|
25
|
+
|
|
26
|
+
- b299a91: An audio challenge, offered only as an accessibility alternative, like reCAPTCHA's audio option. The user hears a short sequence of spoken digits and types them in.
|
|
27
|
+
|
|
28
|
+
It is off by default and needs two switches. Prosopo has to turn on the `captchaTypeFeatureFlags.audio` feature flag for the site, which the site owner cannot do, and the site owner has to set `audioAccessibilityEnabled`. When both are on, image, puzzle and icon-order challenges show a "Use audio instead" button. Pressing it swaps the visual challenge for the audio one. After a wrong answer the user stays on audio and gets a fresh clip.
|
|
29
|
+
|
|
30
|
+
Audio is never a captcha type that can be selected or routed to. A site's `captchaType`, traffic-filter categories, Restrict rules and the site-key CLI all reject it, and routing, PoW escalation and the severity tiers leave it out. The provider only serves audio against the visual session the user was already given, and only on a site with both switches on; any other audio request is refused before the session is used up, and a request without a session is always refused.
|
|
31
|
+
|
|
32
|
+
The spoken digits are synthesised by `@prosopo/audio-assets`, so there is no recorded set of clips to collect. The answer never leaves the provider, and a challenge can be submitted and verified only once, even under concurrent requests. `@prosopo/procaptcha-audio` is the widget. It and the "Use audio instead" button are built on the shared widget code in `@prosopo/procaptcha-common`, and the provider side is built on the shared interactive-captcha code that puzzle and icon-order use.
|
|
33
|
+
|
|
34
|
+
The demo playground has audio pages, and there is an end-to-end test for it.
|
|
35
|
+
|
|
36
|
+
### Patch Changes
|
|
37
|
+
|
|
38
|
+
- e13d7a8: New captcha type: `iconOrder`. The user is shown a frame of icons and a legend, and clicks the legend's icons in the order given.
|
|
39
|
+
|
|
40
|
+
Icon-order is off by default. Only Prosopo can switch it on for a site, with the `captchaTypeFeatureFlags.iconOrder` feature flag; the site owner cannot. A site without the flag is never served icon-order by any route, and the challenge endpoint refuses it. Once the flag is on, the owner can still keep icon-order out of the frictionless flow with `frictionlessTypes.iconOrder`, the same way as image and puzzle.
|
|
41
|
+
|
|
42
|
+
The answer never leaves the provider. Icon positions are stored on the challenge record, and the widget receives only the rendered frame and legend. Grading checks order as well as position, and each icon's hit radius scales with its size. Verifying a token is single-use under concurrent requests.
|
|
43
|
+
|
|
44
|
+
`@prosopo/icon-order-assets` draws the imagery, and `@prosopo/procaptcha-icon-order` is the widget. Its text is translated into every supported language.
|
|
45
|
+
|
|
46
|
+
Puzzle and icon-order now share their server code: challenge and solution handlers, the verify route, the submit and verify pipeline, and the database record methods. The widget code they have in common moves into `@prosopo/procaptcha-common`: the lazy mount wrapper, manager expiry and dispose, spent-session handling, behavioural data encryption and trusted click coordinates. Puzzle's behaviour is unchanged.
|
|
47
|
+
|
|
48
|
+
The demo playground has icon-order pages, and there is an end-to-end test for it.
|
|
49
|
+
- Updated dependencies [b299a91]
|
|
50
|
+
- Updated dependencies [e13d7a8]
|
|
51
|
+
- Updated dependencies [270395d]
|
|
52
|
+
- @prosopo/types@5.14.0
|
|
53
|
+
- @prosopo/api@4.5.0
|
|
54
|
+
- @prosopo/procaptcha-common@2.20.0
|
|
55
|
+
- @prosopo/locale@3.8.0
|
|
56
|
+
- @prosopo/common@3.1.63
|
|
57
|
+
|
|
3
58
|
## 2.15.0
|
|
4
59
|
### Minor Changes
|
|
5
60
|
|
|
@@ -1,22 +1,7 @@
|
|
|
1
|
+
let _prosopo_procaptcha_common = require("@prosopo/procaptcha-common");
|
|
1
2
|
//#region src/components/procaptchaPuzzle.ts
|
|
2
|
-
/**
|
|
3
|
-
* Dynamic import so the puzzle widget lands in its own chunk, replacing the
|
|
4
|
-
* `lazy()` + `<Suspense>` pair that used to provide the split.
|
|
5
|
-
*/
|
|
6
3
|
var loadProcaptchaPuzzle = async () => (await Promise.resolve().then(() => require("./procaptchaWidget.cjs"))).mountProcaptchaPuzzleWidget;
|
|
7
|
-
var mountProcaptchaPuzzle = (
|
|
8
|
-
let destroyed = false;
|
|
9
|
-
let inner;
|
|
10
|
-
loadProcaptchaPuzzle().then((mount) => {
|
|
11
|
-
if (destroyed) return;
|
|
12
|
-
inner = mount(container, props);
|
|
13
|
-
});
|
|
14
|
-
return { destroy: () => {
|
|
15
|
-
destroyed = true;
|
|
16
|
-
inner?.destroy();
|
|
17
|
-
inner = void 0;
|
|
18
|
-
} };
|
|
19
|
-
};
|
|
4
|
+
var mountProcaptchaPuzzle = (0, _prosopo_procaptcha_common.lazyMount)(loadProcaptchaPuzzle);
|
|
20
5
|
//#endregion
|
|
21
6
|
exports.loadProcaptchaPuzzle = loadProcaptchaPuzzle;
|
|
22
7
|
exports.mountProcaptchaPuzzle = mountProcaptchaPuzzle;
|
|
@@ -5,7 +5,7 @@ let _prosopo_procaptcha_common = require("@prosopo/procaptcha-common");
|
|
|
5
5
|
let _prosopo_types = require("@prosopo/types");
|
|
6
6
|
let _prosopo_widget_skeleton = require("@prosopo/widget-skeleton");
|
|
7
7
|
//#region src/components/procaptchaWidget.ts
|
|
8
|
-
var
|
|
8
|
+
var IMAGE_SWITCH_HIGHLIGHT_AFTER = 2;
|
|
9
9
|
var mountProcaptchaPuzzleWidget = (container, props) => {
|
|
10
10
|
const teardown = new _prosopo_procaptcha_common.Teardown();
|
|
11
11
|
const config = props.config;
|
|
@@ -26,7 +26,8 @@ var mountProcaptchaPuzzleWidget = (container, props) => {
|
|
|
26
26
|
let checkbox;
|
|
27
27
|
let puzzle;
|
|
28
28
|
const updateState = (0, _prosopo_procaptcha_common.buildUpdateState)(store.state, store.update);
|
|
29
|
-
const
|
|
29
|
+
const remintsSession = void 0 !== frictionlessState?.sessionId && void 0 !== props.onReload;
|
|
30
|
+
const manager = require_Manager.Manager(config, store.state, updateState, callbacks, frictionlessState, () => honeypot?.getValue(), { widgetReloadsOnFailure: remintsSession });
|
|
30
31
|
const root = (0, _prosopo_procaptcha_common.createElement)("div");
|
|
31
32
|
container.appendChild(root);
|
|
32
33
|
if (frictionlessState?.hp) honeypot = (0, _prosopo_procaptcha_common.mountHoneypot)(root, { encodedQuestion: frictionlessState.hp });
|
|
@@ -53,10 +54,10 @@ var mountProcaptchaPuzzleWidget = (container, props) => {
|
|
|
53
54
|
await replaceChallenge({ showRetry: true });
|
|
54
55
|
};
|
|
55
56
|
const replaceChallenge = async (options) => {
|
|
56
|
-
if (
|
|
57
|
+
if (remintsSession) {
|
|
57
58
|
puzzlePhase = "submitting";
|
|
58
59
|
scheduler.schedule();
|
|
59
|
-
props.onReload(lastCoords?.x, lastCoords?.y, options);
|
|
60
|
+
props.onReload?.(lastCoords?.x, lastCoords?.y, options);
|
|
60
61
|
return;
|
|
61
62
|
}
|
|
62
63
|
try {
|
|
@@ -85,6 +86,18 @@ var mountProcaptchaPuzzleWidget = (container, props) => {
|
|
|
85
86
|
scheduler.schedule();
|
|
86
87
|
replaceChallenge({ refresh: true });
|
|
87
88
|
};
|
|
89
|
+
const handleSwitchToImage = () => {
|
|
90
|
+
if ("dragging" !== puzzlePhase) return;
|
|
91
|
+
callbacks.onReload?.();
|
|
92
|
+
showRetry = false;
|
|
93
|
+
puzzlePhase = "submitting";
|
|
94
|
+
scheduler.schedule();
|
|
95
|
+
replaceChallenge({
|
|
96
|
+
refresh: true,
|
|
97
|
+
switchToImage: true
|
|
98
|
+
});
|
|
99
|
+
};
|
|
100
|
+
const canSwitchToImage = (challenge) => true === challenge.imageSwitchAvailable && remintsSession;
|
|
88
101
|
const handleDismiss = () => {
|
|
89
102
|
puzzlePhase = "checkbox";
|
|
90
103
|
challengeData = null;
|
|
@@ -108,7 +121,12 @@ var mountProcaptchaPuzzleWidget = (container, props) => {
|
|
|
108
121
|
placement: config.placement,
|
|
109
122
|
anchor: props.container,
|
|
110
123
|
onDismiss: handleDismiss,
|
|
111
|
-
|
|
124
|
+
audioAlternative: (0, _prosopo_procaptcha_common.audioAlternativeOffer)(props, translator.isReady() ? translator.t("WIDGET.AUDIO_ALTERNATIVE") : ""),
|
|
125
|
+
onRefresh: handleRefresh,
|
|
126
|
+
...canSwitchToImage(challenge) && {
|
|
127
|
+
onSwitchToImage: handleSwitchToImage,
|
|
128
|
+
imageSwitchHighlighted: (props.startReplacementCount ?? 0) >= IMAGE_SWITCH_HIGHLIGHT_AFTER
|
|
129
|
+
}
|
|
112
130
|
});
|
|
113
131
|
const runErrorEffect = () => {
|
|
114
132
|
if (store.state.error === lastError) return;
|
|
@@ -158,18 +176,7 @@ var mountProcaptchaPuzzleWidget = (container, props) => {
|
|
|
158
176
|
loading = true;
|
|
159
177
|
showRetry = false;
|
|
160
178
|
scheduler.schedule();
|
|
161
|
-
|
|
162
|
-
let y = 0;
|
|
163
|
-
if (!(0, _prosopo_procaptcha_common.isEventTrusted)(event)) {} else if ("touches" in event && event.touches.length > 0) {
|
|
164
|
-
const touch = event.touches[0];
|
|
165
|
-
if (touch) {
|
|
166
|
-
x = touch.clientX;
|
|
167
|
-
y = touch.clientY;
|
|
168
|
-
}
|
|
169
|
-
} else if ("clientX" in event && "clientY" in event) {
|
|
170
|
-
x = event.clientX;
|
|
171
|
-
y = event.clientY;
|
|
172
|
-
}
|
|
179
|
+
const { x, y } = (0, _prosopo_procaptcha_common.trustedClickCoords)(event);
|
|
173
180
|
lastCoords = {
|
|
174
181
|
x,
|
|
175
182
|
y
|
|
@@ -212,8 +219,8 @@ var mountProcaptchaPuzzleWidget = (container, props) => {
|
|
|
212
219
|
}
|
|
213
220
|
})();
|
|
214
221
|
};
|
|
215
|
-
if (props.container) teardown.addEventListener(props.container, PROCAPTCHA_EXECUTE_EVENT, handleExecute);
|
|
216
|
-
if (isInvisible) teardown.addEventListener(document, PROCAPTCHA_EXECUTE_EVENT, handleExecute);
|
|
222
|
+
if (props.container) teardown.addEventListener(props.container, _prosopo_procaptcha_common.PROCAPTCHA_EXECUTE_EVENT, handleExecute);
|
|
223
|
+
if (isInvisible) teardown.addEventListener(document, _prosopo_procaptcha_common.PROCAPTCHA_EXECUTE_EVENT, handleExecute);
|
|
217
224
|
if (config.language) if (i18n) {
|
|
218
225
|
if (i18n.language !== config.language) i18n.changeLanguage(config.language);
|
|
219
226
|
} else (0, _prosopo_locale.loadI18next)(false, config.language);
|
|
@@ -104,6 +104,7 @@ var mountPuzzleCanvas = (initialProps) => {
|
|
|
104
104
|
let shakeTimer;
|
|
105
105
|
let announceTimer;
|
|
106
106
|
let keyboardDragging = false;
|
|
107
|
+
let hasMovedPiece = false;
|
|
107
108
|
const baseId = `prosopo-puzzle-${instanceCount++}`;
|
|
108
109
|
const instructionId = `${baseId}-instruction`;
|
|
109
110
|
const keyboardHintId = `${baseId}-keyboard-hint`;
|
|
@@ -182,6 +183,13 @@ var mountPuzzleCanvas = (initialProps) => {
|
|
|
182
183
|
top: "50%",
|
|
183
184
|
transform: "translateY(-50%)"
|
|
184
185
|
} });
|
|
186
|
+
const switchSlot = (0, _prosopo_procaptcha_common.createElement)("div", { style: {
|
|
187
|
+
position: "absolute",
|
|
188
|
+
left: "8px",
|
|
189
|
+
top: "50%",
|
|
190
|
+
transform: "translateY(-50%)",
|
|
191
|
+
zIndex: "1"
|
|
192
|
+
} });
|
|
185
193
|
const instruction = (0, _prosopo_procaptcha_common.createElement)("div", {
|
|
186
194
|
style: {
|
|
187
195
|
position: "relative",
|
|
@@ -194,7 +202,11 @@ var mountPuzzleCanvas = (initialProps) => {
|
|
|
194
202
|
fontWeight: 500,
|
|
195
203
|
transition: "color 0.3s ease, border-color 0.3s ease"
|
|
196
204
|
},
|
|
197
|
-
children: [
|
|
205
|
+
children: [
|
|
206
|
+
instructionText,
|
|
207
|
+
refreshSlot,
|
|
208
|
+
switchSlot
|
|
209
|
+
]
|
|
198
210
|
});
|
|
199
211
|
const panel = (0, _prosopo_procaptcha_common.createElement)("div", {
|
|
200
212
|
style: {
|
|
@@ -215,6 +227,29 @@ var mountPuzzleCanvas = (initialProps) => {
|
|
|
215
227
|
}
|
|
216
228
|
});
|
|
217
229
|
const refreshButton = props.onRefresh ? (0, _prosopo_procaptcha_common.mountReloadButton)(refreshSlot, refreshButtonProps()) : void 0;
|
|
230
|
+
const imageSwitchButtonProps = () => ({
|
|
231
|
+
themeColor: _prosopo_widget_skeleton.lightTheme === props.theme ? "light" : "dark",
|
|
232
|
+
label: t("WIDGET.PUZZLE.SWITCH_TO_IMAGE", { defaultValue: "Switch to an image challenge" }),
|
|
233
|
+
tooltipPinned: true === props.imageSwitchHighlighted && !hasMovedPiece,
|
|
234
|
+
onSwitch: () => {
|
|
235
|
+
if (!props.submitting && !dragging) props.onSwitchToImage?.();
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
let imageSwitchButton;
|
|
239
|
+
const hidePinnedSwitchTooltip = () => {
|
|
240
|
+
if (hasMovedPiece) return;
|
|
241
|
+
hasMovedPiece = true;
|
|
242
|
+
imageSwitchButton?.update(imageSwitchButtonProps());
|
|
243
|
+
};
|
|
244
|
+
const syncImageSwitchButton = () => {
|
|
245
|
+
if (void 0 === props.onSwitchToImage) {
|
|
246
|
+
imageSwitchButton?.destroy();
|
|
247
|
+
imageSwitchButton = void 0;
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
if (imageSwitchButton) imageSwitchButton.update(imageSwitchButtonProps());
|
|
251
|
+
else imageSwitchButton = (0, _prosopo_procaptcha_common.mountImageSwitchButton)(switchSlot, imageSwitchButtonProps());
|
|
252
|
+
};
|
|
218
253
|
const surfaceProps = () => ({
|
|
219
254
|
show: true,
|
|
220
255
|
placement: props.placement,
|
|
@@ -225,6 +260,12 @@ var mountPuzzleCanvas = (initialProps) => {
|
|
|
225
260
|
});
|
|
226
261
|
const surface = (0, _prosopo_procaptcha_common.mountChallengeSurface)(surfaceProps());
|
|
227
262
|
surface.content.append(style, announcer, keyboardHint, panel);
|
|
263
|
+
const audioAlternativeFooterProps = () => ({
|
|
264
|
+
offer: props.audioAlternative,
|
|
265
|
+
theme: props.theme,
|
|
266
|
+
width: CONTAINER_WIDTH
|
|
267
|
+
});
|
|
268
|
+
const audioAlternativeFooter = (0, _prosopo_procaptcha_common.mountAudioAlternativeFooter)(panel, audioAlternativeFooterProps());
|
|
228
269
|
const announce = (message, delayMs = 0) => {
|
|
229
270
|
if (void 0 !== announceTimer) {
|
|
230
271
|
clearTimeout(announceTimer);
|
|
@@ -293,11 +334,15 @@ var mountPuzzleCanvas = (initialProps) => {
|
|
|
293
334
|
});
|
|
294
335
|
(0, _prosopo_procaptcha_common.applyStyles)(refreshSlot, { visibility: props.submitting ? "hidden" : "visible" });
|
|
295
336
|
refreshButton?.update(refreshButtonProps());
|
|
337
|
+
syncImageSwitchButton();
|
|
338
|
+
(0, _prosopo_procaptcha_common.applyStyles)(switchSlot, { visibility: props.submitting ? "hidden" : "visible" });
|
|
296
339
|
(0, _prosopo_procaptcha_common.applyStyles)(area, {
|
|
297
340
|
background: `linear-gradient(135deg, ${theme.palette.surface} 0%, ${theme.palette.primaryContainer.main} 50%, ${theme.palette.surface} 100%)`,
|
|
298
341
|
opacity: props.submitting ? .6 : 1,
|
|
299
|
-
pointerEvents: props.submitting ? "none" : "auto"
|
|
342
|
+
pointerEvents: props.submitting ? "none" : "auto",
|
|
343
|
+
borderRadius: props.audioAlternative ? "0" : "0 0 20px 20px"
|
|
300
344
|
});
|
|
345
|
+
audioAlternativeFooter.update(audioAlternativeFooterProps());
|
|
301
346
|
for (const tile of backgroundTiles) tile.src = props.background;
|
|
302
347
|
(0, _prosopo_procaptcha_common.applyAttributes)(piece, {
|
|
303
348
|
tabindex: props.submitting ? -1 : 0,
|
|
@@ -346,6 +391,7 @@ var mountPuzzleCanvas = (initialProps) => {
|
|
|
346
391
|
dragging = true;
|
|
347
392
|
keyboardDragging = false;
|
|
348
393
|
puzzleEvents = [];
|
|
394
|
+
hidePinnedSwitchTooltip();
|
|
349
395
|
const offset = containerOffset();
|
|
350
396
|
dragOffset = {
|
|
351
397
|
x: clientX - offset.x - posX,
|
|
@@ -358,6 +404,7 @@ var mountPuzzleCanvas = (initialProps) => {
|
|
|
358
404
|
if (!keyboardDragging) {
|
|
359
405
|
keyboardDragging = true;
|
|
360
406
|
puzzleEvents = [];
|
|
407
|
+
hidePinnedSwitchTooltip();
|
|
361
408
|
}
|
|
362
409
|
posX = clamp(posX + deltaX, 0, CONTAINER_WIDTH);
|
|
363
410
|
posY = clamp(posY + deltaY, 0, CONTAINER_HEIGHT);
|
|
@@ -439,7 +486,9 @@ var mountPuzzleCanvas = (initialProps) => {
|
|
|
439
486
|
announce(t("WIDGET.PUZZLE.CHECKING", { defaultValue: "Checking your answer" }));
|
|
440
487
|
};
|
|
441
488
|
const announceRetry = () => {
|
|
442
|
-
|
|
489
|
+
const retry = t("WIDGET.PUZZLE.RETRY_ANNOUNCEMENT", { defaultValue: "Not quite. A new puzzle has loaded and the piece is back at the start." });
|
|
490
|
+
const offerSwitch = void 0 !== props.onSwitchToImage && true === props.imageSwitchHighlighted;
|
|
491
|
+
announce(offerSwitch ? `${retry} ${t("WIDGET.PUZZLE.SWITCH_TO_IMAGE_ANNOUNCEMENT", { defaultValue: "You can also switch to an image challenge." })}` : retry);
|
|
443
492
|
};
|
|
444
493
|
if (props.submitting) announceSubmitting();
|
|
445
494
|
else if (props.showRetry) announceRetry();
|
|
@@ -463,7 +512,9 @@ var mountPuzzleCanvas = (initialProps) => {
|
|
|
463
512
|
},
|
|
464
513
|
destroy: () => {
|
|
465
514
|
teardown.run();
|
|
515
|
+
audioAlternativeFooter.destroy();
|
|
466
516
|
refreshButton?.destroy();
|
|
517
|
+
imageSwitchButton?.destroy();
|
|
467
518
|
surface.destroy();
|
|
468
519
|
}
|
|
469
520
|
};
|
|
@@ -6,7 +6,7 @@ let _prosopo_common = require("@prosopo/common");
|
|
|
6
6
|
let _prosopo_util = require("@prosopo/util");
|
|
7
7
|
let _prosopo_util_crypto = require("@prosopo/util-crypto");
|
|
8
8
|
//#region src/services/Manager.ts
|
|
9
|
-
var Manager = (configInput, state, onStateUpdate, callbacks, frictionlessState, getHoneypotValue) => {
|
|
9
|
+
var Manager = (configInput, state, onStateUpdate, callbacks, frictionlessState, getHoneypotValue, options = {}) => {
|
|
10
10
|
const events = (0, _prosopo_procaptcha_common.getDefaultEvents)(callbacks);
|
|
11
11
|
let storedChallengeResponse;
|
|
12
12
|
let storedProviderApi;
|
|
@@ -15,7 +15,7 @@ var Manager = (configInput, state, onStateUpdate, callbacks, frictionlessState,
|
|
|
15
15
|
let storedClickX;
|
|
16
16
|
let storedClickY;
|
|
17
17
|
let previousProviderUrl;
|
|
18
|
-
|
|
18
|
+
const spentSession = (0, _prosopo_procaptcha_common.createSpentSessionGuard)();
|
|
19
19
|
const defaultState = () => {
|
|
20
20
|
return {
|
|
21
21
|
showModal: false,
|
|
@@ -28,21 +28,13 @@ var Manager = (configInput, state, onStateUpdate, callbacks, frictionlessState,
|
|
|
28
28
|
account: void 0
|
|
29
29
|
};
|
|
30
30
|
};
|
|
31
|
-
const clearTimeout = () => {
|
|
32
|
-
window.clearTimeout(Number(state.timeout));
|
|
33
|
-
updateState({ timeout: void 0 });
|
|
34
|
-
};
|
|
35
31
|
const onFailed = () => {
|
|
36
32
|
updateState({
|
|
37
33
|
isHuman: false,
|
|
38
34
|
loading: false
|
|
39
35
|
});
|
|
40
36
|
events.onFailed();
|
|
41
|
-
resetState(frictionlessState?.restart);
|
|
42
|
-
};
|
|
43
|
-
const clearSuccessfulChallengeTimeout = () => {
|
|
44
|
-
window.clearTimeout(Number(state.successfullChallengeTimeout));
|
|
45
|
-
updateState({ successfullChallengeTimeout: void 0 });
|
|
37
|
+
resetState(options.widgetReloadsOnFailure ? void 0 : frictionlessState?.restart);
|
|
46
38
|
};
|
|
47
39
|
const getConfig = () => {
|
|
48
40
|
const config = {
|
|
@@ -61,9 +53,9 @@ var Manager = (configInput, state, onStateUpdate, callbacks, frictionlessState,
|
|
|
61
53
|
return state.dappAccount;
|
|
62
54
|
};
|
|
63
55
|
const updateState = (0, _prosopo_procaptcha_common.buildUpdateState)(state, onStateUpdate);
|
|
56
|
+
const lifecycle = (0, _prosopo_procaptcha_common.createManagerLifecycle)(state, updateState);
|
|
64
57
|
const resetState = (frictionlessRestart) => {
|
|
65
|
-
|
|
66
|
-
clearSuccessfulChallengeTimeout();
|
|
58
|
+
lifecycle.clearTimers();
|
|
67
59
|
updateState(defaultState());
|
|
68
60
|
events.onReset();
|
|
69
61
|
if (frictionlessRestart) frictionlessRestart();
|
|
@@ -75,14 +67,11 @@ var Manager = (configInput, state, onStateUpdate, callbacks, frictionlessState,
|
|
|
75
67
|
storedClickY = void 0;
|
|
76
68
|
};
|
|
77
69
|
const setValidChallengeTimeout = () => {
|
|
78
|
-
|
|
79
|
-
const successfullChallengeTimeout = setTimeout(() => {
|
|
80
|
-
if (disposed) return;
|
|
70
|
+
lifecycle.expireSolutionAfter(getConfig().captchas.puzzle.solutionTimeout, () => {
|
|
81
71
|
updateState({ isHuman: false });
|
|
82
72
|
events.onExpired();
|
|
83
73
|
resetState(frictionlessState?.restart);
|
|
84
|
-
}
|
|
85
|
-
updateState({ successfullChallengeTimeout });
|
|
74
|
+
});
|
|
86
75
|
};
|
|
87
76
|
const start = async (x = 0, y = 0) => {
|
|
88
77
|
await (0, _prosopo_procaptcha_common.providerRetry)(async () => {
|
|
@@ -117,7 +106,7 @@ var Manager = (configInput, state, onStateUpdate, callbacks, frictionlessState,
|
|
|
117
106
|
previousProviderUrl = providerUrl;
|
|
118
107
|
const providerApi = new _prosopo_api.ProviderApi(providerUrl, getDappAccount());
|
|
119
108
|
const challengeSessionId = frictionlessState?.sessionId;
|
|
120
|
-
if (challengeSessionId
|
|
109
|
+
if (spentSession.isSpent(challengeSessionId)) {
|
|
121
110
|
updateState({
|
|
122
111
|
loading: false,
|
|
123
112
|
error: {
|
|
@@ -130,7 +119,7 @@ var Manager = (configInput, state, onStateUpdate, callbacks, frictionlessState,
|
|
|
130
119
|
}
|
|
131
120
|
const simdReadingsOnChallenge = frictionlessState?.getSimdReadings ? await frictionlessState.getSimdReadings(0) : void 0;
|
|
132
121
|
const challenge = await providerApi.getPuzzleCaptchaChallenge(userAccount, getDappAccount(), challengeSessionId, simdReadingsOnChallenge);
|
|
133
|
-
|
|
122
|
+
spentSession.markSpent(challengeSessionId);
|
|
134
123
|
if (challenge.error) {
|
|
135
124
|
updateState({
|
|
136
125
|
loading: false,
|
|
@@ -169,18 +158,7 @@ var Manager = (configInput, state, onStateUpdate, callbacks, frictionlessState,
|
|
|
169
158
|
data: (0, _polkadot_util_string.stringToHex)(challenge[_prosopo_types.ApiParams.timestamp].toString()),
|
|
170
159
|
type: "bytes"
|
|
171
160
|
});
|
|
172
|
-
|
|
173
|
-
if (frictionlessState?.encryptBehavioralData && (frictionlessState?.behaviorCollector1 || frictionlessState?.behaviorCollector2 || frictionlessState?.behaviorCollector3 || frictionlessState?.behaviorCollector4)) try {
|
|
174
|
-
const behavioralData = {
|
|
175
|
-
collector1: frictionlessState.behaviorCollector1?.getData() || [],
|
|
176
|
-
collector2: frictionlessState.behaviorCollector2?.getData() || [],
|
|
177
|
-
collector3: frictionlessState.behaviorCollector3?.getData() || [],
|
|
178
|
-
collector4: frictionlessState.behaviorCollector4?.getData() || [],
|
|
179
|
-
deviceCapability: frictionlessState.deviceCapability || "unknown"
|
|
180
|
-
};
|
|
181
|
-
const dataToEncrypt = frictionlessState.packBehavioralData ? frictionlessState.packBehavioralData(behavioralData) : behavioralData;
|
|
182
|
-
encryptedBehavioralData = await frictionlessState.encryptBehavioralData(JSON.stringify(dataToEncrypt));
|
|
183
|
-
} catch {}
|
|
161
|
+
const encryptedBehavioralData = await (0, _prosopo_procaptcha_common.encryptBehavioralDataForSubmit)(frictionlessState);
|
|
184
162
|
let salt;
|
|
185
163
|
if (storedClickX !== void 0 && storedClickY !== void 0) {
|
|
186
164
|
const coords = [storedClickX, storedClickY];
|
|
@@ -190,7 +168,7 @@ var Manager = (configInput, state, onStateUpdate, callbacks, frictionlessState,
|
|
|
190
168
|
const simdReadings = await (0, _prosopo_procaptcha_common.getSimdReadingsForSubmit)(frictionlessState);
|
|
191
169
|
const clientMetaData = (0, _prosopo_procaptcha_common.buildClientMetaData)(getHoneypotValue?.(), getConfig().clientSessionId);
|
|
192
170
|
const verifiedSolution = await providerApi.submitPuzzleCaptchaSolution(challenge, getAccount().account.account.address, getDappAccount(), finalX, finalY, puzzleEvents, userTimestampSignature.signature.toString(), encryptedBehavioralData, salt, simdReadings, clientMetaData);
|
|
193
|
-
if (
|
|
171
|
+
if (lifecycle.isDisposed()) return false;
|
|
194
172
|
if (verifiedSolution[_prosopo_types.ApiParams.verified]) {
|
|
195
173
|
updateState({
|
|
196
174
|
isHuman: true,
|
|
@@ -218,23 +196,11 @@ var Manager = (configInput, state, onStateUpdate, callbacks, frictionlessState,
|
|
|
218
196
|
throw error;
|
|
219
197
|
}
|
|
220
198
|
};
|
|
221
|
-
let disposed = false;
|
|
222
|
-
/**
|
|
223
|
-
* Stops this manager's challenge and solution-expiry timers without firing
|
|
224
|
-
* any event. Left running after the widget is destroyed (reset(), a
|
|
225
|
-
* restart, an SPA route change) they fired onExpired/onReset later on,
|
|
226
|
-
* which also cleared the replacement widget's token from the form.
|
|
227
|
-
*/
|
|
228
|
-
const dispose = () => {
|
|
229
|
-
disposed = true;
|
|
230
|
-
window.clearTimeout(Number(state.timeout));
|
|
231
|
-
window.clearTimeout(Number(state.successfullChallengeTimeout));
|
|
232
|
-
};
|
|
233
199
|
return {
|
|
234
200
|
start,
|
|
235
201
|
submitSolution,
|
|
236
202
|
resetState,
|
|
237
|
-
dispose
|
|
203
|
+
dispose: lifecycle.dispose
|
|
238
204
|
};
|
|
239
205
|
};
|
|
240
206
|
//#endregion
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type WidgetMountFn } from "@prosopo/procaptcha-common";
|
|
2
2
|
import type { ProcaptchaPuzzleHandle } from "./procaptchaWidget.js";
|
|
3
3
|
export type { ProcaptchaPuzzleHandle };
|
|
4
|
-
export type ProcaptchaPuzzleMountFn =
|
|
4
|
+
export type ProcaptchaPuzzleMountFn = WidgetMountFn<ProcaptchaPuzzleHandle>;
|
|
5
5
|
export declare const loadProcaptchaPuzzle: () => Promise<ProcaptchaPuzzleMountFn>;
|
|
6
|
-
export declare const mountProcaptchaPuzzle:
|
|
6
|
+
export declare const mountProcaptchaPuzzle: ProcaptchaPuzzleMountFn;
|
|
7
7
|
//# sourceMappingURL=procaptchaPuzzle.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"procaptchaPuzzle.d.ts","sourceRoot":"","sources":["../../src/components/procaptchaPuzzle.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"procaptchaPuzzle.d.ts","sourceRoot":"","sources":["../../src/components/procaptchaPuzzle.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,KAAK,aAAa,EAAa,MAAM,4BAA4B,CAAC;AAC3E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAEpE,YAAY,EAAE,sBAAsB,EAAE,CAAC;AAEvC,MAAM,MAAM,uBAAuB,GAAG,aAAa,CAAC,sBAAsB,CAAC,CAAC;AAE5E,eAAO,MAAM,oBAAoB,QACtB,OAAO,CAAC,uBAAuB,CAC2B,CAAC;AAEtE,eAAO,MAAM,qBAAqB,EAAE,uBACJ,CAAC"}
|
|
@@ -1,21 +1,6 @@
|
|
|
1
|
+
import { lazyMount } from "@prosopo/procaptcha-common";
|
|
1
2
|
//#region src/components/procaptchaPuzzle.ts
|
|
2
|
-
/**
|
|
3
|
-
* Dynamic import so the puzzle widget lands in its own chunk, replacing the
|
|
4
|
-
* `lazy()` + `<Suspense>` pair that used to provide the split.
|
|
5
|
-
*/
|
|
6
3
|
var loadProcaptchaPuzzle = async () => (await import("./procaptchaWidget.js")).mountProcaptchaPuzzleWidget;
|
|
7
|
-
var mountProcaptchaPuzzle = (
|
|
8
|
-
let destroyed = false;
|
|
9
|
-
let inner;
|
|
10
|
-
loadProcaptchaPuzzle().then((mount) => {
|
|
11
|
-
if (destroyed) return;
|
|
12
|
-
inner = mount(container, props);
|
|
13
|
-
});
|
|
14
|
-
return { destroy: () => {
|
|
15
|
-
destroyed = true;
|
|
16
|
-
inner?.destroy();
|
|
17
|
-
inner = void 0;
|
|
18
|
-
} };
|
|
19
|
-
};
|
|
4
|
+
var mountProcaptchaPuzzle = lazyMount(loadProcaptchaPuzzle);
|
|
20
5
|
//#endregion
|
|
21
6
|
export { loadProcaptchaPuzzle, mountProcaptchaPuzzle };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"procaptchaPuzzle.js","sourceRoot":"","sources":["../../src/components/procaptchaPuzzle.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"procaptchaPuzzle.js","sourceRoot":"","sources":["../../src/components/procaptchaPuzzle.ts"],"names":[],"mappings":"AAcA,OAAO,EAAsB,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAO3E,MAAM,CAAC,MAAM,oBAAoB,GAChC,KAAK,IAAsC,EAAE,CAC5C,CAAC,MAAM,MAAM,CAAC,uBAAuB,CAAC,CAAC,CAAC,2BAA2B,CAAC;AAEtE,MAAM,CAAC,MAAM,qBAAqB,GACjC,SAAS,CAAC,oBAAoB,CAAC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"procaptchaWidget.d.ts","sourceRoot":"","sources":["../../src/components/procaptchaWidget.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"procaptchaWidget.d.ts","sourceRoot":"","sources":["../../src/components/procaptchaWidget.ts"],"names":[],"mappings":"AAmCA,OAAO,EAGN,KAAK,eAAe,EAIpB,MAAM,gBAAgB,CAAC;AAWxB,MAAM,WAAW,sBAAsB;IACtC,OAAO,IAAI,IAAI,CAAC;CAChB;AAED,eAAO,MAAM,2BAA2B,cAC5B,WAAW,SACf,eAAe,KACpB,sBAkZF,CAAC;AAEF,eAAe,2BAA2B,CAAC"}
|