@prosopo/provider 4.15.17 → 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.
Files changed (178) 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 +98 -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 +51 -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 +1 -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.map +1 -1
  24. package/dist/api/captcha/getFrictionlessCaptchaChallenge/decisionMachine.js +21 -2
  25. package/dist/api/captcha/getFrictionlessCaptchaChallenge/decisionMachine.js.map +1 -1
  26. package/dist/api/captcha/getFrictionlessCaptchaChallenge/decryptSimdReadings.d.ts +1 -1
  27. package/dist/api/captcha/getFrictionlessCaptchaChallenge/decryptSimdReadings.d.ts.map +1 -1
  28. package/dist/api/captcha/getFrictionlessCaptchaChallenge/decryptSimdReadings.js +3 -3
  29. package/dist/api/captcha/getFrictionlessCaptchaChallenge/decryptSimdReadings.js.map +1 -1
  30. package/dist/api/captcha/getFrictionlessCaptchaChallenge/handler.d.ts.map +1 -1
  31. package/dist/api/captcha/getFrictionlessCaptchaChallenge/handler.js +13 -7
  32. package/dist/api/captcha/getFrictionlessCaptchaChallenge/handler.js.map +1 -1
  33. package/dist/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.d.ts +3 -0
  34. package/dist/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.d.ts.map +1 -1
  35. package/dist/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.js +52 -18
  36. package/dist/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.js.map +1 -1
  37. package/dist/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.powFallback.test.d.ts +2 -0
  38. package/dist/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.powFallback.test.d.ts.map +1 -0
  39. package/dist/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.powFallback.test.js +86 -0
  40. package/dist/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.powFallback.test.js.map +1 -0
  41. package/dist/api/captcha/submitPoWCaptchaSolution.d.ts.map +1 -1
  42. package/dist/api/captcha/submitPoWCaptchaSolution.js +1 -1
  43. package/dist/api/captcha/submitPoWCaptchaSolution.js.map +1 -1
  44. package/dist/api/captcha.d.ts.map +1 -1
  45. package/dist/api/captcha.js +6 -0
  46. package/dist/api/captcha.js.map +1 -1
  47. package/dist/api/startProviderApi.d.ts.map +1 -1
  48. package/dist/api/startProviderApi.js +21 -3
  49. package/dist/api/startProviderApi.js.map +1 -1
  50. package/dist/cjs/api/admin/apiAdminRoutesProvider.cjs +3 -5
  51. package/dist/cjs/api/admin/apiReplaceDetectorPoolEndpoint.cjs +56 -0
  52. package/dist/cjs/api/captcha/assignDetectorBundle.cjs +51 -0
  53. package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/decisionMachine.cjs +21 -2
  54. package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/decryptSimdReadings.cjs +3 -3
  55. package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/handler.cjs +12 -6
  56. package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.cjs +52 -17
  57. package/dist/cjs/api/captcha/submitPoWCaptchaSolution.cjs +1 -1
  58. package/dist/cjs/api/captcha.cjs +6 -0
  59. package/dist/cjs/api/startProviderApi.cjs +24 -5
  60. package/dist/cjs/tasks/captchaManager.cjs +76 -59
  61. package/dist/cjs/tasks/client/clientTasks.cjs +1 -29
  62. package/dist/cjs/tasks/detection/bundlePool.cjs +220 -0
  63. package/dist/cjs/tasks/detection/decodeBehavior.cjs +644 -362
  64. package/dist/cjs/tasks/detection/decodeBehavior.js +1 -1
  65. package/dist/cjs/tasks/detection/decodePayload.cjs +956 -617
  66. package/dist/cjs/tasks/detection/decodePayload.js +1 -1
  67. package/dist/cjs/tasks/detection/decodeSimd.cjs +723 -411
  68. package/dist/cjs/tasks/detection/decodeSimd.js +1 -1
  69. package/dist/cjs/tasks/detection/getBotScore.cjs +2 -2
  70. package/dist/cjs/tasks/frictionless/frictionlessTasks.cjs +35 -12
  71. package/dist/cjs/tasks/frictionless/frictionlessTasksUtils.cjs +1 -2
  72. package/dist/cjs/tasks/imgCaptcha/imgCaptchaTasks.cjs +6 -6
  73. package/dist/cjs/tasks/powCaptcha/powTasks.cjs +2 -2
  74. package/dist/cjs/tasks/puzzleCaptcha/puzzleTasks.cjs +2 -2
  75. package/dist/tasks/captchaManager.d.ts +12 -4
  76. package/dist/tasks/captchaManager.d.ts.map +1 -1
  77. package/dist/tasks/captchaManager.js +76 -59
  78. package/dist/tasks/captchaManager.js.map +1 -1
  79. package/dist/tasks/client/clientTasks.d.ts +0 -2
  80. package/dist/tasks/client/clientTasks.d.ts.map +1 -1
  81. package/dist/tasks/client/clientTasks.js +1 -29
  82. package/dist/tasks/client/clientTasks.js.map +1 -1
  83. package/dist/tasks/detection/bundlePool.d.ts +32 -0
  84. package/dist/tasks/detection/bundlePool.d.ts.map +1 -0
  85. package/dist/tasks/detection/bundlePool.js +215 -0
  86. package/dist/tasks/detection/bundlePool.js.map +1 -0
  87. package/dist/tasks/detection/bundlePool.test.d.ts +2 -0
  88. package/dist/tasks/detection/bundlePool.test.d.ts.map +1 -0
  89. package/dist/tasks/detection/bundlePool.test.js +178 -0
  90. package/dist/tasks/detection/bundlePool.test.js.map +1 -0
  91. package/dist/tasks/detection/decodeBehavior.d.ts +2 -2
  92. package/dist/tasks/detection/decodeBehavior.d.ts.map +1 -1
  93. package/dist/tasks/detection/decodeBehavior.js +1 -1
  94. package/dist/tasks/detection/decodeBehavior.js.map +1 -1
  95. package/dist/tasks/detection/decodePayload.d.ts +2 -2
  96. package/dist/tasks/detection/decodePayload.d.ts.map +1 -1
  97. package/dist/tasks/detection/decodePayload.js +1 -1
  98. package/dist/tasks/detection/decodePayload.js.map +1 -1
  99. package/dist/tasks/detection/decodeSimd.d.ts +2 -2
  100. package/dist/tasks/detection/decodeSimd.d.ts.map +1 -1
  101. package/dist/tasks/detection/decodeSimd.js +1 -1
  102. package/dist/tasks/detection/decodeSimd.js.map +1 -1
  103. package/dist/tasks/detection/getBotScore.d.ts +1 -1
  104. package/dist/tasks/detection/getBotScore.d.ts.map +1 -1
  105. package/dist/tasks/detection/getBotScore.js +3 -3
  106. package/dist/tasks/detection/getBotScore.js.map +1 -1
  107. package/dist/tasks/frictionless/frictionlessTasks.d.ts +10 -2
  108. package/dist/tasks/frictionless/frictionlessTasks.d.ts.map +1 -1
  109. package/dist/tasks/frictionless/frictionlessTasks.js +35 -12
  110. package/dist/tasks/frictionless/frictionlessTasks.js.map +1 -1
  111. package/dist/tasks/frictionless/frictionlessTasksUtils.d.ts +1 -1
  112. package/dist/tasks/frictionless/frictionlessTasksUtils.d.ts.map +1 -1
  113. package/dist/tasks/frictionless/frictionlessTasksUtils.js +1 -2
  114. package/dist/tasks/frictionless/frictionlessTasksUtils.js.map +1 -1
  115. package/dist/tasks/imgCaptcha/imgCaptchaTasks.d.ts.map +1 -1
  116. package/dist/tasks/imgCaptcha/imgCaptchaTasks.js +6 -6
  117. package/dist/tasks/imgCaptcha/imgCaptchaTasks.js.map +1 -1
  118. package/dist/tasks/powCaptcha/powTasks.d.ts.map +1 -1
  119. package/dist/tasks/powCaptcha/powTasks.js +2 -2
  120. package/dist/tasks/powCaptcha/powTasks.js.map +1 -1
  121. package/dist/tasks/puzzleCaptcha/puzzleTasks.d.ts.map +1 -1
  122. package/dist/tasks/puzzleCaptcha/puzzleTasks.js +2 -2
  123. package/dist/tasks/puzzleCaptcha/puzzleTasks.js.map +1 -1
  124. package/dist/tests/integration/maintenanceModeAdmin.integration.test.js +0 -28
  125. package/dist/tests/integration/maintenanceModeAdmin.integration.test.js.map +1 -1
  126. package/dist/tests/unit/api/admin/apiRegisterSiteKeyEndpoint.unit.test.js +1 -0
  127. package/dist/tests/unit/api/admin/apiRegisterSiteKeyEndpoint.unit.test.js.map +1 -1
  128. package/dist/tests/unit/api/adminRoutes.unit.test.js +0 -46
  129. package/dist/tests/unit/api/adminRoutes.unit.test.js.map +1 -1
  130. package/dist/tests/unit/api/captcha/getFrictionlessCaptchaChallenge/decisionMachine.unit.test.js +51 -0
  131. package/dist/tests/unit/api/captcha/getFrictionlessCaptchaChallenge/decisionMachine.unit.test.js.map +1 -1
  132. package/dist/tests/unit/api/captcha/getFrictionlessCaptchaChallenge/decryptSimdReadings.unit.test.js +12 -12
  133. package/dist/tests/unit/api/captcha/getFrictionlessCaptchaChallenge/decryptSimdReadings.unit.test.js.map +1 -1
  134. package/dist/tests/unit/api/getFrictionlessCaptchaChallenge.unit.test.js +15 -1
  135. package/dist/tests/unit/api/getFrictionlessCaptchaChallenge.unit.test.js.map +1 -1
  136. package/dist/tests/unit/tasks/captchaManager.unit.test.js +14 -30
  137. package/dist/tests/unit/tasks/captchaManager.unit.test.js.map +1 -1
  138. package/dist/tests/unit/tasks/detection/getBotScore.unit.test.js +3 -3
  139. package/dist/tests/unit/tasks/detection/getBotScore.unit.test.js.map +1 -1
  140. package/dist/tests/unit/tasks/frictionless/decryptPayload.unit.test.js +25 -7
  141. package/dist/tests/unit/tasks/frictionless/decryptPayload.unit.test.js.map +1 -1
  142. package/dist/tests/unit/tasks/frictionless/frictionlessTasks.unit.test.js +0 -1
  143. package/dist/tests/unit/tasks/frictionless/frictionlessTasks.unit.test.js.map +1 -1
  144. package/dist/tests/unit/tasks/frictionless/frictionlessTasksUtils.unit.test.js +6 -14
  145. package/dist/tests/unit/tasks/frictionless/frictionlessTasksUtils.unit.test.js.map +1 -1
  146. package/dist/tests/unit/tasks/puzzleCaptcha/puzzleTasks.unit.test.js +0 -1
  147. package/dist/tests/unit/tasks/puzzleCaptcha/puzzleTasks.unit.test.js.map +1 -1
  148. package/dist/tests/unit/tasks/writeQueueIntegration.unit.test.js +0 -2
  149. package/dist/tests/unit/tasks/writeQueueIntegration.unit.test.js.map +1 -1
  150. package/fixtures/detector-pool/bundle-0.js +15 -0
  151. package/fixtures/detector-pool/bundle-0.json +1 -0
  152. package/package.json +14 -14
  153. package/dist/api/admin/apiRemoveDetectorKeyEndpoint.d.ts +0 -23
  154. package/dist/api/admin/apiRemoveDetectorKeyEndpoint.d.ts.map +0 -1
  155. package/dist/api/admin/apiRemoveDetectorKeyEndpoint.js +0 -32
  156. package/dist/api/admin/apiRemoveDetectorKeyEndpoint.js.map +0 -1
  157. package/dist/api/admin/apiUpdateDetectorKeyEndpoint.d.ts +0 -14
  158. package/dist/api/admin/apiUpdateDetectorKeyEndpoint.d.ts.map +0 -1
  159. package/dist/api/admin/apiUpdateDetectorKeyEndpoint.js +0 -39
  160. package/dist/api/admin/apiUpdateDetectorKeyEndpoint.js.map +0 -1
  161. package/dist/cjs/api/admin/apiRemoveDetectorKeyEndpoint.cjs +0 -32
  162. package/dist/cjs/api/admin/apiUpdateDetectorKeyEndpoint.cjs +0 -39
  163. package/dist/tasks/detection/getBehavioralData.d.ts +0 -4
  164. package/dist/tasks/detection/getBehavioralData.d.ts.map +0 -1
  165. package/dist/tasks/detection/getBehavioralData.js +0 -51
  166. package/dist/tasks/detection/getBehavioralData.js.map +0 -1
  167. package/dist/tests/unit/api/admin/apiRemoveDetectorKeyEndpoint.unit.test.d.ts +0 -2
  168. package/dist/tests/unit/api/admin/apiRemoveDetectorKeyEndpoint.unit.test.d.ts.map +0 -1
  169. package/dist/tests/unit/api/admin/apiRemoveDetectorKeyEndpoint.unit.test.js +0 -57
  170. package/dist/tests/unit/api/admin/apiRemoveDetectorKeyEndpoint.unit.test.js.map +0 -1
  171. package/dist/tests/unit/api/admin/apiUpdateDetectorKeyEndpoint.unit.test.d.ts +0 -2
  172. package/dist/tests/unit/api/admin/apiUpdateDetectorKeyEndpoint.unit.test.d.ts.map +0 -1
  173. package/dist/tests/unit/api/admin/apiUpdateDetectorKeyEndpoint.unit.test.js +0 -60
  174. package/dist/tests/unit/api/admin/apiUpdateDetectorKeyEndpoint.unit.test.js.map +0 -1
  175. package/dist/tests/unit/tasks/detection/getBehavioralData.unit.test.d.ts +0 -2
  176. package/dist/tests/unit/tasks/detection/getBehavioralData.unit.test.d.ts.map +0 -1
  177. package/dist/tests/unit/tasks/detection/getBehavioralData.unit.test.js +0 -109
  178. 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;