@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.
- 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
|
@@ -12,9 +12,10 @@
|
|
|
12
12
|
// See the License for the specific language governing permissions and
|
|
13
13
|
// limitations under the License.
|
|
14
14
|
|
|
15
|
-
import { describe, expect, it, vi } from "vitest";
|
|
15
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
16
16
|
import {
|
|
17
17
|
getProcaptchaRandomActiveProvider,
|
|
18
|
+
getRetryDelayMs,
|
|
18
19
|
pickIpMode,
|
|
19
20
|
providerRetry,
|
|
20
21
|
} from "../providers.js";
|
|
@@ -29,9 +30,26 @@ vi.mock("@prosopo/load-balancer", () => ({
|
|
|
29
30
|
: "http://localhost:9229",
|
|
30
31
|
},
|
|
31
32
|
})),
|
|
33
|
+
getRandomProviderFromList: vi.fn(
|
|
34
|
+
async (_env: string, _ipMode?: string, excludeUrl?: string) => ({
|
|
35
|
+
providerAccount: "5FromList",
|
|
36
|
+
provider: {
|
|
37
|
+
url: excludeUrl
|
|
38
|
+
? "https://provider-from-list-b.io"
|
|
39
|
+
: "https://provider-from-list-a.io",
|
|
40
|
+
},
|
|
41
|
+
}),
|
|
42
|
+
),
|
|
32
43
|
}));
|
|
33
44
|
|
|
34
45
|
describe("providers", () => {
|
|
46
|
+
// The load-balancer mock functions are module-level, so their call history
|
|
47
|
+
// accumulates across tests — clear it between cases to keep call-count
|
|
48
|
+
// assertions independent.
|
|
49
|
+
beforeEach(() => {
|
|
50
|
+
vi.clearAllMocks();
|
|
51
|
+
});
|
|
52
|
+
|
|
35
53
|
describe("getProcaptchaRandomActiveProvider", () => {
|
|
36
54
|
it("delegates to the load-balancer static-DNS resolver", async () => {
|
|
37
55
|
const { getRandomActiveProvider } = await import(
|
|
@@ -59,6 +77,65 @@ describe("providers", () => {
|
|
|
59
77
|
"ipv4",
|
|
60
78
|
);
|
|
61
79
|
});
|
|
80
|
+
|
|
81
|
+
it("uses the DNS-routed resolver on the first attempt", async () => {
|
|
82
|
+
const { getRandomActiveProvider, getRandomProviderFromList } =
|
|
83
|
+
await import("@prosopo/load-balancer");
|
|
84
|
+
|
|
85
|
+
const result = await getProcaptchaRandomActiveProvider(
|
|
86
|
+
"production",
|
|
87
|
+
undefined,
|
|
88
|
+
{ attempt: 1 },
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
expect(getRandomActiveProvider).toHaveBeenCalled();
|
|
92
|
+
expect(getRandomProviderFromList).not.toHaveBeenCalled();
|
|
93
|
+
expect(result.provider.url).toBe("https://pronode.prosopo.io");
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("picks a random provider from the list on a retry", async () => {
|
|
97
|
+
const { getRandomProviderFromList } = await import(
|
|
98
|
+
"@prosopo/load-balancer"
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
const result = await getProcaptchaRandomActiveProvider(
|
|
102
|
+
"production",
|
|
103
|
+
"ipv4",
|
|
104
|
+
{ attempt: 2, excludeUrl: "https://pronode.prosopo.io" },
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
expect(getRandomProviderFromList).toHaveBeenCalledWith(
|
|
108
|
+
"production",
|
|
109
|
+
"ipv4",
|
|
110
|
+
"https://pronode.prosopo.io",
|
|
111
|
+
);
|
|
112
|
+
// The mock returns a different url when an excludeUrl is supplied.
|
|
113
|
+
expect(result.provider.url).toBe("https://provider-from-list-b.io");
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe("getRetryDelayMs", () => {
|
|
118
|
+
it("grows exponentially from the base delay, capped at the max", () => {
|
|
119
|
+
// random=1 → full jittered value == the (capped) exponential ceiling.
|
|
120
|
+
expect(getRetryDelayMs(0, () => 1)).toBe(500);
|
|
121
|
+
expect(getRetryDelayMs(1, () => 1)).toBe(1000);
|
|
122
|
+
expect(getRetryDelayMs(2, () => 1)).toBe(2000);
|
|
123
|
+
expect(getRetryDelayMs(3, () => 1)).toBe(4000);
|
|
124
|
+
// 500 * 2^6 = 32000 → capped at 10000.
|
|
125
|
+
expect(getRetryDelayMs(6, () => 1)).toBe(10000);
|
|
126
|
+
expect(getRetryDelayMs(20, () => 1)).toBe(10000);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("applies full jitter — a fraction of the ceiling", () => {
|
|
130
|
+
// random=0 → no delay; random=0.5 → half the ceiling.
|
|
131
|
+
expect(getRetryDelayMs(2, () => 0)).toBe(0);
|
|
132
|
+
expect(getRetryDelayMs(2, () => 0.5)).toBe(1000);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("clamps negative or fractional attempt counts", () => {
|
|
136
|
+
expect(getRetryDelayMs(-5, () => 1)).toBe(500);
|
|
137
|
+
expect(getRetryDelayMs(1.9, () => 1)).toBe(1000);
|
|
138
|
+
});
|
|
62
139
|
});
|
|
63
140
|
|
|
64
141
|
describe("pickIpMode", () => {
|
|
@@ -95,7 +172,8 @@ describe("providers", () => {
|
|
|
95
172
|
const retryFn = vi.fn().mockResolvedValue(undefined);
|
|
96
173
|
const stateReset = vi.fn();
|
|
97
174
|
|
|
98
|
-
|
|
175
|
+
// backoffMs=0 keeps the test fast; timing is covered by getRetryDelayMs.
|
|
176
|
+
await providerRetry(currentFn, retryFn, stateReset, 1, 3, 0);
|
|
99
177
|
|
|
100
178
|
expect(currentFn).toHaveBeenCalledTimes(1);
|
|
101
179
|
expect(stateReset).toHaveBeenCalledTimes(1);
|
|
@@ -152,7 +230,7 @@ describe("providers", () => {
|
|
|
152
230
|
.mockImplementation(() => {});
|
|
153
231
|
|
|
154
232
|
await expect(
|
|
155
|
-
providerRetry(currentFn, retryFn, stateReset, 1, 3),
|
|
233
|
+
providerRetry(currentFn, retryFn, stateReset, 1, 3, 0),
|
|
156
234
|
).rejects.toThrow("Retry failed");
|
|
157
235
|
|
|
158
236
|
expect(currentFn).toHaveBeenCalledTimes(1);
|
|
@@ -161,5 +239,31 @@ describe("providers", () => {
|
|
|
161
239
|
|
|
162
240
|
consoleErrorSpy.mockRestore();
|
|
163
241
|
});
|
|
242
|
+
|
|
243
|
+
it("waits for the backoff delay before retrying", async () => {
|
|
244
|
+
const error = new Error("Provider failed");
|
|
245
|
+
const currentFn = vi.fn().mockRejectedValue(error);
|
|
246
|
+
const order: string[] = [];
|
|
247
|
+
const retryFn = vi.fn(async () => {
|
|
248
|
+
order.push("retry");
|
|
249
|
+
});
|
|
250
|
+
const stateReset = vi.fn(() => {
|
|
251
|
+
order.push("reset");
|
|
252
|
+
});
|
|
253
|
+
const consoleErrorSpy = vi
|
|
254
|
+
.spyOn(console, "error")
|
|
255
|
+
.mockImplementation(() => {});
|
|
256
|
+
|
|
257
|
+
const start = Date.now();
|
|
258
|
+
await providerRetry(currentFn, retryFn, stateReset, 1, 3, 40);
|
|
259
|
+
const elapsed = Date.now() - start;
|
|
260
|
+
|
|
261
|
+
// State is reset before the wait, then the retry fires after the delay.
|
|
262
|
+
expect(order).toEqual(["reset", "retry"]);
|
|
263
|
+
expect(elapsed).toBeGreaterThanOrEqual(30);
|
|
264
|
+
expect(retryFn).toHaveBeenCalledTimes(1);
|
|
265
|
+
|
|
266
|
+
consoleErrorSpy.mockRestore();
|
|
267
|
+
});
|
|
164
268
|
});
|
|
165
269
|
});
|