@arquimedes.co/eureka-forms 3.0.60-test → 3.0.60

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/dist/Form/Form.js CHANGED
@@ -6,7 +6,7 @@ import InternalFormStyle from '../constants/InternalFormStyle';
6
6
  import ColumnForm from './FormTypes/ColumnForm/ColumnForm';
7
7
  import StepperForm from './FormTypes/StepperForm/StepperForm';
8
8
  import { FormStyleTypes, FormTypes } from '../constants/FormStepTypes';
9
- import { calcValue } from './FormFunctions';
9
+ import { calcValue, resolveCaptchaToken } from './FormFunctions';
10
10
  import { FormProvider, useForm } from 'react-hook-form';
11
11
  import widgetInstance from '../Utils/AxiosWidget';
12
12
  import axiosInstance from '../Utils/AxiosAPI';
@@ -38,15 +38,16 @@ function FormComponent({ form, apiKey, reload, isWidget, branding, setSubmit, sc
38
38
  // eslint-disable-next-line react-hooks/exhaustive-deps
39
39
  }, [idFocusStep]);
40
40
  const onSubmit = useCallback(async (values) => {
41
- let token = null;
42
- if (form.hasCaptcha && !internal && !customSubmit) {
43
- token = recaptchaRef.current?.getValue();
44
- token ?? (token = await recaptchaRef?.current.executeAsync());
45
- }
46
41
  const ApiKey = internal ? form.apiKey : apiKey;
47
42
  if (!ApiKey && !customSubmit && !customSubmitBtns)
48
43
  return;
49
44
  try {
45
+ // Resolved inside the try so a captcha that cannot mint a token surfaces through the same
46
+ // error path as any other submit failure, rather than rejecting out of the handler and
47
+ // leaving the submitter with no feedback at all.
48
+ const token = form.hasCaptcha && !internal && !customSubmit
49
+ ? await resolveCaptchaToken(recaptchaRef.current)
50
+ : null;
50
51
  const state = await dispatch(getAppState({ idForm })).unwrap();
51
52
  const newValues = {
52
53
  ...state.site.values.global,
@@ -4,6 +4,23 @@ import { CustomStep } from '../FormSteps/CustomStep';
4
4
  import { CBRFormStep } from '../@Types/CBRFormStep';
5
5
  import { MapperElement } from '../@Types/MapperElement';
6
6
  import { ValuesStore } from '../States/SiteSlice';
7
+ /** The reCAPTCHA widget surface a submission needs: the response it holds, and the challenge that mints one. */
8
+ export interface CaptchaWidget {
9
+ getValue(): string | null;
10
+ executeAsync(): Promise<string | null>;
11
+ }
12
+ /**
13
+ * Resolve the reCAPTCHA response to send with a submission, running the invisible challenge when the widget
14
+ * is not holding one yet.
15
+ *
16
+ * An unsolved invisible widget answers `getValue()` with an EMPTY STRING rather than null, so emptiness —
17
+ * not nullishness — is what decides whether the challenge still has to run. A submission that skips the
18
+ * challenge carries no token, and the API rejects every captcha-protected public form with a 403.
19
+ *
20
+ * Yields null when no widget is mounted, leaving the caller to submit tokenless rather than throwing out of
21
+ * the submit handler.
22
+ */
23
+ export declare function resolveCaptchaToken(captcha: CaptchaWidget | null | undefined): Promise<string | null>;
7
24
  /**
8
25
  * Function that cals the value of a step to output on submit
9
26
  */
@@ -3,6 +3,28 @@ import FormStepTypes, { EntityValueDataTypes } from '../constants/FormStepTypes'
3
3
  import { calcStepDeps } from '../FormSteps/StepHooks';
4
4
  import { calcRecursiveData } from '../Utils/FormStepFunctions';
5
5
  import { isValidPhoneNumber } from '../Utils/PhoneFunctions';
6
+ /**
7
+ * Resolve the reCAPTCHA response to send with a submission, running the invisible challenge when the widget
8
+ * is not holding one yet.
9
+ *
10
+ * An unsolved invisible widget answers `getValue()` with an EMPTY STRING rather than null, so emptiness —
11
+ * not nullishness — is what decides whether the challenge still has to run. A submission that skips the
12
+ * challenge carries no token, and the API rejects every captcha-protected public form with a 403.
13
+ *
14
+ * Yields null when no widget is mounted, leaving the caller to submit tokenless rather than throwing out of
15
+ * the submit handler.
16
+ */
17
+ export async function resolveCaptchaToken(captcha) {
18
+ if (!captcha)
19
+ return null;
20
+ const held = captcha.getValue();
21
+ if (held)
22
+ return held;
23
+ const solved = await captcha.executeAsync();
24
+ if (solved)
25
+ return solved;
26
+ return null;
27
+ }
6
28
  /**
7
29
  * Function that cals the value of a step to output on submit
8
30
  */
@@ -1,7 +1,7 @@
1
1
  /* eslint-disable @typescript-eslint/require-await */
2
2
  import { ContentState, EditorState } from 'draft-js';
3
3
  import FormStepTypes, { EntityValueDataTypes } from '../constants/FormStepTypes';
4
- import { calcDependencies, calcValue } from './FormFunctions';
4
+ import { calcDependencies, calcValue, resolveCaptchaToken } from './FormFunctions';
5
5
  import { stringToDraft } from '../Utils/DraftFunctions';
6
6
  import { describe, expect, test } from 'vitest';
7
7
  describe('FormFunctions', function () {
@@ -455,4 +455,31 @@ describe('FormFunctions', function () {
455
455
  expect(dependencyStore.idStep.dependents.length).toBe(1);
456
456
  });
457
457
  });
458
+ describe('resolveCaptchaToken', function () {
459
+ test('Uses the token the widget already holds without re-running the challenge', async () => {
460
+ let executed = false;
461
+ const token = await resolveCaptchaToken({
462
+ getValue: () => 'already-solved',
463
+ executeAsync: async () => {
464
+ executed = true;
465
+ return 'freshly-solved';
466
+ },
467
+ });
468
+ expect(token).toBe('already-solved');
469
+ expect(executed).toBe(false);
470
+ });
471
+ test('Runs the challenge when the invisible widget reports an empty response', async () => {
472
+ // An unsolved invisible captcha answers `getValue()` with '' — not null — so emptiness is what
473
+ // must decide whether the challenge still has to run. Treating only nullish as unsolved leaves
474
+ // the submission tokenless and the API answers 403.
475
+ const token = await resolveCaptchaToken({
476
+ getValue: () => '',
477
+ executeAsync: async () => 'freshly-solved',
478
+ });
479
+ expect(token).toBe('freshly-solved');
480
+ });
481
+ test('Yields no token when the widget is not mounted', async () => {
482
+ expect(await resolveCaptchaToken(null)).toBeNull();
483
+ });
484
+ });
458
485
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@arquimedes.co/eureka-forms",
3
3
  "repository": "git://github.com/Arquimede5/Eureka-Forms.git",
4
- "version":"3.0.60-test",
4
+ "version": "3.0.60",
5
5
  "scripts": {
6
6
  "watch": "node node_modules/@typescript/native/bin/tsc --noEmit --watch --project tsconfig.app.json",
7
7
  "start": "vite",