@prosopo/provider 5.12.2 → 5.12.3

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 (47) hide show
  1. package/.turbo/turbo-build$colon$cjs.log +8 -5
  2. package/.turbo/turbo-build$colon$tsc.log +27 -27
  3. package/.turbo/turbo-build.log +9 -6
  4. package/CHANGELOG.md +15 -0
  5. package/dist/api/captcha/assetSeal.d.ts +15 -0
  6. package/dist/api/captcha/assetSeal.d.ts.map +1 -0
  7. package/dist/api/captcha/assetSeal.js +22 -0
  8. package/dist/api/captcha/assetSeal.js.map +1 -0
  9. package/dist/api/captcha/assetSealFormat.d.ts +23 -0
  10. package/dist/api/captcha/assetSealFormat.d.ts.map +1 -0
  11. package/dist/api/captcha/assetSealFormat.js +87 -0
  12. package/dist/api/captcha/assetSealFormat.js.map +1 -0
  13. package/dist/api/captcha/getImageCaptchaChallenge.d.ts.map +1 -1
  14. package/dist/api/captcha/getImageCaptchaChallenge.js +2 -1
  15. package/dist/api/captcha/getImageCaptchaChallenge.js.map +1 -1
  16. package/dist/api/captcha/sealedAssetsResolver.d.ts +22 -0
  17. package/dist/api/captcha/sealedAssetsResolver.d.ts.map +1 -0
  18. package/dist/api/captcha/sealedAssetsResolver.js +69 -0
  19. package/dist/api/captcha/sealedAssetsResolver.js.map +1 -0
  20. package/dist/api/captcha/signedAssetsResolver.d.ts +2 -1
  21. package/dist/api/captcha/signedAssetsResolver.d.ts.map +1 -1
  22. package/dist/api/captcha/signedAssetsResolver.js +6 -5
  23. package/dist/api/captcha/signedAssetsResolver.js.map +1 -1
  24. package/dist/cjs/api/captcha/assetSeal.cjs +22 -0
  25. package/dist/cjs/api/captcha/assetSealFormat.cjs +95 -0
  26. package/dist/cjs/api/captcha/getImageCaptchaChallenge.cjs +2 -1
  27. package/dist/cjs/api/captcha/sealedAssetsResolver.cjs +70 -0
  28. package/dist/cjs/api/captcha/signedAssetsResolver.cjs +6 -4
  29. package/dist/edge/assetSealHandler.d.ts +9 -0
  30. package/dist/edge/assetSealHandler.d.ts.map +1 -0
  31. package/dist/edge/assetSealHandler.js +74 -0
  32. package/dist/edge/assetSealHandler.js.map +1 -0
  33. package/dist/tests/unit/api/captcha/assetSeal.unit.test.d.ts +2 -0
  34. package/dist/tests/unit/api/captcha/assetSeal.unit.test.d.ts.map +1 -0
  35. package/dist/tests/unit/api/captcha/assetSeal.unit.test.js +132 -0
  36. package/dist/tests/unit/api/captcha/assetSeal.unit.test.js.map +1 -0
  37. package/dist/tests/unit/api/captcha/sealedAssetsResolver.unit.test.d.ts +2 -0
  38. package/dist/tests/unit/api/captcha/sealedAssetsResolver.unit.test.d.ts.map +1 -0
  39. package/dist/tests/unit/api/captcha/sealedAssetsResolver.unit.test.js +151 -0
  40. package/dist/tests/unit/api/captcha/sealedAssetsResolver.unit.test.js.map +1 -0
  41. package/dist/tests/unit/edge/assetSealHandler.unit.test.d.ts +2 -0
  42. package/dist/tests/unit/edge/assetSealHandler.unit.test.d.ts.map +1 -0
  43. package/dist/tests/unit/edge/assetSealHandler.unit.test.js +145 -0
  44. package/dist/tests/unit/edge/assetSealHandler.unit.test.js.map +1 -0
  45. package/edge/entry.ts +62 -0
  46. package/package.json +4 -2
  47. package/scripts/buildEdgeScript.mjs +31 -0
@@ -0,0 +1,151 @@
1
+ import { afterEach, describe, expect, test } from "vitest";
2
+ import { unsealAssetPath } from "../../../../api/captcha/assetSeal.js";
3
+ import { SEAL_KEY_BYTES, toBase64Url, } from "../../../../api/captcha/assetSealFormat.js";
4
+ import { DEFAULT_ASSET_SEAL_TTL_SECONDS, SealedAssetsResolver, getSealedAssetsResolver, } from "../../../../api/captcha/sealedAssetsResolver.js";
5
+ const KEY = new Uint8Array(SEAL_KEY_BYTES).fill(3);
6
+ const KEY_ID = 4;
7
+ const NOW_MS = 1_700_000_000_000;
8
+ const ORIGIN = "https://prosopoimages.b-cdn.net";
9
+ const PATH = "/v5_dataset_flat/images/abc.webp";
10
+ const IMAGE = `${ORIGIN}${PATH}`;
11
+ const resolver = (options = {}) => new SealedAssetsResolver({
12
+ key: KEY,
13
+ keyId: KEY_ID,
14
+ now: () => NOW_MS,
15
+ ...options,
16
+ });
17
+ const opened = (sealedUrl, nowSeconds = NOW_MS / 1000) => {
18
+ const segments = new URL(sealedUrl).pathname.split("/");
19
+ const blob = segments[segments.length - 1]?.replace(/\.[a-z0-9]+$/, "") ?? "";
20
+ const path = unsealAssetPath({
21
+ blob,
22
+ keys: new Map([[KEY_ID, KEY]]),
23
+ nowSeconds,
24
+ });
25
+ if (!path)
26
+ throw new Error("expected the sealed URL to open");
27
+ return path;
28
+ };
29
+ const withEnv = (values, run) => {
30
+ const saved = new Map();
31
+ for (const [name, value] of Object.entries(values)) {
32
+ saved.set(name, process.env[name]);
33
+ if (undefined === value) {
34
+ Reflect.deleteProperty(process.env, name);
35
+ }
36
+ else {
37
+ process.env[name] = value;
38
+ }
39
+ }
40
+ try {
41
+ run();
42
+ }
43
+ finally {
44
+ for (const [name, value] of saved) {
45
+ if (undefined === value) {
46
+ Reflect.deleteProperty(process.env, name);
47
+ }
48
+ else {
49
+ process.env[name] = value;
50
+ }
51
+ }
52
+ }
53
+ };
54
+ describe("SealedAssetsResolver", () => {
55
+ test("keeps the image on the same zone", () => {
56
+ const url = new URL(resolver().resolveAsset(IMAGE).getURL());
57
+ expect(url.origin).toBe(ORIGIN);
58
+ });
59
+ test("replaces the path with an opaque blob under the prefix", () => {
60
+ const url = new URL(resolver().resolveAsset(IMAGE).getURL());
61
+ expect(url.pathname).toMatch(/^\/s\/[A-Za-z0-9_-]+\.webp$/);
62
+ expect(url.pathname).not.toContain("abc");
63
+ expect(url.pathname).not.toContain("v5_dataset_flat");
64
+ });
65
+ test("the blob opens to the original path", () => {
66
+ expect(opened(resolver().resolveAsset(IMAGE).getURL())).toBe(PATH);
67
+ });
68
+ test("gives the same image a different URL every time", () => {
69
+ const instance = resolver();
70
+ const urls = new Set(Array.from({ length: 20 }, () => instance.resolveAsset(IMAGE).getURL()));
71
+ expect(urls.size).toBe(20);
72
+ });
73
+ test("expires the URL after the ttl", () => {
74
+ const url = resolver({ ttlSeconds: 120 }).resolveAsset(IMAGE).getURL();
75
+ expect(opened(url, NOW_MS / 1000 + 119)).toBe(PATH);
76
+ expect(() => opened(url, NOW_MS / 1000 + 120)).toThrow();
77
+ });
78
+ test("defaults to the same window as a signed URL", () => {
79
+ const url = resolver().resolveAsset(IMAGE).getURL();
80
+ expect(opened(url, NOW_MS / 1000 + DEFAULT_ASSET_SEAL_TTL_SECONDS - 1)).toBe(PATH);
81
+ expect(() => opened(url, NOW_MS / 1000 + DEFAULT_ASSET_SEAL_TTL_SECONDS)).toThrow();
82
+ });
83
+ test("honours a configured url prefix", () => {
84
+ const url = new URL(resolver({ urlPrefix: "img" }).resolveAsset(IMAGE).getURL());
85
+ expect(url.pathname.startsWith("/img/")).toBe(true);
86
+ });
87
+ test("carries the file extension over", () => {
88
+ for (const [path, expected] of [
89
+ ["/a/b.webp", ".webp"],
90
+ ["/a/b.jpeg", ".jpeg"],
91
+ ["/a/b", ""],
92
+ ["/a/b.", ""],
93
+ ]) {
94
+ const url = resolver().resolveAsset(`${ORIGIN}${path}`).getURL();
95
+ expect(url.endsWith(`${expected}`)).toBe(true);
96
+ }
97
+ });
98
+ test("preserves the original URI on the asset", () => {
99
+ expect(resolver().resolveAsset(IMAGE).URI).toBe(IMAGE);
100
+ });
101
+ test("passes through anything that is not an http url", () => {
102
+ for (const uri of ["/local/fixture.webp", "not a url", "file:///a.webp"]) {
103
+ expect(resolver().resolveAsset(uri).getURL()).toBe(uri);
104
+ }
105
+ });
106
+ test("refuses a url that already has a query string", () => {
107
+ expect(() => resolver().resolveAsset(`${IMAGE}?v=2`)).toThrow(/query string/);
108
+ });
109
+ });
110
+ describe("getSealedAssetsResolver", () => {
111
+ afterEach(() => {
112
+ Reflect.deleteProperty(process.env, "PROSOPO_ASSET_SEAL_KEYS");
113
+ Reflect.deleteProperty(process.env, "PROSOPO_ASSET_SEAL_ACTIVE_KEY_ID");
114
+ });
115
+ test("is off until keys are configured", () => {
116
+ withEnv({ PROSOPO_ASSET_SEAL_KEYS: undefined }, () => {
117
+ expect(getSealedAssetsResolver()).toBeUndefined();
118
+ });
119
+ });
120
+ test("seals with the highest key id by default", () => {
121
+ withEnv({
122
+ PROSOPO_ASSET_SEAL_KEYS: `1:${toBase64Url(new Uint8Array(SEAL_KEY_BYTES).fill(1))},${KEY_ID}:${toBase64Url(KEY)}`,
123
+ PROSOPO_ASSET_SEAL_ACTIVE_KEY_ID: undefined,
124
+ }, () => {
125
+ const url = getSealedAssetsResolver()?.resolveAsset(IMAGE).getURL();
126
+ if (!url)
127
+ throw new Error("expected a resolver");
128
+ expect(opened(url, Math.floor(Date.now() / 1000))).toBe(PATH);
129
+ });
130
+ });
131
+ test("seals with the key id it is told to", () => {
132
+ withEnv({
133
+ PROSOPO_ASSET_SEAL_KEYS: `${KEY_ID}:${toBase64Url(KEY)},9:${toBase64Url(new Uint8Array(SEAL_KEY_BYTES).fill(9))}`,
134
+ PROSOPO_ASSET_SEAL_ACTIVE_KEY_ID: String(KEY_ID),
135
+ }, () => {
136
+ const url = getSealedAssetsResolver()?.resolveAsset(IMAGE).getURL();
137
+ if (!url)
138
+ throw new Error("expected a resolver");
139
+ expect(opened(url, Math.floor(Date.now() / 1000))).toBe(PATH);
140
+ });
141
+ });
142
+ test("refuses an active key id it does not hold", () => {
143
+ withEnv({
144
+ PROSOPO_ASSET_SEAL_KEYS: `${KEY_ID}:${toBase64Url(KEY)}`,
145
+ PROSOPO_ASSET_SEAL_ACTIVE_KEY_ID: "77",
146
+ }, () => {
147
+ expect(() => getSealedAssetsResolver()).toThrow(/77/);
148
+ });
149
+ });
150
+ });
151
+ //# sourceMappingURL=sealedAssetsResolver.unit.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sealedAssetsResolver.unit.test.js","sourceRoot":"","sources":["../../../../../src/tests/unit/api/captcha/sealedAssetsResolver.unit.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=assetSealHandler.unit.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assetSealHandler.unit.test.d.ts","sourceRoot":"","sources":["../../../../src/tests/unit/edge/assetSealHandler.unit.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,145 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import { sealAssetPath } from "../../../api/captcha/assetSeal.js";
3
+ import { SEAL_KEY_BYTES } from "../../../api/captcha/assetSealFormat.js";
4
+ import { SealedAssetsResolver, getSealedAssetsResolver, } from "../../../api/captcha/sealedAssetsResolver.js";
5
+ import { createAssetSealHandler, } from "../../../edge/assetSealHandler.js";
6
+ const KEY = new Uint8Array(SEAL_KEY_BYTES).fill(5);
7
+ const OTHER_KEY = new Uint8Array(SEAL_KEY_BYTES).fill(6);
8
+ const KEY_ID = 2;
9
+ const NOW_MS = 1_700_000_000_000;
10
+ const NOW_SECONDS = NOW_MS / 1000;
11
+ const ZONE = "https://prosopoimages.b-cdn.net";
12
+ const PATH = "/v5_dataset_flat/images/abc.webp";
13
+ const handler = (options = {}) => createAssetSealHandler({
14
+ keys: options.keys ?? new Map([[KEY_ID, KEY]]),
15
+ urlPrefix: options.urlPrefix ?? "s",
16
+ now: () => options.nowMs ?? NOW_MS,
17
+ ...(options.requiredPathPrefix !== undefined
18
+ ? { requiredPathPrefix: options.requiredPathPrefix }
19
+ : {}),
20
+ });
21
+ const sealedUrl = (options = {}) => `${ZONE}/s/${sealAssetPath({
22
+ path: options.path ?? PATH,
23
+ key: options.key ?? KEY,
24
+ keyId: options.keyId ?? KEY_ID,
25
+ expiresAtSeconds: NOW_SECONDS + (options.ttl ?? 300),
26
+ })}.webp`;
27
+ const rewritten = async (url, handle = handler()) => handle(new Request(url));
28
+ describe("a sealed request", () => {
29
+ test("is rewritten onto the canonical path", async () => {
30
+ const result = await rewritten(sealedUrl());
31
+ expect(result).toBeInstanceOf(Request);
32
+ expect(new URL(result.url).pathname).toBe(PATH);
33
+ });
34
+ test("keeps the zone it arrived on", async () => {
35
+ const result = await rewritten(sealedUrl());
36
+ expect(new URL(result.url).origin).toBe(ZONE);
37
+ });
38
+ test("carries the method and headers over", async () => {
39
+ const result = await handler()(new Request(sealedUrl(), {
40
+ method: "GET",
41
+ headers: { accept: "image/webp", "x-forwarded-for": "203.0.113.7" },
42
+ }));
43
+ expect(result.method).toBe("GET");
44
+ expect(result.headers.get("accept")).toBe("image/webp");
45
+ expect(result.headers.get("x-forwarded-for")).toBe("203.0.113.7");
46
+ });
47
+ test("drops a query string someone else added", async () => {
48
+ const result = await rewritten(`${sealedUrl()}?width=99`);
49
+ expect(new URL(result.url).search).toBe("");
50
+ });
51
+ test("rewrites two different seals of one image onto the same path", async () => {
52
+ const handle = handler();
53
+ const first = await rewritten(sealedUrl(), handle);
54
+ const second = await rewritten(sealedUrl(), handle);
55
+ expect(new URL(first.url).pathname).toBe(new URL(second.url).pathname);
56
+ });
57
+ });
58
+ describe("what the edge refuses", () => {
59
+ const refused = async (url, handle) => {
60
+ const result = await rewritten(url, handle);
61
+ expect(result).toBeInstanceOf(Response);
62
+ return result;
63
+ };
64
+ test("an expired seal", async () => {
65
+ const url = sealedUrl({ ttl: 60 });
66
+ const response = await refused(url, handler({ nowMs: NOW_MS + 60_000 }));
67
+ expect(response.status).toBe(404);
68
+ });
69
+ test("a seal made with a key the edge does not hold", async () => {
70
+ const response = await refused(sealedUrl({ key: OTHER_KEY, keyId: 7 }));
71
+ expect(response.status).toBe(404);
72
+ });
73
+ test("a tampered blob", async () => {
74
+ const url = sealedUrl();
75
+ const flipped = url.replace(/(.)(\.webp)$/, (_, last, ext) => `${"A" === last ? "B" : "A"}${ext}`);
76
+ const response = await refused(flipped);
77
+ expect(response.status).toBe(404);
78
+ });
79
+ test("a path outside the required prefix", async () => {
80
+ const response = await refused(sealedUrl({ path: "/elsewhere/a.webp" }), handler({ requiredPathPrefix: "/v5_dataset_flat/" }));
81
+ expect(response.status).toBe(404);
82
+ });
83
+ test("noise under the prefix", async () => {
84
+ for (const blob of ["", "x", "not-a-seal", "%20"]) {
85
+ expect((await refused(`${ZONE}/s/${blob}`)).status).toBe(404);
86
+ }
87
+ });
88
+ test("and never lets the refusal be cached", async () => {
89
+ const response = await refused(sealedUrl({ key: OTHER_KEY, keyId: 7 }));
90
+ expect(response.headers.get("cache-control")).toBe("no-store");
91
+ });
92
+ });
93
+ describe("what the edge leaves alone", () => {
94
+ test("a request that is not under the prefix", async () => {
95
+ const result = await rewritten(`${ZONE}/health`);
96
+ expect(result).toBeInstanceOf(Request);
97
+ expect(new URL(result.url).pathname).toBe("/health");
98
+ });
99
+ test("an already-rewritten request, so running twice is safe", async () => {
100
+ const handle = handler();
101
+ const once = await rewritten(sealedUrl(), handle);
102
+ const twice = await handle(once);
103
+ expect(twice).toBeInstanceOf(Request);
104
+ expect(new URL(twice.url).pathname).toBe(PATH);
105
+ });
106
+ test("a request under a different prefix from the one configured", async () => {
107
+ const result = await rewritten(sealedUrl(), handler({ urlPrefix: "img" }));
108
+ expect(result).toBeInstanceOf(Request);
109
+ });
110
+ });
111
+ describe("the provider and the edge agree", () => {
112
+ test("what node seals, webcrypto opens", async () => {
113
+ const url = new SealedAssetsResolver({
114
+ key: KEY,
115
+ keyId: KEY_ID,
116
+ now: () => NOW_MS,
117
+ })
118
+ .resolveAsset(`${ZONE}${PATH}`)
119
+ .getURL();
120
+ const result = await rewritten(url);
121
+ expect(new URL(result.url).pathname).toBe(PATH);
122
+ });
123
+ test("end to end, from the env the provider reads", async () => {
124
+ const saved = process.env.PROSOPO_ASSET_SEAL_KEYS;
125
+ process.env.PROSOPO_ASSET_SEAL_KEYS = `${KEY_ID}:${Buffer.from(KEY).toString("base64")}`;
126
+ try {
127
+ const url = getSealedAssetsResolver()
128
+ ?.resolveAsset(`${ZONE}${PATH}`)
129
+ .getURL();
130
+ if (!url)
131
+ throw new Error("expected a resolver");
132
+ const result = await rewritten(url, handler({ nowMs: Date.now() }));
133
+ expect(new URL(result.url).pathname).toBe(PATH);
134
+ }
135
+ finally {
136
+ if (undefined === saved) {
137
+ Reflect.deleteProperty(process.env, "PROSOPO_ASSET_SEAL_KEYS");
138
+ }
139
+ else {
140
+ process.env.PROSOPO_ASSET_SEAL_KEYS = saved;
141
+ }
142
+ }
143
+ });
144
+ });
145
+ //# sourceMappingURL=assetSealHandler.unit.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assetSealHandler.unit.test.js","sourceRoot":"","sources":["../../../../src/tests/unit/edge/assetSealHandler.unit.test.ts"],"names":[],"mappings":"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"}
package/edge/entry.ts ADDED
@@ -0,0 +1,62 @@
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
+ // @ts-expect-error resolved by the bunny runtime, not by tsc or node
16
+ import * as BunnySDK from "https://esm.sh/@bunny.net/edgescript-sdk@0.13.0-rc.0";
17
+ import { parseSealKeys } from "../src/api/captcha/assetSealFormat.js";
18
+ import {
19
+ type AssetSealHandler,
20
+ createAssetSealHandler,
21
+ } from "../src/edge/assetSealHandler.js";
22
+
23
+ interface PullZoneHandler {
24
+ onClientRequest: (
25
+ middleware: (ctx: { request: Request }) => Promise<Request | Response>,
26
+ ) => PullZoneHandler;
27
+ onOriginRequest: (
28
+ middleware: (ctx: { request: Request }) => Promise<Request | Response>,
29
+ ) => PullZoneHandler;
30
+ }
31
+
32
+ interface EdgeSdk {
33
+ net: {
34
+ http: { servePullZone: (options: { url: string }) => PullZoneHandler };
35
+ };
36
+ }
37
+
38
+ const sdk: EdgeSdk = BunnySDK as EdgeSdk;
39
+
40
+ const required = (name: string): string => {
41
+ const value = process.env[name];
42
+ if (!value) {
43
+ throw new Error(`${name} is not set on this edge script`);
44
+ }
45
+ return value;
46
+ };
47
+
48
+ const handler: AssetSealHandler = createAssetSealHandler({
49
+ keys: parseSealKeys(required("PROSOPO_ASSET_SEAL_KEYS")),
50
+ urlPrefix: process.env.PROSOPO_ASSET_SEAL_URL_PREFIX || "s",
51
+ ...(process.env.PROSOPO_ASSET_SEAL_PATH_PREFIX
52
+ ? { requiredPathPrefix: process.env.PROSOPO_ASSET_SEAL_PATH_PREFIX }
53
+ : {}),
54
+ });
55
+
56
+ const pullZone = sdk.net.http.servePullZone({
57
+ url: process.env.PROSOPO_ASSET_ORIGIN_URL || "https://localhost",
58
+ });
59
+
60
+ pullZone
61
+ .onClientRequest((ctx: { request: Request }) => handler(ctx.request))
62
+ .onOriginRequest((ctx: { request: Request }) => handler(ctx.request));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prosopo/provider",
3
- "version": "5.12.2",
3
+ "version": "5.12.3",
4
4
  "author": "PROSOPO LIMITED <info@prosopo.io>",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -14,6 +14,7 @@
14
14
  "build:cross-env": "vite build --config vite.esm.config.ts",
15
15
  "build:tsc": "tsc --build --verbose",
16
16
  "build:cjs": "NODE_ENV=${NODE_ENV:-development}; vite build --config vite.cjs.config.ts --mode $NODE_ENV",
17
+ "build:edge": "node ./scripts/buildEdgeScript.mjs",
17
18
  "typecheck": "tsc --project tsconfig.types.json",
18
19
  "test:unit": "NODE_ENV=${NODE_ENV:-test}; TEST_TYPE=unit npx vitest run --config ./vite.test.config.ts",
19
20
  "test:integration": "NODE_ENV=${NODE_ENV:-test}; TEST_TYPE=integration DECISION_MACHINE_ARTIFACT_CACHE_TTL_MS=1 npx vitest run --config ./vite.threads.test.config.ts",
@@ -29,7 +30,7 @@
29
30
  "@prosopo/captcha-severity": "1.1.1",
30
31
  "@prosopo/common": "3.1.59",
31
32
  "@prosopo/database": "4.0.39",
32
- "@prosopo/datasets": "3.1.88",
33
+ "@prosopo/datasets": "3.1.89",
33
34
  "@prosopo/env": "3.6.62",
34
35
  "@prosopo/ipinfo": "0.4.8",
35
36
  "@prosopo/keyring": "2.9.95",
@@ -70,6 +71,7 @@
70
71
  "cors": "2.8.6",
71
72
  "del-cli": "6.0.0",
72
73
  "dotenv": "16.4.5",
74
+ "esbuild": "0.28.2",
73
75
  "npm-run-all": "4.1.5",
74
76
  "testcontainers": "12.1.0",
75
77
  "tslib": "2.7.0",
@@ -0,0 +1,31 @@
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
+ import { build } from "esbuild";
16
+
17
+ const OUTPUT = "dist/edge/bunny-asset-seal.js";
18
+
19
+ await build({
20
+ entryPoints: ["edge/entry.ts"],
21
+ outfile: OUTPUT,
22
+ bundle: true,
23
+ format: "esm",
24
+ platform: "neutral",
25
+ target: "es2022",
26
+ external: ["https://*"],
27
+ legalComments: "inline",
28
+ logLevel: "info",
29
+ });
30
+
31
+ console.log(`edge script written to ${OUTPUT}`);