@prosopo/procaptcha-common 2.11.6 → 2.11.8
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 +18 -18
- package/.turbo/turbo-build.log +5 -5
- package/CHANGELOG.md +27 -0
- package/dist/cjs/index.cjs +1 -0
- package/dist/cjs/providers.cjs +23 -3
- package/dist/index.js +2 -1
- package/dist/providers.d.ts +4 -3
- package/dist/providers.d.ts.map +1 -1
- package/dist/providers.js +24 -4
- package/dist/providers.js.map +1 -1
- package/dist/tests/providers.test.js +67 -4
- package/dist/tests/providers.test.js.map +1 -1
- package/package.json +4 -4
- package/src/providers.ts +60 -7
- package/src/tests/providers.test.ts +107 -3
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @prosopo/procaptcha-common@2.11.
|
|
2
|
+
> @prosopo/procaptcha-common@2.11.8 build:cjs
|
|
3
3
|
> NODE_ENV=${NODE_ENV:-development}; vite build --config vite.cjs.config.ts --mode $NODE_ENV
|
|
4
4
|
|
|
5
5
|
ViteCommonJSConfig: .
|
|
@@ -46,13 +46,13 @@ rendering chunks...
|
|
|
46
46
|
[2mdist/cjs/[22m[36mcallbacks/defaultEvents.cjs [39m[1m[2m0.31 kB[22m[1m[22m
|
|
47
47
|
[2mdist/cjs/[22m[36melements/window.cjs [39m[1m[2m0.59 kB[22m[1m[22m
|
|
48
48
|
[2mdist/cjs/[22m[36melements/form.cjs [39m[1m[2m0.72 kB[22m[1m[22m
|
|
49
|
-
[2mdist/cjs/[22m[36mproviders.cjs [39m[1m[2m0.98 kB[22m[1m[22m
|
|
50
49
|
[2mdist/cjs/[22m[36mextensionLoader.cjs [39m[1m[2m1.48 kB[22m[1m[22m
|
|
51
|
-
[2mdist/cjs/[22m[36mindex.cjs [39m[1m[2m1.
|
|
50
|
+
[2mdist/cjs/[22m[36mindex.cjs [39m[1m[2m1.55 kB[22m[1m[22m
|
|
52
51
|
[2mdist/cjs/[22m[36mreactComponents/TestModeBanner.cjs [39m[1m[2m1.61 kB[22m[1m[22m
|
|
52
|
+
[2mdist/cjs/[22m[36mproviders.cjs [39m[1m[2m1.70 kB[22m[1m[22m
|
|
53
53
|
[2mdist/cjs/[22m[36mreactComponents/Honeypot.cjs [39m[1m[2m2.19 kB[22m[1m[22m
|
|
54
54
|
[2mdist/cjs/[22m[36mreactComponents/Reload.cjs [39m[1m[2m3.19 kB[22m[1m[22m
|
|
55
55
|
[2mdist/cjs/[22m[36mstate/builder.cjs [39m[1m[2m3.26 kB[22m[1m[22m
|
|
56
56
|
[2mdist/cjs/[22m[36mcallbacks/defaultCallbacks.cjs [39m[1m[2m4.07 kB[22m[1m[22m
|
|
57
57
|
[2mdist/cjs/[22m[36mreactComponents/Checkbox.cjs [39m[1m[2m4.45 kB[22m[1m[22m
|
|
58
|
-
[32m✓ built in
|
|
58
|
+
[32m✓ built in 297ms[39m
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
|
|
2
|
-
> @prosopo/procaptcha-common@2.11.
|
|
2
|
+
> @prosopo/procaptcha-common@2.11.8 build:tsc
|
|
3
3
|
> tsc --build --verbose
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
8:56:57 AM - Projects in this build:
|
|
6
6
|
* ../../dev/config/tsconfig.json
|
|
7
7
|
* ../locale/tsconfig.json
|
|
8
8
|
* ../util/tsconfig.json
|
|
@@ -18,35 +18,35 @@
|
|
|
18
18
|
* ../account/tsconfig.json
|
|
19
19
|
* tsconfig.json
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
8:56:58 AM - Project '../../dev/config/tsconfig.json' is up to date because newest input '../../dev/config/src/webpack/webpack.config.ts' is older than output '../../dev/config/tsconfig.tsbuildinfo'
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
8:56:58 AM - Project '../locale/tsconfig.json' is up to date because newest input '../locale/src/translationKey.ts' is older than output '../locale/tsconfig.tsbuildinfo'
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
8:56:58 AM - Project '../util/tsconfig.json' is up to date because newest input '../util/src/url.ts' is older than output '../util/tsconfig.tsbuildinfo'
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
8:56:58 AM - Project '../logger/tsconfig.json' is up to date because newest input '../logger/src/index.ts' is older than output '../logger/tsconfig.tsbuildinfo'
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
8:56:58 AM - Project '../util-crypto/tsconfig.json' is up to date because newest input '../util-crypto/src/types.ts' is older than output '../util-crypto/tsconfig.tsbuildinfo'
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
8:56:58 AM - Project '../types/tsconfig.json' is up to date because newest input '../types/src/provider/api.ts' is older than output '../types/tsconfig.tsbuildinfo'
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
8:56:58 AM - Project '../common/tsconfig.json' is up to date because newest input '../common/src/index.ts' is older than output '../common/tsconfig.tsbuildinfo'
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
8:56:58 AM - Project '../load-balancer/tsconfig.json' is up to date because newest input '../load-balancer/src/index.ts' is older than output '../load-balancer/tsconfig.tsbuildinfo'
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
8:56:58 AM - Project '../widget-skeleton/tsconfig.json' is up to date because newest input '../widget-skeleton/src/webComponent/createWidget.ts' is older than output '../widget-skeleton/tsconfig.tsbuildinfo'
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
8:56:58 AM - Project '../fingerprintjs/tsconfig.json' is up to date because newest input '../fingerprintjs/src/utils/browser.ts' is older than output '../fingerprintjs/tsconfig.tsbuildinfo'
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
8:56:58 AM - Project '../fingerprint/tsconfig.json' is up to date because newest input '../fingerprint/src/index.ts' is older than output '../fingerprint/tsconfig.tsbuildinfo'
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
8:56:58 AM - Project '../keyring/tsconfig.json' is up to date because newest input '../keyring/src/keyring/keyring.ts' is older than output '../keyring/tsconfig.tsbuildinfo'
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
8:56:58 AM - Project '../account/tsconfig.json' is out of date because output file '../account/tsconfig.tsbuildinfo' does not exist
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
8:56:58 AM - Building project '/home/runner/work/captcha/captcha/packages/account/tsconfig.json'...
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
8:57:01 AM - Project 'tsconfig.json' is out of date because output file 'tsconfig.tsbuildinfo' does not exist
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
8:57:01 AM - Building project '/home/runner/work/captcha/captcha/packages/procaptcha-common/tsconfig.json'...
|
|
52
52
|
|
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
|
|
2
|
-
> @prosopo/procaptcha-common@2.11.
|
|
2
|
+
> @prosopo/procaptcha-common@2.11.8 build
|
|
3
3
|
> npm run build:cross-env -- --mode ${NODE_ENV:-development}
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
> @prosopo/procaptcha-common@2.11.
|
|
6
|
+
> @prosopo/procaptcha-common@2.11.8 build:cross-env
|
|
7
7
|
> vite build --config vite.esm.config.ts --mode production
|
|
8
8
|
|
|
9
9
|
ViteEsmConfig: .
|
|
@@ -49,12 +49,12 @@ rendering chunks...
|
|
|
49
49
|
[2mdist/[22m[36mextensionLoader.js [39m[1m[2m0.21 kB[22m[1m[22m
|
|
50
50
|
[2mdist/[22m[36melements/window.js [39m[1m[2m0.46 kB[22m[1m[22m
|
|
51
51
|
[2mdist/[22m[36melements/form.js [39m[1m[2m0.58 kB[22m[1m[22m
|
|
52
|
-
[2mdist/[22m[
|
|
53
|
-
[2mdist/[22m[36mindex.js [39m[1m[2m1.01 kB[22m[1m[22m
|
|
52
|
+
[2mdist/[22m[36mindex.js [39m[1m[2m1.04 kB[22m[1m[22m
|
|
54
53
|
[2mdist/[22m[36mreactComponents/TestModeBanner.js [39m[1m[2m1.46 kB[22m[1m[22m
|
|
54
|
+
[2mdist/[22m[36mproviders.js [39m[1m[2m1.53 kB[22m[1m[22m
|
|
55
55
|
[2mdist/[22m[36mreactComponents/Honeypot.js [39m[1m[2m2.06 kB[22m[1m[22m
|
|
56
56
|
[2mdist/[22m[36mreactComponents/Reload.js [39m[1m[2m3.01 kB[22m[1m[22m
|
|
57
57
|
[2mdist/[22m[36mstate/builder.js [39m[1m[2m3.14 kB[22m[1m[22m
|
|
58
58
|
[2mdist/[22m[36mcallbacks/defaultCallbacks.js [39m[1m[2m3.91 kB[22m[1m[22m
|
|
59
59
|
[2mdist/[22m[36mreactComponents/Checkbox.js [39m[1m[2m4.23 kB[22m[1m[22m
|
|
60
|
-
[32m✓ built in
|
|
60
|
+
[32m✓ built in 288ms[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# @prosopo/procaptcha-common
|
|
2
2
|
|
|
3
|
+
## 2.11.8
|
|
4
|
+
### Patch Changes
|
|
5
|
+
|
|
6
|
+
- Updated dependencies [8bde5df]
|
|
7
|
+
- @prosopo/types@4.9.7
|
|
8
|
+
- @prosopo/account@2.8.55
|
|
9
|
+
- @prosopo/load-balancer@2.10.8
|
|
10
|
+
|
|
11
|
+
## 2.11.7
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- b3f351b: fix(procaptcha): random provider re-selection + backoff on error fallback
|
|
15
|
+
|
|
16
|
+
When a provider errored, the widget retried the same DNS-routed endpoint immediately and in a tight loop. A fleet of widgets whose provider was unhealthy could therefore accidentally DDoS the provider fleet — retrying the same (possibly-down) endpoint as fast as the event loop allowed.
|
|
17
|
+
|
|
18
|
+
The error-fallback path now:
|
|
19
|
+
|
|
20
|
+
- **Re-selects a different provider on retry.** The first attempt still hits the DNS-routed endpoint (unchanged happy path, preserves session stickiness). On a retry the widget picks a random provider straight from the provider list (`getRandomProviderFromList`), weighted by provider capacity and excluding the URL that just failed. In development the list holds only the single local provider, so a retry simply re-targets that provider.
|
|
21
|
+
- **Backs off between retries.** `providerRetry` now waits an exponential-backoff-with-full-jitter delay (0.5s → 1s → 2s → 4s …, capped at 10s) before retrying, so a down provider is no longer hammered and a fleet of clients that all errored at once don't reconverge into a thundering herd.
|
|
22
|
+
|
|
23
|
+
Applies to the image, PoW and puzzle managers and the frictionless detection flow. New shared `ProviderSelectRetryContext` type; `BotDetectionFunction` gains an optional retry-context argument.
|
|
24
|
+
- Updated dependencies [b3f351b]
|
|
25
|
+
- Updated dependencies [17bc76e]
|
|
26
|
+
- @prosopo/load-balancer@2.10.7
|
|
27
|
+
- @prosopo/types@4.9.6
|
|
28
|
+
- @prosopo/account@2.8.54
|
|
29
|
+
|
|
3
30
|
## 2.11.6
|
|
4
31
|
### Patch Changes
|
|
5
32
|
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -11,6 +11,7 @@ const Checkbox = require("./reactComponents/Checkbox.cjs");
|
|
|
11
11
|
const Honeypot = require("./reactComponents/Honeypot.cjs");
|
|
12
12
|
const TestModeBanner = require("./reactComponents/TestModeBanner.cjs");
|
|
13
13
|
exports.getProcaptchaRandomActiveProvider = providers.getProcaptchaRandomActiveProvider;
|
|
14
|
+
exports.getRetryDelayMs = providers.getRetryDelayMs;
|
|
14
15
|
exports.pickIpMode = providers.pickIpMode;
|
|
15
16
|
exports.providerRetry = providers.providerRetry;
|
|
16
17
|
exports.buildUpdateState = builder.buildUpdateState;
|
package/dist/cjs/providers.cjs
CHANGED
|
@@ -1,15 +1,33 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
3
|
const loadBalancer = require("@prosopo/load-balancer");
|
|
4
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
4
5
|
const pickIpMode = (flags) => {
|
|
5
6
|
if (flags?.ipv4) return "ipv4";
|
|
6
7
|
if (flags?.ipv6) return "ipv6";
|
|
7
8
|
return void 0;
|
|
8
9
|
};
|
|
9
|
-
const getProcaptchaRandomActiveProvider = async (defaultEnvironment, ipMode) => {
|
|
10
|
-
|
|
10
|
+
const getProcaptchaRandomActiveProvider = async (defaultEnvironment, ipMode, retryContext) => {
|
|
11
|
+
if (!retryContext || retryContext.attempt <= 1) {
|
|
12
|
+
return loadBalancer.getRandomActiveProvider(defaultEnvironment, ipMode);
|
|
13
|
+
}
|
|
14
|
+
return loadBalancer.getRandomProviderFromList(
|
|
15
|
+
defaultEnvironment,
|
|
16
|
+
ipMode,
|
|
17
|
+
retryContext.excludeUrl
|
|
18
|
+
);
|
|
19
|
+
};
|
|
20
|
+
const RETRY_BASE_DELAY_MS = 500;
|
|
21
|
+
const RETRY_MAX_DELAY_MS = 1e4;
|
|
22
|
+
const getRetryDelayMs = (attemptCount, random = Math.random) => {
|
|
23
|
+
const safeAttempt = Math.max(0, Math.floor(attemptCount));
|
|
24
|
+
const cappedDelay = Math.min(
|
|
25
|
+
RETRY_MAX_DELAY_MS,
|
|
26
|
+
RETRY_BASE_DELAY_MS * 2 ** safeAttempt
|
|
27
|
+
);
|
|
28
|
+
return Math.round(random() * cappedDelay);
|
|
11
29
|
};
|
|
12
|
-
const providerRetry = async (currentFn, retryFn, stateReset, attemptCount, retryMax) => {
|
|
30
|
+
const providerRetry = async (currentFn, retryFn, stateReset, attemptCount, retryMax, backoffMs = getRetryDelayMs(attemptCount)) => {
|
|
13
31
|
try {
|
|
14
32
|
await currentFn();
|
|
15
33
|
} catch (err) {
|
|
@@ -22,9 +40,11 @@ const providerRetry = async (currentFn, retryFn, stateReset, attemptCount, retry
|
|
|
22
40
|
}
|
|
23
41
|
console.error(err);
|
|
24
42
|
stateReset();
|
|
43
|
+
await sleep(backoffMs);
|
|
25
44
|
await retryFn();
|
|
26
45
|
}
|
|
27
46
|
};
|
|
28
47
|
exports.getProcaptchaRandomActiveProvider = getProcaptchaRandomActiveProvider;
|
|
48
|
+
exports.getRetryDelayMs = getRetryDelayMs;
|
|
29
49
|
exports.pickIpMode = pickIpMode;
|
|
30
50
|
exports.providerRetry = providerRetry;
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getProcaptchaRandomActiveProvider, pickIpMode, providerRetry } from "./providers.js";
|
|
1
|
+
import { getProcaptchaRandomActiveProvider, getRetryDelayMs, pickIpMode, providerRetry } from "./providers.js";
|
|
2
2
|
import { buildUpdateState, useProcaptcha } from "./state/builder.js";
|
|
3
3
|
import { getDefaultCallbacks, setUserCallbacks } from "./callbacks/defaultCallbacks.js";
|
|
4
4
|
import { getDefaultEvents } from "./callbacks/defaultEvents.js";
|
|
@@ -18,6 +18,7 @@ export {
|
|
|
18
18
|
getDefaultCallbacks,
|
|
19
19
|
getDefaultEvents,
|
|
20
20
|
getProcaptchaRandomActiveProvider,
|
|
21
|
+
getRetryDelayMs,
|
|
21
22
|
getWindowCallback,
|
|
22
23
|
isSecureBrowserContext,
|
|
23
24
|
pickIpMode,
|
package/dist/providers.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { type IpMode } from "@prosopo/load-balancer";
|
|
2
|
-
import type { EnvironmentTypes, RandomProvider } from "@prosopo/types";
|
|
2
|
+
import type { EnvironmentTypes, ProviderSelectRetryContext, RandomProvider } from "@prosopo/types";
|
|
3
3
|
export declare const pickIpMode: (flags: {
|
|
4
4
|
ipv4?: boolean;
|
|
5
5
|
ipv6?: boolean;
|
|
6
6
|
} | undefined) => IpMode | undefined;
|
|
7
|
-
export declare const getProcaptchaRandomActiveProvider: (defaultEnvironment: EnvironmentTypes, ipMode?: IpMode) => Promise<RandomProvider>;
|
|
8
|
-
export declare const
|
|
7
|
+
export declare const getProcaptchaRandomActiveProvider: (defaultEnvironment: EnvironmentTypes, ipMode?: IpMode, retryContext?: ProviderSelectRetryContext) => Promise<RandomProvider>;
|
|
8
|
+
export declare const getRetryDelayMs: (attemptCount: number, random?: () => number) => number;
|
|
9
|
+
export declare const providerRetry: (currentFn: () => Promise<void>, retryFn: () => Promise<void>, stateReset: () => void, attemptCount: number, retryMax: number, backoffMs?: number) => Promise<void>;
|
|
9
10
|
//# sourceMappingURL=providers.d.ts.map
|
package/dist/providers.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"providers.d.ts","sourceRoot":"","sources":["../src/providers.ts"],"names":[],"mappings":"AAcA,OAAO,
|
|
1
|
+
{"version":3,"file":"providers.d.ts","sourceRoot":"","sources":["../src/providers.ts"],"names":[],"mappings":"AAcA,OAAO,EACN,KAAK,MAAM,EAGX,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EACX,gBAAgB,EAChB,0BAA0B,EAC1B,cAAc,EACd,MAAM,gBAAgB,CAAC;AAUxB,eAAO,MAAM,UAAU,UACf;IAAE,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,KACnD,MAAM,GAAG,SAIX,CAAC;AAYF,eAAO,MAAM,iCAAiC,uBACzB,gBAAgB,WAC3B,MAAM,iBACA,0BAA0B,KACvC,OAAO,CAAC,cAAc,CASxB,CAAC;AAWF,eAAO,MAAM,eAAe,iBACb,MAAM,WACZ,MAAM,MAAM,KAClB,MAOF,CAAC;AAEF,eAAO,MAAM,aAAa,cACd,MAAM,OAAO,CAAC,IAAI,CAAC,WACrB,MAAM,OAAO,CAAC,IAAI,CAAC,cAChB,MAAM,IAAI,gBACR,MAAM,YACV,MAAM,cACL,MAAM,kBAqBjB,CAAC"}
|
package/dist/providers.js
CHANGED
|
@@ -1,13 +1,31 @@
|
|
|
1
|
-
import { getRandomActiveProvider } from "@prosopo/load-balancer";
|
|
1
|
+
import { getRandomActiveProvider, getRandomProviderFromList } from "@prosopo/load-balancer";
|
|
2
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
2
3
|
const pickIpMode = (flags) => {
|
|
3
4
|
if (flags?.ipv4) return "ipv4";
|
|
4
5
|
if (flags?.ipv6) return "ipv6";
|
|
5
6
|
return void 0;
|
|
6
7
|
};
|
|
7
|
-
const getProcaptchaRandomActiveProvider = async (defaultEnvironment, ipMode) => {
|
|
8
|
-
|
|
8
|
+
const getProcaptchaRandomActiveProvider = async (defaultEnvironment, ipMode, retryContext) => {
|
|
9
|
+
if (!retryContext || retryContext.attempt <= 1) {
|
|
10
|
+
return getRandomActiveProvider(defaultEnvironment, ipMode);
|
|
11
|
+
}
|
|
12
|
+
return getRandomProviderFromList(
|
|
13
|
+
defaultEnvironment,
|
|
14
|
+
ipMode,
|
|
15
|
+
retryContext.excludeUrl
|
|
16
|
+
);
|
|
17
|
+
};
|
|
18
|
+
const RETRY_BASE_DELAY_MS = 500;
|
|
19
|
+
const RETRY_MAX_DELAY_MS = 1e4;
|
|
20
|
+
const getRetryDelayMs = (attemptCount, random = Math.random) => {
|
|
21
|
+
const safeAttempt = Math.max(0, Math.floor(attemptCount));
|
|
22
|
+
const cappedDelay = Math.min(
|
|
23
|
+
RETRY_MAX_DELAY_MS,
|
|
24
|
+
RETRY_BASE_DELAY_MS * 2 ** safeAttempt
|
|
25
|
+
);
|
|
26
|
+
return Math.round(random() * cappedDelay);
|
|
9
27
|
};
|
|
10
|
-
const providerRetry = async (currentFn, retryFn, stateReset, attemptCount, retryMax) => {
|
|
28
|
+
const providerRetry = async (currentFn, retryFn, stateReset, attemptCount, retryMax, backoffMs = getRetryDelayMs(attemptCount)) => {
|
|
11
29
|
try {
|
|
12
30
|
await currentFn();
|
|
13
31
|
} catch (err) {
|
|
@@ -20,11 +38,13 @@ const providerRetry = async (currentFn, retryFn, stateReset, attemptCount, retry
|
|
|
20
38
|
}
|
|
21
39
|
console.error(err);
|
|
22
40
|
stateReset();
|
|
41
|
+
await sleep(backoffMs);
|
|
23
42
|
await retryFn();
|
|
24
43
|
}
|
|
25
44
|
};
|
|
26
45
|
export {
|
|
27
46
|
getProcaptchaRandomActiveProvider,
|
|
47
|
+
getRetryDelayMs,
|
|
28
48
|
pickIpMode,
|
|
29
49
|
providerRetry
|
|
30
50
|
};
|
package/dist/providers.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"providers.js","sourceRoot":"","sources":["../src/providers.ts"],"names":[],"mappings":"AAcA,OAAO,
|
|
1
|
+
{"version":3,"file":"providers.js","sourceRoot":"","sources":["../src/providers.ts"],"names":[],"mappings":"AAcA,OAAO,EAEN,uBAAuB,EACvB,yBAAyB,GACzB,MAAM,wBAAwB,CAAC;AAShC,MAAM,KAAK,GAAG,CAAC,EAAU,EAAiB,EAAE,CAC3C,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAKnD,MAAM,CAAC,MAAM,UAAU,GAAG,CACzB,KAAqD,EAChC,EAAE;IACvB,IAAI,KAAK,EAAE,IAAI;QAAE,OAAO,MAAM,CAAC;IAC/B,IAAI,KAAK,EAAE,IAAI;QAAE,OAAO,MAAM,CAAC;IAC/B,OAAO,SAAS,CAAC;AAClB,CAAC,CAAC;AAYF,MAAM,CAAC,MAAM,iCAAiC,GAAG,KAAK,EACrD,kBAAoC,EACpC,MAAe,EACf,YAAyC,EACf,EAAE;IAC5B,IAAI,CAAC,YAAY,IAAI,YAAY,CAAC,OAAO,IAAI,CAAC,EAAE,CAAC;QAChD,OAAO,uBAAuB,CAAC,kBAAkB,EAAE,MAAM,CAAC,CAAC;IAC5D,CAAC;IACD,OAAO,yBAAyB,CAC/B,kBAAkB,EAClB,MAAM,EACN,YAAY,CAAC,UAAU,CACvB,CAAC;AACH,CAAC,CAAC;AAQF,MAAM,mBAAmB,GAAG,GAAG,CAAC;AAChC,MAAM,kBAAkB,GAAG,MAAM,CAAC;AAElC,MAAM,CAAC,MAAM,eAAe,GAAG,CAC9B,YAAoB,EACpB,SAAuB,IAAI,CAAC,MAAM,EACzB,EAAE;IACX,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC;IAC1D,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAC3B,kBAAkB,EAClB,mBAAmB,GAAG,CAAC,IAAI,WAAW,CACtC,CAAC;IACF,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,GAAG,WAAW,CAAC,CAAC;AAC3C,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,aAAa,GAAG,KAAK,EACjC,SAA8B,EAC9B,OAA4B,EAC5B,UAAsB,EACtB,YAAoB,EACpB,QAAgB,EAChB,YAAoB,eAAe,CAAC,YAAY,CAAC,EAChD,EAAE;IACH,IAAI,CAAC;QACJ,MAAM,SAAS,EAAE,CAAC;IACnB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,IAAI,YAAY,IAAI,QAAQ,EAAE,CAAC;YAC9B,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YACnB,OAAO,CAAC,KAAK,CACZ,gBAAgB,YAAY,OAAO,QAAQ,qBAAqB,CAChE,CAAC;YACF,OAAO,UAAU,EAAE,CAAC;QACrB,CAAC;QACD,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAEnB,UAAU,EAAE,CAAC;QAGb,MAAM,KAAK,CAAC,SAAS,CAAC,CAAC;QAEvB,MAAM,OAAO,EAAE,CAAC;IACjB,CAAC;AACF,CAAC,CAAC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from "vitest";
|
|
2
|
-
import { getProcaptchaRandomActiveProvider, pickIpMode, providerRetry, } from "../providers.js";
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { getProcaptchaRandomActiveProvider, getRetryDelayMs, pickIpMode, providerRetry, } from "../providers.js";
|
|
3
3
|
vi.mock("@prosopo/load-balancer", () => ({
|
|
4
4
|
getRandomActiveProvider: vi.fn(async (env) => ({
|
|
5
5
|
providerAccount: "dns-routed",
|
|
@@ -9,8 +9,19 @@ vi.mock("@prosopo/load-balancer", () => ({
|
|
|
9
9
|
: "http://localhost:9229",
|
|
10
10
|
},
|
|
11
11
|
})),
|
|
12
|
+
getRandomProviderFromList: vi.fn(async (_env, _ipMode, excludeUrl) => ({
|
|
13
|
+
providerAccount: "5FromList",
|
|
14
|
+
provider: {
|
|
15
|
+
url: excludeUrl
|
|
16
|
+
? "https://provider-from-list-b.io"
|
|
17
|
+
: "https://provider-from-list-a.io",
|
|
18
|
+
},
|
|
19
|
+
})),
|
|
12
20
|
}));
|
|
13
21
|
describe("providers", () => {
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
vi.clearAllMocks();
|
|
24
|
+
});
|
|
14
25
|
describe("getProcaptchaRandomActiveProvider", () => {
|
|
15
26
|
it("delegates to the load-balancer static-DNS resolver", async () => {
|
|
16
27
|
const { getRandomActiveProvider } = await import("@prosopo/load-balancer");
|
|
@@ -23,6 +34,37 @@ describe("providers", () => {
|
|
|
23
34
|
await getProcaptchaRandomActiveProvider("production", "ipv4");
|
|
24
35
|
expect(getRandomActiveProvider).toHaveBeenCalledWith("production", "ipv4");
|
|
25
36
|
});
|
|
37
|
+
it("uses the DNS-routed resolver on the first attempt", async () => {
|
|
38
|
+
const { getRandomActiveProvider, getRandomProviderFromList } = await import("@prosopo/load-balancer");
|
|
39
|
+
const result = await getProcaptchaRandomActiveProvider("production", undefined, { attempt: 1 });
|
|
40
|
+
expect(getRandomActiveProvider).toHaveBeenCalled();
|
|
41
|
+
expect(getRandomProviderFromList).not.toHaveBeenCalled();
|
|
42
|
+
expect(result.provider.url).toBe("https://pronode.prosopo.io");
|
|
43
|
+
});
|
|
44
|
+
it("picks a random provider from the list on a retry", async () => {
|
|
45
|
+
const { getRandomProviderFromList } = await import("@prosopo/load-balancer");
|
|
46
|
+
const result = await getProcaptchaRandomActiveProvider("production", "ipv4", { attempt: 2, excludeUrl: "https://pronode.prosopo.io" });
|
|
47
|
+
expect(getRandomProviderFromList).toHaveBeenCalledWith("production", "ipv4", "https://pronode.prosopo.io");
|
|
48
|
+
expect(result.provider.url).toBe("https://provider-from-list-b.io");
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
describe("getRetryDelayMs", () => {
|
|
52
|
+
it("grows exponentially from the base delay, capped at the max", () => {
|
|
53
|
+
expect(getRetryDelayMs(0, () => 1)).toBe(500);
|
|
54
|
+
expect(getRetryDelayMs(1, () => 1)).toBe(1000);
|
|
55
|
+
expect(getRetryDelayMs(2, () => 1)).toBe(2000);
|
|
56
|
+
expect(getRetryDelayMs(3, () => 1)).toBe(4000);
|
|
57
|
+
expect(getRetryDelayMs(6, () => 1)).toBe(10000);
|
|
58
|
+
expect(getRetryDelayMs(20, () => 1)).toBe(10000);
|
|
59
|
+
});
|
|
60
|
+
it("applies full jitter — a fraction of the ceiling", () => {
|
|
61
|
+
expect(getRetryDelayMs(2, () => 0)).toBe(0);
|
|
62
|
+
expect(getRetryDelayMs(2, () => 0.5)).toBe(1000);
|
|
63
|
+
});
|
|
64
|
+
it("clamps negative or fractional attempt counts", () => {
|
|
65
|
+
expect(getRetryDelayMs(-5, () => 1)).toBe(500);
|
|
66
|
+
expect(getRetryDelayMs(1.9, () => 1)).toBe(1000);
|
|
67
|
+
});
|
|
26
68
|
});
|
|
27
69
|
describe("pickIpMode", () => {
|
|
28
70
|
it("picks ipv4 when both flags are set", () => {
|
|
@@ -51,7 +93,7 @@ describe("providers", () => {
|
|
|
51
93
|
const currentFn = vi.fn().mockRejectedValue(error);
|
|
52
94
|
const retryFn = vi.fn().mockResolvedValue(undefined);
|
|
53
95
|
const stateReset = vi.fn();
|
|
54
|
-
await providerRetry(currentFn, retryFn, stateReset, 1, 3);
|
|
96
|
+
await providerRetry(currentFn, retryFn, stateReset, 1, 3, 0);
|
|
55
97
|
expect(currentFn).toHaveBeenCalledTimes(1);
|
|
56
98
|
expect(stateReset).toHaveBeenCalledTimes(1);
|
|
57
99
|
expect(retryFn).toHaveBeenCalledTimes(1);
|
|
@@ -94,12 +136,33 @@ describe("providers", () => {
|
|
|
94
136
|
const consoleErrorSpy = vi
|
|
95
137
|
.spyOn(console, "error")
|
|
96
138
|
.mockImplementation(() => { });
|
|
97
|
-
await expect(providerRetry(currentFn, retryFn, stateReset, 1, 3)).rejects.toThrow("Retry failed");
|
|
139
|
+
await expect(providerRetry(currentFn, retryFn, stateReset, 1, 3, 0)).rejects.toThrow("Retry failed");
|
|
98
140
|
expect(currentFn).toHaveBeenCalledTimes(1);
|
|
99
141
|
expect(stateReset).toHaveBeenCalledTimes(1);
|
|
100
142
|
expect(retryFn).toHaveBeenCalledTimes(1);
|
|
101
143
|
consoleErrorSpy.mockRestore();
|
|
102
144
|
});
|
|
145
|
+
it("waits for the backoff delay before retrying", async () => {
|
|
146
|
+
const error = new Error("Provider failed");
|
|
147
|
+
const currentFn = vi.fn().mockRejectedValue(error);
|
|
148
|
+
const order = [];
|
|
149
|
+
const retryFn = vi.fn(async () => {
|
|
150
|
+
order.push("retry");
|
|
151
|
+
});
|
|
152
|
+
const stateReset = vi.fn(() => {
|
|
153
|
+
order.push("reset");
|
|
154
|
+
});
|
|
155
|
+
const consoleErrorSpy = vi
|
|
156
|
+
.spyOn(console, "error")
|
|
157
|
+
.mockImplementation(() => { });
|
|
158
|
+
const start = Date.now();
|
|
159
|
+
await providerRetry(currentFn, retryFn, stateReset, 1, 3, 40);
|
|
160
|
+
const elapsed = Date.now() - start;
|
|
161
|
+
expect(order).toEqual(["reset", "retry"]);
|
|
162
|
+
expect(elapsed).toBeGreaterThanOrEqual(30);
|
|
163
|
+
expect(retryFn).toHaveBeenCalledTimes(1);
|
|
164
|
+
consoleErrorSpy.mockRestore();
|
|
165
|
+
});
|
|
103
166
|
});
|
|
104
167
|
});
|
|
105
168
|
//# sourceMappingURL=providers.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"providers.test.js","sourceRoot":"","sources":["../../src/tests/providers.test.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"providers.test.js","sourceRoot":"","sources":["../../src/tests/providers.test.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prosopo/procaptcha-common",
|
|
3
|
-
"version": "2.11.
|
|
3
|
+
"version": "2.11.8",
|
|
4
4
|
"author": "PROSOPO LIMITED <info@prosopo.io>",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -34,9 +34,9 @@
|
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@emotion/react": "11.11.1",
|
|
36
36
|
"@emotion/styled": "11.11.0",
|
|
37
|
-
"@prosopo/account": "2.8.
|
|
38
|
-
"@prosopo/load-balancer": "2.10.
|
|
39
|
-
"@prosopo/types": "4.9.
|
|
37
|
+
"@prosopo/account": "2.8.55",
|
|
38
|
+
"@prosopo/load-balancer": "2.10.8",
|
|
39
|
+
"@prosopo/types": "4.9.7",
|
|
40
40
|
"@prosopo/widget-skeleton": "2.8.4",
|
|
41
41
|
"react": "18.3.1",
|
|
42
42
|
"react-dom": "18.3.1"
|
package/src/providers.ts
CHANGED
|
@@ -12,8 +12,21 @@
|
|
|
12
12
|
// See the License for the specific language governing permissions and
|
|
13
13
|
// limitations under the License.
|
|
14
14
|
|
|
15
|
-
import {
|
|
16
|
-
|
|
15
|
+
import {
|
|
16
|
+
type IpMode,
|
|
17
|
+
getRandomActiveProvider,
|
|
18
|
+
getRandomProviderFromList,
|
|
19
|
+
} from "@prosopo/load-balancer";
|
|
20
|
+
import type {
|
|
21
|
+
EnvironmentTypes,
|
|
22
|
+
ProviderSelectRetryContext,
|
|
23
|
+
RandomProvider,
|
|
24
|
+
} from "@prosopo/types";
|
|
25
|
+
|
|
26
|
+
// Inlined rather than importing `sleep` from @prosopo/util so this package
|
|
27
|
+
// doesn't take on @prosopo/util as a dependency for a one-liner.
|
|
28
|
+
const sleep = (ms: number): Promise<void> =>
|
|
29
|
+
new Promise((resolve) => setTimeout(resolve, ms));
|
|
17
30
|
|
|
18
31
|
// Translate the user-facing ipv4/ipv6 booleans (set via ProcaptchaRenderOptions
|
|
19
32
|
// or data-ipv4/data-ipv6) into the load-balancer's IpMode selector. ipv4 wins
|
|
@@ -26,14 +39,50 @@ export const pickIpMode = (
|
|
|
26
39
|
return undefined;
|
|
27
40
|
};
|
|
28
41
|
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
//
|
|
42
|
+
// Resolves the provider a widget should talk to. On the first attempt this hits
|
|
43
|
+
// the load-balancer's static-DNS endpoint (the normal, healthy path — keeps
|
|
44
|
+
// session creation and submission pinned to the same backend). On a retry
|
|
45
|
+
// (`retryContext.attempt > 1`) the previous provider errored, so instead of
|
|
46
|
+
// re-hitting the same possibly-down endpoint we pick a random *different*
|
|
47
|
+
// provider straight from the provider list. In development the list holds only
|
|
48
|
+
// the single local provider, so a retry just re-targets that provider.
|
|
49
|
+
//
|
|
50
|
+
// Kept as a separate export so widget packages (procaptcha, procaptcha-pow, …)
|
|
51
|
+
// import from procaptcha-common rather than reaching into load-balancer.
|
|
32
52
|
export const getProcaptchaRandomActiveProvider = async (
|
|
33
53
|
defaultEnvironment: EnvironmentTypes,
|
|
34
54
|
ipMode?: IpMode,
|
|
55
|
+
retryContext?: ProviderSelectRetryContext,
|
|
35
56
|
): Promise<RandomProvider> => {
|
|
36
|
-
|
|
57
|
+
if (!retryContext || retryContext.attempt <= 1) {
|
|
58
|
+
return getRandomActiveProvider(defaultEnvironment, ipMode);
|
|
59
|
+
}
|
|
60
|
+
return getRandomProviderFromList(
|
|
61
|
+
defaultEnvironment,
|
|
62
|
+
ipMode,
|
|
63
|
+
retryContext.excludeUrl,
|
|
64
|
+
);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// Exponential-backoff-with-jitter bounds for provider retries. The delay grows
|
|
68
|
+
// 0.5s → 1s → 2s → 4s … capped at 10s. Without a delay a widget whose provider
|
|
69
|
+
// is down re-requests as fast as the event loop allows, and a fleet of such
|
|
70
|
+
// widgets can accidentally DDoS the provider fleet. Full jitter (a random point
|
|
71
|
+
// in [0, cap]) desynchronises clients that all errored at once so their retries
|
|
72
|
+
// don't reconverge into a thundering herd.
|
|
73
|
+
const RETRY_BASE_DELAY_MS = 500;
|
|
74
|
+
const RETRY_MAX_DELAY_MS = 10_000;
|
|
75
|
+
|
|
76
|
+
export const getRetryDelayMs = (
|
|
77
|
+
attemptCount: number,
|
|
78
|
+
random: () => number = Math.random,
|
|
79
|
+
): number => {
|
|
80
|
+
const safeAttempt = Math.max(0, Math.floor(attemptCount));
|
|
81
|
+
const cappedDelay = Math.min(
|
|
82
|
+
RETRY_MAX_DELAY_MS,
|
|
83
|
+
RETRY_BASE_DELAY_MS * 2 ** safeAttempt,
|
|
84
|
+
);
|
|
85
|
+
return Math.round(random() * cappedDelay);
|
|
37
86
|
};
|
|
38
87
|
|
|
39
88
|
export const providerRetry = async (
|
|
@@ -42,6 +91,7 @@ export const providerRetry = async (
|
|
|
42
91
|
stateReset: () => void,
|
|
43
92
|
attemptCount: number,
|
|
44
93
|
retryMax: number,
|
|
94
|
+
backoffMs: number = getRetryDelayMs(attemptCount),
|
|
45
95
|
) => {
|
|
46
96
|
try {
|
|
47
97
|
await currentFn();
|
|
@@ -56,7 +106,10 @@ export const providerRetry = async (
|
|
|
56
106
|
console.error(err);
|
|
57
107
|
// hit an error, disallow user's claim to be human
|
|
58
108
|
stateReset();
|
|
59
|
-
//
|
|
109
|
+
// Back off before retrying so a down provider isn't hammered as fast as
|
|
110
|
+
// the event loop allows (accidental self-inflicted DDoS).
|
|
111
|
+
await sleep(backoffMs);
|
|
112
|
+
// trigger a retry — the managers select a fresh random provider on retry
|
|
60
113
|
await retryFn();
|
|
61
114
|
}
|
|
62
115
|
};
|