@prosopo/procaptcha-pow 2.10.23 → 2.10.25

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.
Files changed (47) hide show
  1. package/.turbo/turbo-build$colon$cjs.log +12 -10
  2. package/.turbo/turbo-build$colon$tsc.log +19 -19
  3. package/.turbo/turbo-build.log +14 -11
  4. package/CHANGELOG.md +46 -0
  5. package/dist/_virtual/_rolldown/runtime.js +3 -0
  6. package/dist/cjs/components/ProcaptchaPoW.cjs +6 -9
  7. package/dist/cjs/components/ProcaptchaWidget.cjs +117 -127
  8. package/dist/cjs/index.cjs +2 -4
  9. package/dist/cjs/services/Manager.cjs +193 -277
  10. package/dist/components/ProcaptchaPoW.js +6 -8
  11. package/dist/components/ProcaptchaWidget.d.ts.map +1 -1
  12. package/dist/components/ProcaptchaWidget.js +117 -127
  13. package/dist/components/ProcaptchaWidget.js.map +1 -1
  14. package/dist/index.js +2 -4
  15. package/dist/services/Manager.d.ts.map +1 -1
  16. package/dist/services/Manager.js +189 -274
  17. package/dist/services/Manager.js.map +1 -1
  18. package/dist/tests/manager.unit.test.d.ts +2 -0
  19. package/dist/tests/manager.unit.test.d.ts.map +1 -0
  20. package/dist/tests/manager.unit.test.js +752 -0
  21. package/dist/tests/manager.unit.test.js.map +1 -0
  22. package/dist/tests/managerHarness.d.ts +17 -0
  23. package/dist/tests/managerHarness.d.ts.map +1 -0
  24. package/dist/tests/managerHarness.js +70 -0
  25. package/dist/tests/managerHarness.js.map +1 -0
  26. package/dist/tests/procaptchaPoW.unit.test.d.ts +2 -0
  27. package/dist/tests/procaptchaPoW.unit.test.d.ts.map +1 -0
  28. package/dist/tests/procaptchaPoW.unit.test.js +79 -0
  29. package/dist/tests/procaptchaPoW.unit.test.js.map +1 -0
  30. package/dist/tests/procaptchaPow.test-d.d.ts +2 -0
  31. package/dist/tests/procaptchaPow.test-d.d.ts.map +1 -0
  32. package/dist/tests/procaptchaPow.test-d.js +62 -0
  33. package/dist/tests/procaptchaPow.test-d.js.map +1 -0
  34. package/dist/tests/procaptchaWidget.unit.test.d.ts +2 -0
  35. package/dist/tests/procaptchaWidget.unit.test.d.ts.map +1 -0
  36. package/dist/tests/procaptchaWidget.unit.test.js +410 -0
  37. package/dist/tests/procaptchaWidget.unit.test.js.map +1 -0
  38. package/package.json +18 -13
  39. package/src/components/ProcaptchaWidget.tsx +22 -5
  40. package/src/services/Manager.ts +9 -5
  41. package/src/tests/manager.unit.test.ts +1105 -0
  42. package/src/tests/managerHarness.ts +135 -0
  43. package/src/tests/procaptchaPoW.unit.test.ts +120 -0
  44. package/src/tests/procaptchaPow.test-d.ts +122 -0
  45. package/src/tests/procaptchaWidget.unit.test.ts +571 -0
  46. package/tsconfig.tsbuildinfo +1 -1
  47. package/vite.test.config.ts +25 -0
@@ -0,0 +1,135 @@
1
+ // Copyright 2021-2026 Prosopo (UK) Ltd.
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+
15
+ import type {
16
+ Account,
17
+ FrictionlessState,
18
+ GetPowCaptchaResponse,
19
+ PowCaptchaSolutionResponse,
20
+ ProcaptchaCallbacks,
21
+ ProcaptchaClientConfigInput,
22
+ ProcaptchaState,
23
+ RandomProvider,
24
+ } from "@prosopo/types";
25
+ import { vi } from "vitest";
26
+
27
+ /**
28
+ * Shared fixtures for the manager suite. Kept out of the test file so the mock
29
+ * factories, which vitest hoists above every import, can build their canned
30
+ * responses from the same shapes the assertions use.
31
+ */
32
+
33
+ export const PROVIDER_URL = "https://provider.one";
34
+ export const OTHER_PROVIDER_URL = "https://provider.two";
35
+ export const USER_ADDRESS = "user-address";
36
+ export const SITE_KEY = "site-key";
37
+
38
+ export const config = (
39
+ overrides: Partial<ProcaptchaClientConfigInput> = {},
40
+ ): ProcaptchaClientConfigInput => ({
41
+ account: { address: SITE_KEY },
42
+ defaultEnvironment: "production",
43
+ ...overrides,
44
+ });
45
+
46
+ export const state = (
47
+ overrides: Partial<ProcaptchaState> = {},
48
+ ): ProcaptchaState => ({
49
+ isHuman: false,
50
+ index: 0,
51
+ solutions: [],
52
+ captchaApi: undefined,
53
+ challenge: undefined,
54
+ showModal: false,
55
+ loading: false,
56
+ account: undefined,
57
+ dappAccount: undefined,
58
+ submission: undefined,
59
+ timeout: undefined,
60
+ successfullChallengeTimeout: undefined,
61
+ sendData: false,
62
+ attemptCount: 0,
63
+ error: undefined,
64
+ sessionId: undefined,
65
+ ...overrides,
66
+ });
67
+
68
+ export type SignRaw = NonNullable<
69
+ NonNullable<Account["extension"]>["signer"]["signRaw"]
70
+ >;
71
+
72
+ /**
73
+ * An account carrying just enough of an injected extension to sign: the rest of
74
+ * the interface is never touched by the manager, but the type demands it.
75
+ */
76
+ export const account = (signRaw?: SignRaw): Account => ({
77
+ account: { address: USER_ADDRESS },
78
+ extension: {
79
+ name: "test-extension",
80
+ version: "0.0.0",
81
+ accounts: {
82
+ get: async () => [{ address: USER_ADDRESS }],
83
+ subscribe: () => () => undefined,
84
+ },
85
+ signer: signRaw ? { signRaw } : {},
86
+ },
87
+ });
88
+
89
+ export const accountWithoutExtension = (): Account => ({
90
+ account: { address: USER_ADDRESS },
91
+ });
92
+
93
+ export const randomProvider = (url: string = PROVIDER_URL): RandomProvider => ({
94
+ providerAccount: "provider-account",
95
+ provider: { url },
96
+ });
97
+
98
+ export const challengeResponse = (
99
+ overrides: Partial<GetPowCaptchaResponse> = {},
100
+ ): GetPowCaptchaResponse => ({
101
+ challenge: "0x1___0xdeadbeef___1700000000000",
102
+ difficulty: 2,
103
+ timestamp: "1700000000000",
104
+ signature: { provider: { challenge: "0xprovider-challenge" } },
105
+ status: "ok",
106
+ ...overrides,
107
+ });
108
+
109
+ export const solutionResponse = (
110
+ overrides: Partial<PowCaptchaSolutionResponse> = {},
111
+ ): PowCaptchaSolutionResponse => ({
112
+ verified: true,
113
+ status: "ok",
114
+ ...overrides,
115
+ });
116
+
117
+ export const callbacks = (
118
+ overrides: Partial<ProcaptchaCallbacks> = {},
119
+ ): ProcaptchaCallbacks => ({ ...overrides });
120
+
121
+ /**
122
+ * The signer every fixture account uses, so a test can assert on what the
123
+ * manager asked the extension to sign without rebuilding the frictionless
124
+ * state it was handed.
125
+ */
126
+ export const signRawMock = vi.fn<SignRaw>();
127
+
128
+ export const frictionless = (
129
+ overrides: Partial<FrictionlessState> = {},
130
+ ): FrictionlessState => ({
131
+ provider: randomProvider(),
132
+ userAccount: account(signRawMock),
133
+ restart: () => undefined,
134
+ ...overrides,
135
+ });
@@ -0,0 +1,120 @@
1
+ // Copyright 2021-2026 Prosopo (UK) Ltd.
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+
15
+ import type { Ti18n } from "@prosopo/locale";
16
+ import type { ProcaptchaProps } from "@prosopo/types";
17
+ import { type ReactElement, act, createElement } from "react";
18
+ import { type Root, createRoot } from "react-dom/client";
19
+ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
20
+ import * as entrypoint from "../index.js";
21
+ import { config } from "./managerHarness.js";
22
+
23
+ /**
24
+ * The outer component exists only to lazy-load the widget and hand it the
25
+ * props it was given — which it once did by naming them one at a time, quietly
26
+ * dropping `onEscalate` and `autoStart`.
27
+ */
28
+ const mocks = vi.hoisted(() => ({
29
+ received: [] as ProcaptchaProps[],
30
+ }));
31
+
32
+ vi.mock("../components/ProcaptchaWidget.js", async () => {
33
+ const { createElement } = await import("react");
34
+ return {
35
+ default: (props: ProcaptchaProps) => {
36
+ mocks.received.push(props);
37
+ return createElement("div", { "data-testid": "widget" });
38
+ },
39
+ };
40
+ });
41
+
42
+ let container: HTMLDivElement;
43
+ let root: Root;
44
+
45
+ const props = (overrides: Partial<ProcaptchaProps> = {}): ProcaptchaProps => ({
46
+ config: config(),
47
+ callbacks: {},
48
+ i18n: undefined as unknown as Ti18n,
49
+ ...overrides,
50
+ });
51
+
52
+ const render = async (widgetProps: ProcaptchaProps): Promise<void> => {
53
+ await act(async () => {
54
+ root.render(
55
+ createElement(entrypoint.ProcaptchaPow, widgetProps) as ReactElement,
56
+ );
57
+ });
58
+ };
59
+
60
+ beforeEach(() => {
61
+ mocks.received.length = 0;
62
+ container = document.createElement("div");
63
+ document.body.appendChild(container);
64
+ act(() => {
65
+ root = createRoot(container);
66
+ });
67
+ });
68
+
69
+ afterEach(() => {
70
+ act(() => {
71
+ root.unmount();
72
+ });
73
+ container.remove();
74
+ });
75
+
76
+ describe("ProcaptchaPow", () => {
77
+ test("renders the widget once it has loaded", async () => {
78
+ await render(props());
79
+ expect(container.querySelector('[data-testid="widget"]')).not.toBeNull();
80
+ });
81
+
82
+ test("passes on the props the widget needs but never names", async () => {
83
+ // Enumerating props here dropped onEscalate and autoStart, so the manager
84
+ // closed over an undefined escalation handler and the PoW checkbox span
85
+ // forever after an escalated solve.
86
+ const onEscalate = vi.fn();
87
+ const onSessionInvalidated = vi.fn();
88
+ await render(
89
+ props({
90
+ onEscalate,
91
+ autoStart: true,
92
+ startCoords: { x: 1, y: 2 },
93
+ onSessionInvalidated,
94
+ }),
95
+ );
96
+ expect(mocks.received[0]).toMatchObject({
97
+ onEscalate,
98
+ autoStart: true,
99
+ startCoords: { x: 1, y: 2 },
100
+ onSessionInvalidated,
101
+ });
102
+ });
103
+
104
+ test("passes on the props it does name, too", async () => {
105
+ const callbacks = { onHuman: vi.fn<(token: string) => void>() };
106
+ await render(props({ callbacks }));
107
+ expect(mocks.received[0]?.callbacks).toBe(callbacks);
108
+ expect(mocks.received[0]?.config).toEqual(config());
109
+ });
110
+ });
111
+
112
+ describe("the package entrypoint", () => {
113
+ test("exports the widget a consumer mounts, and nothing else", () => {
114
+ // The inner ProcaptchaWidget is a default export, which `export *` does
115
+ // not re-export: consumers get the lazy-loading wrapper only, which is
116
+ // the one that works outside a bundler that can code-split.
117
+ expect(typeof entrypoint.ProcaptchaPow).toBe("function");
118
+ expect(Object.keys(entrypoint)).toEqual(["ProcaptchaPow"]);
119
+ });
120
+ });
@@ -0,0 +1,122 @@
1
+ // Copyright 2021-2026 Prosopo (UK) Ltd.
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+
15
+ import type { Ti18n } from "@prosopo/locale";
16
+ import type {
17
+ FrictionlessState,
18
+ ProcaptchaCallbacks,
19
+ ProcaptchaClientConfigInput,
20
+ ProcaptchaEscalationHandler,
21
+ ProcaptchaProps,
22
+ ProcaptchaState,
23
+ ProcaptchaStateUpdateFn,
24
+ } from "@prosopo/types";
25
+ import type { ReactElement } from "react";
26
+ import { assertType, describe, expectTypeOf, test } from "vitest";
27
+ import type * as entrypoint from "../index.js";
28
+ import { ProcaptchaPow } from "../index.js";
29
+ import { Manager } from "../services/Manager.js";
30
+ import { config, frictionless, state } from "./managerHarness.js";
31
+
32
+ /** The widget only ever reads `language`/`changeLanguage` off this. */
33
+ const i18n = (): Ti18n => undefined as unknown as Ti18n;
34
+
35
+ describe("the package entrypoint's types", () => {
36
+ test("ProcaptchaPow takes the shared widget props and renders an element", () => {
37
+ expectTypeOf(ProcaptchaPow).parameters.toEqualTypeOf<[ProcaptchaProps]>();
38
+ expectTypeOf(ProcaptchaPow).returns.toExtend<ReactElement>();
39
+ });
40
+
41
+ test("the inner widget's default export is not re-exported", () => {
42
+ // `export *` skips default exports, so consumers can only reach the lazy
43
+ // wrapper — the one that works without a code-splitting bundler.
44
+ expectTypeOf<keyof typeof entrypoint>().toEqualTypeOf<"ProcaptchaPow">();
45
+ });
46
+
47
+ test("config, callbacks and i18n are all required", () => {
48
+ // @ts-expect-error - a widget with no config has no provider to talk to.
49
+ assertType<ProcaptchaProps>({ callbacks: {}, i18n: i18n() });
50
+ // @ts-expect-error - callbacks decide what a solve reports back.
51
+ assertType<ProcaptchaProps>({ config: config(), i18n: i18n() });
52
+ assertType<ProcaptchaProps>({
53
+ config: config(),
54
+ callbacks: {},
55
+ i18n: i18n(),
56
+ });
57
+ });
58
+ });
59
+
60
+ describe("Manager's types", () => {
61
+ const updateState: ProcaptchaStateUpdateFn = () => undefined;
62
+ const callbacks: ProcaptchaCallbacks = {};
63
+
64
+ test("only the first four arguments are required", () => {
65
+ assertType<ReturnType<typeof Manager>>(
66
+ Manager(config(), state(), updateState, callbacks),
67
+ );
68
+ // @ts-expect-error - callbacks decide what a solve reports back.
69
+ Manager(config(), state(), updateState);
70
+ });
71
+
72
+ test("the optional arguments keep their positions", () => {
73
+ expectTypeOf(Manager)
74
+ .parameter(4)
75
+ .toEqualTypeOf<FrictionlessState | undefined>();
76
+ expectTypeOf(Manager)
77
+ .parameter(5)
78
+ .toEqualTypeOf<ProcaptchaEscalationHandler | undefined>();
79
+ expectTypeOf(Manager)
80
+ .parameter(6)
81
+ .toEqualTypeOf<(() => string | undefined) | undefined>();
82
+ });
83
+
84
+ test("it exposes exactly start and resetState", () => {
85
+ expectTypeOf<keyof ReturnType<typeof Manager>>().toEqualTypeOf<
86
+ "start" | "resetState"
87
+ >();
88
+ expectTypeOf<ReturnType<typeof Manager>["start"]>().toEqualTypeOf<
89
+ (x?: number, y?: number) => Promise<void>
90
+ >();
91
+ // resetState takes the frictionless restart callback the widget hands it
92
+ // on an invalidated session, and nothing else.
93
+ expectTypeOf<
94
+ Parameters<ReturnType<typeof Manager>["resetState"]>
95
+ >().toEqualTypeOf<[frictionlessRestart?: (() => void) | undefined]>();
96
+ expectTypeOf<
97
+ ReturnType<ReturnType<typeof Manager>["resetState"]>
98
+ >().toEqualTypeOf<void>();
99
+ });
100
+
101
+ test("start's coordinates are optional numbers", () => {
102
+ const manager: ReturnType<typeof Manager> = Manager(
103
+ config(),
104
+ state(),
105
+ updateState,
106
+ callbacks,
107
+ frictionless(),
108
+ );
109
+ expectTypeOf(manager.start()).toEqualTypeOf<Promise<void>>();
110
+ expectTypeOf(manager.start(1, 2)).toEqualTypeOf<Promise<void>>();
111
+ // @ts-expect-error - coordinates come off a DOM event, never as strings.
112
+ manager.start("1", "2");
113
+ });
114
+ });
115
+
116
+ describe("the harness fixtures match the shared types", () => {
117
+ test("they build the real shapes, not lookalikes", () => {
118
+ expectTypeOf(config()).toEqualTypeOf<ProcaptchaClientConfigInput>();
119
+ expectTypeOf(state()).toEqualTypeOf<ProcaptchaState>();
120
+ expectTypeOf(frictionless()).toEqualTypeOf<FrictionlessState>();
121
+ });
122
+ });