@prosopo/provider 4.15.17 → 5.0.1
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 +102 -101
- package/.turbo/turbo-build$colon$tsc.log +24 -24
- package/.turbo/turbo-build.log +103 -102
- package/CHANGELOG.md +137 -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 +50 -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 +3 -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 +1 -0
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/decisionMachine.d.ts.map +1 -1
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/decisionMachine.js +59 -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 +16 -8
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/handler.js.map +1 -1
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.d.ts +2 -0
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.d.ts.map +1 -1
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.js +45 -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 +77 -0
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.powFallback.test.js.map +1 -0
- 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 +50 -17
- package/dist/api/startProviderApi.js.map +1 -1
- package/dist/api/verify.d.ts.map +1 -1
- package/dist/api/verify.js +1 -1
- package/dist/api/verify.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 +50 -0
- package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/decisionMachine.cjs +59 -2
- package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/decryptSimdReadings.cjs +3 -3
- package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/handler.cjs +15 -7
- package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.cjs +45 -17
- package/dist/cjs/api/captcha/submitPoWCaptchaSolution.cjs +1 -1
- package/dist/cjs/api/captcha.cjs +6 -0
- package/dist/cjs/api/startProviderApi.cjs +53 -19
- package/dist/cjs/api/verify.cjs +1 -1
- 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 +37 -7
- package/dist/cjs/tasks/powCaptcha/powTasks.cjs +33 -3
- package/dist/cjs/tasks/puzzleCaptcha/puzzleTasks.cjs +36 -4
- package/dist/cjs/tasks/spam/evaluateEmailSpamRules.cjs +23 -0
- 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 +38 -8
- 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 +34 -4
- package/dist/tasks/powCaptcha/powTasks.js.map +1 -1
- package/dist/tasks/puzzleCaptcha/puzzleTasks.d.ts +2 -2
- package/dist/tasks/puzzleCaptcha/puzzleTasks.d.ts.map +1 -1
- package/dist/tasks/puzzleCaptcha/puzzleTasks.js +36 -4
- package/dist/tasks/puzzleCaptcha/puzzleTasks.js.map +1 -1
- package/dist/tasks/spam/evaluateEmailSpamRules.d.ts +1 -0
- package/dist/tasks/spam/evaluateEmailSpamRules.d.ts.map +1 -1
- package/dist/tasks/spam/evaluateEmailSpamRules.js +23 -1
- package/dist/tasks/spam/evaluateEmailSpamRules.js.map +1 -1
- package/dist/tests/integration/clientSettingsPersistence.integration.test.js +23 -1
- package/dist/tests/integration/clientSettingsPersistence.integration.test.js.map +1 -1
- package/dist/tests/integration/decisionMachines.integration.test.js +3 -56
- package/dist/tests/integration/decisionMachines.integration.test.js.map +1 -1
- package/dist/tests/integration/escalationPeekBeforeConsume.integration.test.js +2 -2
- package/dist/tests/integration/escalationPeekBeforeConsume.integration.test.js.map +1 -1
- package/dist/tests/integration/imgCaptcha.integration.test.js +3 -56
- package/dist/tests/integration/imgCaptcha.integration.test.js.map +1 -1
- package/dist/tests/integration/maintenanceModeAdmin.integration.test.js +2 -29
- package/dist/tests/integration/maintenanceModeAdmin.integration.test.js.map +1 -1
- package/dist/tests/integration/powCaptcha.integration.test.js +3 -56
- package/dist/tests/integration/powCaptcha.integration.test.js.map +1 -1
- package/dist/tests/integration/routingDecisionMachines.integration.test.js +2 -2
- package/dist/tests/integration/routingDecisionMachines.integration.test.js.map +1 -1
- package/dist/tests/integration/testUtils.d.ts +1 -0
- package/dist/tests/integration/testUtils.d.ts.map +1 -1
- package/dist/tests/integration/testUtils.js +14 -0
- package/dist/tests/integration/testUtils.js.map +1 -1
- package/dist/tests/integration/testUtils.unit.test.d.ts +2 -0
- package/dist/tests/integration/testUtils.unit.test.d.ts.map +1 -0
- package/dist/tests/integration/testUtils.unit.test.js +30 -0
- package/dist/tests/integration/testUtils.unit.test.js.map +1 -0
- 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 +101 -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/imgCaptcha/imgCaptchaTasks.unit.test.js +99 -0
- package/dist/tests/unit/tasks/imgCaptcha/imgCaptchaTasks.unit.test.js.map +1 -1
- package/dist/tests/unit/tasks/powCaptcha/powTasks.unit.test.js +108 -0
- package/dist/tests/unit/tasks/powCaptcha/powTasks.unit.test.js.map +1 -1
- package/dist/tests/unit/tasks/puzzleCaptcha/puzzleTasks.unit.test.js +102 -1
- package/dist/tests/unit/tasks/puzzleCaptcha/puzzleTasks.unit.test.js.map +1 -1
- package/dist/tests/unit/tasks/spam/evaluateEmailSpamRules.unit.test.js +37 -1
- package/dist/tests/unit/tasks/spam/evaluateEmailSpamRules.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 +19 -19
- 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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const require_runtime = require("../_virtual/_rolldown/runtime.cjs");
|
|
2
2
|
const require_metrics = require("./metrics.cjs");
|
|
3
3
|
const require_blacklistRequestInspector = require("./blacklistRequestInspector.cjs");
|
|
4
|
+
const require_bundlePool = require("../tasks/detection/bundlePool.cjs");
|
|
4
5
|
const require_block = require("./block.cjs");
|
|
5
6
|
const require_captcha = require("./captcha.cjs");
|
|
6
7
|
const require_verify = require("./verify.cjs");
|
|
@@ -15,15 +16,15 @@ const require_ipInfoMiddleware = require("./ipInfoMiddleware.cjs");
|
|
|
15
16
|
const require_robotsMiddleware = require("./robotsMiddleware.cjs");
|
|
16
17
|
let _prosopo_logger = require("@prosopo/logger");
|
|
17
18
|
let _prosopo_types = require("@prosopo/types");
|
|
18
|
-
let node_https = require("node:https");
|
|
19
|
-
node_https = require_runtime.__toESM(node_https, 1);
|
|
20
19
|
let node_fs = require("node:fs");
|
|
21
20
|
node_fs = require_runtime.__toESM(node_fs, 1);
|
|
21
|
+
let node_path = require("node:path");
|
|
22
|
+
node_path = require_runtime.__toESM(node_path, 1);
|
|
23
|
+
let node_https = require("node:https");
|
|
24
|
+
node_https = require_runtime.__toESM(node_https, 1);
|
|
22
25
|
let _prosopo_api_express_router = require("@prosopo/api-express-router");
|
|
23
26
|
let express = require("express");
|
|
24
27
|
express = require_runtime.__toESM(express, 1);
|
|
25
|
-
let node_path = require("node:path");
|
|
26
|
-
node_path = require_runtime.__toESM(node_path, 1);
|
|
27
28
|
let node_url = require("node:url");
|
|
28
29
|
let _prosopo_locale = require("@prosopo/locale");
|
|
29
30
|
let _prosopo_user_access_policy_api = require("@prosopo/user-access-policy/api");
|
|
@@ -32,6 +33,12 @@ cors = require_runtime.__toESM(cors, 1);
|
|
|
32
33
|
let express_rate_limit = require("express-rate-limit");
|
|
33
34
|
express_rate_limit = require_runtime.__toESM(express_rate_limit, 1);
|
|
34
35
|
//#region src/api/startProviderApi.ts
|
|
36
|
+
/**
|
|
37
|
+
* Body-size ceiling for the detector-pool push. ~830 KB/bundle × 100 bundles
|
|
38
|
+
* ≈ 86 MB encoded; 128 MB leaves headroom without approaching the 512 MiB V8
|
|
39
|
+
* string limit that ultimately bounds a single-body push at ~620 bundles.
|
|
40
|
+
*/
|
|
41
|
+
var DETECTOR_POOL_BODY_LIMIT = "128mb";
|
|
35
42
|
var __dirname$1 = node_path.default.dirname((0, node_url.fileURLToPath)(require("url").pathToFileURL(__filename).href));
|
|
36
43
|
var certPath = node_path.default.resolve(__dirname$1, "../../../../certs");
|
|
37
44
|
var keyPath = node_path.default.join(certPath, "server.key");
|
|
@@ -48,7 +55,7 @@ var isTlsAvailable = () => {
|
|
|
48
55
|
* @returns Array of ClientApiPaths excluding verify and spam routes
|
|
49
56
|
*/
|
|
50
57
|
var getClientApiPathsExpectingProsopoHeaders = () => {
|
|
51
|
-
return Object.values(_prosopo_types.ClientApiPaths).filter((path) => path.indexOf("verify") === -1 && path.indexOf("spam") === -1);
|
|
58
|
+
return Object.values(_prosopo_types.ClientApiPaths).filter((path) => path.indexOf("verify") === -1 && path.indexOf("spam") === -1 && path.indexOf("detector/assign") === -1);
|
|
52
59
|
};
|
|
53
60
|
/**
|
|
54
61
|
* Extract authenticated user address from JWT for rate limiting
|
|
@@ -69,6 +76,19 @@ var getUserFromJWT = (req) => {
|
|
|
69
76
|
}
|
|
70
77
|
};
|
|
71
78
|
/**
|
|
79
|
+
* `.listen()` returns before the socket is bound, so a bind failure such as
|
|
80
|
+
* EADDRINUSE arrives later as an 'error' event — an uncaught exception unless
|
|
81
|
+
* something is waiting on it. Settle on whichever event comes first so the
|
|
82
|
+
* caller gets a rejection it can handle.
|
|
83
|
+
*/
|
|
84
|
+
var awaitListening = (server) => new Promise((resolve, reject) => {
|
|
85
|
+
server.once("error", reject);
|
|
86
|
+
server.once("listening", () => {
|
|
87
|
+
server.removeListener("error", reject);
|
|
88
|
+
resolve();
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
/**
|
|
72
92
|
* Start the Prosopo Provider API server
|
|
73
93
|
*
|
|
74
94
|
* This function creates and configures an Express server with all necessary middleware
|
|
@@ -95,6 +115,17 @@ var getUserFromJWT = (req) => {
|
|
|
95
115
|
*/
|
|
96
116
|
async function startProviderApi(env, admin = false, port) {
|
|
97
117
|
env.logger.info(() => ({ msg: "Starting Prosopo API" }));
|
|
118
|
+
const detectorPoolDir = process.env.PROSOPO_DETECTOR_POOL_DIR ?? "/app/data/detector-pool";
|
|
119
|
+
require_bundlePool.initDetectorBundlePool(detectorPoolDir, {
|
|
120
|
+
info: (msg, data) => env.logger.info(() => ({
|
|
121
|
+
msg,
|
|
122
|
+
data
|
|
123
|
+
})),
|
|
124
|
+
warn: (msg, data) => env.logger.warn(() => ({
|
|
125
|
+
msg,
|
|
126
|
+
data
|
|
127
|
+
}))
|
|
128
|
+
}, process.env.PROSOPO_DETECTOR_POOL_RELEASE);
|
|
98
129
|
const apiApp = (0, express.default)();
|
|
99
130
|
const apiPort = port || env.config.server?.port;
|
|
100
131
|
const apiEndpointAdapter = (0, _prosopo_api_express_router.createApiExpressDefaultEndpointAdapter)((0, _prosopo_logger.parseLogLevel)(env.config.logLevel));
|
|
@@ -129,6 +160,7 @@ async function startProviderApi(env, admin = false, port) {
|
|
|
129
160
|
],
|
|
130
161
|
maxAge: 86400
|
|
131
162
|
}));
|
|
163
|
+
apiApp.use(_prosopo_types.AdminApiPaths.ReplaceDetectorPool, express.default.json({ limit: DETECTOR_POOL_BODY_LIMIT }));
|
|
132
164
|
apiApp.use(express.default.json({ limit: "1mb" }));
|
|
133
165
|
apiApp.use(require_public.publicRouter(env));
|
|
134
166
|
apiApp.use(require_metrics.metricsMiddleware());
|
|
@@ -204,22 +236,24 @@ async function startProviderApi(env, admin = false, port) {
|
|
|
204
236
|
key: node_fs.default.readFileSync(keyPath),
|
|
205
237
|
cert: node_fs.default.readFileSync(crtPath)
|
|
206
238
|
};
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
data: {
|
|
210
|
-
apiPort,
|
|
211
|
-
protocol: "https"
|
|
212
|
-
},
|
|
213
|
-
msg: "Prosopo app listening with HTTPS"
|
|
214
|
-
}));
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
return apiApp.listen(apiPort, () => {
|
|
239
|
+
const httpsServer = node_https.default.createServer(httpsOptions, apiApp).listen(apiPort);
|
|
240
|
+
await awaitListening(httpsServer);
|
|
218
241
|
env.logger.info(() => ({
|
|
219
|
-
data: {
|
|
220
|
-
|
|
242
|
+
data: {
|
|
243
|
+
apiPort,
|
|
244
|
+
protocol: "https"
|
|
245
|
+
},
|
|
246
|
+
msg: "Prosopo app listening with HTTPS"
|
|
221
247
|
}));
|
|
222
|
-
|
|
248
|
+
return httpsServer;
|
|
249
|
+
}
|
|
250
|
+
const server = apiApp.listen(apiPort);
|
|
251
|
+
await awaitListening(server);
|
|
252
|
+
env.logger.info(() => ({
|
|
253
|
+
data: { apiPort },
|
|
254
|
+
msg: "Prosopo app listening"
|
|
255
|
+
}));
|
|
256
|
+
return server;
|
|
223
257
|
}
|
|
224
258
|
//#endregion
|
|
225
259
|
exports.getClientApiPathsExpectingProsopoHeaders = getClientApiPathsExpectingProsopoHeaders;
|
package/dist/cjs/api/verify.cjs
CHANGED
|
@@ -317,7 +317,7 @@ function prosopoVerifyRouter(env) {
|
|
|
317
317
|
}
|
|
318
318
|
const dappPair = env.keyring.addFromAddress(dapp);
|
|
319
319
|
(0, _prosopo_api_express_router.verifySignature)(dappSignature, timestamp.toString(), dappPair);
|
|
320
|
-
const { verified, score } = await tasks.puzzleCaptchaManager.serverVerifyPuzzleCaptchaSolution(dapp, challenge, clientRecord.settings.verifiedTimeout, env, ip, userAccessRulesStorage, email, clientRecord.settings.spamEmailDomainCheckEnabled, clientRecord.settings.trafficFilter, clientRecord.settings.storeMetadata);
|
|
320
|
+
const { verified, score } = await tasks.puzzleCaptchaManager.serverVerifyPuzzleCaptchaSolution(dapp, challenge, clientRecord.settings.verifiedTimeout, env, ip, userAccessRulesStorage, email, clientRecord.settings.spamEmailDomainCheckEnabled, clientRecord.settings.spamFilter, clientRecord.settings.trafficFilter, clientRecord.settings.storeMetadata);
|
|
321
321
|
const verificationResponse = tasks.puzzleCaptchaManager.getVerificationResponse(verified, clientRecord, req.i18n.t, score);
|
|
322
322
|
return res.json(verificationResponse);
|
|
323
323
|
} catch (err) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const require_compositeIpAddress = require("../compositeIpAddress.cjs");
|
|
2
2
|
const require_blacklistRequestInspector = require("../api/blacklistRequestInspector.cjs");
|
|
3
|
+
const require_bundlePool = require("./detection/bundlePool.cjs");
|
|
3
4
|
const require_checkTrafficFilter = require("./spam/checkTrafficFilter.cjs");
|
|
4
5
|
const require_enrichDnsEvent = require("./dnsEvent/enrichDnsEvent.cjs");
|
|
5
6
|
const require_checkSpamEmail = require("./spam/checkSpamEmail.cjs");
|
|
@@ -59,19 +60,24 @@ var CaptchaManager = class CaptchaManager {
|
|
|
59
60
|
this.scheduleMongoSessionUpdate(sessionId, updates, streamToCentral);
|
|
60
61
|
}
|
|
61
62
|
/**
|
|
62
|
-
* Decrypt with the
|
|
63
|
-
* `timestamp` field. Returns `undefined` when no
|
|
63
|
+
* Decrypt with the session's detector pool bundle and strip the throwaway
|
|
64
|
+
* `timestamp` field. Returns `undefined` when no bundle is resolved or
|
|
65
|
+
* decryption fails.
|
|
64
66
|
*/
|
|
65
|
-
async decryptSimdReadingsForAttach(simdReadingsCiphertext) {
|
|
66
|
-
const
|
|
67
|
-
const decrypted = await this.decryptSimdReadings(simdReadingsCiphertext, decryptKeys);
|
|
67
|
+
async decryptSimdReadingsForAttach(simdReadingsCiphertext, bundle) {
|
|
68
|
+
const decrypted = await this.decryptSimdReadings(simdReadingsCiphertext, bundle);
|
|
68
69
|
if (!decrypted) return void 0;
|
|
69
70
|
const { timestamp: _ignored, ...readings } = decrypted;
|
|
70
71
|
return readings;
|
|
71
72
|
}
|
|
72
|
-
/**
|
|
73
|
+
/**
|
|
74
|
+
* Decrypt + first-hop-wins attach. Resolves the session's detector pool
|
|
75
|
+
* bundle (promoted onto the session record at frictionless time) to decrypt.
|
|
76
|
+
* No-op on decrypt failure / no bundle.
|
|
77
|
+
*/
|
|
73
78
|
async decryptAndAttachSimdReadingsIfAbsent(sessionId, simdReadingsCiphertext, stage) {
|
|
74
|
-
const
|
|
79
|
+
const bundle = await this.resolveBundleBySessionId(sessionId);
|
|
80
|
+
const readings = await this.decryptSimdReadingsForAttach(simdReadingsCiphertext, bundle);
|
|
75
81
|
if (!readings) return;
|
|
76
82
|
await this.recordSessionSimdReadingsIfAbsentWithCache(sessionId, readings, stage);
|
|
77
83
|
}
|
|
@@ -271,62 +277,81 @@ var CaptchaManager = class CaptchaManager {
|
|
|
271
277
|
async getPrioritisedAccessPolicies(userAccessRulesStorage, clientId, userScope, options) {
|
|
272
278
|
return require_blacklistRequestInspector.getPrioritisedAccessRule(userAccessRulesStorage, userScope, clientId, options);
|
|
273
279
|
}
|
|
274
|
-
|
|
275
|
-
|
|
280
|
+
/**
|
|
281
|
+
* Resolve a detector pool bundle's decryption material from a `bundleId`
|
|
282
|
+
* (in-memory pool lookup). Returns undefined when the id is absent or the
|
|
283
|
+
* bundle is no longer in the pool.
|
|
284
|
+
*/
|
|
285
|
+
resolveBundleById(bundleId) {
|
|
286
|
+
const bundle = bundleId ? require_bundlePool.getDetectorBundlePool()?.get(bundleId) : void 0;
|
|
287
|
+
return bundle ? {
|
|
288
|
+
key: bundle.privateKey,
|
|
289
|
+
innerConfig: bundle.innerConfig
|
|
290
|
+
} : void 0;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Resolve the detector pool bundle for a session via its short-lived
|
|
294
|
+
* `detectorSessionId → bundleId` Redis binding (used at the frictionless hop,
|
|
295
|
+
* before the binding is promoted onto the durable session record). Returns
|
|
296
|
+
* the bundleId too so the caller can promote it onto the session.
|
|
297
|
+
*/
|
|
298
|
+
async resolveBundleByDetectorSession(detectorSessionId) {
|
|
299
|
+
if (!detectorSessionId || !this.writeQueue) return void 0;
|
|
300
|
+
const bundleId = await this.writeQueue.getDetectorBundle(detectorSessionId);
|
|
301
|
+
const decrypt = this.resolveBundleById(bundleId);
|
|
302
|
+
return bundleId && decrypt ? {
|
|
303
|
+
...decrypt,
|
|
304
|
+
bundleId
|
|
305
|
+
} : void 0;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Resolve the detector pool bundle for a session via the `bundleId` promoted
|
|
309
|
+
* onto its durable session record (used at later hops — SIMD attach,
|
|
310
|
+
* PoW/puzzle/image solution submit — after the Redis binding has expired).
|
|
311
|
+
* Cache-first (Redis), Mongo fallback.
|
|
312
|
+
*/
|
|
313
|
+
async resolveBundleBySessionId(sessionId) {
|
|
314
|
+
if (!sessionId) return void 0;
|
|
315
|
+
const cached = this.writeQueue ? await this.writeQueue.getCachedSession(sessionId) : void 0;
|
|
316
|
+
const bundleId = (typeof cached?.bundleId === "string" ? cached.bundleId : void 0) ?? (await this.db.getSessionRecordBySessionId(sessionId))?.bundleId;
|
|
317
|
+
return this.resolveBundleById(bundleId);
|
|
276
318
|
}
|
|
277
319
|
/**
|
|
278
320
|
* Decrypt the catcher's WASM SIMD CPU fingerprint readings via the
|
|
279
321
|
* obfuscated `decodeSimd.js` bundle (source lives in the private
|
|
280
|
-
* @prosopo/catcher repo).
|
|
281
|
-
*
|
|
282
|
-
*
|
|
283
|
-
* the field rather than fail the
|
|
322
|
+
* @prosopo/catcher repo). The detector lives only in provider-served pool
|
|
323
|
+
* bundles, so decryption uses that session's single bundle (RSA key + inner
|
|
324
|
+
* cipher) — there is no key pool. Returns null when no bundle is resolved or
|
|
325
|
+
* decryption fails, so the caller can drop the field rather than fail the
|
|
326
|
+
* whole request.
|
|
284
327
|
*/
|
|
285
|
-
async decryptSimdReadings(encryptedData,
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
if (validKeys.length === 0) {
|
|
289
|
-
this.logger?.error(() => ({ msg: "No decryption keys provided for SIMD readings" }));
|
|
328
|
+
async decryptSimdReadings(encryptedData, bundle) {
|
|
329
|
+
if (!bundle) {
|
|
330
|
+
this.logger?.warn(() => ({ msg: "No detector bundle resolved for SIMD readings — dropping field" }));
|
|
290
331
|
return null;
|
|
291
332
|
}
|
|
292
|
-
|
|
293
|
-
|
|
333
|
+
const decryptSimdReadings = (await Promise.resolve().then(() => require("./detection/decodeSimd.cjs"))).default;
|
|
334
|
+
try {
|
|
335
|
+
return await decryptSimdReadings(encryptedData, bundle.key, bundle.innerConfig);
|
|
294
336
|
} catch (err) {
|
|
295
|
-
this.logger?.
|
|
296
|
-
msg: "Failed to decrypt SIMD readings with
|
|
297
|
-
data: {
|
|
298
|
-
keyIndex: keyIndex + 1,
|
|
299
|
-
totalKeys: validKeys.length,
|
|
300
|
-
err
|
|
301
|
-
}
|
|
337
|
+
this.logger?.warn(() => ({
|
|
338
|
+
msg: "Failed to decrypt SIMD readings with the session's bundle",
|
|
339
|
+
data: { err }
|
|
302
340
|
}));
|
|
341
|
+
return null;
|
|
303
342
|
}
|
|
304
|
-
this.logger?.warn(() => ({
|
|
305
|
-
msg: "Failed to decrypt SIMD readings with all available keys",
|
|
306
|
-
data: { totalKeysAttempted: validKeys.length }
|
|
307
|
-
}));
|
|
308
|
-
return null;
|
|
309
343
|
}
|
|
310
|
-
async decryptBehavioralData(encryptedData,
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
if (validKeys.length === 0) {
|
|
314
|
-
this.logger?.error(() => ({ msg: "No decryption keys provided for behavioral data" }));
|
|
344
|
+
async decryptBehavioralData(encryptedData, bundle) {
|
|
345
|
+
if (!bundle) {
|
|
346
|
+
this.logger?.warn(() => ({ msg: "No detector bundle resolved for behavioral data — dropping field" }));
|
|
315
347
|
return null;
|
|
316
348
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
data: {
|
|
321
|
-
keyIndex: keyIndex + 1,
|
|
322
|
-
totalKeys: validKeys.length
|
|
323
|
-
}
|
|
324
|
-
}));
|
|
325
|
-
const result = await decryptBehavioralData(encryptedData, key);
|
|
349
|
+
const decryptBehavioralData = (await Promise.resolve().then(() => require("./detection/decodeBehavior.cjs"))).default;
|
|
350
|
+
try {
|
|
351
|
+
const result = await decryptBehavioralData(encryptedData, bundle.key, bundle.innerConfig);
|
|
326
352
|
this.logger?.info(() => ({
|
|
327
353
|
msg: "Behavioral data decrypted successfully",
|
|
328
354
|
data: {
|
|
329
|
-
keyIndex: keyIndex + 1,
|
|
330
355
|
c1Length: result.collector1?.length || 0,
|
|
331
356
|
c2Length: result.collector2?.length || 0,
|
|
332
357
|
c3Length: result.collector3?.length || 0,
|
|
@@ -335,20 +360,12 @@ var CaptchaManager = class CaptchaManager {
|
|
|
335
360
|
}));
|
|
336
361
|
return result;
|
|
337
362
|
} catch (error) {
|
|
338
|
-
this.logger?.
|
|
339
|
-
msg: "Failed to decrypt with
|
|
340
|
-
data: {
|
|
341
|
-
keyIndex: keyIndex + 1,
|
|
342
|
-
totalKeys: validKeys.length,
|
|
343
|
-
err: error
|
|
344
|
-
}
|
|
363
|
+
this.logger?.warn(() => ({
|
|
364
|
+
msg: "Failed to decrypt behavioral data with the session's bundle",
|
|
365
|
+
data: { err: error }
|
|
345
366
|
}));
|
|
367
|
+
return null;
|
|
346
368
|
}
|
|
347
|
-
this.logger?.error(() => ({
|
|
348
|
-
msg: "Failed to decrypt behavioral data with all available keys",
|
|
349
|
-
data: { totalKeysAttempted: validKeys.length }
|
|
350
|
-
}));
|
|
351
|
-
return null;
|
|
352
369
|
}
|
|
353
370
|
/**
|
|
354
371
|
* Checks if a user should be hard blocked based on access policies
|
|
@@ -2,23 +2,10 @@ const require_validateAddress = require("../../api/validateAddress.cjs");
|
|
|
2
2
|
let _prosopo_common = require("@prosopo/common");
|
|
3
3
|
let _prosopo_database = require("@prosopo/database");
|
|
4
4
|
let _prosopo_types = require("@prosopo/types");
|
|
5
|
-
|
|
5
|
+
require("node:crypto");
|
|
6
6
|
let _prosopo_util = require("@prosopo/util");
|
|
7
7
|
//#region src/tasks/client/clientTasks.ts
|
|
8
8
|
var SAMPLE_SIZE = 75;
|
|
9
|
-
var isValidPrivateKey = (privateKeyString) => {
|
|
10
|
-
const privateKey = Buffer.from(privateKeyString, "base64").toString("ascii");
|
|
11
|
-
try {
|
|
12
|
-
(0, node_crypto.createPrivateKey)({
|
|
13
|
-
key: privateKey,
|
|
14
|
-
format: "pem",
|
|
15
|
-
type: "pkcs8"
|
|
16
|
-
});
|
|
17
|
-
return true;
|
|
18
|
-
} catch (error) {
|
|
19
|
-
return false;
|
|
20
|
-
}
|
|
21
|
-
};
|
|
22
9
|
var ClientTaskManager = class {
|
|
23
10
|
constructor(config, logger, db) {
|
|
24
11
|
this.config = config;
|
|
@@ -196,21 +183,6 @@ var ClientTaskManager = class {
|
|
|
196
183
|
}
|
|
197
184
|
await this.providerDB.removeClientRecords(accounts);
|
|
198
185
|
}
|
|
199
|
-
async updateDetectorKey(detectorKey) {
|
|
200
|
-
if (!isValidPrivateKey(detectorKey)) throw new _prosopo_common.ProsopoApiError("INVALID_DETECTOR_KEY", {
|
|
201
|
-
context: { detectorKey },
|
|
202
|
-
logger: this.logger
|
|
203
|
-
});
|
|
204
|
-
await this.providerDB.storeDetectorKey(detectorKey);
|
|
205
|
-
return await this.providerDB.getDetectorKeys();
|
|
206
|
-
}
|
|
207
|
-
async removeDetectorKey(detectorKey, expirationInSeconds) {
|
|
208
|
-
if (!isValidPrivateKey(detectorKey)) throw new _prosopo_common.ProsopoApiError("INVALID_DETECTOR_KEY", {
|
|
209
|
-
context: { detectorKey },
|
|
210
|
-
logger: this.logger
|
|
211
|
-
});
|
|
212
|
-
await this.providerDB.removeDetectorKey(detectorKey, expirationInSeconds);
|
|
213
|
-
}
|
|
214
186
|
async updateDecisionMachine(scope, runtime, source, dappAccount, language, name, version, captchaType, kind) {
|
|
215
187
|
if (scope === _prosopo_types.DecisionMachineScope.Dapp && !dappAccount) throw new _prosopo_common.ProsopoApiError("API.BAD_REQUEST", {
|
|
216
188
|
context: {
|
|
@@ -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;
|