@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/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
- };