@prosopo/load-balancer 2.10.49 → 2.11.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.
Files changed (38) hide show
  1. package/.turbo/turbo-build$colon$cjs.log +9 -8
  2. package/.turbo/turbo-build$colon$tsc.log +12 -12
  3. package/.turbo/turbo-build.log +10 -9
  4. package/CHANGELOG.md +82 -0
  5. package/README.md +34 -0
  6. package/dist/balancer.d.ts +3 -14
  7. package/dist/balancer.d.ts.map +1 -1
  8. package/dist/balancer.js +61 -11
  9. package/dist/balancer.js.map +1 -1
  10. package/dist/cjs/balancer.cjs +61 -10
  11. package/dist/cjs/index.cjs +3 -0
  12. package/dist/cjs/providerUrlOverride.cjs +33 -0
  13. package/dist/cjs/providers.cjs +5 -0
  14. package/dist/index.d.ts +1 -0
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +3 -2
  17. package/dist/index.js.map +1 -1
  18. package/dist/providerUrlOverride.d.ts +2 -0
  19. package/dist/providerUrlOverride.d.ts.map +1 -0
  20. package/dist/providerUrlOverride.js +33 -0
  21. package/dist/providerUrlOverride.js.map +1 -0
  22. package/dist/providers.d.ts.map +1 -1
  23. package/dist/providers.js +6 -1
  24. package/dist/providers.js.map +1 -1
  25. package/dist/tests/providerUrlOverride.unit.test.d.ts +2 -0
  26. package/dist/tests/providerUrlOverride.unit.test.d.ts.map +1 -0
  27. package/dist/tests/providerUrlOverride.unit.test.js +144 -0
  28. package/dist/tests/providerUrlOverride.unit.test.js.map +1 -0
  29. package/dist/tests/providers.unit.test.js +2 -0
  30. package/dist/tests/providers.unit.test.js.map +1 -1
  31. package/package.json +4 -5
  32. package/src/balancer.ts +112 -18
  33. package/src/index.ts +1 -0
  34. package/src/providerUrlOverride.ts +44 -0
  35. package/src/providers.ts +13 -0
  36. package/src/tests/providerUrlOverride.unit.test.ts +182 -0
  37. package/src/tests/providers.unit.test.ts +6 -0
  38. package/tsconfig.tsbuildinfo +1 -1
@@ -1,5 +1,5 @@
1
1
 
2
- > @prosopo/load-balancer@2.10.49 build:cjs
2
+ > @prosopo/load-balancer@2.11.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: .
@@ -7,8 +7,8 @@ ViteCommonJSConfig: .
7
7
  tsConfigPaths: [
8
8
  '/home/runner/work/captcha/captcha/packages/common/tsconfig.json',
9
9
  '/home/runner/work/captcha/captcha/packages/locale/tsconfig.json',
10
- '/home/runner/work/captcha/captcha/packages/logger/tsconfig.json',
11
10
  '/home/runner/work/captcha/captcha/packages/util/tsconfig.json',
11
+ '/home/runner/work/captcha/captcha/packages/logger/tsconfig.json',
12
12
  '/home/runner/work/captcha/captcha/packages/types/tsconfig.json',
13
13
  '/home/runner/work/captcha/captcha/packages/util-crypto/tsconfig.json'
14
14
  ]
@@ -17,8 +17,8 @@ ViteCommonJSConfig: .
17
17
  externals: [
18
18
  '@prosopo/common',
19
19
  '@prosopo/locale',
20
- '@prosopo/logger',
21
20
  '@prosopo/util',
21
+ '@prosopo/logger',
22
22
  '@prosopo/types',
23
23
  '@prosopo/util-crypto'
24
24
  ]
@@ -27,13 +27,14 @@ ViteCommonJSConfig: .
27
27
  vite v8.1.5 building ssr environment for production...
28
28
  Bundle build started
29
29
  
30
- ✓ 6 modules transformed.
30
+ ✓ 7 modules transformed.
31
31
  rendering chunks...
32
32
  computing gzip size...
33
33
  dist/cjs/retry.cjs 1.13 kB │ gzip: 0.56 kB
34
- dist/cjs/index.cjs 1.39 kB │ gzip: 0.39 kB
34
+ dist/cjs/providerUrlOverride.cjs 1.42 kB │ gzip: 0.78 kB
35
+ dist/cjs/index.cjs 1.63 kB │ gzip: 0.41 kB
35
36
  dist/cjs/developmentProviderUrl.cjs 1.81 kB │ gzip: 0.88 kB
36
- dist/cjs/balancer.cjs 2.08 kB │ gzip: 0.97 kB
37
- dist/cjs/providers.cjs 5.92 kB │ gzip: 2.04 kB
37
+ dist/cjs/balancer.cjs 4.36 kB │ gzip: 1.90 kB
38
+ dist/cjs/providers.cjs 6.12 kB │ gzip: 2.10 kB
38
39
 
39
- ✓ built in 78ms
40
+ ✓ built in 67ms
@@ -1,32 +1,32 @@
1
1
 
2
- > @prosopo/load-balancer@2.10.49 build:tsc
2
+ > @prosopo/load-balancer@2.11.0 build:tsc
3
3
  > tsc --build --verbose
4
4
 
5
- 11:04:05 AM - Projects in this build:
5
+ 9:05:50 AM - Projects in this build:
6
6
  * ../../dev/config/tsconfig.json
7
- * ../locale/tsconfig.json
8
7
  * ../util/tsconfig.json
8
+ * ../locale/tsconfig.json
9
9
  * ../logger/tsconfig.json
10
10
  * ../util-crypto/tsconfig.json
11
11
  * ../types/tsconfig.json
12
12
  * ../common/tsconfig.json
13
13
  * tsconfig.json
14
14
 
15
- 11:04:05 AM - Project '../../dev/config/tsconfig.json' is up to date because newest input '../../dev/config/src/vite/NodejsPolarsDirnamePlugin.ts' is older than output '../../dev/config/tsconfig.tsbuildinfo'
15
+ 9:05:50 AM - 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
- 11:04:05 AM - Project '../locale/tsconfig.json' is up to date because newest input '../locale/src/process.ts' is older than output '../locale/tsconfig.tsbuildinfo'
17
+ 9:05:50 AM - Project '../util/tsconfig.json' is up to date because newest input '../util/src/url.ts' is older than output '../util/tsconfig.tsbuildinfo'
18
18
 
19
- 11:04:05 AM - Project '../util/tsconfig.json' is up to date because newest input '../util/src/ofLen.ts' is older than output '../util/tsconfig.tsbuildinfo'
19
+ 9:05:50 AM - Project '../locale/tsconfig.json' is up to date because newest input '../locale/src/translationKey.ts' is older than output '../locale/tsconfig.tsbuildinfo'
20
20
 
21
- 11:04:05 AM - Project '../logger/tsconfig.json' is up to date because newest input '../logger/src/index.ts' is older than output '../logger/tsconfig.tsbuildinfo'
21
+ 9:05:50 AM - 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
- 11:04:05 AM - 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'
23
+ 9:05:50 AM - 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
- 11:04:05 AM - Project '../types/tsconfig.json' is up to date because newest input '../types/src/types.test-d.ts' is older than output '../types/tsconfig.tsbuildinfo'
25
+ 9:05:50 AM - Project '../types/tsconfig.json' is up to date because newest input '../types/src/types.test-d.ts' is older than output '../types/tsconfig.tsbuildinfo'
26
26
 
27
- 11:04:05 AM - Project '../common/tsconfig.json' is up to date because newest input '../common/src/utils/batches.ts' is older than output '../common/tsconfig.tsbuildinfo'
27
+ 9:05:50 AM - Project '../common/tsconfig.json' is up to date because newest input '../common/src/error.ts' is older than output '../common/tsconfig.tsbuildinfo'
28
28
 
29
- 11:04:05 AM - Project 'tsconfig.json' is out of date because output file 'tsconfig.tsbuildinfo' does not exist
29
+ 9:05:50 AM - Project 'tsconfig.json' is out of date because output file 'tsconfig.tsbuildinfo' does not exist
30
30
 
31
- 11:04:05 AM - Building project '/home/runner/work/captcha/captcha/packages/load-balancer/tsconfig.json'...
31
+ 9:05:50 AM - Building project '/home/runner/work/captcha/captcha/packages/load-balancer/tsconfig.json'...
32
32
 
@@ -1,9 +1,9 @@
1
1
 
2
- > @prosopo/load-balancer@2.10.49 build
2
+ > @prosopo/load-balancer@2.11.0 build
3
3
  > npm run build:cross-env -- --mode ${NODE_ENV:-development}
4
4
 
5
5
 
6
- > @prosopo/load-balancer@2.10.49 build:cross-env
6
+ > @prosopo/load-balancer@2.11.0 build:cross-env
7
7
  > vite build --config vite.esm.config.ts --mode production
8
8
 
9
9
  ViteEsmConfig: .
@@ -11,8 +11,8 @@ ViteEsmConfig: .
11
11
  tsConfigPaths: [
12
12
  '/home/runner/work/captcha/captcha/packages/common/tsconfig.json',
13
13
  '/home/runner/work/captcha/captcha/packages/locale/tsconfig.json',
14
- '/home/runner/work/captcha/captcha/packages/logger/tsconfig.json',
15
14
  '/home/runner/work/captcha/captcha/packages/util/tsconfig.json',
15
+ '/home/runner/work/captcha/captcha/packages/logger/tsconfig.json',
16
16
  '/home/runner/work/captcha/captcha/packages/types/tsconfig.json',
17
17
  '/home/runner/work/captcha/captcha/packages/util-crypto/tsconfig.json'
18
18
  ]
@@ -21,8 +21,8 @@ ViteEsmConfig: .
21
21
  externals: [
22
22
  '@prosopo/common',
23
23
  '@prosopo/locale',
24
- '@prosopo/logger',
25
24
  '@prosopo/util',
25
+ '@prosopo/logger',
26
26
  '@prosopo/types',
27
27
  '@prosopo/util-crypto'
28
28
  ]
@@ -31,14 +31,15 @@ ViteEsmConfig: .
31
31
  vite v8.1.5 building ssr environment for production...
32
32
  Bundle build started
33
33
  
34
- ✓ 6 modules transformed.
34
+ ✓ 7 modules transformed.
35
35
  rendering chunks...
36
36
  computing gzip size...
37
37
  dist/_virtual/_rolldown/runtime.js 0.04 kB │ gzip: 0.06 kB
38
- dist/index.js 0.86 kB │ gzip: 0.30 kB
38
+ dist/index.js 1.00 kB │ gzip: 0.32 kB
39
39
  dist/retry.js 1.09 kB │ gzip: 0.55 kB
40
+ dist/providerUrlOverride.js 1.39 kB │ gzip: 0.77 kB
40
41
  dist/developmentProviderUrl.js 1.70 kB │ gzip: 0.87 kB
41
- dist/balancer.js 1.88 kB │ gzip: 0.94 kB
42
- dist/providers.js 5.64 kB │ gzip: 2.01 kB
42
+ dist/balancer.js 4.11 kB │ gzip: 1.86 kB
43
+ dist/providers.js 5.85 kB │ gzip: 2.07 kB
43
44
 
44
- ✓ built in 61ms
45
+ ✓ built in 62ms
package/CHANGELOG.md CHANGED
@@ -1,5 +1,87 @@
1
1
  # @prosopo/load-balancer
2
2
 
3
+ ## 2.11.0
4
+ ### Minor Changes
5
+
6
+ - f13bea8: Let a deployment supply its own provider list with `PROSOPO_PROVIDER_LIST`.
7
+
8
+ There was no supported way to point the widget at providers you run. The only lever was
9
+ `PROSOPO_PROVIDER_URL_DEVELOPMENT`, which is read solely when the environment is `development`, and the widget takes
10
+ that environment from `NODE_ENV` at build time. So redirecting a bundle meant building it as a development bundle:
11
+ unminified, ~1.9 MB across chunks instead of ~1.4 MB, with rate limiting implications if anyone copied the same setting
12
+ onto their provider. Building with `NODE_ENV=production` silently ignored the variable and sent the widget back to
13
+ Prosopo's fleet, with nothing in the output to say so.
14
+
15
+ `PROSOPO_PROVIDER_LIST` is environment-independent, so `production` stays correct everywhere. It short-circuits both
16
+ halves of discovery — the `/healthz` call that picks a node, and the list `@prosopo/server` matches a token against —
17
+ so a self-hosted deployment makes no request to Prosopo at all.
18
+
19
+ Two forms, because one node and several have different needs:
20
+
21
+ ```bash
22
+ PROSOPO_PROVIDER_LIST=https://captcha1.example.com,https://captcha2.example.com
23
+ PROSOPO_PROVIDER_LIST='{"one":{"address":"5Abc...","url":"https://captcha1.example.com","datasetId":"","weight":3}}'
24
+ ```
25
+
26
+ The JSON form is the exact shape `provider-list.prosopo.io` serves, parsed by the same `convertHostedProvider`, so
27
+ weights and the `ipv4` / `ipv6` sub-lists behave identically. The bare-URL form fills in placeholder `address` and
28
+ `datasetId`, neither of which is load-bearing on this path — the DNS-routed production path already passes a constant
29
+ for `address`, and clients stopped sending `datasetId`.
30
+
31
+ Because it returns a list rather than one URL, a multi-provider override gets weighted selection and real failover:
32
+ `getRandomProviderFromList` can now exclude a node that just errored and pick a different one, which with a single
33
+ development URL degraded to retrying the same node.
34
+
35
+ Details worth knowing:
36
+
37
+ - Trailing slashes are stripped. A token embeds the provider URL it was minted against and the verifier matches by
38
+ exact string, so `https://host/` and `https://host` must not disagree.
39
+ - A malformed value is ignored and normal discovery resumes rather than throwing, so a deployment-time typo cannot
40
+ take every captcha on a page down with it.
41
+ - The env read lives in its own module holding nothing else. Parsing needs `convertHostedProvider` from `balancer.ts`,
42
+ and `balancer.ts` needs the read — putting both in one file would create an import cycle, which this package has
43
+ already lost a release to when a cycle between chunks broke the widget at load.
44
+ - `PROSOPO_PROVIDER_URL_DEVELOPMENT` is untouched and still works for local development.
45
+
46
+ Verified by building a production bundle with the override set: `defaultEnvironment` stays `production`, the bundle is
47
+ minified, and the override is baked in.
48
+
49
+ ### Patch Changes
50
+
51
+ - Updated dependencies [4c9b84b]
52
+ - @prosopo/types@5.10.1
53
+
54
+ ## 2.10.50
55
+ ### Patch Changes
56
+
57
+ - a9141c3: Stop loading zod before the widget can draw itself. Takes another 13KB gzipped off the critical path.
58
+
59
+ zod is 14KB gzipped and it was being downloaded and parsed before the checkbox appeared, because six small things on the startup path happened to use it:
60
+
61
+ - two lists of strings in `@prosopo/logger` (log levels, output format)
62
+ - two lists of strings in `@prosopo/locale` (language codes, translation keys)
63
+ - two lists of two strings in `@prosopo/types` (start mode, challenge placement)
64
+ - one four-field object in `@prosopo/load-balancer` (a provider entry)
65
+ - an `instanceof ZodError` check in `@prosopo/common`
66
+ - `INPUT_LIMITS`, a plain table of numbers, that happened to live in the same file as zod-based string builders
67
+
68
+ None of these need a validation library. They are now plain TypeScript: a list, a type, and where input is untrusted, a one-line guard. `INPUT_LIMITS` moved to its own file so reading it no longer drags the builders along.
69
+
70
+ zod has not gone anywhere — the real request and response schemas in `@prosopo/types` still use it, and still validate exactly as before. It now arrives with the code that needs it, after the widget is on screen, rather than in front of it.
71
+
72
+ Two API changes for anyone importing these directly:
73
+
74
+ - `LanguageSchema`, `TranslationKeysSchema`, `StartModeSchema` and `Placement` are no longer exported as zod schemas. Use `isLanguage()`, `isStartMode()`, `isPlacement()` to check a value, and `LanguageCodes`, `translationKeys`, `StartModes`, `Placements` for the lists.
75
+ - `isZodError()` now recognises a zod error by its name rather than `instanceof`. That is strictly more tolerant: the name still matches when an error crosses a realm boundary or comes from a second copy of zod, which `instanceof` misses — it was already the fallback arm of the same check.
76
+
77
+ Two behaviour notes: a malformed entry in the fetched provider list now throws a plain `Error` naming the entry, where it used to throw an untranslated zod error; and the language codes accepted are unchanged.
78
+
79
+ Covered by the existing suites for every package touched (types, types-database, locale, logger, common, load-balancer, all five procaptcha packages, api, cli, api-express-router, server, and the provider's 1322 unit tests), all passing. The built bundle was also loaded in a real browser: the widget renders from the first eight chunks, zod arrives in the second wave, and the provider's error came back translated into German.
80
+ - Updated dependencies [a9141c3]
81
+ - Updated dependencies [a9141c3]
82
+ - @prosopo/common@3.1.59
83
+ - @prosopo/types@5.10.0
84
+
3
85
  ## 2.10.49
4
86
  ### Patch Changes
5
87
 
package/README.md CHANGED
@@ -47,3 +47,37 @@ In this example:
47
47
  - Total weight = 4
48
48
  - Provider 1 receives ~25% of traffic
49
49
  - Provider 2 receives ~75% of traffic
50
+
51
+ ## Overriding the provider list
52
+
53
+ By default the list is fetched from `provider-list.prosopo.io` for `staging` and `production`, and hardcoded to a local
54
+ provider for `development`. `PROSOPO_PROVIDER_LIST` replaces all of that with providers you name, in every environment.
55
+
56
+ This is what a [self-hosted](https://docs.prosopo.io/en/self-hosting/) deployment uses. It short-circuits both halves of
57
+ discovery — the `/healthz` call the widget makes to pick a node, and the list a verifier matches a token against — so
58
+ nothing reaches Prosopo.
59
+
60
+ Two forms are accepted. One or more comma-separated URLs:
61
+
62
+ ```bash
63
+ PROSOPO_PROVIDER_LIST=https://captcha1.example.com,https://captcha2.example.com
64
+ ```
65
+
66
+ ...or the same JSON shape documented above, when you want to set weights or the `ipv4` / `ipv6` sub-lists:
67
+
68
+ ```bash
69
+ PROSOPO_PROVIDER_LIST='{"one":{"address":"5Abc...","url":"https://captcha1.example.com","datasetId":"","weight":3}}'
70
+ ```
71
+
72
+ Notes:
73
+
74
+ - Trailing slashes are stripped. A token embeds the provider URL it was minted against and `@prosopo/server` finds the
75
+ issuer by exact string match, so `https://host/` and `https://host` must not be able to disagree.
76
+ - A malformed value is ignored and normal discovery resumes, rather than throwing. A deployment-time typo should not
77
+ take down every captcha on the page — check your logs if an override seems to have no effect.
78
+ - The bare-URL form ignores `ipMode`. The `ipv4.` / `ipv6.` labels are a property of Prosopo's own DNS and prefixing
79
+ them onto another host would resolve to nothing. Use the JSON form's sub-lists if you run single-stack sub-zones.
80
+ - Frontend builds bake the value in at bundle time; Node reads it at runtime.
81
+
82
+ `PROSOPO_PROVIDER_URL_DEVELOPMENT` still exists and still only applies when the environment is `development`. Prefer
83
+ `PROSOPO_PROVIDER_LIST`, which works in a production build.
@@ -1,27 +1,16 @@
1
1
  import type { EnvironmentTypes } from "@prosopo/types";
2
- import { z } from "zod";
3
- declare const HardcodedProviderSchema: z.ZodObject<{
4
- address: z.ZodString;
5
- url: z.ZodString;
6
- datasetId: z.ZodString;
7
- weight: z.ZodEffects<z.ZodDefault<z.ZodOptional<z.ZodNumber>>, number, number | undefined>;
8
- }, "strip", z.ZodTypeAny, {
2
+ export interface HardcodedProvider {
9
3
  address: string;
10
4
  url: string;
11
5
  datasetId: string;
12
6
  weight: number;
13
- }, {
14
- address: string;
15
- url: string;
16
- datasetId: string;
17
- weight?: number | undefined;
18
- }>;
19
- export type HardcodedProvider = z.infer<typeof HardcodedProviderSchema>;
7
+ }
20
8
  export type IpMode = "ipv4" | "ipv6";
21
9
  type hostedProviders = Record<string, unknown>;
22
10
  export declare const convertHostedProvider: (provider: hostedProviders, ipMode?: IpMode) => HardcodedProvider[];
23
11
  export declare const stripIpModeLabel: (hostname: string) => string;
24
12
  export declare const getProviderHostname: (provider: HardcodedProvider) => string;
13
+ export declare const getProviderListOverride: (raw?: string | undefined, ipMode?: IpMode) => HardcodedProvider[];
25
14
  export declare const getLoadBalancerUrl: (environment: EnvironmentTypes) => string;
26
15
  export declare const loadBalancer: (environment: EnvironmentTypes, ipMode?: IpMode) => Promise<HardcodedProvider[]>;
27
16
  export {};
@@ -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;AAGxB,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,WACpB,MAAM,KACb,OAAO,CAAC,iBAAiB,EAAE,CAyB7B,CAAC"}
1
+ {"version":3,"file":"balancer.d.ts","sourceRoot":"","sources":["../src/balancer.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAIvD,MAAM,WAAW,iBAAiB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CACf;AAkCD,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;AA0ClD,eAAO,MAAM,uBAAuB,SAC9B,MAAM,GAAG,SAAS,WACd,MAAM,KACb,iBAAiB,EAiBnB,CAAC;AAEF,eAAO,MAAM,kBAAkB,gBAAiB,gBAAgB,KAAG,MAUlE,CAAC;AAEF,eAAO,MAAM,YAAY,gBACX,gBAAgB,WACpB,MAAM,KACb,OAAO,CAAC,iBAAiB,EAAE,CAmC7B,CAAC"}
package/dist/balancer.js CHANGED
@@ -1,29 +1,79 @@
1
1
  import { getDevelopmentProviderUrl } from "./developmentProviderUrl.js";
2
+ import { readProviderListOverride } from "./providerUrlOverride.js";
2
3
  import { ProsopoEnvError } from "@prosopo/common";
3
- import { z } from "zod";
4
4
  //#region src/balancer.ts
5
- var HardcodedProviderSchema = z.object({
6
- address: z.string(),
7
- url: z.string(),
8
- datasetId: z.string(),
9
- weight: z.number().optional().default(1).transform((val) => {
10
- return Math.max(1, Math.min(100, Math.round(val)));
11
- })
12
- });
5
+ /**
6
+ * The provider list is fetched JSON, so entries are checked rather than
7
+ * trusted. Hand-written rather than a zod schema: this module is on the
8
+ * widget's critical path and zod costs 14KB gzipped to validate three strings
9
+ * and a number.
10
+ */
11
+ var parseHardcodedProvider = (value) => {
12
+ const entry = value;
13
+ if (!isProviderRecord(value) || typeof entry.address !== "string" || typeof entry.url !== "string" || typeof entry.datasetId !== "string" || entry.weight !== void 0 && typeof entry.weight !== "number") throw new Error(`Malformed provider entry in the provider list: ${JSON.stringify(value)}`);
14
+ return {
15
+ address: entry.address,
16
+ url: entry.url,
17
+ datasetId: entry.datasetId,
18
+ weight: Math.max(1, Math.min(100, Math.round(entry.weight ?? 1)))
19
+ };
20
+ };
13
21
  var IP_MODE_KEYS = ["ipv4", "ipv6"];
14
22
  var isProviderRecord = (value) => typeof value === "object" && value !== null;
15
23
  var convertHostedProvider = (provider, ipMode) => {
16
24
  const source = ipMode && isProviderRecord(provider[ipMode]) ? provider[ipMode] : provider;
17
- return Object.entries(source).filter(([key]) => !IP_MODE_KEYS.includes(key)).map(([, value]) => HardcodedProviderSchema.parse(value)).sort((a, b) => a.url.localeCompare(b.url));
25
+ return Object.entries(source).filter(([key]) => !IP_MODE_KEYS.includes(key)).map(([, value]) => parseHardcodedProvider(value)).sort((a, b) => a.url.localeCompare(b.url));
18
26
  };
19
27
  var stripIpModeLabel = (hostname) => hostname.replace(/^ipv[46]\./, "");
20
28
  var getProviderHostname = (provider) => stripIpModeLabel(new URL(provider.url).hostname);
29
+ var SELF_HOSTED_ADDRESS = "self-hosted";
30
+ var SELF_HOSTED_DATASET_ID = "";
31
+ var stripTrailingSlash = (url) => url.replace(/\/$/, "");
32
+ var providersFromUrlList = (raw) => raw.split(",").map((url) => stripTrailingSlash(url.trim())).filter((url) => url.length > 0).map((url) => ({
33
+ address: SELF_HOSTED_ADDRESS,
34
+ url,
35
+ datasetId: SELF_HOSTED_DATASET_ID,
36
+ weight: 1
37
+ }));
38
+ /**
39
+ * Parses `PROSOPO_PROVIDER_LIST` into providers, or returns empty when unset.
40
+ *
41
+ * Two accepted forms:
42
+ *
43
+ * - The same JSON the hosted provider list serves, so weights and the
44
+ * `ipv4` / `ipv6` sub-lists behave identically. See the README.
45
+ * - One or more bare URLs, comma-separated, for a deployment that has
46
+ * nothing to say about weights or addresses.
47
+ *
48
+ * Trailing slashes are stripped throughout: a token embeds the provider URL
49
+ * it was minted against and `@prosopo/server` finds the issuer by exact
50
+ * string match, so `https://host/` and `https://host` must not disagree.
51
+ *
52
+ * A malformed override returns empty rather than throwing, falling back to
53
+ * normal discovery. Throwing would take every captcha on the page down over
54
+ * what is a deployment-time typo.
55
+ */
56
+ var getProviderListOverride = (raw = readProviderListOverride(), ipMode) => {
57
+ const trimmed = raw?.trim();
58
+ if (!trimmed) return [];
59
+ if (!trimmed.startsWith("{")) return providersFromUrlList(trimmed);
60
+ try {
61
+ return convertHostedProvider(JSON.parse(trimmed), ipMode).map((provider) => ({
62
+ ...provider,
63
+ url: stripTrailingSlash(provider.url)
64
+ }));
65
+ } catch {
66
+ return [];
67
+ }
68
+ };
21
69
  var getLoadBalancerUrl = (environment) => {
22
70
  if (environment === "production") return "https://provider-list.prosopo.io/";
23
71
  if (environment === "staging") return "https://provider-list.prosopo.io/staging.json";
24
72
  throw new ProsopoEnvError("CONFIG.UNKNOWN_ENVIRONMENT", { context: { environment } });
25
73
  };
26
74
  var loadBalancer = async (environment, ipMode) => {
75
+ const override = getProviderListOverride(void 0, ipMode);
76
+ if (override.length > 0) return override;
27
77
  if (environment === "development") return [{
28
78
  address: "5EjTA28bKSbFPPyMbUjNtArxyqjwq38r1BapVmLZShaqEedV",
29
79
  url: getDevelopmentProviderUrl(),
@@ -36,4 +86,4 @@ var loadBalancer = async (environment, ipMode) => {
36
86
  }).then((res) => res.json()), ipMode);
37
87
  };
38
88
  //#endregion
39
- export { convertHostedProvider, getLoadBalancerUrl, getProviderHostname, loadBalancer, stripIpModeLabel };
89
+ export { convertHostedProvider, getLoadBalancerUrl, getProviderHostname, getProviderListOverride, loadBalancer, stripIpModeLabel };
@@ -1 +1 @@
1
- {"version":3,"file":"balancer.js","sourceRoot":"","sources":["../src/balancer.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"balancer.js","sourceRoot":"","sources":["../src/balancer.ts"],"names":[],"mappings":"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"}
@@ -1,29 +1,79 @@
1
1
  const require_developmentProviderUrl = require("./developmentProviderUrl.cjs");
2
+ const require_providerUrlOverride = require("./providerUrlOverride.cjs");
2
3
  let _prosopo_common = require("@prosopo/common");
3
- let zod = require("zod");
4
4
  //#region src/balancer.ts
5
- var HardcodedProviderSchema = zod.z.object({
6
- address: zod.z.string(),
7
- url: zod.z.string(),
8
- datasetId: zod.z.string(),
9
- weight: zod.z.number().optional().default(1).transform((val) => {
10
- return Math.max(1, Math.min(100, Math.round(val)));
11
- })
12
- });
5
+ /**
6
+ * The provider list is fetched JSON, so entries are checked rather than
7
+ * trusted. Hand-written rather than a zod schema: this module is on the
8
+ * widget's critical path and zod costs 14KB gzipped to validate three strings
9
+ * and a number.
10
+ */
11
+ var parseHardcodedProvider = (value) => {
12
+ const entry = value;
13
+ if (!isProviderRecord(value) || typeof entry.address !== "string" || typeof entry.url !== "string" || typeof entry.datasetId !== "string" || entry.weight !== void 0 && typeof entry.weight !== "number") throw new Error(`Malformed provider entry in the provider list: ${JSON.stringify(value)}`);
14
+ return {
15
+ address: entry.address,
16
+ url: entry.url,
17
+ datasetId: entry.datasetId,
18
+ weight: Math.max(1, Math.min(100, Math.round(entry.weight ?? 1)))
19
+ };
20
+ };
13
21
  var IP_MODE_KEYS = ["ipv4", "ipv6"];
14
22
  var isProviderRecord = (value) => typeof value === "object" && value !== null;
15
23
  var convertHostedProvider = (provider, ipMode) => {
16
24
  const source = ipMode && isProviderRecord(provider[ipMode]) ? provider[ipMode] : provider;
17
- return Object.entries(source).filter(([key]) => !IP_MODE_KEYS.includes(key)).map(([, value]) => HardcodedProviderSchema.parse(value)).sort((a, b) => a.url.localeCompare(b.url));
25
+ return Object.entries(source).filter(([key]) => !IP_MODE_KEYS.includes(key)).map(([, value]) => parseHardcodedProvider(value)).sort((a, b) => a.url.localeCompare(b.url));
18
26
  };
19
27
  var stripIpModeLabel = (hostname) => hostname.replace(/^ipv[46]\./, "");
20
28
  var getProviderHostname = (provider) => stripIpModeLabel(new URL(provider.url).hostname);
29
+ var SELF_HOSTED_ADDRESS = "self-hosted";
30
+ var SELF_HOSTED_DATASET_ID = "";
31
+ var stripTrailingSlash = (url) => url.replace(/\/$/, "");
32
+ var providersFromUrlList = (raw) => raw.split(",").map((url) => stripTrailingSlash(url.trim())).filter((url) => url.length > 0).map((url) => ({
33
+ address: SELF_HOSTED_ADDRESS,
34
+ url,
35
+ datasetId: SELF_HOSTED_DATASET_ID,
36
+ weight: 1
37
+ }));
38
+ /**
39
+ * Parses `PROSOPO_PROVIDER_LIST` into providers, or returns empty when unset.
40
+ *
41
+ * Two accepted forms:
42
+ *
43
+ * - The same JSON the hosted provider list serves, so weights and the
44
+ * `ipv4` / `ipv6` sub-lists behave identically. See the README.
45
+ * - One or more bare URLs, comma-separated, for a deployment that has
46
+ * nothing to say about weights or addresses.
47
+ *
48
+ * Trailing slashes are stripped throughout: a token embeds the provider URL
49
+ * it was minted against and `@prosopo/server` finds the issuer by exact
50
+ * string match, so `https://host/` and `https://host` must not disagree.
51
+ *
52
+ * A malformed override returns empty rather than throwing, falling back to
53
+ * normal discovery. Throwing would take every captcha on the page down over
54
+ * what is a deployment-time typo.
55
+ */
56
+ var getProviderListOverride = (raw = require_providerUrlOverride.readProviderListOverride(), ipMode) => {
57
+ const trimmed = raw?.trim();
58
+ if (!trimmed) return [];
59
+ if (!trimmed.startsWith("{")) return providersFromUrlList(trimmed);
60
+ try {
61
+ return convertHostedProvider(JSON.parse(trimmed), ipMode).map((provider) => ({
62
+ ...provider,
63
+ url: stripTrailingSlash(provider.url)
64
+ }));
65
+ } catch {
66
+ return [];
67
+ }
68
+ };
21
69
  var getLoadBalancerUrl = (environment) => {
22
70
  if (environment === "production") return "https://provider-list.prosopo.io/";
23
71
  if (environment === "staging") return "https://provider-list.prosopo.io/staging.json";
24
72
  throw new _prosopo_common.ProsopoEnvError("CONFIG.UNKNOWN_ENVIRONMENT", { context: { environment } });
25
73
  };
26
74
  var loadBalancer = async (environment, ipMode) => {
75
+ const override = getProviderListOverride(void 0, ipMode);
76
+ if (override.length > 0) return override;
27
77
  if (environment === "development") return [{
28
78
  address: "5EjTA28bKSbFPPyMbUjNtArxyqjwq38r1BapVmLZShaqEedV",
29
79
  url: require_developmentProviderUrl.getDevelopmentProviderUrl(),
@@ -39,5 +89,6 @@ var loadBalancer = async (environment, ipMode) => {
39
89
  exports.convertHostedProvider = convertHostedProvider;
40
90
  exports.getLoadBalancerUrl = getLoadBalancerUrl;
41
91
  exports.getProviderHostname = getProviderHostname;
92
+ exports.getProviderListOverride = getProviderListOverride;
42
93
  exports.loadBalancer = loadBalancer;
43
94
  exports.stripIpModeLabel = stripIpModeLabel;
@@ -1,5 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_developmentProviderUrl = require("./developmentProviderUrl.cjs");
3
+ const require_providerUrlOverride = require("./providerUrlOverride.cjs");
3
4
  const require_balancer = require("./balancer.cjs");
4
5
  const require_providers = require("./providers.cjs");
5
6
  exports.DEFAULT_DEVELOPMENT_PROVIDER_URL = require_developmentProviderUrl.DEFAULT_DEVELOPMENT_PROVIDER_URL;
@@ -11,9 +12,11 @@ exports.convertHostedProvider = require_balancer.convertHostedProvider;
11
12
  exports.getDevelopmentProviderUrl = require_developmentProviderUrl.getDevelopmentProviderUrl;
12
13
  exports.getLoadBalancerUrl = require_balancer.getLoadBalancerUrl;
13
14
  exports.getProviderHostname = require_balancer.getProviderHostname;
15
+ exports.getProviderListOverride = require_balancer.getProviderListOverride;
14
16
  exports.getProviders = require_providers.getProviders;
15
17
  exports.getRandomActiveProvider = require_providers.getRandomActiveProvider;
16
18
  exports.getRandomProviderFromList = require_providers.getRandomProviderFromList;
17
19
  exports.loadBalancer = require_balancer.loadBalancer;
18
20
  exports.readDevelopmentProviderUrlOverride = require_developmentProviderUrl.readDevelopmentProviderUrlOverride;
21
+ exports.readProviderListOverride = require_providerUrlOverride.readProviderListOverride;
19
22
  exports.stripIpModeLabel = require_balancer.stripIpModeLabel;
@@ -0,0 +1,33 @@
1
+ //#region src/providerUrlOverride.ts
2
+ /**
3
+ * Supplies the provider list directly instead of fetching Prosopo's.
4
+ *
5
+ * A self-hosted deployment has its own providers and no hosted list to
6
+ * discover them from. Setting this replaces both halves of discovery — the
7
+ * endpoint the widget calls and the list a verifier matches a token against —
8
+ * with whatever it names. See `getProviderListOverride` in `balancer.ts` for
9
+ * the accepted forms.
10
+ *
11
+ * Distinct from `PROSOPO_PROVIDER_URL_DEVELOPMENT`, which only applies when
12
+ * the environment is `development`. That coupling forced self-hosters to ship
13
+ * a development build of the widget purely to redirect it; this override is
14
+ * environment-independent, so `production` stays correct everywhere.
15
+ *
16
+ * Kept in its own module, holding nothing but the read, so that parsing can
17
+ * live next to `convertHostedProvider` in `balancer.ts` without the two
18
+ * modules importing each other. A cycle here would be bundled into the widget
19
+ * and this package has already lost a release to one.
20
+ *
21
+ * Written as a static `process.env.X` access for the same reason as the
22
+ * development override: frontend bundlers substitute that exact expression
23
+ * for a literal, which a computed lookup would not match.
24
+ */
25
+ var readProviderListOverride = () => {
26
+ try {
27
+ return process.env.PROSOPO_PROVIDER_LIST;
28
+ } catch {
29
+ return;
30
+ }
31
+ };
32
+ //#endregion
33
+ exports.readProviderListOverride = readProviderListOverride;
@@ -47,6 +47,11 @@ var resolveBaseUrl = (env) => {
47
47
  return endpoints[env] ?? endpoints.development;
48
48
  };
49
49
  var resolvePinnedUrl = async (env, ipMode) => {
50
+ const override = require_balancer.getProviderListOverride(void 0, ipMode);
51
+ if (override.length > 0) {
52
+ const chosen = pickWeightedProvider(override, Math.random);
53
+ if (chosen) return chosen.url;
54
+ }
50
55
  const base = applyIpModeToUrl(resolveBaseUrl(env), ipMode);
51
56
  if (env === "development") return base;
52
57
  const key = cacheKey(env, ipMode);
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from "./providers.js";
2
2
  export * from "./balancer.js";
3
3
  export * from "./developmentProviderUrl.js";
4
+ export * from "./providerUrlOverride.js";
4
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAaA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,6BAA6B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAaA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,0BAA0B,CAAC"}
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import "./_virtual/_rolldown/runtime.js";
2
2
  import { DEFAULT_DEVELOPMENT_PROVIDER_URL, getDevelopmentProviderUrl, readDevelopmentProviderUrlOverride } from "./developmentProviderUrl.js";
3
- import { convertHostedProvider, getLoadBalancerUrl, getProviderHostname, loadBalancer, stripIpModeLabel } from "./balancer.js";
3
+ import { readProviderListOverride } from "./providerUrlOverride.js";
4
+ import { convertHostedProvider, getLoadBalancerUrl, getProviderHostname, getProviderListOverride, loadBalancer, stripIpModeLabel } from "./balancer.js";
4
5
  import { _resetHealthzRetryPolicy, _resetPinCache, _resetProviderListCache, _setHealthzRetryPolicy, getProviders, getRandomActiveProvider, getRandomProviderFromList } from "./providers.js";
5
- export { DEFAULT_DEVELOPMENT_PROVIDER_URL, _resetHealthzRetryPolicy, _resetPinCache, _resetProviderListCache, _setHealthzRetryPolicy, convertHostedProvider, getDevelopmentProviderUrl, getLoadBalancerUrl, getProviderHostname, getProviders, getRandomActiveProvider, getRandomProviderFromList, loadBalancer, readDevelopmentProviderUrlOverride, stripIpModeLabel };
6
+ export { DEFAULT_DEVELOPMENT_PROVIDER_URL, _resetHealthzRetryPolicy, _resetPinCache, _resetProviderListCache, _setHealthzRetryPolicy, convertHostedProvider, getDevelopmentProviderUrl, getLoadBalancerUrl, getProviderHostname, getProviderListOverride, getProviders, getRandomActiveProvider, getRandomProviderFromList, loadBalancer, readDevelopmentProviderUrlOverride, readProviderListOverride, stripIpModeLabel };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAaA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,6BAA6B,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAaA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,0BAA0B,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare const readProviderListOverride: () => string | undefined;
2
+ //# sourceMappingURL=providerUrlOverride.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"providerUrlOverride.d.ts","sourceRoot":"","sources":["../src/providerUrlOverride.ts"],"names":[],"mappings":"AAqCA,eAAO,MAAM,wBAAwB,QAAO,MAAM,GAAG,SAMpD,CAAC"}