@prosopo/provider 4.15.16 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build$colon$cjs.log +103 -102
- package/.turbo/turbo-build$colon$tsc.log +24 -24
- package/.turbo/turbo-build.log +103 -102
- package/CHANGELOG.md +108 -0
- package/dist/api/admin/apiAdminRoutesProvider.d.ts.map +1 -1
- package/dist/api/admin/apiAdminRoutesProvider.js +3 -5
- package/dist/api/admin/apiAdminRoutesProvider.js.map +1 -1
- package/dist/api/admin/apiReplaceDetectorPoolEndpoint.d.ts +11 -0
- package/dist/api/admin/apiReplaceDetectorPoolEndpoint.d.ts.map +1 -0
- package/dist/api/admin/apiReplaceDetectorPoolEndpoint.js +56 -0
- package/dist/api/admin/apiReplaceDetectorPoolEndpoint.js.map +1 -0
- package/dist/api/captcha/assignDetectorBundle.d.ts +6 -0
- package/dist/api/captcha/assignDetectorBundle.d.ts.map +1 -0
- package/dist/api/captcha/assignDetectorBundle.js +51 -0
- package/dist/api/captcha/assignDetectorBundle.js.map +1 -0
- package/dist/api/captcha/assignDetectorBundle.test.d.ts +2 -0
- package/dist/api/captcha/assignDetectorBundle.test.d.ts.map +1 -0
- package/dist/api/captcha/assignDetectorBundle.test.js +71 -0
- package/dist/api/captcha/assignDetectorBundle.test.js.map +1 -0
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/constants.d.ts +1 -0
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/constants.d.ts.map +1 -1
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/constants.js.map +1 -1
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/decisionMachine.d.ts.map +1 -1
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/decisionMachine.js +21 -2
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/decisionMachine.js.map +1 -1
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/decryptSimdReadings.d.ts +1 -1
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/decryptSimdReadings.d.ts.map +1 -1
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/decryptSimdReadings.js +3 -3
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/decryptSimdReadings.js.map +1 -1
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/handler.d.ts.map +1 -1
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/handler.js +13 -7
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/handler.js.map +1 -1
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.d.ts +3 -0
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.d.ts.map +1 -1
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.js +52 -18
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.js.map +1 -1
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.powFallback.test.d.ts +2 -0
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.powFallback.test.d.ts.map +1 -0
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.powFallback.test.js +86 -0
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.powFallback.test.js.map +1 -0
- package/dist/api/captcha/getImageCaptchaChallenge.d.ts.map +1 -1
- package/dist/api/captcha/getImageCaptchaChallenge.js +2 -1
- package/dist/api/captcha/getImageCaptchaChallenge.js.map +1 -1
- package/dist/api/captcha/getPoWCaptchaChallenge.d.ts.map +1 -1
- package/dist/api/captcha/getPoWCaptchaChallenge.js +2 -1
- package/dist/api/captcha/getPoWCaptchaChallenge.js.map +1 -1
- package/dist/api/captcha/getPuzzleCaptchaChallenge.d.ts.map +1 -1
- package/dist/api/captcha/getPuzzleCaptchaChallenge.js +2 -1
- package/dist/api/captcha/getPuzzleCaptchaChallenge.js.map +1 -1
- package/dist/api/captcha/submitPoWCaptchaSolution.d.ts.map +1 -1
- package/dist/api/captcha/submitPoWCaptchaSolution.js +1 -1
- package/dist/api/captcha/submitPoWCaptchaSolution.js.map +1 -1
- package/dist/api/captcha.d.ts.map +1 -1
- package/dist/api/captcha.js +6 -0
- package/dist/api/captcha.js.map +1 -1
- package/dist/api/startProviderApi.d.ts.map +1 -1
- package/dist/api/startProviderApi.js +21 -3
- package/dist/api/startProviderApi.js.map +1 -1
- package/dist/cjs/api/admin/apiAdminRoutesProvider.cjs +3 -5
- package/dist/cjs/api/admin/apiReplaceDetectorPoolEndpoint.cjs +56 -0
- package/dist/cjs/api/captcha/assignDetectorBundle.cjs +51 -0
- package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/decisionMachine.cjs +21 -2
- package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/decryptSimdReadings.cjs +3 -3
- package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/handler.cjs +12 -6
- package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.cjs +52 -17
- package/dist/cjs/api/captcha/getImageCaptchaChallenge.cjs +2 -1
- package/dist/cjs/api/captcha/getPoWCaptchaChallenge.cjs +2 -1
- package/dist/cjs/api/captcha/getPuzzleCaptchaChallenge.cjs +2 -1
- package/dist/cjs/api/captcha/submitPoWCaptchaSolution.cjs +1 -1
- package/dist/cjs/api/captcha.cjs +6 -0
- package/dist/cjs/api/startProviderApi.cjs +24 -5
- package/dist/cjs/tasks/captchaManager.cjs +76 -59
- package/dist/cjs/tasks/client/clientTasks.cjs +1 -29
- package/dist/cjs/tasks/detection/bundlePool.cjs +220 -0
- package/dist/cjs/tasks/detection/decodeBehavior.cjs +644 -362
- package/dist/cjs/tasks/detection/decodeBehavior.js +1 -1
- package/dist/cjs/tasks/detection/decodePayload.cjs +956 -617
- package/dist/cjs/tasks/detection/decodePayload.js +1 -1
- package/dist/cjs/tasks/detection/decodeSimd.cjs +723 -411
- package/dist/cjs/tasks/detection/decodeSimd.js +1 -1
- package/dist/cjs/tasks/detection/getBotScore.cjs +2 -2
- package/dist/cjs/tasks/frictionless/frictionlessTasks.cjs +35 -12
- package/dist/cjs/tasks/frictionless/frictionlessTasksUtils.cjs +1 -2
- package/dist/cjs/tasks/imgCaptcha/imgCaptchaTasks.cjs +6 -6
- package/dist/cjs/tasks/powCaptcha/powTasks.cjs +2 -2
- package/dist/cjs/tasks/puzzleCaptcha/puzzleTasks.cjs +2 -2
- package/dist/tasks/captchaManager.d.ts +12 -4
- package/dist/tasks/captchaManager.d.ts.map +1 -1
- package/dist/tasks/captchaManager.js +76 -59
- package/dist/tasks/captchaManager.js.map +1 -1
- package/dist/tasks/client/clientTasks.d.ts +0 -2
- package/dist/tasks/client/clientTasks.d.ts.map +1 -1
- package/dist/tasks/client/clientTasks.js +1 -29
- package/dist/tasks/client/clientTasks.js.map +1 -1
- package/dist/tasks/detection/bundlePool.d.ts +32 -0
- package/dist/tasks/detection/bundlePool.d.ts.map +1 -0
- package/dist/tasks/detection/bundlePool.js +215 -0
- package/dist/tasks/detection/bundlePool.js.map +1 -0
- package/dist/tasks/detection/bundlePool.test.d.ts +2 -0
- package/dist/tasks/detection/bundlePool.test.d.ts.map +1 -0
- package/dist/tasks/detection/bundlePool.test.js +178 -0
- package/dist/tasks/detection/bundlePool.test.js.map +1 -0
- package/dist/tasks/detection/decodeBehavior.d.ts +2 -2
- package/dist/tasks/detection/decodeBehavior.d.ts.map +1 -1
- package/dist/tasks/detection/decodeBehavior.js +1 -1
- package/dist/tasks/detection/decodeBehavior.js.map +1 -1
- package/dist/tasks/detection/decodePayload.d.ts +2 -2
- package/dist/tasks/detection/decodePayload.d.ts.map +1 -1
- package/dist/tasks/detection/decodePayload.js +1 -1
- package/dist/tasks/detection/decodePayload.js.map +1 -1
- package/dist/tasks/detection/decodeSimd.d.ts +2 -2
- package/dist/tasks/detection/decodeSimd.d.ts.map +1 -1
- package/dist/tasks/detection/decodeSimd.js +1 -1
- package/dist/tasks/detection/decodeSimd.js.map +1 -1
- package/dist/tasks/detection/getBotScore.d.ts +1 -1
- package/dist/tasks/detection/getBotScore.d.ts.map +1 -1
- package/dist/tasks/detection/getBotScore.js +3 -3
- package/dist/tasks/detection/getBotScore.js.map +1 -1
- package/dist/tasks/frictionless/frictionlessTasks.d.ts +10 -2
- package/dist/tasks/frictionless/frictionlessTasks.d.ts.map +1 -1
- package/dist/tasks/frictionless/frictionlessTasks.js +35 -12
- package/dist/tasks/frictionless/frictionlessTasks.js.map +1 -1
- package/dist/tasks/frictionless/frictionlessTasksUtils.d.ts +1 -1
- package/dist/tasks/frictionless/frictionlessTasksUtils.d.ts.map +1 -1
- package/dist/tasks/frictionless/frictionlessTasksUtils.js +1 -2
- package/dist/tasks/frictionless/frictionlessTasksUtils.js.map +1 -1
- package/dist/tasks/imgCaptcha/imgCaptchaTasks.d.ts.map +1 -1
- package/dist/tasks/imgCaptcha/imgCaptchaTasks.js +6 -6
- package/dist/tasks/imgCaptcha/imgCaptchaTasks.js.map +1 -1
- package/dist/tasks/powCaptcha/powTasks.d.ts.map +1 -1
- package/dist/tasks/powCaptcha/powTasks.js +2 -2
- package/dist/tasks/powCaptcha/powTasks.js.map +1 -1
- package/dist/tasks/puzzleCaptcha/puzzleTasks.d.ts.map +1 -1
- package/dist/tasks/puzzleCaptcha/puzzleTasks.js +2 -2
- package/dist/tasks/puzzleCaptcha/puzzleTasks.js.map +1 -1
- package/dist/tests/integration/maintenanceModeAdmin.integration.test.js +0 -28
- package/dist/tests/integration/maintenanceModeAdmin.integration.test.js.map +1 -1
- package/dist/tests/unit/api/admin/apiRegisterSiteKeyEndpoint.unit.test.js +1 -0
- package/dist/tests/unit/api/admin/apiRegisterSiteKeyEndpoint.unit.test.js.map +1 -1
- package/dist/tests/unit/api/adminRoutes.unit.test.js +0 -46
- package/dist/tests/unit/api/adminRoutes.unit.test.js.map +1 -1
- package/dist/tests/unit/api/captcha/getFrictionlessCaptchaChallenge/decisionMachine.unit.test.js +51 -0
- package/dist/tests/unit/api/captcha/getFrictionlessCaptchaChallenge/decisionMachine.unit.test.js.map +1 -1
- package/dist/tests/unit/api/captcha/getFrictionlessCaptchaChallenge/decryptSimdReadings.unit.test.js +12 -12
- package/dist/tests/unit/api/captcha/getFrictionlessCaptchaChallenge/decryptSimdReadings.unit.test.js.map +1 -1
- package/dist/tests/unit/api/getFrictionlessCaptchaChallenge.unit.test.js +15 -1
- package/dist/tests/unit/api/getFrictionlessCaptchaChallenge.unit.test.js.map +1 -1
- package/dist/tests/unit/tasks/captchaManager.unit.test.js +14 -30
- package/dist/tests/unit/tasks/captchaManager.unit.test.js.map +1 -1
- package/dist/tests/unit/tasks/detection/getBotScore.unit.test.js +3 -3
- package/dist/tests/unit/tasks/detection/getBotScore.unit.test.js.map +1 -1
- package/dist/tests/unit/tasks/frictionless/decryptPayload.unit.test.js +25 -7
- package/dist/tests/unit/tasks/frictionless/decryptPayload.unit.test.js.map +1 -1
- package/dist/tests/unit/tasks/frictionless/frictionlessTasks.unit.test.js +0 -1
- package/dist/tests/unit/tasks/frictionless/frictionlessTasks.unit.test.js.map +1 -1
- package/dist/tests/unit/tasks/frictionless/frictionlessTasksUtils.unit.test.js +6 -14
- package/dist/tests/unit/tasks/frictionless/frictionlessTasksUtils.unit.test.js.map +1 -1
- package/dist/tests/unit/tasks/puzzleCaptcha/puzzleTasks.unit.test.js +0 -1
- package/dist/tests/unit/tasks/puzzleCaptcha/puzzleTasks.unit.test.js.map +1 -1
- package/dist/tests/unit/tasks/writeQueueIntegration.unit.test.js +0 -2
- package/dist/tests/unit/tasks/writeQueueIntegration.unit.test.js.map +1 -1
- package/fixtures/detector-pool/bundle-0.js +15 -0
- package/fixtures/detector-pool/bundle-0.json +1 -0
- package/package.json +14 -14
- package/dist/api/admin/apiRemoveDetectorKeyEndpoint.d.ts +0 -23
- package/dist/api/admin/apiRemoveDetectorKeyEndpoint.d.ts.map +0 -1
- package/dist/api/admin/apiRemoveDetectorKeyEndpoint.js +0 -32
- package/dist/api/admin/apiRemoveDetectorKeyEndpoint.js.map +0 -1
- package/dist/api/admin/apiUpdateDetectorKeyEndpoint.d.ts +0 -14
- package/dist/api/admin/apiUpdateDetectorKeyEndpoint.d.ts.map +0 -1
- package/dist/api/admin/apiUpdateDetectorKeyEndpoint.js +0 -39
- package/dist/api/admin/apiUpdateDetectorKeyEndpoint.js.map +0 -1
- package/dist/cjs/api/admin/apiRemoveDetectorKeyEndpoint.cjs +0 -32
- package/dist/cjs/api/admin/apiUpdateDetectorKeyEndpoint.cjs +0 -39
- package/dist/tasks/detection/getBehavioralData.d.ts +0 -4
- package/dist/tasks/detection/getBehavioralData.d.ts.map +0 -1
- package/dist/tasks/detection/getBehavioralData.js +0 -51
- package/dist/tasks/detection/getBehavioralData.js.map +0 -1
- package/dist/tests/unit/api/admin/apiRemoveDetectorKeyEndpoint.unit.test.d.ts +0 -2
- package/dist/tests/unit/api/admin/apiRemoveDetectorKeyEndpoint.unit.test.d.ts.map +0 -1
- package/dist/tests/unit/api/admin/apiRemoveDetectorKeyEndpoint.unit.test.js +0 -57
- package/dist/tests/unit/api/admin/apiRemoveDetectorKeyEndpoint.unit.test.js.map +0 -1
- package/dist/tests/unit/api/admin/apiUpdateDetectorKeyEndpoint.unit.test.d.ts +0 -2
- package/dist/tests/unit/api/admin/apiUpdateDetectorKeyEndpoint.unit.test.d.ts.map +0 -1
- package/dist/tests/unit/api/admin/apiUpdateDetectorKeyEndpoint.unit.test.js +0 -60
- package/dist/tests/unit/api/admin/apiUpdateDetectorKeyEndpoint.unit.test.js.map +0 -1
- package/dist/tests/unit/tasks/detection/getBehavioralData.unit.test.d.ts +0 -2
- package/dist/tests/unit/tasks/detection/getBehavioralData.unit.test.d.ts.map +0 -1
- package/dist/tests/unit/tasks/detection/getBehavioralData.unit.test.js +0 -109
- package/dist/tests/unit/tasks/detection/getBehavioralData.unit.test.js.map +0 -1
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
let node_crypto = require("node:crypto");
|
|
2
|
+
let node_fs = require("node:fs");
|
|
3
|
+
let node_path = require("node:path");
|
|
4
|
+
//#region src/tasks/detection/bundlePool.ts
|
|
5
|
+
/**
|
|
6
|
+
* Detector bundle pool (provider side).
|
|
7
|
+
*
|
|
8
|
+
* Loads a pool of precomputed, obfuscated detector bundles from disk and caches
|
|
9
|
+
* them in memory — the Node analogue of the Rust "bumblebee" `bundle_manager`.
|
|
10
|
+
* Each bundle is a `{id}.js` (served to the browser) paired with a `{id}.json`
|
|
11
|
+
* (`{ privateKey, innerConfig }`, kept server-side) produced by the catcher
|
|
12
|
+
* `bundle:pool` build script.
|
|
13
|
+
*
|
|
14
|
+
* A bundle is assigned per detector session by {@link DetectorBundlePool.pickRandom},
|
|
15
|
+
* served by id, and resolved again at decryption time so the provider uses the
|
|
16
|
+
* single correct keypair + inner config rather than brute-forcing a key pool.
|
|
17
|
+
* The session→bundle binding lives in Redis (short TTL); this module only holds
|
|
18
|
+
* the immutable pool.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Default on-disk location of the pool inside the provider container. Backed by
|
|
22
|
+
* a host volume (see docker-compose.provider.yml) so a pushed pool survives
|
|
23
|
+
* container restarts — nothing is baked into the image, which matters because
|
|
24
|
+
* `prosopo/provider` is a public Docker Hub image and each bundle ships with its
|
|
25
|
+
* own RSA private key.
|
|
26
|
+
*/
|
|
27
|
+
var DEFAULT_DETECTOR_POOL_DIR = "/app/data/detector-pool";
|
|
28
|
+
/**
|
|
29
|
+
* In-memory pool of detector bundles. Immutable after {@link loadFromDir} except
|
|
30
|
+
* via {@link replace} (used by the admin "emergency push" endpoint to hot-swap
|
|
31
|
+
* the pool without a redeploy).
|
|
32
|
+
*/
|
|
33
|
+
var DetectorBundlePool = class {
|
|
34
|
+
constructor() {
|
|
35
|
+
this.bundles = /* @__PURE__ */ new Map();
|
|
36
|
+
this.ids = [];
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Load every `{id}.js`/`{id}.json` pair from `dir` into memory. A `.js`
|
|
40
|
+
* without a readable, well-formed `.json` sibling is skipped (logged). Safe
|
|
41
|
+
* to call on a missing directory (yields an empty pool).
|
|
42
|
+
*/
|
|
43
|
+
loadFromDir(dir, logger = {}, expectedRelease) {
|
|
44
|
+
const next = /* @__PURE__ */ new Map();
|
|
45
|
+
if (!(0, node_fs.existsSync)(dir)) {
|
|
46
|
+
logger.warn?.("bundle pool directory does not exist", { dir });
|
|
47
|
+
this.bundles = next;
|
|
48
|
+
this.ids = [];
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
let mismatched = 0;
|
|
52
|
+
let unstamped = 0;
|
|
53
|
+
for (const entry of (0, node_fs.readdirSync)(dir)) {
|
|
54
|
+
if ((0, node_path.extname)(entry) !== ".js") continue;
|
|
55
|
+
const bundleId = entry.slice(0, -3);
|
|
56
|
+
const jsPath = (0, node_path.join)(dir, entry);
|
|
57
|
+
const jsonPath = (0, node_path.join)(dir, `${bundleId}.json`);
|
|
58
|
+
try {
|
|
59
|
+
const js = (0, node_fs.readFileSync)(jsPath, "utf8");
|
|
60
|
+
const secrets = JSON.parse((0, node_fs.readFileSync)(jsonPath, "utf8"));
|
|
61
|
+
if (typeof secrets.privateKey !== "string" || typeof secrets.innerConfig !== "string") {
|
|
62
|
+
logger.warn?.("bundle pool entry has malformed secrets", { bundleId });
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const release = typeof secrets.release === "string" ? secrets.release : void 0;
|
|
66
|
+
if (expectedRelease && release && release !== expectedRelease) {
|
|
67
|
+
mismatched++;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (expectedRelease && !release) unstamped++;
|
|
71
|
+
next.set(bundleId, {
|
|
72
|
+
js,
|
|
73
|
+
privateKey: secrets.privateKey,
|
|
74
|
+
innerConfig: secrets.innerConfig,
|
|
75
|
+
...release && { release }
|
|
76
|
+
});
|
|
77
|
+
} catch (error) {
|
|
78
|
+
logger.warn?.("failed to load bundle pool entry", {
|
|
79
|
+
bundleId,
|
|
80
|
+
error: error instanceof Error ? error.message : String(error)
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (mismatched > 0) logger.warn?.("skipped bundle pool entries built for another release", {
|
|
85
|
+
skipped: mismatched,
|
|
86
|
+
expectedRelease
|
|
87
|
+
});
|
|
88
|
+
if (unstamped > 0) logger.warn?.("bundle pool entries have no release stamp", {
|
|
89
|
+
count: unstamped,
|
|
90
|
+
expectedRelease
|
|
91
|
+
});
|
|
92
|
+
this.replace(next);
|
|
93
|
+
logger.info?.("bundle pool loaded", {
|
|
94
|
+
count: this.ids.length,
|
|
95
|
+
dir
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
/** Hot-swap the entire pool (used by the admin replace-pool endpoint). */
|
|
99
|
+
replace(bundles) {
|
|
100
|
+
this.bundles = bundles;
|
|
101
|
+
this.ids = [...bundles.keys()].sort();
|
|
102
|
+
}
|
|
103
|
+
/** Number of loaded bundles. */
|
|
104
|
+
size() {
|
|
105
|
+
return this.ids.length;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Pick a uniformly-random bundle. Throws if the pool is empty — the caller
|
|
109
|
+
* must surface this as a hard failure (no bundle ⇒ no valid payload can be
|
|
110
|
+
* produced).
|
|
111
|
+
*/
|
|
112
|
+
pickRandom() {
|
|
113
|
+
if (this.ids.length === 0) throw new Error("detector bundle pool is empty");
|
|
114
|
+
const bundleId = this.ids[(0, node_crypto.randomInt)(this.ids.length)];
|
|
115
|
+
const bundle = bundleId !== void 0 ? this.bundles.get(bundleId) : void 0;
|
|
116
|
+
if (bundleId === void 0 || !bundle) throw new Error("detector bundle pool selection failed");
|
|
117
|
+
return {
|
|
118
|
+
bundleId,
|
|
119
|
+
bundle
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
/** Resolve a bundle by id, or `undefined` if it is not (or no longer) in the pool. */
|
|
123
|
+
get(bundleId) {
|
|
124
|
+
return this.bundles.get(bundleId);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
/** Process-global pool singleton (mirrors bumblebee's OnceCell). */
|
|
128
|
+
var globalPool = null;
|
|
129
|
+
/** Where {@link initDetectorBundlePool} last loaded from — the persist target. */
|
|
130
|
+
var globalPoolDir = DEFAULT_DETECTOR_POOL_DIR;
|
|
131
|
+
/** Initialise the global pool from `dir`. Idempotent (reloads on each call). */
|
|
132
|
+
function initDetectorBundlePool(dir, logger = {}, expectedRelease) {
|
|
133
|
+
const pool = new DetectorBundlePool();
|
|
134
|
+
pool.loadFromDir(dir, logger, expectedRelease);
|
|
135
|
+
globalPool = pool;
|
|
136
|
+
globalPoolDir = dir;
|
|
137
|
+
return pool;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Write a pool to `dir` in the same `{id}.js` / `{id}.json` layout
|
|
141
|
+
* {@link DetectorBundlePool.loadFromDir} reads, so a pushed pool is picked up
|
|
142
|
+
* again on the next boot instead of vanishing with the process.
|
|
143
|
+
*
|
|
144
|
+
* Writes *into* `dir` and never replaces the directory itself. In the provider
|
|
145
|
+
* container `dir` is a bind-mount point, and removing or renaming over a mount
|
|
146
|
+
* point from inside the container fails with EBUSY ("Device or resource busy")
|
|
147
|
+
* — which is exactly what an earlier stage-into-a-sibling-and-rename version of
|
|
148
|
+
* this function did on every push.
|
|
149
|
+
*
|
|
150
|
+
* Each file is still written to a temp name and renamed into place, so a reader
|
|
151
|
+
* never observes a partially-written bundle; renames within one directory are
|
|
152
|
+
* atomic and don't touch the mount. Bundles no longer in the pool are then
|
|
153
|
+
* pruned, giving replace semantics rather than an ever-growing merge.
|
|
154
|
+
*
|
|
155
|
+
* Throws on failure — the caller decides whether an unpersisted push is
|
|
156
|
+
* acceptable.
|
|
157
|
+
*/
|
|
158
|
+
function persistDetectorBundlePool(bundles, dir = globalPoolDir) {
|
|
159
|
+
(0, node_fs.mkdirSync)(dir, { recursive: true });
|
|
160
|
+
const written = /* @__PURE__ */ new Set();
|
|
161
|
+
for (const [bundleId, bundle] of bundles) {
|
|
162
|
+
const jsName = `${bundleId}.js`;
|
|
163
|
+
const jsonName = `${bundleId}.json`;
|
|
164
|
+
const jsTmp = (0, node_path.join)(dir, `.${jsName}.tmp`);
|
|
165
|
+
(0, node_fs.writeFileSync)(jsTmp, bundle.js, "utf8");
|
|
166
|
+
(0, node_fs.renameSync)(jsTmp, (0, node_path.join)(dir, jsName));
|
|
167
|
+
const jsonTmp = (0, node_path.join)(dir, `.${jsonName}.tmp`);
|
|
168
|
+
(0, node_fs.writeFileSync)(jsonTmp, JSON.stringify({
|
|
169
|
+
privateKey: bundle.privateKey,
|
|
170
|
+
innerConfig: bundle.innerConfig,
|
|
171
|
+
...bundle.release && { release: bundle.release }
|
|
172
|
+
}), {
|
|
173
|
+
encoding: "utf8",
|
|
174
|
+
mode: 384
|
|
175
|
+
});
|
|
176
|
+
(0, node_fs.renameSync)(jsonTmp, (0, node_path.join)(dir, jsonName));
|
|
177
|
+
written.add(jsName);
|
|
178
|
+
written.add(jsonName);
|
|
179
|
+
}
|
|
180
|
+
for (const entry of (0, node_fs.readdirSync)(dir)) if (((0, node_path.extname)(entry) === ".js" || (0, node_path.extname)(entry) === ".json") && !written.has(entry)) (0, node_fs.rmSync)((0, node_path.join)(dir, entry), { force: true });
|
|
181
|
+
}
|
|
182
|
+
/** Get the global pool, or `null` if {@link initDetectorBundlePool} was never called. */
|
|
183
|
+
function getDetectorBundlePool() {
|
|
184
|
+
return globalPool;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Hot-swap the global pool's contents (admin "emergency push" channel) and
|
|
188
|
+
* write it to the pool directory so it survives a restart. Creates the global
|
|
189
|
+
* pool if it does not exist yet.
|
|
190
|
+
*
|
|
191
|
+
* The in-memory swap happens first and is never rolled back: a running provider
|
|
192
|
+
* serving the new pool is the point of the push, and it is strictly better than
|
|
193
|
+
* a provider serving the old one. A failed persist is reported so the caller can
|
|
194
|
+
* warn — the pool is live but will be lost on the next restart.
|
|
195
|
+
*/
|
|
196
|
+
function replaceDetectorBundlePool(bundles, logger = {}) {
|
|
197
|
+
if (!globalPool) globalPool = new DetectorBundlePool();
|
|
198
|
+
globalPool.replace(bundles);
|
|
199
|
+
let persisted = false;
|
|
200
|
+
try {
|
|
201
|
+
persistDetectorBundlePool(bundles);
|
|
202
|
+
persisted = true;
|
|
203
|
+
} catch (error) {
|
|
204
|
+
logger.warn?.("failed to persist detector bundle pool to disk", {
|
|
205
|
+
dir: globalPoolDir,
|
|
206
|
+
error: error instanceof Error ? error.message : String(error)
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
return {
|
|
210
|
+
count: globalPool.size(),
|
|
211
|
+
persisted
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
//#endregion
|
|
215
|
+
exports.DEFAULT_DETECTOR_POOL_DIR = DEFAULT_DETECTOR_POOL_DIR;
|
|
216
|
+
exports.DetectorBundlePool = DetectorBundlePool;
|
|
217
|
+
exports.getDetectorBundlePool = getDetectorBundlePool;
|
|
218
|
+
exports.initDetectorBundlePool = initDetectorBundlePool;
|
|
219
|
+
exports.persistDetectorBundlePool = persistDetectorBundlePool;
|
|
220
|
+
exports.replaceDetectorBundlePool = replaceDetectorBundlePool;
|