@prosopo/procaptcha-frictionless 2.13.23 → 2.15.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.23",
3
+ "version": "2.15.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.4",
34
- "@prosopo/common": "3.1.52",
35
- "@prosopo/locale": "3.4.0",
36
- "@prosopo/procaptcha-common": "2.12.5",
37
- "@prosopo/procaptcha-pow": "2.11.5",
38
- "@prosopo/procaptcha-puzzle": "2.11.5",
39
- "@prosopo/procaptcha-react": "2.9.121",
40
- "@prosopo/types": "5.5.3",
41
- "@prosopo/widget-skeleton": "2.8.6",
33
+ "@prosopo/api": "4.1.6",
34
+ "@prosopo/common": "3.1.54",
35
+ "@prosopo/locale": "3.4.1",
36
+ "@prosopo/procaptcha-common": "2.13.0",
37
+ "@prosopo/procaptcha-pow": "2.12.0",
38
+ "@prosopo/procaptcha-puzzle": "2.12.0",
39
+ "@prosopo/procaptcha-react": "2.10.0",
40
+ "@prosopo/types": "5.7.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.14",
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
 
@@ -269,6 +287,7 @@ export const ProcaptchaFrictionless = ({
269
287
  autoStart={resumedAutoStart}
270
288
  startCoords={startCoords}
271
289
  onSessionInvalidated={onSessionInvalidated}
290
+ container={container}
272
291
  onReload={onReload}
273
292
  />,
274
293
  );
@@ -284,6 +303,7 @@ export const ProcaptchaFrictionless = ({
284
303
  autoStart={resumedAutoStart}
285
304
  startCoords={startCoords}
286
305
  onSessionInvalidated={onSessionInvalidated}
306
+ container={container}
287
307
  />,
288
308
  );
289
309
  } else {
@@ -299,6 +319,7 @@ export const ProcaptchaFrictionless = ({
299
319
  autoStart={resumedAutoStart}
300
320
  startCoords={startCoords}
301
321
  onSessionInvalidated={onSessionInvalidated}
322
+ container={container}
302
323
  />,
303
324
  );
304
325
  }
@@ -374,6 +395,59 @@ export const ProcaptchaFrictionless = ({
374
395
  });
375
396
  };
376
397
 
398
+ const startManually = async (
399
+ autoStart: boolean,
400
+ coords?: RetryCoords,
401
+ ): Promise<void> => {
402
+ if (manualStartedRef.current) return;
403
+ manualStartedRef.current = true;
404
+ pendingRetryCoordsRef.current = coords ?? null;
405
+ nextMountAutoStartRef.current = autoStart;
406
+ setComponentToRender(
407
+ renderPlaceholder(
408
+ config.theme,
409
+ config.mode,
410
+ stateRef.current.errorMessage,
411
+ i18n.isInitialized,
412
+ i18n.t,
413
+ true,
414
+ ),
415
+ );
416
+ await start();
417
+ };
418
+
419
+ manualCheckboxHandlerRef.current = async (event: CheckboxEvent) => {
420
+ let x = 0;
421
+ let y = 0;
422
+ const nativeEvent = event.nativeEvent;
423
+ if ("clientX" in nativeEvent && "clientY" in nativeEvent) {
424
+ x = nativeEvent.clientX;
425
+ y = nativeEvent.clientY;
426
+ }
427
+ await startManually(true, normaliseRetryCoords(x, y) ?? undefined);
428
+ };
429
+
430
+ // biome-ignore lint/correctness/useExhaustiveDependencies: intentional — `startManually` captures the mount-time `start()`, as the auto-mode effect below does.
431
+ useEffect(() => {
432
+ if (!manualStart) return;
433
+ const onStartEvent = (event: Event) => {
434
+ const detail = (event as CustomEvent<ProcaptchaStartEventDetail>).detail;
435
+ if (detail?.element && container && !detail.element.contains(container)) {
436
+ return;
437
+ }
438
+ void startManually(false);
439
+ };
440
+ const onExecuteEvent = () => {
441
+ void startManually(true);
442
+ };
443
+ document.addEventListener(PROCAPTCHA_START_EVENT, onStartEvent);
444
+ document.addEventListener(PROCAPTCHA_EXECUTE_EVENT, onExecuteEvent);
445
+ return () => {
446
+ document.removeEventListener(PROCAPTCHA_START_EVENT, onStartEvent);
447
+ document.removeEventListener(PROCAPTCHA_EXECUTE_EVENT, onExecuteEvent);
448
+ };
449
+ }, [manualStart, container]);
450
+
377
451
  // Track which config identity has already been started for. Host
378
452
  // pages often recreate the `callbacks` object (and sometimes the whole
379
453
  // `config`) on every render, which — before this guard — re-fired
@@ -399,6 +473,7 @@ export const ProcaptchaFrictionless = ({
399
473
  }`;
400
474
  if (startedForKeyRef.current === key) return;
401
475
  startedForKeyRef.current = key;
476
+ if (manualStart) return;
402
477
  void start();
403
478
  }, [config.account?.address, config.language, config.mode]);
404
479