feat(js,shared): resolve Protect challenges automatically via a managed modal or inline marker - #9418
Conversation
…ed modal or inline marker
🦋 Changeset detectedLatest commit: a3e2b4a The changes in this PR will be included in the next version bump. This PR includes changesets to release 23 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a3e2b4a21a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| }); | ||
| } | ||
|
|
||
| const container = await waitForElement(`#${PROTECT_CHECK_MODAL_CONTAINER_ID}`); |
There was a problem hiding this comment.
Implement the modal before waiting for its container
When a custom-flow app has no clerk-protect-check marker, this waits forever: the reviewed packages/ui/src/Components.tsx has no protectCheckModal state, renderer, or ProtectCheckModal, and a repo-wide search finds no element creating cl-modal-protect-check-container. openModal('protectCheck') therefore only writes an unused dynamic state key, while waitForElement() has no timeout and never resolves null, leaving the original sign-in/sign-up promise permanently pending.
Useful? React with 👍 / 👎.
| if (!gated || this.hasRegisteredHost(gated.flow)) { | ||
| return current; |
There was a problem hiding this comment.
Register prebuilt hosts before intercepting their responses
When a mounted prebuilt SignIn or SignUp submits a request that returns protect_check, hasRegisteredHost() is always false because the new __internal_registerProtectCheckHost API has no caller anywhere in the reviewed tree. Since interception happens inside BaseResource._fetch before the resource reaches the prebuilt router, the existing inline Protect card never receives the gated state; the request is diverted into managed handling instead (and currently hangs on the absent modal), contrary to the promised preserved inline experience.
Useful? React with 👍 / 👎.
Description
Stacked on #9391. Second step toward fully Clerk-owned Protect challenge support for custom-flow (headless) apps: clerk-js now notices a challenge-gated response at the resource layer and resolves it itself, so the calling code's
awaitsimply completes with the post-challenge state — no customer challenge API, mirroring the bot-protection CAPTCHA contract.ProtectCheckGate(core/protectCheckGate.ts): inspects successful payloads for a direct sign-in/sign-up response carrying a pendingprotect_check. When gated it acquires a host — the<div id="clerk-protect-check" />placement marker when the page provides one, otherwise the managed modal — drives the shared challenge lifecycle from refactor(shared,ui): extract the Protect check lifecycle into shared internal helpers #9391 (executeProtectCheckWithTimeout→PATCH …/protect_check→submitProtectCheckProof), loops chained challenges and expired-challenge reloads with caps, treatsprotect_check_already_resolvedas soft success, and returns the post-challenge payload as the original call's result. One challenge session at a time: concurrent gated calls wait, then replay (the stored proof on the attempt lets replays pass).FraudProtection.executegains an optional raw-fetch argument and funnels every result path (including post-captcha replays) through the gate;BaseResource._fetchsupplies the raw fetch so the gate's own PATCH/GET calls get full resource-call semantics (client piggyback updates,ClerkAPIResponseErroron 4xx) without re-enteringFraudProtection.__internal_openProtectCheckModal/__internal_closeProtectCheckModal(same#clerkUIplumbing as the blank captcha modal) and__internal_registerProtectCheckHost(flow), which lets surfaces that render challenges themselves (prebuilt components, the upcoming inline marker component) suspend managed handling for their flow. All additive and__internal_-prefixed.setWidgetVisiblehandshake — plus a short delay reveal for long-running checks (proof-of-transfer) so a slow solve is never an invisible frozen page.protect_check_unsupported_environment).Inert on its own: the ui package's
ProtectCheckModal(next PR) provides the modal markup, and FAPI does not surfaceprotect_checkuntil the server-side version gates are raised. Rollout stays deploy-then-enable.Checklist
pnpm testruns as expected.pnpm buildruns as expected.Type of change