@prosopo/procaptcha-frictionless 2.12.24 → 2.13.1
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 +13 -14
- package/.turbo/turbo-build$colon$tsc.log +24 -27
- package/.turbo/turbo-build.log +14 -15
- package/CHANGELOG.md +41 -0
- package/dist/cjs/customDetectBot.cjs +48 -9
- package/dist/cjs/detectorLoader.cjs +18 -2
- 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 +49 -10
- package/dist/customDetectBot.js.map +1 -1
- package/dist/detectorLoader.d.ts +30 -3
- package/dist/detectorLoader.d.ts.map +1 -1
- package/dist/detectorLoader.js +18 -2
- package/dist/detectorLoader.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/customDetectBotSimd.test.js +10 -1
- package/dist/tests/customDetectBotSimd.test.js.map +1 -1
- 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 +9 -10
- package/src/customDetectBot.ts +154 -59
- package/src/detectorLoader.ts +65 -3
- package/src/detectorPrefetch.ts +115 -0
- package/src/index.ts +1 -0
- package/src/tests/customDetectBotSimd.test.ts +11 -1
- package/src/tests/detectorPrefetch.test.ts +114 -0
- package/tsconfig.cjs.json +0 -3
- package/tsconfig.json +0 -3
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"detectorLoader.js","sourceRoot":"","sources":["../src/detectorLoader.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"detectorLoader.js","sourceRoot":"","sources":["../src/detectorLoader.ts"],"names":[],"mappings":"AAkEA,MAAM,CAAC,MAAM,wBAAwB,GAAG,KAAK,EAC5C,MAAc,EACU,EAAE;IAC1B,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,CAAC,MAAM,CAAC,EAAE,EAAE,IAAI,EAAE,iBAAiB,EAAE,CAAC,CAAC;IAC7D,MAAM,GAAG,GAAG,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;IACtC,IAAI,CAAC;QACJ,MAAM,GAAG,GAAG,CAAC,MAAM,MAAM,CAAoB,GAAG,CAAC,CAEhD,CAAC;QACF,OAAO,GAAG,CAAC,OAAO,CAAC;IACpB,CAAC;YAAS,CAAC;QACV,GAAG,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC;IAC1B,CAAC;AACF,CAAC,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { getProcaptchaRandomActiveProvider } from "@prosopo/procaptcha-common";
|
|
2
|
+
import type { AssignDetectorBundleResponse, EnvironmentTypes, RandomProvider } from "@prosopo/types";
|
|
3
|
+
type IpModeParam = Parameters<typeof getProcaptchaRandomActiveProvider>[1];
|
|
4
|
+
export interface PrefetchedDetector {
|
|
5
|
+
provider: RandomProvider;
|
|
6
|
+
assigned: AssignDetectorBundleResponse;
|
|
7
|
+
}
|
|
8
|
+
export declare const prefetchDetector: (environment: EnvironmentTypes, ipMode: IpModeParam, siteKey: string) => void;
|
|
9
|
+
export declare const takePrefetchedDetector: (environment: EnvironmentTypes, ipMode: IpModeParam, siteKey: string) => Promise<PrefetchedDetector> | undefined;
|
|
10
|
+
export declare const clearPrefetchedDetectors: () => void;
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=detectorPrefetch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detectorPrefetch.d.ts","sourceRoot":"","sources":["../src/detectorPrefetch.ts"],"names":[],"mappings":"AAoCA,OAAO,EAAE,iCAAiC,EAAE,MAAM,4BAA4B,CAAC;AAC/E,OAAO,KAAK,EACX,4BAA4B,EAC5B,gBAAgB,EAChB,cAAc,EACd,MAAM,gBAAgB,CAAC;AAMxB,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,iCAAiC,CAAC,CAAC,CAAC,CAAC,CAAC;AAE3E,MAAM,WAAW,kBAAkB;IAClC,QAAQ,EAAE,cAAc,CAAC;IACzB,QAAQ,EAAE,4BAA4B,CAAC;CACvC;AAmBD,eAAO,MAAM,gBAAgB,gBACf,gBAAgB,UACrB,WAAW,WACV,MAAM,KACb,IAmBF,CAAC;AAMF,eAAO,MAAM,sBAAsB,gBACrB,gBAAgB,UACrB,WAAW,WACV,MAAM,KACb,OAAO,CAAC,kBAAkB,CAAC,GAAG,SAKhC,CAAC;AAGF,eAAO,MAAM,wBAAwB,QAAO,IAE3C,CAAC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { getProcaptchaRandomActiveProvider } from "@prosopo/procaptcha-common";
|
|
2
|
+
import { ProviderApi } from "@prosopo/api";
|
|
3
|
+
//#region src/detectorPrefetch.ts
|
|
4
|
+
/**
|
|
5
|
+
* Detector bundle prefetch.
|
|
6
|
+
*
|
|
7
|
+
* Since the detector moved into the provider-served pool, the frictionless flow
|
|
8
|
+
* cannot start until `/detector/assign` has returned. That request is issued by
|
|
9
|
+
* `customDetectBot`, which only runs once React has mounted the widget — and
|
|
10
|
+
* that mount sits behind the bundle's dynamic-import chain. Measured on a
|
|
11
|
+
* staging demo page the assign request did not leave the browser until 1513 ms,
|
|
12
|
+
* of which ~700 ms was purely waiting for chunks to arrive in sequence.
|
|
13
|
+
*
|
|
14
|
+
* Nothing in the request depends on React, i18n or the widget config: it needs
|
|
15
|
+
* the site key (a DOM attribute, readable immediately), the environment (a
|
|
16
|
+
* build-time constant) and the IP-mode flags (DOM attributes). So the entry
|
|
17
|
+
* point kicks it off as soon as it has read those, and `customDetectBot` picks
|
|
18
|
+
* up the in-flight promise instead of starting its own.
|
|
19
|
+
*
|
|
20
|
+
* The cache is deliberately single-use. A provider pin is only valid for the
|
|
21
|
+
* attempt it was made for — on a retry the previous pronode is the one that
|
|
22
|
+
* just failed — so a consumed entry is dropped and the retry re-resolves.
|
|
23
|
+
*/
|
|
24
|
+
var inFlight = /* @__PURE__ */ new Map();
|
|
25
|
+
var keyOf = (environment, ipMode, siteKey) => `${environment}|${ipMode ?? "auto"}|${siteKey}`;
|
|
26
|
+
/**
|
|
27
|
+
* Start resolving a provider and assigning a detector bundle. Safe to call more
|
|
28
|
+
* than once for the same key — subsequent calls join the in-flight request.
|
|
29
|
+
*
|
|
30
|
+
* Never rejects to the caller: a failed prefetch is indistinguishable from
|
|
31
|
+
* never having prefetched, and `customDetectBot` already handles assign failure
|
|
32
|
+
* by falling back to PoW. Returning a rejected promise here would surface as an
|
|
33
|
+
* unhandled rejection in the host page.
|
|
34
|
+
*/
|
|
35
|
+
var prefetchDetector = (environment, ipMode, siteKey) => {
|
|
36
|
+
const key = keyOf(environment, ipMode, siteKey);
|
|
37
|
+
if (inFlight.has(key)) return;
|
|
38
|
+
const promise = (async () => {
|
|
39
|
+
const provider = await getProcaptchaRandomActiveProvider(environment, ipMode);
|
|
40
|
+
return {
|
|
41
|
+
provider,
|
|
42
|
+
assigned: await new ProviderApi(provider.provider.url, siteKey).assignDetectorBundle(siteKey)
|
|
43
|
+
};
|
|
44
|
+
})();
|
|
45
|
+
promise.catch(() => void 0);
|
|
46
|
+
inFlight.set(key, promise);
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Claim a prefetched assignment, if one was started for this exact key. The
|
|
50
|
+
* entry is removed, so a retry does not reuse a pin that may have just failed.
|
|
51
|
+
*/
|
|
52
|
+
var takePrefetchedDetector = (environment, ipMode, siteKey) => {
|
|
53
|
+
const key = keyOf(environment, ipMode, siteKey);
|
|
54
|
+
const promise = inFlight.get(key);
|
|
55
|
+
if (promise) inFlight.delete(key);
|
|
56
|
+
return promise;
|
|
57
|
+
};
|
|
58
|
+
/** Test seam — drops any in-flight prefetches. */
|
|
59
|
+
var clearPrefetchedDetectors = () => {
|
|
60
|
+
inFlight.clear();
|
|
61
|
+
};
|
|
62
|
+
//#endregion
|
|
63
|
+
export { clearPrefetchedDetectors, prefetchDetector, takePrefetchedDetector };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detectorPrefetch.js","sourceRoot":"","sources":["../src/detectorPrefetch.ts"],"names":[],"mappings":"AAmCA,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,iCAAiC,EAAE,MAAM,4BAA4B,CAAC;AAkB/E,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAuC,CAAC;AAEhE,MAAM,KAAK,GAAG,CACb,WAA6B,EAC7B,MAAmB,EACnB,OAAe,EACN,EAAE,CAAC,GAAG,WAAW,IAAI,MAAM,IAAI,MAAM,IAAI,OAAO,EAAE,CAAC;AAW7D,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAC/B,WAA6B,EAC7B,MAAmB,EACnB,OAAe,EACR,EAAE;IACT,MAAM,GAAG,GAAG,KAAK,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;IAChD,IAAI,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,OAAO;IAE9B,MAAM,OAAO,GAAG,CAAC,KAAK,IAAiC,EAAE;QACxD,MAAM,QAAQ,GAAG,MAAM,iCAAiC,CACvD,WAAW,EACX,MAAM,CACN,CAAC;QACF,MAAM,WAAW,GAAG,IAAI,WAAW,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QACpE,MAAM,QAAQ,GAAG,MAAM,WAAW,CAAC,oBAAoB,CAAC,OAAO,CAAC,CAAC;QACjE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC;IAC/B,CAAC,CAAC,EAAE,CAAC;IAKL,OAAO,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IAC/B,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;AAC5B,CAAC,CAAC;AAMF,MAAM,CAAC,MAAM,sBAAsB,GAAG,CACrC,WAA6B,EAC7B,MAAmB,EACnB,OAAe,EAC2B,EAAE;IAC5C,MAAM,GAAG,GAAG,KAAK,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;IAChD,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAClC,IAAI,OAAO;QAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAClC,OAAO,OAAO,CAAC;AAChB,CAAC,CAAC;AAGF,MAAM,CAAC,MAAM,wBAAwB,GAAG,GAAS,EAAE;IAClD,QAAQ,CAAC,KAAK,EAAE,CAAC;AAClB,CAAC,CAAC"}
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAaA,cAAc,6BAA6B,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAaA,cAAc,6BAA6B,CAAC;AAC5C,cAAc,uBAAuB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import "./_virtual/_rolldown/runtime.js";
|
|
2
|
+
import { clearPrefetchedDetectors, prefetchDetector, takePrefetchedDetector } from "./detectorPrefetch.js";
|
|
2
3
|
import { ProcaptchaFrictionless } from "./ProcaptchaFrictionless.js";
|
|
3
|
-
export { ProcaptchaFrictionless };
|
|
4
|
+
export { ProcaptchaFrictionless, clearPrefetchedDetectors, prefetchDetector, takePrefetchedDetector };
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAaA,cAAc,6BAA6B,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAaA,cAAc,6BAA6B,CAAC;AAC5C,cAAc,uBAAuB,CAAC"}
|
|
@@ -2,12 +2,14 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
2
2
|
const mocks = vi.hoisted(() => ({
|
|
3
3
|
getFrictionlessCaptcha: vi.fn(),
|
|
4
4
|
getProcaptchaRandomActiveProvider: vi.fn(),
|
|
5
|
+
assignDetectorBundle: vi.fn(),
|
|
5
6
|
detect: vi.fn(),
|
|
6
7
|
}));
|
|
7
8
|
vi.mock("@prosopo/api", () => ({
|
|
8
9
|
ProviderApi: vi.fn(function () {
|
|
9
10
|
return {
|
|
10
11
|
getFrictionlessCaptcha: mocks.getFrictionlessCaptcha,
|
|
12
|
+
assignDetectorBundle: mocks.assignDetectorBundle,
|
|
11
13
|
};
|
|
12
14
|
}),
|
|
13
15
|
}));
|
|
@@ -25,7 +27,7 @@ vi.mock("@prosopo/procaptcha-common", () => ({
|
|
|
25
27
|
pickIpMode: vi.fn(() => undefined),
|
|
26
28
|
}));
|
|
27
29
|
vi.mock("../detectorLoader.js", () => ({
|
|
28
|
-
|
|
30
|
+
DetectorLoaderFromScript: vi.fn(async () => mocks.detect),
|
|
29
31
|
}));
|
|
30
32
|
import customDetectBot from "../customDetectBot.js";
|
|
31
33
|
const baseConfig = {
|
|
@@ -51,11 +53,18 @@ const captchaResponse = {
|
|
|
51
53
|
beforeEach(() => {
|
|
52
54
|
mocks.getFrictionlessCaptcha.mockReset();
|
|
53
55
|
mocks.getProcaptchaRandomActiveProvider.mockReset();
|
|
56
|
+
mocks.assignDetectorBundle.mockReset();
|
|
54
57
|
mocks.detect.mockReset();
|
|
55
58
|
mocks.getProcaptchaRandomActiveProvider.mockResolvedValue({
|
|
56
59
|
providerAccount: "dns-routed",
|
|
57
60
|
provider: { url: "https://provider.test" },
|
|
58
61
|
});
|
|
62
|
+
mocks.assignDetectorBundle.mockResolvedValue({
|
|
63
|
+
useProviderBundle: true,
|
|
64
|
+
detectorSessionId: "det-1",
|
|
65
|
+
detectorScript: "SELF_CONTAINED_ESM",
|
|
66
|
+
status: "ok",
|
|
67
|
+
});
|
|
59
68
|
mocks.getFrictionlessCaptcha.mockResolvedValue(captchaResponse);
|
|
60
69
|
});
|
|
61
70
|
describe("customDetectBot SIMD deferral", () => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"customDetectBotSimd.test.js","sourceRoot":"","sources":["../../src/tests/customDetectBotSimd.test.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAI9D,MAAM,KAAK,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IAC/B,sBAAsB,EAAE,EAAE,CAAC,EAAE,EAAE;IAC/B,iCAAiC,EAAE,EAAE,CAAC,EAAE,EAAE;IAC1C,MAAM,EAAE,EAAE,CAAC,EAAE,EAAE;CACf,CAAC,CAAC,CAAC;AAEJ,EAAE,CAAC,IAAI,CAAC,cAAc,EAAE,GAAG,EAAE,CAAC,CAAC;IAI9B,WAAW,EAAE,EAAE,CAAC,EAAE,CAAC;QAClB,OAAO;YACN,sBAAsB,EAAE,KAAK,CAAC,sBAAsB;
|
|
1
|
+
{"version":3,"file":"customDetectBotSimd.test.js","sourceRoot":"","sources":["../../src/tests/customDetectBotSimd.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.
|
|
3
|
+
"version": "2.13.1",
|
|
4
4
|
"author": "PROSOPO LIMITED <info@prosopo.io>",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -30,21 +30,20 @@
|
|
|
30
30
|
},
|
|
31
31
|
"browserslist": ["> 0.5%, last 2 versions, not dead"],
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@prosopo/api": "
|
|
33
|
+
"@prosopo/api": "4.0.0",
|
|
34
34
|
"@prosopo/common": "3.1.48",
|
|
35
|
-
"@prosopo/detector": "3.5.14",
|
|
36
35
|
"@prosopo/locale": "3.2.8",
|
|
37
|
-
"@prosopo/procaptcha-common": "2.11.
|
|
38
|
-
"@prosopo/procaptcha-pow": "2.10.
|
|
39
|
-
"@prosopo/procaptcha-puzzle": "2.10.
|
|
40
|
-
"@prosopo/procaptcha-react": "2.9.
|
|
41
|
-
"@prosopo/types": "
|
|
42
|
-
"@prosopo/widget-skeleton": "2.8.
|
|
36
|
+
"@prosopo/procaptcha-common": "2.11.20",
|
|
37
|
+
"@prosopo/procaptcha-pow": "2.10.27",
|
|
38
|
+
"@prosopo/procaptcha-puzzle": "2.10.43",
|
|
39
|
+
"@prosopo/procaptcha-react": "2.9.100",
|
|
40
|
+
"@prosopo/types": "5.0.0",
|
|
41
|
+
"@prosopo/widget-skeleton": "2.8.5",
|
|
43
42
|
"dotenv": "16.4.5",
|
|
44
43
|
"react": "18.3.1"
|
|
45
44
|
},
|
|
46
45
|
"devDependencies": {
|
|
47
|
-
"@prosopo/config": "3.3.
|
|
46
|
+
"@prosopo/config": "3.3.6",
|
|
48
47
|
"@types/node": "22.10.2",
|
|
49
48
|
"@vitest/coverage-v8": "4.1.10",
|
|
50
49
|
"concurrently": "9.0.1",
|
package/src/customDetectBot.ts
CHANGED
|
@@ -25,7 +25,20 @@ import type {
|
|
|
25
25
|
ProviderSelectRetryContext,
|
|
26
26
|
} from "@prosopo/types";
|
|
27
27
|
import type { BotDetectionFunctionResult } from "@prosopo/types";
|
|
28
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
DetectorLoaderFromScript,
|
|
30
|
+
type DetectorType,
|
|
31
|
+
} from "./detectorLoader.js";
|
|
32
|
+
import {
|
|
33
|
+
type PrefetchedDetector,
|
|
34
|
+
takePrefetchedDetector,
|
|
35
|
+
} from "./detectorPrefetch.js";
|
|
36
|
+
|
|
37
|
+
// Upper bound on the detector-bundle assignment + load probe. The detector
|
|
38
|
+
// lives ONLY in the provider-served pool bundles, so if the provider is
|
|
39
|
+
// slow/unreachable we abandon the probe and signal `detectorUnavailable`, and
|
|
40
|
+
// the provider serves a PoW challenge instead of running detection.
|
|
41
|
+
const ASSIGN_TIMEOUT_MS = 2000;
|
|
29
42
|
|
|
30
43
|
// The page(s) the widget is rendered on, reduced to origin + path.
|
|
31
44
|
// Deliberately built from `origin` + `pathname` (never `href`) so the query
|
|
@@ -129,80 +142,160 @@ const customDetectBot: BotDetectionFunction = async (
|
|
|
129
142
|
restartFn: () => void,
|
|
130
143
|
retryContext?: ProviderSelectRetryContext,
|
|
131
144
|
): Promise<BotDetectionFunctionResult> => {
|
|
132
|
-
// Kick off the provider-pin resolution (which hits `/healthz` on the
|
|
133
|
-
// load-balancer DNS endpoint) in parallel with the module loaders and
|
|
134
|
-
// the whole `detect()` flow. `getProcaptchaRandomActiveProvider` is
|
|
135
|
-
// memoised via `pinPromiseCache` keyed on `(env, ipMode)`, so the
|
|
136
|
-
// in-flight promise is reused when catcher's internal provider selector
|
|
137
|
-
// and the awaited `getProcaptchaRandomActiveProvider(...)` below reach
|
|
138
|
-
// for it — no duplicate healthz request. Before this ordering change
|
|
139
|
-
// the healthz round-trip was fully serial with `detect()`, adding
|
|
140
|
-
// ~100–200ms to the frictionless critical path on cold DNS/TLS.
|
|
141
|
-
//
|
|
142
|
-
// We warm both cache keys because the catcher-internal selector call
|
|
143
|
-
// (line ~145 below) passes no ipMode, while the awaited call further
|
|
144
|
-
// down passes `pickIpMode(config)`. Same key when the dapp opts into
|
|
145
|
-
// neither ipv4 nor ipv6 (dual stack — the common case); different
|
|
146
|
-
// keys when the dapp is pinned to a single stack. Fire-and-forget on
|
|
147
|
-
// both — the real synchronisation happens at the awaits below.
|
|
148
|
-
const ipMode = pickIpMode(config);
|
|
149
|
-
void getProcaptchaRandomActiveProvider(config.defaultEnvironment).catch(
|
|
150
|
-
() => undefined,
|
|
151
|
-
);
|
|
152
|
-
if (ipMode !== undefined) {
|
|
153
|
-
void getProcaptchaRandomActiveProvider(
|
|
154
|
-
config.defaultEnvironment,
|
|
155
|
-
ipMode,
|
|
156
|
-
retryContext,
|
|
157
|
-
).catch(() => undefined);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
const [ExtClass, detect] = await Promise.all([
|
|
161
|
-
ExtensionLoader(config.web2),
|
|
162
|
-
DetectorLoader(),
|
|
163
|
-
]);
|
|
164
|
-
const ext = new ExtClass();
|
|
165
|
-
|
|
166
|
-
// The detector bundle still expects the legacy 5-arg signature
|
|
167
|
-
// `(env, randomProviderSelectorFn, container, restart, accountGenerator)`.
|
|
168
|
-
// Until the bundle is rebuilt without the provider selector, hand it a
|
|
169
|
-
// noop selector that resolves to the static DNS endpoint — the detector
|
|
170
|
-
// no longer uses the returned RandomProvider for routing.
|
|
171
|
-
const detectionResult = await detect(
|
|
172
|
-
config.defaultEnvironment,
|
|
173
|
-
async () => getProcaptchaRandomActiveProvider(config.defaultEnvironment),
|
|
174
|
-
container,
|
|
175
|
-
restartFn,
|
|
176
|
-
() => ext.getAccount(config),
|
|
177
|
-
);
|
|
178
|
-
|
|
179
|
-
const userAccount = detectionResult.userAccount;
|
|
180
|
-
|
|
181
145
|
if (!config.account.address) {
|
|
182
146
|
throw new ProsopoEnvError("GENERAL.SITE_KEY_MISSING");
|
|
183
147
|
}
|
|
184
148
|
|
|
149
|
+
const ipMode = pickIpMode(config);
|
|
150
|
+
|
|
151
|
+
// Start the extension/account module load now rather than after the bundle
|
|
152
|
+
// assignment. It depends on nothing above, so it overlaps the provider-pin
|
|
153
|
+
// `/healthz` round-trip and the assign request for free; it is awaited below
|
|
154
|
+
// at the point `ExtClass` is first needed.
|
|
155
|
+
//
|
|
156
|
+
// This is what survives of main's parallel-pin optimisation. That change
|
|
157
|
+
// warmed BOTH `pinPromiseCache` keys — with and without ipMode — because the
|
|
158
|
+
// catcher-internal provider selector called with no ipMode while the awaited
|
|
159
|
+
// call passed one. The 3-arg detector has no provider selector at all, so
|
|
160
|
+
// only the `(env, ipMode)` key is ever used now and warming the bare-env key
|
|
161
|
+
// would just issue a healthz request nothing consumes. Nor can the pin be
|
|
162
|
+
// moved off the critical path the way it was before: the detector now comes
|
|
163
|
+
// FROM the provider, so resolving the provider is a hard prerequisite of
|
|
164
|
+
// having anything to run, not something that can proceed alongside it.
|
|
165
|
+
const extClassPromise = ExtensionLoader(config.web2);
|
|
166
|
+
|
|
185
167
|
// On the first attempt this resolves the static DNS endpoint for this env —
|
|
186
168
|
// the DNS layer load-balances across the pronode fleet. On a retry
|
|
187
169
|
// (`retryContext.attempt > 1`) the previously used pronode errored, so we
|
|
188
170
|
// pick a random provider straight from the list instead of re-pinning the
|
|
189
171
|
// same one. `pickIpMode(config)` honours the dapp's data-ipv4 / data-ipv6
|
|
190
172
|
// preference so frictionless and the subsequent captcha hops stay on the
|
|
191
|
-
// same stack.
|
|
192
|
-
//
|
|
193
|
-
//
|
|
194
|
-
//
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
173
|
+
// same stack. Resolved up front — before detection rather than alongside it —
|
|
174
|
+
// because the detector bundle is served BY this provider.
|
|
175
|
+
// The bundle entry starts provider resolution + assign as soon as it has read
|
|
176
|
+
// the site key off the DOM, which is well before React has mounted this
|
|
177
|
+
// widget. Claim that in-flight work if it exists rather than repeating it.
|
|
178
|
+
// Only valid on a first attempt: a retry is retrying *because* the pinned
|
|
179
|
+
// pronode failed, so it must re-resolve.
|
|
180
|
+
const isFirstAttempt = !retryContext || retryContext.attempt <= 1;
|
|
181
|
+
const prefetched = isFirstAttempt
|
|
182
|
+
? takePrefetchedDetector(
|
|
183
|
+
config.defaultEnvironment,
|
|
184
|
+
ipMode,
|
|
185
|
+
config.account.address,
|
|
186
|
+
)
|
|
187
|
+
: undefined;
|
|
188
|
+
|
|
189
|
+
// A prefetch that failed must not fail the flow — it is an optimisation, and
|
|
190
|
+
// the normal path below handles provider selection and assign failure
|
|
191
|
+
// already. So swallow it and re-resolve.
|
|
192
|
+
let prefetchedResult: PrefetchedDetector | undefined;
|
|
193
|
+
if (prefetched) {
|
|
194
|
+
try {
|
|
195
|
+
prefetchedResult = await prefetched;
|
|
196
|
+
} catch {
|
|
197
|
+
prefetchedResult = undefined;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const provider =
|
|
202
|
+
prefetchedResult?.provider ??
|
|
203
|
+
(await getProcaptchaRandomActiveProvider(
|
|
204
|
+
config.defaultEnvironment,
|
|
205
|
+
ipMode,
|
|
206
|
+
retryContext,
|
|
207
|
+
));
|
|
200
208
|
|
|
201
209
|
const providerApi = new ProviderApi(
|
|
202
210
|
provider.provider.url,
|
|
203
211
|
config.account.address,
|
|
204
212
|
);
|
|
205
213
|
|
|
214
|
+
// Ask the provider for a per-session detector bundle. The detector lives ONLY
|
|
215
|
+
// in the provider-served pool: when the provider has a populated pool it
|
|
216
|
+
// returns the obfuscated detector (each with its own keys + inner cipher)
|
|
217
|
+
// plus a detectorSessionId. When it cannot (no pool, network, decode, or
|
|
218
|
+
// timeout) we have NO detector to run — there is no bundled fallback — so we
|
|
219
|
+
// signal `detectorUnavailable` and the provider serves a PoW challenge.
|
|
220
|
+
let detectorSessionId: string | undefined;
|
|
221
|
+
let providerDetect: DetectorType | undefined;
|
|
222
|
+
try {
|
|
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
|
+
));
|
|
231
|
+
if (assigned.useProviderBundle && assigned.detectorScript) {
|
|
232
|
+
detectorSessionId = assigned.detectorSessionId;
|
|
233
|
+
providerDetect = await withTimeout(
|
|
234
|
+
DetectorLoaderFromScript(assigned.detectorScript),
|
|
235
|
+
ASSIGN_TIMEOUT_MS,
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
} catch {
|
|
239
|
+
// No detector available — fall through to the PoW request below.
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const ExtClass = await extClassPromise;
|
|
243
|
+
const ext = new ExtClass();
|
|
244
|
+
|
|
245
|
+
// No provider detector ⇒ no detection is possible. Request a PoW challenge
|
|
246
|
+
// directly: send no token and the detectorUnavailable flag so the provider
|
|
247
|
+
// serves PoW rather than attempting to score an absent payload.
|
|
248
|
+
if (providerDetect === undefined) {
|
|
249
|
+
const userAccount = await ext.getAccount(config);
|
|
250
|
+
const { currentUrl: fallbackUrl, iframeUrl: fallbackIframeUrl } =
|
|
251
|
+
getCurrentPageUrls();
|
|
252
|
+
const powCaptcha = await withTimeout(
|
|
253
|
+
providerApi.getFrictionlessCaptcha(
|
|
254
|
+
undefined,
|
|
255
|
+
undefined,
|
|
256
|
+
config.account.address,
|
|
257
|
+
userAccount.account.address,
|
|
258
|
+
config.mode,
|
|
259
|
+
undefined,
|
|
260
|
+
undefined,
|
|
261
|
+
true,
|
|
262
|
+
fallbackUrl,
|
|
263
|
+
fallbackIframeUrl,
|
|
264
|
+
),
|
|
265
|
+
10000,
|
|
266
|
+
);
|
|
267
|
+
if (powCaptcha.dns_url) {
|
|
268
|
+
try {
|
|
269
|
+
void fetch(powCaptcha.dns_url, {
|
|
270
|
+
method: "GET",
|
|
271
|
+
mode: "no-cors",
|
|
272
|
+
credentials: "omit",
|
|
273
|
+
keepalive: true,
|
|
274
|
+
cache: "no-store",
|
|
275
|
+
}).catch(() => undefined);
|
|
276
|
+
} catch {
|
|
277
|
+
/* swallow */
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return {
|
|
281
|
+
captchaType: powCaptcha.captchaType,
|
|
282
|
+
sessionId: powCaptcha.sessionId,
|
|
283
|
+
provider: provider,
|
|
284
|
+
status: powCaptcha.status,
|
|
285
|
+
userAccount: userAccount,
|
|
286
|
+
error: powCaptcha.error,
|
|
287
|
+
hp: powCaptcha.hp,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const detect: DetectorType = providerDetect;
|
|
292
|
+
|
|
293
|
+
const detectionResult = await detect(container, restartFn, () =>
|
|
294
|
+
ext.getAccount(config),
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
const userAccount = detectionResult.userAccount;
|
|
298
|
+
|
|
206
299
|
// SIMD readings deliberately omitted from the frictionless hop. The WASM
|
|
207
300
|
// benchmark is a CPU-bound loop that contends with BotScoreWorker if it
|
|
208
301
|
// runs during detection; deferring it until after the POST is in flight
|
|
@@ -217,6 +310,8 @@ const customDetectBot: BotDetectionFunction = async (
|
|
|
217
310
|
userAccount.account.address,
|
|
218
311
|
config.mode,
|
|
219
312
|
undefined,
|
|
313
|
+
detectorSessionId,
|
|
314
|
+
undefined,
|
|
220
315
|
currentUrl,
|
|
221
316
|
iframeUrl,
|
|
222
317
|
);
|
package/src/detectorLoader.ts
CHANGED
|
@@ -12,7 +12,69 @@
|
|
|
12
12
|
// See the License for the specific language governing permissions and
|
|
13
13
|
// limitations under the License.
|
|
14
14
|
|
|
15
|
-
type
|
|
15
|
+
import type {
|
|
16
|
+
Account,
|
|
17
|
+
BehavioralData,
|
|
18
|
+
ClickEventPoint,
|
|
19
|
+
MouseMovementPoint,
|
|
20
|
+
PackedBehavioralData,
|
|
21
|
+
TouchEventPoint,
|
|
22
|
+
} from "@prosopo/types";
|
|
16
23
|
|
|
17
|
-
|
|
18
|
-
|
|
24
|
+
// The detector is NOT bundled into the widget — it lives only in the
|
|
25
|
+
// provider-served pool bundles, loaded at runtime via a blob URL below. So the
|
|
26
|
+
// signature is declared locally here (mirroring `@prosopo/detector`'s default
|
|
27
|
+
// export) from shared @prosopo/types primitives, rather than importing the
|
|
28
|
+
// detector package as a build-time type dependency.
|
|
29
|
+
export type DetectorType = (
|
|
30
|
+
container: HTMLElement | undefined,
|
|
31
|
+
restart: () => void,
|
|
32
|
+
accountGenerator: () => Promise<Account>,
|
|
33
|
+
) => Promise<{
|
|
34
|
+
token: string;
|
|
35
|
+
shadowDomCleanup: () => void;
|
|
36
|
+
encryptHeadHash: string;
|
|
37
|
+
mouseTracker?: {
|
|
38
|
+
start: () => void;
|
|
39
|
+
stop: () => void;
|
|
40
|
+
getData: () => MouseMovementPoint[];
|
|
41
|
+
clear: () => void;
|
|
42
|
+
};
|
|
43
|
+
touchTracker?: {
|
|
44
|
+
start: () => void;
|
|
45
|
+
stop: () => void;
|
|
46
|
+
getData: () => TouchEventPoint[];
|
|
47
|
+
clear: () => void;
|
|
48
|
+
};
|
|
49
|
+
clickTracker?: {
|
|
50
|
+
start: () => void;
|
|
51
|
+
stop: () => void;
|
|
52
|
+
getData: () => ClickEventPoint[];
|
|
53
|
+
clear: () => void;
|
|
54
|
+
};
|
|
55
|
+
hasTouchSupport?: string;
|
|
56
|
+
encryptBehavioralData?: (data: string) => Promise<string>;
|
|
57
|
+
packBehavioralData?: (behavioralData: BehavioralData) => PackedBehavioralData;
|
|
58
|
+
getSimdReadings?: (timeoutMs?: number) => Promise<string | undefined>;
|
|
59
|
+
userAccount: Account;
|
|
60
|
+
}>;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Loads a detector from a provider-served obfuscated ESM string (the per-session
|
|
64
|
+
* pool bundle). Evaluated via a blob URL — the served module is self-contained
|
|
65
|
+
* (no imports), so dynamic `import()` of the blob yields its default export.
|
|
66
|
+
*/
|
|
67
|
+
export const DetectorLoaderFromScript = async (
|
|
68
|
+
script: string,
|
|
69
|
+
): Promise<DetectorType> => {
|
|
70
|
+
const blob = new Blob([script], { type: "text/javascript" });
|
|
71
|
+
const url = URL.createObjectURL(blob);
|
|
72
|
+
try {
|
|
73
|
+
const mod = (await import(/* @vite-ignore */ url)) as {
|
|
74
|
+
default: DetectorType;
|
|
75
|
+
};
|
|
76
|
+
return mod.default;
|
|
77
|
+
} finally {
|
|
78
|
+
URL.revokeObjectURL(url);
|
|
79
|
+
}
|
|
80
|
+
};
|