@prosopo/load-balancer 2.9.20 → 2.10.0
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 +5 -5
- package/.turbo/turbo-build$colon$tsc.log +11 -11
- package/.turbo/turbo-build.log +6 -6
- package/CHANGELOG.md +77 -0
- package/dist/balancer.d.ts +4 -1
- package/dist/balancer.d.ts.map +1 -1
- package/dist/balancer.js +10 -5
- package/dist/balancer.js.map +1 -1
- package/dist/cjs/balancer.cjs +9 -4
- package/dist/cjs/index.cjs +3 -4
- package/dist/cjs/providers.cjs +55 -54
- package/dist/index.js +5 -6
- package/dist/providers.d.ts +3 -6
- package/dist/providers.d.ts.map +1 -1
- package/dist/providers.js +56 -55
- package/dist/providers.js.map +1 -1
- package/dist/tests/providers.unit.test.js +73 -211
- package/dist/tests/providers.unit.test.js.map +1 -1
- package/package.json +3 -3
- package/src/balancer.ts +29 -3
- package/src/providers.ts +91 -96
- package/src/tests/providers.unit.test.ts +85 -281
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @prosopo/load-balancer@2.
|
|
2
|
+
> @prosopo/load-balancer@2.10.0 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
|
[32m✓[39m 3 modules transformed.
|
|
31
31
|
rendering chunks...
|
|
32
|
-
[2mdist/cjs/[22m[36mindex.cjs [39m[1m[2m0.
|
|
33
|
-
[2mdist/cjs/[22m[
|
|
34
|
-
[2mdist/cjs/[22m[
|
|
35
|
-
[32m✓ built in
|
|
32
|
+
[2mdist/cjs/[22m[36mindex.cjs [39m[1m[2m0.58 kB[22m[1m[22m
|
|
33
|
+
[2mdist/cjs/[22m[36mproviders.cjs [39m[1m[2m2.17 kB[22m[1m[22m
|
|
34
|
+
[2mdist/cjs/[22m[36mbalancer.cjs [39m[1m[2m2.21 kB[22m[1m[22m
|
|
35
|
+
[32m✓ built in 149ms[39m
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
|
|
2
|
-
> @prosopo/load-balancer@2.
|
|
2
|
+
> @prosopo/load-balancer@2.10.0 build:tsc
|
|
3
3
|
> tsc --build --verbose
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
7:02:14 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
|
-
|
|
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'
|
|
16
16
|
|
|
17
|
-
|
|
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'
|
|
18
18
|
|
|
19
|
-
|
|
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'
|
|
20
20
|
|
|
21
|
-
|
|
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'
|
|
22
22
|
|
|
23
|
-
|
|
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'
|
|
24
24
|
|
|
25
|
-
|
|
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'
|
|
26
26
|
|
|
27
|
-
|
|
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'
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
7:02:15 PM - Project 'tsconfig.json' is out of date because output file 'tsconfig.tsbuildinfo' does not exist
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
7:02:15 PM - Building project '/home/runner/work/captcha/captcha/packages/load-balancer/tsconfig.json'...
|
|
32
32
|
|
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
|
|
2
|
-
> @prosopo/load-balancer@2.
|
|
2
|
+
> @prosopo/load-balancer@2.10.0 build
|
|
3
3
|
> npm run build:cross-env -- --mode ${NODE_ENV:-development}
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
> @prosopo/load-balancer@2.
|
|
6
|
+
> @prosopo/load-balancer@2.10.0 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
|
[32m✓[39m 3 modules transformed.
|
|
35
35
|
rendering chunks...
|
|
36
|
-
[2mdist/[22m[36mindex.js [39m[1m[2m0.
|
|
37
|
-
[2mdist/[22m[36mbalancer.js [39m[1m[2m1.
|
|
38
|
-
[2mdist/[22m[36mproviders.js [39m[1m[
|
|
39
|
-
[32m✓ built in
|
|
36
|
+
[2mdist/[22m[36mindex.js [39m[1m[2m0.36 kB[22m[1m[22m
|
|
37
|
+
[2mdist/[22m[36mbalancer.js [39m[1m[2m1.98 kB[22m[1m[22m
|
|
38
|
+
[2mdist/[22m[36mproviders.js [39m[1m[2m2.04 kB[22m[1m[22m
|
|
39
|
+
[32m✓ built in 145ms[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,82 @@
|
|
|
1
1
|
# @prosopo/load-balancer
|
|
2
2
|
|
|
3
|
+
## 2.10.0
|
|
4
|
+
### Minor Changes
|
|
5
|
+
|
|
6
|
+
- 12cd0a6: Replace client-side weighted-random provider selection with static DNS endpoints.
|
|
7
|
+
|
|
8
|
+
- Removed the `providerSelectEntropy` field from `DetectorResult`, `Session`, the
|
|
9
|
+
Mongoose `SessionRecordSchema` (including its standalone index), and every
|
|
10
|
+
call-site that threaded it through frictionless / image / pow / puzzle flows.
|
|
11
|
+
- Removed `FrictionlessManager.hostVerified` and its decision-machine call site
|
|
12
|
+
— there's nothing to verify when the DNS layer picks the host.
|
|
13
|
+
- `getRandomActiveProvider(env)` now returns the per-environment static DNS
|
|
14
|
+
endpoint (`pronode.prosopo.io` family) instead of fetching the provider list
|
|
15
|
+
and weighted-selecting. The entropy parameter is gone.
|
|
16
|
+
- `getProcaptchaRandomActiveProvider` is now a thin re-export so widget packages
|
|
17
|
+
keep importing from `procaptcha-common`.
|
|
18
|
+
- `FrontendProvider.datasetId` is dropped; `CaptchaRequestBody.datasetId` is
|
|
19
|
+
optional. The server falls back to its own most-recently-uploaded dataset
|
|
20
|
+
(`env.datasetId`, populated from `db.getMostRecentDatasetId()` at startup) —
|
|
21
|
+
clients can't pin a dataset under DNS routing because they don't know which
|
|
22
|
+
pronode they'll hit.
|
|
23
|
+
- Removed dead `setProviderLoader` / `prefetchProviders` / `selectWeightedProvider`
|
|
24
|
+
plumbing from `@prosopo/load-balancer`. The server's cacheFile-based loader
|
|
25
|
+
setup in `startProviderApi` goes with them.
|
|
26
|
+
- `getRandomActiveProvider` now hits `/healthz` on the global hostname once per
|
|
27
|
+
page load, reads the responding pronode's identity from the JSON body, and
|
|
28
|
+
pins subsequent captcha calls to that pronode (`https://pronodeN.prosopo.io`)
|
|
29
|
+
so session creation and submission land on the same backend. Falls back to
|
|
30
|
+
the dual-stack global hostname when `/healthz` is unreachable.
|
|
31
|
+
- `/healthz` now returns `{ ok: true, host: <pronode-identity> }` instead of
|
|
32
|
+
`"OK"` to support the above pinning.
|
|
33
|
+
- CORS preflight is now cached for 24h (`maxAge: 86400`) — previously the
|
|
34
|
+
browser refired an OPTIONS preflight before every captcha call because
|
|
35
|
+
the custom `Prosopo-Site-Key` / `Prosopo-User` headers make the request
|
|
36
|
+
non-simple and the default `maxAge` is 5s.
|
|
37
|
+
- 12cd0a6: Add ipv4-only / ipv6-only provider DNS routing via `data-ipv4` / `data-ipv6`.
|
|
38
|
+
|
|
39
|
+
Dapps that need to pin captcha traffic to a single IP stack can now do so:
|
|
40
|
+
|
|
41
|
+
```html
|
|
42
|
+
<div class="procaptcha" data-sitekey="..." data-ipv4="true"></div>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
What happens under the hood:
|
|
46
|
+
|
|
47
|
+
- The widget reads `data-ipv4` / `data-ipv6` (or the matching `ipv4` / `ipv6`
|
|
48
|
+
booleans on `ProcaptchaRenderOptions` / explicit `render(...)`) and threads
|
|
49
|
+
them through `ProcaptchaConfigSchema`.
|
|
50
|
+
- `pickIpMode(config)` resolves them into an `IpMode` (`"ipv4"` / `"ipv6"` /
|
|
51
|
+
`undefined`); `ipv4` wins if both are set.
|
|
52
|
+
- The frictionless / image / pow / puzzle managers pass the `IpMode` into
|
|
53
|
+
`getProcaptchaRandomActiveProvider`, which calls `/healthz` on the matching
|
|
54
|
+
single-stack global hostname (`ipv4.pronode.prosopo.io` or
|
|
55
|
+
`ipv6.pronode.prosopo.io`) and pins subsequent captcha calls to
|
|
56
|
+
`ipv4.pronodeN.prosopo.io` / `ipv6.pronodeN.prosopo.io`. The dual-stack
|
|
57
|
+
cache and the single-stack caches are kept separate.
|
|
58
|
+
- `convertHostedProvider` now accepts an optional `IpMode` and, when set,
|
|
59
|
+
selects the matching `ipv4` / `ipv6` sub-object from the provider-list JSON.
|
|
60
|
+
Top-level `ipv4` / `ipv6` keys are skipped by default so existing dual-stack
|
|
61
|
+
callers keep working.
|
|
62
|
+
- New helpers in `@prosopo/load-balancer`: `IpMode`, `stripIpModeLabel`,
|
|
63
|
+
`getProviderHostname`.
|
|
64
|
+
|
|
65
|
+
Coordinated with the matching `captcha-private` change that publishes the
|
|
66
|
+
`ipv4` / `ipv6` sub-objects to S3.
|
|
67
|
+
|
|
68
|
+
### Patch Changes
|
|
69
|
+
|
|
70
|
+
- Updated dependencies [12cd0a6]
|
|
71
|
+
- Updated dependencies [12cd0a6]
|
|
72
|
+
- @prosopo/types@4.8.0
|
|
73
|
+
|
|
74
|
+
## 2.9.21
|
|
75
|
+
### Patch Changes
|
|
76
|
+
|
|
77
|
+
- Updated dependencies [bb98af1]
|
|
78
|
+
- @prosopo/types@4.7.4
|
|
79
|
+
|
|
3
80
|
## 2.9.20
|
|
4
81
|
### Patch Changes
|
|
5
82
|
|
package/dist/balancer.d.ts
CHANGED
|
@@ -17,8 +17,11 @@ declare const HardcodedProviderSchema: z.ZodObject<{
|
|
|
17
17
|
weight?: number | undefined;
|
|
18
18
|
}>;
|
|
19
19
|
export type HardcodedProvider = z.infer<typeof HardcodedProviderSchema>;
|
|
20
|
+
export type IpMode = "ipv4" | "ipv6";
|
|
20
21
|
type hostedProviders = Record<string, unknown>;
|
|
21
|
-
export declare const convertHostedProvider: (provider: hostedProviders) => HardcodedProvider[];
|
|
22
|
+
export declare const convertHostedProvider: (provider: hostedProviders, ipMode?: IpMode) => HardcodedProvider[];
|
|
23
|
+
export declare const stripIpModeLabel: (hostname: string) => string;
|
|
24
|
+
export declare const getProviderHostname: (provider: HardcodedProvider) => string;
|
|
22
25
|
export declare const getLoadBalancerUrl: (environment: EnvironmentTypes) => string;
|
|
23
26
|
export declare const loadBalancer: (environment: EnvironmentTypes) => Promise<HardcodedProvider[]>;
|
|
24
27
|
export {};
|
package/dist/balancer.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"balancer.d.ts","sourceRoot":"","sources":["../src/balancer.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,QAAA,MAAM,uBAAuB;;;;;;;;;;;;;;;EAa3B,CAAC;AAEH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAExE,KAAK,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"balancer.d.ts","sourceRoot":"","sources":["../src/balancer.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,QAAA,MAAM,uBAAuB;;;;;;;;;;;;;;;EAa3B,CAAC;AAEH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAExE,MAAM,MAAM,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAQrC,KAAK,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAK/C,eAAO,MAAM,qBAAqB,aACvB,eAAe,WAChB,MAAM,KACb,iBAAiB,EAUnB,CAAC;AAKF,eAAO,MAAM,gBAAgB,aAAc,MAAM,KAAG,MACjB,CAAC;AAEpC,eAAO,MAAM,mBAAmB,aAAc,iBAAiB,KAAG,MACjB,CAAC;AAElD,eAAO,MAAM,kBAAkB,gBAAiB,gBAAgB,KAAG,MAUlE,CAAC;AAEF,eAAO,MAAM,YAAY,gBACX,gBAAgB,KAC3B,OAAO,CAAC,iBAAiB,EAAE,CAqB7B,CAAC"}
|
package/dist/balancer.js
CHANGED
|
@@ -9,12 +9,15 @@ const HardcodedProviderSchema = z.object({
|
|
|
9
9
|
return Math.max(1, Math.min(100, weight));
|
|
10
10
|
})
|
|
11
11
|
});
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
);
|
|
12
|
+
const IP_MODE_KEYS = ["ipv4", "ipv6"];
|
|
13
|
+
const isProviderRecord = (value) => typeof value === "object" && value !== null;
|
|
14
|
+
const convertHostedProvider = (provider, ipMode) => {
|
|
15
|
+
const source = ipMode && isProviderRecord(provider[ipMode]) ? provider[ipMode] : provider;
|
|
16
|
+
const providers = Object.entries(source).filter(([key]) => !IP_MODE_KEYS.includes(key)).map(([, value]) => HardcodedProviderSchema.parse(value));
|
|
16
17
|
return providers.sort((a, b) => a.url.localeCompare(b.url));
|
|
17
18
|
};
|
|
19
|
+
const stripIpModeLabel = (hostname) => hostname.replace(/^ipv[46]\./, "");
|
|
20
|
+
const getProviderHostname = (provider) => stripIpModeLabel(new URL(provider.url).hostname);
|
|
18
21
|
const getLoadBalancerUrl = (environment) => {
|
|
19
22
|
if (environment === "production") {
|
|
20
23
|
return "https://provider-list.prosopo.io/";
|
|
@@ -49,5 +52,7 @@ const loadBalancer = async (environment) => {
|
|
|
49
52
|
export {
|
|
50
53
|
convertHostedProvider,
|
|
51
54
|
getLoadBalancerUrl,
|
|
52
|
-
|
|
55
|
+
getProviderHostname,
|
|
56
|
+
loadBalancer,
|
|
57
|
+
stripIpModeLabel
|
|
53
58
|
};
|
package/dist/balancer.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"balancer.js","sourceRoot":"","sources":["../src/balancer.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAElD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,uBAAuB,GAAG,CAAC,CAAC,MAAM,CAAC;IACxC,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;IACnB,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE;IACf,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;IACrB,MAAM,EAAE,CAAC;SACP,MAAM,EAAE;SACR,QAAQ,EAAE;SACV,OAAO,CAAC,CAAC,CAAC;SACV,SAAS,CAAC,CAAC,GAAG,EAAE,EAAE;QAElB,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC/B,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC;IAC3C,CAAC,CAAC;CACH,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"balancer.js","sourceRoot":"","sources":["../src/balancer.ts"],"names":[],"mappings":"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"}
|
package/dist/cjs/balancer.cjs
CHANGED
|
@@ -11,12 +11,15 @@ const HardcodedProviderSchema = zod.z.object({
|
|
|
11
11
|
return Math.max(1, Math.min(100, weight));
|
|
12
12
|
})
|
|
13
13
|
});
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
);
|
|
14
|
+
const IP_MODE_KEYS = ["ipv4", "ipv6"];
|
|
15
|
+
const isProviderRecord = (value) => typeof value === "object" && value !== null;
|
|
16
|
+
const convertHostedProvider = (provider, ipMode) => {
|
|
17
|
+
const source = ipMode && isProviderRecord(provider[ipMode]) ? provider[ipMode] : provider;
|
|
18
|
+
const providers = Object.entries(source).filter(([key]) => !IP_MODE_KEYS.includes(key)).map(([, value]) => HardcodedProviderSchema.parse(value));
|
|
18
19
|
return providers.sort((a, b) => a.url.localeCompare(b.url));
|
|
19
20
|
};
|
|
21
|
+
const stripIpModeLabel = (hostname) => hostname.replace(/^ipv[46]\./, "");
|
|
22
|
+
const getProviderHostname = (provider) => stripIpModeLabel(new URL(provider.url).hostname);
|
|
20
23
|
const getLoadBalancerUrl = (environment) => {
|
|
21
24
|
if (environment === "production") {
|
|
22
25
|
return "https://provider-list.prosopo.io/";
|
|
@@ -50,4 +53,6 @@ const loadBalancer = async (environment) => {
|
|
|
50
53
|
};
|
|
51
54
|
exports.convertHostedProvider = convertHostedProvider;
|
|
52
55
|
exports.getLoadBalancerUrl = getLoadBalancerUrl;
|
|
56
|
+
exports.getProviderHostname = getProviderHostname;
|
|
53
57
|
exports.loadBalancer = loadBalancer;
|
|
58
|
+
exports.stripIpModeLabel = stripIpModeLabel;
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -2,11 +2,10 @@
|
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
3
|
const providers = require("./providers.cjs");
|
|
4
4
|
const balancer = require("./balancer.cjs");
|
|
5
|
-
exports.
|
|
5
|
+
exports._resetPinCache = providers._resetPinCache;
|
|
6
6
|
exports.getRandomActiveProvider = providers.getRandomActiveProvider;
|
|
7
|
-
exports.prefetchProviders = providers.prefetchProviders;
|
|
8
|
-
exports.selectWeightedProvider = providers.selectWeightedProvider;
|
|
9
|
-
exports.setProviderLoader = providers.setProviderLoader;
|
|
10
7
|
exports.convertHostedProvider = balancer.convertHostedProvider;
|
|
11
8
|
exports.getLoadBalancerUrl = balancer.getLoadBalancerUrl;
|
|
9
|
+
exports.getProviderHostname = balancer.getProviderHostname;
|
|
12
10
|
exports.loadBalancer = balancer.loadBalancer;
|
|
11
|
+
exports.stripIpModeLabel = balancer.stripIpModeLabel;
|
package/dist/cjs/providers.cjs
CHANGED
|
@@ -1,66 +1,67 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const totalWeight = providers.reduce((sum, p) => sum + p.weight, 0);
|
|
18
|
-
const randomValue = entropy % totalWeight;
|
|
19
|
-
let cumulativeWeight = 0;
|
|
20
|
-
for (const provider of providers) {
|
|
21
|
-
cumulativeWeight += provider.weight;
|
|
22
|
-
if (randomValue < cumulativeWeight) {
|
|
23
|
-
return provider;
|
|
24
|
-
}
|
|
3
|
+
const DNS_ENDPOINT = {
|
|
4
|
+
development: "https://localhost:9229",
|
|
5
|
+
staging: "https://staging.pronode.prosopo.io",
|
|
6
|
+
production: "https://pronode.prosopo.io"
|
|
7
|
+
};
|
|
8
|
+
const withIpModeLabel = (hostname, ipMode) => ipMode ? `${ipMode}.${hostname}` : hostname;
|
|
9
|
+
const applyIpModeToUrl = (url, ipMode) => {
|
|
10
|
+
if (!ipMode) return url;
|
|
11
|
+
try {
|
|
12
|
+
const parsed = new URL(url);
|
|
13
|
+
parsed.hostname = withIpModeLabel(parsed.hostname, ipMode);
|
|
14
|
+
return parsed.toString().replace(/\/$/, "");
|
|
15
|
+
} catch {
|
|
16
|
+
return url;
|
|
25
17
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
18
|
+
};
|
|
19
|
+
const cacheKey = (env, ipMode) => `${env}|${ipMode ?? "dual"}`;
|
|
20
|
+
const pinPromiseCache = /* @__PURE__ */ new Map();
|
|
21
|
+
const fetchPinnedHost = async (baseUrl) => {
|
|
22
|
+
const res = await fetch(`${baseUrl}/healthz`, {
|
|
23
|
+
method: "GET",
|
|
24
|
+
cache: "no-store",
|
|
25
|
+
credentials: "omit"
|
|
26
|
+
});
|
|
27
|
+
if (!res.ok) {
|
|
28
|
+
throw new Error(`healthz responded with ${res.status}`);
|
|
29
29
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if (customProviderLoader) {
|
|
34
|
-
return customProviderLoader(env);
|
|
30
|
+
const body = await res.json();
|
|
31
|
+
if (typeof body.host !== "string" || body.host.length === 0) {
|
|
32
|
+
throw new Error("healthz response missing host field");
|
|
35
33
|
}
|
|
36
|
-
return
|
|
34
|
+
return body.host;
|
|
37
35
|
};
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
36
|
+
const resolveBaseUrl = (env) => DNS_ENDPOINT[env] ?? DNS_ENDPOINT.development;
|
|
37
|
+
const resolvePinnedUrl = async (env, ipMode) => {
|
|
38
|
+
const base = applyIpModeToUrl(resolveBaseUrl(env), ipMode);
|
|
39
|
+
if (env === "development") return base;
|
|
40
|
+
const key = cacheKey(env, ipMode);
|
|
41
|
+
const cached = pinPromiseCache.get(key);
|
|
42
|
+
if (cached) return cached;
|
|
43
|
+
const promise = (async () => {
|
|
44
|
+
try {
|
|
45
|
+
const host = await fetchPinnedHost(base);
|
|
46
|
+
const parsed = new URL(base);
|
|
47
|
+
parsed.hostname = withIpModeLabel(host, ipMode);
|
|
48
|
+
return parsed.toString().replace(/\/$/, "");
|
|
49
|
+
} catch {
|
|
50
|
+
return base;
|
|
51
|
+
}
|
|
52
|
+
})();
|
|
53
|
+
pinPromiseCache.set(key, promise);
|
|
46
54
|
return promise;
|
|
47
55
|
};
|
|
48
|
-
const
|
|
49
|
-
await
|
|
50
|
-
};
|
|
51
|
-
const getRandomActiveProvider = async (env, entropy) => {
|
|
52
|
-
const providers = await getProvidersPromise(env);
|
|
53
|
-
const randomProviderObj = selectWeightedProvider(providers, entropy);
|
|
56
|
+
const getRandomActiveProvider = async (env, ipMode) => {
|
|
57
|
+
const url = await resolvePinnedUrl(env, ipMode);
|
|
54
58
|
return {
|
|
55
|
-
providerAccount:
|
|
56
|
-
provider: {
|
|
57
|
-
url: randomProviderObj.url,
|
|
58
|
-
datasetId: randomProviderObj.datasetId
|
|
59
|
-
}
|
|
59
|
+
providerAccount: "dns-routed",
|
|
60
|
+
provider: { url }
|
|
60
61
|
};
|
|
61
62
|
};
|
|
62
|
-
|
|
63
|
+
const _resetPinCache = () => {
|
|
64
|
+
pinPromiseCache.clear();
|
|
65
|
+
};
|
|
66
|
+
exports._resetPinCache = _resetPinCache;
|
|
63
67
|
exports.getRandomActiveProvider = getRandomActiveProvider;
|
|
64
|
-
exports.prefetchProviders = prefetchProviders;
|
|
65
|
-
exports.selectWeightedProvider = selectWeightedProvider;
|
|
66
|
-
exports.setProviderLoader = setProviderLoader;
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { convertHostedProvider, getLoadBalancerUrl, loadBalancer } from "./balancer.js";
|
|
1
|
+
import { _resetPinCache, getRandomActiveProvider } from "./providers.js";
|
|
2
|
+
import { convertHostedProvider, getLoadBalancerUrl, getProviderHostname, loadBalancer, stripIpModeLabel } from "./balancer.js";
|
|
3
3
|
export {
|
|
4
|
-
|
|
4
|
+
_resetPinCache,
|
|
5
5
|
convertHostedProvider,
|
|
6
6
|
getLoadBalancerUrl,
|
|
7
|
+
getProviderHostname,
|
|
7
8
|
getRandomActiveProvider,
|
|
8
9
|
loadBalancer,
|
|
9
|
-
|
|
10
|
-
selectWeightedProvider,
|
|
11
|
-
setProviderLoader
|
|
10
|
+
stripIpModeLabel
|
|
12
11
|
};
|
package/dist/providers.d.ts
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import type { EnvironmentTypes, RandomProvider } from "@prosopo/types";
|
|
2
|
-
import {
|
|
3
|
-
export declare
|
|
4
|
-
export declare
|
|
5
|
-
export declare function selectWeightedProvider(providers: HardcodedProvider[], entropy: number): HardcodedProvider;
|
|
6
|
-
export declare const prefetchProviders: (env: EnvironmentTypes) => Promise<void>;
|
|
7
|
-
export declare const getRandomActiveProvider: (env: EnvironmentTypes, entropy: number) => Promise<RandomProvider>;
|
|
2
|
+
import type { IpMode } from "./balancer.js";
|
|
3
|
+
export declare const getRandomActiveProvider: (env: EnvironmentTypes, ipMode?: IpMode) => Promise<RandomProvider>;
|
|
4
|
+
export declare const _resetPinCache: () => void;
|
|
8
5
|
//# 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,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACvE,OAAO,
|
|
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"}
|
package/dist/providers.js
CHANGED
|
@@ -1,66 +1,67 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const totalWeight = providers.reduce((sum, p) => sum + p.weight, 0);
|
|
16
|
-
const randomValue = entropy % totalWeight;
|
|
17
|
-
let cumulativeWeight = 0;
|
|
18
|
-
for (const provider of providers) {
|
|
19
|
-
cumulativeWeight += provider.weight;
|
|
20
|
-
if (randomValue < cumulativeWeight) {
|
|
21
|
-
return provider;
|
|
22
|
-
}
|
|
1
|
+
const DNS_ENDPOINT = {
|
|
2
|
+
development: "https://localhost:9229",
|
|
3
|
+
staging: "https://staging.pronode.prosopo.io",
|
|
4
|
+
production: "https://pronode.prosopo.io"
|
|
5
|
+
};
|
|
6
|
+
const withIpModeLabel = (hostname, ipMode) => ipMode ? `${ipMode}.${hostname}` : hostname;
|
|
7
|
+
const applyIpModeToUrl = (url, ipMode) => {
|
|
8
|
+
if (!ipMode) return url;
|
|
9
|
+
try {
|
|
10
|
+
const parsed = new URL(url);
|
|
11
|
+
parsed.hostname = withIpModeLabel(parsed.hostname, ipMode);
|
|
12
|
+
return parsed.toString().replace(/\/$/, "");
|
|
13
|
+
} catch {
|
|
14
|
+
return url;
|
|
23
15
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
16
|
+
};
|
|
17
|
+
const cacheKey = (env, ipMode) => `${env}|${ipMode ?? "dual"}`;
|
|
18
|
+
const pinPromiseCache = /* @__PURE__ */ new Map();
|
|
19
|
+
const fetchPinnedHost = async (baseUrl) => {
|
|
20
|
+
const res = await fetch(`${baseUrl}/healthz`, {
|
|
21
|
+
method: "GET",
|
|
22
|
+
cache: "no-store",
|
|
23
|
+
credentials: "omit"
|
|
24
|
+
});
|
|
25
|
+
if (!res.ok) {
|
|
26
|
+
throw new Error(`healthz responded with ${res.status}`);
|
|
27
27
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
if (customProviderLoader) {
|
|
32
|
-
return customProviderLoader(env);
|
|
28
|
+
const body = await res.json();
|
|
29
|
+
if (typeof body.host !== "string" || body.host.length === 0) {
|
|
30
|
+
throw new Error("healthz response missing host field");
|
|
33
31
|
}
|
|
34
|
-
return
|
|
32
|
+
return body.host;
|
|
35
33
|
};
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
34
|
+
const resolveBaseUrl = (env) => DNS_ENDPOINT[env] ?? DNS_ENDPOINT.development;
|
|
35
|
+
const resolvePinnedUrl = async (env, ipMode) => {
|
|
36
|
+
const base = applyIpModeToUrl(resolveBaseUrl(env), ipMode);
|
|
37
|
+
if (env === "development") return base;
|
|
38
|
+
const key = cacheKey(env, ipMode);
|
|
39
|
+
const cached = pinPromiseCache.get(key);
|
|
40
|
+
if (cached) return cached;
|
|
41
|
+
const promise = (async () => {
|
|
42
|
+
try {
|
|
43
|
+
const host = await fetchPinnedHost(base);
|
|
44
|
+
const parsed = new URL(base);
|
|
45
|
+
parsed.hostname = withIpModeLabel(host, ipMode);
|
|
46
|
+
return parsed.toString().replace(/\/$/, "");
|
|
47
|
+
} catch {
|
|
48
|
+
return base;
|
|
49
|
+
}
|
|
50
|
+
})();
|
|
51
|
+
pinPromiseCache.set(key, promise);
|
|
44
52
|
return promise;
|
|
45
53
|
};
|
|
46
|
-
const
|
|
47
|
-
await
|
|
48
|
-
};
|
|
49
|
-
const getRandomActiveProvider = async (env, entropy) => {
|
|
50
|
-
const providers = await getProvidersPromise(env);
|
|
51
|
-
const randomProviderObj = selectWeightedProvider(providers, entropy);
|
|
54
|
+
const getRandomActiveProvider = async (env, ipMode) => {
|
|
55
|
+
const url = await resolvePinnedUrl(env, ipMode);
|
|
52
56
|
return {
|
|
53
|
-
providerAccount:
|
|
54
|
-
provider: {
|
|
55
|
-
url: randomProviderObj.url,
|
|
56
|
-
datasetId: randomProviderObj.datasetId
|
|
57
|
-
}
|
|
57
|
+
providerAccount: "dns-routed",
|
|
58
|
+
provider: { url }
|
|
58
59
|
};
|
|
59
60
|
};
|
|
61
|
+
const _resetPinCache = () => {
|
|
62
|
+
pinPromiseCache.clear();
|
|
63
|
+
};
|
|
60
64
|
export {
|
|
61
|
-
|
|
62
|
-
getRandomActiveProvider
|
|
63
|
-
prefetchProviders,
|
|
64
|
-
selectWeightedProvider,
|
|
65
|
-
setProviderLoader
|
|
65
|
+
_resetPinCache,
|
|
66
|
+
getRandomActiveProvider
|
|
66
67
|
};
|
package/dist/providers.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"providers.js","sourceRoot":"","sources":["../src/providers.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"providers.js","sourceRoot":"","sources":["../src/providers.ts"],"names":[],"mappings":"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"}
|