diff --git a/website/client/composables/turnstileSubmit.ts b/website/client/composables/turnstileSubmit.ts index 4a27ee3c..8b2fdc39 100644 --- a/website/client/composables/turnstileSubmit.ts +++ b/website/client/composables/turnstileSubmit.ts @@ -26,10 +26,12 @@ export async function acquireTurnstileToken( try { return { kind: 'token', token: await turnstile.takeToken(signal) }; } catch (err) { - console.warn('Turnstile token acquisition failed:', err); + // Abort is a normal flow (user cancel, 30s timeout). Don't log it as + // a failure — only log genuine challenge / script-load errors. if (signal.aborted) { return { kind: 'aborted', reason: signal.reason }; } + console.warn('Turnstile token acquisition failed:', err); if (import.meta.env.PROD) { return { kind: 'error', message: turnstileFailureMessage(err) }; } diff --git a/website/client/composables/usePackRequest.ts b/website/client/composables/usePackRequest.ts index c9b5d9fc..97d45769 100644 --- a/website/client/composables/usePackRequest.ts +++ b/website/client/composables/usePackRequest.ts @@ -212,9 +212,12 @@ export function usePackRequest() { // Repeat-pack convenience: warm the cache for a likely follow-up // submission (option tweak + repack, or `repackWithSelectedFiles` // triggered from the result view). Skipped on abort/cancel since - // the user may have given up. Failures swallow silently — they - // surface on the next click via takeToken's cold path. - if (!controller.signal.aborted && isSubmitValid.value && userTouched.value) { + // the user may have given up, and on invalid form (user may have + // cleared the URL mid-request). userTouched is necessarily true + // here — it was a precondition for isSubmitValid to be true at + // submit start. Failures swallow silently — they surface on the + // next click via takeToken's cold path. + if (!controller.signal.aborted && isSubmitValid.value) { turnstile.preMintToken().catch(() => {}); } } diff --git a/website/client/composables/useTurnstile.ts b/website/client/composables/useTurnstile.ts index 13f2d8fd..74c27b42 100644 --- a/website/client/composables/useTurnstile.ts +++ b/website/client/composables/useTurnstile.ts @@ -32,7 +32,6 @@ const MINT_TIMEOUT_MS = 15_000; export function useTurnstile() { const widgetId = ref(null); const containerEl = ref(null); - const error = ref(null); // Resolved when the next widget callback produces a token. Reassigned on // every mint so back-to-back submits don't share state. @@ -99,10 +98,8 @@ export function useTurnstile() { } }, 'error-callback': (errorCode: string) => { - const message = `Turnstile error: ${errorCode}`; - error.value = message; if (pendingReject) { - pendingReject(new Error(message)); + pendingReject(new Error(`Turnstile error: ${errorCode}`)); pendingResolve = null; pendingReject = null; } @@ -147,7 +144,8 @@ export function useTurnstile() { throw new Error('Turnstile container element not registered'); } const turnstile = await ensureWidget(containerEl.value); - if (!widgetId.value) { + const renderedWidgetId = widgetId.value; + if (!renderedWidgetId) { throw new Error('Turnstile widget failed to render'); } @@ -182,8 +180,8 @@ export function useTurnstile() { }; // Tokens are 1-shot, so reset() before each execute() to clear any // stale challenge state inside the widget itself. - if (widgetId.value) turnstile.reset(widgetId.value); - if (widgetId.value) turnstile.execute(widgetId.value); + turnstile.reset(renderedWidgetId); + turnstile.execute(renderedWidgetId); }); const timeoutPromise = new Promise((_, reject) => { diff --git a/website/client/composables/useTurnstileScript.ts b/website/client/composables/useTurnstileScript.ts index fc9b7089..d76236be 100644 --- a/website/client/composables/useTurnstileScript.ts +++ b/website/client/composables/useTurnstileScript.ts @@ -14,7 +14,6 @@ export interface TurnstileGlobal { execute: (widgetId: string) => void; reset: (widgetId: string) => void; remove: (widgetId: string) => void; - getResponse: (widgetId: string) => string | undefined; } export interface TurnstileRenderOptions {