@prosopo/load-balancer 2.11.3 → 2.11.5
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 +3 -6
- package/.turbo/turbo-build$colon$tsc.log +11 -14
- package/.turbo/turbo-build.log +4 -10
- package/CHANGELOG.md +23 -0
- package/LICENSE +201 -0
- package/dist/cjs/retry.cjs +1 -0
- package/dist/retry.d.ts.map +1 -1
- package/dist/retry.js +1 -0
- package/dist/retry.js.map +1 -1
- package/dist/tests/retry.unit.test.js +10 -0
- package/dist/tests/retry.unit.test.js.map +1 -1
- package/package.json +56 -57
- package/src/balancer.ts +0 -225
- package/src/developmentProviderUrl.ts +0 -55
- package/src/index.ts +0 -17
- package/src/providerUrlOverride.ts +0 -44
- package/src/providers.ts +0 -352
- package/src/retry.ts +0 -77
- package/src/tests/developmentProviderUrl.unit.test.ts +0 -76
- package/src/tests/productionProviderList.unit.test.ts +0 -267
- package/src/tests/providerUrlOverride.unit.test.ts +0 -182
- package/src/tests/providers.unit.test.ts +0 -538
- package/src/tests/retry.unit.test.ts +0 -151
- package/tsconfig.cjs.json +0 -24
- package/tsconfig.json +0 -25
- package/tsconfig.tsbuildinfo +0 -1
- package/tsconfig.types.json +0 -9
package/src/balancer.ts
DELETED
|
@@ -1,225 +0,0 @@
|
|
|
1
|
-
// Copyright 2021-2026 Prosopo (UK) Ltd.
|
|
2
|
-
//
|
|
3
|
-
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
-
// you may not use this file except in compliance with the License.
|
|
5
|
-
// You may obtain a copy of the License at
|
|
6
|
-
//
|
|
7
|
-
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
-
//
|
|
9
|
-
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
-
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
-
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
-
// See the License for the specific language governing permissions and
|
|
13
|
-
// limitations under the License.
|
|
14
|
-
import { ProsopoEnvError } from "@prosopo/common";
|
|
15
|
-
import type { EnvironmentTypes } from "@prosopo/types";
|
|
16
|
-
import { getDevelopmentProviderUrl } from "./developmentProviderUrl.js";
|
|
17
|
-
import { readProviderListOverride } from "./providerUrlOverride.js";
|
|
18
|
-
|
|
19
|
-
export interface HardcodedProvider {
|
|
20
|
-
address: string;
|
|
21
|
-
url: string;
|
|
22
|
-
datasetId: string;
|
|
23
|
-
weight: number;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* The provider list is fetched JSON, so entries are checked rather than
|
|
28
|
-
* trusted. Hand-written rather than a zod schema: this module is on the
|
|
29
|
-
* widget's critical path and zod costs 14KB gzipped to validate three strings
|
|
30
|
-
* and a number.
|
|
31
|
-
*/
|
|
32
|
-
const parseHardcodedProvider = (value: unknown): HardcodedProvider => {
|
|
33
|
-
const entry = value as Record<string, unknown>;
|
|
34
|
-
if (
|
|
35
|
-
!isProviderRecord(value) ||
|
|
36
|
-
typeof entry.address !== "string" ||
|
|
37
|
-
typeof entry.url !== "string" ||
|
|
38
|
-
typeof entry.datasetId !== "string" ||
|
|
39
|
-
(entry.weight !== undefined && typeof entry.weight !== "number")
|
|
40
|
-
) {
|
|
41
|
-
// A plain Error, as before: zod threw an untranslated ZodError here,
|
|
42
|
-
// and a malformed provider list is a deployment fault rather than
|
|
43
|
-
// something to show a visitor.
|
|
44
|
-
throw new Error(
|
|
45
|
-
`Malformed provider entry in the provider list: ${JSON.stringify(value)}`,
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return {
|
|
50
|
-
address: entry.address,
|
|
51
|
-
url: entry.url,
|
|
52
|
-
datasetId: entry.datasetId,
|
|
53
|
-
// weight coerced to int 1-100
|
|
54
|
-
weight: Math.max(1, Math.min(100, Math.round(entry.weight ?? 1))),
|
|
55
|
-
};
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
export type IpMode = "ipv4" | "ipv6";
|
|
59
|
-
|
|
60
|
-
// Top-level keys reserved by providerListJson for the ipv4-only / ipv6-only
|
|
61
|
-
// sub-lists. They appear alongside the dual-stack provider entries in the
|
|
62
|
-
// fetched JSON; the converter skips them so existing dual-stack consumers
|
|
63
|
-
// keep working.
|
|
64
|
-
const IP_MODE_KEYS: ReadonlyArray<IpMode> = ["ipv4", "ipv6"];
|
|
65
|
-
|
|
66
|
-
type hostedProviders = Record<string, unknown>;
|
|
67
|
-
|
|
68
|
-
const isProviderRecord = (value: unknown): value is hostedProviders =>
|
|
69
|
-
typeof value === "object" && value !== null;
|
|
70
|
-
|
|
71
|
-
export const convertHostedProvider = (
|
|
72
|
-
provider: hostedProviders,
|
|
73
|
-
ipMode?: IpMode,
|
|
74
|
-
): HardcodedProvider[] => {
|
|
75
|
-
const source =
|
|
76
|
-
ipMode && isProviderRecord(provider[ipMode])
|
|
77
|
-
? (provider[ipMode] as hostedProviders)
|
|
78
|
-
: provider;
|
|
79
|
-
|
|
80
|
-
const providers = Object.entries(source)
|
|
81
|
-
.filter(([key]) => !IP_MODE_KEYS.includes(key as IpMode))
|
|
82
|
-
.map(([, value]) => parseHardcodedProvider(value));
|
|
83
|
-
return providers.sort((a, b) => a.url.localeCompare(b.url));
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
// Strip the leading `ipv4.` / `ipv6.` label that providerListJson prepends for
|
|
87
|
-
// the single-stack sub-lists so callers can derive a stable identity from the
|
|
88
|
-
// provider URL regardless of which ipMode was requested.
|
|
89
|
-
export const stripIpModeLabel = (hostname: string): string =>
|
|
90
|
-
hostname.replace(/^ipv[46]\./, "");
|
|
91
|
-
|
|
92
|
-
export const getProviderHostname = (provider: HardcodedProvider): string =>
|
|
93
|
-
stripIpModeLabel(new URL(provider.url).hostname);
|
|
94
|
-
|
|
95
|
-
// Placeholders for the fields a bare-URL override cannot supply. `address`
|
|
96
|
-
// surfaces as `RandomProvider.providerAccount`, which the DNS-routed
|
|
97
|
-
// production path already fills with a constant rather than a real account;
|
|
98
|
-
// `datasetId` is vestigial on this path because clients stopped sending one
|
|
99
|
-
// and the provider falls back to its own most-recently-loaded dataset.
|
|
100
|
-
const SELF_HOSTED_ADDRESS = "self-hosted";
|
|
101
|
-
const SELF_HOSTED_DATASET_ID = "";
|
|
102
|
-
|
|
103
|
-
const stripTrailingSlash = (url: string): string => url.replace(/\/$/, "");
|
|
104
|
-
|
|
105
|
-
const providersFromUrlList = (raw: string): HardcodedProvider[] =>
|
|
106
|
-
raw
|
|
107
|
-
.split(",")
|
|
108
|
-
.map((url) => stripTrailingSlash(url.trim()))
|
|
109
|
-
.filter((url) => url.length > 0)
|
|
110
|
-
.map((url) => ({
|
|
111
|
-
address: SELF_HOSTED_ADDRESS,
|
|
112
|
-
url,
|
|
113
|
-
datasetId: SELF_HOSTED_DATASET_ID,
|
|
114
|
-
weight: 1,
|
|
115
|
-
}));
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Parses `PROSOPO_PROVIDER_LIST` into providers, or returns empty when unset.
|
|
119
|
-
*
|
|
120
|
-
* Two accepted forms:
|
|
121
|
-
*
|
|
122
|
-
* - The same JSON the hosted provider list serves, so weights and the
|
|
123
|
-
* `ipv4` / `ipv6` sub-lists behave identically. See the README.
|
|
124
|
-
* - One or more bare URLs, comma-separated, for a deployment that has
|
|
125
|
-
* nothing to say about weights or addresses.
|
|
126
|
-
*
|
|
127
|
-
* Trailing slashes are stripped throughout: a token embeds the provider URL
|
|
128
|
-
* it was minted against and `@prosopo/server` finds the issuer by exact
|
|
129
|
-
* string match, so `https://host/` and `https://host` must not disagree.
|
|
130
|
-
*
|
|
131
|
-
* A malformed override returns empty rather than throwing, falling back to
|
|
132
|
-
* normal discovery. Throwing would take every captcha on the page down over
|
|
133
|
-
* what is a deployment-time typo.
|
|
134
|
-
*/
|
|
135
|
-
export const getProviderListOverride = (
|
|
136
|
-
raw: string | undefined = readProviderListOverride(),
|
|
137
|
-
ipMode?: IpMode,
|
|
138
|
-
): HardcodedProvider[] => {
|
|
139
|
-
const trimmed = raw?.trim();
|
|
140
|
-
if (!trimmed) return [];
|
|
141
|
-
|
|
142
|
-
// Anything not starting an object is the bare-URL shorthand.
|
|
143
|
-
if (!trimmed.startsWith("{")) return providersFromUrlList(trimmed);
|
|
144
|
-
|
|
145
|
-
try {
|
|
146
|
-
return convertHostedProvider(JSON.parse(trimmed), ipMode).map(
|
|
147
|
-
(provider) => ({
|
|
148
|
-
...provider,
|
|
149
|
-
url: stripTrailingSlash(provider.url),
|
|
150
|
-
}),
|
|
151
|
-
);
|
|
152
|
-
} catch {
|
|
153
|
-
return [];
|
|
154
|
-
}
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
export const getLoadBalancerUrl = (environment: EnvironmentTypes): string => {
|
|
158
|
-
if (environment === "production") {
|
|
159
|
-
return "https://provider-list.prosopo.io/";
|
|
160
|
-
}
|
|
161
|
-
if (environment === "staging") {
|
|
162
|
-
return "https://provider-list.prosopo.io/staging.json";
|
|
163
|
-
}
|
|
164
|
-
throw new ProsopoEnvError("CONFIG.UNKNOWN_ENVIRONMENT", {
|
|
165
|
-
context: { environment },
|
|
166
|
-
});
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Upper bound on the provider-list fetch. `@prosopo/server` calls
|
|
171
|
-
* `loadBalancer` on every `isVerified`, so a stalled list endpoint would
|
|
172
|
-
* otherwise hold the site owner's request open for the runtime's default
|
|
173
|
-
* (~300s in undici).
|
|
174
|
-
*/
|
|
175
|
-
export const PROVIDER_LIST_FETCH_TIMEOUT_MS = 10_000;
|
|
176
|
-
|
|
177
|
-
const timeoutSignal = (timeoutMs: number): AbortSignal | undefined =>
|
|
178
|
-
// Older browsers running the widget lack AbortSignal.timeout; there the
|
|
179
|
-
// fetch keeps its previous unbounded behaviour rather than throwing.
|
|
180
|
-
typeof AbortSignal !== "undefined" &&
|
|
181
|
-
typeof AbortSignal.timeout === "function"
|
|
182
|
-
? AbortSignal.timeout(timeoutMs)
|
|
183
|
-
: undefined;
|
|
184
|
-
|
|
185
|
-
export const loadBalancer = async (
|
|
186
|
-
environment: EnvironmentTypes,
|
|
187
|
-
ipMode?: IpMode,
|
|
188
|
-
timeoutMs: number = PROVIDER_LIST_FETCH_TIMEOUT_MS,
|
|
189
|
-
): Promise<HardcodedProvider[]> => {
|
|
190
|
-
// An override supplies the whole list. Checked before the environment
|
|
191
|
-
// branches so a self-hosted deployment answers for `production` too, and
|
|
192
|
-
// ahead of the fetch so no request reaches Prosopo's hosted list at all.
|
|
193
|
-
// `ipMode` is honoured only for the JSON form, which can carry its own
|
|
194
|
-
// `ipv4` / `ipv6` sub-lists; the bare-URL form ignores it, because those
|
|
195
|
-
// labels are a property of our fleet's DNS and prefixing `ipv4.` onto
|
|
196
|
-
// someone else's hostname would resolve to nothing.
|
|
197
|
-
const override = getProviderListOverride(undefined, ipMode);
|
|
198
|
-
if (override.length > 0) return override;
|
|
199
|
-
|
|
200
|
-
if (environment === "development") {
|
|
201
|
-
return [
|
|
202
|
-
{
|
|
203
|
-
address: "5EjTA28bKSbFPPyMbUjNtArxyqjwq38r1BapVmLZShaqEedV",
|
|
204
|
-
url: getDevelopmentProviderUrl(),
|
|
205
|
-
datasetId:
|
|
206
|
-
"0x7984714b92d61fd92fd6a7bc9b56b729481470bcc771c19c382ec679acf02e67",
|
|
207
|
-
weight: 1,
|
|
208
|
-
},
|
|
209
|
-
];
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
const providers: hostedProviders = await fetch(
|
|
213
|
-
getLoadBalancerUrl(environment),
|
|
214
|
-
{
|
|
215
|
-
method: "GET",
|
|
216
|
-
mode: "cors",
|
|
217
|
-
signal: timeoutSignal(timeoutMs),
|
|
218
|
-
},
|
|
219
|
-
).then((res) => res.json());
|
|
220
|
-
// `ipMode` steers `convertHostedProvider` at the fetched JSON's
|
|
221
|
-
// `ipv4` / `ipv6` sub-object rather than the dual-stack default,
|
|
222
|
-
// so tokens minted with a single-stack sub-zone URL find their
|
|
223
|
-
// entry — see the `detectIpMode` doc-comment in @prosopo/server.
|
|
224
|
-
return convertHostedProvider(providers, ipMode);
|
|
225
|
-
};
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
// Copyright 2021-2026 Prosopo (UK) Ltd.
|
|
2
|
-
//
|
|
3
|
-
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
-
// you may not use this file except in compliance with the License.
|
|
5
|
-
// You may obtain a copy of the License at
|
|
6
|
-
//
|
|
7
|
-
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
-
//
|
|
9
|
-
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
-
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
-
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
-
// See the License for the specific language governing permissions and
|
|
13
|
-
// limitations under the License.
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Where a development widget looks for its provider.
|
|
17
|
-
*
|
|
18
|
-
* `localhost` is right when the page and the provider share a machine, and
|
|
19
|
-
* wrong the moment they do not: a phone or a simulator loading the demo over
|
|
20
|
-
* the LAN resolves `localhost` to itself, so the widget never reaches the
|
|
21
|
-
* provider and reports the site key as unregistered. The override exists so
|
|
22
|
-
* that case can be pointed at the host running the provider.
|
|
23
|
-
*
|
|
24
|
-
* Development only — staging and production resolve through their DNS-routed
|
|
25
|
-
* endpoints and ignore this entirely.
|
|
26
|
-
*/
|
|
27
|
-
export const DEFAULT_DEVELOPMENT_PROVIDER_URL = "https://localhost:9229";
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Read the override without assuming a runtime.
|
|
31
|
-
*
|
|
32
|
-
* Written as a static `process.env.X` access on purpose: the frontend build
|
|
33
|
-
* substitutes that exact expression for a literal, which a computed lookup
|
|
34
|
-
* (`process.env[key]`) would not match. For the same reason there is no
|
|
35
|
-
* `typeof process` guard — in a browser bundle the whole expression is already
|
|
36
|
-
* a string by the time it runs, and guarding on a `process` that does not
|
|
37
|
-
* exist there would discard it. Under plain node with the variable unset it is
|
|
38
|
-
* simply undefined; where neither applies the access throws and is caught.
|
|
39
|
-
*/
|
|
40
|
-
export const readDevelopmentProviderUrlOverride = (): string | undefined => {
|
|
41
|
-
try {
|
|
42
|
-
return process.env.PROSOPO_PROVIDER_URL_DEVELOPMENT;
|
|
43
|
-
} catch {
|
|
44
|
-
return undefined;
|
|
45
|
-
}
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
export const getDevelopmentProviderUrl = (
|
|
49
|
-
override: string | undefined = readDevelopmentProviderUrlOverride(),
|
|
50
|
-
): string => {
|
|
51
|
-
const trimmed = override?.trim();
|
|
52
|
-
return trimmed
|
|
53
|
-
? trimmed.replace(/\/$/, "")
|
|
54
|
-
: DEFAULT_DEVELOPMENT_PROVIDER_URL;
|
|
55
|
-
};
|
package/src/index.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
// Copyright 2021-2026 Prosopo (UK) Ltd.
|
|
2
|
-
//
|
|
3
|
-
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
-
// you may not use this file except in compliance with the License.
|
|
5
|
-
// You may obtain a copy of the License at
|
|
6
|
-
//
|
|
7
|
-
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
-
//
|
|
9
|
-
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
-
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
-
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
-
// See the License for the specific language governing permissions and
|
|
13
|
-
// limitations under the License.
|
|
14
|
-
export * from "./providers.js";
|
|
15
|
-
export * from "./balancer.js";
|
|
16
|
-
export * from "./developmentProviderUrl.js";
|
|
17
|
-
export * from "./providerUrlOverride.js";
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
// Copyright 2021-2026 Prosopo (UK) Ltd.
|
|
2
|
-
//
|
|
3
|
-
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
-
// you may not use this file except in compliance with the License.
|
|
5
|
-
// You may obtain a copy of the License at
|
|
6
|
-
//
|
|
7
|
-
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
-
//
|
|
9
|
-
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
-
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
-
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
-
// See the License for the specific language governing permissions and
|
|
13
|
-
// limitations under the License.
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Supplies the provider list directly instead of fetching Prosopo's.
|
|
17
|
-
*
|
|
18
|
-
* A self-hosted deployment has its own providers and no hosted list to
|
|
19
|
-
* discover them from. Setting this replaces both halves of discovery — the
|
|
20
|
-
* endpoint the widget calls and the list a verifier matches a token against —
|
|
21
|
-
* with whatever it names. See `getProviderListOverride` in `balancer.ts` for
|
|
22
|
-
* the accepted forms.
|
|
23
|
-
*
|
|
24
|
-
* Distinct from `PROSOPO_PROVIDER_URL_DEVELOPMENT`, which only applies when
|
|
25
|
-
* the environment is `development`. That coupling forced self-hosters to ship
|
|
26
|
-
* a development build of the widget purely to redirect it; this override is
|
|
27
|
-
* environment-independent, so `production` stays correct everywhere.
|
|
28
|
-
*
|
|
29
|
-
* Kept in its own module, holding nothing but the read, so that parsing can
|
|
30
|
-
* live next to `convertHostedProvider` in `balancer.ts` without the two
|
|
31
|
-
* modules importing each other. A cycle here would be bundled into the widget
|
|
32
|
-
* and this package has already lost a release to one.
|
|
33
|
-
*
|
|
34
|
-
* Written as a static `process.env.X` access for the same reason as the
|
|
35
|
-
* development override: frontend bundlers substitute that exact expression
|
|
36
|
-
* for a literal, which a computed lookup would not match.
|
|
37
|
-
*/
|
|
38
|
-
export const readProviderListOverride = (): string | undefined => {
|
|
39
|
-
try {
|
|
40
|
-
return process.env.PROSOPO_PROVIDER_LIST;
|
|
41
|
-
} catch {
|
|
42
|
-
return undefined;
|
|
43
|
-
}
|
|
44
|
-
};
|