@prosopo/procaptcha-frictionless 2.13.22 → 2.14.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.
@@ -0,0 +1,254 @@
1
+ import { CaptchaType, ModeEnum, PROCAPTCHA_START_EVENT, StartModeEnum, } from "@prosopo/types";
2
+ import { act, createElement } from "react";
3
+ import { createRoot } from "react-dom/client";
4
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
5
+ globalThis.IS_REACT_ACT_ENVIRONMENT = true;
6
+ const mocks = vi.hoisted(() => ({
7
+ mounts: [],
8
+ }));
9
+ const stub = (widget) => (props) => {
10
+ mocks.mounts.push({ widget, props });
11
+ return createElement("div", { "data-widget": widget });
12
+ };
13
+ vi.mock("@prosopo/procaptcha-pow", () => ({ ProcaptchaPow: stub("pow") }));
14
+ vi.mock("@prosopo/procaptcha-react", () => ({ Procaptcha: stub("image") }));
15
+ vi.mock("@prosopo/procaptcha-puzzle", () => ({
16
+ ProcaptchaPuzzle: stub("puzzle"),
17
+ }));
18
+ vi.mock("@prosopo/procaptcha-common", async (importOriginal) => {
19
+ const actual = await importOriginal();
20
+ return { ...actual, isSecureBrowserContext: () => true };
21
+ });
22
+ const { ProcaptchaFrictionless } = await import("../ProcaptchaFrictionless.js");
23
+ const SITE_KEY = "5siteKey";
24
+ const SESSION_ID = "provider-session";
25
+ const config = (overrides = {}) => ({
26
+ account: { address: SITE_KEY },
27
+ userAccountAddress: "",
28
+ web2: true,
29
+ mode: ModeEnum.visible,
30
+ startMode: StartModeEnum.manual,
31
+ ...overrides,
32
+ });
33
+ const i18nStub = {
34
+ isInitialized: true,
35
+ language: "en",
36
+ t: (key) => key,
37
+ changeLanguage: vi.fn(),
38
+ };
39
+ const detectionResult = () => ({
40
+ status: "ok",
41
+ captchaType: CaptchaType.image,
42
+ sessionId: SESSION_ID,
43
+ provider: { provider: { url: "https://provider.test" } },
44
+ userAccount: { account: { address: "5FakeUserAccountAddress" } },
45
+ });
46
+ const detectBotResolving = () => vi.fn().mockResolvedValue(detectionResult());
47
+ let host;
48
+ let root;
49
+ const mountWrapper = async (options = {}) => {
50
+ const detectBot = options.detectBot ?? detectBotResolving();
51
+ await act(async () => {
52
+ root.render(createElement(ProcaptchaFrictionless, {
53
+ config: options.config ?? config(),
54
+ callbacks: {},
55
+ restart: vi.fn(),
56
+ i18n: i18nStub,
57
+ detectBot,
58
+ container: options.container,
59
+ }));
60
+ });
61
+ return detectBot;
62
+ };
63
+ const checkbox = () => {
64
+ const element = host.querySelector('[data-cy="captcha-checkbox"]');
65
+ if (!element)
66
+ throw new Error("expected the checkbox to be on the page");
67
+ return element;
68
+ };
69
+ const spinner = () => host.querySelector('[aria-label="Loading spinner"]');
70
+ const lastMountOf = (widget) => {
71
+ const mount = mocks.mounts.filter((m) => m.widget === widget).at(-1);
72
+ if (!mount)
73
+ throw new Error(`expected the ${widget} widget to have mounted`);
74
+ return mount;
75
+ };
76
+ const setTrusted = (event, trusted) => {
77
+ for (const symbol of Object.getOwnPropertySymbols(event)) {
78
+ const impl = Reflect.get(event, symbol);
79
+ if (impl && typeof impl === "object" && "isTrusted" in impl) {
80
+ Object.defineProperty(impl, "isTrusted", {
81
+ configurable: true,
82
+ get: () => trusted,
83
+ set: () => undefined,
84
+ });
85
+ return;
86
+ }
87
+ }
88
+ throw new Error("could not reach the jsdom event implementation");
89
+ };
90
+ const click = async (element, options = {}) => {
91
+ const event = new MouseEvent("click", {
92
+ bubbles: true,
93
+ cancelable: true,
94
+ clientX: options.clientX ?? 0,
95
+ clientY: options.clientY ?? 0,
96
+ });
97
+ setTrusted(event, options.trusted ?? true);
98
+ await act(async () => {
99
+ element.dispatchEvent(event);
100
+ });
101
+ };
102
+ const dispatchStart = async (detail) => {
103
+ await act(async () => {
104
+ document.dispatchEvent(new CustomEvent(PROCAPTCHA_START_EVENT, {
105
+ detail,
106
+ bubbles: true,
107
+ }));
108
+ });
109
+ };
110
+ const dispatchExecute = async () => {
111
+ await act(async () => {
112
+ document.dispatchEvent(new CustomEvent("procaptcha:execute"));
113
+ });
114
+ };
115
+ beforeEach(() => {
116
+ mocks.mounts.length = 0;
117
+ host = document.createElement("div");
118
+ document.body.appendChild(host);
119
+ act(() => {
120
+ root = createRoot(host);
121
+ });
122
+ });
123
+ afterEach(() => {
124
+ act(() => {
125
+ root.unmount();
126
+ });
127
+ host.remove();
128
+ vi.clearAllMocks();
129
+ });
130
+ describe("manual start mode", () => {
131
+ it("mounts a live checkbox without running detection", async () => {
132
+ const detectBot = await mountWrapper();
133
+ expect(detectBot).not.toHaveBeenCalled();
134
+ expect(mocks.mounts).toHaveLength(0);
135
+ expect(checkbox().disabled).toBe(false);
136
+ expect(spinner()).toBeNull();
137
+ });
138
+ it("still runs detection on mount in auto mode", async () => {
139
+ const detectBot = await mountWrapper({
140
+ config: config({ startMode: StartModeEnum.auto }),
141
+ });
142
+ expect(detectBot).toHaveBeenCalledTimes(1);
143
+ expect(lastMountOf("image").props.autoStart).toBe(false);
144
+ });
145
+ it("defaults to auto when no start mode is given", async () => {
146
+ const detectBot = await mountWrapper({
147
+ config: config({ startMode: undefined }),
148
+ });
149
+ expect(detectBot).toHaveBeenCalledTimes(1);
150
+ });
151
+ describe("started by the site", () => {
152
+ it("runs detection on procaptcha:start and leaves the widget waiting for a click", async () => {
153
+ const detectBot = await mountWrapper();
154
+ await dispatchStart();
155
+ expect(detectBot).toHaveBeenCalledTimes(1);
156
+ const mount = lastMountOf("image").props;
157
+ expect(mount.autoStart).toBe(false);
158
+ expect(mount.startCoords).toBeUndefined();
159
+ expect(mount.frictionlessState?.sessionId).toBe(SESSION_ID);
160
+ });
161
+ it("honours an event addressed to its own element", async () => {
162
+ const widgetElement = document.createElement("div");
163
+ const container = document.createElement("div");
164
+ widgetElement.appendChild(container);
165
+ const detectBot = await mountWrapper({ container });
166
+ await dispatchStart({ element: widgetElement });
167
+ expect(detectBot).toHaveBeenCalledTimes(1);
168
+ });
169
+ it("ignores an event addressed to another widget's element", async () => {
170
+ const container = document.createElement("div");
171
+ document.createElement("div").appendChild(container);
172
+ const detectBot = await mountWrapper({ container });
173
+ await dispatchStart({ element: document.createElement("div") });
174
+ expect(detectBot).not.toHaveBeenCalled();
175
+ expect(checkbox().disabled).toBe(false);
176
+ });
177
+ it("receives every event when mounted without a container", async () => {
178
+ const detectBot = await mountWrapper();
179
+ await dispatchStart({ element: document.createElement("div") });
180
+ expect(detectBot).toHaveBeenCalledTimes(1);
181
+ });
182
+ it("opens the challenge straight away on procaptcha:execute", async () => {
183
+ const detectBot = await mountWrapper({
184
+ config: config({ mode: ModeEnum.invisible }),
185
+ });
186
+ await dispatchExecute();
187
+ expect(detectBot).toHaveBeenCalledTimes(1);
188
+ expect(lastMountOf("image").props.autoStart).toBe(true);
189
+ });
190
+ it("ignores the events once started", async () => {
191
+ const detectBot = await mountWrapper();
192
+ await dispatchStart();
193
+ await dispatchStart();
194
+ await dispatchExecute();
195
+ expect(detectBot).toHaveBeenCalledTimes(1);
196
+ expect(mocks.mounts).toHaveLength(1);
197
+ });
198
+ it("does not react to the events in auto mode", async () => {
199
+ const detectBot = await mountWrapper({
200
+ config: config({ startMode: StartModeEnum.auto }),
201
+ });
202
+ expect(detectBot).toHaveBeenCalledTimes(1);
203
+ await dispatchStart();
204
+ expect(detectBot).toHaveBeenCalledTimes(1);
205
+ });
206
+ });
207
+ describe("started by the user", () => {
208
+ it("runs detection on a checkbox click and opens the challenge where the user clicked", async () => {
209
+ const detectBot = await mountWrapper();
210
+ await click(checkbox(), { clientX: 11, clientY: 22 });
211
+ expect(detectBot).toHaveBeenCalledTimes(1);
212
+ const mount = lastMountOf("image").props;
213
+ expect(mount.autoStart).toBe(true);
214
+ expect(mount.startCoords).toEqual({ x: 11, y: 22 });
215
+ });
216
+ it("opens the challenge after a keyboard activation with no position", async () => {
217
+ const detectBot = await mountWrapper();
218
+ await click(checkbox());
219
+ expect(detectBot).toHaveBeenCalledTimes(1);
220
+ const mount = lastMountOf("image").props;
221
+ expect(mount.autoStart).toBe(true);
222
+ expect(mount.startCoords).toBeUndefined();
223
+ });
224
+ it("shows the spinner while the deferred flow runs", async () => {
225
+ let finish;
226
+ const detectBot = vi.fn().mockImplementation(() => new Promise((resolve) => {
227
+ finish = resolve;
228
+ }));
229
+ await mountWrapper({ detectBot });
230
+ await click(checkbox(), { clientX: 1, clientY: 1 });
231
+ expect(spinner()).not.toBeNull();
232
+ expect(mocks.mounts).toHaveLength(0);
233
+ await act(async () => {
234
+ finish?.(detectionResult());
235
+ });
236
+ expect(spinner()).toBeNull();
237
+ expect(lastMountOf("image").props.autoStart).toBe(true);
238
+ });
239
+ it("ignores a synthetic click", async () => {
240
+ const detectBot = await mountWrapper();
241
+ await click(checkbox(), { trusted: false, clientX: 1, clientY: 1 });
242
+ expect(detectBot).not.toHaveBeenCalled();
243
+ });
244
+ it("starts once even if the site also asks", async () => {
245
+ const detectBot = await mountWrapper();
246
+ await click(checkbox(), { clientX: 3, clientY: 4 });
247
+ await dispatchStart();
248
+ expect(detectBot).toHaveBeenCalledTimes(1);
249
+ expect(mocks.mounts).toHaveLength(1);
250
+ expect(lastMountOf("image").props.startCoords).toEqual({ x: 3, y: 4 });
251
+ });
252
+ });
253
+ });
254
+ //# sourceMappingURL=manualStart.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manualStart.test.js","sourceRoot":"","sources":["../../src/tests/manualStart.test.tsx"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prosopo/procaptcha-frictionless",
3
- "version": "2.13.22",
3
+ "version": "2.14.0",
4
4
  "author": "PROSOPO LIMITED <info@prosopo.io>",
5
5
  "license": "Apache-2.0",
6
6
  "main": "dist/index.js",
@@ -30,22 +30,22 @@
30
30
  },
31
31
  "browserslist": ["> 0.5%, last 2 versions, not dead"],
32
32
  "dependencies": {
33
- "@prosopo/api": "4.1.3",
34
- "@prosopo/common": "3.1.52",
35
- "@prosopo/locale": "3.4.0",
36
- "@prosopo/procaptcha-common": "2.12.4",
37
- "@prosopo/procaptcha-pow": "2.11.4",
38
- "@prosopo/procaptcha-puzzle": "2.11.4",
39
- "@prosopo/procaptcha-react": "2.9.120",
40
- "@prosopo/types": "5.5.2",
41
- "@prosopo/widget-skeleton": "2.8.6",
33
+ "@prosopo/api": "4.1.5",
34
+ "@prosopo/common": "3.1.53",
35
+ "@prosopo/locale": "3.4.1",
36
+ "@prosopo/procaptcha-common": "2.12.6",
37
+ "@prosopo/procaptcha-pow": "2.11.6",
38
+ "@prosopo/procaptcha-puzzle": "2.11.6",
39
+ "@prosopo/procaptcha-react": "2.9.122",
40
+ "@prosopo/types": "5.6.0",
41
+ "@prosopo/widget-skeleton": "2.8.7",
42
42
  "dotenv": "16.4.5",
43
43
  "react": "19.2.8"
44
44
  },
45
45
  "devDependencies": {
46
- "@prosopo/config": "3.3.12",
46
+ "@prosopo/config": "3.3.13",
47
47
  "@types/node": "22.10.2",
48
- "@vitest/coverage-v8": "4.1.10",
48
+ "@vitest/coverage-v8": "4.1.11",
49
49
  "concurrently": "9.0.1",
50
50
  "del-cli": "6.0.0",
51
51
  "npm-run-all": "4.1.5",
@@ -54,7 +54,7 @@
54
54
  "tsx": "4.20.3",
55
55
  "typescript": "5.6.2",
56
56
  "vite": "8.1.5",
57
- "vitest": "4.1.10"
57
+ "vitest": "4.1.11"
58
58
  },
59
59
  "repository": {
60
60
  "type": "git",
@@ -24,10 +24,14 @@ import {
24
24
  CaptchaType,
25
25
  type FrictionlessState,
26
26
  type ModeType,
27
+ PROCAPTCHA_START_EVENT,
27
28
  ProcaptchaConfigSchema,
28
29
  type ProcaptchaFrictionlessProps,
30
+ type ProcaptchaStartEventDetail,
31
+ StartModeEnum,
29
32
  } from "@prosopo/types";
30
33
  import { darkTheme, lightTheme } from "@prosopo/widget-skeleton";
34
+ import type { KeyboardEvent, MouseEvent, TouchEvent } from "react";
31
35
  import { useEffect, useRef, useState } from "react";
32
36
  import customDetectBot from "./customDetectBot.js";
33
37
  import { evaluateFrictionlessResult } from "./frictionlessResultGuard.js";
@@ -46,6 +50,12 @@ const ProcaptchaPuzzleLoader = async () =>
46
50
  const ProcaptchaPowLoader = async () =>
47
51
  (await import("@prosopo/procaptcha-pow")).ProcaptchaPow;
48
52
 
53
+ const PROCAPTCHA_EXECUTE_EVENT = "procaptcha:execute";
54
+
55
+ type CheckboxEvent = MouseEvent | TouchEvent | KeyboardEvent;
56
+
57
+ const noopCheckboxHandler = async (_event: CheckboxEvent): Promise<void> => {};
58
+
49
59
  const renderPlaceholder = (
50
60
  theme: string | undefined,
51
61
  mode: ModeType,
@@ -53,6 +63,7 @@ const renderPlaceholder = (
53
63
  isTranslationLoaded: boolean,
54
64
  translationFn: (key: string) => string,
55
65
  loading: boolean,
66
+ onChange: (event: CheckboxEvent) => Promise<void> = noopCheckboxHandler,
56
67
  ) => {
57
68
  const checkboxTheme = "light" === theme ? lightTheme : darkTheme;
58
69
 
@@ -63,7 +74,7 @@ const renderPlaceholder = (
63
74
  return (
64
75
  <Checkbox
65
76
  theme={checkboxTheme}
66
- onChange={async () => {}}
77
+ onChange={onChange}
67
78
  checked={false}
68
79
  labelText={isTranslationLoaded ? translationFn("WIDGET.I_AM_HUMAN") : ""}
69
80
  error={errorMessage}
@@ -116,6 +127,10 @@ export const ProcaptchaFrictionless = ({
116
127
  // because `providerRetry` re-invokes `start` with no arguments, which would
117
128
  // otherwise drop the flag on the first provider retry.
118
129
  const nextMountAutoStartRef = useRef(false);
130
+ const manualStart = config.startMode === StartModeEnum.manual;
131
+ const manualStartedRef = useRef(false);
132
+ // The placeholder is built before `start()` exists in this scope.
133
+ const manualCheckboxHandlerRef = useRef(noopCheckboxHandler);
119
134
 
120
135
  useEffect(() => {
121
136
  if (!config.language) return;
@@ -139,7 +154,10 @@ export const ProcaptchaFrictionless = ({
139
154
  stateRef.current.errorMessage,
140
155
  i18n.isInitialized,
141
156
  i18n.t,
142
- true,
157
+ !manualStart,
158
+ manualStart
159
+ ? (event: CheckboxEvent) => manualCheckboxHandlerRef.current(event)
160
+ : undefined,
143
161
  ),
144
162
  );
145
163
 
@@ -374,6 +392,59 @@ export const ProcaptchaFrictionless = ({
374
392
  });
375
393
  };
376
394
 
395
+ const startManually = async (
396
+ autoStart: boolean,
397
+ coords?: RetryCoords,
398
+ ): Promise<void> => {
399
+ if (manualStartedRef.current) return;
400
+ manualStartedRef.current = true;
401
+ pendingRetryCoordsRef.current = coords ?? null;
402
+ nextMountAutoStartRef.current = autoStart;
403
+ setComponentToRender(
404
+ renderPlaceholder(
405
+ config.theme,
406
+ config.mode,
407
+ stateRef.current.errorMessage,
408
+ i18n.isInitialized,
409
+ i18n.t,
410
+ true,
411
+ ),
412
+ );
413
+ await start();
414
+ };
415
+
416
+ manualCheckboxHandlerRef.current = async (event: CheckboxEvent) => {
417
+ let x = 0;
418
+ let y = 0;
419
+ const nativeEvent = event.nativeEvent;
420
+ if ("clientX" in nativeEvent && "clientY" in nativeEvent) {
421
+ x = nativeEvent.clientX;
422
+ y = nativeEvent.clientY;
423
+ }
424
+ await startManually(true, normaliseRetryCoords(x, y) ?? undefined);
425
+ };
426
+
427
+ // biome-ignore lint/correctness/useExhaustiveDependencies: intentional — `startManually` captures the mount-time `start()`, as the auto-mode effect below does.
428
+ useEffect(() => {
429
+ if (!manualStart) return;
430
+ const onStartEvent = (event: Event) => {
431
+ const detail = (event as CustomEvent<ProcaptchaStartEventDetail>).detail;
432
+ if (detail?.element && container && !detail.element.contains(container)) {
433
+ return;
434
+ }
435
+ void startManually(false);
436
+ };
437
+ const onExecuteEvent = () => {
438
+ void startManually(true);
439
+ };
440
+ document.addEventListener(PROCAPTCHA_START_EVENT, onStartEvent);
441
+ document.addEventListener(PROCAPTCHA_EXECUTE_EVENT, onExecuteEvent);
442
+ return () => {
443
+ document.removeEventListener(PROCAPTCHA_START_EVENT, onStartEvent);
444
+ document.removeEventListener(PROCAPTCHA_EXECUTE_EVENT, onExecuteEvent);
445
+ };
446
+ }, [manualStart, container]);
447
+
377
448
  // Track which config identity has already been started for. Host
378
449
  // pages often recreate the `callbacks` object (and sometimes the whole
379
450
  // `config`) on every render, which — before this guard — re-fired
@@ -399,6 +470,7 @@ export const ProcaptchaFrictionless = ({
399
470
  }`;
400
471
  if (startedForKeyRef.current === key) return;
401
472
  startedForKeyRef.current = key;
473
+ if (manualStart) return;
402
474
  void start();
403
475
  }, [config.account?.address, config.language, config.mode]);
404
476