@qaflo/forms-client 1.3.0 → 1.4.0

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/form.d.ts CHANGED
@@ -19,6 +19,9 @@ export interface ContactFormConfig {
19
19
  * labelled line in the notification.
20
20
  */
21
21
  exclude?: string[]
22
+ /** Abort the request after this many ms. A hung request otherwise leaves
23
+ * the button on "Sending…" forever. */
24
+ timeout?: number
22
25
  onSuccess?: (values: Record<string, string>) => void
23
26
  /** Called on every failed submit, including an unsolved captcha. */
24
27
  onError?: (
package/form.js CHANGED
@@ -95,6 +95,7 @@ export function useContactFormWith(widget, config = {}) {
95
95
  honeypotName = 'website',
96
96
  idPrefix = 'cf-',
97
97
  exclude = [],
98
+ timeout,
98
99
  onSuccess,
99
100
  onError,
100
101
  } = config
@@ -167,7 +168,16 @@ export function useContactFormWith(widget, config = {}) {
167
168
 
168
169
  const body = buildBody(values, { message, subject, honeypotName, exclude })
169
170
 
170
- const result = await widget.submit(body)
171
+ // Without a deadline a hung request leaves the button on "Sending…"
172
+ // forever, with no way back except reloading the page.
173
+ let abort = null
174
+ let timer = null
175
+ if (timeout) {
176
+ abort = new AbortController()
177
+ timer = setTimeout(() => abort.abort(), timeout)
178
+ }
179
+ const result = await widget.submit(body, abort ? { signal: abort.signal } : undefined)
180
+ if (timer) clearTimeout(timer)
171
181
 
172
182
  if (result.ok) {
173
183
  setState('ok')
@@ -185,7 +195,7 @@ export function useContactFormWith(widget, config = {}) {
185
195
  // attempt looks like it did nothing at all.
186
196
  if (onError) onError(result, values)
187
197
  },
188
- [fields, messages, compose, subject, honeypotName, idPrefix, exclude, onSuccess, onError, widget, fieldOrder],
198
+ [fields, messages, compose, subject, honeypotName, idPrefix, exclude, timeout, onSuccess, onError, widget, fieldOrder],
189
199
  )
190
200
 
191
201
  const invalid = useCallback((name) => (showErrors ? errors[name] : undefined), [showErrors, errors])
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qaflo/forms-client",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "Client half of the shared qaflo contact-form service: transport, Turnstile lifecycle and visitor-facing status messages.",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/turnstile.d.ts CHANGED
@@ -28,7 +28,7 @@ export interface TurnstileHandle {
28
28
  getToken: () => string
29
29
  reset: () => void
30
30
  /** Reads the token, refuses without one, posts, then resets this widget. */
31
- submit: (body: SubmitBody) => Promise<SubmitResult>
31
+ submit: (body: SubmitBody, options?: { signal?: AbortSignal }) => Promise<SubmitResult>
32
32
  ready: boolean
33
33
  }
34
34
 
package/turnstile.js CHANGED
@@ -159,7 +159,7 @@ export function useTurnstile(config) {
159
159
  * @param {Record<string, unknown>} body
160
160
  */
161
161
  const submit = useCallback(
162
- async (body) => {
162
+ async (body, options = {}) => {
163
163
  const token = getToken()
164
164
  if (!token) {
165
165
  return { ok: false, status: 0, message: SECURITY_CHECK_PENDING }
@@ -169,6 +169,7 @@ export function useTurnstile(config) {
169
169
  token,
170
170
  messages: config.messages,
171
171
  fallbackEmail: config.fallbackEmail,
172
+ signal: options.signal,
172
173
  })
173
174
  } finally {
174
175
  reset()