@prosopo/procaptcha-react 2.12.0 → 2.13.1
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 +8 -8
- package/.turbo/turbo-build$colon$tsc.log +22 -22
- package/.turbo/turbo-build.log +9 -9
- package/CHANGELOG.md +86 -0
- package/dist/cjs/components/button.cjs +14 -14
- package/dist/cjs/components/captchaComponent.cjs +42 -27
- package/dist/cjs/components/captchaWidget.cjs +41 -24
- package/dist/cjs/components/modal.cjs +7 -3
- package/dist/components/button.d.ts.map +1 -1
- package/dist/components/button.js +16 -16
- package/dist/components/button.js.map +1 -1
- package/dist/components/captchaComponent.d.ts.map +1 -1
- package/dist/components/captchaComponent.js +44 -29
- package/dist/components/captchaComponent.js.map +1 -1
- package/dist/components/captchaWidget.d.ts.map +1 -1
- package/dist/components/captchaWidget.js +43 -26
- package/dist/components/captchaWidget.js.map +1 -1
- package/dist/components/modal.d.ts.map +1 -1
- package/dist/components/modal.js +8 -4
- package/dist/components/modal.js.map +1 -1
- package/dist/tests/button.unit.test.js +42 -3
- package/dist/tests/button.unit.test.js.map +1 -1
- package/dist/tests/captchaComponent.unit.test.js +4 -4
- package/dist/tests/captchaComponent.unit.test.js.map +1 -1
- package/dist/tests/captchaWidget.unit.test.js +91 -11
- package/dist/tests/captchaWidget.unit.test.js.map +1 -1
- package/dist/tests/modal.unit.test.js +26 -10
- package/dist/tests/modal.unit.test.js.map +1 -1
- package/dist/tests/procaptchaWidget.unit.test.js +10 -8
- package/dist/tests/procaptchaWidget.unit.test.js.map +1 -1
- package/package.json +8 -8
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @prosopo/procaptcha-react@2.
|
|
2
|
+
> @prosopo/procaptcha-react@2.13.1 build:cjs
|
|
3
3
|
> NODE_ENV=${NODE_ENV:-development}; vite build --config vite.cjs.config.ts --mode $NODE_ENV
|
|
4
4
|
|
|
5
5
|
ViteCommonJSConfig: .
|
|
@@ -7,8 +7,8 @@ ViteCommonJSConfig: .
|
|
|
7
7
|
tsConfigPaths: [
|
|
8
8
|
'/home/runner/work/captcha/captcha/packages/common/tsconfig.json',
|
|
9
9
|
'/home/runner/work/captcha/captcha/packages/locale/tsconfig.json',
|
|
10
|
-
'/home/runner/work/captcha/captcha/packages/logger/tsconfig.json',
|
|
11
10
|
'/home/runner/work/captcha/captcha/packages/util/tsconfig.json',
|
|
11
|
+
'/home/runner/work/captcha/captcha/packages/logger/tsconfig.json',
|
|
12
12
|
'/home/runner/work/captcha/captcha/packages/types/tsconfig.json',
|
|
13
13
|
'/home/runner/work/captcha/captcha/packages/util-crypto/tsconfig.json',
|
|
14
14
|
'/home/runner/work/captcha/captcha/packages/procaptcha/tsconfig.json',
|
|
@@ -27,8 +27,8 @@ ViteCommonJSConfig: .
|
|
|
27
27
|
externals: [
|
|
28
28
|
'@prosopo/common',
|
|
29
29
|
'@prosopo/locale',
|
|
30
|
-
'@prosopo/logger',
|
|
31
30
|
'@prosopo/util',
|
|
31
|
+
'@prosopo/logger',
|
|
32
32
|
'@prosopo/types',
|
|
33
33
|
'@prosopo/util-crypto',
|
|
34
34
|
'@prosopo/procaptcha',
|
|
@@ -54,11 +54,11 @@ dist/cjs/util/index.cjs 0.56 kB │ gzip: 0.35 kB
|
|
|
54
54
|
dist/cjs/components/procaptcha.cjs 0.75 kB │ gzip: 0.41 kB
|
|
55
55
|
dist/cjs/components/index.cjs 0.89 kB │ gzip: 0.22 kB
|
|
56
56
|
dist/cjs/index.cjs 1.04 kB │ gzip: 0.28 kB
|
|
57
|
-
dist/cjs/components/modal.cjs 1.
|
|
57
|
+
dist/cjs/components/modal.cjs 1.39 kB │ gzip: 0.64 kB
|
|
58
58
|
dist/cjs/components/collector.cjs 1.53 kB │ gzip: 0.68 kB
|
|
59
|
-
dist/cjs/components/button.cjs 3.
|
|
59
|
+
dist/cjs/components/button.cjs 3.28 kB │ gzip: 1.24 kB
|
|
60
60
|
dist/cjs/components/procaptchaWidget.cjs 6.33 kB │ gzip: 2.06 kB
|
|
61
|
-
dist/cjs/components/captchaWidget.cjs
|
|
62
|
-
dist/cjs/components/captchaComponent.cjs
|
|
61
|
+
dist/cjs/components/captchaWidget.cjs 7.49 kB │ gzip: 2.70 kB
|
|
62
|
+
dist/cjs/components/captchaComponent.cjs 8.29 kB │ gzip: 2.17 kB
|
|
63
63
|
|
|
64
|
-
[32m✓ built in
|
|
64
|
+
[32m✓ built in 40ms[39m
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
|
|
2
|
-
> @prosopo/procaptcha-react@2.
|
|
2
|
+
> @prosopo/procaptcha-react@2.13.1 build:tsc
|
|
3
3
|
> tsc --build --verbose
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
10:40:51 PM - Projects in this build:
|
|
6
6
|
* ../../dev/config/tsconfig.json
|
|
7
|
-
* ../locale/tsconfig.json
|
|
8
7
|
* ../util/tsconfig.json
|
|
8
|
+
* ../locale/tsconfig.json
|
|
9
9
|
* ../logger/tsconfig.json
|
|
10
10
|
* ../util-crypto/tsconfig.json
|
|
11
11
|
* ../types/tsconfig.json
|
|
@@ -22,41 +22,41 @@
|
|
|
22
22
|
* ../procaptcha/tsconfig.json
|
|
23
23
|
* tsconfig.json
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
10:40:51 PM - 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'
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
10:40:51 PM - Project '../util/tsconfig.json' is up to date because newest input '../util/src/url.ts' is older than output '../util/tsconfig.tsbuildinfo'
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
10:40:51 PM - Project '../locale/tsconfig.json' is up to date because newest input '../locale/src/process.ts' is older than output '../locale/tsconfig.tsbuildinfo'
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
10:40:51 PM - Project '../logger/tsconfig.json' is up to date because newest input '../logger/src/tests/loggerBrowser.unit.test.ts' is older than output '../logger/tsconfig.tsbuildinfo'
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
10:40:51 PM - 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'
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
10:40:51 PM - Project '../types/tsconfig.json' is up to date because newest input '../types/src/types.test-d.ts' is older than output '../types/tsconfig.tsbuildinfo'
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
10:40:51 PM - Project '../common/tsconfig.json' is up to date because newest input '../common/src/tests/utils/objectUtils.unit.test.ts' is older than output '../common/tsconfig.tsbuildinfo'
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
10:40:51 PM - Project '../datasets/tsconfig.json' is up to date because newest input '../datasets/src/tests/captcha.unit.test.ts' is older than output '../datasets/tsconfig.tsbuildinfo'
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
10:40:51 PM - Project '../api/tsconfig.json' is up to date because newest input '../api/src/index.ts' is older than output '../api/tsconfig.tsbuildinfo'
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
10:40:51 PM - 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'
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
10:40:51 PM - 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'
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
10:40:51 PM - Project '../fingerprintjs/tsconfig.json' is up to date because newest input '../fingerprintjs/src/utils/browser.ts' is older than output '../fingerprintjs/tsconfig.tsbuildinfo'
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
10:40:51 PM - Project '../fingerprint/tsconfig.json' is up to date because newest input '../fingerprint/src/index.ts' is older than output '../fingerprint/tsconfig.tsbuildinfo'
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
10:40:51 PM - Project '../keyring/tsconfig.json' is up to date because newest input '../keyring/src/index.ts' is older than output '../keyring/tsconfig.tsbuildinfo'
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
10:40:51 PM - Project '../account/tsconfig.json' is up to date because newest input '../account/src/workers/cryptoWorker.ts' is older than output '../account/tsconfig.tsbuildinfo'
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
10:40:51 PM - Project '../procaptcha-common/tsconfig.json' is up to date because newest input '../procaptcha-common/src/tests/testModeBanner.unit.test.ts' is older than output '../procaptcha-common/tsconfig.tsbuildinfo'
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
10:40:51 PM - Project '../procaptcha/tsconfig.json' is up to date because newest input '../procaptcha/src/modules/Manager.ts' is older than output '../procaptcha/tsconfig.tsbuildinfo'
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
10:40:51 PM - Project 'tsconfig.json' is out of date because output file 'tsconfig.tsbuildinfo' does not exist
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
10:40:51 PM - Building project '/home/runner/work/captcha/captcha/packages/procaptcha-react/tsconfig.json'...
|
|
62
62
|
|
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
|
|
2
|
-
> @prosopo/procaptcha-react@2.
|
|
2
|
+
> @prosopo/procaptcha-react@2.13.1 build
|
|
3
3
|
> npm run build:cross-env -- --mode ${NODE_ENV:-development}
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
> @prosopo/procaptcha-react@2.
|
|
6
|
+
> @prosopo/procaptcha-react@2.13.1 build:cross-env
|
|
7
7
|
> vite build --config vite.esm.config.ts --mode production
|
|
8
8
|
|
|
9
9
|
ViteEsmConfig: .
|
|
@@ -11,8 +11,8 @@ ViteEsmConfig: .
|
|
|
11
11
|
tsConfigPaths: [
|
|
12
12
|
'/home/runner/work/captcha/captcha/packages/common/tsconfig.json',
|
|
13
13
|
'/home/runner/work/captcha/captcha/packages/locale/tsconfig.json',
|
|
14
|
-
'/home/runner/work/captcha/captcha/packages/logger/tsconfig.json',
|
|
15
14
|
'/home/runner/work/captcha/captcha/packages/util/tsconfig.json',
|
|
15
|
+
'/home/runner/work/captcha/captcha/packages/logger/tsconfig.json',
|
|
16
16
|
'/home/runner/work/captcha/captcha/packages/types/tsconfig.json',
|
|
17
17
|
'/home/runner/work/captcha/captcha/packages/util-crypto/tsconfig.json',
|
|
18
18
|
'/home/runner/work/captcha/captcha/packages/procaptcha/tsconfig.json',
|
|
@@ -31,8 +31,8 @@ ViteEsmConfig: .
|
|
|
31
31
|
externals: [
|
|
32
32
|
'@prosopo/common',
|
|
33
33
|
'@prosopo/locale',
|
|
34
|
-
'@prosopo/logger',
|
|
35
34
|
'@prosopo/util',
|
|
35
|
+
'@prosopo/logger',
|
|
36
36
|
'@prosopo/types',
|
|
37
37
|
'@prosopo/util-crypto',
|
|
38
38
|
'@prosopo/procaptcha',
|
|
@@ -59,11 +59,11 @@ dist/components/index.js 0.53 kB │ gzip: 0.19 kB
|
|
|
59
59
|
dist/util/index.js 0.56 kB │ gzip: 0.35 kB
|
|
60
60
|
dist/index.js 0.65 kB │ gzip: 0.21 kB
|
|
61
61
|
dist/components/procaptcha.js 0.68 kB │ gzip: 0.38 kB
|
|
62
|
-
dist/components/modal.js 1.
|
|
62
|
+
dist/components/modal.js 1.22 kB │ gzip: 0.61 kB
|
|
63
63
|
dist/components/collector.js 1.44 kB │ gzip: 0.66 kB
|
|
64
|
-
dist/components/button.js 3.
|
|
65
|
-
dist/components/captchaWidget.js 5.98 kB │ gzip: 2.27 kB
|
|
64
|
+
dist/components/button.js 3.03 kB │ gzip: 1.22 kB
|
|
66
65
|
dist/components/procaptchaWidget.js 6.00 kB │ gzip: 2.05 kB
|
|
67
|
-
dist/components/
|
|
66
|
+
dist/components/captchaWidget.js 6.92 kB │ gzip: 2.68 kB
|
|
67
|
+
dist/components/captchaComponent.js 7.28 kB │ gzip: 2.13 kB
|
|
68
68
|
|
|
69
|
-
[32m✓ built in
|
|
69
|
+
[32m✓ built in 40ms[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,91 @@
|
|
|
1
1
|
# @prosopo/procaptcha-react
|
|
2
2
|
|
|
3
|
+
## 2.13.1
|
|
4
|
+
### Patch Changes
|
|
5
|
+
|
|
6
|
+
- aed164d: Make asset signing survive contact with a CDN that actually enforces it.
|
|
7
|
+
|
|
8
|
+
Signing image URLs is optional and has been off in practice, so two paths that only break when a zone starts checking tokens have never been exercised. Both are fixed here, so enabling enforcement is a config change rather than a config change plus an incident.
|
|
9
|
+
|
|
10
|
+
**A dataset could not be imported.** Hashing an item downloads the image from the URL stored in the dataset, and that URL is the canonical, unsigned one. Against an enforcing zone every one of those downloads is rejected, so a dataset becomes impossible to import on exactly the configuration it is meant to be served from. `downloadImage` now signs the URL when a signing key is configured, and leaves it alone when one is not.
|
|
11
|
+
|
|
12
|
+
**A failed image never recovered.** The widget retried a broken image by appending a cache-busting query parameter. A token is a signature over the query string, so on a signed URL that parameter invalidates it and every retry is rejected — a transient failure turned permanent. The retry now re-requests the signed URL unchanged, and keeps the cache-buster for unsigned URLs, where it is still worth having and costs nothing.
|
|
13
|
+
|
|
14
|
+
The token scheme itself now lives in one place, `@prosopo/datasets`, rather than being written out twice. The provider keeps its synchronous implementation, because `resolveAsset` cannot await, but both ends build the string to hash and encode the result through the same helpers.
|
|
15
|
+
- 3d45c37: Stop the widget's hover highlights costing a phone user their first tap.
|
|
16
|
+
|
|
17
|
+
Tapping the checkbox on an iPhone did nothing the first time. The second tap worked. The cause is a rule iOS applies to every page: if the first tap on a control changes what is under the finger, Safari treats it as "show me the hover state" rather than "activate this", and withholds the click. Every control in the widget that lights up on hover was therefore asking to be tapped twice.
|
|
18
|
+
|
|
19
|
+
Hover feedback is now drawn only where a pointer can actually rest on something. On a desktop nothing changes. On a touch screen the highlight never appears and the first tap activates the control, which is what a visitor expects.
|
|
20
|
+
|
|
21
|
+
Four places had it, and only one of them was the checkbox:
|
|
22
|
+
|
|
23
|
+
- the **checkbox** — a state layer around the box
|
|
24
|
+
- the **reload button** in the challenge dialog — a fill change
|
|
25
|
+
- the dialog's **action buttons**, Cancel, Next and Submit — a state layer
|
|
26
|
+
- the **widget container** itself, via a CSS `:hover` rule. This one is easy to miss and matters most: the box that lights up is the one the checkbox sits inside, so it repaints under the finger even when the tap never touches a control with its own hover.
|
|
27
|
+
|
|
28
|
+
The first three ask `matchMedia("(hover: hover)")` before listening for the pointer at all; the fourth is wrapped in the matching media query. The image tiles never had hover feedback and are unchanged.
|
|
29
|
+
|
|
30
|
+
None of this came from the recent randomisation work — the React components these replaced carried the same handlers, so it has been there as long as the widget has.
|
|
31
|
+
|
|
32
|
+
Covered by a test per control asserting no state layer is drawn when the device reports it cannot hover, plus tests for the check itself, including that it assumes a pointer when the environment cannot answer — which is what keeps the existing desktop behaviour, and every existing hover test, intact.
|
|
33
|
+
- 4cc28db: Fix the image challenge laying out two columns instead of three on a phone, which pushed the last images below the screen where they could not be reached.
|
|
34
|
+
|
|
35
|
+
On a Samsung A52s the image grid is 345.578px wide. Each tile asked for `calc(33.333% - 5.33px)` at an 8px gap, so three tiles and the two gaps between them came to 345.584px — **six thousandths of a pixel too wide**. That is enough for the browser to wrap, so the challenge drew two columns and five rows rather than three and three. The panel went from about 370px tall to about 1080px, and three of the nine images ended up below a 718px viewport.
|
|
36
|
+
|
|
37
|
+
The tile width is now written as `calc((100% - 16px) / 3)`, leaving both the subtraction and the division to the browser. There is nothing left to round, so three tiles and their gaps come to exactly the width of the row at every gap the grid draws.
|
|
38
|
+
|
|
39
|
+
This arrived with the dialog randomisation, which replaced a fixed `calc(33.333% - 10px)` with a width derived from the randomised gap. The old value subtracted a whole gap per tile where only two thirds was needed, so it happened to leave about 10px of slack and always fitted. Deriving the width exactly removed the slack, and the rounding then tipped it over. Which gaps break depends on the arithmetic — 8, 11 and 14 round down and overflow, the rest do not — so it looked intermittent.
|
|
40
|
+
|
|
41
|
+
The panel that holds the images is also `touch-action: pan-y` rather than `touch-action: none` now. It is an `overflow-y: auto` box, so on a phone it is the thing a finger has to drag when the images do not fit, and `none` told the browser not to pan it at all — which is what turned "some images are off-screen" into "some images cannot be reached". Stopping the page behind from scrolling is `overscroll-behavior`'s job and it still does it. That one is older than the randomisation; it came in with the React to vanilla rewrite, and the React component before it had the same thing.
|
|
42
|
+
|
|
43
|
+
Covered by a test that works out where three tiles and two gaps land for every gap the challenge draws, against a set of panel widths including the A52s' 345.578px. It fails on the old expression with exactly the numbers measured on the device.
|
|
44
|
+
- Updated dependencies [3d45c37]
|
|
45
|
+
- Updated dependencies [4cc28db]
|
|
46
|
+
- Updated dependencies [d3b3286]
|
|
47
|
+
- @prosopo/widget-skeleton@2.10.0
|
|
48
|
+
- @prosopo/procaptcha-common@2.17.1
|
|
49
|
+
- @prosopo/procaptcha@2.11.19
|
|
50
|
+
|
|
51
|
+
## 2.13.0
|
|
52
|
+
### Minor Changes
|
|
53
|
+
|
|
54
|
+
- 5e5fb9e: Give the image challenge dialog different class names, a different DOM shape, different element types for its controls and slightly different spacing on every page load, so a solver script cannot hardcode a way to find or click anything inside it.
|
|
55
|
+
|
|
56
|
+
This is the same treatment the checkbox got. Until now the dialog was identical for every user, forever: the panel was always `.prosopo-modalInner` inside `.prosopo-modalOuter` inside `.prosopo-challenge-content`, every tile and every action was a `<button>`, the grid gap was always 10px, and the whole thing sat at the same offsets. The end-to-end suite was itself finding the images by climbing four parents and stepping sideways from the instruction line — which is the clearest possible evidence that the markup was a reliable handle.
|
|
57
|
+
|
|
58
|
+
Four things now change, all drawn fresh each time a challenge mounts:
|
|
59
|
+
|
|
60
|
+
- **The names.** Every class the dialog renders — the two modal layers, the challenge surface and its panel, the reload control — is an 8-character token drawn at mount. Selectors scraped from one page load are dead on the next. The challenge surface is shared with the puzzle, so the puzzle gets this too. Nothing in the widget's own CSS referenced these names, but a site that had written its own rules against `.prosopo-modalOuter`, `.prosopo-modalInner`, `.prosopo-challenge-content` or `.reload-button` will find they no longer match. `image-captcha` on the widget root stays fixed, as `prosopo-checkbox` did, because that one sits in the embedding page's light DOM.
|
|
61
|
+
- **The shape.** The panel, the header, the grid, the action row, each action and each tile sit inside a random number of randomly named `div` or `span` wrappers. The wrappers are `display: contents`, so they generate no boxes and cannot move anything: depth varies, layout does not. Only elements with no accessible role are used, so nothing new is announced to a screen reader.
|
|
62
|
+
- **The element each control is made of.** Every tile and every action is now randomly either a real `<button>` or a `<div role="button" tabindex="0">`, so `button` no longer finds them all. The generic variant is given by hand what the button gave for free: it is in the tab order, it activates on Enter and on Space, Space does not scroll the dialog, and it carries the same `aria-pressed` state. The surface's focus trap already matched `[tabindex]:not([tabindex="-1"])`, so both variants are trapped and tabbable alike.
|
|
63
|
+
- **The spacing.** The grid gap, the grid's and the header's padding, the action row's padding and the panel's own margins are each nudged a few pixels, so tile centres are not where they were last time. Each of these changes the panel's size rather than moving the panel, so the surface still centres it and a panel taller than the viewport still scrolls to its own edges. The tile and action widths are now derived from the gap they were drawn with — three columns fit only if each cell gives up two thirds of a gap, and the old hardcoded basis would have wrapped the row at the wide end of the range.
|
|
64
|
+
|
|
65
|
+
Three related fixes come with it:
|
|
66
|
+
|
|
67
|
+
- The tick badge on a selected tile was shipping `data-testid="CheckIcon"` to production, plus an `aria-label` on an `aria-hidden` element that nothing could ever read and a stray `color="#fff"` attribute. All three are gone.
|
|
68
|
+
- The reload button acted on any click, including one a script dispatched. It now takes the same trusted-event gate every other control in the widget has.
|
|
69
|
+
- The end-to-end suite no longer depends on the markup at all. It uses `data-cy` hooks — the instruction line, the challenge panel, the round, the reload control — that are withheld from production builds, exactly as the checkbox withholds `captcha-checkbox`.
|
|
70
|
+
|
|
71
|
+
Note what this does not do. The dialog still says it is a dialog, the tiles still expose the button role and their pressed state, and the images still carry alt text. A script that looks for what these things mean rather than what they are called will still find them. That is the price of the challenge being usable with a screen reader, and it is the right price; the point here is to kill the cheap selector, not to claim the dialog is unfindable.
|
|
72
|
+
|
|
73
|
+
### Patch Changes
|
|
74
|
+
|
|
75
|
+
- Updated dependencies [94929c3]
|
|
76
|
+
- Updated dependencies [a9141c3]
|
|
77
|
+
- Updated dependencies [a9141c3]
|
|
78
|
+
- Updated dependencies [a9141c3]
|
|
79
|
+
- Updated dependencies [e180281]
|
|
80
|
+
- Updated dependencies [5e5fb9e]
|
|
81
|
+
- @prosopo/procaptcha-common@2.17.0
|
|
82
|
+
- @prosopo/locale@3.6.0
|
|
83
|
+
- @prosopo/common@3.1.59
|
|
84
|
+
- @prosopo/types@5.10.0
|
|
85
|
+
- @prosopo/util@3.3.11
|
|
86
|
+
- @prosopo/widget-skeleton@2.9.0
|
|
87
|
+
- @prosopo/procaptcha@2.11.18
|
|
88
|
+
|
|
3
89
|
## 2.12.0
|
|
4
90
|
### Minor Changes
|
|
5
91
|
|
|
@@ -27,7 +27,10 @@ var mountButton = (container, initialProps) => {
|
|
|
27
27
|
let props = initialProps;
|
|
28
28
|
let hover = false;
|
|
29
29
|
let focusVisible = false;
|
|
30
|
-
const button = (0, _prosopo_procaptcha_common.
|
|
30
|
+
const button = (0, _prosopo_procaptcha_common.createControl)(teardown, { onActivate: (event) => {
|
|
31
|
+
event.preventDefault();
|
|
32
|
+
props.onClick();
|
|
33
|
+
} });
|
|
31
34
|
const buttonStyle = () => {
|
|
32
35
|
const theme = "light" === props.themeColor ? _prosopo_widget_skeleton.lightTheme : _prosopo_widget_skeleton.darkTheme;
|
|
33
36
|
const baseStyle = {
|
|
@@ -62,14 +65,16 @@ var mountButton = (container, initialProps) => {
|
|
|
62
65
|
});
|
|
63
66
|
button.textContent = props.text;
|
|
64
67
|
};
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
68
|
+
if ((0, _prosopo_widget_skeleton.canHover)()) {
|
|
69
|
+
teardown.addEventListener(button, "mouseenter", () => {
|
|
70
|
+
hover = true;
|
|
71
|
+
render();
|
|
72
|
+
});
|
|
73
|
+
teardown.addEventListener(button, "mouseleave", () => {
|
|
74
|
+
hover = false;
|
|
75
|
+
render();
|
|
76
|
+
});
|
|
77
|
+
}
|
|
73
78
|
teardown.addEventListener(button, "focus", () => {
|
|
74
79
|
focusVisible = button.matches(":focus-visible");
|
|
75
80
|
render();
|
|
@@ -78,11 +83,6 @@ var mountButton = (container, initialProps) => {
|
|
|
78
83
|
focusVisible = false;
|
|
79
84
|
render();
|
|
80
85
|
});
|
|
81
|
-
teardown.addEventListener(button, "click", (event) => {
|
|
82
|
-
if (!(0, _prosopo_procaptcha_common.isEventTrusted)(event)) return;
|
|
83
|
-
event.preventDefault();
|
|
84
|
-
props.onClick();
|
|
85
|
-
});
|
|
86
86
|
render();
|
|
87
87
|
container.appendChild(button);
|
|
88
88
|
return {
|
|
@@ -17,7 +17,7 @@ var outerStyle = (theme) => ({
|
|
|
17
17
|
borderRadius: theme.shape.card,
|
|
18
18
|
backgroundColor: theme.palette.background.default,
|
|
19
19
|
userSelect: "none",
|
|
20
|
-
touchAction: "
|
|
20
|
+
touchAction: "pan-y",
|
|
21
21
|
overscrollBehavior: "none"
|
|
22
22
|
});
|
|
23
23
|
var promptStyle = (theme) => ({
|
|
@@ -34,11 +34,22 @@ var targetStyle = (theme) => ({
|
|
|
34
34
|
color: theme.palette.titleAccent,
|
|
35
35
|
fontWeight: 700
|
|
36
36
|
});
|
|
37
|
-
var columnStyle = {
|
|
37
|
+
var columnStyle = (gap) => ({
|
|
38
38
|
position: "relative",
|
|
39
39
|
flexGrow: 1,
|
|
40
|
-
flexBasis:
|
|
41
|
-
};
|
|
40
|
+
flexBasis: (0, _prosopo_procaptcha_common.threeColumnBasis)(gap)
|
|
41
|
+
});
|
|
42
|
+
var SPACING_JITTER = 4;
|
|
43
|
+
var MIN_CONTROL_GAP = 6;
|
|
44
|
+
var MAX_CONTROL_GAP = 14;
|
|
45
|
+
var HEADER_PADDING_Y = 12;
|
|
46
|
+
var HEADER_PADDING_X = 14;
|
|
47
|
+
var HEADER_PADDING_JITTER = 3;
|
|
48
|
+
var MIN_WRAPPER_DEPTH = 0;
|
|
49
|
+
var MAX_WRAPPER_DEPTH = 2;
|
|
50
|
+
var decoy = (element) => (0, _prosopo_procaptcha_common.wrapRandomly)(element, MIN_WRAPPER_DEPTH, MAX_WRAPPER_DEPTH);
|
|
51
|
+
/** A spacing token moved by a few pixels, never below zero. */
|
|
52
|
+
var jittered = (base, jitter = SPACING_JITTER) => Math.max(0, base + (0, _prosopo_widget_skeleton.randomInt)(-jitter, jitter));
|
|
42
53
|
var mountCaptchaComponent = (container, initialProps) => {
|
|
43
54
|
let props = initialProps;
|
|
44
55
|
const themeOf = (themeColor) => "light" === themeColor ? _prosopo_widget_skeleton.lightTheme : _prosopo_widget_skeleton.darkTheme;
|
|
@@ -46,26 +57,29 @@ var mountCaptchaComponent = (container, initialProps) => {
|
|
|
46
57
|
const currentSolution = () => (0, _prosopo_util.at)(props.solutions, props.index, { noWrap: true });
|
|
47
58
|
const isLastRound = () => props.index >= props.challenge.captchas.length - 1;
|
|
48
59
|
const theme = themeOf(props.themeColor);
|
|
49
|
-
const
|
|
50
|
-
const
|
|
60
|
+
const controlGap = (0, _prosopo_widget_skeleton.randomInt)(MIN_CONTROL_GAP, MAX_CONTROL_GAP);
|
|
61
|
+
const column = columnStyle(controlGap);
|
|
51
62
|
const targetLabel = (0, _prosopo_procaptcha_common.createElement)("span", { style: targetStyle(theme) });
|
|
52
|
-
const promptText = (0, _prosopo_procaptcha_common.createElement)("p", {
|
|
63
|
+
const promptText = (0, _prosopo_procaptcha_common.createElement)("p", {
|
|
64
|
+
style: promptStyle(theme),
|
|
65
|
+
attributes: require_index.default({ dev: { cy: "captcha-prompt" } })
|
|
66
|
+
});
|
|
53
67
|
const hintText = (0, _prosopo_procaptcha_common.createElement)("p", { style: hintStyle(theme) });
|
|
54
68
|
const header = (0, _prosopo_procaptcha_common.createElement)("div", {
|
|
55
69
|
style: {
|
|
56
|
-
padding:
|
|
70
|
+
padding: `${jittered(HEADER_PADDING_Y, HEADER_PADDING_JITTER)}px ${jittered(HEADER_PADDING_X, HEADER_PADDING_JITTER)}px`,
|
|
57
71
|
fontFamily: theme.font.fontFamily
|
|
58
72
|
},
|
|
59
|
-
children: [promptText, hintText]
|
|
73
|
+
children: [decoy(promptText), decoy(hintText)]
|
|
60
74
|
});
|
|
61
75
|
const headerBar = (0, _prosopo_procaptcha_common.createElement)("div", {
|
|
62
76
|
style: {
|
|
63
77
|
backgroundColor: theme.palette.primaryContainer.main,
|
|
64
78
|
borderRadius: theme.shape.header,
|
|
65
79
|
width: "100%",
|
|
66
|
-
marginTop:
|
|
80
|
+
marginTop: `${jittered(theme.spacing.unit)}px`
|
|
67
81
|
},
|
|
68
|
-
children: [header]
|
|
82
|
+
children: [decoy(header)]
|
|
69
83
|
});
|
|
70
84
|
const headerRow = (0, _prosopo_procaptcha_common.createElement)("div", {
|
|
71
85
|
style: {
|
|
@@ -73,15 +87,15 @@ var mountCaptchaComponent = (container, initialProps) => {
|
|
|
73
87
|
alignItems: "center",
|
|
74
88
|
width: "100%"
|
|
75
89
|
},
|
|
76
|
-
children: [headerBar]
|
|
90
|
+
children: [decoy(headerBar)]
|
|
77
91
|
});
|
|
78
92
|
const gridHost = (0, _prosopo_procaptcha_common.createElement)("div", {
|
|
79
93
|
style: { overflow: "hidden" },
|
|
80
94
|
attributes: require_index.default({ dev: { cy: `captcha-${props.index}` } })
|
|
81
95
|
});
|
|
82
|
-
const cancelHost = (0, _prosopo_procaptcha_common.createElement)("div", { style:
|
|
83
|
-
const reloadHost = (0, _prosopo_procaptcha_common.createElement)("div", { style:
|
|
84
|
-
const nextHost = (0, _prosopo_procaptcha_common.createElement)("div", { style:
|
|
96
|
+
const cancelHost = (0, _prosopo_procaptcha_common.createElement)("div", { style: column });
|
|
97
|
+
const reloadHost = (0, _prosopo_procaptcha_common.createElement)("div", { style: column });
|
|
98
|
+
const nextHost = (0, _prosopo_procaptcha_common.createElement)("div", { style: column });
|
|
85
99
|
const controls = (0, _prosopo_procaptcha_common.createElement)("div", {
|
|
86
100
|
style: {
|
|
87
101
|
width: "100%",
|
|
@@ -89,12 +103,12 @@ var mountCaptchaComponent = (container, initialProps) => {
|
|
|
89
103
|
display: "flex",
|
|
90
104
|
flexDirection: "row",
|
|
91
105
|
justifyContent: "space-between",
|
|
92
|
-
gap:
|
|
106
|
+
gap: `${controlGap}px`
|
|
93
107
|
},
|
|
94
108
|
children: [
|
|
95
|
-
cancelHost,
|
|
96
|
-
reloadHost,
|
|
97
|
-
nextHost
|
|
109
|
+
decoy(cancelHost),
|
|
110
|
+
decoy(reloadHost),
|
|
111
|
+
decoy(nextHost)
|
|
98
112
|
]
|
|
99
113
|
});
|
|
100
114
|
const controlsRow = (0, _prosopo_procaptcha_common.createElement)("div", {
|
|
@@ -103,9 +117,9 @@ var mountCaptchaComponent = (container, initialProps) => {
|
|
|
103
117
|
alignItems: "center",
|
|
104
118
|
justifyContent: "space-between",
|
|
105
119
|
lineHeight: 1.75,
|
|
106
|
-
padding: `${
|
|
120
|
+
padding: `${jittered(theme.spacing.unit)}px 0 ${jittered(theme.spacing.unit * 2)}px 0`
|
|
107
121
|
},
|
|
108
|
-
children: [controls]
|
|
122
|
+
children: [decoy(controls)]
|
|
109
123
|
});
|
|
110
124
|
const inner = (0, _prosopo_procaptcha_common.createElement)("div", {
|
|
111
125
|
style: {
|
|
@@ -113,19 +127,20 @@ var mountCaptchaComponent = (container, initialProps) => {
|
|
|
113
127
|
display: "flex",
|
|
114
128
|
flexDirection: "column",
|
|
115
129
|
minWidth: "300px",
|
|
116
|
-
marginLeft:
|
|
117
|
-
marginRight:
|
|
130
|
+
marginLeft: `${jittered(theme.spacing.unit)}px`,
|
|
131
|
+
marginRight: `${jittered(theme.spacing.unit)}px`,
|
|
118
132
|
justifyContent: "center"
|
|
119
133
|
},
|
|
120
134
|
children: [
|
|
121
|
-
headerRow,
|
|
122
|
-
gridHost,
|
|
123
|
-
controlsRow
|
|
135
|
+
decoy(headerRow),
|
|
136
|
+
decoy(gridHost),
|
|
137
|
+
decoy(controlsRow)
|
|
124
138
|
]
|
|
125
139
|
});
|
|
126
140
|
const root = (0, _prosopo_procaptcha_common.createElement)("div", {
|
|
127
141
|
style: outerStyle(theme),
|
|
128
|
-
|
|
142
|
+
attributes: require_index.default({ dev: { cy: "captcha-panel" } }),
|
|
143
|
+
children: [decoy(inner)]
|
|
129
144
|
});
|
|
130
145
|
const grid = require_captchaWidget.mountCaptchaWidget(gridHost, {
|
|
131
146
|
challenge: currentCaptcha(),
|
|
@@ -6,23 +6,41 @@ var getHash = (item) => {
|
|
|
6
6
|
if (!item.hash) throw new _prosopo_common.ProsopoDatasetError("CAPTCHA.MISSING_ITEM_HASH", { context: { item } });
|
|
7
7
|
return item.hash;
|
|
8
8
|
};
|
|
9
|
-
var
|
|
9
|
+
var cellStyle = (gap) => ({
|
|
10
10
|
flexGrow: 1,
|
|
11
|
-
flexBasis:
|
|
11
|
+
flexBasis: (0, _prosopo_procaptcha_common.threeColumnBasis)(gap),
|
|
12
12
|
boxSizing: "border-box"
|
|
13
|
-
};
|
|
13
|
+
});
|
|
14
|
+
var MIN_GRID_GAP = 8;
|
|
15
|
+
var MAX_GRID_GAP = 15;
|
|
16
|
+
var GRID_PADDING_JITTER = 4;
|
|
17
|
+
var MIN_WRAPPER_DEPTH = 0;
|
|
18
|
+
var MAX_WRAPPER_DEPTH = 2;
|
|
14
19
|
var CHECK_ICON_PATH = "M9 16.17 4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z";
|
|
20
|
+
/**
|
|
21
|
+
* Asset signing is optional, so both shapes of URL arrive here. An unsigned one
|
|
22
|
+
* gets the cache-buster it always got; a signed one cannot, because its token
|
|
23
|
+
* is a signature over the query string and any addition invalidates it —
|
|
24
|
+
* turning a transient failure into a permanent 403.
|
|
25
|
+
*/
|
|
26
|
+
var retrySrc = (url) => url.includes("token=") ? url : `${url}${url.includes("?") ? "&" : "?"}retry=${Date.now()}`;
|
|
15
27
|
var themeOf = (themeColor) => "light" === themeColor ? _prosopo_widget_skeleton.lightTheme : _prosopo_widget_skeleton.darkTheme;
|
|
16
|
-
|
|
28
|
+
/** Drawn once per mounted grid, so tile centres move between page loads. */
|
|
29
|
+
var generateMetrics = (theme) => ({
|
|
30
|
+
gap: (0, _prosopo_widget_skeleton.randomInt)(MIN_GRID_GAP, MAX_GRID_GAP),
|
|
31
|
+
paddingTop: Math.max(0, theme.spacing.unit + (0, _prosopo_widget_skeleton.randomInt)(-GRID_PADDING_JITTER, GRID_PADDING_JITTER)),
|
|
32
|
+
paddingBottom: Math.max(0, theme.spacing.unit + (0, _prosopo_widget_skeleton.randomInt)(-GRID_PADDING_JITTER, GRID_PADDING_JITTER))
|
|
33
|
+
});
|
|
34
|
+
var gridStyle = (metrics) => ({
|
|
17
35
|
width: "100%",
|
|
18
36
|
height: "100%",
|
|
19
37
|
display: "flex",
|
|
20
38
|
flexDirection: "row",
|
|
21
39
|
flexWrap: "wrap",
|
|
22
40
|
justifyContent: "space-between",
|
|
23
|
-
paddingTop: `${
|
|
24
|
-
paddingBottom: `${
|
|
25
|
-
gap:
|
|
41
|
+
paddingTop: `${metrics.paddingTop}px`,
|
|
42
|
+
paddingBottom: `${metrics.paddingBottom}px`,
|
|
43
|
+
gap: `${metrics.gap}px`
|
|
26
44
|
});
|
|
27
45
|
var selectionStyle = (theme, selected) => ({
|
|
28
46
|
borderRadius: selected ? theme.shape.tileSelected : theme.shape.tile,
|
|
@@ -42,7 +60,8 @@ var mountCaptchaWidget = (container, initialProps) => {
|
|
|
42
60
|
let renderedChallenge;
|
|
43
61
|
let tiles = [];
|
|
44
62
|
let focusedHash = null;
|
|
45
|
-
const
|
|
63
|
+
const metrics = generateMetrics(themeOf(initialProps.themeColor));
|
|
64
|
+
const grid = (0, _prosopo_procaptcha_common.createElement)("div", { style: gridStyle(metrics) });
|
|
46
65
|
const buildTile = (item, index) => {
|
|
47
66
|
const hash = getHash(item);
|
|
48
67
|
const theme = themeOf(props.themeColor);
|
|
@@ -67,7 +86,10 @@ var mountCaptchaWidget = (container, initialProps) => {
|
|
|
67
86
|
teardown.addEventListener(image, "error", () => {
|
|
68
87
|
const retryCount = Number(image.dataset.retryCount ?? "0") + 1;
|
|
69
88
|
image.dataset.retryCount = String(retryCount);
|
|
70
|
-
if (retryCount <= 3)
|
|
89
|
+
if (retryCount <= 3) {
|
|
90
|
+
image.removeAttribute("src");
|
|
91
|
+
image.src = retrySrc(item.data);
|
|
92
|
+
}
|
|
71
93
|
});
|
|
72
94
|
const icon = (0, _prosopo_procaptcha_common.createSvgElement)("svg", {
|
|
73
95
|
style: {
|
|
@@ -84,11 +106,8 @@ var mountCaptchaWidget = (container, initialProps) => {
|
|
|
84
106
|
},
|
|
85
107
|
attributes: {
|
|
86
108
|
focusable: "false",
|
|
87
|
-
color: "#fff",
|
|
88
109
|
"aria-hidden": "true",
|
|
89
|
-
viewBox: "0 0 24 24"
|
|
90
|
-
"data-testid": "CheckIcon",
|
|
91
|
-
"aria-label": "Check icon"
|
|
110
|
+
viewBox: "0 0 24 24"
|
|
92
111
|
},
|
|
93
112
|
children: [(0, _prosopo_procaptcha_common.createSvgElement)("path", { attributes: { d: CHECK_ICON_PATH } })]
|
|
94
113
|
});
|
|
@@ -110,7 +129,7 @@ var mountCaptchaWidget = (container, initialProps) => {
|
|
|
110
129
|
},
|
|
111
130
|
children: [icon]
|
|
112
131
|
});
|
|
113
|
-
const clickable = (0, _prosopo_procaptcha_common.
|
|
132
|
+
const clickable = (0, _prosopo_procaptcha_common.createControl)(teardown, {
|
|
114
133
|
style: {
|
|
115
134
|
position: "relative",
|
|
116
135
|
cursor: "pointer",
|
|
@@ -123,13 +142,11 @@ var mountCaptchaWidget = (container, initialProps) => {
|
|
|
123
142
|
appearance: "none",
|
|
124
143
|
display: "block"
|
|
125
144
|
},
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const mouseEvent = event;
|
|
132
|
-
props.onClick(hash, mouseEvent.clientX, mouseEvent.clientY);
|
|
145
|
+
children: [image, overlay],
|
|
146
|
+
onActivate: (event) => {
|
|
147
|
+
const fromPointer = "clientX" in event;
|
|
148
|
+
props.onClick(hash, fromPointer ? event.clientX : 0, fromPointer ? event.clientY : 0);
|
|
149
|
+
}
|
|
133
150
|
});
|
|
134
151
|
teardown.addEventListener(clickable, "focus", () => {
|
|
135
152
|
focusedHash = clickable.matches(":focus-visible") ? hash : null;
|
|
@@ -140,10 +157,10 @@ var mountCaptchaWidget = (container, initialProps) => {
|
|
|
140
157
|
applyFocusRing();
|
|
141
158
|
});
|
|
142
159
|
const cell = (0, _prosopo_procaptcha_common.createElement)("div", {
|
|
143
|
-
style:
|
|
160
|
+
style: cellStyle(metrics.gap),
|
|
144
161
|
children: [clickable]
|
|
145
162
|
});
|
|
146
|
-
grid.appendChild(cell);
|
|
163
|
+
grid.appendChild((0, _prosopo_procaptcha_common.wrapRandomly)(cell, MIN_WRAPPER_DEPTH, MAX_WRAPPER_DEPTH));
|
|
147
164
|
return {
|
|
148
165
|
hash,
|
|
149
166
|
image,
|
|
@@ -165,7 +182,7 @@ var mountCaptchaWidget = (container, initialProps) => {
|
|
|
165
182
|
teardown.run();
|
|
166
183
|
teardown = new _prosopo_procaptcha_common.Teardown();
|
|
167
184
|
(0, _prosopo_procaptcha_common.clearElement)(grid);
|
|
168
|
-
(0, _prosopo_procaptcha_common.applyStyles)(grid, gridStyle(
|
|
185
|
+
(0, _prosopo_procaptcha_common.applyStyles)(grid, gridStyle(metrics));
|
|
169
186
|
tiles = props.challenge.items.map(buildTile);
|
|
170
187
|
renderedChallenge = props.challenge;
|
|
171
188
|
};
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
let _prosopo_procaptcha_common = require("@prosopo/procaptcha-common");
|
|
2
|
+
let _prosopo_widget_skeleton = require("@prosopo/widget-skeleton");
|
|
2
3
|
//#region src/components/modal.ts
|
|
4
|
+
var MIN_WRAPPER_DEPTH = 1;
|
|
5
|
+
var MAX_WRAPPER_DEPTH = 3;
|
|
3
6
|
/**
|
|
4
7
|
* The image captcha's dialog frame; positioning lives in `ChallengeSurface`.
|
|
5
8
|
*/
|
|
6
9
|
var mountModal = (initialProps) => {
|
|
7
|
-
const inner = (0, _prosopo_procaptcha_common.createElement)("div", { className:
|
|
10
|
+
const inner = (0, _prosopo_procaptcha_common.createElement)("div", { className: (0, _prosopo_widget_skeleton.randomToken)() });
|
|
8
11
|
(0, _prosopo_procaptcha_common.applyStyles)(inner, {
|
|
9
12
|
maxWidth: "500px",
|
|
10
13
|
maxHeight: "100%",
|
|
@@ -14,17 +17,18 @@ var mountModal = (initialProps) => {
|
|
|
14
17
|
alignSelf: "center",
|
|
15
18
|
boxSizing: "border-box"
|
|
16
19
|
});
|
|
20
|
+
const outerClassName = (0, _prosopo_widget_skeleton.randomToken)();
|
|
17
21
|
const surfaceProps = (props) => ({
|
|
18
22
|
show: props.show,
|
|
19
23
|
placement: props.placement,
|
|
20
24
|
anchor: props.anchor,
|
|
21
25
|
onDismiss: props.onDismiss,
|
|
22
26
|
scrim: "none",
|
|
23
|
-
className:
|
|
27
|
+
className: outerClassName,
|
|
24
28
|
dialogLabel: props.dialogLabel
|
|
25
29
|
});
|
|
26
30
|
const surface = (0, _prosopo_procaptcha_common.mountChallengeSurface)(surfaceProps(initialProps));
|
|
27
|
-
surface.content.appendChild(inner);
|
|
31
|
+
surface.content.appendChild((0, _prosopo_procaptcha_common.wrapRandomly)(inner, MIN_WRAPPER_DEPTH, MAX_WRAPPER_DEPTH));
|
|
28
32
|
return {
|
|
29
33
|
content: inner,
|
|
30
34
|
update: (props) => surface.update(surfaceProps(props)),
|