@prosopo/procaptcha-frictionless 2.16.3 → 2.16.4
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/.turbo/turbo-build$colon$cjs.log +4 -4
- package/.turbo/turbo-build$colon$tsc.log +24 -24
- package/.turbo/turbo-build.log +5 -5
- package/CHANGELOG.md +24 -0
- package/dist/ProcaptchaFrictionless.d.ts.map +1 -1
- package/dist/ProcaptchaFrictionless.js +32 -1
- package/dist/ProcaptchaFrictionless.js.map +1 -1
- package/dist/cjs/ProcaptchaFrictionless.cjs +31 -0
- package/dist/cjs/frictionlessResultGuard.cjs +43 -6
- package/dist/frictionlessResultGuard.d.ts +1 -0
- package/dist/frictionlessResultGuard.d.ts.map +1 -1
- package/dist/frictionlessResultGuard.js +43 -6
- package/dist/frictionlessResultGuard.js.map +1 -1
- package/dist/tests/executeBeforeMount.test.d.ts +5 -0
- package/dist/tests/executeBeforeMount.test.d.ts.map +1 -0
- package/dist/tests/executeBeforeMount.test.js +152 -0
- package/dist/tests/executeBeforeMount.test.js.map +1 -0
- package/dist/tests/frictionlessResultGuard.test.js +59 -2
- package/dist/tests/frictionlessResultGuard.test.js.map +1 -1
- package/package.json +12 -12
- package/src/ProcaptchaFrictionless.tsx +57 -0
- package/src/frictionlessResultGuard.ts +49 -3
- package/src/tests/executeBeforeMount.test.tsx +231 -0
- package/src/tests/frictionlessResultGuard.test.ts +77 -2
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { CaptchaType, ModeEnum, StartModeEnum, } from "@prosopo/types";
|
|
2
|
+
import { act, createElement, useEffect } 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 EXECUTE_EVENT = "procaptcha:execute";
|
|
7
|
+
const mocks = vi.hoisted(() => ({
|
|
8
|
+
executes: [],
|
|
9
|
+
}));
|
|
10
|
+
const ListeningWidget = (props) => {
|
|
11
|
+
const { container } = props;
|
|
12
|
+
const invisible = props.config.mode === "invisible";
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
const onContainer = () => mocks.executes.push("container");
|
|
15
|
+
const onDocument = () => mocks.executes.push("document");
|
|
16
|
+
container?.addEventListener(EXECUTE_EVENT, onContainer);
|
|
17
|
+
if (invisible)
|
|
18
|
+
document.addEventListener(EXECUTE_EVENT, onDocument);
|
|
19
|
+
return () => {
|
|
20
|
+
container?.removeEventListener(EXECUTE_EVENT, onContainer);
|
|
21
|
+
if (invisible)
|
|
22
|
+
document.removeEventListener(EXECUTE_EVENT, onDocument);
|
|
23
|
+
};
|
|
24
|
+
}, [container, invisible]);
|
|
25
|
+
return createElement("div", { "data-widget": "image" });
|
|
26
|
+
};
|
|
27
|
+
vi.mock("@prosopo/procaptcha-react", () => ({ Procaptcha: ListeningWidget }));
|
|
28
|
+
vi.mock("@prosopo/procaptcha-pow", () => ({ ProcaptchaPow: ListeningWidget }));
|
|
29
|
+
vi.mock("@prosopo/procaptcha-puzzle", () => ({
|
|
30
|
+
ProcaptchaPuzzle: ListeningWidget,
|
|
31
|
+
}));
|
|
32
|
+
vi.mock("@prosopo/procaptcha-common", async (importOriginal) => {
|
|
33
|
+
const actual = await importOriginal();
|
|
34
|
+
return { ...actual, isSecureBrowserContext: () => true };
|
|
35
|
+
});
|
|
36
|
+
const { ProcaptchaFrictionless } = await import("../ProcaptchaFrictionless.js");
|
|
37
|
+
const config = (overrides = {}) => ({
|
|
38
|
+
account: { address: "5siteKey" },
|
|
39
|
+
userAccountAddress: "",
|
|
40
|
+
web2: true,
|
|
41
|
+
mode: ModeEnum.invisible,
|
|
42
|
+
startMode: StartModeEnum.auto,
|
|
43
|
+
...overrides,
|
|
44
|
+
});
|
|
45
|
+
const i18nStub = {
|
|
46
|
+
isInitialized: true,
|
|
47
|
+
language: "en",
|
|
48
|
+
t: (key) => key,
|
|
49
|
+
changeLanguage: vi.fn(),
|
|
50
|
+
};
|
|
51
|
+
const detectionResult = () => ({
|
|
52
|
+
status: "ok",
|
|
53
|
+
captchaType: CaptchaType.image,
|
|
54
|
+
sessionId: "provider-session",
|
|
55
|
+
provider: { provider: { url: "https://provider.test" } },
|
|
56
|
+
userAccount: { account: { address: "5FakeUserAccountAddress" } },
|
|
57
|
+
});
|
|
58
|
+
const heldDetection = () => {
|
|
59
|
+
let resolve;
|
|
60
|
+
const detectBot = vi.fn().mockImplementation(() => new Promise((r) => {
|
|
61
|
+
resolve = r;
|
|
62
|
+
}));
|
|
63
|
+
const finish = async () => {
|
|
64
|
+
await act(async () => {
|
|
65
|
+
resolve?.(detectionResult());
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
return { detectBot, finish };
|
|
69
|
+
};
|
|
70
|
+
let host;
|
|
71
|
+
let container;
|
|
72
|
+
let root;
|
|
73
|
+
const mountWrapper = async (detectBot, overrides = {}) => {
|
|
74
|
+
await act(async () => {
|
|
75
|
+
root.render(createElement(ProcaptchaFrictionless, {
|
|
76
|
+
config: config(overrides),
|
|
77
|
+
callbacks: {},
|
|
78
|
+
restart: vi.fn(),
|
|
79
|
+
i18n: i18nStub,
|
|
80
|
+
detectBot,
|
|
81
|
+
container,
|
|
82
|
+
}));
|
|
83
|
+
});
|
|
84
|
+
};
|
|
85
|
+
const dispatch = async (target) => {
|
|
86
|
+
await act(async () => {
|
|
87
|
+
target.dispatchEvent(new CustomEvent(EXECUTE_EVENT));
|
|
88
|
+
});
|
|
89
|
+
};
|
|
90
|
+
beforeEach(() => {
|
|
91
|
+
mocks.executes.length = 0;
|
|
92
|
+
host = document.createElement("div");
|
|
93
|
+
container = document.createElement("div");
|
|
94
|
+
document.body.append(host, container);
|
|
95
|
+
act(() => {
|
|
96
|
+
root = createRoot(host);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
afterEach(() => {
|
|
100
|
+
act(() => {
|
|
101
|
+
root.unmount();
|
|
102
|
+
});
|
|
103
|
+
host.remove();
|
|
104
|
+
container.remove();
|
|
105
|
+
vi.clearAllMocks();
|
|
106
|
+
});
|
|
107
|
+
describe("execute() before the inner widget is listening", () => {
|
|
108
|
+
it("replays a bare execute() once the invisible widget mounts", async () => {
|
|
109
|
+
const { detectBot, finish } = heldDetection();
|
|
110
|
+
await mountWrapper(detectBot);
|
|
111
|
+
await dispatch(document);
|
|
112
|
+
expect(mocks.executes).toEqual([]);
|
|
113
|
+
await finish();
|
|
114
|
+
expect(mocks.executes).toEqual(["container"]);
|
|
115
|
+
});
|
|
116
|
+
it("replays a targeted execute() in visible mode", async () => {
|
|
117
|
+
const { detectBot, finish } = heldDetection();
|
|
118
|
+
await mountWrapper(detectBot, { mode: ModeEnum.visible });
|
|
119
|
+
await dispatch(container);
|
|
120
|
+
await finish();
|
|
121
|
+
expect(mocks.executes).toEqual(["container"]);
|
|
122
|
+
});
|
|
123
|
+
it("replays repeated early calls only once", async () => {
|
|
124
|
+
const { detectBot, finish } = heldDetection();
|
|
125
|
+
await mountWrapper(detectBot);
|
|
126
|
+
await dispatch(document);
|
|
127
|
+
await dispatch(document);
|
|
128
|
+
await finish();
|
|
129
|
+
expect(mocks.executes).toEqual(["container"]);
|
|
130
|
+
});
|
|
131
|
+
it("does not start the widget when execute() was never called", async () => {
|
|
132
|
+
const { detectBot, finish } = heldDetection();
|
|
133
|
+
await mountWrapper(detectBot);
|
|
134
|
+
await finish();
|
|
135
|
+
expect(mocks.executes).toEqual([]);
|
|
136
|
+
});
|
|
137
|
+
it("leaves an execute() after mount to the widget alone", async () => {
|
|
138
|
+
const { detectBot, finish } = heldDetection();
|
|
139
|
+
await mountWrapper(detectBot);
|
|
140
|
+
await finish();
|
|
141
|
+
await dispatch(document);
|
|
142
|
+
expect(mocks.executes).toEqual(["document"]);
|
|
143
|
+
});
|
|
144
|
+
it("ignores a bare execute() in visible mode, as the widgets do", async () => {
|
|
145
|
+
const { detectBot, finish } = heldDetection();
|
|
146
|
+
await mountWrapper(detectBot, { mode: ModeEnum.visible });
|
|
147
|
+
await dispatch(document);
|
|
148
|
+
await finish();
|
|
149
|
+
expect(mocks.executes).toEqual([]);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
//# sourceMappingURL=executeBeforeMount.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"executeBeforeMount.test.js","sourceRoot":"","sources":["../../src/tests/executeBeforeMount.test.tsx"],"names":[],"mappings":"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"}
|
|
@@ -16,7 +16,12 @@ describe("evaluateFrictionlessResult", () => {
|
|
|
16
16
|
expect(evaluateFrictionlessResult({
|
|
17
17
|
captchaType: "pow",
|
|
18
18
|
error: { message: "Boom", key: "API.SOMETHING" },
|
|
19
|
-
})).toEqual({
|
|
19
|
+
})).toEqual({
|
|
20
|
+
kind: "error",
|
|
21
|
+
message: "Boom",
|
|
22
|
+
key: "API.SOMETHING",
|
|
23
|
+
retryable: true,
|
|
24
|
+
});
|
|
20
25
|
});
|
|
21
26
|
it("omits key from the outcome when the server error carries no key", () => {
|
|
22
27
|
const outcome = evaluateFrictionlessResult({
|
|
@@ -32,12 +37,17 @@ describe("evaluateFrictionlessResult", () => {
|
|
|
32
37
|
it("halts when captchaType is missing (bare-string 401 body shape from access-policy / autoBan / middleware)", () => {
|
|
33
38
|
expect(evaluateFrictionlessResult({
|
|
34
39
|
error: undefined,
|
|
35
|
-
})).toEqual({
|
|
40
|
+
})).toEqual({
|
|
41
|
+
kind: "error",
|
|
42
|
+
message: MISSING_CAPTCHA_TYPE_MESSAGE,
|
|
43
|
+
retryable: false,
|
|
44
|
+
});
|
|
36
45
|
});
|
|
37
46
|
it("halts when both captchaType and error are absent (opaque parse failure)", () => {
|
|
38
47
|
expect(evaluateFrictionlessResult({})).toEqual({
|
|
39
48
|
kind: "error",
|
|
40
49
|
message: MISSING_CAPTCHA_TYPE_MESSAGE,
|
|
50
|
+
retryable: false,
|
|
41
51
|
});
|
|
42
52
|
});
|
|
43
53
|
it("prefers the structured error over the missing-captchaType message when both apply", () => {
|
|
@@ -47,6 +57,53 @@ describe("evaluateFrictionlessResult", () => {
|
|
|
47
57
|
kind: "error",
|
|
48
58
|
message: "Server error",
|
|
49
59
|
key: "API.SERVER_ERROR",
|
|
60
|
+
retryable: true,
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
describe("retryable classification", () => {
|
|
64
|
+
const retryableOf = (key) => {
|
|
65
|
+
const outcome = evaluateFrictionlessResult({
|
|
66
|
+
error: { message: "msg", ...(key !== undefined && { key }) },
|
|
67
|
+
});
|
|
68
|
+
if (outcome.kind !== "error")
|
|
69
|
+
throw new Error("expected error outcome");
|
|
70
|
+
return outcome.retryable;
|
|
71
|
+
};
|
|
72
|
+
it.each([
|
|
73
|
+
"API.SITE_KEY_NOT_REGISTERED",
|
|
74
|
+
"API.INVALID_SITE_KEY",
|
|
75
|
+
"API.UNAUTHORIZED_ORIGIN_URL",
|
|
76
|
+
"API.INCORRECT_CAPTCHA_TYPE",
|
|
77
|
+
])("shows integration fault %s rather than re-rolling", (key) => {
|
|
78
|
+
expect(retryableOf(key)).toBe(false);
|
|
79
|
+
});
|
|
80
|
+
it.each([
|
|
81
|
+
"API.ACCESS_POLICY_BLOCK",
|
|
82
|
+
"API.ABUSER_BLOCKED",
|
|
83
|
+
"API.CRAWLER_BLOCKED",
|
|
84
|
+
"API.DATACENTER_BLOCKED",
|
|
85
|
+
"API.DISALLOWED_WEBVIEW",
|
|
86
|
+
"API.MOBILE_BLOCKED",
|
|
87
|
+
"API.PROXY_BLOCKED",
|
|
88
|
+
"API.SATELLITE_BLOCKED",
|
|
89
|
+
"API.TOR_BLOCKED",
|
|
90
|
+
"API.VPN_BLOCKED",
|
|
91
|
+
"API.FORBIDDEN",
|
|
92
|
+
"API.UNAUTHORIZED",
|
|
93
|
+
])("does not re-roll policy denial %s", (key) => {
|
|
94
|
+
expect(retryableOf(key)).toBe(false);
|
|
95
|
+
});
|
|
96
|
+
it("leaves CAPTCHA.NO_SESSION_FOUND to its own restart timer", () => {
|
|
97
|
+
expect(retryableOf("CAPTCHA.NO_SESSION_FOUND")).toBe(false);
|
|
98
|
+
});
|
|
99
|
+
it("re-rolls API.BAD_REQUEST, which is what an unhandled provider throw becomes", () => {
|
|
100
|
+
expect(retryableOf("API.BAD_REQUEST")).toBe(true);
|
|
101
|
+
});
|
|
102
|
+
it.each(["API.UNKNOWN", "API.INTERNAL_SERVER_ERROR", "DATABASE.UNKNOWN"])("re-rolls unrecognised provider fault %s", (key) => {
|
|
103
|
+
expect(retryableOf(key)).toBe(true);
|
|
104
|
+
});
|
|
105
|
+
it("does not re-roll an error with no key at all", () => {
|
|
106
|
+
expect(retryableOf(undefined)).toBe(false);
|
|
50
107
|
});
|
|
51
108
|
});
|
|
52
109
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"frictionlessResultGuard.test.js","sourceRoot":"","sources":["../../src/tests/frictionlessResultGuard.test.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EACN,4BAA4B,EAC5B,0BAA0B,GAC1B,MAAM,+BAA+B,CAAC;AAWvC,QAAQ,CAAC,4BAA4B,EAAE,GAAG,EAAE;IAC3C,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC3D,MAAM,CACL,0BAA0B,CAAC;YAC1B,WAAW,EAAE,KAAK;SAClB,CAAC,CACF,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;IAC7B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wEAAwE,EAAE,GAAG,EAAE;QACjF,MAAM,CACL,0BAA0B,CAAC;YAC1B,WAAW,EAAE,OAAO;YACpB,KAAK,EAAE,SAAS;SAChB,CAAC,CACF,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;IAC7B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2EAA2E,EAAE,GAAG,EAAE;QACpF,MAAM,CACL,0BAA0B,CAAC;YAC1B,WAAW,EAAE,KAAK;YAClB,KAAK,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,eAAe,EAAE;SAChD,CAAC,CACF,CAAC,OAAO,CAAC,
|
|
1
|
+
{"version":3,"file":"frictionlessResultGuard.test.js","sourceRoot":"","sources":["../../src/tests/frictionlessResultGuard.test.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EACN,4BAA4B,EAC5B,0BAA0B,GAC1B,MAAM,+BAA+B,CAAC;AAWvC,QAAQ,CAAC,4BAA4B,EAAE,GAAG,EAAE;IAC3C,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC3D,MAAM,CACL,0BAA0B,CAAC;YAC1B,WAAW,EAAE,KAAK;SAClB,CAAC,CACF,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;IAC7B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wEAAwE,EAAE,GAAG,EAAE;QACjF,MAAM,CACL,0BAA0B,CAAC;YAC1B,WAAW,EAAE,OAAO;YACpB,KAAK,EAAE,SAAS;SAChB,CAAC,CACF,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;IAC7B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2EAA2E,EAAE,GAAG,EAAE;QACpF,MAAM,CACL,0BAA0B,CAAC;YAC1B,WAAW,EAAE,KAAK;YAClB,KAAK,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,eAAe,EAAE;SAChD,CAAC,CACF,CAAC,OAAO,CAAC;YACT,IAAI,EAAE,OAAO;YACb,OAAO,EAAE,MAAM;YACf,GAAG,EAAE,eAAe;YACpB,SAAS,EAAE,IAAI;SACf,CAAC,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iEAAiE,EAAE,GAAG,EAAE;QAC1E,MAAM,OAAO,GAAG,0BAA0B,CAAC;YAC1C,WAAW,EAAE,KAAK;YAClB,KAAK,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE;SAC1B,CAAC,CAAC;QACH,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACnC,IAAI,OAAO,CAAC,IAAI,KAAK,OAAO;YAAE,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC;QACxE,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACrC,MAAM,CAAC,KAAK,IAAI,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0GAA0G,EAAE,GAAG,EAAE;QAOnH,MAAM,CACL,0BAA0B,CAAC;YAG1B,KAAK,EAAE,SAAS;SAChB,CAAC,CACF,CAAC,OAAO,CAAC;YACT,IAAI,EAAE,OAAO;YACb,OAAO,EAAE,4BAA4B;YACrC,SAAS,EAAE,KAAK;SAChB,CAAC,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yEAAyE,EAAE,GAAG,EAAE;QAClF,MAAM,CAAC,0BAA0B,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;YAC9C,IAAI,EAAE,OAAO;YACb,OAAO,EAAE,4BAA4B;YACrC,SAAS,EAAE,KAAK;SAChB,CAAC,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mFAAmF,EAAE,GAAG,EAAE;QAG5F,MAAM,CACL,0BAA0B,CAAC;YAC1B,KAAK,EAAE,EAAE,OAAO,EAAE,cAAc,EAAE,GAAG,EAAE,kBAAkB,EAAE;SAC3D,CAAC,CACF,CAAC,OAAO,CAAC;YACT,IAAI,EAAE,OAAO;YACb,OAAO,EAAE,cAAc;YACvB,GAAG,EAAE,kBAAkB;YACvB,SAAS,EAAE,IAAI;SACf,CAAC,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,0BAA0B,EAAE,GAAG,EAAE;QACzC,MAAM,WAAW,GAAG,CAAC,GAAY,EAAW,EAAE;YAC7C,MAAM,OAAO,GAAG,0BAA0B,CAAC;gBAC1C,KAAK,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,CAAC,GAAG,KAAK,SAAS,IAAI,EAAE,GAAG,EAAE,CAAC,EAAE;aAC5D,CAAC,CAAC;YACH,IAAI,OAAO,CAAC,IAAI,KAAK,OAAO;gBAAE,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC;YACxE,OAAO,OAAO,CAAC,SAAS,CAAC;QAC1B,CAAC,CAAC;QAEF,EAAE,CAAC,IAAI,CAAC;YACP,6BAA6B;YAC7B,sBAAsB;YACtB,6BAA6B;YAC7B,4BAA4B;SAC5B,CAAC,CAAC,mDAAmD,EAAE,CAAC,GAAW,EAAE,EAAE;YAGvE,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACtC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,IAAI,CAAC;YACP,yBAAyB;YACzB,oBAAoB;YACpB,qBAAqB;YACrB,wBAAwB;YACxB,wBAAwB;YACxB,oBAAoB;YACpB,mBAAmB;YACnB,uBAAuB;YACvB,iBAAiB;YACjB,iBAAiB;YACjB,eAAe;YACf,kBAAkB;SAClB,CAAC,CAAC,mCAAmC,EAAE,CAAC,GAAW,EAAE,EAAE;YAGvD,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACtC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,0DAA0D,EAAE,GAAG,EAAE;YACnE,MAAM,CAAC,WAAW,CAAC,0BAA0B,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC7D,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,6EAA6E,EAAE,GAAG,EAAE;YAKtF,MAAM,CAAC,WAAW,CAAC,iBAAiB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,IAAI,CAAC,CAAC,aAAa,EAAE,2BAA2B,EAAE,kBAAkB,CAAC,CAAC,CACxE,yCAAyC,EACzC,CAAC,GAAW,EAAE,EAAE;YACf,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrC,CAAC,CACD,CAAC;QAEF,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;YAEvD,MAAM,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC5C,CAAC,CAAC,CAAC;IACJ,CAAC,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prosopo/procaptcha-frictionless",
|
|
3
|
-
"version": "2.16.
|
|
3
|
+
"version": "2.16.4",
|
|
4
4
|
"author": "PROSOPO LIMITED <info@prosopo.io>",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -30,26 +30,26 @@
|
|
|
30
30
|
},
|
|
31
31
|
"browserslist": ["> 0.5%, last 2 versions, not dead"],
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@prosopo/api": "4.2.
|
|
34
|
-
"@prosopo/common": "3.1.
|
|
35
|
-
"@prosopo/locale": "3.4.
|
|
36
|
-
"@prosopo/procaptcha-common": "2.14.
|
|
37
|
-
"@prosopo/procaptcha-pow": "2.12.
|
|
38
|
-
"@prosopo/procaptcha-puzzle": "2.13.
|
|
39
|
-
"@prosopo/procaptcha-react": "2.11.
|
|
40
|
-
"@prosopo/types": "5.8.
|
|
33
|
+
"@prosopo/api": "4.2.4",
|
|
34
|
+
"@prosopo/common": "3.1.56",
|
|
35
|
+
"@prosopo/locale": "3.4.3",
|
|
36
|
+
"@prosopo/procaptcha-common": "2.14.1",
|
|
37
|
+
"@prosopo/procaptcha-pow": "2.12.5",
|
|
38
|
+
"@prosopo/procaptcha-puzzle": "2.13.1",
|
|
39
|
+
"@prosopo/procaptcha-react": "2.11.1",
|
|
40
|
+
"@prosopo/types": "5.8.4",
|
|
41
41
|
"@prosopo/widget-skeleton": "2.8.7",
|
|
42
42
|
"dotenv": "16.4.5",
|
|
43
|
-
"react": "19.
|
|
43
|
+
"react": "19.3.0"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"@prosopo/config": "3.3.
|
|
46
|
+
"@prosopo/config": "3.3.17",
|
|
47
47
|
"@types/node": "22.10.2",
|
|
48
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",
|
|
52
|
-
"react-dom": "19.
|
|
52
|
+
"react-dom": "19.3.0",
|
|
53
53
|
"tslib": "2.7.0",
|
|
54
54
|
"tsx": "4.20.3",
|
|
55
55
|
"typescript": "5.6.2",
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
import {
|
|
24
24
|
CaptchaType,
|
|
25
25
|
type FrictionlessState,
|
|
26
|
+
ModeEnum,
|
|
26
27
|
type ModeType,
|
|
27
28
|
PROCAPTCHA_START_EVENT,
|
|
28
29
|
ProcaptchaConfigSchema,
|
|
@@ -149,6 +150,13 @@ export const ProcaptchaFrictionless = ({
|
|
|
149
150
|
const manualStartedRef = useRef(false);
|
|
150
151
|
// The placeholder is built before `start()` exists in this scope.
|
|
151
152
|
const manualCheckboxHandlerRef = useRef(noopCheckboxHandler);
|
|
153
|
+
// The inner widget only listens for `procaptcha:execute` once /frictionless
|
|
154
|
+
// has answered, its chunk has loaded and it has mounted. An execute() in
|
|
155
|
+
// that window (e.g. a fast form submit) would otherwise be dropped and the
|
|
156
|
+
// challenge never open, so it is held here and replayed on mount.
|
|
157
|
+
const innerWidgetRenderedRef = useRef(false);
|
|
158
|
+
const innerWidgetListeningRef = useRef(false);
|
|
159
|
+
const pendingExecuteRef = useRef(false);
|
|
152
160
|
|
|
153
161
|
useEffect(() => {
|
|
154
162
|
if (!config.language) return;
|
|
@@ -199,6 +207,7 @@ export const ProcaptchaFrictionless = ({
|
|
|
199
207
|
restartComponentTimeout();
|
|
200
208
|
}, 0);
|
|
201
209
|
}
|
|
210
|
+
innerWidgetRenderedRef.current = false;
|
|
202
211
|
setComponentToRender(
|
|
203
212
|
renderPlaceholder(
|
|
204
213
|
config.theme,
|
|
@@ -321,6 +330,7 @@ export const ProcaptchaFrictionless = ({
|
|
|
321
330
|
const startCoords = escalationCoords ?? retryStartCoords;
|
|
322
331
|
mountCountRef.current += 1;
|
|
323
332
|
const mountKey = mountCountRef.current;
|
|
333
|
+
innerWidgetRenderedRef.current = true;
|
|
324
334
|
|
|
325
335
|
if (captchaType === CaptchaType.authenticated) {
|
|
326
336
|
// Web Bot Auth pre-verified pass-through. No challenge, no
|
|
@@ -423,6 +433,14 @@ export const ProcaptchaFrictionless = ({
|
|
|
423
433
|
|
|
424
434
|
const guard = evaluateFrictionlessResult(result);
|
|
425
435
|
if (guard.kind === "error") {
|
|
436
|
+
// Throwing hands this to providerRetry, which re-rolls onto a
|
|
437
|
+
// different provider. The client does not throw on a 400 with a
|
|
438
|
+
// JSON body, so without this an unrecognised provider-side
|
|
439
|
+
// failure stranded the user on the first response even when
|
|
440
|
+
// every other node was healthy.
|
|
441
|
+
if (guard.retryable) {
|
|
442
|
+
throw new Error(guard.message);
|
|
443
|
+
}
|
|
426
444
|
stateRef.current = {
|
|
427
445
|
...stateRef.current,
|
|
428
446
|
loading: false,
|
|
@@ -461,6 +479,10 @@ export const ProcaptchaFrictionless = ({
|
|
|
461
479
|
5,
|
|
462
480
|
).finally(() => {
|
|
463
481
|
if (stateRef.current.attemptCount >= 5) {
|
|
482
|
+
// Retries swallow the underlying error, so without this a site's
|
|
483
|
+
// error callback never fires for a failure that retried — it would
|
|
484
|
+
// have fired immediately before retrying was introduced.
|
|
485
|
+
events.onError(new Error("Cannot load CAPTCHA"));
|
|
464
486
|
fallOverWithStyle();
|
|
465
487
|
restartComponentTimeout();
|
|
466
488
|
}
|
|
@@ -475,6 +497,7 @@ export const ProcaptchaFrictionless = ({
|
|
|
475
497
|
manualStartedRef.current = true;
|
|
476
498
|
pendingRetryCoordsRef.current = coords ?? null;
|
|
477
499
|
nextMountAutoStartRef.current = autoStart;
|
|
500
|
+
innerWidgetRenderedRef.current = false;
|
|
478
501
|
setComponentToRender(
|
|
479
502
|
renderPlaceholder(
|
|
480
503
|
config.theme,
|
|
@@ -520,6 +543,40 @@ export const ProcaptchaFrictionless = ({
|
|
|
520
543
|
};
|
|
521
544
|
}, [manualStart, container]);
|
|
522
545
|
|
|
546
|
+
// Mirrors the inner widgets' own listeners: a targeted execute() arrives on
|
|
547
|
+
// the container in either mode, a bare one on document in invisible mode.
|
|
548
|
+
// Manual start has its own listener above that defers the whole flow.
|
|
549
|
+
useEffect(() => {
|
|
550
|
+
if (manualStart) return;
|
|
551
|
+
const invisible = config.mode === ModeEnum.invisible;
|
|
552
|
+
const onExecuteEvent = () => {
|
|
553
|
+
if (!innerWidgetListeningRef.current) pendingExecuteRef.current = true;
|
|
554
|
+
};
|
|
555
|
+
container?.addEventListener(PROCAPTCHA_EXECUTE_EVENT, onExecuteEvent);
|
|
556
|
+
if (invisible) {
|
|
557
|
+
document.addEventListener(PROCAPTCHA_EXECUTE_EVENT, onExecuteEvent);
|
|
558
|
+
}
|
|
559
|
+
return () => {
|
|
560
|
+
container?.removeEventListener(PROCAPTCHA_EXECUTE_EVENT, onExecuteEvent);
|
|
561
|
+
if (invisible) {
|
|
562
|
+
document.removeEventListener(PROCAPTCHA_EXECUTE_EVENT, onExecuteEvent);
|
|
563
|
+
}
|
|
564
|
+
};
|
|
565
|
+
}, [manualStart, config.mode, container]);
|
|
566
|
+
|
|
567
|
+
// Child effects run before this one, so a newly mounted inner widget is
|
|
568
|
+
// already listening when the held execute() is replayed. It goes to the
|
|
569
|
+
// container, not document, so other widgets on the page don't run twice.
|
|
570
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: intentional — must run after every commit that swaps the rendered widget.
|
|
571
|
+
useEffect(() => {
|
|
572
|
+
innerWidgetListeningRef.current = innerWidgetRenderedRef.current;
|
|
573
|
+
if (!innerWidgetListeningRef.current || !pendingExecuteRef.current) return;
|
|
574
|
+
pendingExecuteRef.current = false;
|
|
575
|
+
(container ?? document).dispatchEvent(
|
|
576
|
+
new CustomEvent(PROCAPTCHA_EXECUTE_EVENT),
|
|
577
|
+
);
|
|
578
|
+
}, [componentToRender, container]);
|
|
579
|
+
|
|
523
580
|
// Track which config identity has already been started for. Host
|
|
524
581
|
// pages often recreate the `callbacks` object (and sometimes the whole
|
|
525
582
|
// `config`) on every render, which — before this guard — re-fired
|
|
@@ -14,7 +14,41 @@
|
|
|
14
14
|
|
|
15
15
|
export type FrictionlessGuardOutcome =
|
|
16
16
|
| { kind: "pass" }
|
|
17
|
-
| { kind: "error"; message: string; key?: string };
|
|
17
|
+
| { kind: "error"; message: string; key?: string; retryable: boolean };
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Error keys that describe the caller's situation rather than the provider's
|
|
21
|
+
* health, so re-rolling onto another provider would return the same answer.
|
|
22
|
+
*
|
|
23
|
+
* Two groups. Integration faults (site key, origin, captcha type) need the
|
|
24
|
+
* site owner to change something and the text is what tells them what. Policy
|
|
25
|
+
* denials are a decision about this visitor that every provider shares —
|
|
26
|
+
* retrying them would both mislead the user and hammer the fleet on behalf of
|
|
27
|
+
* traffic we have just refused.
|
|
28
|
+
*
|
|
29
|
+
* Anything outside this list is treated as the provider failing, not the
|
|
30
|
+
* caller: see {@link evaluateFrictionlessResult}.
|
|
31
|
+
*/
|
|
32
|
+
const TERMINAL_ERROR_KEYS: ReadonlySet<string> = new Set([
|
|
33
|
+
"API.SITE_KEY_NOT_REGISTERED",
|
|
34
|
+
"API.INVALID_SITE_KEY",
|
|
35
|
+
"API.UNAUTHORIZED_ORIGIN_URL",
|
|
36
|
+
"API.INCORRECT_CAPTCHA_TYPE",
|
|
37
|
+
"API.ACCESS_POLICY_BLOCK",
|
|
38
|
+
"API.ABUSER_BLOCKED",
|
|
39
|
+
"API.CRAWLER_BLOCKED",
|
|
40
|
+
"API.DATACENTER_BLOCKED",
|
|
41
|
+
"API.DISALLOWED_WEBVIEW",
|
|
42
|
+
"API.MOBILE_BLOCKED",
|
|
43
|
+
"API.PROXY_BLOCKED",
|
|
44
|
+
"API.SATELLITE_BLOCKED",
|
|
45
|
+
"API.TOR_BLOCKED",
|
|
46
|
+
"API.VPN_BLOCKED",
|
|
47
|
+
"API.FORBIDDEN",
|
|
48
|
+
"API.UNAUTHORIZED",
|
|
49
|
+
// carries its own ten-second restart in ProcaptchaFrictionless
|
|
50
|
+
"CAPTCHA.NO_SESSION_FOUND",
|
|
51
|
+
]);
|
|
18
52
|
|
|
19
53
|
// Fields the guard reads off the `/frictionless` result. Kept minimal
|
|
20
54
|
// (subset of BotDetectionFunctionResult) so the helper can be exercised
|
|
@@ -49,14 +83,26 @@ export const evaluateFrictionlessResult = (
|
|
|
49
83
|
result: FrictionlessGuardInput,
|
|
50
84
|
): FrictionlessGuardOutcome => {
|
|
51
85
|
if (result.error?.message) {
|
|
86
|
+
const key = result.error.key;
|
|
52
87
|
return {
|
|
53
88
|
kind: "error",
|
|
54
89
|
message: result.error.message,
|
|
55
|
-
...(
|
|
90
|
+
...(key !== undefined && { key }),
|
|
91
|
+
// A key we do not recognise means the provider failed in a way it
|
|
92
|
+
// has no vocabulary for — API.BAD_REQUEST is what an unhandled throw
|
|
93
|
+
// inside a handler serialises to. That is worth trying another
|
|
94
|
+
// provider for. Retrying is gated on having a key at all: the
|
|
95
|
+
// bare-string errors handled below are hard blocks, and re-rolling
|
|
96
|
+
// those would hammer the fleet for traffic already refused.
|
|
97
|
+
retryable: key !== undefined && !TERMINAL_ERROR_KEYS.has(key),
|
|
56
98
|
};
|
|
57
99
|
}
|
|
58
100
|
if (!result.captchaType) {
|
|
59
|
-
return {
|
|
101
|
+
return {
|
|
102
|
+
kind: "error",
|
|
103
|
+
message: MISSING_CAPTCHA_TYPE_MESSAGE,
|
|
104
|
+
retryable: false,
|
|
105
|
+
};
|
|
60
106
|
}
|
|
61
107
|
return { kind: "pass" };
|
|
62
108
|
};
|