@prosopo/load-balancer 2.9.21 → 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,224 +1,132 @@
1
- import { beforeEach, describe, expect, it, vi } from "vitest";
2
- import { loadBalancer } from "../index.js";
3
- import { getRandomActiveProvider, selectWeightedProvider, } from "../providers.js";
4
- import { _resetCache } from "../providers.js";
5
- vi.mock("../index.js", () => ({
6
- loadBalancer: vi.fn(),
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+ import { _resetPinCache, _resetProviderListCache, getProviders, getRandomActiveProvider, } from "../providers.js";
3
+ const loadBalancer = vi.fn();
4
+ vi.mock("../balancer.js", () => ({
5
+ loadBalancer: (...args) => loadBalancer(...args),
7
6
  }));
8
- describe("selectWeightedProvider", () => {
9
- it("selects provider based on weight distribution", () => {
10
- const providers = [
11
- {
12
- address: "address1",
13
- url: "url1",
14
- datasetId: "dataset1",
15
- weight: 1,
16
- },
17
- {
18
- address: "address2",
19
- url: "url2",
20
- datasetId: "dataset2",
21
- weight: 3,
22
- },
23
- {
24
- address: "address3",
25
- url: "url3",
26
- datasetId: "dataset3",
27
- weight: 1,
28
- },
29
- ];
30
- expect(selectWeightedProvider(providers, 0).address).toBe("address1");
31
- expect(selectWeightedProvider(providers, 1).address).toBe("address2");
32
- expect(selectWeightedProvider(providers, 2).address).toBe("address2");
33
- expect(selectWeightedProvider(providers, 3).address).toBe("address2");
34
- expect(selectWeightedProvider(providers, 4).address).toBe("address3");
35
- expect(selectWeightedProvider(providers, 5).address).toBe("address1");
36
- expect(selectWeightedProvider(providers, 6).address).toBe("address2");
7
+ const originalFetch = globalThis.fetch;
8
+ const mockHealthzFetch = (host, ok = true, status = 200) => {
9
+ const mocked = vi.fn(async () => ({
10
+ ok,
11
+ status,
12
+ json: async () => ({ ok: true, host }),
13
+ }));
14
+ globalThis.fetch = mocked;
15
+ return mocked;
16
+ };
17
+ beforeEach(() => {
18
+ _resetPinCache();
19
+ });
20
+ afterEach(() => {
21
+ globalThis.fetch = originalFetch;
22
+ });
23
+ describe("getRandomActiveProvider (dual stack)", () => {
24
+ it("uses the local URL directly in development (no healthz round-trip)", async () => {
25
+ const mocked = mockHealthzFetch("ignored");
26
+ const result = await getRandomActiveProvider("development");
27
+ expect(result.provider.url).toBe("https://localhost:9229");
28
+ expect(mocked).not.toHaveBeenCalled();
37
29
  });
38
- it("handles equal weights correctly", () => {
39
- const providers = [
40
- {
41
- address: "address1",
42
- url: "url1",
43
- datasetId: "dataset1",
44
- weight: 1,
45
- },
46
- {
47
- address: "address2",
48
- url: "url2",
49
- datasetId: "dataset2",
50
- weight: 1,
51
- },
52
- ];
53
- expect(selectWeightedProvider(providers, 0).address).toBe("address1");
54
- expect(selectWeightedProvider(providers, 1).address).toBe("address2");
55
- expect(selectWeightedProvider(providers, 2).address).toBe("address1");
56
- expect(selectWeightedProvider(providers, 3).address).toBe("address2");
30
+ it("pins to the host returned by /healthz in production", async () => {
31
+ mockHealthzFetch("pronode7.prosopo.io");
32
+ const result = await getRandomActiveProvider("production");
33
+ expect(result.provider.url).toBe("https://pronode7.prosopo.io");
57
34
  });
58
- it("handles single provider", () => {
59
- const providers = [
60
- {
61
- address: "address1",
62
- url: "url1",
63
- datasetId: "dataset1",
64
- weight: 10,
65
- },
66
- ];
67
- expect(selectWeightedProvider(providers, 0).address).toBe("address1");
68
- expect(selectWeightedProvider(providers, 5).address).toBe("address1");
69
- expect(selectWeightedProvider(providers, 100).address).toBe("address1");
35
+ it("pins to the host returned by /healthz in staging", async () => {
36
+ mockHealthzFetch("staging-pronode2.prosopo.io");
37
+ const result = await getRandomActiveProvider("staging");
38
+ expect(result.provider.url).toBe("https://staging-pronode2.prosopo.io");
70
39
  });
71
- it("throws error for empty provider list", () => {
72
- expect(() => selectWeightedProvider([], 0)).toThrow("No providers available");
40
+ it("caches the pinned URL for repeat callers in the same env + ipMode", async () => {
41
+ const mocked = mockHealthzFetch("pronode3.prosopo.io");
42
+ await getRandomActiveProvider("production");
43
+ await getRandomActiveProvider("production");
44
+ await getRandomActiveProvider("production");
45
+ expect(mocked).toHaveBeenCalledTimes(1);
73
46
  });
74
- it("heavily weighted provider is selected more often", () => {
75
- const providers = [
76
- {
77
- address: "address1",
78
- url: "url1",
79
- datasetId: "dataset1",
80
- weight: 1,
81
- },
82
- {
83
- address: "address2",
84
- url: "url2",
85
- datasetId: "dataset2",
86
- weight: 99,
87
- },
88
- ];
89
- const selections = { address1: 0, address2: 0 };
90
- for (let i = 0; i < 100; i++) {
91
- const selected = selectWeightedProvider(providers, i);
92
- if (selected.address === "address1") {
93
- selections.address1++;
94
- }
95
- else {
96
- selections.address2++;
97
- }
98
- }
99
- expect(selections.address1).toBe(1);
100
- expect(selections.address2).toBe(99);
47
+ it("falls back to the dual-stack base URL when /healthz responds non-OK", async () => {
48
+ mockHealthzFetch("ignored", false, 503);
49
+ const result = await getRandomActiveProvider("production");
50
+ expect(result.provider.url).toBe("https://pronode.prosopo.io");
101
51
  });
102
- it("handles maximum weight value (100)", () => {
103
- const providers = [
104
- {
105
- address: "address1",
106
- url: "url1",
107
- datasetId: "dataset1",
108
- weight: 100,
109
- },
110
- {
111
- address: "address2",
112
- url: "url2",
113
- datasetId: "dataset2",
114
- weight: 100,
115
- },
116
- ];
117
- const selections = { address1: 0, address2: 0 };
118
- for (let i = 0; i < 200; i++) {
119
- const selected = selectWeightedProvider(providers, i);
120
- if (selected.address === "address1") {
121
- selections.address1++;
122
- }
123
- else {
124
- selections.address2++;
125
- }
126
- }
127
- expect(selections.address1).toBe(100);
128
- expect(selections.address2).toBe(100);
52
+ it("falls back to the dual-stack base URL when /healthz body is malformed", async () => {
53
+ const mocked = vi.fn(async () => ({
54
+ ok: true,
55
+ status: 200,
56
+ json: async () => ({ ok: true }),
57
+ }));
58
+ globalThis.fetch = mocked;
59
+ const result = await getRandomActiveProvider("production");
60
+ expect(result.provider.url).toBe("https://pronode.prosopo.io");
129
61
  });
130
- it("correctly handles providers without values for weight", () => {
131
- const providers = [
132
- {
133
- address: "address1",
134
- url: "url1",
135
- datasetId: "dataset1",
136
- },
137
- {
138
- address: "address2",
139
- url: "url2",
140
- datasetId: "dataset2",
141
- weight: 3,
142
- },
143
- ];
144
- const providersWithDefaults = [
145
- { ...providers[0], weight: 1 },
146
- providers[1],
147
- ];
148
- const selections = { address1: 0, address2: 0 };
149
- for (let i = 0; i < 100; i++) {
150
- const selected = selectWeightedProvider(providersWithDefaults, i);
151
- if (selected.address === "address1") {
152
- selections.address1++;
153
- }
154
- else {
155
- selections.address2++;
156
- }
157
- }
158
- expect(selections.address1).toBe(25);
159
- expect(selections.address2).toBe(75);
62
+ });
63
+ describe("getRandomActiveProvider (single stack ipMode)", () => {
64
+ it("hits ipv4.pronode.prosopo.io/healthz and pins to ipv4.pronodeN", async () => {
65
+ const mocked = mockHealthzFetch("pronode4.prosopo.io");
66
+ const result = await getRandomActiveProvider("production", "ipv4");
67
+ expect(result.provider.url).toBe("https://ipv4.pronode4.prosopo.io");
68
+ expect(mocked).toHaveBeenCalledWith("https://ipv4.pronode.prosopo.io/healthz", expect.any(Object));
69
+ });
70
+ it("hits ipv6 staging /healthz and pins to ipv6.staging-pronodeN", async () => {
71
+ const mocked = mockHealthzFetch("staging-pronode2.prosopo.io");
72
+ const result = await getRandomActiveProvider("staging", "ipv6");
73
+ expect(result.provider.url).toBe("https://ipv6.staging-pronode2.prosopo.io");
74
+ expect(mocked).toHaveBeenCalledWith("https://ipv6.staging.pronode.prosopo.io/healthz", expect.any(Object));
75
+ });
76
+ it("falls back to the ipv4-labelled global URL when ipv4 /healthz fails", async () => {
77
+ mockHealthzFetch("ignored", false, 500);
78
+ const result = await getRandomActiveProvider("production", "ipv4");
79
+ expect(result.provider.url).toBe("https://ipv4.pronode.prosopo.io");
80
+ });
81
+ it("keeps the dual-stack cache and the ipv4 cache separate", async () => {
82
+ const mocked = mockHealthzFetch("pronode9.prosopo.io");
83
+ await getRandomActiveProvider("production");
84
+ await getRandomActiveProvider("production", "ipv4");
85
+ await getRandomActiveProvider("production");
86
+ await getRandomActiveProvider("production", "ipv4");
87
+ expect(mocked).toHaveBeenCalledTimes(2);
160
88
  });
161
89
  });
162
- describe("getRandomActiveProvider", () => {
90
+ describe("getProviders", () => {
163
91
  beforeEach(() => {
164
- _resetCache();
165
- vi.clearAllMocks();
166
- vi.resetAllMocks();
167
- vi.resetModules();
92
+ _resetProviderListCache();
93
+ loadBalancer.mockReset();
168
94
  });
169
- it("returns a random provider when providers list is populated", async () => {
170
- const mockProviders = [
171
- { address: "address1", url: "url1", datasetId: "dataset1", weight: 1 },
172
- { address: "address2", url: "url2", datasetId: "dataset2", weight: 1 },
173
- ];
174
- loadBalancer.mockResolvedValue(mockProviders);
175
- const result = await getRandomActiveProvider("development", 1);
176
- expect(result.providerAccount).toBe("address2");
177
- expect(result.provider.url).toBe("url2");
178
- expect(result.provider.datasetId).toBe("dataset2");
179
- });
180
- it("loads providers only once when called multiple times", async () => {
181
- const mockProviders = [
182
- { address: "address1", url: "url1", datasetId: "dataset1", weight: 1 },
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" },
183
98
  ];
184
- loadBalancer.mockResolvedValue(mockProviders);
185
- await getRandomActiveProvider("development", 123);
186
- await getRandomActiveProvider("development", 456);
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);
187
104
  expect(loadBalancer).toHaveBeenCalledTimes(1);
188
105
  });
189
- it("handles empty providers list gracefully", async () => {
190
- loadBalancer.mockResolvedValue([]);
191
- await expect(getRandomActiveProvider("development", 123)).rejects.toThrow();
192
- });
193
- it("respects provider weights when selecting", async () => {
194
- const mockProviders = [
195
- { address: "address1", url: "url1", datasetId: "dataset1", weight: 1 },
196
- { address: "address2", url: "url2", datasetId: "dataset2", weight: 3 },
106
+ it("dedupes concurrent in-flight loads into a single call", async () => {
107
+ const providers = [
108
+ { address: "5xyz", url: "https://p1", datasetId: "0x0" },
197
109
  ];
198
- loadBalancer.mockResolvedValue(mockProviders);
199
- const result0 = await getRandomActiveProvider("development", 0);
200
- expect(result0.providerAccount).toBe("address1");
201
- _resetCache();
202
- const result1 = await getRandomActiveProvider("development", 1);
203
- expect(result1.providerAccount).toBe("address2");
204
- _resetCache();
205
- const result2 = await getRandomActiveProvider("development", 2);
206
- expect(result2.providerAccount).toBe("address2");
207
- _resetCache();
208
- const result3 = await getRandomActiveProvider("development", 3);
209
- expect(result3.providerAccount).toBe("address2");
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);
210
118
  });
211
- it("handles providers with missing weight field (defaults to 1)", async () => {
212
- const mockProviders = [
213
- { address: "address1", url: "url1", datasetId: "dataset1", weight: 1 },
214
- { address: "address2", url: "url2", datasetId: "dataset2", weight: 1 },
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" },
215
122
  ];
216
- loadBalancer.mockResolvedValue(mockProviders);
217
- const result0 = await getRandomActiveProvider("development", 0);
218
- expect(result0.providerAccount).toBe("address1");
219
- _resetCache();
220
- const result1 = await getRandomActiveProvider("development", 1);
221
- expect(result1.providerAccount).toBe("address2");
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);
222
130
  });
223
131
  });
224
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":"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"}
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.9.21",
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.7.4",
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.2",
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/balancer.ts CHANGED
@@ -32,17 +32,43 @@ const HardcodedProviderSchema = z.object({
32
32
 
33
33
  export type HardcodedProvider = z.infer<typeof HardcodedProviderSchema>;
34
34
 
35
+ export type IpMode = "ipv4" | "ipv6";
36
+
37
+ // Top-level keys reserved by providerListJson for the ipv4-only / ipv6-only
38
+ // sub-lists. They appear alongside the dual-stack provider entries in the
39
+ // fetched JSON; the converter skips them so existing dual-stack consumers
40
+ // keep working.
41
+ const IP_MODE_KEYS: ReadonlyArray<IpMode> = ["ipv4", "ipv6"];
42
+
35
43
  type hostedProviders = Record<string, unknown>;
36
44
 
45
+ const isProviderRecord = (value: unknown): value is hostedProviders =>
46
+ typeof value === "object" && value !== null;
47
+
37
48
  export const convertHostedProvider = (
38
49
  provider: hostedProviders,
50
+ ipMode?: IpMode,
39
51
  ): HardcodedProvider[] => {
40
- const providers = Object.values(provider).map((p) =>
41
- HardcodedProviderSchema.parse(p),
42
- );
52
+ const source =
53
+ ipMode && isProviderRecord(provider[ipMode])
54
+ ? (provider[ipMode] as hostedProviders)
55
+ : provider;
56
+
57
+ const providers = Object.entries(source)
58
+ .filter(([key]) => !IP_MODE_KEYS.includes(key as IpMode))
59
+ .map(([, value]) => HardcodedProviderSchema.parse(value));
43
60
  return providers.sort((a, b) => a.url.localeCompare(b.url));
44
61
  };
45
62
 
63
+ // Strip the leading `ipv4.` / `ipv6.` label that providerListJson prepends for
64
+ // the single-stack sub-lists so callers can derive a stable identity from the
65
+ // provider URL regardless of which ipMode was requested.
66
+ export const stripIpModeLabel = (hostname: string): string =>
67
+ hostname.replace(/^ipv[46]\./, "");
68
+
69
+ export const getProviderHostname = (provider: HardcodedProvider): string =>
70
+ stripIpModeLabel(new URL(provider.url).hostname);
71
+
46
72
  export const getLoadBalancerUrl = (environment: EnvironmentTypes): string => {
47
73
  if (environment === "production") {
48
74
  return "https://provider-list.prosopo.io/";