@prosopo/load-balancer 2.10.0 → 2.10.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.
@@ -1,5 +1,5 @@
1
1
 
2
- > @prosopo/load-balancer@2.10.0 build:cjs
2
+ > @prosopo/load-balancer@2.10.1 build:cjs
3
3
  > NODE_ENV=${NODE_ENV:-development}; vite build --config vite.cjs.config.ts --mode $NODE_ENV
4
4
 
5
5
  ViteCommonJSConfig: .
@@ -29,7 +29,7 @@ transforming...
29
29
  Build end
30
30
  ✓ 3 modules transformed.
31
31
  rendering chunks...
32
- dist/cjs/index.cjs 0.58 kB
33
- dist/cjs/providers.cjs 2.17 kB
32
+ dist/cjs/index.cjs 0.70 kB
34
33
  dist/cjs/balancer.cjs 2.21 kB
35
- ✓ built in 149ms
34
+ dist/cjs/providers.cjs 2.76 kB
35
+ ✓ built in 144ms
@@ -1,8 +1,8 @@
1
1
 
2
- > @prosopo/load-balancer@2.10.0 build:tsc
2
+ > @prosopo/load-balancer@2.10.1 build:tsc
3
3
  > tsc --build --verbose
4
4
 
5
- 7:02:14 PM - Projects in this build:
5
+ 3:24:41 PM - Projects in this build:
6
6
  * ../../dev/config/tsconfig.json
7
7
  * ../locale/tsconfig.json
8
8
  * ../util/tsconfig.json
@@ -12,21 +12,21 @@
12
12
  * ../common/tsconfig.json
13
13
  * tsconfig.json
14
14
 
15
- 7:02:15 PM - 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'
15
+ 3:24:41 PM - 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'
16
16
 
17
- 7:02:15 PM - Project '../locale/tsconfig.json' is up to date because newest input '../locale/src/translationKey.ts' is older than output '../locale/tsconfig.tsbuildinfo'
17
+ 3:24:41 PM - Project '../locale/tsconfig.json' is up to date because newest input '../locale/src/translationKey.ts' is older than output '../locale/tsconfig.tsbuildinfo'
18
18
 
19
- 7:02:15 PM - Project '../util/tsconfig.json' is up to date because newest input '../util/src/version.ts' is older than output '../util/tsconfig.tsbuildinfo'
19
+ 3:24:41 PM - Project '../util/tsconfig.json' is up to date because newest input '../util/src/url.ts' is older than output '../util/tsconfig.tsbuildinfo'
20
20
 
21
- 7:02:15 PM - Project '../logger/tsconfig.json' is up to date because newest input '../logger/src/index.ts' is older than output '../logger/tsconfig.tsbuildinfo'
21
+ 3:24:41 PM - Project '../logger/tsconfig.json' is up to date because newest input '../logger/src/index.ts' is older than output '../logger/tsconfig.tsbuildinfo'
22
22
 
23
- 7:02:15 PM - 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'
23
+ 3:24:41 PM - 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'
24
24
 
25
- 7:02:15 PM - Project '../types/tsconfig.json' is up to date because newest input '../types/src/procaptcha/api.ts' is older than output '../types/tsconfig.tsbuildinfo'
25
+ 3:24:41 PM - Project '../types/tsconfig.json' is up to date because newest input '../types/src/procaptcha/api.ts' is older than output '../types/tsconfig.tsbuildinfo'
26
26
 
27
- 7:02:15 PM - Project '../common/tsconfig.json' is up to date because newest input '../common/src/index.ts' is older than output '../common/tsconfig.tsbuildinfo'
27
+ 3:24:41 PM - Project '../common/tsconfig.json' is up to date because newest input '../common/src/error.ts' is older than output '../common/tsconfig.tsbuildinfo'
28
28
 
29
- 7:02:15 PM - Project 'tsconfig.json' is out of date because output file 'tsconfig.tsbuildinfo' does not exist
29
+ 3:24:41 PM - Project 'tsconfig.json' is out of date because output file 'tsconfig.tsbuildinfo' does not exist
30
30
 
31
- 7:02:15 PM - Building project '/home/runner/work/captcha/captcha/packages/load-balancer/tsconfig.json'...
31
+ 3:24:41 PM - Building project '/home/runner/work/captcha/captcha/packages/load-balancer/tsconfig.json'...
32
32
 
@@ -1,9 +1,9 @@
1
1
 
2
- > @prosopo/load-balancer@2.10.0 build
2
+ > @prosopo/load-balancer@2.10.1 build
3
3
  > npm run build:cross-env -- --mode ${NODE_ENV:-development}
4
4
 
5
5
 
6
- > @prosopo/load-balancer@2.10.0 build:cross-env
6
+ > @prosopo/load-balancer@2.10.1 build:cross-env
7
7
  > vite build --config vite.esm.config.ts --mode production
8
8
 
9
9
  ViteEsmConfig: .
@@ -33,7 +33,7 @@ transforming...
33
33
  Build end
34
34
  ✓ 3 modules transformed.
35
35
  rendering chunks...
36
- dist/index.js 0.36 kB
36
+ dist/index.js 0.45 kB
37
37
  dist/balancer.js 1.98 kB
38
- dist/providers.js 2.04 kB
39
- ✓ built in 145ms
38
+ dist/providers.js 2.57 kB
39
+ ✓ built in 153ms
package/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # @prosopo/load-balancer
2
2
 
3
+ ## 2.10.1
4
+ ### Patch Changes
5
+
6
+ - 6a7b122: Allow a client to send a captcha verify request to any pronode: a provider that did not issue the token now forwards the verification to the issuing provider (decoded from the token's providerUrl, SSRF-guarded against the known provider list) and returns its response, mirroring the AWS Lambda verify endpoint. Falls back to local verification when this node is the issuer, the provider list can't be loaded, or the issuer can't be determined.
7
+ - Updated dependencies [dfb0c53]
8
+ - Updated dependencies [11f1e8c]
9
+ - Updated dependencies [b166037]
10
+ - Updated dependencies [1111ff2]
11
+ - @prosopo/common@3.1.41
12
+ - @prosopo/types@4.9.0
13
+
3
14
  ## 2.10.0
4
15
  ### Minor Changes
5
16
 
@@ -3,6 +3,8 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const providers = require("./providers.cjs");
4
4
  const balancer = require("./balancer.cjs");
5
5
  exports._resetPinCache = providers._resetPinCache;
6
+ exports._resetProviderListCache = providers._resetProviderListCache;
7
+ exports.getProviders = providers.getProviders;
6
8
  exports.getRandomActiveProvider = providers.getRandomActiveProvider;
7
9
  exports.convertHostedProvider = balancer.convertHostedProvider;
8
10
  exports.getLoadBalancerUrl = balancer.getLoadBalancerUrl;
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const balancer = require("./balancer.cjs");
3
4
  const DNS_ENDPOINT = {
4
5
  development: "https://localhost:9229",
5
6
  staging: "https://staging.pronode.prosopo.io",
@@ -53,6 +54,17 @@ const resolvePinnedUrl = async (env, ipMode) => {
53
54
  pinPromiseCache.set(key, promise);
54
55
  return promise;
55
56
  };
57
+ const providerListPromiseCache = /* @__PURE__ */ new Map();
58
+ const getProviders = async (env) => {
59
+ const cached = providerListPromiseCache.get(env);
60
+ if (cached) return cached;
61
+ const promise = balancer.loadBalancer(env).catch((err) => {
62
+ providerListPromiseCache.delete(env);
63
+ throw err;
64
+ });
65
+ providerListPromiseCache.set(env, promise);
66
+ return promise;
67
+ };
56
68
  const getRandomActiveProvider = async (env, ipMode) => {
57
69
  const url = await resolvePinnedUrl(env, ipMode);
58
70
  return {
@@ -63,5 +75,10 @@ const getRandomActiveProvider = async (env, ipMode) => {
63
75
  const _resetPinCache = () => {
64
76
  pinPromiseCache.clear();
65
77
  };
78
+ const _resetProviderListCache = () => {
79
+ providerListPromiseCache.clear();
80
+ };
66
81
  exports._resetPinCache = _resetPinCache;
82
+ exports._resetProviderListCache = _resetProviderListCache;
83
+ exports.getProviders = getProviders;
67
84
  exports.getRandomActiveProvider = getRandomActiveProvider;
package/dist/index.js CHANGED
@@ -1,10 +1,12 @@
1
- import { _resetPinCache, getRandomActiveProvider } from "./providers.js";
1
+ import { _resetPinCache, _resetProviderListCache, getProviders, getRandomActiveProvider } from "./providers.js";
2
2
  import { convertHostedProvider, getLoadBalancerUrl, getProviderHostname, loadBalancer, stripIpModeLabel } from "./balancer.js";
3
3
  export {
4
4
  _resetPinCache,
5
+ _resetProviderListCache,
5
6
  convertHostedProvider,
6
7
  getLoadBalancerUrl,
7
8
  getProviderHostname,
9
+ getProviders,
8
10
  getRandomActiveProvider,
9
11
  loadBalancer,
10
12
  stripIpModeLabel
@@ -1,5 +1,7 @@
1
1
  import type { EnvironmentTypes, RandomProvider } from "@prosopo/types";
2
- import type { IpMode } from "./balancer.js";
2
+ import { type HardcodedProvider, type IpMode } from "./balancer.js";
3
+ export declare const getProviders: (env: EnvironmentTypes) => Promise<HardcodedProvider[]>;
3
4
  export declare const getRandomActiveProvider: (env: EnvironmentTypes, ipMode?: IpMode) => Promise<RandomProvider>;
4
5
  export declare const _resetPinCache: () => void;
6
+ export declare const _resetProviderListCache: () => void;
5
7
  //# sourceMappingURL=providers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"providers.d.ts","sourceRoot":"","sources":["../src/providers.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACvE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AA+F5C,eAAO,MAAM,uBAAuB,QAC9B,gBAAgB,WACZ,MAAM,KACb,OAAO,CAAC,cAAc,CAMxB,CAAC;AAIF,eAAO,MAAM,cAAc,YAE1B,CAAC"}
1
+ {"version":3,"file":"providers.d.ts","sourceRoot":"","sources":["../src/providers.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACvE,OAAO,EACN,KAAK,iBAAiB,EACtB,KAAK,MAAM,EAEX,MAAM,eAAe,CAAC;AA4GvB,eAAO,MAAM,YAAY,QACnB,gBAAgB,KACnB,OAAO,CAAC,iBAAiB,EAAE,CAY7B,CAAC;AAEF,eAAO,MAAM,uBAAuB,QAC9B,gBAAgB,WACZ,MAAM,KACb,OAAO,CAAC,cAAc,CAMxB,CAAC;AAIF,eAAO,MAAM,cAAc,YAE1B,CAAC;AAIF,eAAO,MAAM,uBAAuB,YAEnC,CAAC"}
package/dist/providers.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { loadBalancer } from "./balancer.js";
1
2
  const DNS_ENDPOINT = {
2
3
  development: "https://localhost:9229",
3
4
  staging: "https://staging.pronode.prosopo.io",
@@ -51,6 +52,17 @@ const resolvePinnedUrl = async (env, ipMode) => {
51
52
  pinPromiseCache.set(key, promise);
52
53
  return promise;
53
54
  };
55
+ const providerListPromiseCache = /* @__PURE__ */ new Map();
56
+ const getProviders = async (env) => {
57
+ const cached = providerListPromiseCache.get(env);
58
+ if (cached) return cached;
59
+ const promise = loadBalancer(env).catch((err) => {
60
+ providerListPromiseCache.delete(env);
61
+ throw err;
62
+ });
63
+ providerListPromiseCache.set(env, promise);
64
+ return promise;
65
+ };
54
66
  const getRandomActiveProvider = async (env, ipMode) => {
55
67
  const url = await resolvePinnedUrl(env, ipMode);
56
68
  return {
@@ -61,7 +73,12 @@ const getRandomActiveProvider = async (env, ipMode) => {
61
73
  const _resetPinCache = () => {
62
74
  pinPromiseCache.clear();
63
75
  };
76
+ const _resetProviderListCache = () => {
77
+ providerListPromiseCache.clear();
78
+ };
64
79
  export {
65
80
  _resetPinCache,
81
+ _resetProviderListCache,
82
+ getProviders,
66
83
  getRandomActiveProvider
67
84
  };
@@ -1 +1 @@
1
- {"version":3,"file":"providers.js","sourceRoot":"","sources":["../src/providers.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"providers.js","sourceRoot":"","sources":["../src/providers.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,9 @@
1
1
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
- import { _resetPinCache, getRandomActiveProvider } from "../providers.js";
2
+ import { _resetPinCache, _resetProviderListCache, getProviders, getRandomActiveProvider, } from "../providers.js";
3
+ const loadBalancer = vi.fn();
4
+ vi.mock("../balancer.js", () => ({
5
+ loadBalancer: (...args) => loadBalancer(...args),
6
+ }));
3
7
  const originalFetch = globalThis.fetch;
4
8
  const mockHealthzFetch = (host, ok = true, status = 200) => {
5
9
  const mocked = vi.fn(async () => ({
@@ -83,4 +87,46 @@ describe("getRandomActiveProvider (single stack ipMode)", () => {
83
87
  expect(mocked).toHaveBeenCalledTimes(2);
84
88
  });
85
89
  });
90
+ describe("getProviders", () => {
91
+ beforeEach(() => {
92
+ _resetProviderListCache();
93
+ loadBalancer.mockReset();
94
+ });
95
+ it("loads the provider list once and serves repeat callers from cache", async () => {
96
+ const providers = [
97
+ { address: "5xyz", url: "https://p1", datasetId: "0x0" },
98
+ ];
99
+ loadBalancer.mockResolvedValue(providers);
100
+ const first = await getProviders("production");
101
+ const second = await getProviders("production");
102
+ expect(first).toBe(providers);
103
+ expect(second).toBe(providers);
104
+ expect(loadBalancer).toHaveBeenCalledTimes(1);
105
+ });
106
+ it("dedupes concurrent in-flight loads into a single call", async () => {
107
+ const providers = [
108
+ { address: "5xyz", url: "https://p1", datasetId: "0x0" },
109
+ ];
110
+ loadBalancer.mockResolvedValue(providers);
111
+ const [a, b] = await Promise.all([
112
+ getProviders("production"),
113
+ getProviders("production"),
114
+ ]);
115
+ expect(a).toBe(providers);
116
+ expect(b).toBe(providers);
117
+ expect(loadBalancer).toHaveBeenCalledTimes(1);
118
+ });
119
+ it("does not cache failures — a rejected load is retried on the next call", async () => {
120
+ const providers = [
121
+ { address: "5xyz", url: "https://p1", datasetId: "0x0" },
122
+ ];
123
+ loadBalancer
124
+ .mockRejectedValueOnce(new Error("transient"))
125
+ .mockResolvedValueOnce(providers);
126
+ await expect(getProviders("production")).rejects.toThrow("transient");
127
+ const retry = await getProviders("production");
128
+ expect(retry).toBe(providers);
129
+ expect(loadBalancer).toHaveBeenCalledTimes(2);
130
+ });
131
+ });
86
132
  //# sourceMappingURL=providers.unit.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"providers.unit.test.js","sourceRoot":"","sources":["../../src/tests/providers.unit.test.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AACzE,OAAO,EAAE,cAAc,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAE1E,MAAM,aAAa,GAAG,UAAU,CAAC,KAAK,CAAC;AAEvC,MAAM,gBAAgB,GAAG,CAAC,IAAY,EAAE,EAAE,GAAG,IAAI,EAAE,MAAM,GAAG,GAAG,EAAE,EAAE;IAClE,MAAM,MAAM,GAAG,EAAE,CAAC,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;QACjC,EAAE;QACF,MAAM;QACN,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;KACtC,CAAC,CAAC,CAAC;IAEJ,UAAU,CAAC,KAAK,GAAG,MAAa,CAAC;IACjC,OAAO,MAAM,CAAC;AACf,CAAC,CAAC;AAEF,UAAU,CAAC,GAAG,EAAE;IACf,cAAc,EAAE,CAAC;AAClB,CAAC,CAAC,CAAC;AAEH,SAAS,CAAC,GAAG,EAAE;IACd,UAAU,CAAC,KAAK,GAAG,aAAa,CAAC;AAClC,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,sCAAsC,EAAE,GAAG,EAAE;IACrD,EAAE,CAAC,oEAAoE,EAAE,KAAK,IAAI,EAAE;QACnF,MAAM,MAAM,GAAG,gBAAgB,CAAC,SAAS,CAAC,CAAC;QAC3C,MAAM,MAAM,GAAG,MAAM,uBAAuB,CAAC,aAAa,CAAC,CAAC;QAC5D,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC;QAC3D,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;IACvC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qDAAqD,EAAE,KAAK,IAAI,EAAE;QACpE,gBAAgB,CAAC,qBAAqB,CAAC,CAAC;QACxC,MAAM,MAAM,GAAG,MAAM,uBAAuB,CAAC,YAAY,CAAC,CAAC;QAC3D,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC;IACjE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kDAAkD,EAAE,KAAK,IAAI,EAAE;QACjE,gBAAgB,CAAC,6BAA6B,CAAC,CAAC;QAChD,MAAM,MAAM,GAAG,MAAM,uBAAuB,CAAC,SAAS,CAAC,CAAC;QACxD,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,qCAAqC,CAAC,CAAC;IACzE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mEAAmE,EAAE,KAAK,IAAI,EAAE;QAClF,MAAM,MAAM,GAAG,gBAAgB,CAAC,qBAAqB,CAAC,CAAC;QACvD,MAAM,uBAAuB,CAAC,YAAY,CAAC,CAAC;QAC5C,MAAM,uBAAuB,CAAC,YAAY,CAAC,CAAC;QAC5C,MAAM,uBAAuB,CAAC,YAAY,CAAC,CAAC;QAC5C,MAAM,CAAC,MAAM,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qEAAqE,EAAE,KAAK,IAAI,EAAE;QACpF,gBAAgB,CAAC,SAAS,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;QACxC,MAAM,MAAM,GAAG,MAAM,uBAAuB,CAAC,YAAY,CAAC,CAAC;QAC3D,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC;IAChE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uEAAuE,EAAE,KAAK,IAAI,EAAE;QACtF,MAAM,MAAM,GAAG,EAAE,CAAC,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;YACjC,EAAE,EAAE,IAAI;YACR,MAAM,EAAE,GAAG;YACX,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;SAChC,CAAC,CAAC,CAAC;QAEJ,UAAU,CAAC,KAAK,GAAG,MAAa,CAAC;QACjC,MAAM,MAAM,GAAG,MAAM,uBAAuB,CAAC,YAAY,CAAC,CAAC;QAC3D,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC;IAChE,CAAC,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,+CAA+C,EAAE,GAAG,EAAE;IAC9D,EAAE,CAAC,gEAAgE,EAAE,KAAK,IAAI,EAAE;QAC/E,MAAM,MAAM,GAAG,gBAAgB,CAAC,qBAAqB,CAAC,CAAC;QACvD,MAAM,MAAM,GAAG,MAAM,uBAAuB,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;QACnE,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,kCAAkC,CAAC,CAAC;QACrE,MAAM,CAAC,MAAM,CAAC,CAAC,oBAAoB,CAClC,yCAAyC,EACzC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAClB,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8DAA8D,EAAE,KAAK,IAAI,EAAE;QAC7E,MAAM,MAAM,GAAG,gBAAgB,CAAC,6BAA6B,CAAC,CAAC;QAC/D,MAAM,MAAM,GAAG,MAAM,uBAAuB,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;QAChE,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,CAC/B,0CAA0C,CAC1C,CAAC;QACF,MAAM,CAAC,MAAM,CAAC,CAAC,oBAAoB,CAClC,iDAAiD,EACjD,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAClB,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qEAAqE,EAAE,KAAK,IAAI,EAAE;QACpF,gBAAgB,CAAC,SAAS,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;QACxC,MAAM,MAAM,GAAG,MAAM,uBAAuB,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;QACnE,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,iCAAiC,CAAC,CAAC;IACrE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wDAAwD,EAAE,KAAK,IAAI,EAAE;QACvE,MAAM,MAAM,GAAG,gBAAgB,CAAC,qBAAqB,CAAC,CAAC;QACvD,MAAM,uBAAuB,CAAC,YAAY,CAAC,CAAC;QAC5C,MAAM,uBAAuB,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;QACpD,MAAM,uBAAuB,CAAC,YAAY,CAAC,CAAC;QAC5C,MAAM,uBAAuB,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;QAEpD,MAAM,CAAC,MAAM,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"providers.unit.test.js","sourceRoot":"","sources":["../../src/tests/providers.unit.test.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prosopo/load-balancer",
3
- "version": "2.10.0",
3
+ "version": "2.10.1",
4
4
  "description": "Provider load balancer",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -37,12 +37,12 @@
37
37
  },
38
38
  "homepage": "https://github.com/prosopo/captcha#readme",
39
39
  "dependencies": {
40
- "@prosopo/common": "3.1.40",
41
- "@prosopo/types": "4.8.0",
40
+ "@prosopo/common": "3.1.41",
41
+ "@prosopo/types": "4.9.0",
42
42
  "zod": "3.23.8"
43
43
  },
44
44
  "devDependencies": {
45
- "@prosopo/config": "3.3.3",
45
+ "@prosopo/config": "3.3.4",
46
46
  "@types/node": "22.10.2",
47
47
  "@vitest/coverage-v8": "3.2.4",
48
48
  "concurrently": "9.0.1",
package/src/providers.ts CHANGED
@@ -13,7 +13,11 @@
13
13
  // limitations under the License.
14
14
 
15
15
  import type { EnvironmentTypes, RandomProvider } from "@prosopo/types";
16
- import type { IpMode } from "./balancer.js";
16
+ import {
17
+ type HardcodedProvider,
18
+ type IpMode,
19
+ loadBalancer,
20
+ } from "./balancer.js";
17
21
 
18
22
  // Base DNS endpoint per env — the `pronode.prosopo.io` family is latency-routed
19
23
  // (A/AAAA records across the pronode fleet). Clients hit this URL's `/healthz`
@@ -108,6 +112,35 @@ const resolvePinnedUrl = async (
108
112
  return promise;
109
113
  };
110
114
 
115
+ // Cached, in-flight provider-list load per env. The list rarely changes, so a
116
+ // single fetch is shared across callers rather than re-fetching the
117
+ // provider-list JSON on every verify-forward decision.
118
+ const providerListPromiseCache: Map<
119
+ EnvironmentTypes,
120
+ Promise<HardcodedProvider[]>
121
+ > = new Map();
122
+
123
+ /**
124
+ * Returns the full (cached) list of active providers for an environment.
125
+ * Used to look up a provider by url/address, e.g. to find the provider that
126
+ * issued a token before forwarding a verification request to it.
127
+ */
128
+ export const getProviders = async (
129
+ env: EnvironmentTypes,
130
+ ): Promise<HardcodedProvider[]> => {
131
+ const cached = providerListPromiseCache.get(env);
132
+ if (cached) return cached;
133
+
134
+ const promise = loadBalancer(env).catch((err) => {
135
+ // Don't cache failures — a transient fetch error shouldn't poison the
136
+ // cache for the lifetime of the process.
137
+ providerListPromiseCache.delete(env);
138
+ throw err;
139
+ });
140
+ providerListPromiseCache.set(env, promise);
141
+ return promise;
142
+ };
143
+
111
144
  export const getRandomActiveProvider = async (
112
145
  env: EnvironmentTypes,
113
146
  ipMode?: IpMode,
@@ -124,3 +157,9 @@ export const getRandomActiveProvider = async (
124
157
  export const _resetPinCache = () => {
125
158
  pinPromiseCache.clear();
126
159
  };
160
+
161
+ // Test-only escape hatch to isolate the provider-list cache between cases.
162
+ // Not exported from the package index — internal use only.
163
+ export const _resetProviderListCache = () => {
164
+ providerListPromiseCache.clear();
165
+ };
@@ -13,7 +13,20 @@
13
13
  // limitations under the License.
14
14
 
15
15
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
16
- import { _resetPinCache, getRandomActiveProvider } from "../providers.js";
16
+ import {
17
+ _resetPinCache,
18
+ _resetProviderListCache,
19
+ getProviders,
20
+ getRandomActiveProvider,
21
+ } from "../providers.js";
22
+
23
+ // Mock the underlying load balancer so getProviders' caching/failure semantics
24
+ // can be exercised without real network calls. getRandomActiveProvider tests
25
+ // don't use loadBalancer, so this mock leaves them unaffected.
26
+ const loadBalancer = vi.fn();
27
+ vi.mock("../balancer.js", () => ({
28
+ loadBalancer: (...args: unknown[]) => loadBalancer(...args),
29
+ }));
17
30
 
18
31
  const originalFetch = globalThis.fetch;
19
32
 
@@ -122,3 +135,55 @@ describe("getRandomActiveProvider (single stack ipMode)", () => {
122
135
  expect(mocked).toHaveBeenCalledTimes(2);
123
136
  });
124
137
  });
138
+
139
+ describe("getProviders", () => {
140
+ beforeEach(() => {
141
+ _resetProviderListCache();
142
+ loadBalancer.mockReset();
143
+ });
144
+
145
+ it("loads the provider list once and serves repeat callers from cache", async () => {
146
+ const providers = [
147
+ { address: "5xyz", url: "https://p1", datasetId: "0x0" },
148
+ ];
149
+ loadBalancer.mockResolvedValue(providers);
150
+
151
+ const first = await getProviders("production");
152
+ const second = await getProviders("production");
153
+
154
+ expect(first).toBe(providers);
155
+ expect(second).toBe(providers);
156
+ expect(loadBalancer).toHaveBeenCalledTimes(1);
157
+ });
158
+
159
+ it("dedupes concurrent in-flight loads into a single call", async () => {
160
+ const providers = [
161
+ { address: "5xyz", url: "https://p1", datasetId: "0x0" },
162
+ ];
163
+ loadBalancer.mockResolvedValue(providers);
164
+
165
+ const [a, b] = await Promise.all([
166
+ getProviders("production"),
167
+ getProviders("production"),
168
+ ]);
169
+
170
+ expect(a).toBe(providers);
171
+ expect(b).toBe(providers);
172
+ expect(loadBalancer).toHaveBeenCalledTimes(1);
173
+ });
174
+
175
+ it("does not cache failures — a rejected load is retried on the next call", async () => {
176
+ const providers = [
177
+ { address: "5xyz", url: "https://p1", datasetId: "0x0" },
178
+ ];
179
+ loadBalancer
180
+ .mockRejectedValueOnce(new Error("transient"))
181
+ .mockResolvedValueOnce(providers);
182
+
183
+ await expect(getProviders("production")).rejects.toThrow("transient");
184
+ // The failure must have cleared the cache, so the next call retries.
185
+ const retry = await getProviders("production");
186
+ expect(retry).toBe(providers);
187
+ expect(loadBalancer).toHaveBeenCalledTimes(2);
188
+ });
189
+ });