Repository navigation
fix(procaptcha-frictionless): share one detector assignment across widgets on a page - #3156
Conversation
…dgets takePrefetchedDetector removed the in-flight entry on claim, so on a page carrying several widgets only the first shared the prefetch and every other widget issued its own provider resolve plus assignDetectorBundle. One production integration mounts a widget per form — eight on a property page, with the bundle included twice — so a single page view cost sixteen assign calls instead of one. That was enough on its own to push ordinary visitors past a per-IP rate detector. Sharing is sound: detectorSessionId binds to a bundleId in Redis purely so the provider can resolve which cipher keys decrypt that widget's SIMD readings (resolveBundleByDetectorSession). Widgets sharing an assignment run the same bundle, so those are the right keys — it is a lookup, not a one-shot token. The guarantee delete-on-claim existed for is kept by two narrower mechanisms: a prefetch that rejects drops itself, so no later widget inherits a failed pin, and entries go stale after 60s so a widget mounting much later re-resolves rather than reusing an aged provider pin. Retries were never affected either way — customDetectBot only claims on a first attempt. The existing single-use test asserted the mechanism rather than the guarantee; it now pins shareability, with a sibling test for the failed-pin case. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
No updates since Nothing is lost — Converting to draft disables auto-merge, so PRs with auto-merge enabled are excluded from this entirely. |
|
No updates since Nothing is lost — Converting to draft disables auto-merge, so PRs with auto-merge enabled are excluded from this entirely. |
|
No updates since Nothing is lost — Converting to draft disables auto-merge, so PRs with auto-merge enabled are excluded from this entirely. |
|
No updates since Nothing is lost — Converting to draft disables auto-merge, so PRs with auto-merge enabled are excluded from this entirely. |
|
No updates since Nothing is lost — Converting to draft disables auto-merge, so PRs with auto-merge enabled are excluded from this entirely. |
|
No updates since Nothing is lost — Converting to draft disables auto-merge, so PRs with auto-merge enabled are excluded from this entirely. |
|
No updates since Nothing is lost — Converting to draft disables auto-merge, so PRs with auto-merge enabled are excluded from this entirely. |
|
No updates since Nothing is lost — Converting to draft disables auto-merge, so PRs with auto-merge enabled are excluded from this entirely. |
|
No updates since Nothing is lost — Converting to draft disables auto-merge, so PRs with auto-merge enabled are excluded from this entirely. |
|
No updates since Nothing is lost — Converting to draft disables auto-merge, so PRs with auto-merge enabled are excluded from this entirely. |
|
No updates since Nothing is lost — Converting to draft disables auto-merge, so PRs with auto-merge enabled are excluded from this entirely. |
|
No updates since Nothing is lost — Converting to draft disables auto-merge, so PRs with auto-merge enabled are excluded from this entirely. |
|
No updates since Nothing is lost — Converting to draft disables auto-merge, so PRs with auto-merge enabled are excluded from this entirely. |
|
No updates since Nothing is lost — Converting to draft disables auto-merge, so PRs with auto-merge enabled are excluded from this entirely. |
|
No updates since Nothing is lost — Converting to draft disables auto-merge, so PRs with auto-merge enabled are excluded from this entirely. |
|
No updates since Nothing is lost — Converting to draft disables auto-merge, so PRs with auto-merge enabled are excluded from this entirely. |
|
No updates since Nothing is lost — Converting to draft disables auto-merge, so PRs with auto-merge enabled are excluded from this entirely. |
|
No updates since Nothing is lost — Converting to draft disables auto-merge, so PRs with auto-merge enabled are excluded from this entirely. |
|
No updates since Nothing is lost — Converting to draft disables auto-merge, so PRs with auto-merge enabled are excluded from this entirely. |
|
No updates since Nothing is lost — Converting to draft disables auto-merge, so PRs with auto-merge enabled are excluded from this entirely. |
|
No updates since Nothing is lost — Converting to draft disables auto-merge, so PRs with auto-merge enabled are excluded from this entirely. |
|
No updates since Nothing is lost — Converting to draft disables auto-merge, so PRs with auto-merge enabled are excluded from this entirely. |
|
No updates since Nothing is lost — Converting to draft disables auto-merge, so PRs with auto-merge enabled are excluded from this entirely. |
|
No updates since Nothing is lost — Converting to draft disables auto-merge, so PRs with auto-merge enabled are excluded from this entirely. |
|
No updates since Nothing is lost — Converting to draft disables auto-merge, so PRs with auto-merge enabled are excluded from this entirely. |
|
No updates since Nothing is lost — Converting to draft disables auto-merge, so PRs with auto-merge enabled are excluded from this entirely. |
- prefetchDetector now treats an entry older than the TTL as absent, so a widget rendered after a minute starts a fresh prefetch instead of being blocked by the stale one. - Remove detectorPrefetch.multiWidget.test.ts: it never mocked the provider, so it made real network calls and failed in CI when the call succeeded. Its TTL case moves into the mocked detectorPrefetch.test.ts alongside a new stale-replacement case; the others were already covered there. - Update the module header, which still described the cache as single-use.
|
don't merge - currently testing that this properly works on my local |
takePrefetchedDetectorremoved the in-flight entry on claim. On a page carrying several widgets that meant the first widget shared the prefetch and every other widget issued its own provider resolve plusassignDetectorBundle.Measured on a live integration with Chrome CDP: 8 widget instances on one page (a widget per form), bundle included twice, and 16 assign calls fire on a single page load — all within 0.0s of each other. Idling 45s adds none, so it is not a retry or polling loop; it is one call per widget per bundle include.
The guarantee delete-on-claim was protecting
Its comment said "so a retry does not reuse a pin that may have just failed". That is kept, by two narrower mechanisms:
Retries were never affected either way —
customDetectBotonly claims on a first attempt (isFirstAttempt), so a retry re-resolves by construction.Tests
All in
detectorPrefetch.test.ts, with the provider mocked:procaptcha-frictionless: 16 test files, 170 tests pass;build:tscclean.