@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.
Files changed (215) hide show
  1. package/.turbo/turbo-build$colon$cjs.log +102 -101
  2. package/.turbo/turbo-build$colon$tsc.log +24 -24
  3. package/.turbo/turbo-build.log +103 -102
  4. package/CHANGELOG.md +137 -0
  5. package/dist/api/admin/apiAdminRoutesProvider.d.ts.map +1 -1
  6. package/dist/api/admin/apiAdminRoutesProvider.js +3 -5
  7. package/dist/api/admin/apiAdminRoutesProvider.js.map +1 -1
  8. package/dist/api/admin/apiReplaceDetectorPoolEndpoint.d.ts +11 -0
  9. package/dist/api/admin/apiReplaceDetectorPoolEndpoint.d.ts.map +1 -0
  10. package/dist/api/admin/apiReplaceDetectorPoolEndpoint.js +56 -0
  11. package/dist/api/admin/apiReplaceDetectorPoolEndpoint.js.map +1 -0
  12. package/dist/api/captcha/assignDetectorBundle.d.ts +6 -0
  13. package/dist/api/captcha/assignDetectorBundle.d.ts.map +1 -0
  14. package/dist/api/captcha/assignDetectorBundle.js +50 -0
  15. package/dist/api/captcha/assignDetectorBundle.js.map +1 -0
  16. package/dist/api/captcha/assignDetectorBundle.test.d.ts +2 -0
  17. package/dist/api/captcha/assignDetectorBundle.test.d.ts.map +1 -0
  18. package/dist/api/captcha/assignDetectorBundle.test.js +71 -0
  19. package/dist/api/captcha/assignDetectorBundle.test.js.map +1 -0
  20. package/dist/api/captcha/getFrictionlessCaptchaChallenge/constants.d.ts +3 -0
  21. package/dist/api/captcha/getFrictionlessCaptchaChallenge/constants.d.ts.map +1 -1
  22. package/dist/api/captcha/getFrictionlessCaptchaChallenge/constants.js.map +1 -1
  23. package/dist/api/captcha/getFrictionlessCaptchaChallenge/decisionMachine.d.ts +1 -0
  24. package/dist/api/captcha/getFrictionlessCaptchaChallenge/decisionMachine.d.ts.map +1 -1
  25. package/dist/api/captcha/getFrictionlessCaptchaChallenge/decisionMachine.js +59 -2
  26. package/dist/api/captcha/getFrictionlessCaptchaChallenge/decisionMachine.js.map +1 -1
  27. package/dist/api/captcha/getFrictionlessCaptchaChallenge/decryptSimdReadings.d.ts +1 -1
  28. package/dist/api/captcha/getFrictionlessCaptchaChallenge/decryptSimdReadings.d.ts.map +1 -1
  29. package/dist/api/captcha/getFrictionlessCaptchaChallenge/decryptSimdReadings.js +3 -3
  30. package/dist/api/captcha/getFrictionlessCaptchaChallenge/decryptSimdReadings.js.map +1 -1
  31. package/dist/api/captcha/getFrictionlessCaptchaChallenge/handler.d.ts.map +1 -1
  32. package/dist/api/captcha/getFrictionlessCaptchaChallenge/handler.js +16 -8
  33. package/dist/api/captcha/getFrictionlessCaptchaChallenge/handler.js.map +1 -1
  34. package/dist/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.d.ts +2 -0
  35. package/dist/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.d.ts.map +1 -1
  36. package/dist/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.js +45 -18
  37. package/dist/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.js.map +1 -1
  38. package/dist/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.powFallback.test.d.ts +2 -0
  39. package/dist/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.powFallback.test.d.ts.map +1 -0
  40. package/dist/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.powFallback.test.js +77 -0
  41. package/dist/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.powFallback.test.js.map +1 -0
  42. package/dist/api/captcha/submitPoWCaptchaSolution.d.ts.map +1 -1
  43. package/dist/api/captcha/submitPoWCaptchaSolution.js +1 -1
  44. package/dist/api/captcha/submitPoWCaptchaSolution.js.map +1 -1
  45. package/dist/api/captcha.d.ts.map +1 -1
  46. package/dist/api/captcha.js +6 -0
  47. package/dist/api/captcha.js.map +1 -1
  48. package/dist/api/startProviderApi.d.ts.map +1 -1
  49. package/dist/api/startProviderApi.js +50 -17
  50. package/dist/api/startProviderApi.js.map +1 -1
  51. package/dist/api/verify.d.ts.map +1 -1
  52. package/dist/api/verify.js +1 -1
  53. package/dist/api/verify.js.map +1 -1
  54. package/dist/cjs/api/admin/apiAdminRoutesProvider.cjs +3 -5
  55. package/dist/cjs/api/admin/apiReplaceDetectorPoolEndpoint.cjs +56 -0
  56. package/dist/cjs/api/captcha/assignDetectorBundle.cjs +50 -0
  57. package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/decisionMachine.cjs +59 -2
  58. package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/decryptSimdReadings.cjs +3 -3
  59. package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/handler.cjs +15 -7
  60. package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.cjs +45 -17
  61. package/dist/cjs/api/captcha/submitPoWCaptchaSolution.cjs +1 -1
  62. package/dist/cjs/api/captcha.cjs +6 -0
  63. package/dist/cjs/api/startProviderApi.cjs +53 -19
  64. package/dist/cjs/api/verify.cjs +1 -1
  65. package/dist/cjs/tasks/captchaManager.cjs +76 -59
  66. package/dist/cjs/tasks/client/clientTasks.cjs +1 -29
  67. package/dist/cjs/tasks/detection/bundlePool.cjs +220 -0
  68. package/dist/cjs/tasks/detection/decodeBehavior.cjs +644 -362
  69. package/dist/cjs/tasks/detection/decodeBehavior.js +1 -1
  70. package/dist/cjs/tasks/detection/decodePayload.cjs +956 -617
  71. package/dist/cjs/tasks/detection/decodePayload.js +1 -1
  72. package/dist/cjs/tasks/detection/decodeSimd.cjs +723 -411
  73. package/dist/cjs/tasks/detection/decodeSimd.js +1 -1
  74. package/dist/cjs/tasks/detection/getBotScore.cjs +2 -2
  75. package/dist/cjs/tasks/frictionless/frictionlessTasks.cjs +35 -12
  76. package/dist/cjs/tasks/frictionless/frictionlessTasksUtils.cjs +1 -2
  77. package/dist/cjs/tasks/imgCaptcha/imgCaptchaTasks.cjs +37 -7
  78. package/dist/cjs/tasks/powCaptcha/powTasks.cjs +33 -3
  79. package/dist/cjs/tasks/puzzleCaptcha/puzzleTasks.cjs +36 -4
  80. package/dist/cjs/tasks/spam/evaluateEmailSpamRules.cjs +23 -0
  81. package/dist/tasks/captchaManager.d.ts +12 -4
  82. package/dist/tasks/captchaManager.d.ts.map +1 -1
  83. package/dist/tasks/captchaManager.js +76 -59
  84. package/dist/tasks/captchaManager.js.map +1 -1
  85. package/dist/tasks/client/clientTasks.d.ts +0 -2
  86. package/dist/tasks/client/clientTasks.d.ts.map +1 -1
  87. package/dist/tasks/client/clientTasks.js +1 -29
  88. package/dist/tasks/client/clientTasks.js.map +1 -1
  89. package/dist/tasks/detection/bundlePool.d.ts +32 -0
  90. package/dist/tasks/detection/bundlePool.d.ts.map +1 -0
  91. package/dist/tasks/detection/bundlePool.js +215 -0
  92. package/dist/tasks/detection/bundlePool.js.map +1 -0
  93. package/dist/tasks/detection/bundlePool.test.d.ts +2 -0
  94. package/dist/tasks/detection/bundlePool.test.d.ts.map +1 -0
  95. package/dist/tasks/detection/bundlePool.test.js +178 -0
  96. package/dist/tasks/detection/bundlePool.test.js.map +1 -0
  97. package/dist/tasks/detection/decodeBehavior.d.ts +2 -2
  98. package/dist/tasks/detection/decodeBehavior.d.ts.map +1 -1
  99. package/dist/tasks/detection/decodeBehavior.js +1 -1
  100. package/dist/tasks/detection/decodeBehavior.js.map +1 -1
  101. package/dist/tasks/detection/decodePayload.d.ts +2 -2
  102. package/dist/tasks/detection/decodePayload.d.ts.map +1 -1
  103. package/dist/tasks/detection/decodePayload.js +1 -1
  104. package/dist/tasks/detection/decodePayload.js.map +1 -1
  105. package/dist/tasks/detection/decodeSimd.d.ts +2 -2
  106. package/dist/tasks/detection/decodeSimd.d.ts.map +1 -1
  107. package/dist/tasks/detection/decodeSimd.js +1 -1
  108. package/dist/tasks/detection/decodeSimd.js.map +1 -1
  109. package/dist/tasks/detection/getBotScore.d.ts +1 -1
  110. package/dist/tasks/detection/getBotScore.d.ts.map +1 -1
  111. package/dist/tasks/detection/getBotScore.js +3 -3
  112. package/dist/tasks/detection/getBotScore.js.map +1 -1
  113. package/dist/tasks/frictionless/frictionlessTasks.d.ts +10 -2
  114. package/dist/tasks/frictionless/frictionlessTasks.d.ts.map +1 -1
  115. package/dist/tasks/frictionless/frictionlessTasks.js +35 -12
  116. package/dist/tasks/frictionless/frictionlessTasks.js.map +1 -1
  117. package/dist/tasks/frictionless/frictionlessTasksUtils.d.ts +1 -1
  118. package/dist/tasks/frictionless/frictionlessTasksUtils.d.ts.map +1 -1
  119. package/dist/tasks/frictionless/frictionlessTasksUtils.js +1 -2
  120. package/dist/tasks/frictionless/frictionlessTasksUtils.js.map +1 -1
  121. package/dist/tasks/imgCaptcha/imgCaptchaTasks.d.ts.map +1 -1
  122. package/dist/tasks/imgCaptcha/imgCaptchaTasks.js +38 -8
  123. package/dist/tasks/imgCaptcha/imgCaptchaTasks.js.map +1 -1
  124. package/dist/tasks/powCaptcha/powTasks.d.ts.map +1 -1
  125. package/dist/tasks/powCaptcha/powTasks.js +34 -4
  126. package/dist/tasks/powCaptcha/powTasks.js.map +1 -1
  127. package/dist/tasks/puzzleCaptcha/puzzleTasks.d.ts +2 -2
  128. package/dist/tasks/puzzleCaptcha/puzzleTasks.d.ts.map +1 -1
  129. package/dist/tasks/puzzleCaptcha/puzzleTasks.js +36 -4
  130. package/dist/tasks/puzzleCaptcha/puzzleTasks.js.map +1 -1
  131. package/dist/tasks/spam/evaluateEmailSpamRules.d.ts +1 -0
  132. package/dist/tasks/spam/evaluateEmailSpamRules.d.ts.map +1 -1
  133. package/dist/tasks/spam/evaluateEmailSpamRules.js +23 -1
  134. package/dist/tasks/spam/evaluateEmailSpamRules.js.map +1 -1
  135. package/dist/tests/integration/clientSettingsPersistence.integration.test.js +23 -1
  136. package/dist/tests/integration/clientSettingsPersistence.integration.test.js.map +1 -1
  137. package/dist/tests/integration/decisionMachines.integration.test.js +3 -56
  138. package/dist/tests/integration/decisionMachines.integration.test.js.map +1 -1
  139. package/dist/tests/integration/escalationPeekBeforeConsume.integration.test.js +2 -2
  140. package/dist/tests/integration/escalationPeekBeforeConsume.integration.test.js.map +1 -1
  141. package/dist/tests/integration/imgCaptcha.integration.test.js +3 -56
  142. package/dist/tests/integration/imgCaptcha.integration.test.js.map +1 -1
  143. package/dist/tests/integration/maintenanceModeAdmin.integration.test.js +2 -29
  144. package/dist/tests/integration/maintenanceModeAdmin.integration.test.js.map +1 -1
  145. package/dist/tests/integration/powCaptcha.integration.test.js +3 -56
  146. package/dist/tests/integration/powCaptcha.integration.test.js.map +1 -1
  147. package/dist/tests/integration/routingDecisionMachines.integration.test.js +2 -2
  148. package/dist/tests/integration/routingDecisionMachines.integration.test.js.map +1 -1
  149. package/dist/tests/integration/testUtils.d.ts +1 -0
  150. package/dist/tests/integration/testUtils.d.ts.map +1 -1
  151. package/dist/tests/integration/testUtils.js +14 -0
  152. package/dist/tests/integration/testUtils.js.map +1 -1
  153. package/dist/tests/integration/testUtils.unit.test.d.ts +2 -0
  154. package/dist/tests/integration/testUtils.unit.test.d.ts.map +1 -0
  155. package/dist/tests/integration/testUtils.unit.test.js +30 -0
  156. package/dist/tests/integration/testUtils.unit.test.js.map +1 -0
  157. package/dist/tests/unit/api/admin/apiRegisterSiteKeyEndpoint.unit.test.js +1 -0
  158. package/dist/tests/unit/api/admin/apiRegisterSiteKeyEndpoint.unit.test.js.map +1 -1
  159. package/dist/tests/unit/api/adminRoutes.unit.test.js +0 -46
  160. package/dist/tests/unit/api/adminRoutes.unit.test.js.map +1 -1
  161. package/dist/tests/unit/api/captcha/getFrictionlessCaptchaChallenge/decisionMachine.unit.test.js +101 -0
  162. package/dist/tests/unit/api/captcha/getFrictionlessCaptchaChallenge/decisionMachine.unit.test.js.map +1 -1
  163. package/dist/tests/unit/api/captcha/getFrictionlessCaptchaChallenge/decryptSimdReadings.unit.test.js +12 -12
  164. package/dist/tests/unit/api/captcha/getFrictionlessCaptchaChallenge/decryptSimdReadings.unit.test.js.map +1 -1
  165. package/dist/tests/unit/api/getFrictionlessCaptchaChallenge.unit.test.js +15 -1
  166. package/dist/tests/unit/api/getFrictionlessCaptchaChallenge.unit.test.js.map +1 -1
  167. package/dist/tests/unit/tasks/captchaManager.unit.test.js +14 -30
  168. package/dist/tests/unit/tasks/captchaManager.unit.test.js.map +1 -1
  169. package/dist/tests/unit/tasks/detection/getBotScore.unit.test.js +3 -3
  170. package/dist/tests/unit/tasks/detection/getBotScore.unit.test.js.map +1 -1
  171. package/dist/tests/unit/tasks/frictionless/decryptPayload.unit.test.js +25 -7
  172. package/dist/tests/unit/tasks/frictionless/decryptPayload.unit.test.js.map +1 -1
  173. package/dist/tests/unit/tasks/frictionless/frictionlessTasks.unit.test.js +0 -1
  174. package/dist/tests/unit/tasks/frictionless/frictionlessTasks.unit.test.js.map +1 -1
  175. package/dist/tests/unit/tasks/frictionless/frictionlessTasksUtils.unit.test.js +6 -14
  176. package/dist/tests/unit/tasks/frictionless/frictionlessTasksUtils.unit.test.js.map +1 -1
  177. package/dist/tests/unit/tasks/imgCaptcha/imgCaptchaTasks.unit.test.js +99 -0
  178. package/dist/tests/unit/tasks/imgCaptcha/imgCaptchaTasks.unit.test.js.map +1 -1
  179. package/dist/tests/unit/tasks/powCaptcha/powTasks.unit.test.js +108 -0
  180. package/dist/tests/unit/tasks/powCaptcha/powTasks.unit.test.js.map +1 -1
  181. package/dist/tests/unit/tasks/puzzleCaptcha/puzzleTasks.unit.test.js +102 -1
  182. package/dist/tests/unit/tasks/puzzleCaptcha/puzzleTasks.unit.test.js.map +1 -1
  183. package/dist/tests/unit/tasks/spam/evaluateEmailSpamRules.unit.test.js +37 -1
  184. package/dist/tests/unit/tasks/spam/evaluateEmailSpamRules.unit.test.js.map +1 -1
  185. package/dist/tests/unit/tasks/writeQueueIntegration.unit.test.js +0 -2
  186. package/dist/tests/unit/tasks/writeQueueIntegration.unit.test.js.map +1 -1
  187. package/fixtures/detector-pool/bundle-0.js +15 -0
  188. package/fixtures/detector-pool/bundle-0.json +1 -0
  189. package/package.json +19 -19
  190. package/dist/api/admin/apiRemoveDetectorKeyEndpoint.d.ts +0 -23
  191. package/dist/api/admin/apiRemoveDetectorKeyEndpoint.d.ts.map +0 -1
  192. package/dist/api/admin/apiRemoveDetectorKeyEndpoint.js +0 -32
  193. package/dist/api/admin/apiRemoveDetectorKeyEndpoint.js.map +0 -1
  194. package/dist/api/admin/apiUpdateDetectorKeyEndpoint.d.ts +0 -14
  195. package/dist/api/admin/apiUpdateDetectorKeyEndpoint.d.ts.map +0 -1
  196. package/dist/api/admin/apiUpdateDetectorKeyEndpoint.js +0 -39
  197. package/dist/api/admin/apiUpdateDetectorKeyEndpoint.js.map +0 -1
  198. package/dist/cjs/api/admin/apiRemoveDetectorKeyEndpoint.cjs +0 -32
  199. package/dist/cjs/api/admin/apiUpdateDetectorKeyEndpoint.cjs +0 -39
  200. package/dist/tasks/detection/getBehavioralData.d.ts +0 -4
  201. package/dist/tasks/detection/getBehavioralData.d.ts.map +0 -1
  202. package/dist/tasks/detection/getBehavioralData.js +0 -51
  203. package/dist/tasks/detection/getBehavioralData.js.map +0 -1
  204. package/dist/tests/unit/api/admin/apiRemoveDetectorKeyEndpoint.unit.test.d.ts +0 -2
  205. package/dist/tests/unit/api/admin/apiRemoveDetectorKeyEndpoint.unit.test.d.ts.map +0 -1
  206. package/dist/tests/unit/api/admin/apiRemoveDetectorKeyEndpoint.unit.test.js +0 -57
  207. package/dist/tests/unit/api/admin/apiRemoveDetectorKeyEndpoint.unit.test.js.map +0 -1
  208. package/dist/tests/unit/api/admin/apiUpdateDetectorKeyEndpoint.unit.test.d.ts +0 -2
  209. package/dist/tests/unit/api/admin/apiUpdateDetectorKeyEndpoint.unit.test.d.ts.map +0 -1
  210. package/dist/tests/unit/api/admin/apiUpdateDetectorKeyEndpoint.unit.test.js +0 -60
  211. package/dist/tests/unit/api/admin/apiUpdateDetectorKeyEndpoint.unit.test.js.map +0 -1
  212. package/dist/tests/unit/tasks/detection/getBehavioralData.unit.test.d.ts +0 -2
  213. package/dist/tests/unit/tasks/detection/getBehavioralData.unit.test.d.ts.map +0 -1
  214. package/dist/tests/unit/tasks/detection/getBehavioralData.unit.test.js +0 -109
  215. 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
- return node_https.default.createServer(httpsOptions, apiApp).listen(apiPort, () => {
208
- env.logger.info(() => ({
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: { apiPort },
220
- msg: "Prosopo app listening"
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;
@@ -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 active detector keys and strip the throwaway
63
- * `timestamp` field. Returns `undefined` when no key decrypts.
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 decryptKeys = [...await this.getDetectorKeys(), process.env.BOT_DECRYPTION_KEY];
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
- /** Decrypt + first-hop-wins attach. No-op on decrypt failure. */
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 readings = await this.decryptSimdReadingsForAttach(simdReadingsCiphertext);
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
- async getDetectorKeys() {
275
- return await this.db.getDetectorKeys();
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). Mirrors `decryptBehavioralData`: tries each
281
- * detector key in turn (loaded by the caller, with the env-var key as
282
- * fallback). Returns null when no key works so the caller can drop
283
- * the field rather than fail the whole request.
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, decryptKeys) {
286
- const decryptSimdReadings = (await Promise.resolve().then(() => require("./detection/decodeSimd.cjs"))).default;
287
- const validKeys = decryptKeys.filter((k) => k);
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
- for (const [keyIndex, key] of validKeys.entries()) try {
293
- return await decryptSimdReadings(encryptedData, key);
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?.debug(() => ({
296
- msg: "Failed to decrypt SIMD readings with key, trying next",
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, decryptKeys) {
311
- const decryptBehavioralData = (await Promise.resolve().then(() => require("./detection/decodeBehavior.cjs"))).default;
312
- const validKeys = decryptKeys.filter((k) => k);
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
- for (const [keyIndex, key] of validKeys.entries()) try {
318
- this.logger?.debug(() => ({
319
- msg: "Attempting to decrypt behavioral data",
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?.debug(() => ({
339
- msg: "Failed to decrypt with key, trying next",
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
- let node_crypto = require("node:crypto");
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;