Commit Graph

5 Commits

Author SHA1 Message Date
Kazuki Yamada 2a0922d114 refactor(website): Address claude follow-up review on Turnstile pre-mint
- Drop the unused `error` ref from `useTurnstile`. The widget-level
  error-callback writes had no observer (only `usePackRequest.error`
  feeds the UI), so the export and writes were vestigial.
- Drop `getResponse` from `TurnstileGlobal`. Never called anywhere in
  the codebase; clearer to leave it off the typed surface.
- Don't `console.warn` on normal cancel/timeout flows in
  `acquireTurnstileToken`. Move the warn after the `signal.aborted`
  check so the dev console only logs genuine challenge / script-load
  failures.
- Hoist the consecutive `if (widgetId.value)` guards in `mintToken` by
  capturing the rendered widget id into a local const after the throw.
- Drop the redundant `userTouched.value` check in the post-pack
  pre-mint guard. `userTouched` is necessarily true at this point —
  it was a precondition for `isSubmitValid` being true when the submit
  started.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 22:28:33 +09:00
Kazuki Yamada b9a9e719f8 fix(website): Address PR review feedback on Turnstile pre-mint
- Drop the unused `invalidateCache` export from useTurnstile. Both call
  paths (takeToken cache claim, expired-callback) already null
  cachedToken inline, so the helper had no callers.
- Update stale `turnstile.getToken()` references in usePackRequest and
  useTurnstileScript comments to match the renamed `takeToken()` /
  `preMintToken()` API.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 21:41:48 +09:00
Kazuki Yamada 0f3a6e69a1 fix(website): Address review feedback on PR #1541
intent(comment-drift): Claude's review surfaced three stale comments that no longer match the code after pre-warm was narrowed to the script-load step. No behaviour change — the comments were lying about what the code actually does now.

fix(useTurnstileScript-jsdoc): The JSDoc on `TurnstileRenderOptions.execution` claimed `'execute'` was chosen so the pre-warm path could render() without firing a challenge. PR #1541 proved that's not what `'execute'` does in practice — render() itself counts toward the dashboard's challenge counters, regardless of `execution`. Rewrote the comment to explain why we still pass `'execute'` (token-mint guardrail) and why we no longer pre-warm by rendering.

fix(useTurnstile-render-comment): The inline comment at the render() call site said this option is "what makes the pre-warm in setContainer() free of side-effects". setContainer() no longer calls render(), so the rationale is obsolete. Updated to describe the current role: a guardrail against an accidental render() minting a token before getToken() is ready.

fix(useTurnstile-race-comment): The single-flight cache comment said "pre-warm and getToken() can both race past the widgetId.value null check". Pre-warm doesn't enter ensureWidget anymore, so only back-to-back getToken() submits can race. Updated to reflect the narrower scope.

perf(preconnect-crossorigin): Add a `crossorigin` companion to the existing `<link rel="preconnect">`. Turnstile's `api.js` is fetched anonymously, but the Turnstile iframe issues CORS sub-requests on a separate browser connection pool. Without both hints, the iframe's first handshake still happens on click. Cheap defensive addition.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 15:37:59 +09:00
Kazuki Yamada 0e0f572964 fix(website): Defer Turnstile challenge with execution='execute'
intent(token-waste): デプロイ後の Turnstile ダッシュボードで「解決チャレンジ 90 / siteverify 22」というギャップ (約 68 個の token が無駄に発行されて捨てられている) と「未解決チャレンジ 127 件 / ボット可能性 58.53%」が観測された。

fix(root-cause): Cloudflare Turnstile の invisible widget はデフォルトで `execution: 'render'` モードであり、`render()` 直後に automatic challenge を実行して token を発行する。我々の pre-warm ロジック (commit `68090aa2`) は render() で widget を立ち上げると同時に challenge も走らせていた。pack ボタンを押すと execute() で 2 度目の challenge が走り、最初の token は捨てられる。さらに正規クローラ (Googlebot 等) がホームページを訪れた際、JS 実行で widget が render → automatic challenge → クローラは解けず「未解決」としてカウント、これがボット率を inflate していた。

fix(execution-execute): render オプションに `execution: 'execute'` を追加。これで pre-warm は script load + widget shell のみ実行し、challenge は明示的な turnstile.execute() (= ユーザが pack ボタン押下時) まで遅延する。pre-warm の latency 削減効果はそのまま、token 浪費とクローラ false-positive は消える。

constraint(type-update): TurnstileRenderOptions interface に `execution?: 'render' | 'execute'` を追加して TypeScript で表現可能に。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 13:09:42 +09:00
Kazuki Yamada b58644154e fix(website): Address claude iteration 2 review
intent(maintainability): claude のフォローアップレビューで残った Improve / test gap / nit を対応。production 影響はないが将来の保守性を上げる。

fix(server/log-helper): turnstile.ts で 7 回繰り返されていた `{ event, outcome, reason, requestId, source, ...cf }` の log payload 構築を `rejectAndLog(reason, message, level, extra)` ヘルパーにまとめた。約 40 行短縮、新しい reason を追加するときに envelope を取りこぼすリスクが消える。
fix(server/extract-siteverify): 同ファイル内に `runSiteverify` を抽出して try/catch を内包。中身が無関係な fail-closed 経路 (network error, JSON parse error) を Error sentinel として返し、middleware 本体はポリシー(reject vs pass)だけに集中させる。
fix(client/cli-fallback): script-blocked エラー時のメッセージに `npx repomix --remote owner/repo` の CLI 案内を追加。privacy extension で詰まったユーザーへの逃げ道を提示。
fix(client/file-size): useTurnstile.ts が 286 行 → 250 行ガイドライン超過していたため、script load 部分を `useTurnstileScript.ts` に分離。useTurnstile 側は widget lifecycle / token request / abort に集中。

test(turnstile): 18 → 20 cases。
- siteverify が non-JSON (HTML 5xx ページ等) を返した時に runSiteverify が Error sentinel に変換し、middleware が 403 fail-closed する経路をカバー
- cross-stack contract: server の EXPECTED_TURNSTILE_ACTION (export) と client の useTurnstile.ts に埋め込まれた `action: 'pack'` 文字列が一致する、を regex で検証。片側 rename で他方が silent break する drift を防ぐ

skipped (with reason):
- Log sampling (claude #1): 現状スケール (21K req/day = 0.24 req/sec) で Cloud Logging quota 影響なし、metric 移行のときに併せて検討
- secretMissingLogged tautology (claude #2): 既に `0b09cf9d` で logger spy に置き換え済み
- hostname check (claude #3): 既に `0b09cf9d` で ALLOWED_HOSTNAMES として実装済み
- siteverify timeout fake-timer test: codex も round 2 で skip 推奨、setup が複雑で得る保証が小さい
- Client useTurnstile.ts component test: Vue test 環境が未整備
- External /api/pack consumer release note: code change ではないので別途
- secret_missing alert: out of scope の follow-up issue

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 01:15:51 +09:00