@prosopo/procaptcha-common 2.11.5 → 2.11.7

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.
@@ -1,5 +1,5 @@
1
1
 
2
- > @prosopo/procaptcha-common@2.11.5 build:cjs
2
+ > @prosopo/procaptcha-common@2.11.7 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
  dist/cjs/callbacks/defaultEvents.cjs 0.31 kB
47
47
  dist/cjs/elements/window.cjs 0.59 kB
48
48
  dist/cjs/elements/form.cjs 0.72 kB
49
- dist/cjs/providers.cjs 0.98 kB
50
49
  dist/cjs/extensionLoader.cjs 1.48 kB
51
- dist/cjs/index.cjs 1.49 kB
50
+ dist/cjs/index.cjs 1.55 kB
52
51
  dist/cjs/reactComponents/TestModeBanner.cjs 1.61 kB
52
+ dist/cjs/providers.cjs 1.70 kB
53
53
  dist/cjs/reactComponents/Honeypot.cjs 2.19 kB
54
54
  dist/cjs/reactComponents/Reload.cjs 3.19 kB
55
55
  dist/cjs/state/builder.cjs 3.26 kB
56
56
  dist/cjs/callbacks/defaultCallbacks.cjs 4.07 kB
57
57
  dist/cjs/reactComponents/Checkbox.cjs 4.45 kB
58
- ✓ built in 258ms
58
+ ✓ built in 207ms
@@ -1,8 +1,8 @@
1
1
 
2
- > @prosopo/procaptcha-common@2.11.5 build:tsc
2
+ > @prosopo/procaptcha-common@2.11.7 build:tsc
3
3
  > tsc --build --verbose
4
4
 
5
- 9:22:16 AM - Projects in this build:
5
+ 11:42:06 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
- 9:22:16 AM - Project '../../dev/config/tsconfig.json' is up to date because newest input '../../dev/config/src/vite/vite-plugin-fix-absolute-imports.ts' is older than output '../../dev/config/tsconfig.tsbuildinfo'
21
+ 11:42:06 AM - Project '../../dev/config/tsconfig.json' is up to date because newest input '../../dev/config/src/vite/NodejsPolarsNativeFilePlugin.ts' is older than output '../../dev/config/tsconfig.tsbuildinfo'
22
22
 
23
- 9:22:16 AM - Project '../locale/tsconfig.json' is up to date because newest input '../locale/src/useTranslation.ts' is older than output '../locale/tsconfig.tsbuildinfo'
23
+ 11:42:06 AM - Project '../locale/tsconfig.json' is up to date because newest input '../locale/src/useTranslation.ts' is older than output '../locale/tsconfig.tsbuildinfo'
24
24
 
25
- 9:22:16 AM - Project '../util/tsconfig.json' is up to date because newest input '../util/src/url.ts' is older than output '../util/tsconfig.tsbuildinfo'
25
+ 11:42:06 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
- 9:22:16 AM - Project '../logger/tsconfig.json' is up to date because newest input '../logger/src/index.ts' is older than output '../logger/tsconfig.tsbuildinfo'
27
+ 11:42:06 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
- 9:22:16 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'
29
+ 11:42:06 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
- 9:22:16 AM - Project '../types/tsconfig.json' is up to date because newest input '../types/src/provider/api.ts' is older than output '../types/tsconfig.tsbuildinfo'
31
+ 11:42:06 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
- 9:22:16 AM - Project '../common/tsconfig.json' is up to date because newest input '../common/src/index.ts' is older than output '../common/tsconfig.tsbuildinfo'
33
+ 11:42:06 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
- 9:22:16 AM - Project '../load-balancer/tsconfig.json' is up to date because newest input '../load-balancer/src/balancer.ts' is older than output '../load-balancer/tsconfig.tsbuildinfo'
35
+ 11:42:06 AM - Project '../load-balancer/tsconfig.json' is up to date because newest input '../load-balancer/src/balancer.ts' is older than output '../load-balancer/tsconfig.tsbuildinfo'
36
36
 
37
- 9:22:16 AM - Project '../widget-skeleton/tsconfig.json' is up to date because newest input '../widget-skeleton/src/constants.ts' is older than output '../widget-skeleton/tsconfig.tsbuildinfo'
37
+ 11:42:06 AM - Project '../widget-skeleton/tsconfig.json' is up to date because newest input '../widget-skeleton/src/constants.ts' is older than output '../widget-skeleton/tsconfig.tsbuildinfo'
38
38
 
39
- 9:22:16 AM - Project '../fingerprintjs/tsconfig.json' is up to date because newest input '../fingerprintjs/src/utils/browser.ts' is older than output '../fingerprintjs/tsconfig.tsbuildinfo'
39
+ 11:42:06 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
- 9:22:16 AM - Project '../fingerprint/tsconfig.json' is up to date because newest input '../fingerprint/src/index.ts' is older than output '../fingerprint/tsconfig.tsbuildinfo'
41
+ 11:42:06 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
- 9:22:16 AM - Project '../keyring/tsconfig.json' is up to date because newest input '../keyring/src/keyring/keyring.ts' is older than output '../keyring/tsconfig.tsbuildinfo'
43
+ 11:42:06 AM - Project '../keyring/tsconfig.json' is up to date because newest input '../keyring/src/index.ts' is older than output '../keyring/tsconfig.tsbuildinfo'
44
44
 
45
- 9:22:16 AM - Project '../account/tsconfig.json' is out of date because output file '../account/tsconfig.tsbuildinfo' does not exist
45
+ 11:42:06 AM - Project '../account/tsconfig.json' is out of date because output file '../account/tsconfig.tsbuildinfo' does not exist
46
46
 
47
- 9:22:16 AM - Building project '/home/runner/work/captcha/captcha/packages/account/tsconfig.json'...
47
+ 11:42:06 AM - Building project '/home/runner/work/captcha/captcha/packages/account/tsconfig.json'...
48
48
 
49
- 9:22:21 AM - Project 'tsconfig.json' is out of date because output file 'tsconfig.tsbuildinfo' does not exist
49
+ 11:42:09 AM - Project 'tsconfig.json' is out of date because output file 'tsconfig.tsbuildinfo' does not exist
50
50
 
51
- 9:22:21 AM - Building project '/home/runner/work/captcha/captcha/packages/procaptcha-common/tsconfig.json'...
51
+ 11:42:09 AM - Building project '/home/runner/work/captcha/captcha/packages/procaptcha-common/tsconfig.json'...
52
52
 
@@ -1,9 +1,9 @@
1
1
 
2
- > @prosopo/procaptcha-common@2.11.5 build
2
+ > @prosopo/procaptcha-common@2.11.7 build
3
3
  > npm run build:cross-env -- --mode ${NODE_ENV:-development}
4
4
 
5
5
 
6
- > @prosopo/procaptcha-common@2.11.5 build:cross-env
6
+ > @prosopo/procaptcha-common@2.11.7 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
  dist/extensionLoader.js 0.21 kB
50
50
  dist/elements/window.js 0.46 kB
51
51
  dist/elements/form.js 0.58 kB
52
- dist/providers.js 0.82 kB
53
- dist/index.js 1.01 kB
52
+ dist/index.js 1.04 kB
54
53
  dist/reactComponents/TestModeBanner.js 1.46 kB
54
+ dist/providers.js 1.53 kB
55
55
  dist/reactComponents/Honeypot.js 2.06 kB
56
56
  dist/reactComponents/Reload.js 3.01 kB
57
57
  dist/state/builder.js 3.14 kB
58
58
  dist/callbacks/defaultCallbacks.js 3.91 kB
59
59
  dist/reactComponents/Checkbox.js 4.23 kB
60
- ✓ built in 238ms
60
+ ✓ built in 312ms
package/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  # @prosopo/procaptcha-common
2
2
 
3
+ ## 2.11.7
4
+ ### Patch Changes
5
+
6
+ - b3f351b: fix(procaptcha): random provider re-selection + backoff on error fallback
7
+
8
+ 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.
9
+
10
+ The error-fallback path now:
11
+
12
+ - **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.
13
+ - **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.
14
+
15
+ Applies to the image, PoW and puzzle managers and the frictionless detection flow. New shared `ProviderSelectRetryContext` type; `BotDetectionFunction` gains an optional retry-context argument.
16
+ - Updated dependencies [b3f351b]
17
+ - Updated dependencies [17bc76e]
18
+ - @prosopo/load-balancer@2.10.7
19
+ - @prosopo/types@4.9.6
20
+ - @prosopo/account@2.8.54
21
+
22
+ ## 2.11.6
23
+ ### Patch Changes
24
+
25
+ - Updated dependencies [6cb3218]
26
+ - @prosopo/types@4.9.5
27
+ - @prosopo/account@2.8.53
28
+ - @prosopo/load-balancer@2.10.6
29
+
3
30
  ## 2.11.5
4
31
  ### Patch Changes
5
32
 
@@ -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;
@@ -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
- return loadBalancer.getRandomActiveProvider(defaultEnvironment, ipMode);
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,
@@ -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 providerRetry: (currentFn: () => Promise<void>, retryFn: () => Promise<void>, stateReset: () => void, attemptCount: number, retryMax: number) => Promise<void>;
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
@@ -1 +1 @@
1
- {"version":3,"file":"providers.d.ts","sourceRoot":"","sources":["../src/providers.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,KAAK,MAAM,EAA2B,MAAM,wBAAwB,CAAC;AAC9E,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAKvE,eAAO,MAAM,UAAU,UACf;IAAE,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,KACnD,MAAM,GAAG,SAIX,CAAC;AAKF,eAAO,MAAM,iCAAiC,uBACzB,gBAAgB,WAC3B,MAAM,KACb,OAAO,CAAC,cAAc,CAExB,CAAC;AAEF,eAAO,MAAM,aAAa,cACd,MAAM,OAAO,CAAC,IAAI,CAAC,WACrB,MAAM,OAAO,CAAC,IAAI,CAAC,cAChB,MAAM,IAAI,gBACR,MAAM,YACV,MAAM,kBAkBhB,CAAC"}
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
- return getRandomActiveProvider(defaultEnvironment, ipMode);
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
  };
@@ -1 +1 @@
1
- {"version":3,"file":"providers.js","sourceRoot":"","sources":["../src/providers.ts"],"names":[],"mappings":"AAcA,OAAO,EAAe,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAM9E,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;AAKF,MAAM,CAAC,MAAM,iCAAiC,GAAG,KAAK,EACrD,kBAAoC,EACpC,MAAe,EACW,EAAE;IAC5B,OAAO,uBAAuB,CAAC,kBAAkB,EAAE,MAAM,CAAC,CAAC;AAC5D,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,aAAa,GAAG,KAAK,EACjC,SAA8B,EAC9B,OAA4B,EAC5B,UAAsB,EACtB,YAAoB,EACpB,QAAgB,EACf,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;QAEb,MAAM,OAAO,EAAE,CAAC;IACjB,CAAC;AACF,CAAC,CAAC"}
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":"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"}
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.5",
3
+ "version": "2.11.7",
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.52",
38
- "@prosopo/load-balancer": "2.10.5",
39
- "@prosopo/types": "4.9.4",
37
+ "@prosopo/account": "2.8.54",
38
+ "@prosopo/load-balancer": "2.10.7",
39
+ "@prosopo/types": "4.9.6",
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 { type IpMode, getRandomActiveProvider } from "@prosopo/load-balancer";
16
- import type { EnvironmentTypes, RandomProvider } from "@prosopo/types";
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
- // Thin wrapper over the load-balancer's static-DNS endpoint resolver. Kept as
30
- // a separate export so widget packages (procaptcha, procaptcha-pow, …) import
31
- // from procaptcha-common rather than reaching into load-balancer directly.
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
- return getRandomActiveProvider(defaultEnvironment, ipMode);
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
- // trigger a retry to attempt a new provider until it passes
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
  };