@prosopo/procaptcha-frictionless 2.13.0 → 2.13.2
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 +6 -5
- package/.turbo/turbo-build$colon$tsc.log +24 -24
- package/.turbo/turbo-build.log +7 -6
- package/CHANGELOG.md +36 -0
- package/dist/cjs/customDetectBot.cjs +21 -13
- package/dist/cjs/detectorPrefetch.cjs +65 -0
- package/dist/cjs/index.cjs +4 -0
- package/dist/customDetectBot.d.ts.map +1 -1
- package/dist/customDetectBot.js +21 -13
- package/dist/customDetectBot.js.map +1 -1
- package/dist/detectorPrefetch.d.ts +12 -0
- package/dist/detectorPrefetch.d.ts.map +1 -0
- package/dist/detectorPrefetch.js +63 -0
- package/dist/detectorPrefetch.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/tests/customDetectBotAssignTimeout.test.d.ts +2 -0
- package/dist/tests/customDetectBotAssignTimeout.test.d.ts.map +1 -0
- package/dist/tests/customDetectBotAssignTimeout.test.js +118 -0
- package/dist/tests/customDetectBotAssignTimeout.test.js.map +1 -0
- package/dist/tests/detectorPrefetch.test.d.ts +2 -0
- package/dist/tests/detectorPrefetch.test.d.ts.map +1 -0
- package/dist/tests/detectorPrefetch.test.js +71 -0
- package/dist/tests/detectorPrefetch.test.js.map +1 -0
- package/package.json +10 -10
- package/src/customDetectBot.ts +67 -33
- package/src/detectorPrefetch.ts +115 -0
- package/src/index.ts +1 -0
- package/src/tests/customDetectBotAssignTimeout.test.ts +180 -0
- package/src/tests/detectorPrefetch.test.ts +114 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
const mocks = vi.hoisted(() => ({
|
|
3
|
+
getFrictionlessCaptcha: vi.fn(),
|
|
4
|
+
getProcaptchaRandomActiveProvider: vi.fn(),
|
|
5
|
+
assignDetectorBundle: vi.fn(),
|
|
6
|
+
detectorLoaderFromScript: vi.fn(),
|
|
7
|
+
detect: vi.fn(),
|
|
8
|
+
}));
|
|
9
|
+
vi.mock("@prosopo/api", () => ({
|
|
10
|
+
ProviderApi: vi.fn(function () {
|
|
11
|
+
return {
|
|
12
|
+
getFrictionlessCaptcha: mocks.getFrictionlessCaptcha,
|
|
13
|
+
assignDetectorBundle: mocks.assignDetectorBundle,
|
|
14
|
+
};
|
|
15
|
+
}),
|
|
16
|
+
}));
|
|
17
|
+
vi.mock("@prosopo/procaptcha-common", () => ({
|
|
18
|
+
ExtensionLoader: vi.fn(async () => {
|
|
19
|
+
return class FakeExtension {
|
|
20
|
+
getAccount() {
|
|
21
|
+
return Promise.resolve({
|
|
22
|
+
account: { address: "5FakeUserAccountAddress" },
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
}),
|
|
27
|
+
getProcaptchaRandomActiveProvider: mocks.getProcaptchaRandomActiveProvider,
|
|
28
|
+
pickIpMode: vi.fn(() => undefined),
|
|
29
|
+
}));
|
|
30
|
+
vi.mock("../detectorLoader.js", () => ({
|
|
31
|
+
DetectorLoaderFromScript: mocks.detectorLoaderFromScript,
|
|
32
|
+
}));
|
|
33
|
+
import customDetectBot from "../customDetectBot.js";
|
|
34
|
+
const baseConfig = {
|
|
35
|
+
account: { address: "5CcNvLUdiXFpzKDMjThGLSK9rhWHA1H4EF3zrgkpkjAdqmuP" },
|
|
36
|
+
defaultEnvironment: "staging",
|
|
37
|
+
web2: true,
|
|
38
|
+
mode: "visible",
|
|
39
|
+
};
|
|
40
|
+
const assignResponse = {
|
|
41
|
+
useProviderBundle: true,
|
|
42
|
+
detectorSessionId: "det-5cad2419-43c0-41b7-a9b8-0117767624bc",
|
|
43
|
+
detectorScript: "SELF_CONTAINED_ESM",
|
|
44
|
+
status: "ok",
|
|
45
|
+
};
|
|
46
|
+
const detectionResult = {
|
|
47
|
+
token: "ENCRYPTED_TOKEN",
|
|
48
|
+
encryptHeadHash: "ENCRYPTED_HEAD_HASH",
|
|
49
|
+
userAccount: { account: { address: "5FakeUserAccountAddress" } },
|
|
50
|
+
shadowDomCleanup: () => undefined,
|
|
51
|
+
};
|
|
52
|
+
const captchaResponse = {
|
|
53
|
+
captchaType: "pow",
|
|
54
|
+
sessionId: "SID",
|
|
55
|
+
status: "ok",
|
|
56
|
+
};
|
|
57
|
+
const SLOW_MS = 6700;
|
|
58
|
+
const resolveAfter = (ms, value) => new Promise((resolve) => {
|
|
59
|
+
setTimeout(() => resolve(value), ms);
|
|
60
|
+
});
|
|
61
|
+
const postedToken = () => {
|
|
62
|
+
const call = mocks.getFrictionlessCaptcha.mock.calls[0];
|
|
63
|
+
return call?.[0];
|
|
64
|
+
};
|
|
65
|
+
const postedDetectorSessionId = () => {
|
|
66
|
+
const call = mocks.getFrictionlessCaptcha.mock.calls[0];
|
|
67
|
+
return call?.[6];
|
|
68
|
+
};
|
|
69
|
+
beforeEach(() => {
|
|
70
|
+
vi.useFakeTimers();
|
|
71
|
+
mocks.getFrictionlessCaptcha.mockReset();
|
|
72
|
+
mocks.getProcaptchaRandomActiveProvider.mockReset();
|
|
73
|
+
mocks.assignDetectorBundle.mockReset();
|
|
74
|
+
mocks.detectorLoaderFromScript.mockReset();
|
|
75
|
+
mocks.detect.mockReset();
|
|
76
|
+
mocks.getProcaptchaRandomActiveProvider.mockResolvedValue({
|
|
77
|
+
providerAccount: "dns-routed",
|
|
78
|
+
provider: { url: "https://staging-pronode3.prosopo.io" },
|
|
79
|
+
});
|
|
80
|
+
mocks.detect.mockResolvedValue(detectionResult);
|
|
81
|
+
mocks.detectorLoaderFromScript.mockResolvedValue(mocks.detect);
|
|
82
|
+
mocks.assignDetectorBundle.mockResolvedValue(assignResponse);
|
|
83
|
+
mocks.getFrictionlessCaptcha.mockResolvedValue(captchaResponse);
|
|
84
|
+
});
|
|
85
|
+
afterEach(() => {
|
|
86
|
+
vi.useRealTimers();
|
|
87
|
+
});
|
|
88
|
+
const runDetection = async () => {
|
|
89
|
+
const pending = customDetectBot(baseConfig, undefined, () => undefined);
|
|
90
|
+
await vi.advanceTimersByTimeAsync(SLOW_MS + 1000);
|
|
91
|
+
await pending;
|
|
92
|
+
};
|
|
93
|
+
describe("customDetectBot detector-assignment deadline", () => {
|
|
94
|
+
it("uses a bundle whose assign POST is slow", async () => {
|
|
95
|
+
mocks.assignDetectorBundle.mockImplementation(() => resolveAfter(SLOW_MS, assignResponse));
|
|
96
|
+
await runDetection();
|
|
97
|
+
expect(mocks.detectorLoaderFromScript).toHaveBeenCalledWith(assignResponse.detectorScript);
|
|
98
|
+
expect(postedToken()).toBe("ENCRYPTED_TOKEN");
|
|
99
|
+
expect(postedDetectorSessionId()).toBe(assignResponse.detectorSessionId);
|
|
100
|
+
});
|
|
101
|
+
it("uses a bundle whose blob import is slow", async () => {
|
|
102
|
+
mocks.detectorLoaderFromScript.mockImplementation(() => resolveAfter(SLOW_MS, mocks.detect));
|
|
103
|
+
await runDetection();
|
|
104
|
+
expect(postedToken()).toBe("ENCRYPTED_TOKEN");
|
|
105
|
+
expect(postedDetectorSessionId()).toBe(assignResponse.detectorSessionId);
|
|
106
|
+
});
|
|
107
|
+
it("still sends an empty token when the provider has no bundle to assign", async () => {
|
|
108
|
+
mocks.assignDetectorBundle.mockResolvedValue({
|
|
109
|
+
useProviderBundle: false,
|
|
110
|
+
status: "ok",
|
|
111
|
+
});
|
|
112
|
+
await runDetection();
|
|
113
|
+
expect(mocks.detectorLoaderFromScript).not.toHaveBeenCalled();
|
|
114
|
+
expect(postedToken()).toBeUndefined();
|
|
115
|
+
expect(postedDetectorSessionId()).toBeUndefined();
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
//# sourceMappingURL=customDetectBotAssignTimeout.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"customDetectBotAssignTimeout.test.js","sourceRoot":"","sources":["../../src/tests/customDetectBotAssignTimeout.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detectorPrefetch.test.d.ts","sourceRoot":"","sources":["../../src/tests/detectorPrefetch.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
const assignDetectorBundle = vi.fn();
|
|
3
|
+
const getProcaptchaRandomActiveProvider = vi.fn();
|
|
4
|
+
vi.mock("@prosopo/api", () => ({
|
|
5
|
+
ProviderApi: class {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.assignDetectorBundle = assignDetectorBundle;
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
}));
|
|
11
|
+
vi.mock("@prosopo/procaptcha-common", () => ({
|
|
12
|
+
getProcaptchaRandomActiveProvider: (...args) => getProcaptchaRandomActiveProvider(...args),
|
|
13
|
+
}));
|
|
14
|
+
const { prefetchDetector, takePrefetchedDetector, clearPrefetchedDetectors } = await import("../detectorPrefetch.js");
|
|
15
|
+
const ENV = "staging";
|
|
16
|
+
const SITE_KEY = "5CcNvLUdiXFpzKDMjThGLSK9rhWHA1H4EF3zrgkpkjAdqmuP";
|
|
17
|
+
const provider = { provider: { url: "https://pronode.example" } };
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
clearPrefetchedDetectors();
|
|
20
|
+
vi.clearAllMocks();
|
|
21
|
+
});
|
|
22
|
+
describe("detectorPrefetch", () => {
|
|
23
|
+
it("returns undefined when nothing was prefetched", () => {
|
|
24
|
+
expect(takePrefetchedDetector(ENV, undefined, SITE_KEY)).toBeUndefined();
|
|
25
|
+
});
|
|
26
|
+
it("resolves the provider and assigns a bundle", async () => {
|
|
27
|
+
getProcaptchaRandomActiveProvider.mockResolvedValue(provider);
|
|
28
|
+
assignDetectorBundle.mockResolvedValue({ useProviderBundle: true });
|
|
29
|
+
prefetchDetector(ENV, undefined, SITE_KEY);
|
|
30
|
+
const claimed = takePrefetchedDetector(ENV, undefined, SITE_KEY);
|
|
31
|
+
expect(claimed).toBeDefined();
|
|
32
|
+
const result = await claimed;
|
|
33
|
+
expect(result).toStrictEqual({
|
|
34
|
+
provider,
|
|
35
|
+
assigned: { useProviderBundle: true },
|
|
36
|
+
});
|
|
37
|
+
expect(assignDetectorBundle).toHaveBeenCalledWith(SITE_KEY);
|
|
38
|
+
});
|
|
39
|
+
it("is single-use, so a retry does not reuse a stale provider pin", async () => {
|
|
40
|
+
getProcaptchaRandomActiveProvider.mockResolvedValue(provider);
|
|
41
|
+
assignDetectorBundle.mockResolvedValue({ useProviderBundle: true });
|
|
42
|
+
prefetchDetector(ENV, undefined, SITE_KEY);
|
|
43
|
+
const first = takePrefetchedDetector(ENV, undefined, SITE_KEY);
|
|
44
|
+
await first;
|
|
45
|
+
expect(takePrefetchedDetector(ENV, undefined, SITE_KEY)).toBeUndefined();
|
|
46
|
+
});
|
|
47
|
+
it("does not start a second request for the same key", () => {
|
|
48
|
+
getProcaptchaRandomActiveProvider.mockResolvedValue(provider);
|
|
49
|
+
assignDetectorBundle.mockResolvedValue({ useProviderBundle: true });
|
|
50
|
+
prefetchDetector(ENV, undefined, SITE_KEY);
|
|
51
|
+
prefetchDetector(ENV, undefined, SITE_KEY);
|
|
52
|
+
expect(getProcaptchaRandomActiveProvider).toHaveBeenCalledTimes(1);
|
|
53
|
+
});
|
|
54
|
+
it("keys on site key and ip mode, so a different widget does not claim it", () => {
|
|
55
|
+
getProcaptchaRandomActiveProvider.mockResolvedValue(provider);
|
|
56
|
+
assignDetectorBundle.mockResolvedValue({ useProviderBundle: true });
|
|
57
|
+
prefetchDetector(ENV, "ipv4", SITE_KEY);
|
|
58
|
+
expect(takePrefetchedDetector(ENV, "ipv6", SITE_KEY)).toBeUndefined();
|
|
59
|
+
expect(takePrefetchedDetector(ENV, undefined, SITE_KEY)).toBeUndefined();
|
|
60
|
+
expect(takePrefetchedDetector(ENV, "ipv4", "other-key")).toBeUndefined();
|
|
61
|
+
expect(takePrefetchedDetector(ENV, "ipv4", SITE_KEY)).toBeDefined();
|
|
62
|
+
});
|
|
63
|
+
it("surfaces failure to the claimant without an unhandled rejection", async () => {
|
|
64
|
+
getProcaptchaRandomActiveProvider.mockRejectedValue(new Error("no providers"));
|
|
65
|
+
prefetchDetector(ENV, undefined, SITE_KEY);
|
|
66
|
+
const claimed = takePrefetchedDetector(ENV, undefined, SITE_KEY);
|
|
67
|
+
expect(claimed).toBeDefined();
|
|
68
|
+
await expect(claimed).rejects.toThrow("no providers");
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
//# sourceMappingURL=detectorPrefetch.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detectorPrefetch.test.js","sourceRoot":"","sources":["../../src/tests/detectorPrefetch.test.ts"],"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.
|
|
3
|
+
"version": "2.13.2",
|
|
4
4
|
"author": "PROSOPO LIMITED <info@prosopo.io>",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -30,20 +30,20 @@
|
|
|
30
30
|
},
|
|
31
31
|
"browserslist": ["> 0.5%, last 2 versions, not dead"],
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@prosopo/api": "4.0.
|
|
34
|
-
"@prosopo/common": "3.1.
|
|
35
|
-
"@prosopo/locale": "3.2.
|
|
36
|
-
"@prosopo/procaptcha-common": "2.11.
|
|
37
|
-
"@prosopo/procaptcha-pow": "2.10.
|
|
38
|
-
"@prosopo/procaptcha-puzzle": "2.10.
|
|
39
|
-
"@prosopo/procaptcha-react": "2.9.
|
|
40
|
-
"@prosopo/types": "5.0.
|
|
33
|
+
"@prosopo/api": "4.0.1",
|
|
34
|
+
"@prosopo/common": "3.1.49",
|
|
35
|
+
"@prosopo/locale": "3.2.9",
|
|
36
|
+
"@prosopo/procaptcha-common": "2.11.21",
|
|
37
|
+
"@prosopo/procaptcha-pow": "2.10.28",
|
|
38
|
+
"@prosopo/procaptcha-puzzle": "2.10.44",
|
|
39
|
+
"@prosopo/procaptcha-react": "2.9.101",
|
|
40
|
+
"@prosopo/types": "5.0.1",
|
|
41
41
|
"@prosopo/widget-skeleton": "2.8.5",
|
|
42
42
|
"dotenv": "16.4.5",
|
|
43
43
|
"react": "18.3.1"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"@prosopo/config": "3.3.
|
|
46
|
+
"@prosopo/config": "3.3.7",
|
|
47
47
|
"@types/node": "22.10.2",
|
|
48
48
|
"@vitest/coverage-v8": "4.1.10",
|
|
49
49
|
"concurrently": "9.0.1",
|
package/src/customDetectBot.ts
CHANGED
|
@@ -29,12 +29,17 @@ import {
|
|
|
29
29
|
DetectorLoaderFromScript,
|
|
30
30
|
type DetectorType,
|
|
31
31
|
} from "./detectorLoader.js";
|
|
32
|
+
import {
|
|
33
|
+
type PrefetchedDetector,
|
|
34
|
+
takePrefetchedDetector,
|
|
35
|
+
} from "./detectorPrefetch.js";
|
|
32
36
|
|
|
33
|
-
// Upper bound on the
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
|
|
37
|
+
// Upper bound on the assign POST, which serves the detector bundle inline
|
|
38
|
+
// (~215 KB gzipped). The hop is always cold — /healthz pins a different
|
|
39
|
+
// hostname, so it pays fresh DNS + TLS + a CORS preflight before the download
|
|
40
|
+
// even starts, measured at 6.7s against staging. Anything tighter silently
|
|
41
|
+
// drops the detector and sends an empty token.
|
|
42
|
+
const ASSIGN_TIMEOUT_MS = 10_000;
|
|
38
43
|
|
|
39
44
|
// The page(s) the widget is rendered on, reduced to origin + path.
|
|
40
45
|
// Deliberately built from `origin` + `pathname` (never `href`) so the query
|
|
@@ -168,11 +173,39 @@ const customDetectBot: BotDetectionFunction = async (
|
|
|
168
173
|
// preference so frictionless and the subsequent captcha hops stay on the
|
|
169
174
|
// same stack. Resolved up front — before detection rather than alongside it —
|
|
170
175
|
// because the detector bundle is served BY this provider.
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
+
// The bundle entry starts provider resolution + assign as soon as it has read
|
|
177
|
+
// the site key off the DOM, which is well before React has mounted this
|
|
178
|
+
// widget. Claim that in-flight work if it exists rather than repeating it.
|
|
179
|
+
// Only valid on a first attempt: a retry is retrying *because* the pinned
|
|
180
|
+
// pronode failed, so it must re-resolve.
|
|
181
|
+
const isFirstAttempt = !retryContext || retryContext.attempt <= 1;
|
|
182
|
+
const prefetched = isFirstAttempt
|
|
183
|
+
? takePrefetchedDetector(
|
|
184
|
+
config.defaultEnvironment,
|
|
185
|
+
ipMode,
|
|
186
|
+
config.account.address,
|
|
187
|
+
)
|
|
188
|
+
: undefined;
|
|
189
|
+
|
|
190
|
+
// A prefetch that failed must not fail the flow — it is an optimisation, and
|
|
191
|
+
// the normal path below handles provider selection and assign failure
|
|
192
|
+
// already. So swallow it and re-resolve.
|
|
193
|
+
let prefetchedResult: PrefetchedDetector | undefined;
|
|
194
|
+
if (prefetched) {
|
|
195
|
+
try {
|
|
196
|
+
prefetchedResult = await prefetched;
|
|
197
|
+
} catch {
|
|
198
|
+
prefetchedResult = undefined;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const provider =
|
|
203
|
+
prefetchedResult?.provider ??
|
|
204
|
+
(await getProcaptchaRandomActiveProvider(
|
|
205
|
+
config.defaultEnvironment,
|
|
206
|
+
ipMode,
|
|
207
|
+
retryContext,
|
|
208
|
+
));
|
|
176
209
|
|
|
177
210
|
const providerApi = new ProviderApi(
|
|
178
211
|
provider.provider.url,
|
|
@@ -183,21 +216,24 @@ const customDetectBot: BotDetectionFunction = async (
|
|
|
183
216
|
// in the provider-served pool: when the provider has a populated pool it
|
|
184
217
|
// returns the obfuscated detector (each with its own keys + inner cipher)
|
|
185
218
|
// plus a detectorSessionId. When it cannot (no pool, network, decode, or
|
|
186
|
-
// timeout) we have NO detector to run
|
|
187
|
-
// signal `detectorUnavailable` and the provider serves a PoW challenge.
|
|
219
|
+
// timeout) we have NO detector to run and there is no bundled fallback.
|
|
188
220
|
let detectorSessionId: string | undefined;
|
|
189
221
|
let providerDetect: DetectorType | undefined;
|
|
190
222
|
try {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
223
|
+
// Reuse the prefetched assignment when the entry already fetched one for
|
|
224
|
+
// this provider; otherwise issue it now.
|
|
225
|
+
const assigned =
|
|
226
|
+
prefetchedResult?.assigned ??
|
|
227
|
+
(await withTimeout(
|
|
228
|
+
providerApi.assignDetectorBundle(config.account.address),
|
|
229
|
+
ASSIGN_TIMEOUT_MS,
|
|
230
|
+
));
|
|
195
231
|
if (assigned.useProviderBundle && assigned.detectorScript) {
|
|
232
|
+
// Deliberately untimed: this is a local parse + evaluate, so a timer
|
|
233
|
+
// cannot rescue a stalled main thread — it can only throw away a bundle
|
|
234
|
+
// we are already holding.
|
|
235
|
+
providerDetect = await DetectorLoaderFromScript(assigned.detectorScript);
|
|
196
236
|
detectorSessionId = assigned.detectorSessionId;
|
|
197
|
-
providerDetect = await withTimeout(
|
|
198
|
-
DetectorLoaderFromScript(assigned.detectorScript),
|
|
199
|
-
ASSIGN_TIMEOUT_MS,
|
|
200
|
-
);
|
|
201
237
|
}
|
|
202
238
|
} catch {
|
|
203
239
|
// No detector available — fall through to the PoW request below.
|
|
@@ -206,14 +242,14 @@ const customDetectBot: BotDetectionFunction = async (
|
|
|
206
242
|
const ExtClass = await extClassPromise;
|
|
207
243
|
const ext = new ExtClass();
|
|
208
244
|
|
|
209
|
-
// No provider detector ⇒ no detection is possible
|
|
210
|
-
//
|
|
211
|
-
//
|
|
245
|
+
// No provider detector ⇒ no detection is possible, so there is nothing to
|
|
246
|
+
// send. The request goes out with an empty token and the provider decides
|
|
247
|
+
// what to serve; the client gets no say in that.
|
|
212
248
|
if (providerDetect === undefined) {
|
|
213
249
|
const userAccount = await ext.getAccount(config);
|
|
214
250
|
const { currentUrl: fallbackUrl, iframeUrl: fallbackIframeUrl } =
|
|
215
251
|
getCurrentPageUrls();
|
|
216
|
-
const
|
|
252
|
+
const captcha = await withTimeout(
|
|
217
253
|
providerApi.getFrictionlessCaptcha(
|
|
218
254
|
undefined,
|
|
219
255
|
undefined,
|
|
@@ -222,15 +258,14 @@ const customDetectBot: BotDetectionFunction = async (
|
|
|
222
258
|
config.mode,
|
|
223
259
|
undefined,
|
|
224
260
|
undefined,
|
|
225
|
-
true,
|
|
226
261
|
fallbackUrl,
|
|
227
262
|
fallbackIframeUrl,
|
|
228
263
|
),
|
|
229
264
|
10000,
|
|
230
265
|
);
|
|
231
|
-
if (
|
|
266
|
+
if (captcha.dns_url) {
|
|
232
267
|
try {
|
|
233
|
-
void fetch(
|
|
268
|
+
void fetch(captcha.dns_url, {
|
|
234
269
|
method: "GET",
|
|
235
270
|
mode: "no-cors",
|
|
236
271
|
credentials: "omit",
|
|
@@ -242,13 +277,13 @@ const customDetectBot: BotDetectionFunction = async (
|
|
|
242
277
|
}
|
|
243
278
|
}
|
|
244
279
|
return {
|
|
245
|
-
captchaType:
|
|
246
|
-
sessionId:
|
|
280
|
+
captchaType: captcha.captchaType,
|
|
281
|
+
sessionId: captcha.sessionId,
|
|
247
282
|
provider: provider,
|
|
248
|
-
status:
|
|
283
|
+
status: captcha.status,
|
|
249
284
|
userAccount: userAccount,
|
|
250
|
-
error:
|
|
251
|
-
hp:
|
|
285
|
+
error: captcha.error,
|
|
286
|
+
hp: captcha.hp,
|
|
252
287
|
};
|
|
253
288
|
}
|
|
254
289
|
|
|
@@ -275,7 +310,6 @@ const customDetectBot: BotDetectionFunction = async (
|
|
|
275
310
|
config.mode,
|
|
276
311
|
undefined,
|
|
277
312
|
detectorSessionId,
|
|
278
|
-
undefined,
|
|
279
313
|
currentUrl,
|
|
280
314
|
iframeUrl,
|
|
281
315
|
);
|
|
@@ -0,0 +1,115 @@
|
|
|
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
|
+
/**
|
|
16
|
+
* Detector bundle prefetch.
|
|
17
|
+
*
|
|
18
|
+
* Since the detector moved into the provider-served pool, the frictionless flow
|
|
19
|
+
* cannot start until `/detector/assign` has returned. That request is issued by
|
|
20
|
+
* `customDetectBot`, which only runs once React has mounted the widget — and
|
|
21
|
+
* that mount sits behind the bundle's dynamic-import chain. Measured on a
|
|
22
|
+
* staging demo page the assign request did not leave the browser until 1513 ms,
|
|
23
|
+
* of which ~700 ms was purely waiting for chunks to arrive in sequence.
|
|
24
|
+
*
|
|
25
|
+
* Nothing in the request depends on React, i18n or the widget config: it needs
|
|
26
|
+
* the site key (a DOM attribute, readable immediately), the environment (a
|
|
27
|
+
* build-time constant) and the IP-mode flags (DOM attributes). So the entry
|
|
28
|
+
* point kicks it off as soon as it has read those, and `customDetectBot` picks
|
|
29
|
+
* up the in-flight promise instead of starting its own.
|
|
30
|
+
*
|
|
31
|
+
* The cache is deliberately single-use. A provider pin is only valid for the
|
|
32
|
+
* attempt it was made for — on a retry the previous pronode is the one that
|
|
33
|
+
* just failed — so a consumed entry is dropped and the retry re-resolves.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import { ProviderApi } from "@prosopo/api";
|
|
37
|
+
import { getProcaptchaRandomActiveProvider } from "@prosopo/procaptcha-common";
|
|
38
|
+
import type {
|
|
39
|
+
AssignDetectorBundleResponse,
|
|
40
|
+
EnvironmentTypes,
|
|
41
|
+
RandomProvider,
|
|
42
|
+
} from "@prosopo/types";
|
|
43
|
+
|
|
44
|
+
// `IpMode` is declared in @prosopo/load-balancer, which this package does not
|
|
45
|
+
// depend on. Derive it from the selector we already call rather than adding a
|
|
46
|
+
// dependency (and a matching tsconfig project reference) for one type alias —
|
|
47
|
+
// this also cannot drift from the function's real signature.
|
|
48
|
+
type IpModeParam = Parameters<typeof getProcaptchaRandomActiveProvider>[1];
|
|
49
|
+
|
|
50
|
+
export interface PrefetchedDetector {
|
|
51
|
+
provider: RandomProvider;
|
|
52
|
+
assigned: AssignDetectorBundleResponse;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const inFlight = new Map<string, Promise<PrefetchedDetector>>();
|
|
56
|
+
|
|
57
|
+
const keyOf = (
|
|
58
|
+
environment: EnvironmentTypes,
|
|
59
|
+
ipMode: IpModeParam,
|
|
60
|
+
siteKey: string,
|
|
61
|
+
): string => `${environment}|${ipMode ?? "auto"}|${siteKey}`;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Start resolving a provider and assigning a detector bundle. Safe to call more
|
|
65
|
+
* than once for the same key — subsequent calls join the in-flight request.
|
|
66
|
+
*
|
|
67
|
+
* Never rejects to the caller: a failed prefetch is indistinguishable from
|
|
68
|
+
* never having prefetched, and `customDetectBot` already handles assign failure
|
|
69
|
+
* by falling back to PoW. Returning a rejected promise here would surface as an
|
|
70
|
+
* unhandled rejection in the host page.
|
|
71
|
+
*/
|
|
72
|
+
export const prefetchDetector = (
|
|
73
|
+
environment: EnvironmentTypes,
|
|
74
|
+
ipMode: IpModeParam,
|
|
75
|
+
siteKey: string,
|
|
76
|
+
): void => {
|
|
77
|
+
const key = keyOf(environment, ipMode, siteKey);
|
|
78
|
+
if (inFlight.has(key)) return;
|
|
79
|
+
|
|
80
|
+
const promise = (async (): Promise<PrefetchedDetector> => {
|
|
81
|
+
const provider = await getProcaptchaRandomActiveProvider(
|
|
82
|
+
environment,
|
|
83
|
+
ipMode,
|
|
84
|
+
);
|
|
85
|
+
const providerApi = new ProviderApi(provider.provider.url, siteKey);
|
|
86
|
+
const assigned = await providerApi.assignDetectorBundle(siteKey);
|
|
87
|
+
return { provider, assigned };
|
|
88
|
+
})();
|
|
89
|
+
|
|
90
|
+
// Attach a no-op catch so a failed prefetch never becomes an unhandled
|
|
91
|
+
// rejection. `takePrefetchedDetector`'s consumer still sees the rejection on
|
|
92
|
+
// the original promise and falls back.
|
|
93
|
+
promise.catch(() => undefined);
|
|
94
|
+
inFlight.set(key, promise);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Claim a prefetched assignment, if one was started for this exact key. The
|
|
99
|
+
* entry is removed, so a retry does not reuse a pin that may have just failed.
|
|
100
|
+
*/
|
|
101
|
+
export const takePrefetchedDetector = (
|
|
102
|
+
environment: EnvironmentTypes,
|
|
103
|
+
ipMode: IpModeParam,
|
|
104
|
+
siteKey: string,
|
|
105
|
+
): Promise<PrefetchedDetector> | undefined => {
|
|
106
|
+
const key = keyOf(environment, ipMode, siteKey);
|
|
107
|
+
const promise = inFlight.get(key);
|
|
108
|
+
if (promise) inFlight.delete(key);
|
|
109
|
+
return promise;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/** Test seam — drops any in-flight prefetches. */
|
|
113
|
+
export const clearPrefetchedDetectors = (): void => {
|
|
114
|
+
inFlight.clear();
|
|
115
|
+
};
|
package/src/index.ts
CHANGED