@prosopo/database 4.2.10 → 4.3.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 (315) hide show
  1. package/.turbo/turbo-build$colon$cjs.log +2 -2
  2. package/.turbo/{turbo-build.log → turbo-build$colon$esm.log} +15 -16
  3. package/.turbo/turbo-build$colon$tsc.log +15 -15
  4. package/.turbo/turbo-build$colon$types.log +1 -0
  5. package/CHANGELOG.md +26 -0
  6. package/dist/cjs/databases/client.cjs +3 -1
  7. package/dist/{databases → esm/databases}/client.js +3 -1
  8. package/dist/tsc/base/index.d.ts.map +1 -0
  9. package/dist/tsc/base/index.js +3 -0
  10. package/dist/tsc/base/index.js.map +1 -0
  11. package/dist/tsc/base/mongo.d.ts.map +1 -0
  12. package/dist/tsc/base/mongo.js +142 -0
  13. package/dist/tsc/base/mongo.js.map +1 -0
  14. package/dist/tsc/base/mongoMemory.d.ts.map +1 -0
  15. package/dist/tsc/base/mongoMemory.js +21 -0
  16. package/dist/tsc/base/mongoMemory.js.map +1 -0
  17. package/dist/tsc/databases/captcha.d.ts.map +1 -0
  18. package/dist/tsc/databases/captcha.js +276 -0
  19. package/dist/tsc/databases/captcha.js.map +1 -0
  20. package/dist/tsc/databases/centralDbStreamer.d.ts.map +1 -0
  21. package/dist/tsc/databases/centralDbStreamer.js +163 -0
  22. package/dist/tsc/databases/centralDbStreamer.js.map +1 -0
  23. package/dist/{databases → tsc/databases}/client.d.ts +1 -1
  24. package/dist/tsc/databases/client.d.ts.map +1 -0
  25. package/dist/tsc/databases/client.js +72 -0
  26. package/dist/tsc/databases/client.js.map +1 -0
  27. package/dist/tsc/databases/clientRecordCache.d.ts.map +1 -0
  28. package/dist/tsc/databases/clientRecordCache.js +31 -0
  29. package/dist/tsc/databases/clientRecordCache.js.map +1 -0
  30. package/dist/tsc/databases/index.d.ts.map +1 -0
  31. package/dist/tsc/databases/index.js +18 -0
  32. package/dist/tsc/databases/index.js.map +1 -0
  33. package/dist/tsc/databases/provider.d.ts.map +1 -0
  34. package/dist/tsc/databases/provider.js +2052 -0
  35. package/dist/tsc/databases/provider.js.map +1 -0
  36. package/dist/tsc/errorSites.d.ts.map +1 -0
  37. package/dist/{errorSites.js.map → tsc/errorSites.js.map} +1 -1
  38. package/dist/tsc/index.d.ts.map +1 -0
  39. package/dist/tsc/index.js +7 -0
  40. package/dist/tsc/index.js.map +1 -0
  41. package/dist/tsc/mongooseOptions.d.ts.map +1 -0
  42. package/dist/tsc/mongooseOptions.js +56 -0
  43. package/dist/tsc/mongooseOptions.js.map +1 -0
  44. package/dist/tsc/redisCache.d.ts.map +1 -0
  45. package/dist/tsc/redisCache.js +340 -0
  46. package/dist/tsc/redisCache.js.map +1 -0
  47. package/dist/tsc/tests/integration/centralStreamingEnrichment.integration.test.d.ts.map +1 -0
  48. package/dist/tsc/tests/integration/centralStreamingEnrichment.integration.test.js.map +1 -0
  49. package/dist/tsc/tests/integration/clientRecordCache.integration.test.d.ts.map +1 -0
  50. package/dist/tsc/tests/integration/clientRecordCache.integration.test.js.map +1 -0
  51. package/dist/tsc/tests/integration/commitmentRecordProjection.integration.test.d.ts.map +1 -0
  52. package/dist/tsc/tests/integration/commitmentRecordProjection.integration.test.js.map +1 -0
  53. package/dist/tsc/tests/integration/commitmentUpdateTimestamp.integration.test.d.ts.map +1 -0
  54. package/dist/tsc/tests/integration/commitmentUpdateTimestamp.integration.test.js.map +1 -0
  55. package/dist/tsc/tests/integration/commitmentUpsertNoMutation.integration.test.d.ts.map +1 -0
  56. package/dist/tsc/tests/integration/commitmentUpsertNoMutation.integration.test.js.map +1 -0
  57. package/dist/tsc/tests/integration/countCommitmentsByNormalisedEmail.integration.test.d.ts.map +1 -0
  58. package/dist/tsc/tests/integration/countCommitmentsByNormalisedEmail.integration.test.js.map +1 -0
  59. package/dist/tsc/tests/integration/getUpdatedClients.integration.test.d.ts.map +1 -0
  60. package/dist/{tests → tsc/tests}/integration/getUpdatedClients.integration.test.js +39 -1
  61. package/dist/tsc/tests/integration/getUpdatedClients.integration.test.js.map +1 -0
  62. package/dist/tsc/tests/integration/ipInfoPersistence.integration.test.d.ts.map +1 -0
  63. package/dist/tsc/tests/integration/ipInfoPersistence.integration.test.js.map +1 -0
  64. package/dist/tsc/tests/integration/projectionContract.d.ts.map +1 -0
  65. package/dist/tsc/tests/integration/projectionContract.js.map +1 -0
  66. package/dist/tsc/tests/integration/projectionContracts.integration.test.d.ts.map +1 -0
  67. package/dist/tsc/tests/integration/projectionContracts.integration.test.js.map +1 -0
  68. package/dist/tsc/tests/integration/providedIpPipelineCast.integration.test.d.ts.map +1 -0
  69. package/dist/tsc/tests/integration/providedIpPipelineCast.integration.test.js.map +1 -0
  70. package/dist/tsc/tests/integration/puzzleCentralStreaming.integration.test.d.ts.map +1 -0
  71. package/dist/tsc/tests/integration/puzzleCentralStreaming.integration.test.js.map +1 -0
  72. package/dist/tsc/tests/integration/redisCacheSessionPatch.integration.test.d.ts.map +1 -0
  73. package/dist/tsc/tests/integration/redisCacheSessionPatch.integration.test.js.map +1 -0
  74. package/dist/tsc/tests/integration/saveCaptchasSessionUpsert.integration.test.d.ts.map +1 -0
  75. package/dist/tsc/tests/integration/saveCaptchasSessionUpsert.integration.test.js.map +1 -0
  76. package/dist/tsc/tests/integration/serverCheckClaim.integration.test.d.ts.map +1 -0
  77. package/dist/tsc/tests/integration/serverCheckClaim.integration.test.js.map +1 -0
  78. package/dist/tsc/tests/integration/sessionRecordProjection.integration.test.d.ts.map +1 -0
  79. package/dist/tsc/tests/integration/sessionRecordProjection.integration.test.js.map +1 -0
  80. package/dist/tsc/tests/mongoGlobalSetup.d.ts.map +1 -0
  81. package/dist/tsc/tests/mongoGlobalSetup.js.map +1 -0
  82. package/dist/tsc/tests/mongooseOptions.unit.test.d.ts.map +1 -0
  83. package/dist/tsc/tests/mongooseOptions.unit.test.js.map +1 -0
  84. package/dist/tsc/tests/unit/base/mongo.unit.test.d.ts.map +1 -0
  85. package/dist/tsc/tests/unit/base/mongo.unit.test.js.map +1 -0
  86. package/dist/tsc/tests/unit/captchaLabel.unit.test.d.ts.map +1 -0
  87. package/dist/tsc/tests/unit/captchaLabel.unit.test.js.map +1 -0
  88. package/dist/tsc/tests/unit/databases/centralDbStreamer.unit.test.d.ts.map +1 -0
  89. package/dist/tsc/tests/unit/databases/centralDbStreamer.unit.test.js.map +1 -0
  90. package/dist/tsc/tests/unit/detectorBundleTtl.unit.test.d.ts.map +1 -0
  91. package/dist/tsc/tests/unit/detectorBundleTtl.unit.test.js.map +1 -0
  92. package/dist/tsc/tests/unit/inputMethods.unit.test.d.ts.map +1 -0
  93. package/dist/tsc/tests/unit/inputMethods.unit.test.js.map +1 -0
  94. package/dist/tsc/tests/unit/redisCacheEscalation.unit.test.d.ts.map +1 -0
  95. package/dist/tsc/tests/unit/redisCacheEscalation.unit.test.js.map +1 -0
  96. package/dist/types/base/index.d.ts +3 -0
  97. package/dist/types/base/index.d.ts.map +1 -0
  98. package/dist/types/base/mongo.d.ts +24 -0
  99. package/dist/types/base/mongo.d.ts.map +1 -0
  100. package/dist/types/base/mongoMemory.d.ts +10 -0
  101. package/dist/types/base/mongoMemory.d.ts.map +1 -0
  102. package/dist/types/databases/captcha.d.ts +34 -0
  103. package/dist/types/databases/captcha.d.ts.map +1 -0
  104. package/dist/types/databases/centralDbStreamer.d.ts +29 -0
  105. package/dist/types/databases/centralDbStreamer.d.ts.map +1 -0
  106. package/dist/types/databases/client.d.ts +12 -0
  107. package/dist/types/databases/client.d.ts.map +1 -0
  108. package/dist/types/databases/clientRecordCache.d.ts +11 -0
  109. package/dist/types/databases/clientRecordCache.d.ts.map +1 -0
  110. package/dist/types/databases/index.d.ts +17 -0
  111. package/dist/types/databases/index.d.ts.map +1 -0
  112. package/dist/types/databases/provider.d.ts +171 -0
  113. package/dist/types/databases/provider.d.ts.map +1 -0
  114. package/dist/types/errorSites.d.ts +57 -0
  115. package/dist/types/errorSites.d.ts.map +1 -0
  116. package/dist/types/index.d.ts +5 -0
  117. package/dist/types/index.d.ts.map +1 -0
  118. package/dist/types/mongooseOptions.d.ts +15 -0
  119. package/dist/types/mongooseOptions.d.ts.map +1 -0
  120. package/dist/types/redisCache.d.ts +26 -0
  121. package/dist/types/redisCache.d.ts.map +1 -0
  122. package/dist/types/tests/integration/centralStreamingEnrichment.integration.test.d.ts +2 -0
  123. package/dist/types/tests/integration/centralStreamingEnrichment.integration.test.d.ts.map +1 -0
  124. package/dist/types/tests/integration/clientRecordCache.integration.test.d.ts +2 -0
  125. package/dist/types/tests/integration/clientRecordCache.integration.test.d.ts.map +1 -0
  126. package/dist/types/tests/integration/commitmentRecordProjection.integration.test.d.ts +2 -0
  127. package/dist/types/tests/integration/commitmentRecordProjection.integration.test.d.ts.map +1 -0
  128. package/dist/types/tests/integration/commitmentUpdateTimestamp.integration.test.d.ts +2 -0
  129. package/dist/types/tests/integration/commitmentUpdateTimestamp.integration.test.d.ts.map +1 -0
  130. package/dist/types/tests/integration/commitmentUpsertNoMutation.integration.test.d.ts +2 -0
  131. package/dist/types/tests/integration/commitmentUpsertNoMutation.integration.test.d.ts.map +1 -0
  132. package/dist/types/tests/integration/countCommitmentsByNormalisedEmail.integration.test.d.ts +2 -0
  133. package/dist/types/tests/integration/countCommitmentsByNormalisedEmail.integration.test.d.ts.map +1 -0
  134. package/dist/types/tests/integration/getUpdatedClients.integration.test.d.ts +2 -0
  135. package/dist/types/tests/integration/getUpdatedClients.integration.test.d.ts.map +1 -0
  136. package/dist/types/tests/integration/ipInfoPersistence.integration.test.d.ts +2 -0
  137. package/dist/types/tests/integration/ipInfoPersistence.integration.test.d.ts.map +1 -0
  138. package/dist/types/tests/integration/projectionContract.d.ts +9 -0
  139. package/dist/types/tests/integration/projectionContract.d.ts.map +1 -0
  140. package/dist/types/tests/integration/projectionContracts.integration.test.d.ts +2 -0
  141. package/dist/types/tests/integration/projectionContracts.integration.test.d.ts.map +1 -0
  142. package/dist/types/tests/integration/providedIpPipelineCast.integration.test.d.ts +2 -0
  143. package/dist/types/tests/integration/providedIpPipelineCast.integration.test.d.ts.map +1 -0
  144. package/dist/types/tests/integration/puzzleCentralStreaming.integration.test.d.ts +2 -0
  145. package/dist/types/tests/integration/puzzleCentralStreaming.integration.test.d.ts.map +1 -0
  146. package/dist/types/tests/integration/redisCacheSessionPatch.integration.test.d.ts +2 -0
  147. package/dist/types/tests/integration/redisCacheSessionPatch.integration.test.d.ts.map +1 -0
  148. package/dist/types/tests/integration/saveCaptchasSessionUpsert.integration.test.d.ts +2 -0
  149. package/dist/types/tests/integration/saveCaptchasSessionUpsert.integration.test.d.ts.map +1 -0
  150. package/dist/types/tests/integration/serverCheckClaim.integration.test.d.ts +2 -0
  151. package/dist/types/tests/integration/serverCheckClaim.integration.test.d.ts.map +1 -0
  152. package/dist/types/tests/integration/sessionRecordProjection.integration.test.d.ts +2 -0
  153. package/dist/types/tests/integration/sessionRecordProjection.integration.test.d.ts.map +1 -0
  154. package/dist/types/tests/mongoGlobalSetup.d.ts +2 -0
  155. package/dist/types/tests/mongoGlobalSetup.d.ts.map +1 -0
  156. package/dist/types/tests/mongooseOptions.unit.test.d.ts +2 -0
  157. package/dist/types/tests/mongooseOptions.unit.test.d.ts.map +1 -0
  158. package/dist/types/tests/unit/base/mongo.unit.test.d.ts +2 -0
  159. package/dist/types/tests/unit/base/mongo.unit.test.d.ts.map +1 -0
  160. package/dist/types/tests/unit/captchaLabel.unit.test.d.ts +2 -0
  161. package/dist/types/tests/unit/captchaLabel.unit.test.d.ts.map +1 -0
  162. package/dist/types/tests/unit/databases/centralDbStreamer.unit.test.d.ts +2 -0
  163. package/dist/types/tests/unit/databases/centralDbStreamer.unit.test.d.ts.map +1 -0
  164. package/dist/types/tests/unit/detectorBundleTtl.unit.test.d.ts +2 -0
  165. package/dist/types/tests/unit/detectorBundleTtl.unit.test.d.ts.map +1 -0
  166. package/dist/types/tests/unit/inputMethods.unit.test.d.ts +2 -0
  167. package/dist/types/tests/unit/inputMethods.unit.test.d.ts.map +1 -0
  168. package/dist/types/tests/unit/redisCacheEscalation.unit.test.d.ts +2 -0
  169. package/dist/types/tests/unit/redisCacheEscalation.unit.test.d.ts.map +1 -0
  170. package/package.json +16 -17
  171. package/dist/base/index.d.ts.map +0 -1
  172. package/dist/base/index.js.map +0 -1
  173. package/dist/base/mongo.d.ts.map +0 -1
  174. package/dist/base/mongo.js.map +0 -1
  175. package/dist/base/mongoMemory.d.ts.map +0 -1
  176. package/dist/base/mongoMemory.js.map +0 -1
  177. package/dist/databases/captcha.d.ts.map +0 -1
  178. package/dist/databases/captcha.js.map +0 -1
  179. package/dist/databases/centralDbStreamer.d.ts.map +0 -1
  180. package/dist/databases/centralDbStreamer.js.map +0 -1
  181. package/dist/databases/client.d.ts.map +0 -1
  182. package/dist/databases/client.js.map +0 -1
  183. package/dist/databases/clientRecordCache.d.ts.map +0 -1
  184. package/dist/databases/clientRecordCache.js.map +0 -1
  185. package/dist/databases/index.d.ts.map +0 -1
  186. package/dist/databases/index.js.map +0 -1
  187. package/dist/databases/provider.d.ts.map +0 -1
  188. package/dist/databases/provider.js.map +0 -1
  189. package/dist/errorSites.d.ts.map +0 -1
  190. package/dist/index.d.ts.map +0 -1
  191. package/dist/index.js.map +0 -1
  192. package/dist/mongooseOptions.d.ts.map +0 -1
  193. package/dist/mongooseOptions.js.map +0 -1
  194. package/dist/redisCache.d.ts.map +0 -1
  195. package/dist/redisCache.js.map +0 -1
  196. package/dist/tests/integration/centralStreamingEnrichment.integration.test.d.ts.map +0 -1
  197. package/dist/tests/integration/centralStreamingEnrichment.integration.test.js.map +0 -1
  198. package/dist/tests/integration/clientRecordCache.integration.test.d.ts.map +0 -1
  199. package/dist/tests/integration/clientRecordCache.integration.test.js.map +0 -1
  200. package/dist/tests/integration/commitmentRecordProjection.integration.test.d.ts.map +0 -1
  201. package/dist/tests/integration/commitmentRecordProjection.integration.test.js.map +0 -1
  202. package/dist/tests/integration/commitmentUpdateTimestamp.integration.test.d.ts.map +0 -1
  203. package/dist/tests/integration/commitmentUpdateTimestamp.integration.test.js.map +0 -1
  204. package/dist/tests/integration/commitmentUpsertNoMutation.integration.test.d.ts.map +0 -1
  205. package/dist/tests/integration/commitmentUpsertNoMutation.integration.test.js.map +0 -1
  206. package/dist/tests/integration/countCommitmentsByNormalisedEmail.integration.test.d.ts.map +0 -1
  207. package/dist/tests/integration/countCommitmentsByNormalisedEmail.integration.test.js.map +0 -1
  208. package/dist/tests/integration/getUpdatedClients.integration.test.d.ts.map +0 -1
  209. package/dist/tests/integration/getUpdatedClients.integration.test.js.map +0 -1
  210. package/dist/tests/integration/ipInfoPersistence.integration.test.d.ts.map +0 -1
  211. package/dist/tests/integration/ipInfoPersistence.integration.test.js.map +0 -1
  212. package/dist/tests/integration/projectionContract.d.ts.map +0 -1
  213. package/dist/tests/integration/projectionContract.js.map +0 -1
  214. package/dist/tests/integration/projectionContracts.integration.test.d.ts.map +0 -1
  215. package/dist/tests/integration/projectionContracts.integration.test.js.map +0 -1
  216. package/dist/tests/integration/providedIpPipelineCast.integration.test.d.ts.map +0 -1
  217. package/dist/tests/integration/providedIpPipelineCast.integration.test.js.map +0 -1
  218. package/dist/tests/integration/puzzleCentralStreaming.integration.test.d.ts.map +0 -1
  219. package/dist/tests/integration/puzzleCentralStreaming.integration.test.js.map +0 -1
  220. package/dist/tests/integration/redisCacheSessionPatch.integration.test.d.ts.map +0 -1
  221. package/dist/tests/integration/redisCacheSessionPatch.integration.test.js.map +0 -1
  222. package/dist/tests/integration/saveCaptchasSessionUpsert.integration.test.d.ts.map +0 -1
  223. package/dist/tests/integration/saveCaptchasSessionUpsert.integration.test.js.map +0 -1
  224. package/dist/tests/integration/serverCheckClaim.integration.test.d.ts.map +0 -1
  225. package/dist/tests/integration/serverCheckClaim.integration.test.js.map +0 -1
  226. package/dist/tests/integration/sessionRecordProjection.integration.test.d.ts.map +0 -1
  227. package/dist/tests/integration/sessionRecordProjection.integration.test.js.map +0 -1
  228. package/dist/tests/mongoGlobalSetup.d.ts.map +0 -1
  229. package/dist/tests/mongoGlobalSetup.js.map +0 -1
  230. package/dist/tests/mongooseOptions.unit.test.d.ts.map +0 -1
  231. package/dist/tests/mongooseOptions.unit.test.js.map +0 -1
  232. package/dist/tests/unit/base/mongo.unit.test.d.ts.map +0 -1
  233. package/dist/tests/unit/base/mongo.unit.test.js.map +0 -1
  234. package/dist/tests/unit/captchaLabel.unit.test.d.ts.map +0 -1
  235. package/dist/tests/unit/captchaLabel.unit.test.js.map +0 -1
  236. package/dist/tests/unit/databases/centralDbStreamer.unit.test.d.ts.map +0 -1
  237. package/dist/tests/unit/databases/centralDbStreamer.unit.test.js.map +0 -1
  238. package/dist/tests/unit/detectorBundleTtl.unit.test.d.ts.map +0 -1
  239. package/dist/tests/unit/detectorBundleTtl.unit.test.js.map +0 -1
  240. package/dist/tests/unit/inputMethods.unit.test.d.ts.map +0 -1
  241. package/dist/tests/unit/inputMethods.unit.test.js.map +0 -1
  242. package/dist/tests/unit/redisCacheEscalation.unit.test.d.ts.map +0 -1
  243. package/dist/tests/unit/redisCacheEscalation.unit.test.js.map +0 -1
  244. /package/dist/{_virtual → esm/_virtual}/_rolldown/runtime.js +0 -0
  245. /package/dist/{base → esm/base}/index.js +0 -0
  246. /package/dist/{base → esm/base}/mongo.js +0 -0
  247. /package/dist/{base → esm/base}/mongoMemory.js +0 -0
  248. /package/dist/{databases → esm/databases}/captcha.js +0 -0
  249. /package/dist/{databases → esm/databases}/centralDbStreamer.js +0 -0
  250. /package/dist/{databases → esm/databases}/clientRecordCache.js +0 -0
  251. /package/dist/{databases → esm/databases}/index.js +0 -0
  252. /package/dist/{databases → esm/databases}/provider.js +0 -0
  253. /package/dist/{index.js → esm/index.js} +0 -0
  254. /package/dist/{mongooseOptions.js → esm/mongooseOptions.js} +0 -0
  255. /package/dist/{redisCache.js → esm/redisCache.js} +0 -0
  256. /package/dist/{base → tsc/base}/index.d.ts +0 -0
  257. /package/dist/{base → tsc/base}/mongo.d.ts +0 -0
  258. /package/dist/{base → tsc/base}/mongoMemory.d.ts +0 -0
  259. /package/dist/{databases → tsc/databases}/captcha.d.ts +0 -0
  260. /package/dist/{databases → tsc/databases}/centralDbStreamer.d.ts +0 -0
  261. /package/dist/{databases → tsc/databases}/clientRecordCache.d.ts +0 -0
  262. /package/dist/{databases → tsc/databases}/index.d.ts +0 -0
  263. /package/dist/{databases → tsc/databases}/provider.d.ts +0 -0
  264. /package/dist/{errorSites.d.ts → tsc/errorSites.d.ts} +0 -0
  265. /package/dist/{errorSites.js → tsc/errorSites.js} +0 -0
  266. /package/dist/{index.d.ts → tsc/index.d.ts} +0 -0
  267. /package/dist/{mongooseOptions.d.ts → tsc/mongooseOptions.d.ts} +0 -0
  268. /package/dist/{redisCache.d.ts → tsc/redisCache.d.ts} +0 -0
  269. /package/dist/{tests → tsc/tests}/integration/centralStreamingEnrichment.integration.test.d.ts +0 -0
  270. /package/dist/{tests → tsc/tests}/integration/centralStreamingEnrichment.integration.test.js +0 -0
  271. /package/dist/{tests → tsc/tests}/integration/clientRecordCache.integration.test.d.ts +0 -0
  272. /package/dist/{tests → tsc/tests}/integration/clientRecordCache.integration.test.js +0 -0
  273. /package/dist/{tests → tsc/tests}/integration/commitmentRecordProjection.integration.test.d.ts +0 -0
  274. /package/dist/{tests → tsc/tests}/integration/commitmentRecordProjection.integration.test.js +0 -0
  275. /package/dist/{tests → tsc/tests}/integration/commitmentUpdateTimestamp.integration.test.d.ts +0 -0
  276. /package/dist/{tests → tsc/tests}/integration/commitmentUpdateTimestamp.integration.test.js +0 -0
  277. /package/dist/{tests → tsc/tests}/integration/commitmentUpsertNoMutation.integration.test.d.ts +0 -0
  278. /package/dist/{tests → tsc/tests}/integration/commitmentUpsertNoMutation.integration.test.js +0 -0
  279. /package/dist/{tests → tsc/tests}/integration/countCommitmentsByNormalisedEmail.integration.test.d.ts +0 -0
  280. /package/dist/{tests → tsc/tests}/integration/countCommitmentsByNormalisedEmail.integration.test.js +0 -0
  281. /package/dist/{tests → tsc/tests}/integration/getUpdatedClients.integration.test.d.ts +0 -0
  282. /package/dist/{tests → tsc/tests}/integration/ipInfoPersistence.integration.test.d.ts +0 -0
  283. /package/dist/{tests → tsc/tests}/integration/ipInfoPersistence.integration.test.js +0 -0
  284. /package/dist/{tests → tsc/tests}/integration/projectionContract.d.ts +0 -0
  285. /package/dist/{tests → tsc/tests}/integration/projectionContract.js +0 -0
  286. /package/dist/{tests → tsc/tests}/integration/projectionContracts.integration.test.d.ts +0 -0
  287. /package/dist/{tests → tsc/tests}/integration/projectionContracts.integration.test.js +0 -0
  288. /package/dist/{tests → tsc/tests}/integration/providedIpPipelineCast.integration.test.d.ts +0 -0
  289. /package/dist/{tests → tsc/tests}/integration/providedIpPipelineCast.integration.test.js +0 -0
  290. /package/dist/{tests → tsc/tests}/integration/puzzleCentralStreaming.integration.test.d.ts +0 -0
  291. /package/dist/{tests → tsc/tests}/integration/puzzleCentralStreaming.integration.test.js +0 -0
  292. /package/dist/{tests → tsc/tests}/integration/redisCacheSessionPatch.integration.test.d.ts +0 -0
  293. /package/dist/{tests → tsc/tests}/integration/redisCacheSessionPatch.integration.test.js +0 -0
  294. /package/dist/{tests → tsc/tests}/integration/saveCaptchasSessionUpsert.integration.test.d.ts +0 -0
  295. /package/dist/{tests → tsc/tests}/integration/saveCaptchasSessionUpsert.integration.test.js +0 -0
  296. /package/dist/{tests → tsc/tests}/integration/serverCheckClaim.integration.test.d.ts +0 -0
  297. /package/dist/{tests → tsc/tests}/integration/serverCheckClaim.integration.test.js +0 -0
  298. /package/dist/{tests → tsc/tests}/integration/sessionRecordProjection.integration.test.d.ts +0 -0
  299. /package/dist/{tests → tsc/tests}/integration/sessionRecordProjection.integration.test.js +0 -0
  300. /package/dist/{tests → tsc/tests}/mongoGlobalSetup.d.ts +0 -0
  301. /package/dist/{tests → tsc/tests}/mongoGlobalSetup.js +0 -0
  302. /package/dist/{tests → tsc/tests}/mongooseOptions.unit.test.d.ts +0 -0
  303. /package/dist/{tests → tsc/tests}/mongooseOptions.unit.test.js +0 -0
  304. /package/dist/{tests → tsc/tests}/unit/base/mongo.unit.test.d.ts +0 -0
  305. /package/dist/{tests → tsc/tests}/unit/base/mongo.unit.test.js +0 -0
  306. /package/dist/{tests → tsc/tests}/unit/captchaLabel.unit.test.d.ts +0 -0
  307. /package/dist/{tests → tsc/tests}/unit/captchaLabel.unit.test.js +0 -0
  308. /package/dist/{tests → tsc/tests}/unit/databases/centralDbStreamer.unit.test.d.ts +0 -0
  309. /package/dist/{tests → tsc/tests}/unit/databases/centralDbStreamer.unit.test.js +0 -0
  310. /package/dist/{tests → tsc/tests}/unit/detectorBundleTtl.unit.test.d.ts +0 -0
  311. /package/dist/{tests → tsc/tests}/unit/detectorBundleTtl.unit.test.js +0 -0
  312. /package/dist/{tests → tsc/tests}/unit/inputMethods.unit.test.d.ts +0 -0
  313. /package/dist/{tests → tsc/tests}/unit/inputMethods.unit.test.js +0 -0
  314. /package/dist/{tests → tsc/tests}/unit/redisCacheEscalation.unit.test.d.ts +0 -0
  315. /package/dist/{tests → tsc/tests}/unit/redisCacheEscalation.unit.test.js +0 -0
@@ -0,0 +1,2052 @@
1
+ import { isHex } from "@polkadot/util/is";
2
+ import { ProsopoError } from "@prosopo/common";
3
+ import { connectToRedis, setupRedisIndex, } from "@prosopo/redis-client";
4
+ import { CaptchaStates, CaptchaStatus, CaptchaType, DatasetWithIdsAndTreeSchema, DecisionMachineKind, StoredStatusNames, UserCommitmentSchema, isBlockingCaptchaResult, } from "@prosopo/types";
5
+ import { AudioCaptchaRecordSchema, CaptchaRecordSchema, ClientRecordSchema, DatasetRecordSchema, DecisionMachineArtifactRecordSchema, IconOrderCaptchaRecordSchema, PoWCaptchaRecordSchema, PuzzleCaptchaRecordSchema, SESSION_PROJECTION, ScheduledTaskRecordSchema, ScheduledTaskSchema, SessionRecordSchema, SolutionRecordSchema, SpamEmailDomainRecordSchema, UserCommitmentRecordSchema, UserSolutionRecordSchema, } from "@prosopo/types-database";
6
+ import { accessRulesRedisIndex, createRedisAccessRulesStorage, } from "@prosopo/user-access-policy/redis";
7
+ import { assertCoordsSafe, buildDomainSuffixCandidates } from "@prosopo/util";
8
+ import { MongoDatabase } from "../base/mongo.js";
9
+ import { clientRecordCache } from "./clientRecordCache.js";
10
+ const TWENTY_FOUR_HOURS_IN_MS = 24 * 60 * 60 * 1000;
11
+ const MAX_DOMAIN_SUFFIX_CANDIDATES = 5;
12
+ var TableNames;
13
+ (function (TableNames) {
14
+ TableNames["captcha"] = "captcha";
15
+ TableNames["dataset"] = "dataset";
16
+ TableNames["solution"] = "solution";
17
+ TableNames["commitment"] = "commitment";
18
+ TableNames["usersolution"] = "usersolution";
19
+ TableNames["scheduler"] = "scheduler";
20
+ TableNames["powcaptcha"] = "powcaptcha";
21
+ TableNames["puzzlecaptcha"] = "puzzlecaptcha";
22
+ TableNames["audiocaptcha"] = "audiocaptcha";
23
+ TableNames["iconordercaptcha"] = "iconordercaptcha";
24
+ TableNames["client"] = "client";
25
+ TableNames["session"] = "session";
26
+ TableNames["detector"] = "detector";
27
+ TableNames["decisionMachine"] = "decisionMachine";
28
+ TableNames["spamEmailDomain"] = "spamEmailDomain";
29
+ })(TableNames || (TableNames = {}));
30
+ const PROVIDER_TABLES = [
31
+ {
32
+ collectionName: TableNames.captcha,
33
+ modelName: "Captcha",
34
+ schema: CaptchaRecordSchema,
35
+ },
36
+ {
37
+ collectionName: TableNames.powcaptcha,
38
+ modelName: "PowCaptcha",
39
+ schema: PoWCaptchaRecordSchema,
40
+ },
41
+ {
42
+ collectionName: TableNames.puzzlecaptcha,
43
+ modelName: "PuzzleCaptcha",
44
+ schema: PuzzleCaptchaRecordSchema,
45
+ },
46
+ {
47
+ collectionName: TableNames.audiocaptcha,
48
+ modelName: "AudioCaptcha",
49
+ schema: AudioCaptchaRecordSchema,
50
+ },
51
+ {
52
+ collectionName: TableNames.iconordercaptcha,
53
+ modelName: "IconOrderCaptcha",
54
+ schema: IconOrderCaptchaRecordSchema,
55
+ },
56
+ {
57
+ collectionName: TableNames.dataset,
58
+ modelName: "Dataset",
59
+ schema: DatasetRecordSchema,
60
+ },
61
+ {
62
+ collectionName: TableNames.solution,
63
+ modelName: "Solution",
64
+ schema: SolutionRecordSchema,
65
+ },
66
+ {
67
+ collectionName: TableNames.commitment,
68
+ modelName: "UserCommitment",
69
+ schema: UserCommitmentRecordSchema,
70
+ },
71
+ {
72
+ collectionName: TableNames.usersolution,
73
+ modelName: "UserSolution",
74
+ schema: UserSolutionRecordSchema,
75
+ },
76
+ {
77
+ collectionName: TableNames.scheduler,
78
+ modelName: "Scheduler",
79
+ schema: ScheduledTaskRecordSchema,
80
+ },
81
+ {
82
+ collectionName: TableNames.client,
83
+ modelName: "Client",
84
+ schema: ClientRecordSchema,
85
+ },
86
+ {
87
+ collectionName: TableNames.session,
88
+ modelName: "Session",
89
+ schema: SessionRecordSchema,
90
+ },
91
+ {
92
+ collectionName: TableNames.decisionMachine,
93
+ modelName: "DecisionMachine",
94
+ schema: DecisionMachineArtifactRecordSchema,
95
+ },
96
+ {
97
+ collectionName: TableNames.spamEmailDomain,
98
+ modelName: "SpamEmailDomain",
99
+ schema: SpamEmailDomainRecordSchema,
100
+ },
101
+ ];
102
+ export class ProviderDatabase extends MongoDatabase {
103
+ constructor(options) {
104
+ super(options.mongo.url, options.mongo.dbname, options.mongo.authSource, options.logger);
105
+ this.options = options;
106
+ this.tables = {};
107
+ this.indexesEnsured = false;
108
+ this.tables = {};
109
+ this.redisAccessRulesConnection = null;
110
+ this.redisConnection = null;
111
+ this.userAccessRulesStorage = null;
112
+ }
113
+ setCentralDbStreamer(streamer) {
114
+ this.centralStreamer = streamer;
115
+ }
116
+ hasCentralDbStreamer() {
117
+ return this.centralStreamer !== undefined;
118
+ }
119
+ async connect() {
120
+ await super.connect();
121
+ this.loadTables();
122
+ await this.setupRedis();
123
+ }
124
+ async setupRedis() {
125
+ this.redisConnection = connectToRedis({
126
+ url: this.options.redis?.url,
127
+ password: this.options.redis?.password,
128
+ logger: this.logger,
129
+ });
130
+ this.redisAccessRulesConnection = setupRedisIndex(this.redisConnection, {
131
+ ...accessRulesRedisIndex,
132
+ name: this.options.redis?.indexName || accessRulesRedisIndex.name,
133
+ }, this.logger);
134
+ this.userAccessRulesStorage = createRedisAccessRulesStorage(this.redisAccessRulesConnection, this.logger);
135
+ }
136
+ loadTables() {
137
+ const tables = {};
138
+ PROVIDER_TABLES.map(({ collectionName, modelName, schema }) => {
139
+ if (this.connection) {
140
+ tables[collectionName] = this.connection.model(modelName, schema);
141
+ }
142
+ });
143
+ this.tables = tables;
144
+ }
145
+ getTables() {
146
+ if (!this.tables) {
147
+ throw new ProsopoError({
148
+ code: "DB_UNAVAILABLE",
149
+ errorSite: "DBSE-5",
150
+ message: "Tables undefined",
151
+ context: { failedFuncName: this.getTables.name },
152
+ });
153
+ }
154
+ return this.tables;
155
+ }
156
+ getRedisAccessRulesConnection() {
157
+ if (null === this.redisAccessRulesConnection) {
158
+ throw new ProsopoError({
159
+ code: "DB_UNAVAILABLE",
160
+ errorSite: "DBSE-6",
161
+ message: "Redis access rules connection undefined",
162
+ });
163
+ }
164
+ return this.redisAccessRulesConnection;
165
+ }
166
+ getRedisConnection() {
167
+ if (null === this.redisConnection) {
168
+ throw new ProsopoError({
169
+ code: "DB_UNAVAILABLE",
170
+ errorSite: "DBSE-7",
171
+ message: "Redis access rules connection undefined",
172
+ });
173
+ }
174
+ return this.redisConnection;
175
+ }
176
+ async ensureIndexes() {
177
+ const indexPromises = [];
178
+ if (!this.indexesEnsured) {
179
+ PROVIDER_TABLES.map(({ collectionName }) => {
180
+ indexPromises.push(new Promise((resolve) => {
181
+ if (this.connected) {
182
+ this.tables[collectionName].collection.dropIndexes().then(() => {
183
+ this.tables[collectionName]
184
+ .ensureIndexes()
185
+ .then(() => {
186
+ this.logger.info(() => ({
187
+ msg: `Indexes ensured for collection ${collectionName}`,
188
+ }));
189
+ resolve();
190
+ })
191
+ .catch((err) => {
192
+ this.logger.warn(() => ({
193
+ err,
194
+ msg: `Error creating indexes for collection ${collectionName}`,
195
+ }));
196
+ resolve();
197
+ });
198
+ });
199
+ }
200
+ else {
201
+ this.logger.info(() => ({
202
+ msg: `Skipping index creation for collection ${collectionName} as not connected`,
203
+ }));
204
+ resolve();
205
+ }
206
+ }));
207
+ });
208
+ }
209
+ await Promise.all(indexPromises);
210
+ this.indexesEnsured = true;
211
+ }
212
+ getUserAccessRulesStorage() {
213
+ if (null === this.userAccessRulesStorage) {
214
+ throw new ProsopoError({
215
+ code: "DB_UNAVAILABLE",
216
+ errorSite: "DBSE-8",
217
+ message: "User access rules storage undefined",
218
+ });
219
+ }
220
+ return this.userAccessRulesStorage;
221
+ }
222
+ async storeDataset(dataset) {
223
+ try {
224
+ this.logger.debug(() => ({
225
+ data: { datasetId: dataset.datasetId },
226
+ msg: "Storing dataset in database",
227
+ }));
228
+ const parsedDataset = DatasetWithIdsAndTreeSchema.parse(dataset);
229
+ const datasetDoc = {
230
+ datasetId: parsedDataset.datasetId,
231
+ datasetContentId: parsedDataset.datasetContentId,
232
+ format: parsedDataset.format,
233
+ contentTree: parsedDataset.contentTree,
234
+ solutionTree: parsedDataset.solutionTree,
235
+ };
236
+ const filter = {
237
+ datasetId: parsedDataset.datasetId,
238
+ };
239
+ await this.tables.dataset?.updateOne(filter, { $set: datasetDoc }, { upsert: true });
240
+ const captchaDocs = parsedDataset.captchas.map(({ solution, ...captcha }, index) => ({
241
+ ...captcha,
242
+ datasetId: parsedDataset.datasetId,
243
+ datasetContentId: parsedDataset.datasetContentId,
244
+ index,
245
+ solved: !!solution?.length,
246
+ }));
247
+ this.logger.debug(() => ({
248
+ msg: "Inserting captcha records",
249
+ }));
250
+ if (captchaDocs.length) {
251
+ await this.tables?.captcha.bulkWrite(captchaDocs.map((captchaDoc) => ({
252
+ updateOne: {
253
+ filter: { captchaId: captchaDoc.captchaId },
254
+ update: {
255
+ $set: captchaDoc,
256
+ $setOnInsert: { randomKey: Math.random() },
257
+ },
258
+ upsert: true,
259
+ },
260
+ })));
261
+ }
262
+ const captchaSolutionDocs = parsedDataset.captchas
263
+ .filter(({ solution }) => solution?.length)
264
+ .map((captcha) => ({
265
+ captchaId: captcha.captchaId,
266
+ captchaContentId: captcha.captchaContentId,
267
+ solution: captcha.solution,
268
+ salt: captcha.salt,
269
+ datasetId: parsedDataset.datasetId,
270
+ datasetContentId: parsedDataset.datasetContentId,
271
+ }));
272
+ this.logger.debug(() => ({
273
+ msg: "Inserting solution records",
274
+ }));
275
+ if (captchaSolutionDocs.length) {
276
+ await this.tables?.solution.bulkWrite(captchaSolutionDocs.map((captchaSolutionDoc) => ({
277
+ updateOne: {
278
+ filter: { captchaId: captchaSolutionDoc.captchaId },
279
+ update: { $set: captchaSolutionDoc },
280
+ upsert: true,
281
+ },
282
+ })));
283
+ }
284
+ this.logger.debug(() => ({
285
+ msg: "Dataset stored in database",
286
+ }));
287
+ }
288
+ catch (err) {
289
+ throw new ProsopoError({
290
+ code: "DB_QUERY_FAILED",
291
+ errorSite: "DBSE-9",
292
+ message: "Data set load failed",
293
+ cause: err,
294
+ context: { failedFuncName: this.storeDataset.name },
295
+ });
296
+ }
297
+ }
298
+ async getSolutions(datasetId) {
299
+ const filter = { datasetId };
300
+ const docs = await this.tables?.solution
301
+ .find(filter)
302
+ .lean();
303
+ return docs ? docs : [];
304
+ }
305
+ async getSolutionByCaptchaId(captchaId) {
306
+ const filter = { captchaId };
307
+ const doc = await this.tables?.solution
308
+ .findOne(filter)
309
+ .lean();
310
+ return doc || null;
311
+ }
312
+ async getDataset(datasetId) {
313
+ const filter = { datasetId };
314
+ const datasetDoc = await this.tables?.dataset.findOne(filter).lean();
315
+ if (datasetDoc) {
316
+ const { datasetContentId, format, contentTree, solutionTree } = datasetDoc;
317
+ const captchas = (await this.tables?.captcha.find(filter).lean()) || [];
318
+ const solutions = (await this.tables?.solution.find(filter).lean()) ||
319
+ [];
320
+ const solutionsKeyed = {};
321
+ for (const solution of solutions) {
322
+ solutionsKeyed[solution.captchaId] = solution;
323
+ }
324
+ return {
325
+ datasetId,
326
+ datasetContentId,
327
+ format,
328
+ contentTree: contentTree || [],
329
+ solutionTree: solutionTree || [],
330
+ captchas: captchas.map((captchaDoc) => {
331
+ const { captchaId, captchaContentId, items, target, salt, solved } = captchaDoc;
332
+ const solution = solutionsKeyed[captchaId];
333
+ return {
334
+ captchaId,
335
+ captchaContentId,
336
+ solved: !!solved,
337
+ salt,
338
+ items,
339
+ target,
340
+ solution: solved && solution ? solution.solution : [],
341
+ };
342
+ }),
343
+ };
344
+ }
345
+ throw new ProsopoError({
346
+ code: "DB_QUERY_FAILED",
347
+ errorSite: "DBSE-10",
348
+ message: "Failed to get dataset",
349
+ context: { failedFuncName: this.getDataset.name, datasetId },
350
+ });
351
+ }
352
+ async getRandomCaptcha(solved, datasetId, size) {
353
+ if (!isHex(datasetId)) {
354
+ throw new ProsopoError({
355
+ code: "INVALID_DATA",
356
+ errorSite: "DBSE-11",
357
+ message: "Invalid hash",
358
+ context: { failedFuncName: this.getRandomCaptcha.name, datasetId },
359
+ });
360
+ }
361
+ const sampleSize = size ? Math.abs(Math.trunc(size)) : 1;
362
+ const projection = {
363
+ _id: 0,
364
+ datasetId: 1,
365
+ datasetContentId: 1,
366
+ captchaId: 1,
367
+ captchaContentId: 1,
368
+ items: 1,
369
+ target: 1,
370
+ };
371
+ const baseFilter = { datasetId: datasetId, solved };
372
+ const pivot = Math.random();
373
+ const head = (await this.tables?.captcha
374
+ .find({ ...baseFilter, randomKey: { $gte: pivot } }, projection)
375
+ .sort({ randomKey: 1 })
376
+ .limit(sampleSize)
377
+ .lean()) ?? [];
378
+ let docs = head;
379
+ if (docs.length < sampleSize) {
380
+ const tail = (await this.tables?.captcha
381
+ .find({ ...baseFilter, randomKey: { $lt: pivot } }, projection)
382
+ .sort({ randomKey: 1 })
383
+ .limit(sampleSize - docs.length)
384
+ .lean()) ?? [];
385
+ docs = docs.concat(tail);
386
+ }
387
+ if (docs.length === 0) {
388
+ const fallback = (await this.tables?.captcha
389
+ .aggregate([
390
+ { $match: baseFilter },
391
+ { $sample: { size: sampleSize } },
392
+ { $project: projection },
393
+ ])
394
+ .exec()) ?? [];
395
+ if (fallback.length > 0) {
396
+ return fallback.map(({ _id, ...keep }) => keep);
397
+ }
398
+ }
399
+ if (docs.length > 0) {
400
+ return docs;
401
+ }
402
+ throw new ProsopoError({
403
+ code: "DB_QUERY_FAILED",
404
+ errorSite: "DBSE-12",
405
+ message: "Failed to get captcha",
406
+ context: {
407
+ failedFuncName: this.getRandomCaptcha.name,
408
+ solved,
409
+ datasetId,
410
+ size,
411
+ },
412
+ });
413
+ }
414
+ async getCaptchaById(captchaId) {
415
+ const filter = { captchaId: { $in: captchaId } };
416
+ const cursor = this.tables?.captcha
417
+ .find(filter, {
418
+ _id: 0,
419
+ captchaId: 1,
420
+ datasetId: 1,
421
+ items: 1,
422
+ })
423
+ .lean();
424
+ const docs = await cursor;
425
+ if (docs?.length) {
426
+ return docs.map(({ _id, ...keepAttrs }) => keepAttrs);
427
+ }
428
+ throw new ProsopoError({
429
+ code: "DB_QUERY_FAILED",
430
+ errorSite: "DBSE-13",
431
+ message: "Failed to get captcha",
432
+ context: { failedFuncName: this.getCaptchaById.name, captchaId },
433
+ });
434
+ }
435
+ async updateCaptcha(captcha, datasetId) {
436
+ if (!isHex(datasetId)) {
437
+ throw new ProsopoError({
438
+ code: "INVALID_DATA",
439
+ errorSite: "DBSE-14",
440
+ message: "Invalid hash",
441
+ context: { failedFuncName: this.updateCaptcha.name, datasetId },
442
+ });
443
+ }
444
+ try {
445
+ const filter = { datasetId };
446
+ await this.tables?.captcha.updateOne(filter, { $set: captcha }, { upsert: false });
447
+ }
448
+ catch (err) {
449
+ throw new ProsopoError({
450
+ code: "DB_QUERY_FAILED",
451
+ errorSite: "DBSE-15",
452
+ message: "Failed to update captcha",
453
+ cause: err,
454
+ context: { failedFuncName: this.getDatasetDetails.name },
455
+ });
456
+ }
457
+ }
458
+ async removeCaptchas(captchaIds) {
459
+ const filter = { captchaId: { $in: captchaIds } };
460
+ await this.tables?.captcha.deleteMany(filter);
461
+ }
462
+ async getMostRecentDatasetId() {
463
+ const dataset = await this.tables?.dataset
464
+ .findOne()
465
+ .sort({ _id: -1 })
466
+ .lean();
467
+ const datasetId = dataset?.datasetId;
468
+ return typeof datasetId === "string" ? datasetId : undefined;
469
+ }
470
+ async getDatasetDetails(datasetId) {
471
+ if (!isHex(datasetId)) {
472
+ throw new ProsopoError({
473
+ code: "INVALID_DATA",
474
+ errorSite: "DBSE-16",
475
+ message: "Invalid hash",
476
+ context: { failedFuncName: this.getDatasetDetails.name, datasetId },
477
+ });
478
+ }
479
+ const filter = { datasetId };
480
+ const doc = await this.tables?.dataset
481
+ .findOne(filter)
482
+ .lean();
483
+ if (doc) {
484
+ return doc;
485
+ }
486
+ throw new ProsopoError({
487
+ code: "RECORD_NOT_FOUND",
488
+ errorSite: "DBSE-17",
489
+ message: "Failed to get dataset",
490
+ context: { failedFuncName: this.getDatasetDetails.name, datasetId },
491
+ });
492
+ }
493
+ async storeUserImageCaptchaSolution(captchas, commit) {
494
+ const commitmentRecord = UserCommitmentSchema.parse({
495
+ ...commit,
496
+ lastUpdatedTimestamp: new Date(),
497
+ });
498
+ if (captchas.length) {
499
+ const filter = {
500
+ id: commit.id,
501
+ };
502
+ await this.tables?.commitment.updateOne(filter, { $set: { ...commitmentRecord } }, { upsert: true });
503
+ const ops = captchas.map((captcha) => ({
504
+ updateOne: {
505
+ filter: {
506
+ commitmentId: commit.id,
507
+ captchaId: captcha.captchaId,
508
+ },
509
+ update: {
510
+ $set: {
511
+ captchaId: captcha.captchaId,
512
+ captchaContentId: captcha.captchaContentId,
513
+ salt: captcha.salt,
514
+ solution: captcha.solution,
515
+ commitmentId: commit.id,
516
+ processed: false,
517
+ },
518
+ },
519
+ upsert: true,
520
+ },
521
+ }));
522
+ await this.tables?.usersolution.bulkWrite(ops);
523
+ this.centralStreamer?.streamImageRecord(commitmentRecord, (ts) => this.tables.commitment
524
+ .updateOne({ id: commit.id }, { $set: { storedAtTimestamp: ts } })
525
+ .then(() => { }));
526
+ }
527
+ }
528
+ async storePowCaptchaRecord(challenge, components, difficulty, providerSignature, ipAddress, headers, ja4, sessionId, serverChecked = false, userSubmitted = false, userSignature, ipInfo) {
529
+ const tables = this.getTables();
530
+ const powCaptchaRecord = {
531
+ challenge,
532
+ userAccount: components.userAccount,
533
+ dappAccount: components.dappAccount,
534
+ requestedAtTimestamp: new Date(components.requestedAtTimestamp),
535
+ ipAddress,
536
+ headers,
537
+ ja4,
538
+ result: { status: CaptchaStatus.pending },
539
+ userSubmitted,
540
+ serverChecked,
541
+ difficulty,
542
+ providerSignature,
543
+ userSignature,
544
+ lastUpdatedTimestamp: new Date(),
545
+ pendingStage: true,
546
+ sessionId,
547
+ ipInfo,
548
+ };
549
+ try {
550
+ await tables.powcaptcha.create(powCaptchaRecord);
551
+ this.logger.info(() => ({
552
+ data: {
553
+ challenge,
554
+ userSubmitted,
555
+ serverChecked,
556
+ countryCode: ipInfo?.isValid ? ipInfo.countryCode : undefined,
557
+ },
558
+ msg: "PowCaptcha record added successfully",
559
+ }));
560
+ this.centralStreamer?.streamPowRecord(powCaptchaRecord, (ts) => this.tables.powcaptcha
561
+ .updateOne({ challenge, lastUpdatedTimestamp: { $lte: ts } }, {
562
+ $set: { storedAtTimestamp: ts },
563
+ $unset: { pendingStage: 1 },
564
+ })
565
+ .then(() => { }));
566
+ }
567
+ catch (error) {
568
+ const err = new ProsopoError({
569
+ code: "DB_QUERY_FAILED",
570
+ errorSite: "DBSE-18",
571
+ message: "Failed to update captcha",
572
+ cause: error,
573
+ context: { challenge, userSubmitted, serverChecked, ipInfo },
574
+ });
575
+ this.logger.error(() => ({
576
+ err: error,
577
+ msg: "Failed to add PowCaptcha record",
578
+ }));
579
+ throw err;
580
+ }
581
+ }
582
+ async getPowCaptchaRecordByChallenge(challenge) {
583
+ if (!this.tables) {
584
+ throw new ProsopoError({
585
+ code: "DB_QUERY_FAILED",
586
+ errorSite: "DBSE-19",
587
+ message: "Database client is not connected",
588
+ context: { failedFuncName: this.getPowCaptchaRecordByChallenge.name },
589
+ });
590
+ }
591
+ try {
592
+ const filter = { challenge };
593
+ const record = await this.tables.powcaptcha
594
+ .findOne(filter, {
595
+ challenge: 1,
596
+ userAccount: 1,
597
+ dappAccount: 1,
598
+ requestedAtTimestamp: 1,
599
+ submittedAtTimestamp: 1,
600
+ verifiedAtTimestamp: 1,
601
+ failedAtTimestamp: 1,
602
+ ipAddress: 1,
603
+ headers: 1,
604
+ ja4: 1,
605
+ result: 1,
606
+ difficulty: 1,
607
+ sessionId: 1,
608
+ ipInfo: 1,
609
+ deviceCapability: 1,
610
+ behavioralDataPacked: 1,
611
+ serverChecked: 1,
612
+ userSubmitted: 1,
613
+ coords: 1,
614
+ clientMetaData: 1,
615
+ })
616
+ .lean();
617
+ if (record) {
618
+ this.logger.info(() => ({
619
+ data: { challenge },
620
+ msg: "PowCaptcha record retrieved successfully",
621
+ }));
622
+ return record;
623
+ }
624
+ this.logger.info(() => ({
625
+ data: { challenge },
626
+ msg: "No PowCaptcha record found",
627
+ }));
628
+ return null;
629
+ }
630
+ catch (error) {
631
+ const err = new ProsopoError({
632
+ code: "DB_QUERY_FAILED",
633
+ errorSite: "DBSE-20",
634
+ message: "Failed to get captcha",
635
+ cause: error,
636
+ context: { challenge },
637
+ });
638
+ this.logger.error(() => ({
639
+ err: err,
640
+ msg: "Failed to retrieve PowCaptcha record",
641
+ }));
642
+ throw err;
643
+ }
644
+ }
645
+ async updatePowCaptchaRecordResult(challenge, result, serverChecked = false, userSubmitted = false, userSignature, coords) {
646
+ const tables = this.getTables();
647
+ const timestamp = new Date();
648
+ const isDisapproved = result.status === CaptchaStatus.disapproved;
649
+ assertCoordsSafe(coords, "coords");
650
+ const setStage = {
651
+ result,
652
+ serverChecked,
653
+ userSubmitted,
654
+ userSignature,
655
+ lastUpdatedTimestamp: timestamp,
656
+ pendingStage: true,
657
+ blocked: isBlockingCaptchaResult(CaptchaType.pow, result),
658
+ ...(coords && { coords }),
659
+ };
660
+ if (userSubmitted) {
661
+ setStage.submittedAtTimestamp = {
662
+ $ifNull: ["$submittedAtTimestamp", timestamp],
663
+ };
664
+ }
665
+ if (isDisapproved) {
666
+ setStage.failedAtTimestamp = {
667
+ $ifNull: ["$failedAtTimestamp", timestamp],
668
+ };
669
+ }
670
+ try {
671
+ const updateResult = await tables.powcaptcha.updateOne({ challenge }, [{ $set: setStage }], { updatePipeline: true });
672
+ if (updateResult.matchedCount === 0) {
673
+ const err = new ProsopoError({
674
+ code: "DB_QUERY_FAILED",
675
+ errorSite: "DBSE-21",
676
+ message: "Failed to get captcha",
677
+ context: { challenge, ...setStage },
678
+ });
679
+ this.logger.info(() => ({
680
+ err: err,
681
+ msg: "No PowCaptcha record found to update",
682
+ }));
683
+ throw err;
684
+ }
685
+ this.logger.info(() => ({
686
+ data: {
687
+ challenge,
688
+ ...setStage,
689
+ },
690
+ msg: "PowCaptcha record updated successfully",
691
+ }));
692
+ this.centralStreamer?.streamPowUpdate(() => this.getPowCaptchaRecordByChallenge(challenge), (ts) => this.tables.powcaptcha
693
+ .updateOne({ challenge, lastUpdatedTimestamp: { $lte: ts } }, {
694
+ $set: { storedAtTimestamp: ts },
695
+ $unset: { pendingStage: 1 },
696
+ })
697
+ .then(() => { }));
698
+ }
699
+ catch (error) {
700
+ const err = new ProsopoError({
701
+ code: "DB_QUERY_FAILED",
702
+ errorSite: "DBSE-22",
703
+ message: "Failed to update captcha",
704
+ cause: error,
705
+ context: { challenge, ...setStage },
706
+ });
707
+ this.logger.error(() => ({
708
+ err: err,
709
+ msg: "Failed to update PowCaptcha record",
710
+ }));
711
+ throw err;
712
+ }
713
+ }
714
+ async updatePowCaptchaRecord(challenge, updates) {
715
+ const tables = this.getTables();
716
+ await tables.powcaptcha.updateOne({ challenge }, { $set: { ...updates, pendingStage: true } }, { upsert: false });
717
+ this.centralStreamer?.streamPowUpdate(() => this.getPowCaptchaRecordByChallenge(challenge), (ts) => this.tables.powcaptcha
718
+ .updateOne({ challenge, lastUpdatedTimestamp: { $lte: ts } }, {
719
+ $set: { storedAtTimestamp: ts },
720
+ $unset: { pendingStage: 1 },
721
+ })
722
+ .then(() => { }));
723
+ }
724
+ interactiveCollection(captchaType) {
725
+ switch (captchaType) {
726
+ case CaptchaType.puzzle:
727
+ return {
728
+ table: TableNames.puzzlecaptcha,
729
+ label: "PuzzleCaptcha",
730
+ captchaType,
731
+ answerProjection: {
732
+ targetX: 1,
733
+ targetY: 1,
734
+ originX: 1,
735
+ originY: 1,
736
+ tolerance: 1,
737
+ puzzleEvents: 1,
738
+ },
739
+ streamRecord: (record, markStored) => this.centralStreamer?.streamPuzzleRecord(record, markStored),
740
+ streamUpdate: (challenge, markStored) => this.centralStreamer?.streamPuzzleUpdate(() => this.getPuzzleCaptchaRecordByChallenge(challenge), markStored),
741
+ };
742
+ case CaptchaType.iconOrder:
743
+ return {
744
+ table: TableNames.iconordercaptcha,
745
+ label: "IconOrderCaptcha",
746
+ captchaType,
747
+ answerProjection: {
748
+ targets: 1,
749
+ tolerance: 1,
750
+ clicks: 1,
751
+ iconOrderEvents: 1,
752
+ },
753
+ streamRecord: (record, markStored) => this.centralStreamer?.streamIconOrderRecord(record, markStored),
754
+ streamUpdate: (challenge, markStored) => this.centralStreamer?.streamIconOrderUpdate(() => this.getIconOrderCaptchaRecordByChallenge(challenge), markStored),
755
+ };
756
+ case CaptchaType.audio:
757
+ return {
758
+ table: TableNames.audiocaptcha,
759
+ label: "AudioCaptcha",
760
+ captchaType,
761
+ answerProjection: {
762
+ answer: 1,
763
+ submittedAnswer: 1,
764
+ replays: 1,
765
+ audioEvents: 1,
766
+ },
767
+ streamRecord: (record, markStored) => this.centralStreamer?.streamAudioRecord(record, markStored),
768
+ streamUpdate: (challenge, markStored) => this.centralStreamer?.streamAudioUpdate(() => this.getAudioCaptchaRecordByChallenge(challenge), markStored),
769
+ };
770
+ }
771
+ }
772
+ markInteractiveRecordStored(collection, challenge) {
773
+ return (ts) => this.tables[collection.table]
774
+ .updateOne({ challenge, lastUpdatedTimestamp: { $lte: ts } }, {
775
+ $set: { storedAtTimestamp: ts },
776
+ $unset: { pendingStage: 1 },
777
+ })
778
+ .then(() => { });
779
+ }
780
+ streamInteractiveUpdate(collection, challenge) {
781
+ collection.streamUpdate(challenge, this.markInteractiveRecordStored(collection, challenge));
782
+ }
783
+ async storeInteractiveCaptchaRecord(collection, record) {
784
+ const tables = this.getTables();
785
+ const { challenge, ipInfo } = record;
786
+ try {
787
+ await tables[collection.table].create(record);
788
+ this.logger.info(() => ({
789
+ data: {
790
+ challenge,
791
+ countryCode: ipInfo?.isValid ? ipInfo.countryCode : undefined,
792
+ },
793
+ msg: `${collection.label} record added successfully`,
794
+ }));
795
+ collection.streamRecord(record, this.markInteractiveRecordStored(collection, challenge));
796
+ }
797
+ catch (error) {
798
+ const err = new ProsopoError({
799
+ code: "DB_QUERY_FAILED",
800
+ errorSite: "DBSE-23",
801
+ message: "Failed to update captcha",
802
+ cause: error,
803
+ context: { challenge, ipInfo },
804
+ });
805
+ this.logger.error(() => ({
806
+ err: error,
807
+ msg: `Failed to add ${collection.label} record`,
808
+ }));
809
+ throw err;
810
+ }
811
+ }
812
+ pendingInteractiveRecord(challenge, components, providerSignature, ipAddress, headers, ja4, sessionId, ipInfo) {
813
+ return {
814
+ challenge,
815
+ userAccount: components.userAccount,
816
+ dappAccount: components.dappAccount,
817
+ requestedAtTimestamp: new Date(components.requestedAtTimestamp),
818
+ ipAddress,
819
+ headers,
820
+ ja4,
821
+ result: { status: CaptchaStatus.pending },
822
+ userSubmitted: false,
823
+ serverChecked: false,
824
+ providerSignature,
825
+ lastUpdatedTimestamp: new Date(),
826
+ pendingStage: true,
827
+ sessionId,
828
+ ipInfo,
829
+ };
830
+ }
831
+ async storePuzzleCaptchaRecord(challenge, components, targetX, targetY, originX, originY, tolerance, providerSignature, ipAddress, headers, ja4, sessionId, ipInfo) {
832
+ await this.storeInteractiveCaptchaRecord(this.interactiveCollection(CaptchaType.puzzle), {
833
+ ...this.pendingInteractiveRecord(challenge, components, providerSignature, ipAddress, headers, ja4, sessionId, ipInfo),
834
+ tolerance,
835
+ targetX,
836
+ targetY,
837
+ originX,
838
+ originY,
839
+ });
840
+ }
841
+ async storeAudioCaptchaRecord(challenge, components, answer, providerSignature, ipAddress, headers, ja4, sessionId, ipInfo) {
842
+ await this.storeInteractiveCaptchaRecord(this.interactiveCollection(CaptchaType.audio), {
843
+ ...this.pendingInteractiveRecord(challenge, components, providerSignature, ipAddress, headers, ja4, sessionId, ipInfo),
844
+ answer,
845
+ });
846
+ }
847
+ async storeIconOrderCaptchaRecord(challenge, components, targets, tolerance, providerSignature, ipAddress, headers, ja4, sessionId, ipInfo) {
848
+ await this.storeInteractiveCaptchaRecord(this.interactiveCollection(CaptchaType.iconOrder), {
849
+ ...this.pendingInteractiveRecord(challenge, components, providerSignature, ipAddress, headers, ja4, sessionId, ipInfo),
850
+ targets,
851
+ tolerance,
852
+ });
853
+ }
854
+ async getInteractiveCaptchaRecordByChallenge(collection, challenge, failedFuncName) {
855
+ if (!this.tables) {
856
+ throw new ProsopoError({
857
+ code: "DB_QUERY_FAILED",
858
+ errorSite: "DBSE-24",
859
+ message: "Database client is not connected",
860
+ context: { failedFuncName },
861
+ });
862
+ }
863
+ try {
864
+ const record = await this.tables[collection.table]
865
+ .findOne({ challenge }, {
866
+ challenge: 1,
867
+ userAccount: 1,
868
+ dappAccount: 1,
869
+ requestedAtTimestamp: 1,
870
+ submittedAtTimestamp: 1,
871
+ ipAddress: 1,
872
+ headers: 1,
873
+ ja4: 1,
874
+ result: 1,
875
+ sessionId: 1,
876
+ ipInfo: 1,
877
+ deviceCapability: 1,
878
+ behavioralDataPacked: 1,
879
+ serverChecked: 1,
880
+ userSubmitted: 1,
881
+ coords: 1,
882
+ clientMetaData: 1,
883
+ ...collection.answerProjection,
884
+ })
885
+ .lean();
886
+ if (record) {
887
+ this.logger.info(() => ({
888
+ data: { challenge },
889
+ msg: `${collection.label} record retrieved successfully`,
890
+ }));
891
+ return record;
892
+ }
893
+ this.logger.info(() => ({
894
+ data: { challenge },
895
+ msg: `No ${collection.label} record found`,
896
+ }));
897
+ return null;
898
+ }
899
+ catch (error) {
900
+ const err = new ProsopoError({
901
+ code: "DB_QUERY_FAILED",
902
+ errorSite: "DBSE-25",
903
+ message: "Failed to get captcha",
904
+ cause: error,
905
+ context: { challenge },
906
+ });
907
+ this.logger.error(() => ({
908
+ err: err,
909
+ msg: `Failed to retrieve ${collection.label} record`,
910
+ }));
911
+ throw err;
912
+ }
913
+ }
914
+ async getPuzzleCaptchaRecordByChallenge(challenge) {
915
+ return this.getInteractiveCaptchaRecordByChallenge(this.interactiveCollection(CaptchaType.puzzle), challenge, this.getPuzzleCaptchaRecordByChallenge.name);
916
+ }
917
+ async getIconOrderCaptchaRecordByChallenge(challenge) {
918
+ return this.getInteractiveCaptchaRecordByChallenge(this.interactiveCollection(CaptchaType.iconOrder), challenge, this.getIconOrderCaptchaRecordByChallenge.name);
919
+ }
920
+ async getAudioCaptchaRecordByChallenge(challenge) {
921
+ return this.getInteractiveCaptchaRecordByChallenge(this.interactiveCollection(CaptchaType.audio), challenge, this.getAudioCaptchaRecordByChallenge.name);
922
+ }
923
+ async claimInteractiveCaptchaSubmission(collection, challenge) {
924
+ const tables = this.getTables();
925
+ try {
926
+ const claim = await tables[collection.table].updateOne({ challenge, userSubmitted: { $ne: true } }, { $set: { userSubmitted: true, submittedAtTimestamp: new Date() } });
927
+ const won = claim.modifiedCount === 1;
928
+ this.logger.info(() => ({
929
+ data: { challenge, won },
930
+ msg: won
931
+ ? `${collection.label} submission claimed`
932
+ : `${collection.label} submission already claimed`,
933
+ }));
934
+ return won;
935
+ }
936
+ catch (error) {
937
+ const err = new ProsopoError({
938
+ code: "DB_QUERY_FAILED",
939
+ errorSite: "DBSE-26",
940
+ message: "Failed to update captcha",
941
+ cause: error,
942
+ context: { challenge },
943
+ });
944
+ this.logger.error(() => ({
945
+ err: err,
946
+ msg: `Failed to claim ${collection.label} submission`,
947
+ }));
948
+ throw err;
949
+ }
950
+ }
951
+ async claimIconOrderCaptchaSubmission(challenge) {
952
+ return this.claimInteractiveCaptchaSubmission(this.interactiveCollection(CaptchaType.iconOrder), challenge);
953
+ }
954
+ async claimAudioCaptchaSubmission(challenge) {
955
+ return this.claimInteractiveCaptchaSubmission(this.interactiveCollection(CaptchaType.audio), challenge);
956
+ }
957
+ async updateInteractiveCaptchaRecordResult(collection, challenge, result, serverChecked, userSubmitted, userSignature, coords, lastUpdatedTimestamp) {
958
+ const tables = this.getTables();
959
+ const timestamp = lastUpdatedTimestamp ?? new Date();
960
+ const isDisapproved = result.status === CaptchaStatus.disapproved;
961
+ assertCoordsSafe(coords, "coords");
962
+ const setStage = {
963
+ result,
964
+ serverChecked,
965
+ userSubmitted,
966
+ userSignature,
967
+ lastUpdatedTimestamp: timestamp,
968
+ pendingStage: true,
969
+ blocked: isBlockingCaptchaResult(collection.captchaType, result),
970
+ ...(coords && { coords }),
971
+ ...(userSubmitted && { submittedAtTimestamp: timestamp }),
972
+ ...(isDisapproved && { failedAtTimestamp: timestamp }),
973
+ };
974
+ try {
975
+ const updateResult = await tables[collection.table].updateOne({ challenge }, { $set: setStage });
976
+ if (updateResult.matchedCount === 0) {
977
+ const err = new ProsopoError({
978
+ code: "DB_QUERY_FAILED",
979
+ errorSite: "DBSE-27",
980
+ message: "Failed to get captcha",
981
+ context: { challenge, ...setStage },
982
+ });
983
+ this.logger.info(() => ({
984
+ err: err,
985
+ msg: `No ${collection.label} record found to update`,
986
+ }));
987
+ throw err;
988
+ }
989
+ this.logger.info(() => ({
990
+ data: {
991
+ challenge,
992
+ ...setStage,
993
+ },
994
+ msg: `${collection.label} record updated successfully`,
995
+ }));
996
+ this.streamInteractiveUpdate(collection, challenge);
997
+ }
998
+ catch (error) {
999
+ const err = new ProsopoError({
1000
+ code: "DB_QUERY_FAILED",
1001
+ errorSite: "DBSE-28",
1002
+ message: "Failed to update captcha",
1003
+ cause: error,
1004
+ context: { challenge, ...setStage },
1005
+ });
1006
+ this.logger.error(() => ({
1007
+ err: err,
1008
+ msg: `Failed to update ${collection.label} record`,
1009
+ }));
1010
+ throw err;
1011
+ }
1012
+ }
1013
+ async updatePuzzleCaptchaRecordResult(challenge, result, serverChecked = false, userSubmitted = false, userSignature, coords, lastUpdatedTimestamp) {
1014
+ await this.updateInteractiveCaptchaRecordResult(this.interactiveCollection(CaptchaType.puzzle), challenge, result, serverChecked, userSubmitted, userSignature, coords, lastUpdatedTimestamp);
1015
+ }
1016
+ async updateIconOrderCaptchaRecordResult(challenge, result, serverChecked = false, userSubmitted = false, userSignature, coords, lastUpdatedTimestamp) {
1017
+ await this.updateInteractiveCaptchaRecordResult(this.interactiveCollection(CaptchaType.iconOrder), challenge, result, serverChecked, userSubmitted, userSignature, coords, lastUpdatedTimestamp);
1018
+ }
1019
+ async updateAudioCaptchaRecordResult(challenge, result, serverChecked = false, userSubmitted = false, userSignature, coords, lastUpdatedTimestamp) {
1020
+ await this.updateInteractiveCaptchaRecordResult(this.interactiveCollection(CaptchaType.audio), challenge, result, serverChecked, userSubmitted, userSignature, coords, lastUpdatedTimestamp);
1021
+ }
1022
+ async updateInteractiveCaptchaRecord(collection, challenge, updates) {
1023
+ const tables = this.getTables();
1024
+ const timestamp = new Date();
1025
+ const baseSet = {
1026
+ ...updates,
1027
+ pendingStage: true,
1028
+ ...(updates.serverChecked === true && {
1029
+ verifiedAtTimestamp: timestamp,
1030
+ }),
1031
+ ...(updates.userSubmitted === true && {
1032
+ submittedAtTimestamp: timestamp,
1033
+ }),
1034
+ ...(updates.result?.status === CaptchaStatus.disapproved && {
1035
+ failedAtTimestamp: timestamp,
1036
+ }),
1037
+ };
1038
+ await tables[collection.table].updateOne({ challenge }, { $set: baseSet });
1039
+ this.streamInteractiveUpdate(collection, challenge);
1040
+ }
1041
+ async updatePuzzleCaptchaRecord(challenge, updates) {
1042
+ await this.updateInteractiveCaptchaRecord(this.interactiveCollection(CaptchaType.puzzle), challenge, updates);
1043
+ }
1044
+ async updateIconOrderCaptchaRecord(challenge, updates) {
1045
+ await this.updateInteractiveCaptchaRecord(this.interactiveCollection(CaptchaType.iconOrder), challenge, updates);
1046
+ }
1047
+ async updateAudioCaptchaRecord(challenge, updates) {
1048
+ await this.updateInteractiveCaptchaRecord(this.interactiveCollection(CaptchaType.audio), challenge, updates);
1049
+ }
1050
+ async markInteractiveCaptchaRecordChecked(collection, challenge) {
1051
+ const tables = this.getTables();
1052
+ const timestamp = new Date();
1053
+ const result = await tables[collection.table].updateOne({ challenge, serverChecked: { $ne: true } }, {
1054
+ $set: {
1055
+ serverChecked: true,
1056
+ lastUpdatedTimestamp: timestamp,
1057
+ verifiedAtTimestamp: timestamp,
1058
+ pendingStage: true,
1059
+ },
1060
+ });
1061
+ if (result.modifiedCount === 0) {
1062
+ return false;
1063
+ }
1064
+ this.streamInteractiveUpdate(collection, challenge);
1065
+ return true;
1066
+ }
1067
+ async markIconOrderCaptchaRecordChecked(challenge) {
1068
+ return this.markInteractiveCaptchaRecordChecked(this.interactiveCollection(CaptchaType.iconOrder), challenge);
1069
+ }
1070
+ async markAudioCaptchaRecordChecked(challenge) {
1071
+ return this.markInteractiveCaptchaRecordChecked(this.interactiveCollection(CaptchaType.audio), challenge);
1072
+ }
1073
+ async markPuzzleCaptchaRecordChecked(challenge) {
1074
+ return this.markInteractiveCaptchaRecordChecked(this.interactiveCollection(CaptchaType.puzzle), challenge);
1075
+ }
1076
+ async getCheckedDappUserCommitments() {
1077
+ const filter = { [StoredStatusNames.serverChecked]: true };
1078
+ const docs = await this.tables?.commitment
1079
+ .find(filter)
1080
+ .lean();
1081
+ return docs || [];
1082
+ }
1083
+ async getUnstoredDappUserCommitments(limit = 1000, afterId) {
1084
+ const filter = {
1085
+ pendingStage: true,
1086
+ };
1087
+ if (afterId !== undefined) {
1088
+ filter._id = { $gt: afterId };
1089
+ }
1090
+ const docs = await this.tables?.commitment
1091
+ .find(filter)
1092
+ .hint("pendingStage_partial")
1093
+ .sort({ _id: 1 })
1094
+ .limit(limit)
1095
+ .lean();
1096
+ return docs || [];
1097
+ }
1098
+ async markDappUserCommitmentsStored(commitmentIds, asOfTimestamp = new Date()) {
1099
+ await this.tables?.commitment.updateMany({
1100
+ id: { $in: commitmentIds },
1101
+ lastUpdatedTimestamp: { $lte: asOfTimestamp },
1102
+ }, {
1103
+ $set: { storedAtTimestamp: new Date() },
1104
+ $unset: { pendingStage: 1 },
1105
+ }, { upsert: false });
1106
+ }
1107
+ async markDappUserCommitmentsChecked(commitmentIds) {
1108
+ const timestamp = new Date();
1109
+ const result = await this.getTables().commitment.updateMany({ id: { $in: commitmentIds }, serverChecked: { $ne: true } }, [
1110
+ {
1111
+ $set: {
1112
+ serverChecked: true,
1113
+ lastUpdatedTimestamp: timestamp,
1114
+ pendingStage: true,
1115
+ verifiedAtTimestamp: {
1116
+ $ifNull: ["$verifiedAtTimestamp", timestamp],
1117
+ },
1118
+ },
1119
+ },
1120
+ ], { updatePipeline: true });
1121
+ return result.modifiedCount;
1122
+ }
1123
+ async updateDappUserCommitment(commitmentId, updates) {
1124
+ const filter = { id: commitmentId };
1125
+ const timestamp = new Date();
1126
+ const baseSet = {
1127
+ ...updates,
1128
+ lastUpdatedTimestamp: timestamp,
1129
+ pendingStage: true,
1130
+ };
1131
+ const pipelineExprs = {};
1132
+ if (updates.userSubmitted === true) {
1133
+ pipelineExprs.submittedAtTimestamp = {
1134
+ $ifNull: ["$submittedAtTimestamp", timestamp],
1135
+ };
1136
+ }
1137
+ if (updates.serverChecked === true) {
1138
+ pipelineExprs.verifiedAtTimestamp = {
1139
+ $ifNull: ["$verifiedAtTimestamp", timestamp],
1140
+ };
1141
+ }
1142
+ if (updates.result?.status === CaptchaStatus.disapproved) {
1143
+ pipelineExprs.failedAtTimestamp = {
1144
+ $ifNull: ["$failedAtTimestamp", timestamp],
1145
+ };
1146
+ }
1147
+ if (Object.keys(pipelineExprs).length === 0) {
1148
+ await this.tables?.commitment.updateOne(filter, { $set: baseSet });
1149
+ return;
1150
+ }
1151
+ await this.tables?.commitment.updateOne(filter, [{ $set: { ...baseSet, ...pipelineExprs } }], { updatePipeline: true });
1152
+ }
1153
+ async countCommitmentsByNormalisedEmail(dappAccount, emailNormalised) {
1154
+ if (!emailNormalised)
1155
+ return 0;
1156
+ const tables = this.getTables();
1157
+ const filter = {
1158
+ dappAccount,
1159
+ serverChecked: true,
1160
+ "metadata.emailNormalised": emailNormalised,
1161
+ };
1162
+ const counts = await Promise.all([
1163
+ tables.commitment.countDocuments(filter),
1164
+ tables.powcaptcha.countDocuments(filter),
1165
+ tables.puzzlecaptcha.countDocuments(filter),
1166
+ tables.iconordercaptcha.countDocuments(filter),
1167
+ tables.audiocaptcha.countDocuments(filter),
1168
+ ]);
1169
+ return counts.reduce((total, count) => total + count, 0);
1170
+ }
1171
+ async getUnstoredDappUserPoWCommitments(limit = 1000, afterId) {
1172
+ const filter = {
1173
+ pendingStage: true,
1174
+ };
1175
+ if (afterId !== undefined) {
1176
+ filter._id = { $gt: afterId };
1177
+ }
1178
+ const docs = await this.tables?.powcaptcha
1179
+ .find(filter)
1180
+ .hint("pendingStage_partial")
1181
+ .sort({ _id: 1 })
1182
+ .limit(limit)
1183
+ .lean();
1184
+ return docs || [];
1185
+ }
1186
+ async markDappUserPoWCommitmentsStored(challenges, asOfTimestamp = new Date()) {
1187
+ await this.tables?.powcaptcha.updateMany({
1188
+ challenge: { $in: challenges },
1189
+ lastUpdatedTimestamp: { $lte: asOfTimestamp },
1190
+ }, {
1191
+ $set: { storedAtTimestamp: new Date() },
1192
+ $unset: { pendingStage: 1 },
1193
+ }, { upsert: false });
1194
+ }
1195
+ async markDappUserPoWCommitmentsChecked(challenges) {
1196
+ const timestamp = new Date();
1197
+ const result = await this.getTables().powcaptcha.updateMany({ challenge: { $in: challenges }, serverChecked: { $ne: true } }, [
1198
+ {
1199
+ $set: {
1200
+ serverChecked: true,
1201
+ lastUpdatedTimestamp: timestamp,
1202
+ pendingStage: true,
1203
+ verifiedAtTimestamp: {
1204
+ $ifNull: ["$verifiedAtTimestamp", timestamp],
1205
+ },
1206
+ },
1207
+ },
1208
+ ], { upsert: false, updatePipeline: true });
1209
+ return result.modifiedCount;
1210
+ }
1211
+ async storeSessionRecord(sessionRecord) {
1212
+ try {
1213
+ this.logger.debug(() => ({
1214
+ data: { action: "storing", sessionRecord },
1215
+ }));
1216
+ const recordWithFlag = {
1217
+ ...sessionRecord,
1218
+ lastUpdatedTimestamp: sessionRecord.lastUpdatedTimestamp ?? sessionRecord.createdAt,
1219
+ pendingStage: true,
1220
+ };
1221
+ await this.tables.session.create(recordWithFlag);
1222
+ this.centralStreamer?.streamSessionRecord(recordWithFlag, (ts) => this.tables.session
1223
+ .updateOne({
1224
+ sessionId: sessionRecord.sessionId,
1225
+ lastUpdatedTimestamp: { $lte: ts },
1226
+ }, {
1227
+ $set: { storedAtTimestamp: ts },
1228
+ $unset: { pendingStage: 1 },
1229
+ })
1230
+ .then(() => { }));
1231
+ }
1232
+ catch (err) {
1233
+ throw new ProsopoError({
1234
+ code: "DB_QUERY_FAILED",
1235
+ errorSite: "DBSE-29",
1236
+ message: "Failed to store session",
1237
+ cause: err,
1238
+ context: { sessionId: sessionRecord.sessionId },
1239
+ });
1240
+ }
1241
+ }
1242
+ async storeBlockedSession(record) {
1243
+ try {
1244
+ const stored = {
1245
+ ...record,
1246
+ blocked: true,
1247
+ deleted: true,
1248
+ storedAtTimestamp: record.storedAtTimestamp ?? record.createdAt,
1249
+ lastUpdatedTimestamp: record.lastUpdatedTimestamp ?? record.createdAt,
1250
+ };
1251
+ await this.tables.session.create(stored);
1252
+ this.centralStreamer?.streamSessionRecord(stored, (ts) => this.tables.session
1253
+ .updateOne({
1254
+ sessionId: stored.sessionId,
1255
+ lastUpdatedTimestamp: { $lte: ts },
1256
+ }, { $set: { storedAtTimestamp: ts } })
1257
+ .then(() => { }));
1258
+ }
1259
+ catch (err) {
1260
+ this.logger.warn(() => ({
1261
+ err,
1262
+ msg: "Failed to persist blocked session record",
1263
+ data: { sessionId: record.sessionId, siteKey: record.siteKey },
1264
+ }));
1265
+ }
1266
+ }
1267
+ async getSessionRecordBySessionId(sessionId) {
1268
+ const filter = { sessionId };
1269
+ const doc = await this.tables.session
1270
+ .findOne(filter, SESSION_PROJECTION)
1271
+ .lean();
1272
+ return doc || undefined;
1273
+ }
1274
+ async getSessionRecordByToken(token) {
1275
+ const filter = { token };
1276
+ const record = await this.tables.session.findOne(filter).lean();
1277
+ return record || undefined;
1278
+ }
1279
+ async checkAndRemoveSession(sessionId) {
1280
+ this.logger.debug(() => ({
1281
+ data: { action: "checking and removing", sessionId },
1282
+ }));
1283
+ const filter = {
1284
+ sessionId,
1285
+ deleted: { $exists: false },
1286
+ };
1287
+ try {
1288
+ const session = await this.tables.session
1289
+ .findOneAndUpdate(filter, {
1290
+ deleted: true,
1291
+ lastUpdatedTimestamp: new Date(),
1292
+ pendingStage: true,
1293
+ })
1294
+ .lean();
1295
+ return session || undefined;
1296
+ }
1297
+ catch (err) {
1298
+ throw new ProsopoError({
1299
+ code: "DB_QUERY_FAILED",
1300
+ errorSite: "DBSE-30",
1301
+ message: "Failed to check and remove session",
1302
+ cause: err,
1303
+ context: { sessionId },
1304
+ });
1305
+ }
1306
+ }
1307
+ async claimSessionServerCheck(sessionId) {
1308
+ try {
1309
+ const result = await this.tables.session.updateOne({ sessionId, serverChecked: { $ne: true } }, {
1310
+ $set: {
1311
+ serverChecked: true,
1312
+ lastUpdatedTimestamp: new Date(),
1313
+ pendingStage: true,
1314
+ },
1315
+ });
1316
+ return result.modifiedCount > 0;
1317
+ }
1318
+ catch (err) {
1319
+ throw new ProsopoError({
1320
+ code: "DB_QUERY_FAILED",
1321
+ errorSite: "DBSE-31",
1322
+ message: "Failed to get session",
1323
+ cause: err,
1324
+ context: { sessionId },
1325
+ });
1326
+ }
1327
+ }
1328
+ async updateSessionRecord(sessionId, updates, streamToCentral) {
1329
+ try {
1330
+ await this.tables.session.updateOne({ sessionId }, {
1331
+ $set: {
1332
+ ...updates,
1333
+ lastUpdatedTimestamp: new Date(),
1334
+ pendingStage: true,
1335
+ },
1336
+ });
1337
+ if (streamToCentral && this.centralStreamer) {
1338
+ const streamer = this.centralStreamer;
1339
+ const markStored = (ts) => this.tables.session
1340
+ .updateOne({ sessionId, lastUpdatedTimestamp: { $lte: ts } }, {
1341
+ $set: { storedAtTimestamp: ts },
1342
+ $unset: { pendingStage: 1 },
1343
+ })
1344
+ .then(() => { });
1345
+ this.tables.session
1346
+ .findOne({ sessionId })
1347
+ .lean()
1348
+ .then((record) => {
1349
+ if (record) {
1350
+ streamer.streamSessionRecord(record, markStored);
1351
+ }
1352
+ })
1353
+ .catch(() => { });
1354
+ }
1355
+ }
1356
+ catch (err) {
1357
+ throw new ProsopoError({
1358
+ code: "DB_QUERY_FAILED",
1359
+ errorSite: "DBSE-32",
1360
+ message: "Failed to get session",
1361
+ cause: err,
1362
+ context: { sessionId },
1363
+ });
1364
+ }
1365
+ }
1366
+ async recordSessionSimdReadingsIfAbsent(sessionId, readings, stage) {
1367
+ try {
1368
+ await this.tables.session.updateOne({ sessionId }, [
1369
+ {
1370
+ $set: {
1371
+ simdReadings: { $ifNull: ["$simdReadings", readings] },
1372
+ simdReadingsStage: { $ifNull: ["$simdReadingsStage", stage] },
1373
+ lastUpdatedTimestamp: new Date(),
1374
+ pendingStage: true,
1375
+ },
1376
+ },
1377
+ ], { updatePipeline: true });
1378
+ }
1379
+ catch (err) {
1380
+ throw new ProsopoError({
1381
+ code: "DB_QUERY_FAILED",
1382
+ errorSite: "DBSE-33",
1383
+ message: "Failed to get session",
1384
+ cause: err,
1385
+ context: { sessionId, stage },
1386
+ });
1387
+ }
1388
+ }
1389
+ async mergeSessionDnsEvent(sessionId, fields, receivedAtIfAbsent) {
1390
+ const setStage = {
1391
+ "dnsEvent.receivedAt": {
1392
+ $ifNull: ["$dnsEvent.receivedAt", receivedAtIfAbsent],
1393
+ },
1394
+ lastUpdatedTimestamp: new Date(),
1395
+ pendingStage: true,
1396
+ };
1397
+ if (fields.resolverIp !== undefined) {
1398
+ setStage["dnsEvent.resolverIp"] = fields.resolverIp;
1399
+ }
1400
+ if (fields.peerIp !== undefined) {
1401
+ setStage["dnsEvent.peerIp"] = fields.peerIp;
1402
+ }
1403
+ if (fields.pathValid !== undefined) {
1404
+ setStage["dnsEvent.pathValid"] = fields.pathValid;
1405
+ }
1406
+ try {
1407
+ const result = await this.tables.session.updateOne({ sessionId }, [{ $set: setStage }], { updatePipeline: true });
1408
+ return result.matchedCount > 0;
1409
+ }
1410
+ catch (err) {
1411
+ throw new ProsopoError({
1412
+ code: "DB_QUERY_FAILED",
1413
+ errorSite: "DBSE-34",
1414
+ message: "Failed to get session",
1415
+ cause: err,
1416
+ context: { sessionId },
1417
+ });
1418
+ }
1419
+ }
1420
+ async getSessionByuserSitekeyIpHash(userSitekeyIpHash) {
1421
+ this.logger.debug(() => ({
1422
+ data: { action: "getting session by user IP hash", userSitekeyIpHash },
1423
+ }));
1424
+ const filter = {
1425
+ userSitekeyIpHash,
1426
+ deleted: { $exists: false },
1427
+ };
1428
+ try {
1429
+ const session = await this.tables.session
1430
+ .findOne(filter)
1431
+ .lean();
1432
+ return session || undefined;
1433
+ }
1434
+ catch (err) {
1435
+ throw new ProsopoError({
1436
+ code: "DB_QUERY_FAILED",
1437
+ errorSite: "DBSE-35",
1438
+ message: "Failed to get session",
1439
+ cause: err,
1440
+ context: { userSitekeyIpHash },
1441
+ });
1442
+ }
1443
+ }
1444
+ getUnstoredSessionRecords(limit = 1000, afterId) {
1445
+ const filter = {
1446
+ pendingStage: true,
1447
+ };
1448
+ if (afterId !== undefined) {
1449
+ filter._id = { $gt: afterId };
1450
+ }
1451
+ return Promise.resolve(this.tables?.session)
1452
+ .then((tbl) => tbl
1453
+ ?.find(filter)
1454
+ .hint("pendingStage_partial")
1455
+ .sort({ _id: 1 })
1456
+ .limit(limit)
1457
+ .lean())
1458
+ .then((docs) => docs || []);
1459
+ }
1460
+ async markSessionRecordsStored(sessionIds, asOfTimestamp = new Date()) {
1461
+ await this.tables?.session.updateMany({
1462
+ sessionId: { $in: sessionIds },
1463
+ lastUpdatedTimestamp: { $lte: asOfTimestamp },
1464
+ }, {
1465
+ $set: { storedAtTimestamp: new Date() },
1466
+ $unset: { pendingStage: 1 },
1467
+ }, { upsert: false });
1468
+ }
1469
+ async storePendingImageCommitment(userAccount, requestHash, salt, deadlineTimestamp, requestedAtTimestamp, ipAddress, threshold, sessionId, ipInfo) {
1470
+ if (!isHex(requestHash)) {
1471
+ throw new ProsopoError({
1472
+ code: "INVALID_DATA",
1473
+ errorSite: "DBSE-36",
1474
+ message: "Invalid hash",
1475
+ context: {
1476
+ failedFuncName: this.storePendingImageCommitment.name,
1477
+ requestHash,
1478
+ },
1479
+ });
1480
+ }
1481
+ const pendingRecord = {
1482
+ userAccount,
1483
+ pending: true,
1484
+ salt,
1485
+ requestHash,
1486
+ deadlineTimestamp,
1487
+ requestedAtTimestamp,
1488
+ ipAddress,
1489
+ sessionId,
1490
+ threshold,
1491
+ ipInfo,
1492
+ dappAccount: "",
1493
+ providerAccount: "",
1494
+ datasetId: "",
1495
+ id: "",
1496
+ result: { status: CaptchaStatus.pending },
1497
+ headers: {},
1498
+ ja4: "",
1499
+ userSignature: "",
1500
+ userSubmitted: false,
1501
+ serverChecked: false,
1502
+ };
1503
+ await this.tables?.commitment.updateOne({ requestHash: requestHash }, { $set: pendingRecord }, { upsert: true });
1504
+ }
1505
+ async getPendingImageCommitment(requestHash) {
1506
+ if (!isHex(requestHash)) {
1507
+ throw new ProsopoError({
1508
+ code: "INVALID_DATA",
1509
+ errorSite: "DBSE-37",
1510
+ message: "Invalid hash",
1511
+ context: {
1512
+ failedFuncName: this.getPendingImageCommitment.name,
1513
+ requestHash,
1514
+ },
1515
+ });
1516
+ }
1517
+ const doc = await this.tables?.commitment
1518
+ .findOne({
1519
+ requestHash: requestHash,
1520
+ pending: true,
1521
+ })
1522
+ .lean();
1523
+ if (doc) {
1524
+ return {
1525
+ dappAccount: doc.dappAccount,
1526
+ pending: doc.pending,
1527
+ salt: doc.salt,
1528
+ requestHash: doc.requestHash,
1529
+ deadlineTimestamp: doc.deadlineTimestamp,
1530
+ requestedAtTimestamp: doc.requestedAtTimestamp,
1531
+ ipAddress: doc.ipAddress,
1532
+ sessionId: doc.sessionId,
1533
+ threshold: doc.threshold,
1534
+ };
1535
+ }
1536
+ throw new ProsopoError({
1537
+ code: "RECORD_NOT_FOUND",
1538
+ errorSite: "DBSE-38",
1539
+ message: "No pending record found",
1540
+ context: {
1541
+ failedFuncName: this.getPendingImageCommitment.name,
1542
+ requestHash,
1543
+ },
1544
+ });
1545
+ }
1546
+ async updatePendingImageCommitmentStatus(requestHash) {
1547
+ if (!isHex(requestHash)) {
1548
+ throw new ProsopoError({
1549
+ code: "INVALID_DATA",
1550
+ errorSite: "DBSE-39",
1551
+ message: "Invalid hash",
1552
+ context: {
1553
+ failedFuncName: this.updatePendingImageCommitmentStatus.name,
1554
+ requestHash,
1555
+ },
1556
+ });
1557
+ }
1558
+ const result = await this.getTables().commitment.updateOne({ requestHash: requestHash, pending: true }, {
1559
+ $set: {
1560
+ pending: false,
1561
+ },
1562
+ });
1563
+ return result.modifiedCount > 0;
1564
+ }
1565
+ async getAllCaptchasByDatasetId(datasetId, state) {
1566
+ const filter = {
1567
+ datasetId,
1568
+ solved: state === CaptchaStates.Solved,
1569
+ };
1570
+ const cursor = this.tables?.captcha
1571
+ .find(filter)
1572
+ .lean();
1573
+ const docs = await cursor;
1574
+ if (docs) {
1575
+ return docs.map(({ _id, ...keepAttrs }) => keepAttrs);
1576
+ }
1577
+ throw new ProsopoError({
1578
+ code: "DB_QUERY_FAILED",
1579
+ errorSite: "DBSE-40",
1580
+ message: "Failed to get captcha",
1581
+ });
1582
+ }
1583
+ async getAllDappUserSolutions(captchaId) {
1584
+ const filter = {
1585
+ captchaId: { $in: captchaId },
1586
+ };
1587
+ const cursor = this.tables?.usersolution
1588
+ ?.find(filter)
1589
+ .lean();
1590
+ const docs = await cursor;
1591
+ if (docs) {
1592
+ return docs.map(({ _id, ...keepAttrs }) => keepAttrs);
1593
+ }
1594
+ throw new ProsopoError({
1595
+ code: "DB_QUERY_FAILED",
1596
+ errorSite: "DBSE-41",
1597
+ message: "Failed to get solution",
1598
+ });
1599
+ }
1600
+ async getDatasetIdWithSolvedCaptchasOfSizeN(solvedCaptchaCount) {
1601
+ const cursor = this.tables?.solution.aggregate([
1602
+ {
1603
+ $match: {},
1604
+ },
1605
+ {
1606
+ $group: {
1607
+ _id: "$datasetId",
1608
+ count: { $sum: 1 },
1609
+ },
1610
+ },
1611
+ {
1612
+ $match: {
1613
+ count: { $gte: solvedCaptchaCount },
1614
+ },
1615
+ },
1616
+ {
1617
+ $sample: { size: 1 },
1618
+ },
1619
+ ]);
1620
+ const docs = await cursor;
1621
+ if (docs?.length) {
1622
+ return docs[0]._id;
1623
+ }
1624
+ throw new ProsopoError({
1625
+ code: "DATASET_INVALID",
1626
+ errorSite: "DBSE-42",
1627
+ message: "No datasets found with required number of solutions",
1628
+ });
1629
+ }
1630
+ async getRandomSolvedCaptchasFromSingleDataset(datasetId, size) {
1631
+ if (!isHex(datasetId)) {
1632
+ throw new ProsopoError({
1633
+ code: "INVALID_DATA",
1634
+ errorSite: "DBSE-43",
1635
+ message: "Invalid hash",
1636
+ context: {
1637
+ failedFuncName: this.getRandomSolvedCaptchasFromSingleDataset.name,
1638
+ datasetId,
1639
+ },
1640
+ });
1641
+ }
1642
+ const sampleSize = size ? Math.abs(Math.trunc(size)) : 1;
1643
+ const cursor = this.tables?.solution.aggregate([
1644
+ { $match: { datasetId } },
1645
+ { $sample: { size: sampleSize } },
1646
+ {
1647
+ $project: {
1648
+ captchaId: 1,
1649
+ captchaContentId: 1,
1650
+ solution: 1,
1651
+ },
1652
+ },
1653
+ ]);
1654
+ const docs = await cursor;
1655
+ if (docs?.length) {
1656
+ return docs;
1657
+ }
1658
+ throw new ProsopoError({
1659
+ code: "DB_QUERY_FAILED",
1660
+ errorSite: "DBSE-44",
1661
+ message: "Failed to get solution",
1662
+ context: {
1663
+ failedFuncName: this.getRandomSolvedCaptchasFromSingleDataset.name,
1664
+ datasetId,
1665
+ size,
1666
+ },
1667
+ });
1668
+ }
1669
+ async getDappUserSolutionById(commitmentId) {
1670
+ const filter = {
1671
+ commitmentId: commitmentId,
1672
+ };
1673
+ const project = { _id: 0 };
1674
+ const cursor = this.tables?.usersolution?.findOne(filter, project).lean();
1675
+ const doc = await cursor;
1676
+ if (doc) {
1677
+ return doc;
1678
+ }
1679
+ throw new ProsopoError({
1680
+ code: "DB_QUERY_FAILED",
1681
+ errorSite: "DBSE-45",
1682
+ message: "Failed to get solution",
1683
+ context: { failedFuncName: this.getCaptchaById.name, commitmentId },
1684
+ });
1685
+ }
1686
+ async getDappUserCommitmentById(commitmentId) {
1687
+ const filter = { id: commitmentId };
1688
+ const commitmentCursor = this.tables?.commitment
1689
+ ?.findOne(filter, ProviderDatabase.DAPP_USER_COMMITMENT_PROJECTION)
1690
+ .lean();
1691
+ const doc = await commitmentCursor;
1692
+ return doc ? doc : undefined;
1693
+ }
1694
+ async getDappUserCommitmentByAccount(userAccount, dappAccount) {
1695
+ const filter = {
1696
+ userAccount,
1697
+ dappAccount,
1698
+ };
1699
+ const sort = { sort: { _id: -1 } };
1700
+ const docs = await this.tables?.commitment
1701
+ ?.find(filter, ProviderDatabase.DAPP_USER_COMMITMENT_PROJECTION, sort)
1702
+ .lean();
1703
+ return docs ? docs : [];
1704
+ }
1705
+ async approveDappUserCommitment(commitmentId, coords) {
1706
+ try {
1707
+ const result = { status: CaptchaStatus.approved };
1708
+ const updateDoc = {
1709
+ result,
1710
+ lastUpdatedTimestamp: new Date(),
1711
+ pendingStage: true,
1712
+ blocked: isBlockingCaptchaResult(CaptchaType.image, result),
1713
+ ...(coords ? { coords } : {}),
1714
+ };
1715
+ const filter = { id: commitmentId };
1716
+ await this.tables?.commitment
1717
+ ?.findOneAndUpdate(filter, { $set: updateDoc }, { upsert: false })
1718
+ .lean();
1719
+ this.centralStreamer?.streamImageUpdate(() => this.tables.commitment
1720
+ .findOne({ id: commitmentId })
1721
+ .lean(), (ts) => this.tables.commitment
1722
+ .updateOne({ id: commitmentId, lastUpdatedTimestamp: { $lte: ts } }, {
1723
+ $set: { storedAtTimestamp: ts },
1724
+ $unset: { pendingStage: 1 },
1725
+ })
1726
+ .then(() => { }));
1727
+ }
1728
+ catch (err) {
1729
+ throw new ProsopoError({
1730
+ code: "DB_QUERY_FAILED",
1731
+ errorSite: "DBSE-46",
1732
+ message: "Failed to approve solution",
1733
+ cause: err,
1734
+ context: { commitmentId },
1735
+ });
1736
+ }
1737
+ }
1738
+ async disapproveDappUserCommitment(commitmentId, reason, coords) {
1739
+ try {
1740
+ const result = {
1741
+ status: CaptchaStatus.disapproved,
1742
+ reason,
1743
+ };
1744
+ const updateDoc = {
1745
+ result,
1746
+ lastUpdatedTimestamp: new Date(),
1747
+ pendingStage: true,
1748
+ blocked: isBlockingCaptchaResult(CaptchaType.image, result),
1749
+ ...(coords ? { coords } : {}),
1750
+ };
1751
+ const filter = { id: commitmentId };
1752
+ await this.tables?.commitment
1753
+ ?.findOneAndUpdate(filter, { $set: updateDoc }, { upsert: false })
1754
+ .lean();
1755
+ this.centralStreamer?.streamImageUpdate(() => this.tables.commitment
1756
+ .findOne({ id: commitmentId })
1757
+ .lean(), (ts) => this.tables.commitment
1758
+ .updateOne({ id: commitmentId, lastUpdatedTimestamp: { $lte: ts } }, {
1759
+ $set: { storedAtTimestamp: ts },
1760
+ $unset: { pendingStage: 1 },
1761
+ })
1762
+ .then(() => { }));
1763
+ }
1764
+ catch (err) {
1765
+ throw new ProsopoError({
1766
+ code: "DB_QUERY_FAILED",
1767
+ errorSite: "DBSE-47",
1768
+ message: "Failed to approve solution",
1769
+ cause: err,
1770
+ context: { commitmentId },
1771
+ });
1772
+ }
1773
+ }
1774
+ async flagProcessedDappUserSolutions(captchaIds) {
1775
+ try {
1776
+ await this.tables?.usersolution
1777
+ ?.updateMany({ captchaId: { $in: captchaIds } }, { $set: { processed: true } }, { upsert: false })
1778
+ .lean();
1779
+ }
1780
+ catch (err) {
1781
+ throw new ProsopoError({
1782
+ code: "DB_QUERY_FAILED",
1783
+ errorSite: "DBSE-48",
1784
+ message: "Failed to flag solution as processed",
1785
+ cause: err,
1786
+ context: { captchaIds },
1787
+ });
1788
+ }
1789
+ }
1790
+ async flagProcessedDappUserCommitments(commitmentIds) {
1791
+ try {
1792
+ const distinctCommitmentIds = [...new Set(commitmentIds)];
1793
+ await this.tables?.commitment
1794
+ ?.updateMany({ id: { $in: distinctCommitmentIds } }, { $set: { processed: true } }, { upsert: false })
1795
+ .lean();
1796
+ }
1797
+ catch (err) {
1798
+ throw new ProsopoError({
1799
+ code: "DB_QUERY_FAILED",
1800
+ errorSite: "DBSE-49",
1801
+ message: "Failed to flag commitment as processed",
1802
+ cause: err,
1803
+ context: { commitmentIds },
1804
+ });
1805
+ }
1806
+ }
1807
+ async getScheduledTaskStatus(taskId, status) {
1808
+ const filter = {
1809
+ _id: taskId,
1810
+ status: status,
1811
+ };
1812
+ const cursor = await this.tables?.scheduler
1813
+ ?.findOne(filter)
1814
+ .lean();
1815
+ return cursor ? cursor : undefined;
1816
+ }
1817
+ async getLastScheduledTaskStatus(task, status) {
1818
+ const filter = { processName: task };
1819
+ if (status) {
1820
+ filter.status = status;
1821
+ }
1822
+ const sort = {
1823
+ datetime: -1,
1824
+ };
1825
+ const cursor = await this.tables?.scheduler
1826
+ ?.findOne(filter)
1827
+ .sort(sort)
1828
+ .limit(1)
1829
+ .lean();
1830
+ return cursor ? cursor : undefined;
1831
+ }
1832
+ async createScheduledTaskStatus(taskName, status) {
1833
+ const now = new Date();
1834
+ const doc = ScheduledTaskSchema.parse({
1835
+ processName: taskName,
1836
+ datetime: now,
1837
+ status,
1838
+ });
1839
+ const taskRecord = await this.tables?.scheduler.create(doc);
1840
+ return taskRecord._id;
1841
+ }
1842
+ async updateScheduledTaskStatus(taskId, status, result) {
1843
+ const update = {
1844
+ status,
1845
+ updated: new Date(),
1846
+ ...(result && { result }),
1847
+ };
1848
+ const filter = { _id: taskId };
1849
+ await this.tables?.scheduler.updateOne(filter, { $set: update }, {
1850
+ upsert: false,
1851
+ });
1852
+ }
1853
+ async cleanupScheduledTaskStatus(status) {
1854
+ const filter = {
1855
+ status,
1856
+ };
1857
+ await this.tables?.scheduler.deleteMany(filter);
1858
+ }
1859
+ async updateClientRecords(clientRecords) {
1860
+ const usable = clientRecords.filter((record) => !!record.account);
1861
+ const skipped = clientRecords.length - usable.length;
1862
+ if (skipped > 0) {
1863
+ await this.logger.error(() => ({
1864
+ msg: "Refusing to upsert client records with no account",
1865
+ data: { skipped, received: clientRecords.length },
1866
+ }));
1867
+ }
1868
+ const ops = usable.map((record) => {
1869
+ const clientRecord = {
1870
+ account: record.account,
1871
+ settings: record.settings,
1872
+ tier: record.tier,
1873
+ };
1874
+ const filter = {
1875
+ account: record.account,
1876
+ };
1877
+ return {
1878
+ updateOne: {
1879
+ filter,
1880
+ update: {
1881
+ $set: clientRecord,
1882
+ },
1883
+ upsert: true,
1884
+ },
1885
+ };
1886
+ });
1887
+ if (!ops || ops.length === 0) {
1888
+ await this.logger.debug(() => ({ msg: "No client updates to apply" }));
1889
+ return;
1890
+ }
1891
+ try {
1892
+ await this.tables?.client.bulkWrite(ops);
1893
+ }
1894
+ finally {
1895
+ clientRecordCache.clear();
1896
+ }
1897
+ }
1898
+ async removeClientRecords(accounts) {
1899
+ if (!accounts || accounts.length === 0) {
1900
+ return;
1901
+ }
1902
+ try {
1903
+ await this.tables?.client.deleteMany({
1904
+ account: { $in: accounts },
1905
+ });
1906
+ }
1907
+ finally {
1908
+ clientRecordCache.clear();
1909
+ }
1910
+ }
1911
+ async getAllClientRecords() {
1912
+ const docs = await this.tables?.client.find().lean();
1913
+ return docs || [];
1914
+ }
1915
+ async getClientRecord(account) {
1916
+ const cacheKey = `${this.url}|${account}`;
1917
+ const cached = clientRecordCache.get(cacheKey);
1918
+ if (cached)
1919
+ return cached;
1920
+ const filter = { account };
1921
+ const doc = await this.tables?.client
1922
+ .findOne(filter, {
1923
+ account: 1,
1924
+ settings: 1,
1925
+ tier: 1,
1926
+ })
1927
+ .lean();
1928
+ if (!doc)
1929
+ return undefined;
1930
+ clientRecordCache.set(cacheKey, doc);
1931
+ return doc;
1932
+ }
1933
+ async upsertDecisionMachineArtifact(artifact) {
1934
+ const now = new Date();
1935
+ const dappAccount = artifact.dappAccount ?? null;
1936
+ const kind = artifact.kind ?? DecisionMachineKind.Routing;
1937
+ const filter = {
1938
+ scope: artifact.scope,
1939
+ dappAccount,
1940
+ kind,
1941
+ };
1942
+ await this.tables?.decisionMachine.updateOne(filter, {
1943
+ $set: {
1944
+ scope: artifact.scope,
1945
+ dappAccount,
1946
+ kind,
1947
+ runtime: artifact.runtime,
1948
+ language: artifact.language,
1949
+ source: artifact.source,
1950
+ name: artifact.name,
1951
+ version: artifact.version,
1952
+ captchaType: artifact.captchaType,
1953
+ updatedAt: now,
1954
+ },
1955
+ $setOnInsert: {
1956
+ createdAt: now,
1957
+ },
1958
+ }, { upsert: true });
1959
+ }
1960
+ async getDecisionMachineArtifact(scope, dappAccount, kind) {
1961
+ const filter = {
1962
+ scope,
1963
+ dappAccount: dappAccount ?? null,
1964
+ };
1965
+ if (kind !== undefined) {
1966
+ filter.kind = kind;
1967
+ }
1968
+ const doc = await this.tables?.decisionMachine
1969
+ .findOne(filter)
1970
+ .lean();
1971
+ return doc ?? undefined;
1972
+ }
1973
+ async getAllDecisionMachineArtifacts() {
1974
+ const docs = await this.tables?.decisionMachine
1975
+ .find({})
1976
+ .lean();
1977
+ return docs ?? [];
1978
+ }
1979
+ async getDecisionMachineArtifactById(id) {
1980
+ const doc = await this.tables?.decisionMachine
1981
+ .findById(id)
1982
+ .lean();
1983
+ return doc ?? undefined;
1984
+ }
1985
+ async removeDecisionMachineArtifact(id) {
1986
+ const result = await this.tables?.decisionMachine.deleteOne({
1987
+ _id: id,
1988
+ });
1989
+ return (result?.deletedCount ?? 0) > 0;
1990
+ }
1991
+ async removeAllDecisionMachineArtifacts() {
1992
+ const result = await this.tables?.decisionMachine.deleteMany({});
1993
+ return result?.deletedCount ?? 0;
1994
+ }
1995
+ async getSpamEmailDomain(domain) {
1996
+ if (!this.tables?.spamEmailDomain) {
1997
+ throw new ProsopoError({
1998
+ code: "DB_QUERY_FAILED",
1999
+ errorSite: "DBSE-50",
2000
+ message: "Failed to import database engine",
2001
+ context: { failedFuncName: this.getSpamEmailDomain.name },
2002
+ });
2003
+ }
2004
+ const suffixCandidates = buildDomainSuffixCandidates(domain).slice(0, MAX_DOMAIN_SUFFIX_CANDIDATES);
2005
+ const query = suffixCandidates.length > 0
2006
+ ? { domain: { $in: suffixCandidates } }
2007
+ : { domain };
2008
+ return await this.tables.spamEmailDomain
2009
+ .findOne(query)
2010
+ .exec();
2011
+ }
2012
+ async bulkUpdateSpamEmailDomains(domains, upsert) {
2013
+ if (!this.tables?.spamEmailDomain) {
2014
+ throw new ProsopoError({
2015
+ code: "DB_QUERY_FAILED",
2016
+ errorSite: "DBSE-51",
2017
+ message: "Failed to import database engine",
2018
+ context: { failedFuncName: this.bulkUpdateSpamEmailDomains.name },
2019
+ });
2020
+ }
2021
+ const bulkOps = domains.map((op) => ({
2022
+ updateOne: {
2023
+ filter: op.filter,
2024
+ update: { $set: op.update },
2025
+ upsert,
2026
+ },
2027
+ }));
2028
+ if (bulkOps.length > 0) {
2029
+ await this.tables.spamEmailDomain.bulkWrite(bulkOps);
2030
+ }
2031
+ }
2032
+ }
2033
+ ProviderDatabase.DAPP_USER_COMMITMENT_PROJECTION = {
2034
+ id: 1,
2035
+ result: 1,
2036
+ serverChecked: 1,
2037
+ requestedAtTimestamp: 1,
2038
+ submittedAtTimestamp: 1,
2039
+ verifiedAtTimestamp: 1,
2040
+ failedAtTimestamp: 1,
2041
+ ipAddress: 1,
2042
+ sessionId: 1,
2043
+ userAccount: 1,
2044
+ dappAccount: 1,
2045
+ headers: 1,
2046
+ ipInfo: 1,
2047
+ behavioralDataPacked: 1,
2048
+ deviceCapability: 1,
2049
+ coords: 1,
2050
+ clientMetaData: 1,
2051
+ };
2052
+ //# sourceMappingURL=provider.js.map