@pkcprotocol/pkc-js 0.0.11

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 (843) hide show
  1. package/LICENSE +339 -0
  2. package/README.md +1663 -0
  3. package/dist/browser/challenges.d.ts +1 -0
  4. package/dist/browser/challenges.js +2 -0
  5. package/dist/browser/challenges.js.map +1 -0
  6. package/dist/browser/clients/base-client-manager.d.ts +126 -0
  7. package/dist/browser/clients/base-client-manager.js +673 -0
  8. package/dist/browser/clients/base-client-manager.js.map +1 -0
  9. package/dist/browser/clients/name-resolver-client.d.ts +8 -0
  10. package/dist/browser/clients/name-resolver-client.js +10 -0
  11. package/dist/browser/clients/name-resolver-client.js.map +1 -0
  12. package/dist/browser/clients/pkc-typed-emitter.d.ts +9 -0
  13. package/dist/browser/clients/pkc-typed-emitter.js +52 -0
  14. package/dist/browser/clients/pkc-typed-emitter.js.map +1 -0
  15. package/dist/browser/clients/rpc-client/decode-rpc-response-util.d.ts +8 -0
  16. package/dist/browser/clients/rpc-client/decode-rpc-response-util.js +53 -0
  17. package/dist/browser/clients/rpc-client/decode-rpc-response-util.js.map +1 -0
  18. package/dist/browser/clients/rpc-client/pkc-rpc-client.d.ts +68 -0
  19. package/dist/browser/clients/rpc-client/pkc-rpc-client.js +404 -0
  20. package/dist/browser/clients/rpc-client/pkc-rpc-client.js.map +1 -0
  21. package/dist/browser/clients/rpc-client/rpc-schema-util.d.ts +147 -0
  22. package/dist/browser/clients/rpc-client/rpc-schema-util.js +11 -0
  23. package/dist/browser/clients/rpc-client/rpc-schema-util.js.map +1 -0
  24. package/dist/browser/clients/rpc-client/schema.d.ts +433 -0
  25. package/dist/browser/clients/rpc-client/schema.js +49 -0
  26. package/dist/browser/clients/rpc-client/schema.js.map +1 -0
  27. package/dist/browser/clients/rpc-client/types.d.ts +8 -0
  28. package/dist/browser/clients/rpc-client/types.js +2 -0
  29. package/dist/browser/clients/rpc-client/types.js.map +1 -0
  30. package/dist/browser/community/community-client-manager.d.ts +60 -0
  31. package/dist/browser/community/community-client-manager.js +717 -0
  32. package/dist/browser/community/community-client-manager.js.map +1 -0
  33. package/dist/browser/community/community-clients.d.ts +18 -0
  34. package/dist/browser/community/community-clients.js +12 -0
  35. package/dist/browser/community/community-clients.js.map +1 -0
  36. package/dist/browser/community/community-wire.d.ts +20 -0
  37. package/dist/browser/community/community-wire.js +38 -0
  38. package/dist/browser/community/community-wire.js.map +1 -0
  39. package/dist/browser/community/remote-community.d.ts +110 -0
  40. package/dist/browser/community/remote-community.js +555 -0
  41. package/dist/browser/community/remote-community.js.map +1 -0
  42. package/dist/browser/community/rpc-local-community.d.ts +41 -0
  43. package/dist/browser/community/rpc-local-community.js +289 -0
  44. package/dist/browser/community/rpc-local-community.js.map +1 -0
  45. package/dist/browser/community/rpc-remote-community.d.ts +18 -0
  46. package/dist/browser/community/rpc-remote-community.js +286 -0
  47. package/dist/browser/community/rpc-remote-community.js.map +1 -0
  48. package/dist/browser/community/schema.d.ts +4217 -0
  49. package/dist/browser/community/schema.js +289 -0
  50. package/dist/browser/community/schema.js.map +1 -0
  51. package/dist/browser/community/types.d.ts +135 -0
  52. package/dist/browser/community/types.js +2 -0
  53. package/dist/browser/community/types.js.map +1 -0
  54. package/dist/browser/constants.d.ts +6 -0
  55. package/dist/browser/constants.js +9 -0
  56. package/dist/browser/constants.js.map +1 -0
  57. package/dist/browser/decorator-util.d.ts +1 -0
  58. package/dist/browser/decorator-util.js +35 -0
  59. package/dist/browser/decorator-util.js.map +1 -0
  60. package/dist/browser/errors.d.ts +343 -0
  61. package/dist/browser/errors.js +358 -0
  62. package/dist/browser/errors.js.map +1 -0
  63. package/dist/browser/general-util/limited-set.d.ts +15 -0
  64. package/dist/browser/general-util/limited-set.js +66 -0
  65. package/dist/browser/general-util/limited-set.js.map +1 -0
  66. package/dist/browser/generated-version.d.ts +1 -0
  67. package/dist/browser/generated-version.js +3 -0
  68. package/dist/browser/generated-version.js.map +1 -0
  69. package/dist/browser/generic-state-client.d.ts +6 -0
  70. package/dist/browser/generic-state-client.js +11 -0
  71. package/dist/browser/generic-state-client.js.map +1 -0
  72. package/dist/browser/helia/helia-for-pkc.d.ts +3 -0
  73. package/dist/browser/helia/helia-for-pkc.js +255 -0
  74. package/dist/browser/helia/helia-for-pkc.js.map +1 -0
  75. package/dist/browser/helia/ipns-over-pubsub-with-fetch.d.ts +36 -0
  76. package/dist/browser/helia/ipns-over-pubsub-with-fetch.js +229 -0
  77. package/dist/browser/helia/ipns-over-pubsub-with-fetch.js.map +1 -0
  78. package/dist/browser/helia/libp2pjsClient.d.ts +27 -0
  79. package/dist/browser/helia/libp2pjsClient.js +15 -0
  80. package/dist/browser/helia/libp2pjsClient.js.map +1 -0
  81. package/dist/browser/helia/types.d.ts +19 -0
  82. package/dist/browser/helia/types.js +2 -0
  83. package/dist/browser/helia/types.js.map +1 -0
  84. package/dist/browser/helia/util.d.ts +13 -0
  85. package/dist/browser/helia/util.js +98 -0
  86. package/dist/browser/helia/util.js.map +1 -0
  87. package/dist/browser/index.d.ts +244 -0
  88. package/dist/browser/index.js +36 -0
  89. package/dist/browser/index.js.map +1 -0
  90. package/dist/browser/logger.d.ts +12 -0
  91. package/dist/browser/logger.js +11 -0
  92. package/dist/browser/logger.js.map +1 -0
  93. package/dist/browser/pages/pages-client-manager.d.ts +159 -0
  94. package/dist/browser/pages/pages-client-manager.js +334 -0
  95. package/dist/browser/pages/pages-client-manager.js.map +1 -0
  96. package/dist/browser/pages/pages-clients.d.ts +11 -0
  97. package/dist/browser/pages/pages-clients.js +10 -0
  98. package/dist/browser/pages/pages-clients.js.map +1 -0
  99. package/dist/browser/pages/pages.d.ts +107 -0
  100. package/dist/browser/pages/pages.js +262 -0
  101. package/dist/browser/pages/pages.js.map +1 -0
  102. package/dist/browser/pages/schema-util.d.ts +3 -0
  103. package/dist/browser/pages/schema-util.js +3 -0
  104. package/dist/browser/pages/schema-util.js.map +1 -0
  105. package/dist/browser/pages/schema.d.ts +719 -0
  106. package/dist/browser/pages/schema.js +32 -0
  107. package/dist/browser/pages/schema.js.map +1 -0
  108. package/dist/browser/pages/types.d.ts +44 -0
  109. package/dist/browser/pages/types.js +2 -0
  110. package/dist/browser/pages/types.js.map +1 -0
  111. package/dist/browser/pages/util.d.ts +56 -0
  112. package/dist/browser/pages/util.js +446 -0
  113. package/dist/browser/pages/util.js.map +1 -0
  114. package/dist/browser/pkc/pkc-client-manager.d.ts +44 -0
  115. package/dist/browser/pkc/pkc-client-manager.js +156 -0
  116. package/dist/browser/pkc/pkc-client-manager.js.map +1 -0
  117. package/dist/browser/pkc/pkc-clients.d.ts +11 -0
  118. package/dist/browser/pkc/pkc-clients.js +8 -0
  119. package/dist/browser/pkc/pkc-clients.js.map +1 -0
  120. package/dist/browser/pkc/pkc-with-rpc-client.d.ts +19 -0
  121. package/dist/browser/pkc/pkc-with-rpc-client.js +128 -0
  122. package/dist/browser/pkc/pkc-with-rpc-client.js.map +1 -0
  123. package/dist/browser/pkc/pkc.d.ts +137 -0
  124. package/dist/browser/pkc/pkc.js +888 -0
  125. package/dist/browser/pkc/pkc.js.map +1 -0
  126. package/dist/browser/pkc/tracked-instance-registry-util.d.ts +44 -0
  127. package/dist/browser/pkc/tracked-instance-registry-util.js +106 -0
  128. package/dist/browser/pkc/tracked-instance-registry-util.js.map +1 -0
  129. package/dist/browser/pkc/tracked-instance-registry.d.ts +18 -0
  130. package/dist/browser/pkc/tracked-instance-registry.js +134 -0
  131. package/dist/browser/pkc/tracked-instance-registry.js.map +1 -0
  132. package/dist/browser/pkc-error.d.ts +65 -0
  133. package/dist/browser/pkc-error.js +137 -0
  134. package/dist/browser/pkc-error.js.map +1 -0
  135. package/dist/browser/publications/comment/comment-client-manager.d.ts +86 -0
  136. package/dist/browser/publications/comment/comment-client-manager.js +908 -0
  137. package/dist/browser/publications/comment/comment-client-manager.js.map +1 -0
  138. package/dist/browser/publications/comment/comment-clients.d.ts +19 -0
  139. package/dist/browser/publications/comment/comment-clients.js +12 -0
  140. package/dist/browser/publications/comment/comment-clients.js.map +1 -0
  141. package/dist/browser/publications/comment/comment-util.d.ts +10 -0
  142. package/dist/browser/publications/comment/comment-util.js +202 -0
  143. package/dist/browser/publications/comment/comment-util.js.map +1 -0
  144. package/dist/browser/publications/comment/comment.d.ts +147 -0
  145. package/dist/browser/publications/comment/comment.js +1044 -0
  146. package/dist/browser/publications/comment/comment.js.map +1 -0
  147. package/dist/browser/publications/comment/schema.d.ts +1237 -0
  148. package/dist/browser/publications/comment/schema.js +184 -0
  149. package/dist/browser/publications/comment/schema.js.map +1 -0
  150. package/dist/browser/publications/comment/types.d.ts +100 -0
  151. package/dist/browser/publications/comment/types.js +2 -0
  152. package/dist/browser/publications/comment/types.js.map +1 -0
  153. package/dist/browser/publications/comment-edit/comment-edit.d.ts +41 -0
  154. package/dist/browser/publications/comment-edit/comment-edit.js +63 -0
  155. package/dist/browser/publications/comment-edit/comment-edit.js.map +1 -0
  156. package/dist/browser/publications/comment-edit/schema.d.ts +295 -0
  157. package/dist/browser/publications/comment-edit/schema.js +55 -0
  158. package/dist/browser/publications/comment-edit/schema.js.map +1 -0
  159. package/dist/browser/publications/comment-edit/types.d.ts +25 -0
  160. package/dist/browser/publications/comment-edit/types.js +2 -0
  161. package/dist/browser/publications/comment-edit/types.js.map +1 -0
  162. package/dist/browser/publications/comment-moderation/comment-moderation.d.ts +36 -0
  163. package/dist/browser/publications/comment-moderation/comment-moderation.js +53 -0
  164. package/dist/browser/publications/comment-moderation/comment-moderation.js.map +1 -0
  165. package/dist/browser/publications/comment-moderation/schema.d.ts +315 -0
  166. package/dist/browser/publications/comment-moderation/schema.js +62 -0
  167. package/dist/browser/publications/comment-moderation/schema.js.map +1 -0
  168. package/dist/browser/publications/comment-moderation/types.d.ts +22 -0
  169. package/dist/browser/publications/comment-moderation/types.js +2 -0
  170. package/dist/browser/publications/comment-moderation/types.js.map +1 -0
  171. package/dist/browser/publications/community-edit/community-edit.d.ts +35 -0
  172. package/dist/browser/publications/community-edit/community-edit.js +50 -0
  173. package/dist/browser/publications/community-edit/community-edit.js.map +1 -0
  174. package/dist/browser/publications/community-edit/schema.d.ts +467 -0
  175. package/dist/browser/publications/community-edit/schema.js +36 -0
  176. package/dist/browser/publications/community-edit/schema.js.map +1 -0
  177. package/dist/browser/publications/community-edit/types.d.ts +19 -0
  178. package/dist/browser/publications/community-edit/types.js +2 -0
  179. package/dist/browser/publications/community-edit/types.js.map +1 -0
  180. package/dist/browser/publications/publication-author.d.ts +22 -0
  181. package/dist/browser/publications/publication-author.js +66 -0
  182. package/dist/browser/publications/publication-author.js.map +1 -0
  183. package/dist/browser/publications/publication-client-manager.d.ts +62 -0
  184. package/dist/browser/publications/publication-client-manager.js +257 -0
  185. package/dist/browser/publications/publication-client-manager.js.map +1 -0
  186. package/dist/browser/publications/publication-clients.d.ts +19 -0
  187. package/dist/browser/publications/publication-clients.js +12 -0
  188. package/dist/browser/publications/publication-clients.js.map +1 -0
  189. package/dist/browser/publications/publication-community.d.ts +55 -0
  190. package/dist/browser/publications/publication-community.js +87 -0
  191. package/dist/browser/publications/publication-community.js.map +1 -0
  192. package/dist/browser/publications/publication.d.ts +120 -0
  193. package/dist/browser/publications/publication.js +950 -0
  194. package/dist/browser/publications/publication.js.map +1 -0
  195. package/dist/browser/publications/types.d.ts +26 -0
  196. package/dist/browser/publications/types.js +2 -0
  197. package/dist/browser/publications/types.js.map +1 -0
  198. package/dist/browser/publications/vote/schema.d.ts +150 -0
  199. package/dist/browser/publications/vote/schema.js +44 -0
  200. package/dist/browser/publications/vote/schema.js.map +1 -0
  201. package/dist/browser/publications/vote/types.d.ts +21 -0
  202. package/dist/browser/publications/vote/types.js +2 -0
  203. package/dist/browser/publications/vote/types.js.map +1 -0
  204. package/dist/browser/publications/vote/vote.d.ts +36 -0
  205. package/dist/browser/publications/vote/vote.js +49 -0
  206. package/dist/browser/publications/vote/vote.js.map +1 -0
  207. package/dist/browser/pubsub-messages/schema.d.ts +964 -0
  208. package/dist/browser/pubsub-messages/schema.js +98 -0
  209. package/dist/browser/pubsub-messages/schema.js.map +1 -0
  210. package/dist/browser/pubsub-messages/types.d.ts +81 -0
  211. package/dist/browser/pubsub-messages/types.js +2 -0
  212. package/dist/browser/pubsub-messages/types.js.map +1 -0
  213. package/dist/browser/rpc/src/index.d.ts +483 -0
  214. package/dist/browser/rpc/src/index.js +1267 -0
  215. package/dist/browser/rpc/src/index.js.map +1 -0
  216. package/dist/browser/rpc/src/json-rpc-util.d.ts +1 -0
  217. package/dist/browser/rpc/src/json-rpc-util.js +19 -0
  218. package/dist/browser/rpc/src/json-rpc-util.js.map +1 -0
  219. package/dist/browser/rpc/src/lib/pkc-js/index.d.ts +132 -0
  220. package/dist/browser/rpc/src/lib/pkc-js/index.js +29 -0
  221. package/dist/browser/rpc/src/lib/pkc-js/index.js.map +1 -0
  222. package/dist/browser/rpc/src/lib/pkc-js/pkc-js-mock.d.ts +1 -0
  223. package/dist/browser/rpc/src/lib/pkc-js/pkc-js-mock.js +472 -0
  224. package/dist/browser/rpc/src/lib/pkc-js/pkc-js-mock.js.map +1 -0
  225. package/dist/browser/rpc/src/schema.d.ts +843 -0
  226. package/dist/browser/rpc/src/schema.js +28 -0
  227. package/dist/browser/rpc/src/schema.js.map +1 -0
  228. package/dist/browser/rpc/src/types.d.ts +24 -0
  229. package/dist/browser/rpc/src/types.js +2 -0
  230. package/dist/browser/rpc/src/types.js.map +1 -0
  231. package/dist/browser/rpc/src/utils.d.ts +7 -0
  232. package/dist/browser/rpc/src/utils.js +58 -0
  233. package/dist/browser/rpc/src/utils.js.map +1 -0
  234. package/dist/browser/runtime/browser/community/challenges/index.d.ts +6 -0
  235. package/dist/browser/runtime/browser/community/challenges/index.js +7 -0
  236. package/dist/browser/runtime/browser/community/challenges/index.js.map +1 -0
  237. package/dist/browser/runtime/browser/community/local-community.d.ts +3 -0
  238. package/dist/browser/runtime/browser/community/local-community.js +6 -0
  239. package/dist/browser/runtime/browser/community/local-community.js.map +1 -0
  240. package/dist/browser/runtime/browser/db-handler.d.ts +4 -0
  241. package/dist/browser/runtime/browser/db-handler.js +8 -0
  242. package/dist/browser/runtime/browser/db-handler.js.map +1 -0
  243. package/dist/browser/runtime/browser/localforage-lru.d.ts +15 -0
  244. package/dist/browser/runtime/browser/localforage-lru.js +140 -0
  245. package/dist/browser/runtime/browser/localforage-lru.js.map +1 -0
  246. package/dist/browser/runtime/browser/lru-storage.d.ts +14 -0
  247. package/dist/browser/runtime/browser/lru-storage.js +34 -0
  248. package/dist/browser/runtime/browser/lru-storage.js.map +1 -0
  249. package/dist/browser/runtime/browser/native-functions.d.ts +3 -0
  250. package/dist/browser/runtime/browser/native-functions.js +6 -0
  251. package/dist/browser/runtime/browser/native-functions.js.map +1 -0
  252. package/dist/browser/runtime/browser/polyfill.d.ts +3 -0
  253. package/dist/browser/runtime/browser/polyfill.js +37 -0
  254. package/dist/browser/runtime/browser/polyfill.js.map +1 -0
  255. package/dist/browser/runtime/browser/setup-kubo-address-rewriter-and-http-router.d.ts +1 -0
  256. package/dist/browser/runtime/browser/setup-kubo-address-rewriter-and-http-router.js +4 -0
  257. package/dist/browser/runtime/browser/setup-kubo-address-rewriter-and-http-router.js.map +1 -0
  258. package/dist/browser/runtime/browser/storage.d.ts +13 -0
  259. package/dist/browser/runtime/browser/storage.js +37 -0
  260. package/dist/browser/runtime/browser/storage.js.map +1 -0
  261. package/dist/browser/runtime/browser/util.d.ts +14 -0
  262. package/dist/browser/runtime/browser/util.js +61 -0
  263. package/dist/browser/runtime/browser/util.js.map +1 -0
  264. package/dist/browser/runtime/node/address-rewriter-db.d.ts +31 -0
  265. package/dist/browser/runtime/node/address-rewriter-db.js +156 -0
  266. package/dist/browser/runtime/node/address-rewriter-db.js.map +1 -0
  267. package/dist/browser/runtime/node/addresses-rewriter-proxy-server.d.ts +45 -0
  268. package/dist/browser/runtime/node/addresses-rewriter-proxy-server.js +493 -0
  269. package/dist/browser/runtime/node/addresses-rewriter-proxy-server.js.map +1 -0
  270. package/dist/browser/runtime/node/community/challenges/exclude/exclude.d.ts +8 -0
  271. package/dist/browser/runtime/node/community/challenges/exclude/exclude.js +280 -0
  272. package/dist/browser/runtime/node/community/challenges/exclude/exclude.js.map +1 -0
  273. package/dist/browser/runtime/node/community/challenges/exclude/index.d.ts +3 -0
  274. package/dist/browser/runtime/node/community/challenges/exclude/index.js +4 -0
  275. package/dist/browser/runtime/node/community/challenges/exclude/index.js.map +1 -0
  276. package/dist/browser/runtime/node/community/challenges/exclude/rate-limiter.d.ts +5 -0
  277. package/dist/browser/runtime/node/community/challenges/exclude/rate-limiter.js +127 -0
  278. package/dist/browser/runtime/node/community/challenges/exclude/rate-limiter.js.map +1 -0
  279. package/dist/browser/runtime/node/community/challenges/exclude/utils.d.ts +13 -0
  280. package/dist/browser/runtime/node/community/challenges/exclude/utils.js +52 -0
  281. package/dist/browser/runtime/node/community/challenges/exclude/utils.js.map +1 -0
  282. package/dist/browser/runtime/node/community/challenges/index.d.ts +32 -0
  283. package/dist/browser/runtime/node/community/challenges/index.js +307 -0
  284. package/dist/browser/runtime/node/community/challenges/index.js.map +1 -0
  285. package/dist/browser/runtime/node/community/challenges/pkc-js-challenges/blacklist.d.ts +5 -0
  286. package/dist/browser/runtime/node/community/challenges/pkc-js-challenges/blacklist.js +118 -0
  287. package/dist/browser/runtime/node/community/challenges/pkc-js-challenges/blacklist.js.map +1 -0
  288. package/dist/browser/runtime/node/community/challenges/pkc-js-challenges/fail.d.ts +5 -0
  289. package/dist/browser/runtime/node/community/challenges/pkc-js-challenges/fail.js +26 -0
  290. package/dist/browser/runtime/node/community/challenges/pkc-js-challenges/fail.js.map +1 -0
  291. package/dist/browser/runtime/node/community/challenges/pkc-js-challenges/publication-match.d.ts +5 -0
  292. package/dist/browser/runtime/node/community/challenges/pkc-js-challenges/publication-match.js +135 -0
  293. package/dist/browser/runtime/node/community/challenges/pkc-js-challenges/publication-match.js.map +1 -0
  294. package/dist/browser/runtime/node/community/challenges/pkc-js-challenges/question.d.ts +5 -0
  295. package/dist/browser/runtime/node/community/challenges/pkc-js-challenges/question.js +66 -0
  296. package/dist/browser/runtime/node/community/challenges/pkc-js-challenges/question.js.map +1 -0
  297. package/dist/browser/runtime/node/community/challenges/pkc-js-challenges/text-math.d.ts +5 -0
  298. package/dist/browser/runtime/node/community/challenges/pkc-js-challenges/text-math.js +61 -0
  299. package/dist/browser/runtime/node/community/challenges/pkc-js-challenges/text-math.js.map +1 -0
  300. package/dist/browser/runtime/node/community/challenges/pkc-js-challenges/whitelist.d.ts +5 -0
  301. package/dist/browser/runtime/node/community/challenges/pkc-js-challenges/whitelist.js +118 -0
  302. package/dist/browser/runtime/node/community/challenges/pkc-js-challenges/whitelist.js.map +1 -0
  303. package/dist/browser/runtime/node/community/db-handler-types.d.ts +19 -0
  304. package/dist/browser/runtime/node/community/db-handler-types.js +2 -0
  305. package/dist/browser/runtime/node/community/db-handler-types.js.map +1 -0
  306. package/dist/browser/runtime/node/community/db-handler.d.ts +226 -0
  307. package/dist/browser/runtime/node/community/db-handler.js +2462 -0
  308. package/dist/browser/runtime/node/community/db-handler.js.map +1 -0
  309. package/dist/browser/runtime/node/community/db-row-parser.d.ts +19 -0
  310. package/dist/browser/runtime/node/community/db-row-parser.js +40 -0
  311. package/dist/browser/runtime/node/community/db-row-parser.js.map +1 -0
  312. package/dist/browser/runtime/node/community/keyv-better-sqlite3.d.ts +68 -0
  313. package/dist/browser/runtime/node/community/keyv-better-sqlite3.js +251 -0
  314. package/dist/browser/runtime/node/community/keyv-better-sqlite3.js.map +1 -0
  315. package/dist/browser/runtime/node/community/local-community.d.ts +129 -0
  316. package/dist/browser/runtime/node/community/local-community.js +2978 -0
  317. package/dist/browser/runtime/node/community/local-community.js.map +1 -0
  318. package/dist/browser/runtime/node/community/page-generator.d.ts +433 -0
  319. package/dist/browser/runtime/node/community/page-generator.js +441 -0
  320. package/dist/browser/runtime/node/community/page-generator.js.map +1 -0
  321. package/dist/browser/runtime/node/lru-storage.d.ts +14 -0
  322. package/dist/browser/runtime/node/lru-storage.js +40 -0
  323. package/dist/browser/runtime/node/lru-storage.js.map +1 -0
  324. package/dist/browser/runtime/node/native-functions.d.ts +3 -0
  325. package/dist/browser/runtime/node/native-functions.js +7 -0
  326. package/dist/browser/runtime/node/native-functions.js.map +1 -0
  327. package/dist/browser/runtime/node/polyfill.d.ts +3 -0
  328. package/dist/browser/runtime/node/polyfill.js +20 -0
  329. package/dist/browser/runtime/node/polyfill.js.map +1 -0
  330. package/dist/browser/runtime/node/setup-kubo-address-rewriter-and-http-router.d.ts +4 -0
  331. package/dist/browser/runtime/node/setup-kubo-address-rewriter-and-http-router.js +240 -0
  332. package/dist/browser/runtime/node/setup-kubo-address-rewriter-and-http-router.js.map +1 -0
  333. package/dist/browser/runtime/node/sqlite-lru-cache.d.ts +52 -0
  334. package/dist/browser/runtime/node/sqlite-lru-cache.js +127 -0
  335. package/dist/browser/runtime/node/sqlite-lru-cache.js.map +1 -0
  336. package/dist/browser/runtime/node/storage.d.ts +14 -0
  337. package/dist/browser/runtime/node/storage.js +52 -0
  338. package/dist/browser/runtime/node/storage.js.map +1 -0
  339. package/dist/browser/runtime/node/test/helpers/hanging-runner.d.ts +1 -0
  340. package/dist/browser/runtime/node/test/helpers/hanging-runner.js +157 -0
  341. package/dist/browser/runtime/node/test/helpers/hanging-runner.js.map +1 -0
  342. package/dist/browser/runtime/node/test/helpers/run-hanging-node.d.ts +7 -0
  343. package/dist/browser/runtime/node/test/helpers/run-hanging-node.js +68 -0
  344. package/dist/browser/runtime/node/test/helpers/run-hanging-node.js.map +1 -0
  345. package/dist/browser/runtime/node/test/mock-http-router.d.ts +54 -0
  346. package/dist/browser/runtime/node/test/mock-http-router.js +397 -0
  347. package/dist/browser/runtime/node/test/mock-http-router.js.map +1 -0
  348. package/dist/browser/runtime/node/util.d.ts +43 -0
  349. package/dist/browser/runtime/node/util.js +384 -0
  350. package/dist/browser/runtime/node/util.js.map +1 -0
  351. package/dist/browser/schema/schema-util.d.ts +2751 -0
  352. package/dist/browser/schema/schema-util.js +562 -0
  353. package/dist/browser/schema/schema-util.js.map +1 -0
  354. package/dist/browser/schema/schema.d.ts +237 -0
  355. package/dist/browser/schema/schema.js +128 -0
  356. package/dist/browser/schema/schema.js.map +1 -0
  357. package/dist/browser/schema.d.ts +1142 -0
  358. package/dist/browser/schema.js +104 -0
  359. package/dist/browser/schema.js.map +1 -0
  360. package/dist/browser/signer/constants.d.ts +2 -0
  361. package/dist/browser/signer/constants.js +3 -0
  362. package/dist/browser/signer/constants.js.map +1 -0
  363. package/dist/browser/signer/encryption.d.ts +21 -0
  364. package/dist/browser/signer/encryption.js +122 -0
  365. package/dist/browser/signer/encryption.js.map +1 -0
  366. package/dist/browser/signer/index.d.ts +21 -0
  367. package/dist/browser/signer/index.js +49 -0
  368. package/dist/browser/signer/index.js.map +1 -0
  369. package/dist/browser/signer/signatures.d.ts +200 -0
  370. package/dist/browser/signer/signatures.js +594 -0
  371. package/dist/browser/signer/signatures.js.map +1 -0
  372. package/dist/browser/signer/types.d.ts +20 -0
  373. package/dist/browser/signer/types.js +2 -0
  374. package/dist/browser/signer/types.js.map +1 -0
  375. package/dist/browser/signer/util.d.ts +14 -0
  376. package/dist/browser/signer/util.js +156 -0
  377. package/dist/browser/signer/util.js.map +1 -0
  378. package/dist/browser/stats.d.ts +15 -0
  379. package/dist/browser/stats.js +64 -0
  380. package/dist/browser/stats.js.map +1 -0
  381. package/dist/browser/test/mock-ipfs-client.d.ts +34 -0
  382. package/dist/browser/test/mock-ipfs-client.js +208 -0
  383. package/dist/browser/test/mock-ipfs-client.js.map +1 -0
  384. package/dist/browser/test/node/hanging-test/scenarios/comment-publish-pending.scenario.d.ts +8 -0
  385. package/dist/browser/test/node/hanging-test/scenarios/comment-publish-pending.scenario.js +21 -0
  386. package/dist/browser/test/node/hanging-test/scenarios/comment-publish-pending.scenario.js.map +1 -0
  387. package/dist/browser/test/node/hanging-test/scenarios/comment-publish.scenario.d.ts +8 -0
  388. package/dist/browser/test/node/hanging-test/scenarios/comment-publish.scenario.js +19 -0
  389. package/dist/browser/test/node/hanging-test/scenarios/comment-publish.scenario.js.map +1 -0
  390. package/dist/browser/test/node/hanging-test/scenarios/comment-update.scenario.d.ts +8 -0
  391. package/dist/browser/test/node/hanging-test/scenarios/comment-update.scenario.js +22 -0
  392. package/dist/browser/test/node/hanging-test/scenarios/comment-update.scenario.js.map +1 -0
  393. package/dist/browser/test/node/hanging-test/scenarios/community-start.scenario.d.ts +8 -0
  394. package/dist/browser/test/node/hanging-test/scenarios/community-start.scenario.js +23 -0
  395. package/dist/browser/test/node/hanging-test/scenarios/community-start.scenario.js.map +1 -0
  396. package/dist/browser/test/node/hanging-test/scenarios/community-update.scenario.d.ts +8 -0
  397. package/dist/browser/test/node/hanging-test/scenarios/community-update.scenario.js +21 -0
  398. package/dist/browser/test/node/hanging-test/scenarios/community-update.scenario.js.map +1 -0
  399. package/dist/browser/test/node/hanging-test/scenarios/destroy-only.scenario.d.ts +7 -0
  400. package/dist/browser/test/node/hanging-test/scenarios/destroy-only.scenario.js +15 -0
  401. package/dist/browser/test/node/hanging-test/scenarios/destroy-only.scenario.js.map +1 -0
  402. package/dist/browser/test/node/hanging-test/scenarios/hanging-test-util.d.ts +30 -0
  403. package/dist/browser/test/node/hanging-test/scenarios/hanging-test-util.js +46 -0
  404. package/dist/browser/test/node/hanging-test/scenarios/hanging-test-util.js.map +1 -0
  405. package/dist/browser/test/test-util.d.ts +1019 -0
  406. package/dist/browser/test/test-util.js +1886 -0
  407. package/dist/browser/test/test-util.js.map +1 -0
  408. package/dist/browser/types.d.ts +165 -0
  409. package/dist/browser/types.js +2 -0
  410. package/dist/browser/types.js.map +1 -0
  411. package/dist/browser/util/inflight-fetch-manager.d.ts +11 -0
  412. package/dist/browser/util/inflight-fetch-manager.js +41 -0
  413. package/dist/browser/util/inflight-fetch-manager.js.map +1 -0
  414. package/dist/browser/util.d.ts +120 -0
  415. package/dist/browser/util.js +816 -0
  416. package/dist/browser/util.js.map +1 -0
  417. package/dist/browser/version.d.ts +7 -0
  418. package/dist/browser/version.js +12 -0
  419. package/dist/browser/version.js.map +1 -0
  420. package/dist/browser/zod-error-map.d.ts +1 -0
  421. package/dist/browser/zod-error-map.js +10 -0
  422. package/dist/browser/zod-error-map.js.map +1 -0
  423. package/dist/node/challenges.d.ts +1 -0
  424. package/dist/node/challenges.js +2 -0
  425. package/dist/node/challenges.js.map +1 -0
  426. package/dist/node/clients/base-client-manager.d.ts +126 -0
  427. package/dist/node/clients/base-client-manager.js +673 -0
  428. package/dist/node/clients/base-client-manager.js.map +1 -0
  429. package/dist/node/clients/name-resolver-client.d.ts +8 -0
  430. package/dist/node/clients/name-resolver-client.js +10 -0
  431. package/dist/node/clients/name-resolver-client.js.map +1 -0
  432. package/dist/node/clients/pkc-typed-emitter.d.ts +9 -0
  433. package/dist/node/clients/pkc-typed-emitter.js +52 -0
  434. package/dist/node/clients/pkc-typed-emitter.js.map +1 -0
  435. package/dist/node/clients/rpc-client/decode-rpc-response-util.d.ts +8 -0
  436. package/dist/node/clients/rpc-client/decode-rpc-response-util.js +53 -0
  437. package/dist/node/clients/rpc-client/decode-rpc-response-util.js.map +1 -0
  438. package/dist/node/clients/rpc-client/pkc-rpc-client.d.ts +68 -0
  439. package/dist/node/clients/rpc-client/pkc-rpc-client.js +404 -0
  440. package/dist/node/clients/rpc-client/pkc-rpc-client.js.map +1 -0
  441. package/dist/node/clients/rpc-client/rpc-schema-util.d.ts +147 -0
  442. package/dist/node/clients/rpc-client/rpc-schema-util.js +11 -0
  443. package/dist/node/clients/rpc-client/rpc-schema-util.js.map +1 -0
  444. package/dist/node/clients/rpc-client/schema.d.ts +433 -0
  445. package/dist/node/clients/rpc-client/schema.js +49 -0
  446. package/dist/node/clients/rpc-client/schema.js.map +1 -0
  447. package/dist/node/clients/rpc-client/types.d.ts +8 -0
  448. package/dist/node/clients/rpc-client/types.js +2 -0
  449. package/dist/node/clients/rpc-client/types.js.map +1 -0
  450. package/dist/node/community/community-client-manager.d.ts +60 -0
  451. package/dist/node/community/community-client-manager.js +717 -0
  452. package/dist/node/community/community-client-manager.js.map +1 -0
  453. package/dist/node/community/community-clients.d.ts +18 -0
  454. package/dist/node/community/community-clients.js +12 -0
  455. package/dist/node/community/community-clients.js.map +1 -0
  456. package/dist/node/community/community-wire.d.ts +20 -0
  457. package/dist/node/community/community-wire.js +38 -0
  458. package/dist/node/community/community-wire.js.map +1 -0
  459. package/dist/node/community/remote-community.d.ts +110 -0
  460. package/dist/node/community/remote-community.js +555 -0
  461. package/dist/node/community/remote-community.js.map +1 -0
  462. package/dist/node/community/rpc-local-community.d.ts +41 -0
  463. package/dist/node/community/rpc-local-community.js +289 -0
  464. package/dist/node/community/rpc-local-community.js.map +1 -0
  465. package/dist/node/community/rpc-remote-community.d.ts +18 -0
  466. package/dist/node/community/rpc-remote-community.js +286 -0
  467. package/dist/node/community/rpc-remote-community.js.map +1 -0
  468. package/dist/node/community/schema.d.ts +4217 -0
  469. package/dist/node/community/schema.js +289 -0
  470. package/dist/node/community/schema.js.map +1 -0
  471. package/dist/node/community/types.d.ts +135 -0
  472. package/dist/node/community/types.js +2 -0
  473. package/dist/node/community/types.js.map +1 -0
  474. package/dist/node/constants.d.ts +6 -0
  475. package/dist/node/constants.js +9 -0
  476. package/dist/node/constants.js.map +1 -0
  477. package/dist/node/decorator-util.d.ts +1 -0
  478. package/dist/node/decorator-util.js +35 -0
  479. package/dist/node/decorator-util.js.map +1 -0
  480. package/dist/node/errors.d.ts +343 -0
  481. package/dist/node/errors.js +358 -0
  482. package/dist/node/errors.js.map +1 -0
  483. package/dist/node/general-util/limited-set.d.ts +15 -0
  484. package/dist/node/general-util/limited-set.js +66 -0
  485. package/dist/node/general-util/limited-set.js.map +1 -0
  486. package/dist/node/generated-version.d.ts +1 -0
  487. package/dist/node/generated-version.js +3 -0
  488. package/dist/node/generated-version.js.map +1 -0
  489. package/dist/node/generic-state-client.d.ts +6 -0
  490. package/dist/node/generic-state-client.js +11 -0
  491. package/dist/node/generic-state-client.js.map +1 -0
  492. package/dist/node/helia/helia-for-pkc.d.ts +3 -0
  493. package/dist/node/helia/helia-for-pkc.js +255 -0
  494. package/dist/node/helia/helia-for-pkc.js.map +1 -0
  495. package/dist/node/helia/ipns-over-pubsub-with-fetch.d.ts +36 -0
  496. package/dist/node/helia/ipns-over-pubsub-with-fetch.js +229 -0
  497. package/dist/node/helia/ipns-over-pubsub-with-fetch.js.map +1 -0
  498. package/dist/node/helia/libp2pjsClient.d.ts +27 -0
  499. package/dist/node/helia/libp2pjsClient.js +15 -0
  500. package/dist/node/helia/libp2pjsClient.js.map +1 -0
  501. package/dist/node/helia/types.d.ts +19 -0
  502. package/dist/node/helia/types.js +2 -0
  503. package/dist/node/helia/types.js.map +1 -0
  504. package/dist/node/helia/util.d.ts +13 -0
  505. package/dist/node/helia/util.js +98 -0
  506. package/dist/node/helia/util.js.map +1 -0
  507. package/dist/node/index.d.ts +244 -0
  508. package/dist/node/index.js +36 -0
  509. package/dist/node/index.js.map +1 -0
  510. package/dist/node/logger.d.ts +12 -0
  511. package/dist/node/logger.js +11 -0
  512. package/dist/node/logger.js.map +1 -0
  513. package/dist/node/pages/pages-client-manager.d.ts +159 -0
  514. package/dist/node/pages/pages-client-manager.js +334 -0
  515. package/dist/node/pages/pages-client-manager.js.map +1 -0
  516. package/dist/node/pages/pages-clients.d.ts +11 -0
  517. package/dist/node/pages/pages-clients.js +10 -0
  518. package/dist/node/pages/pages-clients.js.map +1 -0
  519. package/dist/node/pages/pages.d.ts +107 -0
  520. package/dist/node/pages/pages.js +262 -0
  521. package/dist/node/pages/pages.js.map +1 -0
  522. package/dist/node/pages/schema-util.d.ts +3 -0
  523. package/dist/node/pages/schema-util.js +3 -0
  524. package/dist/node/pages/schema-util.js.map +1 -0
  525. package/dist/node/pages/schema.d.ts +719 -0
  526. package/dist/node/pages/schema.js +32 -0
  527. package/dist/node/pages/schema.js.map +1 -0
  528. package/dist/node/pages/types.d.ts +44 -0
  529. package/dist/node/pages/types.js +2 -0
  530. package/dist/node/pages/types.js.map +1 -0
  531. package/dist/node/pages/util.d.ts +56 -0
  532. package/dist/node/pages/util.js +446 -0
  533. package/dist/node/pages/util.js.map +1 -0
  534. package/dist/node/pkc/pkc-client-manager.d.ts +44 -0
  535. package/dist/node/pkc/pkc-client-manager.js +156 -0
  536. package/dist/node/pkc/pkc-client-manager.js.map +1 -0
  537. package/dist/node/pkc/pkc-clients.d.ts +11 -0
  538. package/dist/node/pkc/pkc-clients.js +8 -0
  539. package/dist/node/pkc/pkc-clients.js.map +1 -0
  540. package/dist/node/pkc/pkc-with-rpc-client.d.ts +19 -0
  541. package/dist/node/pkc/pkc-with-rpc-client.js +128 -0
  542. package/dist/node/pkc/pkc-with-rpc-client.js.map +1 -0
  543. package/dist/node/pkc/pkc.d.ts +137 -0
  544. package/dist/node/pkc/pkc.js +888 -0
  545. package/dist/node/pkc/pkc.js.map +1 -0
  546. package/dist/node/pkc/tracked-instance-registry-util.d.ts +44 -0
  547. package/dist/node/pkc/tracked-instance-registry-util.js +106 -0
  548. package/dist/node/pkc/tracked-instance-registry-util.js.map +1 -0
  549. package/dist/node/pkc/tracked-instance-registry.d.ts +18 -0
  550. package/dist/node/pkc/tracked-instance-registry.js +134 -0
  551. package/dist/node/pkc/tracked-instance-registry.js.map +1 -0
  552. package/dist/node/pkc-error.d.ts +65 -0
  553. package/dist/node/pkc-error.js +137 -0
  554. package/dist/node/pkc-error.js.map +1 -0
  555. package/dist/node/publications/comment/comment-client-manager.d.ts +86 -0
  556. package/dist/node/publications/comment/comment-client-manager.js +908 -0
  557. package/dist/node/publications/comment/comment-client-manager.js.map +1 -0
  558. package/dist/node/publications/comment/comment-clients.d.ts +19 -0
  559. package/dist/node/publications/comment/comment-clients.js +12 -0
  560. package/dist/node/publications/comment/comment-clients.js.map +1 -0
  561. package/dist/node/publications/comment/comment-util.d.ts +10 -0
  562. package/dist/node/publications/comment/comment-util.js +202 -0
  563. package/dist/node/publications/comment/comment-util.js.map +1 -0
  564. package/dist/node/publications/comment/comment.d.ts +147 -0
  565. package/dist/node/publications/comment/comment.js +1044 -0
  566. package/dist/node/publications/comment/comment.js.map +1 -0
  567. package/dist/node/publications/comment/schema.d.ts +1237 -0
  568. package/dist/node/publications/comment/schema.js +184 -0
  569. package/dist/node/publications/comment/schema.js.map +1 -0
  570. package/dist/node/publications/comment/types.d.ts +100 -0
  571. package/dist/node/publications/comment/types.js +2 -0
  572. package/dist/node/publications/comment/types.js.map +1 -0
  573. package/dist/node/publications/comment-edit/comment-edit.d.ts +41 -0
  574. package/dist/node/publications/comment-edit/comment-edit.js +63 -0
  575. package/dist/node/publications/comment-edit/comment-edit.js.map +1 -0
  576. package/dist/node/publications/comment-edit/schema.d.ts +295 -0
  577. package/dist/node/publications/comment-edit/schema.js +55 -0
  578. package/dist/node/publications/comment-edit/schema.js.map +1 -0
  579. package/dist/node/publications/comment-edit/types.d.ts +25 -0
  580. package/dist/node/publications/comment-edit/types.js +2 -0
  581. package/dist/node/publications/comment-edit/types.js.map +1 -0
  582. package/dist/node/publications/comment-moderation/comment-moderation.d.ts +36 -0
  583. package/dist/node/publications/comment-moderation/comment-moderation.js +53 -0
  584. package/dist/node/publications/comment-moderation/comment-moderation.js.map +1 -0
  585. package/dist/node/publications/comment-moderation/schema.d.ts +315 -0
  586. package/dist/node/publications/comment-moderation/schema.js +62 -0
  587. package/dist/node/publications/comment-moderation/schema.js.map +1 -0
  588. package/dist/node/publications/comment-moderation/types.d.ts +22 -0
  589. package/dist/node/publications/comment-moderation/types.js +2 -0
  590. package/dist/node/publications/comment-moderation/types.js.map +1 -0
  591. package/dist/node/publications/community-edit/community-edit.d.ts +35 -0
  592. package/dist/node/publications/community-edit/community-edit.js +50 -0
  593. package/dist/node/publications/community-edit/community-edit.js.map +1 -0
  594. package/dist/node/publications/community-edit/schema.d.ts +467 -0
  595. package/dist/node/publications/community-edit/schema.js +36 -0
  596. package/dist/node/publications/community-edit/schema.js.map +1 -0
  597. package/dist/node/publications/community-edit/types.d.ts +19 -0
  598. package/dist/node/publications/community-edit/types.js +2 -0
  599. package/dist/node/publications/community-edit/types.js.map +1 -0
  600. package/dist/node/publications/publication-author.d.ts +22 -0
  601. package/dist/node/publications/publication-author.js +66 -0
  602. package/dist/node/publications/publication-author.js.map +1 -0
  603. package/dist/node/publications/publication-client-manager.d.ts +62 -0
  604. package/dist/node/publications/publication-client-manager.js +257 -0
  605. package/dist/node/publications/publication-client-manager.js.map +1 -0
  606. package/dist/node/publications/publication-clients.d.ts +19 -0
  607. package/dist/node/publications/publication-clients.js +12 -0
  608. package/dist/node/publications/publication-clients.js.map +1 -0
  609. package/dist/node/publications/publication-community.d.ts +55 -0
  610. package/dist/node/publications/publication-community.js +87 -0
  611. package/dist/node/publications/publication-community.js.map +1 -0
  612. package/dist/node/publications/publication.d.ts +120 -0
  613. package/dist/node/publications/publication.js +950 -0
  614. package/dist/node/publications/publication.js.map +1 -0
  615. package/dist/node/publications/types.d.ts +26 -0
  616. package/dist/node/publications/types.js +2 -0
  617. package/dist/node/publications/types.js.map +1 -0
  618. package/dist/node/publications/vote/schema.d.ts +150 -0
  619. package/dist/node/publications/vote/schema.js +44 -0
  620. package/dist/node/publications/vote/schema.js.map +1 -0
  621. package/dist/node/publications/vote/types.d.ts +21 -0
  622. package/dist/node/publications/vote/types.js +2 -0
  623. package/dist/node/publications/vote/types.js.map +1 -0
  624. package/dist/node/publications/vote/vote.d.ts +36 -0
  625. package/dist/node/publications/vote/vote.js +49 -0
  626. package/dist/node/publications/vote/vote.js.map +1 -0
  627. package/dist/node/pubsub-messages/schema.d.ts +964 -0
  628. package/dist/node/pubsub-messages/schema.js +98 -0
  629. package/dist/node/pubsub-messages/schema.js.map +1 -0
  630. package/dist/node/pubsub-messages/types.d.ts +81 -0
  631. package/dist/node/pubsub-messages/types.js +2 -0
  632. package/dist/node/pubsub-messages/types.js.map +1 -0
  633. package/dist/node/rpc/src/index.d.ts +483 -0
  634. package/dist/node/rpc/src/index.js +1267 -0
  635. package/dist/node/rpc/src/index.js.map +1 -0
  636. package/dist/node/rpc/src/json-rpc-util.d.ts +1 -0
  637. package/dist/node/rpc/src/json-rpc-util.js +19 -0
  638. package/dist/node/rpc/src/json-rpc-util.js.map +1 -0
  639. package/dist/node/rpc/src/lib/pkc-js/index.d.ts +132 -0
  640. package/dist/node/rpc/src/lib/pkc-js/index.js +29 -0
  641. package/dist/node/rpc/src/lib/pkc-js/index.js.map +1 -0
  642. package/dist/node/rpc/src/lib/pkc-js/pkc-js-mock.d.ts +1 -0
  643. package/dist/node/rpc/src/lib/pkc-js/pkc-js-mock.js +472 -0
  644. package/dist/node/rpc/src/lib/pkc-js/pkc-js-mock.js.map +1 -0
  645. package/dist/node/rpc/src/schema.d.ts +843 -0
  646. package/dist/node/rpc/src/schema.js +28 -0
  647. package/dist/node/rpc/src/schema.js.map +1 -0
  648. package/dist/node/rpc/src/types.d.ts +24 -0
  649. package/dist/node/rpc/src/types.js +2 -0
  650. package/dist/node/rpc/src/types.js.map +1 -0
  651. package/dist/node/rpc/src/utils.d.ts +7 -0
  652. package/dist/node/rpc/src/utils.js +58 -0
  653. package/dist/node/rpc/src/utils.js.map +1 -0
  654. package/dist/node/runtime/browser/community/challenges/index.d.ts +6 -0
  655. package/dist/node/runtime/browser/community/challenges/index.js +7 -0
  656. package/dist/node/runtime/browser/community/challenges/index.js.map +1 -0
  657. package/dist/node/runtime/browser/community/local-community.d.ts +3 -0
  658. package/dist/node/runtime/browser/community/local-community.js +6 -0
  659. package/dist/node/runtime/browser/community/local-community.js.map +1 -0
  660. package/dist/node/runtime/browser/db-handler.d.ts +4 -0
  661. package/dist/node/runtime/browser/db-handler.js +8 -0
  662. package/dist/node/runtime/browser/db-handler.js.map +1 -0
  663. package/dist/node/runtime/browser/localforage-lru.d.ts +15 -0
  664. package/dist/node/runtime/browser/localforage-lru.js +140 -0
  665. package/dist/node/runtime/browser/localforage-lru.js.map +1 -0
  666. package/dist/node/runtime/browser/lru-storage.d.ts +14 -0
  667. package/dist/node/runtime/browser/lru-storage.js +34 -0
  668. package/dist/node/runtime/browser/lru-storage.js.map +1 -0
  669. package/dist/node/runtime/browser/native-functions.d.ts +3 -0
  670. package/dist/node/runtime/browser/native-functions.js +6 -0
  671. package/dist/node/runtime/browser/native-functions.js.map +1 -0
  672. package/dist/node/runtime/browser/polyfill.d.ts +3 -0
  673. package/dist/node/runtime/browser/polyfill.js +37 -0
  674. package/dist/node/runtime/browser/polyfill.js.map +1 -0
  675. package/dist/node/runtime/browser/setup-kubo-address-rewriter-and-http-router.d.ts +1 -0
  676. package/dist/node/runtime/browser/setup-kubo-address-rewriter-and-http-router.js +4 -0
  677. package/dist/node/runtime/browser/setup-kubo-address-rewriter-and-http-router.js.map +1 -0
  678. package/dist/node/runtime/browser/storage.d.ts +13 -0
  679. package/dist/node/runtime/browser/storage.js +37 -0
  680. package/dist/node/runtime/browser/storage.js.map +1 -0
  681. package/dist/node/runtime/browser/util.d.ts +14 -0
  682. package/dist/node/runtime/browser/util.js +61 -0
  683. package/dist/node/runtime/browser/util.js.map +1 -0
  684. package/dist/node/runtime/node/address-rewriter-db.d.ts +31 -0
  685. package/dist/node/runtime/node/address-rewriter-db.js +156 -0
  686. package/dist/node/runtime/node/address-rewriter-db.js.map +1 -0
  687. package/dist/node/runtime/node/addresses-rewriter-proxy-server.d.ts +45 -0
  688. package/dist/node/runtime/node/addresses-rewriter-proxy-server.js +493 -0
  689. package/dist/node/runtime/node/addresses-rewriter-proxy-server.js.map +1 -0
  690. package/dist/node/runtime/node/community/challenges/exclude/exclude.d.ts +8 -0
  691. package/dist/node/runtime/node/community/challenges/exclude/exclude.js +280 -0
  692. package/dist/node/runtime/node/community/challenges/exclude/exclude.js.map +1 -0
  693. package/dist/node/runtime/node/community/challenges/exclude/index.d.ts +3 -0
  694. package/dist/node/runtime/node/community/challenges/exclude/index.js +4 -0
  695. package/dist/node/runtime/node/community/challenges/exclude/index.js.map +1 -0
  696. package/dist/node/runtime/node/community/challenges/exclude/rate-limiter.d.ts +5 -0
  697. package/dist/node/runtime/node/community/challenges/exclude/rate-limiter.js +127 -0
  698. package/dist/node/runtime/node/community/challenges/exclude/rate-limiter.js.map +1 -0
  699. package/dist/node/runtime/node/community/challenges/exclude/utils.d.ts +13 -0
  700. package/dist/node/runtime/node/community/challenges/exclude/utils.js +52 -0
  701. package/dist/node/runtime/node/community/challenges/exclude/utils.js.map +1 -0
  702. package/dist/node/runtime/node/community/challenges/index.d.ts +32 -0
  703. package/dist/node/runtime/node/community/challenges/index.js +307 -0
  704. package/dist/node/runtime/node/community/challenges/index.js.map +1 -0
  705. package/dist/node/runtime/node/community/challenges/pkc-js-challenges/blacklist.d.ts +5 -0
  706. package/dist/node/runtime/node/community/challenges/pkc-js-challenges/blacklist.js +118 -0
  707. package/dist/node/runtime/node/community/challenges/pkc-js-challenges/blacklist.js.map +1 -0
  708. package/dist/node/runtime/node/community/challenges/pkc-js-challenges/fail.d.ts +5 -0
  709. package/dist/node/runtime/node/community/challenges/pkc-js-challenges/fail.js +26 -0
  710. package/dist/node/runtime/node/community/challenges/pkc-js-challenges/fail.js.map +1 -0
  711. package/dist/node/runtime/node/community/challenges/pkc-js-challenges/publication-match.d.ts +5 -0
  712. package/dist/node/runtime/node/community/challenges/pkc-js-challenges/publication-match.js +135 -0
  713. package/dist/node/runtime/node/community/challenges/pkc-js-challenges/publication-match.js.map +1 -0
  714. package/dist/node/runtime/node/community/challenges/pkc-js-challenges/question.d.ts +5 -0
  715. package/dist/node/runtime/node/community/challenges/pkc-js-challenges/question.js +66 -0
  716. package/dist/node/runtime/node/community/challenges/pkc-js-challenges/question.js.map +1 -0
  717. package/dist/node/runtime/node/community/challenges/pkc-js-challenges/text-math.d.ts +5 -0
  718. package/dist/node/runtime/node/community/challenges/pkc-js-challenges/text-math.js +61 -0
  719. package/dist/node/runtime/node/community/challenges/pkc-js-challenges/text-math.js.map +1 -0
  720. package/dist/node/runtime/node/community/challenges/pkc-js-challenges/whitelist.d.ts +5 -0
  721. package/dist/node/runtime/node/community/challenges/pkc-js-challenges/whitelist.js +118 -0
  722. package/dist/node/runtime/node/community/challenges/pkc-js-challenges/whitelist.js.map +1 -0
  723. package/dist/node/runtime/node/community/db-handler-types.d.ts +19 -0
  724. package/dist/node/runtime/node/community/db-handler-types.js +2 -0
  725. package/dist/node/runtime/node/community/db-handler-types.js.map +1 -0
  726. package/dist/node/runtime/node/community/db-handler.d.ts +226 -0
  727. package/dist/node/runtime/node/community/db-handler.js +2462 -0
  728. package/dist/node/runtime/node/community/db-handler.js.map +1 -0
  729. package/dist/node/runtime/node/community/db-row-parser.d.ts +19 -0
  730. package/dist/node/runtime/node/community/db-row-parser.js +40 -0
  731. package/dist/node/runtime/node/community/db-row-parser.js.map +1 -0
  732. package/dist/node/runtime/node/community/keyv-better-sqlite3.d.ts +68 -0
  733. package/dist/node/runtime/node/community/keyv-better-sqlite3.js +251 -0
  734. package/dist/node/runtime/node/community/keyv-better-sqlite3.js.map +1 -0
  735. package/dist/node/runtime/node/community/local-community.d.ts +129 -0
  736. package/dist/node/runtime/node/community/local-community.js +2978 -0
  737. package/dist/node/runtime/node/community/local-community.js.map +1 -0
  738. package/dist/node/runtime/node/community/page-generator.d.ts +433 -0
  739. package/dist/node/runtime/node/community/page-generator.js +441 -0
  740. package/dist/node/runtime/node/community/page-generator.js.map +1 -0
  741. package/dist/node/runtime/node/lru-storage.d.ts +14 -0
  742. package/dist/node/runtime/node/lru-storage.js +40 -0
  743. package/dist/node/runtime/node/lru-storage.js.map +1 -0
  744. package/dist/node/runtime/node/native-functions.d.ts +3 -0
  745. package/dist/node/runtime/node/native-functions.js +7 -0
  746. package/dist/node/runtime/node/native-functions.js.map +1 -0
  747. package/dist/node/runtime/node/polyfill.d.ts +3 -0
  748. package/dist/node/runtime/node/polyfill.js +20 -0
  749. package/dist/node/runtime/node/polyfill.js.map +1 -0
  750. package/dist/node/runtime/node/setup-kubo-address-rewriter-and-http-router.d.ts +4 -0
  751. package/dist/node/runtime/node/setup-kubo-address-rewriter-and-http-router.js +240 -0
  752. package/dist/node/runtime/node/setup-kubo-address-rewriter-and-http-router.js.map +1 -0
  753. package/dist/node/runtime/node/sqlite-lru-cache.d.ts +52 -0
  754. package/dist/node/runtime/node/sqlite-lru-cache.js +127 -0
  755. package/dist/node/runtime/node/sqlite-lru-cache.js.map +1 -0
  756. package/dist/node/runtime/node/storage.d.ts +14 -0
  757. package/dist/node/runtime/node/storage.js +52 -0
  758. package/dist/node/runtime/node/storage.js.map +1 -0
  759. package/dist/node/runtime/node/test/helpers/hanging-runner.d.ts +1 -0
  760. package/dist/node/runtime/node/test/helpers/hanging-runner.js +157 -0
  761. package/dist/node/runtime/node/test/helpers/hanging-runner.js.map +1 -0
  762. package/dist/node/runtime/node/test/helpers/run-hanging-node.d.ts +7 -0
  763. package/dist/node/runtime/node/test/helpers/run-hanging-node.js +68 -0
  764. package/dist/node/runtime/node/test/helpers/run-hanging-node.js.map +1 -0
  765. package/dist/node/runtime/node/test/mock-http-router.d.ts +54 -0
  766. package/dist/node/runtime/node/test/mock-http-router.js +397 -0
  767. package/dist/node/runtime/node/test/mock-http-router.js.map +1 -0
  768. package/dist/node/runtime/node/util.d.ts +43 -0
  769. package/dist/node/runtime/node/util.js +384 -0
  770. package/dist/node/runtime/node/util.js.map +1 -0
  771. package/dist/node/schema/schema-util.d.ts +2751 -0
  772. package/dist/node/schema/schema-util.js +562 -0
  773. package/dist/node/schema/schema-util.js.map +1 -0
  774. package/dist/node/schema/schema.d.ts +237 -0
  775. package/dist/node/schema/schema.js +128 -0
  776. package/dist/node/schema/schema.js.map +1 -0
  777. package/dist/node/schema.d.ts +1142 -0
  778. package/dist/node/schema.js +104 -0
  779. package/dist/node/schema.js.map +1 -0
  780. package/dist/node/signer/constants.d.ts +2 -0
  781. package/dist/node/signer/constants.js +3 -0
  782. package/dist/node/signer/constants.js.map +1 -0
  783. package/dist/node/signer/encryption.d.ts +21 -0
  784. package/dist/node/signer/encryption.js +122 -0
  785. package/dist/node/signer/encryption.js.map +1 -0
  786. package/dist/node/signer/index.d.ts +21 -0
  787. package/dist/node/signer/index.js +49 -0
  788. package/dist/node/signer/index.js.map +1 -0
  789. package/dist/node/signer/signatures.d.ts +200 -0
  790. package/dist/node/signer/signatures.js +594 -0
  791. package/dist/node/signer/signatures.js.map +1 -0
  792. package/dist/node/signer/types.d.ts +20 -0
  793. package/dist/node/signer/types.js +2 -0
  794. package/dist/node/signer/types.js.map +1 -0
  795. package/dist/node/signer/util.d.ts +14 -0
  796. package/dist/node/signer/util.js +156 -0
  797. package/dist/node/signer/util.js.map +1 -0
  798. package/dist/node/stats.d.ts +15 -0
  799. package/dist/node/stats.js +64 -0
  800. package/dist/node/stats.js.map +1 -0
  801. package/dist/node/test/mock-ipfs-client.d.ts +34 -0
  802. package/dist/node/test/mock-ipfs-client.js +208 -0
  803. package/dist/node/test/mock-ipfs-client.js.map +1 -0
  804. package/dist/node/test/node/hanging-test/scenarios/comment-publish-pending.scenario.d.ts +8 -0
  805. package/dist/node/test/node/hanging-test/scenarios/comment-publish-pending.scenario.js +21 -0
  806. package/dist/node/test/node/hanging-test/scenarios/comment-publish-pending.scenario.js.map +1 -0
  807. package/dist/node/test/node/hanging-test/scenarios/comment-publish.scenario.d.ts +8 -0
  808. package/dist/node/test/node/hanging-test/scenarios/comment-publish.scenario.js +19 -0
  809. package/dist/node/test/node/hanging-test/scenarios/comment-publish.scenario.js.map +1 -0
  810. package/dist/node/test/node/hanging-test/scenarios/comment-update.scenario.d.ts +8 -0
  811. package/dist/node/test/node/hanging-test/scenarios/comment-update.scenario.js +22 -0
  812. package/dist/node/test/node/hanging-test/scenarios/comment-update.scenario.js.map +1 -0
  813. package/dist/node/test/node/hanging-test/scenarios/community-start.scenario.d.ts +8 -0
  814. package/dist/node/test/node/hanging-test/scenarios/community-start.scenario.js +23 -0
  815. package/dist/node/test/node/hanging-test/scenarios/community-start.scenario.js.map +1 -0
  816. package/dist/node/test/node/hanging-test/scenarios/community-update.scenario.d.ts +8 -0
  817. package/dist/node/test/node/hanging-test/scenarios/community-update.scenario.js +21 -0
  818. package/dist/node/test/node/hanging-test/scenarios/community-update.scenario.js.map +1 -0
  819. package/dist/node/test/node/hanging-test/scenarios/destroy-only.scenario.d.ts +7 -0
  820. package/dist/node/test/node/hanging-test/scenarios/destroy-only.scenario.js +15 -0
  821. package/dist/node/test/node/hanging-test/scenarios/destroy-only.scenario.js.map +1 -0
  822. package/dist/node/test/node/hanging-test/scenarios/hanging-test-util.d.ts +30 -0
  823. package/dist/node/test/node/hanging-test/scenarios/hanging-test-util.js +46 -0
  824. package/dist/node/test/node/hanging-test/scenarios/hanging-test-util.js.map +1 -0
  825. package/dist/node/test/test-util.d.ts +1019 -0
  826. package/dist/node/test/test-util.js +1886 -0
  827. package/dist/node/test/test-util.js.map +1 -0
  828. package/dist/node/types.d.ts +165 -0
  829. package/dist/node/types.js +2 -0
  830. package/dist/node/types.js.map +1 -0
  831. package/dist/node/util/inflight-fetch-manager.d.ts +11 -0
  832. package/dist/node/util/inflight-fetch-manager.js +41 -0
  833. package/dist/node/util/inflight-fetch-manager.js.map +1 -0
  834. package/dist/node/util.d.ts +120 -0
  835. package/dist/node/util.js +816 -0
  836. package/dist/node/util.js.map +1 -0
  837. package/dist/node/version.d.ts +7 -0
  838. package/dist/node/version.js +12 -0
  839. package/dist/node/version.js.map +1 -0
  840. package/dist/node/zod-error-map.d.ts +1 -0
  841. package/dist/node/zod-error-map.js +10 -0
  842. package/dist/node/zod-error-map.js.map +1 -0
  843. package/package.json +212 -0
@@ -0,0 +1,2978 @@
1
+ import Logger from "../../../logger.js";
2
+ import { LRUCache } from "lru-cache";
3
+ import { PageGenerator } from "./page-generator.js";
4
+ import { DbHandler } from "./db-handler.js";
5
+ import { of as calculateIpfsHash } from "typestub-ipfs-only-hash";
6
+ import { derivePublicationFromChallengeRequest, doesDomainAddressHaveCapitalLetter, genToArray, hideClassPrivateProps, ipnsNameToIpnsOverPubsubTopic, isLinkOfMedia, isLinkOfImage, isLinkOfVideo, isLinkOfAnimatedImage, isLinkValid, isStringDomain, pubsubTopicToDhtKey, timestamp, getErrorCodeFromMessage, removeMfsFilesSafely, removeBlocksFromKuboNode, writeKuboFilesWithTimeout, retryKuboIpfsAddAndProvide, retryKuboBlockPutPinAndProvidePubsubTopic, calculateIpfsCidV0, calculateStringSizeSameAsIpfsAddCidV0, getIpnsRecordInLocalKuboNode, contentContainsMarkdownImages, contentContainsMarkdownVideos, isLinkOfAudio, contentContainsMarkdownAudio, areEquivalentCommunityAddresses } from "../../../util.js";
7
+ import { STORAGE_KEYS } from "../../../constants.js";
8
+ import { stringify as deterministicStringify } from "safe-stable-stringify";
9
+ import { PKCError } from "../../../pkc-error.js";
10
+ import { cleanUpBeforePublishing, signChallengeMessage, signChallengeVerification, signComment, signCommentEdit, signCommentUpdate, signCommentUpdateForChallengeVerification, signCommunity, verifyChallengeAnswer, verifyChallengeRequest, verifyCommentEdit, verifyCommentModeration, verifyCommentUpdate, verifyCommunityEdit } from "../../../signer/signatures.js";
11
+ import { calculateExpectedSignatureSize, calculateInlineRepliesBudget, deriveCommentIpfsFromCommentTableRow, getThumbnailPropsOfLink, importSignerIntoKuboNode, moveCommunityDbToDeletedDirectory } from "../util.js";
12
+ import { SignerWithPublicKeyAddress, decryptEd25519AesGcmPublicKeyBuffer, verifyCommentPubsubMessage, verifyCommunity, verifyVote } from "../../../signer/index.js";
13
+ import { encryptEd25519AesGcmPublicKeyBuffer } from "../../../signer/encryption.js";
14
+ import { messages } from "../../../errors.js";
15
+ import { getChallengeVerification, getCommunityChallengeFromCommunityChallengeSettings } from "./challenges/index.js";
16
+ import * as cborg from "cborg";
17
+ import env from "../../../version.js";
18
+ import { getIpfsKeyFromPrivateKey, getPKCAddressFromPublicKey, getPublicKeyFromPrivateKey } from "../../../signer/util.js";
19
+ import { RpcLocalCommunity } from "../../../community/rpc-local-community.js";
20
+ import * as remeda from "remeda";
21
+ import { buildRuntimeAuthor, cleanWireAuthor, getAuthorDomainFromWire, getAuthorNameFromWire } from "../../../publications/publication-author.js";
22
+ import { getCommunityPublicKeyFromWire, getCommunityNameFromWire } from "../../../publications/publication-community.js";
23
+ import { CommentEditPubsubMessagePublicationSchema, CommentEditPubsubMessagePublicationWithFlexibleAuthorSchema, CommentEditReservedFields } from "../../../publications/comment-edit/schema.js";
24
+ import { CommunityIpfsSchema, CommunitySignedPropertyNames } from "../../../community/schema.js";
25
+ import { ChallengeAnswerMessageSchema, ChallengeMessageSchema, ChallengeRequestMessageSchema, ChallengeVerificationMessageSchema, DecryptedChallengeRequestPublicationSchema, DecryptedChallengeRequestSchema } from "../../../pubsub-messages/schema.js";
26
+ import { parseDecryptedChallengeAnswerWithPKCErrorIfItFails, parseJsonWithPKCErrorIfFails, parseCommunityEditOptionsSchemaWithPKCErrorIfItFails, parseCommunityIpfsSchemaPassthroughWithPKCErrorIfItFails } from "../../../schema/schema-util.js";
27
+ import { CommentIpfsSchema, CommentPubsubMessageReservedFields, CommentPubsubMessagePublicationSchema } from "../../../publications/comment/schema.js";
28
+ import { VotePubsubMessagePublicationSchema, VotePubsubReservedFields } from "../../../publications/vote/schema.js";
29
+ import { v4 as uuidV4 } from "uuid";
30
+ import { AuthorReservedFields } from "../../../schema/schema.js";
31
+ import { CommentModerationPubsubMessagePublicationSchema, CommentModerationReservedFields } from "../../../publications/comment-moderation/schema.js";
32
+ import { CommunityEditPublicationPubsubReservedFields } from "../../../publications/community-edit/schema.js";
33
+ import { default as lodashDeepMerge } from "lodash.merge"; // Importing only the `merge` function
34
+ import { MAX_FILE_SIZE_BYTES_FOR_COMMUNITY_IPFS } from "../../../community/community-client-manager.js";
35
+ import pLimit from "p-limit";
36
+ import { sha256 } from "js-sha256";
37
+ import { iterateOverPageCidsToFindAllCids } from "../../../pages/util.js";
38
+ import { TrackedInstanceRegistry } from "../../../pkc/tracked-instance-registry.js";
39
+ import { findStartedCommunity, findCommunityInRegistry, findUpdatingCommunity, syncCommunityRegistryEntry, trackStartedCommunity, trackUpdatingCommunity, untrackStartedCommunity, untrackUpdatingCommunity } from "../../../pkc/tracked-instance-registry-util.js";
40
+ const processStartedCommunities = new TrackedInstanceRegistry(); // A global registry on process level to track started communities
41
+ const DUPLICATE_PUBLICATION_ERRORS = new Set([
42
+ messages.ERR_DUPLICATE_COMMENT,
43
+ messages.ERR_DUPLICATE_COMMENT_EDIT,
44
+ messages.ERR_DUPLICATE_COMMENT_MODERATION
45
+ ]);
46
+ // This is a sub we have locally in our pkc datapath, in a NodeJS environment
47
+ export class LocalCommunity extends RpcLocalCommunity {
48
+ constructor(pkc) {
49
+ super(pkc);
50
+ this.raw = {};
51
+ this._postUpdatesBuckets = [86400, 604800, 2592000, 3153600000]; // 1 day, 1 week, 1 month, 100 years. Expecting to be sorted from smallest to largest
52
+ this._defaultCommunityChallenges = [
53
+ {
54
+ name: "question",
55
+ options: {
56
+ question: "Placeholder challenge. Set your own challenges otherwise you risk getting spammed",
57
+ answer: "Placeholder answer"
58
+ }
59
+ }
60
+ ];
61
+ this._challengeExchangesFromLocalPublishers = {}; // key is stringified challengeRequestId and value is true if the challenge exchange is ongoing
62
+ this._cidsToUnPin = new Set();
63
+ this._mfsPathsToRemove = new Set();
64
+ this._communityUpdateTrigger = false;
65
+ this._combinedHashOfPendingCommentsCids = sha256("");
66
+ this._publishLoopPromise = undefined;
67
+ this._updateLoopPromise = undefined;
68
+ this._firstUpdateAfterStart = true;
69
+ this._internalStateUpdateId = "";
70
+ this._lastPubsubTopicRoutingProvideAt = undefined;
71
+ this._mirroredStartedOrUpdatingCommunity = undefined; // The pkc._startedCommunities we're subscribed to
72
+ this._pendingEditProps = [];
73
+ this._blocksToRm = [];
74
+ this.handleChallengeExchange = this.handleChallengeExchange.bind(this);
75
+ this._setState("stopped");
76
+ this.started = false;
77
+ this._stopHasBeenCalled = false;
78
+ // need to make sure these props are undefined on the constructor level, so they wouldn't show while logging
79
+ //@ts-expect-error
80
+ this._pageGenerator = undefined;
81
+ //@ts-expect-error
82
+ this._challengeAnswerPromises = undefined;
83
+ //@ts-expect-error
84
+ this._challengeAnswerResolveReject = undefined;
85
+ //@ts-expect-error
86
+ this._ongoingChallengeExchanges = undefined;
87
+ //@ts-expect-error
88
+ this._duplicatePublicationAttempts = undefined;
89
+ //@ts-expect-error
90
+ this._internalStateUpdateId = undefined;
91
+ //@ts-expect-error
92
+ this._dbHandler = undefined;
93
+ hideClassPrivateProps(this);
94
+ }
95
+ // This will be stored in DB
96
+ toJSONInternalAfterFirstUpdate() {
97
+ const rpcJson = this.toJSONInternalRpcAfterFirstUpdate();
98
+ return {
99
+ ...rpcJson.community,
100
+ ...remeda.omit(rpcJson.localCommunity, ["started", "startedState"]),
101
+ updateCid: rpcJson.runtimeFields.updateCid,
102
+ signer: remeda.pick(this.signer, ["privateKey", "type", "address", "shortAddress", "publicKey"]),
103
+ _internalStateUpdateId: this._internalStateUpdateId,
104
+ _cidsToUnPin: [...this._cidsToUnPin],
105
+ _mfsPathsToRemove: [...this._mfsPathsToRemove],
106
+ _pendingEditProps: this._pendingEditProps
107
+ };
108
+ }
109
+ toJSONInternalBeforeFirstUpdate() {
110
+ const rpcJson = this.toJSONInternalRpcBeforeFirstUpdate();
111
+ return {
112
+ ...remeda.omit(rpcJson.localCommunity, ["started", "startedState"]),
113
+ signer: remeda.pick(this.signer, ["privateKey", "type", "address", "shortAddress", "publicKey"]),
114
+ _internalStateUpdateId: this._internalStateUpdateId,
115
+ _pendingEditProps: this._pendingEditProps
116
+ };
117
+ }
118
+ toJSONInternalRpcAfterFirstUpdate() {
119
+ const base = super.toJSONInternalRpcAfterFirstUpdate();
120
+ return {
121
+ ...base,
122
+ localCommunity: {
123
+ ...base.localCommunity,
124
+ signer: remeda.pick(this.signer, ["publicKey", "address", "shortAddress", "type"])
125
+ }
126
+ };
127
+ }
128
+ toJSONInternalRpcBeforeFirstUpdate() {
129
+ const base = super.toJSONInternalRpcBeforeFirstUpdate();
130
+ return {
131
+ localCommunity: {
132
+ ...base.localCommunity,
133
+ signer: remeda.pick(this.signer, ["publicKey", "address", "shortAddress", "type"])
134
+ }
135
+ };
136
+ }
137
+ async _updateStartedValue() {
138
+ this.started = await this._dbHandler.isCommunityStartLocked(this.address);
139
+ }
140
+ async initNewLocalCommunityPropsNoMerge(newProps) {
141
+ await this._initSignerProps(newProps.signer);
142
+ this.title = newProps.title;
143
+ this.description = newProps.description;
144
+ this.setAddress(newProps.address);
145
+ this.pubsubTopic = newProps.pubsubTopic;
146
+ this.roles = newProps.roles;
147
+ this.features = newProps.features;
148
+ this.suggested = newProps.suggested;
149
+ this.rules = newProps.rules;
150
+ this.flairs = newProps.flairs;
151
+ if (newProps.settings)
152
+ this.settings = newProps.settings;
153
+ }
154
+ async initInternalCommunityAfterFirstUpdateNoMerge(newProps) {
155
+ const keysOfCommunityIpfs = [...CommunitySignedPropertyNames, "signature"];
156
+ this.initRpcInternalCommunityAfterFirstUpdateNoMerge({
157
+ community: remeda.pick(newProps, keysOfCommunityIpfs),
158
+ localCommunity: {
159
+ signer: remeda.pick(newProps.signer, ["publicKey", "address", "shortAddress", "type"]),
160
+ settings: newProps.settings,
161
+ _usingDefaultChallenge: newProps._usingDefaultChallenge,
162
+ address: newProps.address,
163
+ started: this.started,
164
+ startedState: this.startedState
165
+ },
166
+ runtimeFields: { updateCid: newProps.updateCid }
167
+ });
168
+ await this._initSignerProps(newProps.signer);
169
+ this._internalStateUpdateId = newProps._internalStateUpdateId;
170
+ if (Array.isArray(newProps._cidsToUnPin))
171
+ newProps._cidsToUnPin.forEach((cid) => this._cidsToUnPin.add(cid));
172
+ if (Array.isArray(newProps._mfsPathsToRemove))
173
+ newProps._mfsPathsToRemove.forEach((path) => this._mfsPathsToRemove.add(path));
174
+ this._updateIpnsPubsubPropsIfNeeded(newProps);
175
+ if (processStartedCommunities.has(this))
176
+ syncCommunityRegistryEntry(processStartedCommunities, this);
177
+ if (this.updateCid)
178
+ this.raw.localCommunity = this.toJSONInternalRpcAfterFirstUpdate();
179
+ }
180
+ async initInternalCommunityBeforeFirstUpdateNoMerge(newProps) {
181
+ this.initRpcInternalCommunityBeforeFirstUpdateNoMerge({
182
+ localCommunity: {
183
+ ...remeda.omit(newProps, ["signer", "_internalStateUpdateId", "_pendingEditProps"]),
184
+ signer: remeda.pick(newProps.signer, ["publicKey", "address", "shortAddress", "type"]),
185
+ started: this.started,
186
+ startedState: this.startedState
187
+ }
188
+ });
189
+ await this._initSignerProps(newProps.signer);
190
+ this._internalStateUpdateId = newProps._internalStateUpdateId;
191
+ this._updateIpnsPubsubPropsIfNeeded(newProps);
192
+ this.ipnsName = newProps.signer.address;
193
+ this.ipnsPubsubTopic = ipnsNameToIpnsOverPubsubTopic(this.ipnsName);
194
+ this.ipnsPubsubTopicRoutingCid = pubsubTopicToDhtKey(this.ipnsPubsubTopic);
195
+ if (processStartedCommunities.has(this))
196
+ syncCommunityRegistryEntry(processStartedCommunities, this);
197
+ this.raw.localCommunity = this.toJSONInternalRpcBeforeFirstUpdate();
198
+ }
199
+ async initDbHandlerIfNeeded() {
200
+ if (!this._dbHandler) {
201
+ this._dbHandler = new DbHandler(this);
202
+ await this._dbHandler.initDbConfigIfNeeded();
203
+ this._pageGenerator = new PageGenerator(this);
204
+ }
205
+ }
206
+ async _updateInstancePropsWithStartedCommunityOrDb() {
207
+ // if it's started in the same pkc instance, we will load it from the started community instance
208
+ // if it's started in another process, we will throw an error
209
+ // if community is not started, load the InternalCommunity props from the local db
210
+ const log = Logger("pkc-js:local-community:_updateInstancePropsWithStartedCommunityOrDb");
211
+ const startedCommunity = ((findStartedCommunity(this._pkc, { address: this.address }) ||
212
+ findCommunityInRegistry(processStartedCommunities, { address: this.address })));
213
+ if (startedCommunity) {
214
+ log("Loading local community", this.address, "from started community instance");
215
+ if (startedCommunity.updatedAt)
216
+ await this.initInternalCommunityAfterFirstUpdateNoMerge(startedCommunity.toJSONInternalAfterFirstUpdate());
217
+ else
218
+ await this.initInternalCommunityBeforeFirstUpdateNoMerge(startedCommunity.toJSONInternalBeforeFirstUpdate());
219
+ this.started = true;
220
+ }
221
+ else {
222
+ await this.initDbHandlerIfNeeded();
223
+ try {
224
+ await this._updateStartedValue();
225
+ const communityDbExists = this._dbHandler.communityDbExists();
226
+ if (!communityDbExists)
227
+ throw new PKCError("CAN_NOT_LOAD_LOCAL_COMMUNITY_IF_DB_DOES_NOT_EXIST", {
228
+ address: this.address,
229
+ dataPath: this._pkc.dataPath
230
+ });
231
+ const dbConfig = this.state === "updating" ? { readonly: true } : undefined;
232
+ await this._dbHandler.initDbIfNeeded(dbConfig);
233
+ await this._updateInstanceStateWithDbState(); // Load InternalCommunity from DB here
234
+ if (!this.signer)
235
+ throw new PKCError("ERR_LOCAL_COMMUNITY_HAS_NO_SIGNER_IN_INTERNAL_STATE", { address: this.address });
236
+ await this._updateStartedValue();
237
+ log("Loaded local community", this.address, "from db");
238
+ }
239
+ catch (e) {
240
+ throw e;
241
+ }
242
+ finally {
243
+ this._dbHandler.destoryConnection(); // Need to destory connection so process wouldn't hang
244
+ }
245
+ }
246
+ // need to validate schema of Community IPFS
247
+ if (this.raw.communityIpfs)
248
+ try {
249
+ parseCommunityIpfsSchemaPassthroughWithPKCErrorIfItFails(this.raw.communityIpfs);
250
+ }
251
+ catch (e) {
252
+ if (e instanceof Error) {
253
+ log("Local community", this.address, "has an invalid communityIpfs schema from DB, clearing for re-generation after migration:", e.message);
254
+ this.raw.communityIpfs = undefined;
255
+ }
256
+ }
257
+ }
258
+ async _importCommunitySignerIntoIpfsIfNeeded() {
259
+ if (!this.signer.ipnsKeyName)
260
+ throw Error("community.signer.ipnsKeyName is not defined");
261
+ if (!this.signer.ipfsKey)
262
+ throw Error("community.signer.ipfsKey is not defined");
263
+ await importSignerIntoKuboNode(this.signer.ipnsKeyName, this.signer.ipfsKey, {
264
+ url: this._pkc.kuboRpcClientsOptions[0].url.toString(),
265
+ headers: this._pkc.kuboRpcClientsOptions[0].headers
266
+ });
267
+ }
268
+ async _updateDbInternalState(props) {
269
+ const log = Logger("pkc-js:local-community:_updateDbInternalState");
270
+ if (remeda.isEmpty(props))
271
+ throw Error("props to update DB internal state should not be empty");
272
+ await this._dbHandler.initDbIfNeeded();
273
+ props._internalStateUpdateId = uuidV4();
274
+ let lockedIt = false;
275
+ try {
276
+ await this._dbHandler.lockCommunityState();
277
+ lockedIt = true;
278
+ const internalStateBefore = await this._getDbInternalState(false);
279
+ const mergedInternalState = { ...internalStateBefore, ...props };
280
+ await this._dbHandler.keyvSet(STORAGE_KEYS[STORAGE_KEYS.INTERNAL_COMMUNITY], mergedInternalState);
281
+ this._internalStateUpdateId = props._internalStateUpdateId;
282
+ log.trace("Updated community", this.address, "internal state in db with new props", Object.keys(props));
283
+ if (this.updateCid && this.raw.communityIpfs) {
284
+ this.raw.localCommunity = this.toJSONInternalRpcAfterFirstUpdate();
285
+ }
286
+ else if (this.settings) {
287
+ this.raw.localCommunity = this.toJSONInternalRpcBeforeFirstUpdate();
288
+ }
289
+ return mergedInternalState;
290
+ }
291
+ catch (e) {
292
+ log.error("Failed to update community", this.address, "internal state in db with new props", Object.keys(props), e);
293
+ throw e;
294
+ }
295
+ finally {
296
+ if (lockedIt)
297
+ await this._dbHandler.unlockCommunityState();
298
+ }
299
+ }
300
+ async _getDbInternalState(lock) {
301
+ const log = Logger("pkc-js:local-community:_getDbInternalState");
302
+ if (!this._dbHandler.keyvHas(STORAGE_KEYS[STORAGE_KEYS.INTERNAL_COMMUNITY]))
303
+ throw new PKCError("ERR_COMMUNITY_HAS_NO_INTERNAL_STATE", { address: this.address, dataPath: this._pkc.dataPath });
304
+ let lockedIt = false;
305
+ try {
306
+ if (lock) {
307
+ await this._dbHandler.lockCommunityState();
308
+ lockedIt = true;
309
+ }
310
+ const internalState = await this._dbHandler.keyvGet(STORAGE_KEYS[STORAGE_KEYS.INTERNAL_COMMUNITY]);
311
+ if (!internalState)
312
+ throw new PKCError("ERR_COMMUNITY_HAS_NO_INTERNAL_STATE", { address: this.address, dataPath: this._pkc.dataPath });
313
+ return internalState;
314
+ }
315
+ catch (e) {
316
+ log.error("Failed to get community", this.address, "internal state from db", e);
317
+ throw e;
318
+ }
319
+ finally {
320
+ if (lockedIt)
321
+ await this._dbHandler.unlockCommunityState();
322
+ }
323
+ }
324
+ async _updateInstanceStateWithDbState() {
325
+ const currentDbState = await this._getDbInternalState(false);
326
+ if ("updatedAt" in currentDbState) {
327
+ await this.initInternalCommunityAfterFirstUpdateNoMerge(currentDbState);
328
+ }
329
+ else
330
+ await this.initInternalCommunityBeforeFirstUpdateNoMerge(currentDbState);
331
+ }
332
+ async _setChallengesToDefaultIfNotDefined(log) {
333
+ if (this._usingDefaultChallenge !== false &&
334
+ (!this.settings?.challenges || remeda.isDeepEqual(this.settings?.challenges, this._defaultCommunityChallenges)))
335
+ this._usingDefaultChallenge = true;
336
+ if (this._usingDefaultChallenge && !remeda.isDeepEqual(this.settings?.challenges, this._defaultCommunityChallenges)) {
337
+ await this.edit({ settings: { ...this.settings, challenges: this._defaultCommunityChallenges } });
338
+ log(`Defaulted the challenges of community (${this.address}) to`, this._defaultCommunityChallenges);
339
+ }
340
+ }
341
+ async _createNewLocalCommunityDb() {
342
+ // We're creating a totally new community here with a new db
343
+ // This function should be called only once per community
344
+ const log = Logger("pkc-js:local-community:_createNewLocalCommunityDb");
345
+ await this.initDbHandlerIfNeeded();
346
+ await this._dbHandler.initDbIfNeeded({ fileMustExist: false });
347
+ await this._dbHandler.createOrMigrateTablesIfNeeded();
348
+ await this._initSignerProps(this.signer); // init this.encryption as well
349
+ if (!this.pubsubTopic)
350
+ this.pubsubTopic = remeda.clone(this.signer.address);
351
+ if (typeof this.createdAt !== "number")
352
+ this.createdAt = timestamp();
353
+ if (!this.protocolVersion)
354
+ this.protocolVersion = env.PROTOCOL_VERSION;
355
+ if (!this.settings?.maxPendingApprovalCount)
356
+ this.settings = { ...this.settings, maxPendingApprovalCount: 500 };
357
+ if (!this.settings?.challenges) {
358
+ this.settings = { ...this.settings, challenges: this._defaultCommunityChallenges };
359
+ this._usingDefaultChallenge = true;
360
+ log(`Defaulted the challenges of community (${this.address}) to`, this._defaultCommunityChallenges);
361
+ }
362
+ if (typeof this.settings?.purgeDisapprovedCommentsOlderThan !== "number") {
363
+ this.settings = { ...this.settings, purgeDisapprovedCommentsOlderThan: 1.21e6 }; // two weeks
364
+ }
365
+ this.challenges = await Promise.all(this.settings.challenges.map((cs) => getCommunityChallengeFromCommunityChallengeSettings(cs, this._pkc)));
366
+ if (this._dbHandler.keyvHas(STORAGE_KEYS[STORAGE_KEYS.INTERNAL_COMMUNITY]))
367
+ throw Error("Internal state exists already");
368
+ await this._dbHandler.keyvSet(STORAGE_KEYS[STORAGE_KEYS.INTERNAL_COMMUNITY], this.toJSONInternalBeforeFirstUpdate());
369
+ await this._updateStartedValue();
370
+ this._dbHandler.destoryConnection(); // Need to destory connection so process wouldn't hang
371
+ this._updateIpnsPubsubPropsIfNeeded({
372
+ ...this.toJSONInternalBeforeFirstUpdate(), //@ts-expect-error
373
+ signature: { publicKey: this.signer.publicKey }
374
+ });
375
+ }
376
+ async _calculateNewPostUpdates() {
377
+ const postUpdates = {};
378
+ const kuboRpcClient = this._clientsManager.getDefaultKuboRpcClient()._client;
379
+ for (const timeBucket of this._postUpdatesBuckets) {
380
+ try {
381
+ const statRes = await kuboRpcClient.files.stat(`/${this.address}/postUpdates/${timeBucket}`);
382
+ if (statRes.blocks !== 0)
383
+ postUpdates[String(timeBucket)] = String(statRes.cid);
384
+ }
385
+ catch { }
386
+ }
387
+ if (remeda.isEmpty(postUpdates))
388
+ return undefined;
389
+ return postUpdates;
390
+ }
391
+ _calculateLatestUpdateTrigger() {
392
+ const lastPublishTooOld = (this.updatedAt || 0) < timestamp() - 60 * 15; // Publish a community record every 15 minutes at least
393
+ // these two checks below are for rare cases where a purged comments or post is not forcing community for a new update
394
+ const lastPostCidChanged = this.lastPostCid !== this._dbHandler.queryLatestPostCid()?.cid;
395
+ const lastCommentCidChanged = this.lastCommentCid !== this._dbHandler.queryLatestCommentCid()?.cid;
396
+ this._communityUpdateTrigger =
397
+ this._communityUpdateTrigger ||
398
+ lastPublishTooOld ||
399
+ this._pendingEditProps.length > 0 ||
400
+ this._blocksToRm.length > 0 ||
401
+ lastCommentCidChanged ||
402
+ lastPostCidChanged; // we have at least one edit to include in new ipns
403
+ }
404
+ _requireCommunityUpdateIfModQueueChanged() {
405
+ const combinedHashOfAllQueuedComments = this._dbHandler.queryCombinedHashOfPendingComments();
406
+ if (this._combinedHashOfPendingCommentsCids !== combinedHashOfAllQueuedComments)
407
+ this._communityUpdateTrigger = true;
408
+ }
409
+ async _resolveIpnsAndLogIfPotentialProblematicSequence() {
410
+ const log = Logger("pkc-js:local-community:_resolveIpnsAndLogIfPotentialProblematicSequence");
411
+ if (!this.signer.ipnsKeyName)
412
+ throw Error("IPNS key name is not defined");
413
+ if (!this.updateCid)
414
+ return;
415
+ try {
416
+ const ipnsCid = await this._clientsManager.resolveIpnsToCidP2P(this.signer.ipnsKeyName, { timeoutMs: 120000 });
417
+ log.trace("Resolved community", this.address, "IPNS key", this.signer.ipnsKeyName, "to", ipnsCid);
418
+ if (ipnsCid && this.updateCid && ipnsCid !== this.updateCid) {
419
+ log.error("community", this.address, "IPNS key", this.signer.ipnsKeyName, "points to", ipnsCid, "but we expected it to point to", this.updateCid, "This could result an IPNS record with invalid sequence number");
420
+ }
421
+ }
422
+ catch (e) {
423
+ log.trace("Failed to resolve community before publishing", this.address, "IPNS key", this.signer.ipnsKeyName, e);
424
+ }
425
+ }
426
+ async _addOldPageCidsToCidsToUnpin(curPages, newPages, addToBlockRm) {
427
+ if (!curPages && !newPages)
428
+ return;
429
+ else if (curPages && !newPages) {
430
+ // we had to reset our community pages, maybe because we purged all comments or changed community address
431
+ const allPageCidsUnderCurPages = await iterateOverPageCidsToFindAllCids({
432
+ pages: curPages,
433
+ clientManager: this._clientsManager
434
+ });
435
+ allPageCidsUnderCurPages.forEach((cid) => {
436
+ this._cidsToUnPin.add(cid);
437
+ if (addToBlockRm)
438
+ this._blocksToRm.push(cid);
439
+ });
440
+ }
441
+ else if (curPages && newPages) {
442
+ // need to find cids for both, and compare them and only keep ones in newPages
443
+ const allPageCidsUnderCurPages = await iterateOverPageCidsToFindAllCids({
444
+ pages: curPages,
445
+ clientManager: this._clientsManager
446
+ });
447
+ const allPageCidsUnderNewPages = await iterateOverPageCidsToFindAllCids({
448
+ pages: newPages,
449
+ clientManager: this._clientsManager
450
+ });
451
+ const cidsToUnpin = remeda.difference(allPageCidsUnderCurPages, allPageCidsUnderNewPages);
452
+ cidsToUnpin.forEach((cid) => {
453
+ this._cidsToUnPin.add(cid);
454
+ if (addToBlockRm)
455
+ this._blocksToRm.push(cid);
456
+ });
457
+ }
458
+ }
459
+ async updateCommunityIpnsIfNeeded(commentUpdateRowsToPublishToIpfs) {
460
+ const log = Logger("pkc-js:local-community:start:updateCommunityIpnsIfNeeded");
461
+ this._calculateLatestUpdateTrigger();
462
+ if (!this._communityUpdateTrigger)
463
+ return; // No reason to update
464
+ this._dbHandler.createTransaction();
465
+ const latestPost = this._dbHandler.queryLatestPostCid();
466
+ const latestComment = this._dbHandler.queryLatestCommentCid();
467
+ this._dbHandler.commitTransaction();
468
+ const stats = this._dbHandler.queryCommunityStats();
469
+ if (commentUpdateRowsToPublishToIpfs.length > 0)
470
+ await this._syncPostUpdatesWithIpfs(commentUpdateRowsToPublishToIpfs);
471
+ const newPostUpdates = await this._calculateNewPostUpdates();
472
+ const newModQueue = await this._pageGenerator.generateModQueuePages();
473
+ const kuboRpcClient = this._clientsManager.getDefaultKuboRpcClient();
474
+ const statsCid = (await retryKuboIpfsAddAndProvide({
475
+ ipfsClient: kuboRpcClient._client,
476
+ log,
477
+ content: deterministicStringify(stats),
478
+ addOptions: { pin: true },
479
+ provideOptions: { recursive: true },
480
+ provideInBackground: true
481
+ })).path;
482
+ if (this.statsCid && statsCid !== this.statsCid)
483
+ this._cidsToUnPin.add(this.statsCid);
484
+ const currentTimestamp = timestamp();
485
+ const updatedAt = typeof this?.updatedAt === "number" && this.updatedAt >= currentTimestamp ? this.updatedAt + 1 : currentTimestamp;
486
+ const editIdsToIncludeInNextUpdate = this._pendingEditProps.map((editProps) => editProps.editId);
487
+ const pendingCommunityIpfsEditProps = Object.assign({}, //@ts-expect-error
488
+ ...this._pendingEditProps.map((editProps) => remeda.pick(editProps, remeda.keys.strict(CommunityIpfsSchema.shape))));
489
+ if (this._pendingEditProps.length > 0)
490
+ log("Including edit props in next IPNS update", this._pendingEditProps);
491
+ const newIpns = {
492
+ ...cleanUpBeforePublishing({
493
+ ...remeda.omit(this._toJSONIpfsBaseNoPosts(), ["signature"]),
494
+ ...pendingCommunityIpfsEditProps,
495
+ lastPostCid: latestPost?.cid,
496
+ lastCommentCid: latestComment?.cid,
497
+ statsCid,
498
+ updatedAt,
499
+ postUpdates: newPostUpdates,
500
+ protocolVersion: env.PROTOCOL_VERSION
501
+ })
502
+ };
503
+ const preloadedPostsPages = "hot";
504
+ // Calculate size taken by community without posts and signature
505
+ const communityWithoutPostsSignatureSize = Buffer.byteLength(JSON.stringify(newIpns), "utf8");
506
+ // Calculate expected signature size
507
+ const expectedSignatureSize = calculateExpectedSignatureSize(newIpns);
508
+ // Calculate remaining space for posts
509
+ const availablePostsSize = MAX_FILE_SIZE_BYTES_FOR_COMMUNITY_IPFS - communityWithoutPostsSignatureSize - expectedSignatureSize - 1000;
510
+ const generatedPosts = await this._pageGenerator.generateCommunityPosts(preloadedPostsPages, availablePostsSize);
511
+ // posts should not be cleaned up because we want to make sure not to modify authors' posts
512
+ if (generatedPosts) {
513
+ if ("singlePreloadedPage" in generatedPosts)
514
+ newIpns.posts = { pages: generatedPosts.singlePreloadedPage };
515
+ else if (generatedPosts.pageCids) {
516
+ // multiple pages
517
+ newIpns.posts = {
518
+ pageCids: generatedPosts.pageCids,
519
+ pages: remeda.pick(generatedPosts.pages, [preloadedPostsPages])
520
+ };
521
+ }
522
+ }
523
+ else {
524
+ await this._updateDbInternalState({ posts: undefined }); // make sure db resets posts as well
525
+ // TODO make sure to capture this.posts cids to unpin
526
+ }
527
+ this._addOldPageCidsToCidsToUnpin(this.raw.communityIpfs?.posts, newIpns.posts).catch((err) => log.error("Failed to add old page cids of community.posts to _cidsToUnpin", err));
528
+ if (newModQueue) {
529
+ newIpns.modQueue = { pageCids: newModQueue.pageCids };
530
+ }
531
+ else {
532
+ await this._updateDbInternalState({ modQueue: undefined });
533
+ this.modQueue.resetPages();
534
+ }
535
+ const signature = await signCommunity({ community: newIpns, signer: this.signer });
536
+ const newCommunityRecord = { ...newIpns, signature };
537
+ await this._validateCommunitySizeSchemaAndSignatureBeforePublishing(newCommunityRecord);
538
+ const contentToPublish = deterministicStringify(newCommunityRecord);
539
+ const file = await retryKuboIpfsAddAndProvide({
540
+ ipfsClient: kuboRpcClient._client,
541
+ log,
542
+ content: contentToPublish, // you need to do deterministic here or otherwise cids in commentUpdate.replies won't match up correctly
543
+ addOptions: { pin: true },
544
+ provideOptions: { recursive: true },
545
+ provideInBackground: false
546
+ });
547
+ log(`Published community record. Kubo CID: ${file.path}. updatedAt: ${newCommunityRecord.updatedAt}. ` +
548
+ `Content length: ${contentToPublish.length}`);
549
+ if (file.size > MAX_FILE_SIZE_BYTES_FOR_COMMUNITY_IPFS) {
550
+ throw new PKCError("ERR_LOCAL_COMMUNITY_RECORD_TOO_LARGE", {
551
+ calculatedSizeOfNewCommunityRecord: file.size,
552
+ maxSize: MAX_FILE_SIZE_BYTES_FOR_COMMUNITY_IPFS,
553
+ newCommunityRecord,
554
+ address: this.address
555
+ });
556
+ }
557
+ if (!this.signer.ipnsKeyName)
558
+ throw Error("IPNS key name is not defined");
559
+ // after kubo 0.40 implements fetching IPNS record from local blockstore, we don't need line below anymore
560
+ if (this._firstUpdateAfterStart)
561
+ await this._resolveIpnsAndLogIfPotentialProblematicSequence();
562
+ const ttl = `${this._pkc.publishInterval * 3}ms`; // default publish interval is 20s, so default ttl is 60s
563
+ const lastPublishedIpnsRecordData = await this._dbHandler.keyvGet(STORAGE_KEYS[STORAGE_KEYS.LAST_IPNS_RECORD]);
564
+ const decodedIpnsRecord = lastPublishedIpnsRecordData
565
+ ? cborg.decode(new Uint8Array(Object.values(lastPublishedIpnsRecordData)))
566
+ : undefined;
567
+ const ipnsSequence = decodedIpnsRecord ? BigInt(decodedIpnsRecord.sequence) + 1n : undefined;
568
+ const publishRes = await kuboRpcClient._client.name.publish(file.path, {
569
+ key: this.signer.ipnsKeyName,
570
+ allowOffline: true,
571
+ resolve: true,
572
+ ttl
573
+ // enable below line after kubo fixes their problems with fetching IPNS records from local blockstore
574
+ // ...(ipnsSequence ? { sequence: ipnsSequence } : undefined)
575
+ });
576
+ log(`Published a new IPNS record for community(${this.address}) on IPNS (${publishRes.name}) that points to file (${publishRes.value}) with updatedAt (${newCommunityRecord.updatedAt}) and TTL (${ttl})`);
577
+ this._clientsManager.updateKuboRpcState("stopped", kuboRpcClient.url);
578
+ this._addOldPageCidsToCidsToUnpin(this.raw.communityIpfs?.modQueue, newIpns.modQueue).catch((err) => log.error("Failed to add old page cids of community.modQueue to _cidsToUnpin", err));
579
+ await this._unpinStaleCids();
580
+ if (this._blocksToRm.length > 0) {
581
+ const removedBlocks = await removeBlocksFromKuboNode({
582
+ ipfsClient: this._clientsManager.getDefaultKuboRpcClient()._client,
583
+ log,
584
+ cids: this._blocksToRm,
585
+ options: { force: true }
586
+ });
587
+ log("Removed blocks", removedBlocks, "from kubo node");
588
+ this._blocksToRm = this._blocksToRm.filter((blockCid) => !removedBlocks.includes(blockCid));
589
+ }
590
+ if (this.updateCid)
591
+ this._cidsToUnPin.add(this.updateCid); // add old cid of community to be unpinned
592
+ this.initCommunityIpfsPropsNoMerge(newCommunityRecord);
593
+ this.updateCid = file.path;
594
+ this._pendingEditProps = this._pendingEditProps.filter((editProps) => !editIdsToIncludeInNextUpdate.includes(editProps.editId));
595
+ // Re-apply remaining pending edits to in-memory state.
596
+ // initCommunityIpfsPropsNoMerge above overwrites all CommunityIpfs properties from the
597
+ // published IPNS record. If edit() was called during the long IPNS publish await,
598
+ // those edits are still in _pendingEditProps but their in-memory values were overwritten.
599
+ if (this._pendingEditProps.length > 0) {
600
+ const remainingEditProps = Object.assign({}, //@ts-expect-error
601
+ ...this._pendingEditProps.map((editProps) => remeda.pick(editProps, remeda.keys.strict(CommunityIpfsSchema.shape))));
602
+ Object.assign(this, remainingEditProps);
603
+ }
604
+ this._communityUpdateTrigger = false;
605
+ this._firstUpdateAfterStart = false;
606
+ try {
607
+ // this call will fail if we have http routers + kubo 0.38 and earlier
608
+ const ipnsRecord = await getIpnsRecordInLocalKuboNode(kuboRpcClient, this.signer.address);
609
+ await this._dbHandler.keyvSet(STORAGE_KEYS[STORAGE_KEYS.LAST_IPNS_RECORD], cborg.encode(ipnsRecord));
610
+ }
611
+ catch (e) {
612
+ log.trace("Failed to update IPNS record in sqlite record, not a critical error and will most likely be fixed by kubo past 0.38", e);
613
+ }
614
+ this._combinedHashOfPendingCommentsCids = newModQueue?.combinedHashOfCids || sha256("");
615
+ log.trace("Updated combined hash of pending comments to", this._combinedHashOfPendingCommentsCids);
616
+ await this._updateDbInternalState(this.toJSONInternalAfterFirstUpdate());
617
+ this._changeStateEmitEventEmitStateChangeEvent({
618
+ newStartedState: "succeeded",
619
+ event: { name: "update", args: [this] }
620
+ });
621
+ }
622
+ shouldResolveDomainForVerification() {
623
+ return this.address.includes(".") && Math.random() < 0.005; // Resolving domain should be a rare process because default rpcs throttle if we resolve too much
624
+ }
625
+ async _validateCommunitySizeSchemaAndSignatureBeforePublishing(recordToPublishRaw) {
626
+ const log = Logger("pkc-js:local-community:_validateCommunitySchemaAndSignatureBeforePublishing");
627
+ const stringifiedNewCommunityRecord = deterministicStringify(recordToPublishRaw);
628
+ const calculatedSizeOfNewCommunityRecord = await calculateStringSizeSameAsIpfsAddCidV0(stringifiedNewCommunityRecord);
629
+ // Check if the community record size is less than 1MB
630
+ if (calculatedSizeOfNewCommunityRecord > MAX_FILE_SIZE_BYTES_FOR_COMMUNITY_IPFS) {
631
+ const error = new PKCError("ERR_LOCAL_COMMUNITY_RECORD_TOO_LARGE", {
632
+ calculatedSizeOfNewCommunityRecord,
633
+ maxSize: MAX_FILE_SIZE_BYTES_FOR_COMMUNITY_IPFS,
634
+ recordToPublishRaw,
635
+ address: this.address
636
+ });
637
+ log.error(`Local community (${this.address}) produced a record that is too large (${calculatedSizeOfNewCommunityRecord.toFixed(2)} bytes). Maximum size is ${MAX_FILE_SIZE_BYTES_FOR_COMMUNITY_IPFS} bytes.`, error);
638
+ throw error;
639
+ }
640
+ const parseRes = CommunityIpfsSchema.safeParse(recordToPublishRaw);
641
+ if (!parseRes.success) {
642
+ const error = new PKCError("ERR_LOCAL_COMMUNITY_PRODUCED_INVALID_SCHEMA", {
643
+ invalidRecord: recordToPublishRaw,
644
+ err: parseRes.error
645
+ });
646
+ log.error(`Local community (${this.address}) produced an invalid CommunityIpfs schema`, error);
647
+ throw error;
648
+ }
649
+ const verificationOpts = {
650
+ community: recordToPublishRaw,
651
+ communityIpnsName: this.signer.address,
652
+ resolveAuthorNames: false,
653
+ clientsManager: this._clientsManager,
654
+ validatePages: true,
655
+ cacheIfValid: false
656
+ };
657
+ try {
658
+ const validation = await verifyCommunity(verificationOpts);
659
+ if (!validation.valid) {
660
+ throw new PKCError("ERR_LOCAL_COMMUNITY_PRODUCED_INVALID_SIGNATURE", {
661
+ validation,
662
+ verificationOpts
663
+ });
664
+ }
665
+ }
666
+ catch (e) {
667
+ log.error(`Local community (${this.address}) produced an invalid signature`, e);
668
+ throw e;
669
+ }
670
+ verificationOpts.community = JSON.parse(stringifiedNewCommunityRecord); // let's stringify and parse again to make sure we're not using any invalid data
671
+ try {
672
+ const validation = await verifyCommunity(verificationOpts);
673
+ if (!validation.valid) {
674
+ throw new PKCError("ERR_LOCAL_COMMUNITY_PRODUCED_INVALID_SIGNATURE", {
675
+ validation,
676
+ verificationOpts
677
+ });
678
+ }
679
+ }
680
+ catch (e) {
681
+ log.error(`Local community (${this.address}) produced an invalid signature after stringifying and parsing again. This is a critical bug.`, e);
682
+ throw e;
683
+ }
684
+ if (this.shouldResolveDomainForVerification()) {
685
+ try {
686
+ log(`Resolving domain ${this.address} to make sure it's the same as signer.address ${this.signer.address}`);
687
+ await this._assertDomainResolvesCorrectly(this.address);
688
+ }
689
+ catch (e) {
690
+ log.error(e);
691
+ this.emit("error", e);
692
+ }
693
+ }
694
+ }
695
+ async storeCommentEdit(commentEditRaw, challengeRequestId) {
696
+ const log = Logger("pkc-js:local-community:storeCommentEdit");
697
+ const strippedOutEditPublication = CommentEditPubsubMessagePublicationWithFlexibleAuthorSchema.strip().parse(commentEditRaw); // we strip out here so we don't store any extra props in commentedits table
698
+ strippedOutEditPublication.author = cleanWireAuthor(strippedOutEditPublication.author); // strip runtime-only author fields (address, publicKey, etc.)
699
+ // Normalize to new wire format: ensure communityPublicKey/communityName for DB columns
700
+ if (!strippedOutEditPublication.communityPublicKey)
701
+ strippedOutEditPublication.communityPublicKey = this.signer.address;
702
+ if (!strippedOutEditPublication.communityName && isStringDomain(this.address))
703
+ strippedOutEditPublication.communityName = this.address;
704
+ const commentToBeEdited = this._dbHandler.queryComment(commentEditRaw.commentCid); // We assume commentToBeEdited to be defined because we already tested for its existence above
705
+ if (!commentToBeEdited)
706
+ throw Error("The comment to edit doesn't exist"); // unlikely error to happen, but always a good idea to verify
707
+ const editSignedByOriginalAuthor = commentEditRaw.signature.publicKey === commentToBeEdited.signature.publicKey;
708
+ const authorSignerAddress = await getPKCAddressFromPublicKey(commentEditRaw.signature.publicKey);
709
+ const editTableRow = {
710
+ ...strippedOutEditPublication,
711
+ isAuthorEdit: editSignedByOriginalAuthor,
712
+ authorSignerAddress,
713
+ insertedAt: timestamp()
714
+ };
715
+ const extraPropsInEdit = remeda
716
+ .difference(remeda.keys.strict(commentEditRaw), remeda.keys.strict(CommentEditPubsubMessagePublicationSchema.shape))
717
+ .filter((key) => key !== "communityAddress"); // communityAddress is excluded because it's been converted to communityPublicKey/communityName above
718
+ if (extraPropsInEdit.length > 0) {
719
+ log("Found extra props on CommentEdit", extraPropsInEdit, "Will be adding them to extraProps column");
720
+ editTableRow.extraProps = remeda.pick(commentEditRaw, extraPropsInEdit);
721
+ }
722
+ const isEditDuplicate = this._dbHandler.hasCommentEditWithSignatureEncoded(editTableRow.signature.signature);
723
+ if (isEditDuplicate) {
724
+ throw new PKCError("ERR_DUPLICATE_COMMENT_EDIT", { editTableRow });
725
+ }
726
+ this._dbHandler.insertCommentEdits([editTableRow]);
727
+ // If author is deleting a pending or disapproved comment, purge it immediately from the database
728
+ if (commentEditRaw.deleted === true) {
729
+ const isPending = commentToBeEdited.pendingApproval;
730
+ const disapprovalResult = this._dbHandler._queryIsCommentApproved(commentToBeEdited);
731
+ const isDisapproved = disapprovalResult && !disapprovalResult.approved;
732
+ if (isPending || isDisapproved) {
733
+ log("Author deleted a pending/disapproved comment, purging immediately", commentEditRaw.commentCid);
734
+ this._dbHandler.purgeComment(commentEditRaw.commentCid);
735
+ this._communityUpdateTrigger = true;
736
+ }
737
+ }
738
+ }
739
+ async storeCommentModeration(commentModRaw, challengeRequestId) {
740
+ const log = Logger("pkc-js:local-community:storeCommentModeration");
741
+ const strippedOutModPublication = CommentModerationPubsubMessagePublicationSchema.strip().parse(commentModRaw); // we strip out here so we don't store any extra props in commentedits table
742
+ strippedOutModPublication.author = cleanWireAuthor(strippedOutModPublication.author); // strip runtime-only author fields (address, publicKey, etc.)
743
+ // Normalize to new wire format: ensure communityPublicKey/communityName for DB columns
744
+ if (!strippedOutModPublication.communityPublicKey)
745
+ strippedOutModPublication.communityPublicKey = this.signer.address;
746
+ if (!strippedOutModPublication.communityName && isStringDomain(this.address))
747
+ strippedOutModPublication.communityName = this.address;
748
+ const commentToBeEdited = this._dbHandler.queryComment(commentModRaw.commentCid); // We assume commentToBeEdited to be defined because we already tested for its existence above
749
+ if (!commentToBeEdited)
750
+ throw Error("The comment to edit doesn't exist"); // unlikely error to happen, but always a good idea to verify
751
+ const modSignerAddress = await getPKCAddressFromPublicKey(commentModRaw.signature.publicKey);
752
+ // Determine the target author signer address and domain if this moderation affects the author (ban/flair)
753
+ let targetAuthorSignerAddress;
754
+ let targetAuthorDomain;
755
+ if (strippedOutModPublication.commentModeration.author) {
756
+ // Check if the comment was published with pseudonymity - if so, get the original author address/domain
757
+ const aliasInfo = this._dbHandler.queryPseudonymityAliasByCommentCid(commentModRaw.commentCid);
758
+ if (aliasInfo) {
759
+ targetAuthorSignerAddress = await getPKCAddressFromPublicKey(aliasInfo.originalAuthorSignerPublicKey);
760
+ targetAuthorDomain = aliasInfo.originalAuthorDomain || undefined;
761
+ }
762
+ else {
763
+ targetAuthorSignerAddress = commentToBeEdited.authorSignerAddress;
764
+ targetAuthorDomain = getAuthorDomainFromWire(commentToBeEdited.author);
765
+ }
766
+ }
767
+ const modTableRow = {
768
+ ...strippedOutModPublication,
769
+ modSignerAddress,
770
+ insertedAt: timestamp(),
771
+ targetAuthorSignerAddress,
772
+ targetAuthorDomain
773
+ };
774
+ const isCommentModDuplicate = this._dbHandler.hasCommentModerationWithSignatureEncoded(modTableRow.signature.signature);
775
+ if (isCommentModDuplicate) {
776
+ throw new PKCError("ERR_DUPLICATE_COMMENT_MODERATION", { modTableRow });
777
+ }
778
+ const extraPropsInMod = remeda
779
+ .difference(remeda.keys.strict(commentModRaw), remeda.keys.strict(CommentModerationPubsubMessagePublicationSchema.shape))
780
+ .filter((key) => key !== "communityAddress"); // communityAddress is excluded because it's been converted to communityPublicKey/communityName above
781
+ if (extraPropsInMod.length > 0) {
782
+ log("Found extra props on CommentModeration", extraPropsInMod, "Will be adding them to extraProps column");
783
+ modTableRow.extraProps = remeda.pick(commentModRaw, extraPropsInMod);
784
+ }
785
+ if (modTableRow.commentModeration.purged) {
786
+ log("commentModeration.purged=true, and therefore will delete the post/comment and all its reply tree from the db as well as unpin the cids from ipfs", "comment cid is", modTableRow.commentCid);
787
+ const commentToPurge = this._dbHandler.queryComment(modTableRow.commentCid);
788
+ if (!commentToPurge)
789
+ throw Error("Comment to purge not found");
790
+ const purgedTableRows = this._dbHandler.purgeComment(modTableRow.commentCid);
791
+ for (const purgedTableRow of purgedTableRows)
792
+ await this._addAllCidsUnderPurgedCommentToBeRemoved(purgedTableRow);
793
+ log("Purged comment", modTableRow.commentCid, "and its comment and comment update children", "out of DB and IPFS");
794
+ await this._rmUnneededMfsPaths(); // not sure if needed here
795
+ if (this.updateCid) {
796
+ // need to remove any update cids with reference to purged comment
797
+ this._blocksToRm.push(this.updateCid);
798
+ this._cidsToUnPin.add(this.updateCid);
799
+ }
800
+ }
801
+ else if ("approved" in modTableRow.commentModeration) {
802
+ if (modTableRow.commentModeration.approved) {
803
+ log("commentModeration.approved=true, and therefore move comment from pending approval and add it to IPFS", "comment cid is", modTableRow.commentCid);
804
+ await this._addCommentRowToIPFS(commentToBeEdited, Logger("pkc-js:local-community:storeCommentModeration:_addCommentRowToIPFS"));
805
+ this._dbHandler.approvePendingComment({ cid: modTableRow.commentCid });
806
+ }
807
+ else {
808
+ const shouldPurgeDisapprovedComment = Object.keys(modTableRow.commentModeration).length === 1; // no other props were included, if so purge the comment
809
+ log("commentModeration.approved=false, and therefore this comment will be removed entirely from DB", "should we purge this comment? = ", shouldPurgeDisapprovedComment, "comment cid is", modTableRow.commentCid);
810
+ if (shouldPurgeDisapprovedComment)
811
+ this._dbHandler.purgeComment(modTableRow.commentCid);
812
+ else
813
+ this._dbHandler.removeCommentFromPendingApproval({ cid: modTableRow.commentCid });
814
+ }
815
+ }
816
+ this._dbHandler.insertCommentModerations([modTableRow]);
817
+ this._communityUpdateTrigger = true;
818
+ log("Inserted comment moderation", "of comment", modTableRow.commentCid, "into db", "with props", modTableRow);
819
+ }
820
+ async storeVote(newVoteProps, challengeRequestId) {
821
+ const log = Logger("pkc-js:local-community:storeVote");
822
+ const authorSignerAddress = await getPKCAddressFromPublicKey(newVoteProps.signature.publicKey);
823
+ this._dbHandler.deleteVote(authorSignerAddress, newVoteProps.commentCid);
824
+ const voteTableRow = {
825
+ ...remeda.pick(newVoteProps, ["vote", "commentCid", "protocolVersion", "timestamp"]),
826
+ authorSignerAddress,
827
+ insertedAt: timestamp()
828
+ };
829
+ const extraPropsInVote = remeda.difference(remeda.keys.strict(newVoteProps), remeda.keys.strict(VotePubsubMessagePublicationSchema.shape));
830
+ if (extraPropsInVote.length > 0) {
831
+ log("Found extra props on Vote", extraPropsInVote, "Will be adding them to extraProps column");
832
+ voteTableRow.extraProps = remeda.pick(newVoteProps, extraPropsInVote);
833
+ }
834
+ this._dbHandler.insertVotes([voteTableRow]);
835
+ log("Inserted vote", "of comment", voteTableRow.commentCid, "into db", "with props", voteTableRow);
836
+ return undefined;
837
+ }
838
+ async storeCommunityEditPublication(editProps, challengeRequestId) {
839
+ const log = Logger("pkc-js:local-community:storeCommunityEdit");
840
+ const authorSignerAddress = await getPKCAddressFromPublicKey(editProps.signature.publicKey);
841
+ const authorIdentity = getAuthorNameFromWire(editProps.author) || authorSignerAddress;
842
+ log("Received community edit", editProps.communityEdit, "from author", authorIdentity, "with signer address", authorSignerAddress, "Will be using these props to edit the community props");
843
+ const propsAfterEdit = remeda.pick(this, remeda.keys.strict(editProps.communityEdit));
844
+ log("Current props from community edit (not edited yet)", propsAfterEdit);
845
+ lodashDeepMerge(propsAfterEdit, editProps.communityEdit);
846
+ await this.edit(propsAfterEdit);
847
+ return undefined;
848
+ }
849
+ isPublicationReply(publication) {
850
+ return Boolean(publication.parentCid);
851
+ }
852
+ isPublicationPost(publication) {
853
+ return !publication.parentCid;
854
+ }
855
+ async _calculateLinkProps(link) {
856
+ if (!link || !this.settings?.fetchThumbnailUrls)
857
+ return undefined;
858
+ return getThumbnailPropsOfLink(link, this, this.settings.fetchThumbnailUrlsProxyUrl);
859
+ }
860
+ async _calculateLatestPostProps() {
861
+ this._dbHandler.createTransaction();
862
+ const previousCid = this._dbHandler.queryLatestPostCid()?.cid;
863
+ this._dbHandler.commitTransaction();
864
+ return { depth: 0, previousCid };
865
+ }
866
+ async _calculateReplyProps(comment) {
867
+ if (!comment.parentCid)
868
+ throw Error("Reply has to have parentCid");
869
+ this._dbHandler.createTransaction();
870
+ const commentsUnderParent = this._dbHandler.queryCommentsUnderComment(comment.parentCid);
871
+ const parent = this._dbHandler.queryComment(comment.parentCid);
872
+ this._dbHandler.commitTransaction();
873
+ if (!parent)
874
+ throw Error("Failed to find parent of reply");
875
+ return {
876
+ depth: parent.depth + 1,
877
+ postCid: parent.postCid,
878
+ previousCid: commentsUnderParent[0]?.cid
879
+ };
880
+ }
881
+ async _resolveAliasPrivateKeyForCommentPublication(opts) {
882
+ if (opts.mode === "per-post") {
883
+ // For a new post (no postCid yet), always generate a fresh alias; once stored the postCid will be used for reuse.
884
+ if (opts.postCid) {
885
+ const existing = this._dbHandler.queryPseudonymityAliasForPost(opts.originalAuthorSignerPublicKey, opts.postCid);
886
+ if (existing?.aliasPrivateKey)
887
+ return existing.aliasPrivateKey;
888
+ }
889
+ return (await this._pkc.createSigner()).privateKey;
890
+ }
891
+ else if (opts.mode === "per-reply") {
892
+ const signer = await this._pkc.createSigner();
893
+ return signer.privateKey;
894
+ }
895
+ else if (opts.mode === "per-author") {
896
+ const existing = this._dbHandler.queryPseudonymityAliasForAuthor(opts.originalAuthorSignerPublicKey);
897
+ if (existing?.aliasPrivateKey)
898
+ return existing.aliasPrivateKey;
899
+ const signer = await this._pkc.createSigner();
900
+ return signer.privateKey;
901
+ }
902
+ else
903
+ throw Error(`Unsupported pseudonymityMode (${opts.mode})`);
904
+ }
905
+ async _prepareCommentWithAnonymity(originalComment) {
906
+ const mode = this.features?.pseudonymityMode;
907
+ if (!mode)
908
+ return { publication: originalComment };
909
+ // Mods (owner, admin, moderator) are never pseudonymized
910
+ const isAuthorMod = await this._isPublicationAuthorPartOfRoles(originalComment, ["owner", "admin", "moderator"]);
911
+ if (isAuthorMod)
912
+ return { publication: originalComment };
913
+ const originalAuthorSignerPublicKey = originalComment.signature.publicKey;
914
+ const postCid = originalComment.postCid;
915
+ const aliasPrivateKey = await this._resolveAliasPrivateKeyForCommentPublication({
916
+ mode,
917
+ originalAuthorSignerPublicKey,
918
+ postCid
919
+ });
920
+ const aliasSigner = await this._pkc.createSigner({ privateKey: aliasPrivateKey, type: "ed25519" });
921
+ const displayName = originalComment.author?.displayName;
922
+ const sanitizedAuthor = cleanWireAuthor(displayName !== undefined ? { displayName } : undefined);
923
+ const anonymizedComment = remeda.clone(originalComment);
924
+ if (sanitizedAuthor !== undefined) {
925
+ anonymizedComment.author = sanitizedAuthor;
926
+ }
927
+ else {
928
+ delete anonymizedComment.author;
929
+ }
930
+ anonymizedComment.signature = await signComment({
931
+ comment: { ...anonymizedComment, signer: aliasSigner, communityAddress: this.address },
932
+ pkc: this._pkc
933
+ });
934
+ return {
935
+ publication: anonymizedComment,
936
+ anonymity: {
937
+ aliasPrivateKey,
938
+ originalAuthorSignerPublicKey,
939
+ mode,
940
+ originalComment
941
+ }
942
+ };
943
+ }
944
+ async _prepareCommentEditWithAlias(originalEdit) {
945
+ const aliasSignerOfComment = this._dbHandler.queryPseudonymityAliasByCommentCid(originalEdit.commentCid);
946
+ if (!aliasSignerOfComment)
947
+ return originalEdit;
948
+ const aliasSigner = await this._pkc.createSigner({
949
+ privateKey: aliasSignerOfComment.aliasPrivateKey,
950
+ type: "ed25519"
951
+ });
952
+ const commentEditSignedByAlias = remeda.clone(originalEdit);
953
+ delete commentEditSignedByAlias.author;
954
+ commentEditSignedByAlias.signature = await signCommentEdit({
955
+ edit: { ...commentEditSignedByAlias, signer: aliasSigner, communityAddress: this.address },
956
+ pkc: this._pkc
957
+ });
958
+ return commentEditSignedByAlias;
959
+ }
960
+ async storeComment(opts) {
961
+ const { commentPubsub, pendingApproval, pseudonymityMode, originalCommentSignatureEncoded } = opts;
962
+ const log = Logger("pkc-js:local-community:handleChallengeExchange:storeComment");
963
+ const commentIpfs = {
964
+ ...commentPubsub,
965
+ ...(await this._calculateLinkProps(commentPubsub.link)),
966
+ ...(this.isPublicationPost(commentPubsub) && (await this._calculateLatestPostProps())),
967
+ ...(this.isPublicationReply(commentPubsub) && (await this._calculateReplyProps(commentPubsub))),
968
+ ...(pseudonymityMode ? { pseudonymityMode } : {})
969
+ };
970
+ // Normalize to new wire format: ensure communityPublicKey/communityName, remove old communityAddress
971
+ commentIpfs.communityPublicKey = this.signer.address;
972
+ if (isStringDomain(this.address))
973
+ commentIpfs.communityName = this.address;
974
+ delete commentIpfs.communityAddress;
975
+ // Strip runtime-only author fields (nameResolved, address, publicKey, etc.) before IPFS storage
976
+ commentIpfs.author = cleanWireAuthor(commentIpfs.author);
977
+ const ipfsClient = this._clientsManager.getDefaultKuboRpcClient();
978
+ const file = pendingApproval
979
+ ? undefined
980
+ : await retryKuboIpfsAddAndProvide({
981
+ ipfsClient: ipfsClient._client,
982
+ log,
983
+ content: deterministicStringify(commentIpfs),
984
+ addOptions: { pin: true },
985
+ provideOptions: { recursive: true },
986
+ provideInBackground: false
987
+ });
988
+ const commentCid = file?.path || (await calculateIpfsCidV0(deterministicStringify(commentIpfs)));
989
+ const postCid = commentIpfs.postCid || commentCid; // if postCid is not defined, then we're adding a post to IPFS, so its own cid is the postCid
990
+ const authorSignerAddress = await getPKCAddressFromPublicKey(commentPubsub.signature.publicKey);
991
+ const strippedOutCommentIpfs = CommentIpfsSchema.strip().parse(commentIpfs); // remove unknown props
992
+ strippedOutCommentIpfs.author = cleanWireAuthor(strippedOutCommentIpfs.author); // strip runtime-only author fields (address, publicKey, etc.)
993
+ const signaturesToCheck = Array.from(new Set([commentPubsub.signature.signature, originalCommentSignatureEncoded].filter((sig) => typeof sig === "string")));
994
+ const isCommentDuplicate = signaturesToCheck.some((signatureEncoded) => this._dbHandler.hasCommentWithSignatureEncoded(signatureEncoded));
995
+ if (isCommentDuplicate) {
996
+ this._cidsToUnPin.add(commentCid);
997
+ throw new PKCError("ERR_DUPLICATE_COMMENT", { file, commentIpfs, commentPubsub });
998
+ }
999
+ const commentRow = {
1000
+ ...strippedOutCommentIpfs,
1001
+ cid: commentCid,
1002
+ postCid,
1003
+ authorSignerAddress,
1004
+ insertedAt: timestamp(),
1005
+ pendingApproval
1006
+ };
1007
+ const unknownProps = remeda
1008
+ .difference(remeda.keys.strict(commentPubsub), remeda.keys.strict(CommentPubsubMessagePublicationSchema.shape))
1009
+ .filter((key) => key !== "communityAddress"); // communityAddress is excluded because it's been converted to communityPublicKey/communityName above
1010
+ if (unknownProps.length > 0) {
1011
+ log("Found extra props on Comment", unknownProps, "Will be adding them to extraProps column");
1012
+ commentRow.extraProps = remeda.pick(commentPubsub, unknownProps);
1013
+ }
1014
+ if (originalCommentSignatureEncoded)
1015
+ commentRow.originalCommentSignatureEncoded = originalCommentSignatureEncoded;
1016
+ // we may need to query comment and verify its signature
1017
+ this._dbHandler.createTransaction();
1018
+ try {
1019
+ if (!pendingApproval) {
1020
+ const { number, postNumber } = this._dbHandler.getNextCommentNumbers(commentRow.depth);
1021
+ commentRow.number = number;
1022
+ if (typeof postNumber === "number")
1023
+ commentRow.postNumber = postNumber;
1024
+ }
1025
+ this._dbHandler.insertComments([commentRow]);
1026
+ if (typeof this.settings?.maxPendingApprovalCount === "number")
1027
+ this._dbHandler.removeOldestPendingCommentIfWeHitMaxPendingCount(this.settings.maxPendingApprovalCount);
1028
+ this._dbHandler.commitTransaction();
1029
+ }
1030
+ catch (e) {
1031
+ this._dbHandler.rollbackTransaction();
1032
+ throw e;
1033
+ }
1034
+ log("Inserted comment", commentRow.cid, "into db", "with props", commentRow);
1035
+ return { comment: commentIpfs, cid: commentCid };
1036
+ }
1037
+ async storePublication(request, pendingApproval) {
1038
+ if (request.vote)
1039
+ return this.storeVote(request.vote, request.challengeRequestId);
1040
+ else if (request.commentEdit) {
1041
+ const commentEditWithAlias = await this._prepareCommentEditWithAlias(request.commentEdit);
1042
+ return this.storeCommentEdit(commentEditWithAlias, request.challengeRequestId);
1043
+ }
1044
+ else if (request.commentModeration)
1045
+ return this.storeCommentModeration(request.commentModeration, request.challengeRequestId);
1046
+ else if (request.comment) {
1047
+ const originalCommentSignatureEncoded = request.comment.signature.signature;
1048
+ const { publication, anonymity } = await this._prepareCommentWithAnonymity(request.comment);
1049
+ const storedComment = await this.storeComment({
1050
+ commentPubsub: publication,
1051
+ pendingApproval,
1052
+ pseudonymityMode: anonymity?.mode,
1053
+ originalCommentSignatureEncoded: anonymity ? originalCommentSignatureEncoded : undefined
1054
+ });
1055
+ if (anonymity)
1056
+ this._dbHandler.insertPseudonymityAliases([
1057
+ {
1058
+ commentCid: storedComment.cid,
1059
+ aliasPrivateKey: anonymity.aliasPrivateKey,
1060
+ originalAuthorSignerPublicKey: anonymity.originalAuthorSignerPublicKey,
1061
+ originalAuthorDomain: getAuthorDomainFromWire(anonymity.originalComment.author) || null,
1062
+ mode: anonymity.mode,
1063
+ insertedAt: timestamp()
1064
+ }
1065
+ ]);
1066
+ return storedComment;
1067
+ }
1068
+ else if (request.communityEdit)
1069
+ return this.storeCommunityEditPublication(request.communityEdit, request.challengeRequestId);
1070
+ else
1071
+ throw Error("Don't know how to store this publication" + request);
1072
+ }
1073
+ async _decryptOrRespondWithFailure(request) {
1074
+ const log = Logger("pkc-js:local-community:_decryptOrRespondWithFailure");
1075
+ try {
1076
+ return await decryptEd25519AesGcmPublicKeyBuffer(request.encrypted, this.signer.privateKey, request.signature.publicKey);
1077
+ }
1078
+ catch (e) {
1079
+ log.error(`Failed to decrypt request (${request.challengeRequestId.toString()}) due to error`, e);
1080
+ await this._publishFailedChallengeVerification({ reason: messages.ERR_COMMUNITY_FAILED_TO_DECRYPT_PUBSUB_MSG }, request.challengeRequestId);
1081
+ throw e;
1082
+ }
1083
+ }
1084
+ async _respondWithErrorIfSignatureOfPublicationIsInvalid(request) {
1085
+ let validity;
1086
+ if (request.comment)
1087
+ validity = await verifyCommentPubsubMessage({
1088
+ comment: request.comment,
1089
+ resolveAuthorNames: this._pkc.resolveAuthorNames,
1090
+ clientsManager: this._clientsManager
1091
+ });
1092
+ else if (request.commentEdit)
1093
+ validity = await verifyCommentEdit({
1094
+ edit: request.commentEdit,
1095
+ resolveAuthorNames: this._pkc.resolveAuthorNames,
1096
+ clientsManager: this._clientsManager
1097
+ });
1098
+ else if (request.vote)
1099
+ validity = await verifyVote({
1100
+ vote: request.vote,
1101
+ resolveAuthorNames: this._pkc.resolveAuthorNames,
1102
+ clientsManager: this._clientsManager
1103
+ });
1104
+ else if (request.commentModeration)
1105
+ validity = await verifyCommentModeration({
1106
+ moderation: request.commentModeration,
1107
+ resolveAuthorNames: this._pkc.resolveAuthorNames,
1108
+ clientsManager: this._clientsManager
1109
+ });
1110
+ else if (request.communityEdit)
1111
+ validity = await verifyCommunityEdit({
1112
+ communityEdit: request.communityEdit,
1113
+ resolveAuthorNames: this._pkc.resolveAuthorNames,
1114
+ clientsManager: this._clientsManager
1115
+ });
1116
+ else
1117
+ throw Error("Can't detect the type of publication");
1118
+ if (!validity.valid) {
1119
+ await this._publishFailedChallengeVerification({ reason: validity.reason }, request.challengeRequestId);
1120
+ throw new PKCError(getErrorCodeFromMessage(validity.reason), { request, validity });
1121
+ }
1122
+ }
1123
+ async _publishChallenges(challenges, request) {
1124
+ const log = Logger("pkc-js:local-community:_publishChallenges");
1125
+ const toEncryptChallenge = { challenges };
1126
+ const toSignChallenge = cleanUpBeforePublishing({
1127
+ type: "CHALLENGE",
1128
+ protocolVersion: env.PROTOCOL_VERSION,
1129
+ userAgent: this._pkc.userAgent,
1130
+ challengeRequestId: request.challengeRequestId,
1131
+ encrypted: await encryptEd25519AesGcmPublicKeyBuffer(deterministicStringify(toEncryptChallenge), this.signer.privateKey, request.signature.publicKey),
1132
+ timestamp: timestamp()
1133
+ });
1134
+ const challengeMessage = {
1135
+ ...toSignChallenge,
1136
+ signature: await signChallengeMessage({ challengeMessage: toSignChallenge, signer: this.signer })
1137
+ };
1138
+ const pubsubClient = this._clientsManager.getDefaultKuboPubsubClient();
1139
+ this._clientsManager.updateKuboRpcPubsubState("publishing-challenge", pubsubClient.url);
1140
+ // we only publish over pubsub if the challenge exchange is not ongoing for local publishers
1141
+ if (!this._challengeExchangesFromLocalPublishers[request.challengeRequestId.toString()])
1142
+ await this._clientsManager.pubsubPublish(this.pubsubTopicWithfallback(), challengeMessage);
1143
+ log(`Community ${this.address} with pubsub topic ${this.pubsubTopicWithfallback()} published ${challengeMessage.type} over pubsub: `, remeda.pick(toSignChallenge, ["timestamp"]), toEncryptChallenge.challenges.map((challenge) => challenge.type));
1144
+ this._clientsManager.updateKuboRpcPubsubState("waiting-challenge-answers", pubsubClient.url);
1145
+ this.emit("challenge", {
1146
+ ...challengeMessage,
1147
+ challenges
1148
+ });
1149
+ }
1150
+ async _publishFailedChallengeVerification(result, challengeRequestId) {
1151
+ // challengeSucess=false
1152
+ const log = Logger("pkc-js:local-community:_publishFailedChallengeVerification");
1153
+ const toSignVerification = cleanUpBeforePublishing({
1154
+ type: "CHALLENGEVERIFICATION",
1155
+ challengeRequestId: challengeRequestId,
1156
+ challengeSuccess: false,
1157
+ challengeErrors: result.challengeErrors,
1158
+ reason: result.reason,
1159
+ userAgent: this._pkc.userAgent,
1160
+ protocolVersion: env.PROTOCOL_VERSION,
1161
+ timestamp: timestamp()
1162
+ });
1163
+ const challengeVerification = {
1164
+ ...toSignVerification,
1165
+ signature: await signChallengeVerification({ challengeVerification: toSignVerification, signer: this.signer })
1166
+ };
1167
+ const pubsubClient = this._clientsManager.getDefaultKuboPubsubClient();
1168
+ this._clientsManager.updateKuboRpcPubsubState("publishing-challenge-verification", pubsubClient.url);
1169
+ log(`Will publish ${challengeVerification.type} over pubsub topic ${this.pubsubTopicWithfallback()} on community ${this.address}:`, remeda.omit(toSignVerification, ["challengeRequestId"]));
1170
+ if (!this._challengeExchangesFromLocalPublishers[challengeRequestId.toString()])
1171
+ await this._clientsManager.pubsubPublish(this.pubsubTopicWithfallback(), challengeVerification);
1172
+ this._clientsManager.updateKuboRpcPubsubState("waiting-challenge-requests", pubsubClient.url);
1173
+ this.emit("challengeverification", challengeVerification);
1174
+ this._ongoingChallengeExchanges.delete(challengeRequestId.toString());
1175
+ delete this._challengeExchangesFromLocalPublishers[challengeRequestId.toString()];
1176
+ this._cleanUpChallengeAnswerPromise(challengeRequestId.toString());
1177
+ }
1178
+ async _publishIdempotentDuplicateVerification(request, challengeRequestId, duplicateReason) {
1179
+ const log = Logger("pkc-js:local-community:_publishIdempotentDuplicateVerification");
1180
+ let encrypted;
1181
+ let toEncryptDecrypted;
1182
+ // For comments, include the existing comment data in the encrypted response
1183
+ if (duplicateReason === messages.ERR_DUPLICATE_COMMENT && request.comment) {
1184
+ const existingComment = this._dbHandler.queryCommentBySignatureEncoded(request.comment.signature.signature);
1185
+ if (!existingComment) {
1186
+ return this._publishFailedChallengeVerification({ reason: duplicateReason }, challengeRequestId);
1187
+ }
1188
+ log("Returning idempotent success for duplicate comment", existingComment.cid);
1189
+ const authorSignerAddress = await getPKCAddressFromPublicKey(existingComment.signature.publicKey);
1190
+ const authorDomain = getAuthorDomainFromWire(existingComment.author);
1191
+ const authorCommunity = this._dbHandler.queryCommunityAuthor(authorSignerAddress, authorDomain);
1192
+ if (!authorCommunity) {
1193
+ return this._publishFailedChallengeVerification({ reason: duplicateReason }, challengeRequestId);
1194
+ }
1195
+ const commentNumberPostNumber = this._dbHandler._assignNumbersForComment(existingComment.cid);
1196
+ const commentUpdateNoSig = cleanUpBeforePublishing({
1197
+ author: { community: authorCommunity },
1198
+ cid: existingComment.cid,
1199
+ protocolVersion: env.PROTOCOL_VERSION,
1200
+ ...commentNumberPostNumber
1201
+ });
1202
+ const commentUpdate = {
1203
+ ...commentUpdateNoSig,
1204
+ signature: await signCommentUpdateForChallengeVerification({
1205
+ update: commentUpdateNoSig,
1206
+ signer: this.signer
1207
+ })
1208
+ };
1209
+ const commentIpfs = CommentIpfsSchema.strip().parse(existingComment);
1210
+ toEncryptDecrypted = { comment: commentIpfs, commentUpdate };
1211
+ encrypted = await encryptEd25519AesGcmPublicKeyBuffer(deterministicStringify(toEncryptDecrypted), this.signer.privateKey, request.signature.publicKey);
1212
+ }
1213
+ else {
1214
+ // For edits/moderations: success has no encrypted data (same as normal success)
1215
+ log("Returning idempotent success for duplicate", duplicateReason);
1216
+ }
1217
+ const toSignMsg = cleanUpBeforePublishing({
1218
+ type: "CHALLENGEVERIFICATION",
1219
+ challengeRequestId,
1220
+ encrypted,
1221
+ challengeSuccess: true,
1222
+ reason: undefined,
1223
+ userAgent: this._pkc.userAgent,
1224
+ protocolVersion: env.PROTOCOL_VERSION,
1225
+ timestamp: timestamp()
1226
+ });
1227
+ const challengeVerification = {
1228
+ ...toSignMsg,
1229
+ signature: await signChallengeVerification({ challengeVerification: toSignMsg, signer: this.signer })
1230
+ };
1231
+ const pubsubClient = this._clientsManager.getDefaultKuboPubsubClient();
1232
+ this._clientsManager.updateKuboRpcPubsubState("publishing-challenge-verification", pubsubClient.url);
1233
+ if (!this._challengeExchangesFromLocalPublishers[challengeRequestId.toString()])
1234
+ await this._clientsManager.pubsubPublish(this.pubsubTopicWithfallback(), challengeVerification);
1235
+ this._clientsManager.updateKuboRpcPubsubState("waiting-challenge-requests", pubsubClient.url);
1236
+ const objectToEmit = { ...challengeVerification, ...toEncryptDecrypted };
1237
+ this.emit("challengeverification", objectToEmit);
1238
+ this._ongoingChallengeExchanges.delete(challengeRequestId.toString());
1239
+ delete this._challengeExchangesFromLocalPublishers[challengeRequestId.toString()];
1240
+ this._cleanUpChallengeAnswerPromise(challengeRequestId.toString());
1241
+ }
1242
+ async _storePublicationAndEncryptForChallengeVerification(request, pendingApproval) {
1243
+ const commentAfterAddingToIpfs = await this.storePublication(request, pendingApproval);
1244
+ if (!commentAfterAddingToIpfs)
1245
+ return undefined;
1246
+ const authorSignerAddress = await getPKCAddressFromPublicKey(commentAfterAddingToIpfs.comment.signature.publicKey);
1247
+ const authorDomain = getAuthorDomainFromWire(commentAfterAddingToIpfs.comment.author);
1248
+ const authorCommunity = this._dbHandler.queryCommunityAuthor(authorSignerAddress, authorDomain);
1249
+ if (!authorCommunity)
1250
+ throw Error("author.community can never be undefined after adding a comment");
1251
+ const commentNumberPostNumber = this._dbHandler._assignNumbersForComment(commentAfterAddingToIpfs.cid);
1252
+ const commentUpdateOfVerificationNoSignature = (cleanUpBeforePublishing({
1253
+ author: { community: authorCommunity },
1254
+ cid: commentAfterAddingToIpfs.cid,
1255
+ protocolVersion: env.PROTOCOL_VERSION,
1256
+ pendingApproval,
1257
+ ...commentNumberPostNumber
1258
+ }));
1259
+ const commentUpdate = {
1260
+ ...commentUpdateOfVerificationNoSignature,
1261
+ signature: await signCommentUpdateForChallengeVerification({
1262
+ update: commentUpdateOfVerificationNoSignature,
1263
+ signer: this.signer
1264
+ })
1265
+ };
1266
+ const toEncrypt = { comment: commentAfterAddingToIpfs.comment, commentUpdate };
1267
+ const encrypted = await encryptEd25519AesGcmPublicKeyBuffer(deterministicStringify(toEncrypt), this.signer.privateKey, request.signature.publicKey);
1268
+ return { ...toEncrypt, encrypted };
1269
+ }
1270
+ async _publishChallengeVerification(challengeResult, request, pendingApproval) {
1271
+ const log = Logger("pkc-js:local-community:_publishChallengeVerification");
1272
+ if (!challengeResult.challengeSuccess)
1273
+ return this._publishFailedChallengeVerification(challengeResult, request.challengeRequestId);
1274
+ else {
1275
+ // Challenge has passed, we store the publication (except if there's an issue with the publication)
1276
+ // call below could fail if the comment is duplicated
1277
+ let failureReason;
1278
+ let toEncrypt;
1279
+ try {
1280
+ toEncrypt = await this._storePublicationAndEncryptForChallengeVerification(request, pendingApproval);
1281
+ }
1282
+ catch (e) {
1283
+ failureReason = e.message;
1284
+ log.error("Failed to store store Publication And Encrypt For ChallengeVerification", e);
1285
+ }
1286
+ const toSignMsg = cleanUpBeforePublishing({
1287
+ type: "CHALLENGEVERIFICATION",
1288
+ challengeRequestId: request.challengeRequestId,
1289
+ encrypted: toEncrypt?.encrypted, // could be undefined
1290
+ challengeErrors: challengeResult.challengeErrors,
1291
+ userAgent: this._pkc.userAgent,
1292
+ protocolVersion: env.PROTOCOL_VERSION,
1293
+ timestamp: timestamp(),
1294
+ ...(failureReason ? { reason: failureReason, challengeSuccess: false } : { challengeSuccess: true, reason: undefined })
1295
+ });
1296
+ const challengeVerification = {
1297
+ ...toSignMsg,
1298
+ signature: await signChallengeVerification({ challengeVerification: toSignMsg, signer: this.signer })
1299
+ };
1300
+ const pubsubClient = this._clientsManager.getDefaultKuboPubsubClient();
1301
+ this._clientsManager.updateKuboRpcPubsubState("publishing-challenge-verification", pubsubClient.url);
1302
+ if (!this._challengeExchangesFromLocalPublishers[request.challengeRequestId.toString()])
1303
+ await this._clientsManager.pubsubPublish(this.pubsubTopicWithfallback(), challengeVerification);
1304
+ this._clientsManager.updateKuboRpcPubsubState("waiting-challenge-requests", pubsubClient.url);
1305
+ const objectToEmit = { ...challengeVerification, ...toEncrypt };
1306
+ this.emit("challengeverification", objectToEmit);
1307
+ this._ongoingChallengeExchanges.delete(request.challengeRequestId.toString());
1308
+ delete this._challengeExchangesFromLocalPublishers[request.challengeRequestId.toString()];
1309
+ this._cleanUpChallengeAnswerPromise(request.challengeRequestId.toString());
1310
+ log.trace(`Published ${challengeVerification.type} over pubsub topic ${this.pubsubTopicWithfallback()}:`, remeda.omit(objectToEmit, ["signature", "encrypted", "challengeRequestId"]));
1311
+ }
1312
+ }
1313
+ async _isPublicationAuthorPartOfRoles(publication, rolesToCheckAgainst) {
1314
+ if (!this.roles)
1315
+ return false;
1316
+ // is the author of publication a moderator?
1317
+ const signerAddress = await getPKCAddressFromPublicKey(publication.signature.publicKey);
1318
+ if (rolesToCheckAgainst.includes(this.roles[signerAddress]?.role))
1319
+ return true;
1320
+ const authorName = getAuthorNameFromWire(publication.author);
1321
+ if (typeof authorName === "string") {
1322
+ if (rolesToCheckAgainst.includes(this.roles[authorName]?.role))
1323
+ return true;
1324
+ if (this._pkc.resolveAuthorNames && isStringDomain(authorName)) {
1325
+ const resolvedSignerAddress = await this._pkc.resolveAuthorName({ address: authorName });
1326
+ if (resolvedSignerAddress !== signerAddress)
1327
+ return false;
1328
+ if (rolesToCheckAgainst.includes(this.roles[resolvedSignerAddress]?.role))
1329
+ return true;
1330
+ }
1331
+ }
1332
+ return false;
1333
+ }
1334
+ async _checkPublicationValidity(request, publication, authorCommunity) {
1335
+ const log = Logger("pkc-js:local-community:handleChallengeRequest:checkPublicationValidity");
1336
+ // Reject deprecated old wire format fields
1337
+ if ("subplebbitAddress" in publication)
1338
+ return messages.ERR_PUBLICATION_USES_DEPRECATED_SUBPLEBBIT_ADDRESS;
1339
+ if ("communityAddress" in publication)
1340
+ return messages.ERR_PUBLICATION_USES_DEPRECATED_COMMUNITY_ADDRESS;
1341
+ // communityPublicKey must be present and match this community's IPNS key
1342
+ const pubCommunityPublicKey = getCommunityPublicKeyFromWire(publication);
1343
+ if (!pubCommunityPublicKey || pubCommunityPublicKey !== this.signer.address)
1344
+ return messages.ERR_PUBLICATION_INVALID_COMMUNITY_PUBLIC_KEY;
1345
+ // communityName, if present, must match this community's address
1346
+ const pubCommunityName = getCommunityNameFromWire(publication);
1347
+ if (pubCommunityName && pubCommunityName !== this.address)
1348
+ return messages.ERR_PUBLICATION_INVALID_COMMUNITY_NAME;
1349
+ if (publication.timestamp <= timestamp() - 5 * 60 || publication.timestamp >= timestamp() + 5 * 60)
1350
+ return messages.ERR_PUBLICATION_TIMESTAMP_IS_NOT_IN_PROPER_RANGE;
1351
+ if (typeof authorCommunity?.banExpiresAt === "number" && authorCommunity.banExpiresAt > timestamp())
1352
+ return messages.ERR_AUTHOR_IS_BANNED;
1353
+ if (publication.author && remeda.intersection(remeda.keys.strict(publication.author), AuthorReservedFields).length > 0)
1354
+ return messages.ERR_PUBLICATION_AUTHOR_HAS_RESERVED_FIELD;
1355
+ // Reject publications with non-domain author.name — author.name must be a domain or absent
1356
+ const authorName = getAuthorNameFromWire(publication.author);
1357
+ if (authorName && !isStringDomain(authorName)) {
1358
+ log("Rejecting publication: author.name is not a domain", authorName);
1359
+ return messages.ERR_AUTHOR_NAME_MUST_BE_A_DOMAIN;
1360
+ }
1361
+ // Reject publications with author domains that can't be resolved or don't match the signer
1362
+ if (authorName && isStringDomain(authorName) && this._pkc.resolveAuthorNames) {
1363
+ let resolvedAddress;
1364
+ try {
1365
+ resolvedAddress = await this._clientsManager.resolveAuthorNameIfNeeded({ authorAddress: authorName });
1366
+ }
1367
+ catch (e) {
1368
+ log("Rejecting publication with unresolvable author domain", authorName, e);
1369
+ return messages.ERR_FAILED_TO_RESOLVE_AUTHOR_DOMAIN;
1370
+ }
1371
+ if (resolvedAddress === null) {
1372
+ log("Rejecting publication: author domain could not be resolved", authorName);
1373
+ return messages.ERR_FAILED_TO_RESOLVE_AUTHOR_DOMAIN;
1374
+ }
1375
+ const signerAddress = await getPKCAddressFromPublicKey(publication.signature.publicKey);
1376
+ if (resolvedAddress !== signerAddress) {
1377
+ log("Rejecting publication: author domain resolves to different signer", authorName, resolvedAddress, signerAddress);
1378
+ return messages.ERR_AUTHOR_DOMAIN_RESOLVES_TO_DIFFERENT_SIGNER;
1379
+ }
1380
+ }
1381
+ if ("commentCid" in publication || "parentCid" in publication) {
1382
+ // vote or reply or commentEdit or commentModeration
1383
+ // not post though
1384
+ //@ts-expect-error
1385
+ const parentCid = publication.parentCid || publication.commentCid;
1386
+ if (typeof parentCid !== "string")
1387
+ return messages.ERR_COMMUNITY_PUBLICATION_PARENT_CID_NOT_DEFINED;
1388
+ const parent = this._dbHandler.queryComment(parentCid);
1389
+ if (!parent)
1390
+ return messages.ERR_PUBLICATION_PARENT_DOES_NOT_EXIST_IN_COMMUNITY;
1391
+ const parentFlags = this._dbHandler.queryCommentFlagsSetByMod(parentCid);
1392
+ if (parentFlags.removed && !request.commentModeration)
1393
+ // not allowed to vote or reply under removed comments
1394
+ return messages.ERR_COMMUNITY_PUBLICATION_PARENT_HAS_BEEN_REMOVED;
1395
+ const isParentDeletedQueryRes = this._dbHandler.queryAuthorEditDeleted(parentCid);
1396
+ if (isParentDeletedQueryRes?.deleted && !request.commentModeration)
1397
+ return messages.ERR_COMMUNITY_PUBLICATION_PARENT_HAS_BEEN_DELETED; // not allowed to vote or reply under deleted comments
1398
+ const postFlags = this._dbHandler.queryCommentFlagsSetByMod(parent.postCid);
1399
+ if (postFlags.removed && !request.commentModeration)
1400
+ return messages.ERR_COMMUNITY_PUBLICATION_POST_HAS_BEEN_REMOVED;
1401
+ const isPostDeletedQueryRes = this._dbHandler.queryAuthorEditDeleted(parent.postCid);
1402
+ if (isPostDeletedQueryRes?.deleted && !request.commentModeration)
1403
+ return messages.ERR_COMMUNITY_PUBLICATION_POST_HAS_BEEN_DELETED;
1404
+ if (postFlags.locked && !request.commentModeration)
1405
+ return messages.ERR_COMMUNITY_PUBLICATION_POST_IS_LOCKED;
1406
+ if (postFlags.archived && !request.commentModeration)
1407
+ return messages.ERR_COMMUNITY_PUBLICATION_POST_IS_ARCHIVED;
1408
+ if (parent.timestamp > publication.timestamp)
1409
+ return messages.ERR_COMMUNITY_COMMENT_TIMESTAMP_IS_EARLIER_THAN_PARENT;
1410
+ // if user publishes vote/reply/commentEdit under pending comment, it should fail
1411
+ if (parent.pendingApproval && !("commentModeration" in request) && !(request.commentEdit?.deleted === true))
1412
+ return messages.ERR_USER_PUBLISHED_UNDER_PENDING_COMMENT;
1413
+ const isCommentDisapproved = this._dbHandler._queryIsCommentApproved(parent);
1414
+ if (isCommentDisapproved &&
1415
+ !isCommentDisapproved.approved &&
1416
+ !("commentModeration" in request) &&
1417
+ !(request.commentEdit?.deleted === true))
1418
+ return messages.ERR_USER_PUBLISHED_UNDER_DISAPPROVED_COMMENT;
1419
+ }
1420
+ // Reject publications if their size is over 40kb
1421
+ const publicationKilobyteSize = Buffer.byteLength(JSON.stringify(publication)) / 1000;
1422
+ if (publicationKilobyteSize > 40)
1423
+ return messages.ERR_REQUEST_PUBLICATION_OVER_ALLOWED_SIZE;
1424
+ if (request.comment) {
1425
+ const commentPublication = request.comment;
1426
+ if (remeda.intersection(remeda.keys.strict(commentPublication), CommentPubsubMessageReservedFields).length > 0)
1427
+ return messages.ERR_COMMENT_HAS_RESERVED_FIELD;
1428
+ if (this.features?.requirePostLink &&
1429
+ !commentPublication.parentCid &&
1430
+ (!commentPublication.link || (!this.features?.requirePostLinkIsMedia && !isLinkValid(commentPublication.link))))
1431
+ return messages.ERR_COMMENT_HAS_INVALID_LINK_FIELD;
1432
+ if (this.features?.requirePostLinkIsMedia &&
1433
+ commentPublication.link &&
1434
+ (!isLinkValid(commentPublication.link) || !isLinkOfMedia(commentPublication.link)))
1435
+ return messages.ERR_POST_LINK_IS_NOT_OF_MEDIA;
1436
+ if (this.features?.requireReplyLink &&
1437
+ commentPublication.parentCid &&
1438
+ (!commentPublication.link || (!this.features?.requireReplyLinkIsMedia && !isLinkValid(commentPublication.link))))
1439
+ return messages.ERR_REPLY_HAS_INVALID_LINK_FIELD;
1440
+ if (this.features?.requireReplyLinkIsMedia &&
1441
+ commentPublication.parentCid &&
1442
+ commentPublication.link &&
1443
+ (!isLinkValid(commentPublication.link) || !isLinkOfMedia(commentPublication.link)))
1444
+ return messages.ERR_REPLY_LINK_IS_NOT_OF_MEDIA;
1445
+ if (this.features?.noMarkdownImages && commentPublication.content && contentContainsMarkdownImages(commentPublication.content))
1446
+ return messages.ERR_COMMENT_CONTENT_CONTAINS_MARKDOWN_IMAGE;
1447
+ if (this.features?.noMarkdownVideos && commentPublication.content && contentContainsMarkdownVideos(commentPublication.content))
1448
+ return messages.ERR_COMMENT_CONTENT_CONTAINS_MARKDOWN_VIDEO;
1449
+ if (this.features?.noMarkdownAudio && commentPublication.content && contentContainsMarkdownAudio(commentPublication.content))
1450
+ return messages.ERR_COMMENT_CONTENT_CONTAINS_MARKDOWN_AUDIO;
1451
+ // noImages - block ALL comments with image links
1452
+ if (this.features?.noImages && commentPublication.link && isLinkOfImage(commentPublication.link))
1453
+ return messages.ERR_COMMENT_HAS_LINK_THAT_IS_IMAGE;
1454
+ // noVideos - block ALL comments with video links (including animated images like GIF/APNG)
1455
+ if (this.features?.noVideos &&
1456
+ commentPublication.link &&
1457
+ (isLinkOfVideo(commentPublication.link) || isLinkOfAnimatedImage(commentPublication.link)))
1458
+ return messages.ERR_COMMENT_HAS_LINK_THAT_IS_VIDEO;
1459
+ // noSpoilers - block ALL comments with spoiler=true
1460
+ if (this.features?.noSpoilers && commentPublication.spoiler === true)
1461
+ return messages.ERR_COMMENT_HAS_SPOILER_ENABLED;
1462
+ // noImageReplies - block only replies with image links
1463
+ if (this.features?.noImageReplies &&
1464
+ commentPublication.parentCid &&
1465
+ commentPublication.link &&
1466
+ isLinkOfImage(commentPublication.link))
1467
+ return messages.ERR_REPLY_HAS_LINK_THAT_IS_IMAGE;
1468
+ // noVideoReplies - block only replies with video links (including animated images like GIF/APNG)
1469
+ if (this.features?.noVideoReplies &&
1470
+ commentPublication.parentCid &&
1471
+ commentPublication.link &&
1472
+ (isLinkOfVideo(commentPublication.link) || isLinkOfAnimatedImage(commentPublication.link)))
1473
+ return messages.ERR_REPLY_HAS_LINK_THAT_IS_VIDEO;
1474
+ // noAudio - block ALL comments with audio links
1475
+ if (this.features?.noAudio && commentPublication.link && isLinkOfAudio(commentPublication.link))
1476
+ return messages.ERR_COMMENT_HAS_LINK_THAT_IS_AUDIO;
1477
+ // noAudioReplies - block only replies with audio links
1478
+ if (this.features?.noAudioReplies &&
1479
+ commentPublication.parentCid &&
1480
+ commentPublication.link &&
1481
+ isLinkOfAudio(commentPublication.link))
1482
+ return messages.ERR_REPLY_HAS_LINK_THAT_IS_AUDIO;
1483
+ // noSpoilerReplies - block only replies with spoiler=true
1484
+ if (this.features?.noSpoilerReplies && commentPublication.parentCid && commentPublication.spoiler === true)
1485
+ return messages.ERR_REPLY_HAS_SPOILER_ENABLED;
1486
+ // noNestedReplies - block replies with depth > 1 (replies to replies)
1487
+ if (this.features?.noNestedReplies && commentPublication.parentCid) {
1488
+ const parent = this._dbHandler.queryComment(commentPublication.parentCid);
1489
+ if (parent && parent.depth > 0) {
1490
+ return messages.ERR_NESTED_REPLIES_NOT_ALLOWED;
1491
+ }
1492
+ }
1493
+ // Post flairs validation (comment.flairs)
1494
+ if (commentPublication.flairs && commentPublication.flairs.length > 0) {
1495
+ if (!this.features?.postFlairs) {
1496
+ return messages.ERR_POST_FLAIRS_NOT_ALLOWED;
1497
+ }
1498
+ const allowedPostFlairs = this.flairs?.["post"] || [];
1499
+ for (const flair of commentPublication.flairs) {
1500
+ if (!this._isFlairInAllowedList(flair, allowedPostFlairs)) {
1501
+ return messages.ERR_POST_FLAIR_NOT_IN_ALLOWED_FLAIRS;
1502
+ }
1503
+ }
1504
+ }
1505
+ // requirePostFlairs - only for posts (depth=0)
1506
+ if (this.features?.requirePostFlairs && !commentPublication.parentCid) {
1507
+ if (!commentPublication.flairs || commentPublication.flairs.length === 0) {
1508
+ return messages.ERR_POST_FLAIRS_REQUIRED;
1509
+ }
1510
+ }
1511
+ // Author flairs validation (comment.author.flairs)
1512
+ if (commentPublication.author?.flairs && commentPublication.author.flairs.length > 0 && !this.features?.pseudonymityMode) {
1513
+ if (!this.features?.authorFlairs) {
1514
+ return messages.ERR_AUTHOR_FLAIRS_NOT_ALLOWED;
1515
+ }
1516
+ const allowedAuthorFlairs = this.flairs?.["author"] || [];
1517
+ for (const flair of commentPublication.author.flairs) {
1518
+ if (!this._isFlairInAllowedList(flair, allowedAuthorFlairs)) {
1519
+ return messages.ERR_AUTHOR_FLAIR_NOT_IN_ALLOWED_FLAIRS;
1520
+ }
1521
+ }
1522
+ }
1523
+ // requireAuthorFlairs - for all comments (posts and replies)
1524
+ if (this.features?.requireAuthorFlairs && !this.features?.pseudonymityMode) {
1525
+ if (!commentPublication.author?.flairs || commentPublication.author.flairs.length === 0) {
1526
+ return messages.ERR_AUTHOR_FLAIRS_REQUIRED;
1527
+ }
1528
+ }
1529
+ if (commentPublication.parentCid && !commentPublication.postCid)
1530
+ return messages.ERR_REPLY_HAS_NOT_DEFINED_POST_CID;
1531
+ if (commentPublication.parentCid) {
1532
+ // query parents, and make sure commentPublication.postCid is the final parent
1533
+ const parentsOfComment = this._dbHandler.queryParentsCids({ parentCid: commentPublication.parentCid });
1534
+ if (parentsOfComment[parentsOfComment.length - 1].cid !== commentPublication.postCid)
1535
+ return messages.ERR_REPLY_POST_CID_IS_NOT_PARENT_OF_REPLY;
1536
+ }
1537
+ // Validate quotedCids
1538
+ if (commentPublication.quotedCids && commentPublication.quotedCids.length > 0) {
1539
+ // Only replies can have quotedCids
1540
+ if (!commentPublication.parentCid) {
1541
+ return messages.ERR_POST_CANNOT_HAVE_QUOTED_CIDS;
1542
+ }
1543
+ const threadPostCid = commentPublication.postCid; // postCid is always defined for replies
1544
+ for (const quotedCid of commentPublication.quotedCids) {
1545
+ // 1. Check existence
1546
+ const quotedComment = this._dbHandler.queryComment(quotedCid);
1547
+ if (!quotedComment) {
1548
+ return messages.ERR_QUOTED_CID_DOES_NOT_EXIST;
1549
+ }
1550
+ // 2. Check quoted comment is under the same post
1551
+ const quotedPostCid = quotedComment.depth === 0 ? quotedComment.cid : quotedComment.postCid;
1552
+ if (quotedPostCid !== threadPostCid) {
1553
+ return messages.ERR_QUOTED_CID_NOT_UNDER_POST;
1554
+ }
1555
+ // 3. Check not pending approval
1556
+ if (quotedComment.pendingApproval) {
1557
+ return messages.ERR_QUOTED_CID_IS_PENDING_APPROVAL;
1558
+ }
1559
+ }
1560
+ }
1561
+ const isCommentDuplicate = this._dbHandler.hasCommentWithSignatureEncoded(commentPublication.signature.signature);
1562
+ if (isCommentDuplicate)
1563
+ return messages.ERR_DUPLICATE_COMMENT;
1564
+ }
1565
+ else if (request.vote) {
1566
+ const votePublication = request.vote;
1567
+ if (remeda.intersection(VotePubsubReservedFields, remeda.keys.strict(votePublication)).length > 0)
1568
+ return messages.ERR_VOTE_HAS_RESERVED_FIELD;
1569
+ if (this.features?.noUpvotes && votePublication.vote === 1)
1570
+ return messages.ERR_NOT_ALLOWED_TO_PUBLISH_UPVOTES;
1571
+ if (this.features?.noDownvotes && votePublication.vote === -1)
1572
+ return messages.ERR_NOT_ALLOWED_TO_PUBLISH_DOWNVOTES;
1573
+ const commentToVoteOn = this._dbHandler.queryComment(request.vote.commentCid);
1574
+ if (this.features?.noPostDownvotes && commentToVoteOn.depth === 0 && votePublication.vote === -1)
1575
+ return messages.ERR_NOT_ALLOWED_TO_PUBLISH_POST_DOWNVOTES;
1576
+ if (this.features?.noPostUpvotes && commentToVoteOn.depth === 0 && votePublication.vote === 1)
1577
+ return messages.ERR_NOT_ALLOWED_TO_PUBLISH_POST_UPVOTES;
1578
+ if (this.features?.noReplyDownvotes && commentToVoteOn.depth > 0 && votePublication.vote === -1)
1579
+ return messages.ERR_NOT_ALLOWED_TO_PUBLISH_REPLY_DOWNVOTES;
1580
+ if (this.features?.noReplyUpvotes && commentToVoteOn.depth > 0 && votePublication.vote === 1)
1581
+ return messages.ERR_NOT_ALLOWED_TO_PUBLISH_REPLY_UPVOTES;
1582
+ const voteAuthorSignerAddress = await getPKCAddressFromPublicKey(votePublication.signature.publicKey);
1583
+ const previousVote = this._dbHandler.queryVote(commentToVoteOn.cid, voteAuthorSignerAddress);
1584
+ if (!previousVote && votePublication.vote === 0)
1585
+ return messages.ERR_THERE_IS_NO_PREVIOUS_VOTE_TO_CANCEL;
1586
+ }
1587
+ else if (request.commentModeration) {
1588
+ const commentModerationPublication = request.commentModeration;
1589
+ if (remeda.intersection(CommentModerationReservedFields, remeda.keys.strict(commentModerationPublication)).length > 0)
1590
+ return messages.ERR_COMMENT_MODERATION_HAS_RESERVED_FIELD;
1591
+ const isAuthorMod = await this._isPublicationAuthorPartOfRoles(commentModerationPublication, ["owner", "moderator", "admin"]);
1592
+ if (!isAuthorMod)
1593
+ return messages.ERR_COMMENT_MODERATION_ATTEMPTED_WITHOUT_BEING_MODERATOR;
1594
+ const commentToBeEdited = this._dbHandler.queryComment(commentModerationPublication.commentCid); // We assume commentToBeEdited to be defined because we already tested for its existence above
1595
+ if (!commentToBeEdited)
1596
+ return messages.ERR_COMMENT_MODERATION_NO_COMMENT_TO_EDIT;
1597
+ if (isAuthorMod && commentModerationPublication.commentModeration.locked && commentToBeEdited.depth !== 0)
1598
+ return messages.ERR_COMMUNITY_COMMENT_MOD_CAN_NOT_LOCK_REPLY;
1599
+ if (isAuthorMod && commentModerationPublication.commentModeration.archived && commentToBeEdited.depth !== 0)
1600
+ return messages.ERR_COMMUNITY_COMMENT_MOD_CAN_NOT_ARCHIVE_REPLY;
1601
+ const commentModInDb = this._dbHandler.hasCommentModerationWithSignatureEncoded(commentModerationPublication.signature.signature);
1602
+ if (commentModInDb)
1603
+ return messages.ERR_DUPLICATE_COMMENT_MODERATION;
1604
+ if ("approved" in commentModerationPublication.commentModeration && !commentToBeEdited.pendingApproval)
1605
+ return messages.ERR_MOD_ATTEMPTING_TO_APPROVE_OR_DISAPPROVE_COMMENT_THAT_IS_NOT_PENDING;
1606
+ }
1607
+ else if (request.communityEdit) {
1608
+ const communityEdit = request.communityEdit;
1609
+ if (remeda.intersection(CommunityEditPublicationPubsubReservedFields, remeda.keys.strict(communityEdit)).length > 0)
1610
+ return messages.ERR_COMMUNITY_EDIT_HAS_RESERVED_FIELD;
1611
+ if (communityEdit.communityEdit.roles || communityEdit.communityEdit.address) {
1612
+ const isAuthorOwner = await this._isPublicationAuthorPartOfRoles(communityEdit, ["owner"]);
1613
+ if (!isAuthorOwner)
1614
+ return messages.ERR_COMMUNITY_EDIT_ATTEMPTED_TO_MODIFY_OWNER_EXCLUSIVE_PROPS;
1615
+ }
1616
+ const isAuthorOwnerOrAdmin = await this._isPublicationAuthorPartOfRoles(communityEdit, ["owner", "admin"]);
1617
+ if (!isAuthorOwnerOrAdmin) {
1618
+ return messages.ERR_COMMUNITY_EDIT_ATTEMPTED_TO_MODIFY_COMMUNITY_WITHOUT_BEING_OWNER_OR_ADMIN;
1619
+ }
1620
+ const allowedCommunityEditKeys = [...remeda.keys.strict(CommunityIpfsSchema.shape), "address"];
1621
+ if (remeda.difference(remeda.keys.strict(communityEdit.communityEdit), allowedCommunityEditKeys).length > 0) {
1622
+ // should only be allowed to modify public props from CommunityIpfs
1623
+ // shouldn't be able to modify settings for example
1624
+ return messages.ERR_COMMUNITY_EDIT_ATTEMPTED_TO_NON_PUBLIC_PROPS;
1625
+ }
1626
+ }
1627
+ else if (request.commentEdit) {
1628
+ const commentEditPublication = request.commentEdit;
1629
+ if (remeda.intersection(CommentEditReservedFields, remeda.keys.strict(commentEditPublication)).length > 0)
1630
+ return messages.ERR_COMMENT_EDIT_HAS_RESERVED_FIELD;
1631
+ const commentToBeEdited = this._dbHandler.queryComment(commentEditPublication.commentCid); // We assume commentToBeEdited to be defined because we already tested for its existence above
1632
+ if (!commentToBeEdited)
1633
+ return messages.ERR_COMMENT_EDIT_NO_COMMENT_TO_EDIT;
1634
+ const commentEditInDb = this._dbHandler.hasCommentEditWithSignatureEncoded(commentEditPublication.signature.signature);
1635
+ if (commentEditInDb)
1636
+ return messages.ERR_DUPLICATE_COMMENT_EDIT;
1637
+ const aliasSignerOfComment = this._dbHandler.queryPseudonymityAliasByCommentCid(commentToBeEdited.cid);
1638
+ if (aliasSignerOfComment) {
1639
+ const editSignedByOriginalAuthor = commentEditPublication.signature.publicKey === aliasSignerOfComment.originalAuthorSignerPublicKey;
1640
+ if (!editSignedByOriginalAuthor)
1641
+ return messages.ERR_COMMENT_EDIT_CAN_NOT_EDIT_COMMENT_IF_NOT_ORIGINAL_AUTHOR;
1642
+ }
1643
+ else {
1644
+ const editSignedByOriginalAuthor = commentEditPublication.signature.publicKey === commentToBeEdited.signature.publicKey;
1645
+ if (!editSignedByOriginalAuthor)
1646
+ return messages.ERR_COMMENT_EDIT_CAN_NOT_EDIT_COMMENT_IF_NOT_ORIGINAL_AUTHOR;
1647
+ }
1648
+ // Validate markdown content restrictions for comment edits
1649
+ if (this.features?.noMarkdownImages &&
1650
+ commentEditPublication.content &&
1651
+ contentContainsMarkdownImages(commentEditPublication.content))
1652
+ return messages.ERR_COMMENT_CONTENT_CONTAINS_MARKDOWN_IMAGE;
1653
+ if (this.features?.noMarkdownVideos &&
1654
+ commentEditPublication.content &&
1655
+ contentContainsMarkdownVideos(commentEditPublication.content))
1656
+ return messages.ERR_COMMENT_CONTENT_CONTAINS_MARKDOWN_VIDEO;
1657
+ if (this.features?.noMarkdownAudio &&
1658
+ commentEditPublication.content &&
1659
+ contentContainsMarkdownAudio(commentEditPublication.content))
1660
+ return messages.ERR_COMMENT_CONTENT_CONTAINS_MARKDOWN_AUDIO;
1661
+ // noSpoilers - block ALL comment edits that set spoiler=true
1662
+ if (this.features?.noSpoilers && commentEditPublication.spoiler === true)
1663
+ return messages.ERR_COMMENT_HAS_SPOILER_ENABLED;
1664
+ // noSpoilerReplies - block only reply edits that set spoiler=true
1665
+ if (this.features?.noSpoilerReplies && commentToBeEdited.depth > 0 && commentEditPublication.spoiler === true)
1666
+ return messages.ERR_REPLY_HAS_SPOILER_ENABLED;
1667
+ // Post flairs validation for comment edits
1668
+ if (commentEditPublication.flairs && commentEditPublication.flairs.length > 0) {
1669
+ if (!this.features?.postFlairs) {
1670
+ return messages.ERR_POST_FLAIRS_NOT_ALLOWED;
1671
+ }
1672
+ const allowedPostFlairs = this.flairs?.["post"] || [];
1673
+ for (const flair of commentEditPublication.flairs) {
1674
+ if (!this._isFlairInAllowedList(flair, allowedPostFlairs)) {
1675
+ return messages.ERR_POST_FLAIR_NOT_IN_ALLOWED_FLAIRS;
1676
+ }
1677
+ }
1678
+ }
1679
+ }
1680
+ return undefined;
1681
+ }
1682
+ async _parseChallengeRequestPublicationOrRespondWithFailure(request, decryptedRawString) {
1683
+ let decryptedJson;
1684
+ try {
1685
+ decryptedJson = parseJsonWithPKCErrorIfFails(decryptedRawString);
1686
+ }
1687
+ catch (e) {
1688
+ await this._publishFailedChallengeVerification({ reason: messages.ERR_REQUEST_ENCRYPTED_IS_INVALID_JSON_AFTER_DECRYPTION }, request.challengeRequestId);
1689
+ throw e;
1690
+ }
1691
+ const parseRes = DecryptedChallengeRequestSchema.loose().safeParse(decryptedJson);
1692
+ if (!parseRes.success) {
1693
+ await this._publishFailedChallengeVerification({ reason: messages.ERR_REQUEST_ENCRYPTED_HAS_INVALID_SCHEMA_AFTER_DECRYPTING }, request.challengeRequestId);
1694
+ throw new PKCError("ERR_REQUEST_ENCRYPTED_HAS_INVALID_SCHEMA_AFTER_DECRYPTING", {
1695
+ decryptedJson,
1696
+ schemaError: parseRes.error
1697
+ });
1698
+ }
1699
+ return decryptedJson;
1700
+ }
1701
+ _buildRuntimeChallengeRequestPublication({ publication, authorCommunity }) {
1702
+ return {
1703
+ ...publication,
1704
+ author: buildRuntimeAuthor({
1705
+ author: publication.author,
1706
+ signaturePublicKey: publication.signature.publicKey,
1707
+ community: authorCommunity
1708
+ })
1709
+ };
1710
+ }
1711
+ _buildRuntimeChallengeRequest({ request, authorCommunity }) {
1712
+ // This function needs to be updated everytime we add a new publication type
1713
+ const runtimeRequest = remeda.clone(request);
1714
+ if (request.comment)
1715
+ runtimeRequest.comment = this._buildRuntimeChallengeRequestPublication({
1716
+ publication: request.comment,
1717
+ authorCommunity
1718
+ });
1719
+ if (request.vote)
1720
+ runtimeRequest.vote = this._buildRuntimeChallengeRequestPublication({
1721
+ publication: request.vote,
1722
+ authorCommunity
1723
+ });
1724
+ if (request.commentEdit)
1725
+ runtimeRequest.commentEdit = this._buildRuntimeChallengeRequestPublication({
1726
+ publication: request.commentEdit,
1727
+ authorCommunity
1728
+ });
1729
+ if (request.commentModeration)
1730
+ runtimeRequest.commentModeration = this._buildRuntimeChallengeRequestPublication({
1731
+ publication: request.commentModeration,
1732
+ authorCommunity
1733
+ });
1734
+ if (request.communityEdit)
1735
+ runtimeRequest.communityEdit = this._buildRuntimeChallengeRequestPublication({
1736
+ publication: request.communityEdit,
1737
+ authorCommunity
1738
+ });
1739
+ return runtimeRequest;
1740
+ }
1741
+ async handleChallengeRequest(request, isLocalPublisher) {
1742
+ const log = Logger("pkc-js:local-community:handleChallengeRequest");
1743
+ if (this._ongoingChallengeExchanges.has(request.challengeRequestId.toString())) {
1744
+ log("Received a duplicate challenge request", request.challengeRequestId.toString());
1745
+ return; // This is a duplicate challenge request
1746
+ }
1747
+ if (isLocalPublisher) {
1748
+ // we need to mark the challenge exchange as ongoing for local publishers and skip publishing it over pubsub
1749
+ log("Marking challenge exchange as ongoing for local publisher");
1750
+ this._challengeExchangesFromLocalPublishers[request.challengeRequestId.toString()] = true;
1751
+ }
1752
+ this._ongoingChallengeExchanges.set(request.challengeRequestId.toString(), true);
1753
+ const requestSignatureValidation = await verifyChallengeRequest({ request, validateTimestampRange: true });
1754
+ if (!requestSignatureValidation.valid)
1755
+ throw new PKCError(getErrorCodeFromMessage(requestSignatureValidation.reason), {
1756
+ challengeRequest: remeda.omit(request, ["encrypted"])
1757
+ });
1758
+ const decryptedRawString = await this._decryptOrRespondWithFailure(request);
1759
+ const decryptedRequest = await this._parseChallengeRequestPublicationOrRespondWithFailure(request, decryptedRawString);
1760
+ const publicationFieldNames = remeda.keys.strict(DecryptedChallengeRequestPublicationSchema.shape);
1761
+ let publication;
1762
+ try {
1763
+ publication = derivePublicationFromChallengeRequest(decryptedRequest);
1764
+ }
1765
+ catch {
1766
+ return this._publishFailedChallengeVerification({ reason: messages.ERR_CHALLENGE_REQUEST_ENCRYPTED_HAS_NO_PUBLICATION_AFTER_DECRYPTING }, request.challengeRequestId);
1767
+ }
1768
+ let publicationCount = 0;
1769
+ publicationFieldNames.forEach((pubField) => {
1770
+ if (pubField in decryptedRequest)
1771
+ publicationCount++;
1772
+ });
1773
+ if (publicationCount > 1)
1774
+ return this._publishFailedChallengeVerification({ reason: messages.ERR_CHALLENGE_REQUEST_ENCRYPTED_HAS_MULTIPLE_PUBLICATIONS_AFTER_DECRYPTING }, request.challengeRequestId);
1775
+ // Reject deprecated wire format fields early, before signature verification
1776
+ // (these fields are never in signedPropertyNames and would otherwise fail with a generic error)
1777
+ if ("subplebbitAddress" in publication) {
1778
+ return this._publishFailedChallengeVerification({ reason: messages.ERR_PUBLICATION_USES_DEPRECATED_SUBPLEBBIT_ADDRESS }, request.challengeRequestId);
1779
+ }
1780
+ if ("communityAddress" in publication) {
1781
+ return this._publishFailedChallengeVerification({ reason: messages.ERR_PUBLICATION_USES_DEPRECATED_COMMUNITY_ADDRESS }, request.challengeRequestId);
1782
+ }
1783
+ const authorSignerAddress = await getPKCAddressFromPublicKey(publication.signature.publicKey);
1784
+ const authorDomain = getAuthorDomainFromWire(publication.author);
1785
+ // Check publication props validity
1786
+ const communityAuthor = this._dbHandler.queryCommunityAuthor(authorSignerAddress, authorDomain);
1787
+ const decryptedRequestMsg = { ...request, ...decryptedRequest };
1788
+ const decryptedRequestWithCommunityAuthor = this._buildRuntimeChallengeRequest({
1789
+ request: decryptedRequestMsg,
1790
+ authorCommunity: communityAuthor
1791
+ });
1792
+ try {
1793
+ await this._respondWithErrorIfSignatureOfPublicationIsInvalid(decryptedRequestMsg); // This function will throw an error if signature is invalid
1794
+ }
1795
+ catch (e) {
1796
+ log.error("Signature of challengerequest.publication is invalid, emitting an error event and aborting the challenge exchange", e);
1797
+ this.emit("challengerequest", decryptedRequestWithCommunityAuthor);
1798
+ return;
1799
+ }
1800
+ log.trace("Received a valid challenge request", decryptedRequestWithCommunityAuthor);
1801
+ this.emit("challengerequest", decryptedRequestWithCommunityAuthor);
1802
+ const publicationInvalidityReason = await this._checkPublicationValidity(decryptedRequestMsg, publication, communityAuthor);
1803
+ if (publicationInvalidityReason) {
1804
+ if (DUPLICATE_PUBLICATION_ERRORS.has(publicationInvalidityReason)) {
1805
+ const sig = publication.signature.signature;
1806
+ const attempts = (this._duplicatePublicationAttempts.get(sig) || 0) + 1;
1807
+ this._duplicatePublicationAttempts.set(sig, attempts);
1808
+ if (attempts <= 1) {
1809
+ return this._publishIdempotentDuplicateVerification(decryptedRequestMsg, request.challengeRequestId, publicationInvalidityReason);
1810
+ }
1811
+ }
1812
+ return this._publishFailedChallengeVerification({ reason: publicationInvalidityReason }, request.challengeRequestId);
1813
+ }
1814
+ const answerPromiseKey = decryptedRequestWithCommunityAuthor.challengeRequestId.toString();
1815
+ const getChallengeAnswers = async (challenges) => {
1816
+ // ...get challenge answers from user. e.g.:
1817
+ // step 1. community publishes challenge pubsub message with `challenges` provided in argument of `getChallengeAnswers`
1818
+ // step 2. community waits for challenge answer pubsub message with `challengeAnswers` and then returns `challengeAnswers`
1819
+ await this._publishChallenges(challenges, decryptedRequestWithCommunityAuthor);
1820
+ const challengeAnswerPromise = new Promise((resolve, reject) => this._challengeAnswerResolveReject.set(answerPromiseKey, { resolve, reject }));
1821
+ this._challengeAnswerPromises.set(answerPromiseKey, challengeAnswerPromise);
1822
+ const challengeAnswers = await this._challengeAnswerPromises.get(answerPromiseKey);
1823
+ if (!challengeAnswers)
1824
+ throw Error("Failed to retrieve challenge answers from promise. This is a critical error");
1825
+ this._cleanUpChallengeAnswerPromise(answerPromiseKey);
1826
+ return challengeAnswers;
1827
+ };
1828
+ // NOTE: we try to get challenge verification immediately after receiving challenge request
1829
+ // because some challenges are automatic and skip the challenge message
1830
+ let challengeVerification;
1831
+ try {
1832
+ challengeVerification = await getChallengeVerification(decryptedRequestWithCommunityAuthor, this, getChallengeAnswers);
1833
+ }
1834
+ catch (e) {
1835
+ // getChallengeVerification will throw if one of the getChallenge function throws, which indicates a bug with the challenge script
1836
+ // notify the community owner that that one of his challenge is misconfigured via an error event
1837
+ log.error("getChallenge failed, the community owner needs to check the challenge code. The error is: ", e);
1838
+ this.emit("error", e);
1839
+ // notify the author that his publication wasn't published because the community is misconfigured
1840
+ challengeVerification = {
1841
+ challengeSuccess: false,
1842
+ reason: `One of the community challenges is misconfigured: ${e.message}`
1843
+ };
1844
+ }
1845
+ await this._publishChallengeVerification(challengeVerification, decryptedRequestMsg, challengeVerification.pendingApproval);
1846
+ }
1847
+ _cleanUpChallengeAnswerPromise(challengeRequestIdString) {
1848
+ this._challengeAnswerPromises.delete(challengeRequestIdString);
1849
+ this._challengeAnswerResolveReject.delete(challengeRequestIdString);
1850
+ delete this._challengeExchangesFromLocalPublishers[challengeRequestIdString];
1851
+ }
1852
+ _isFlairInAllowedList(flair, allowedFlairs) {
1853
+ return allowedFlairs.some((allowed) => remeda.isDeepEqual(allowed, flair));
1854
+ }
1855
+ async _parseChallengeAnswerOrRespondWithFailure(challengeAnswer, decryptedRawString) {
1856
+ let parsedJson;
1857
+ try {
1858
+ parsedJson = parseJsonWithPKCErrorIfFails(decryptedRawString);
1859
+ }
1860
+ catch (e) {
1861
+ await this._publishFailedChallengeVerification({ reason: messages.ERR_CHALLENGE_ANSWER_IS_INVALID_JSON }, challengeAnswer.challengeRequestId);
1862
+ throw e;
1863
+ }
1864
+ try {
1865
+ return parseDecryptedChallengeAnswerWithPKCErrorIfItFails(parsedJson);
1866
+ }
1867
+ catch (e) {
1868
+ await this._publishFailedChallengeVerification({ reason: messages.ERR_CHALLENGE_ANSWER_IS_INVALID_SCHEMA }, challengeAnswer.challengeRequestId);
1869
+ throw e;
1870
+ }
1871
+ }
1872
+ async handleChallengeAnswer(challengeAnswer) {
1873
+ const log = Logger("pkc-js:local-community:handleChallengeAnswer");
1874
+ if (!this._ongoingChallengeExchanges.has(challengeAnswer.challengeRequestId.toString()))
1875
+ // Respond with error to answers without challenge request
1876
+ return this._publishFailedChallengeVerification({ reason: messages.ERR_CHALLENGE_ANSWER_WITH_NO_CHALLENGE_REQUEST }, challengeAnswer.challengeRequestId);
1877
+ const answerSignatureValidation = await verifyChallengeAnswer({ answer: challengeAnswer, validateTimestampRange: true });
1878
+ if (!answerSignatureValidation.valid) {
1879
+ this._cleanUpChallengeAnswerPromise(challengeAnswer.challengeRequestId.toString());
1880
+ this._ongoingChallengeExchanges.delete(challengeAnswer.challengeRequestId.toString());
1881
+ delete this._challengeExchangesFromLocalPublishers[challengeAnswer.challengeRequestId.toString()];
1882
+ throw new PKCError(getErrorCodeFromMessage(answerSignatureValidation.reason), { challengeAnswer });
1883
+ }
1884
+ const decryptedRawString = await this._decryptOrRespondWithFailure(challengeAnswer);
1885
+ const decryptedAnswers = await this._parseChallengeAnswerOrRespondWithFailure(challengeAnswer, decryptedRawString);
1886
+ const decryptedChallengeAnswerPubsubMessage = { ...challengeAnswer, ...decryptedAnswers };
1887
+ this.emit("challengeanswer", decryptedChallengeAnswerPubsubMessage);
1888
+ const challengeAnswerPromise = this._challengeAnswerResolveReject.get(challengeAnswer.challengeRequestId.toString());
1889
+ if (!challengeAnswerPromise)
1890
+ throw Error("The challenge answer promise is undefined, there is an issue with challenge. This is a critical error");
1891
+ challengeAnswerPromise.resolve(decryptedChallengeAnswerPubsubMessage.challengeAnswers);
1892
+ }
1893
+ async handleChallengeExchange(pubsubMsg) {
1894
+ const log = Logger("pkc-js:local-community:handleChallengeExchange");
1895
+ const timeReceived = timestamp();
1896
+ const pubsubKilobyteSize = Buffer.byteLength(pubsubMsg.data) / 1000;
1897
+ if (pubsubKilobyteSize > 80) {
1898
+ log.error(`Received a pubsub message at (${timeReceived}) with size of ${pubsubKilobyteSize}. Silently dropping it`);
1899
+ return;
1900
+ }
1901
+ let decodedMsg;
1902
+ try {
1903
+ decodedMsg = cborg.decode(pubsubMsg.data);
1904
+ }
1905
+ catch (e) {
1906
+ log.error(`Failed to decode pubsub message received at (${timeReceived})`, e.toString());
1907
+ return;
1908
+ }
1909
+ const pubsubSchemas = [
1910
+ ChallengeRequestMessageSchema.loose(),
1911
+ ChallengeMessageSchema.loose(),
1912
+ ChallengeAnswerMessageSchema.loose(),
1913
+ ChallengeVerificationMessageSchema.loose()
1914
+ ];
1915
+ let parsedPubsubMsg;
1916
+ for (const pubsubSchema of pubsubSchemas) {
1917
+ const parseRes = pubsubSchema.safeParse(decodedMsg);
1918
+ if (parseRes.success) {
1919
+ parsedPubsubMsg = parseRes.data;
1920
+ break;
1921
+ }
1922
+ }
1923
+ if (!parsedPubsubMsg) {
1924
+ log.error(`Failed to parse the schema of pubsub message received at (${timeReceived})`, decodedMsg);
1925
+ return;
1926
+ }
1927
+ if (parsedPubsubMsg.type === "CHALLENGE" || parsedPubsubMsg.type === "CHALLENGEVERIFICATION") {
1928
+ log.trace(`Received a pubsub message that is not meant to by processed by the community - ${parsedPubsubMsg.type}. Will ignore it`);
1929
+ return;
1930
+ }
1931
+ else if (parsedPubsubMsg.type === "CHALLENGEREQUEST") {
1932
+ try {
1933
+ await this.handleChallengeRequest(parsedPubsubMsg, false);
1934
+ }
1935
+ catch (e) {
1936
+ log.error(`Failed to process challenge request message received at (${timeReceived})`, e);
1937
+ this._dbHandler.rollbackTransaction();
1938
+ }
1939
+ }
1940
+ else if (parsedPubsubMsg.type === "CHALLENGEANSWER") {
1941
+ try {
1942
+ await this.handleChallengeAnswer(parsedPubsubMsg);
1943
+ }
1944
+ catch (e) {
1945
+ log.error(`Failed to process challenge answer message received at (${timeReceived})`, e);
1946
+ this._dbHandler.rollbackTransaction();
1947
+ }
1948
+ }
1949
+ }
1950
+ _calculateLocalMfsPathForCommentUpdate(postDbComment, timestampRange) {
1951
+ // TODO Can optimize the call below by only asking for timestamp field
1952
+ return ["/" + this.address, "postUpdates", timestampRange, postDbComment.cid, "update"].join("/");
1953
+ }
1954
+ async _calculateNewCommentUpdate(comment) {
1955
+ const log = Logger("pkc-js:local-community:_calculateNewCommentUpdate");
1956
+ // If we're here that means we're gonna calculate the new update and publish it
1957
+ log.trace(`Attempting to calculate new CommentUpdate for comment (${comment.cid}) on community`, this.address);
1958
+ // This comment will have the local new CommentUpdate, which we will publish to IPFS fiels
1959
+ // It includes new author.community as well as updated values in CommentUpdate (except for replies field)
1960
+ const storedCommentUpdate = this._dbHandler.queryStoredCommentUpdate(comment);
1961
+ const authorDomain = getAuthorDomainFromWire(comment.author);
1962
+ const calculatedCommentUpdate = this._dbHandler.queryCalculatedCommentUpdate({ comment, authorDomain });
1963
+ log.trace("Calculated comment update for comment", comment.cid, "on community", this.address, "with reply count", calculatedCommentUpdate.replyCount);
1964
+ const currentTimestamp = timestamp();
1965
+ const newUpdatedAt = typeof storedCommentUpdate?.updatedAt === "number" && storedCommentUpdate.updatedAt >= currentTimestamp
1966
+ ? storedCommentUpdate.updatedAt + 1
1967
+ : currentTimestamp;
1968
+ const commentUpdatePriorToSigning = {
1969
+ ...cleanUpBeforePublishing({
1970
+ ...calculatedCommentUpdate,
1971
+ updatedAt: newUpdatedAt,
1972
+ protocolVersion: env.PROTOCOL_VERSION
1973
+ })
1974
+ };
1975
+ const preloadedRepliesPages = "best";
1976
+ const inlineRepliesBudget = calculateInlineRepliesBudget({
1977
+ comment,
1978
+ commentUpdateWithoutReplies: commentUpdatePriorToSigning
1979
+ });
1980
+ const adjustedPreloadedRepliesPageSizeBytes = Math.max(inlineRepliesBudget, 1);
1981
+ const generatedRepliesPages = comment.depth === 0
1982
+ ? await this._pageGenerator.generatePostPages(comment, preloadedRepliesPages, adjustedPreloadedRepliesPageSizeBytes)
1983
+ : await this._pageGenerator.generateReplyPages(comment, preloadedRepliesPages, adjustedPreloadedRepliesPageSizeBytes);
1984
+ // we have to make sure not clean up submissions of authors by calling cleanUpBeforePublishing
1985
+ if (generatedRepliesPages) {
1986
+ if ("singlePreloadedPage" in generatedRepliesPages)
1987
+ commentUpdatePriorToSigning.replies = { pages: generatedRepliesPages.singlePreloadedPage };
1988
+ else if (generatedRepliesPages.pageCids) {
1989
+ commentUpdatePriorToSigning.replies = {
1990
+ pageCids: generatedRepliesPages.pageCids,
1991
+ pages: remeda.pick(generatedRepliesPages.pages, [preloadedRepliesPages])
1992
+ };
1993
+ }
1994
+ }
1995
+ this._addOldPageCidsToCidsToUnpin(storedCommentUpdate?.replies, commentUpdatePriorToSigning.replies).catch((err) => log.error("Failed to add old page cids of comment.replies to _cidsToUnpin", err));
1996
+ const newCommentUpdate = {
1997
+ ...commentUpdatePriorToSigning,
1998
+ signature: await signCommentUpdate({ update: commentUpdatePriorToSigning, signer: this.signer })
1999
+ };
2000
+ await this._validateCommentUpdateSignature(newCommentUpdate, comment, log);
2001
+ const newPostUpdateBucket = comment.depth === 0 ? this._postUpdatesBuckets.find((bucket) => timestamp() - bucket <= comment.timestamp) : undefined;
2002
+ const newLocalMfsPath = typeof newPostUpdateBucket === "number" ? this._calculateLocalMfsPathForCommentUpdate(comment, newPostUpdateBucket) : undefined;
2003
+ if (storedCommentUpdate?.postUpdatesBucket &&
2004
+ newLocalMfsPath &&
2005
+ newPostUpdateBucket &&
2006
+ storedCommentUpdate.postUpdatesBucket !== newPostUpdateBucket) {
2007
+ const oldPostUpdates = this._calculateLocalMfsPathForCommentUpdate(comment, storedCommentUpdate.postUpdatesBucket).replace("/update", "");
2008
+ this._mfsPathsToRemove.add(oldPostUpdates);
2009
+ }
2010
+ const newCommentUpdateDbRecord = {
2011
+ ...newCommentUpdate,
2012
+ postUpdatesBucket: newPostUpdateBucket,
2013
+ publishedToPostUpdatesMFS: false,
2014
+ insertedAt: timestamp()
2015
+ };
2016
+ if (!generatedRepliesPages)
2017
+ newCommentUpdateDbRecord.replies = undefined;
2018
+ return {
2019
+ newCommentUpdate,
2020
+ newCommentUpdateToWriteToDb: newCommentUpdateDbRecord,
2021
+ localMfsPath: newLocalMfsPath,
2022
+ pendingApproval: comment.pendingApproval
2023
+ };
2024
+ }
2025
+ async _validateCommentUpdateSignature(newCommentUpdate, comment, log) {
2026
+ // This function should be deleted at some point, once the protocol ossifies
2027
+ const verificationOpts = {
2028
+ update: newCommentUpdate,
2029
+ resolveAuthorNames: false,
2030
+ clientsManager: this._clientsManager,
2031
+ community: this,
2032
+ comment,
2033
+ validatePages: this._pkc.validatePages,
2034
+ validateUpdateSignature: true
2035
+ };
2036
+ const validation = await verifyCommentUpdate(verificationOpts);
2037
+ if (!validation.valid) {
2038
+ log.error(`CommentUpdate (${comment.cid}) signature is invalid due to (${validation.reason}). This is a critical error`);
2039
+ throw new PKCError("ERR_COMMENT_UPDATE_SIGNATURE_IS_INVALID", { validation, verificationOpts });
2040
+ }
2041
+ }
2042
+ async _listenToIncomingRequests() {
2043
+ const log = Logger("pkc-js:local-community:sync:_listenToIncomingRequests");
2044
+ // Make sure community listens to pubsub topic
2045
+ // Code below is to handle in case the ipfs node restarted and the subscription got lost or something
2046
+ const pubsubClient = this._clientsManager.getDefaultKuboPubsubClient();
2047
+ const subscribedTopics = await pubsubClient._client.pubsub.ls();
2048
+ if (!subscribedTopics.includes(this.pubsubTopicWithfallback())) {
2049
+ await this._clientsManager.pubsubUnsubscribe(this.pubsubTopicWithfallback(), this.handleChallengeExchange); // Make sure it's not hanging
2050
+ await this._clientsManager.pubsubSubscribe(this.pubsubTopicWithfallback(), this.handleChallengeExchange);
2051
+ this._clientsManager.updateKuboRpcPubsubState("waiting-challenge-requests", pubsubClient.url);
2052
+ log(`Waiting for publications on pubsub topic (${this.pubsubTopicWithfallback()})`);
2053
+ }
2054
+ }
2055
+ async _movePostUpdatesFolderToNewAddress(oldAddress, newAddress) {
2056
+ const log = Logger("pkc-js:local-community:_movePostUpdatesFolderToNewAddress");
2057
+ const kuboRpc = this._clientsManager.getDefaultKuboRpcClient();
2058
+ try {
2059
+ await kuboRpc._client.files.mv(`/${oldAddress}`, `/${newAddress}`); // Could throw
2060
+ }
2061
+ catch (e) {
2062
+ if (e instanceof Error && e.message !== "file does not exist") {
2063
+ log.error("Failed to move directory of post updates in MFS", this.address, e);
2064
+ throw e; // A critical error
2065
+ }
2066
+ }
2067
+ }
2068
+ async _updateCommentsThatNeedToBeUpdated() {
2069
+ const log = Logger(`pkc-js:local-community:_updateCommentsThatNeedToBeUpdated`);
2070
+ // Get all comments that need to be updated
2071
+ const commentsToUpdate = this._dbHandler.queryCommentsToBeUpdated();
2072
+ if (commentsToUpdate.length === 0)
2073
+ return [];
2074
+ this._communityUpdateTrigger = true;
2075
+ log(`Will update ${commentsToUpdate.length} comments in this update loop for community (${this.address})`);
2076
+ // Group by postCid
2077
+ const commentsByPostCid = remeda.groupBy.strict(commentsToUpdate, (x) => x.postCid);
2078
+ const allCommentUpdateRows = [];
2079
+ // Process different post trees in parallel
2080
+ const postLimit = pLimit(10); // Process up to 10 post trees concurrently
2081
+ const postProcessingPromises = Object.entries(commentsByPostCid).map(([postCid, commentsForPost]) => postLimit(async () => {
2082
+ try {
2083
+ // Group by depth
2084
+ const commentsByDepth = remeda.groupBy.strict(commentsForPost, (x) => x.depth);
2085
+ const depthsKeySorted = remeda.keys.strict(commentsByDepth).sort((a, b) => Number(b) - Number(a)); // Sort depths from highest to lowest
2086
+ const postUpdateRows = [];
2087
+ // Process each depth level in sequence within this post tree
2088
+ for (const depthKey of depthsKeySorted) {
2089
+ const commentsAtDepth = commentsByDepth[depthKey];
2090
+ // Process all comments at this depth in parallel
2091
+ const depthLimit = pLimit(50);
2092
+ // Calculate updates for all comments at this depth in parallel
2093
+ const depthUpdatePromises = commentsAtDepth.map((comment) => depthLimit(async () => await this._calculateNewCommentUpdate(comment)));
2094
+ // Wait for all comments at this depth to be calculated
2095
+ const depthResults = await Promise.all(depthUpdatePromises);
2096
+ // Batch write all updates for this depth to the database
2097
+ this._dbHandler.upsertCommentUpdates(depthResults.map((r) => r.newCommentUpdateToWriteToDb));
2098
+ // Add to our results
2099
+ postUpdateRows.push(...depthResults);
2100
+ }
2101
+ return postUpdateRows;
2102
+ }
2103
+ catch (error) {
2104
+ log.error(`Failed to process post tree ${postCid}:`, error);
2105
+ throw error;
2106
+ }
2107
+ }));
2108
+ // Wait for all post trees to be processed
2109
+ const postResults = await Promise.all(postProcessingPromises);
2110
+ // Collect all results
2111
+ for (const result of postResults) {
2112
+ allCommentUpdateRows.push(...result);
2113
+ }
2114
+ return allCommentUpdateRows;
2115
+ }
2116
+ async _addCommentRowToIPFS(unpinnedCommentRow, log) {
2117
+ const ipfsClient = this._clientsManager.getDefaultKuboRpcClient();
2118
+ const finalCommentIpfsJson = deriveCommentIpfsFromCommentTableRow(unpinnedCommentRow);
2119
+ const commentIpfsContent = deterministicStringify(finalCommentIpfsJson);
2120
+ const contentHash = await calculateIpfsHash(commentIpfsContent);
2121
+ if (contentHash !== unpinnedCommentRow.cid) {
2122
+ throw Error("Unable to recreate the CommentIpfs. This is a critical error");
2123
+ }
2124
+ const addRes = await retryKuboIpfsAddAndProvide({
2125
+ ipfsClient: ipfsClient._client,
2126
+ log,
2127
+ content: commentIpfsContent,
2128
+ addOptions: { pin: true },
2129
+ provideOptions: { recursive: true },
2130
+ provideInBackground: false
2131
+ });
2132
+ if (addRes.path !== unpinnedCommentRow.cid)
2133
+ throw Error("Unable to recreate the CommentIpfs. This is a critical error");
2134
+ log.trace("Pinned comment", unpinnedCommentRow.cid, "of community", this.address, "to IPFS node");
2135
+ }
2136
+ async _repinCommentsIPFSIfNeeded() {
2137
+ const log = Logger("pkc-js:local-community:start:_repinCommentsIPFSIfNeeded");
2138
+ const latestCommentCid = this._dbHandler.queryLatestCommentCid(); // latest comment ordered by id
2139
+ if (!latestCommentCid)
2140
+ return;
2141
+ const kuboRpcOrHelia = this._clientsManager.getDefaultKuboRpcClient();
2142
+ try {
2143
+ await genToArray(kuboRpcOrHelia._client.pin.ls({ paths: latestCommentCid.cid }));
2144
+ return; // the comment is already pinned, we assume the rest of the comments are so too
2145
+ }
2146
+ catch (e) {
2147
+ if (!e.message.includes("is not pinned"))
2148
+ throw e;
2149
+ }
2150
+ log("The latest comment is not pinned in the ipfs node, pkc-js will repin all existing comment ipfs for community", this.address);
2151
+ // latestCommentCid should be the last in unpinnedCommentsFromDb array, in case we throw an error on a comment before it, it does not get pinned
2152
+ const unpinnedCommentsFromDb = this._dbHandler.queryAllCommentsOrderedByIdAsc(); // we assume all comments are unpinned if latest comment is not pinned
2153
+ // In the _repinCommentIpfs method:
2154
+ const limit = pLimit(50);
2155
+ const pinningPromises = unpinnedCommentsFromDb.map((unpinnedCommentRow) => limit(async () => {
2156
+ if (unpinnedCommentRow.pendingApproval)
2157
+ return; // we don't pin comments waiting to get approved
2158
+ await this._addCommentRowToIPFS(unpinnedCommentRow, Logger("pkc-js:local-community:start:_repinCommentsIPFSIfNeeded:_addCommentRowToIPFS"));
2159
+ }));
2160
+ await Promise.all(pinningPromises);
2161
+ this._dbHandler.forceUpdateOnAllComments(); // force pkc-js to republish all comment updates
2162
+ log(`${unpinnedCommentsFromDb.length} comments' IPFS have been repinned`);
2163
+ }
2164
+ async _unpinStaleCids() {
2165
+ const log = Logger("pkc-js:local-community:sync:unpinStaleCids");
2166
+ if (this._cidsToUnPin.size > 0) {
2167
+ const sizeBefore = this._cidsToUnPin.size;
2168
+ // Create a concurrency limiter with a limit of 50
2169
+ const limit = pLimit(50);
2170
+ const kuboRpc = this._clientsManager.getDefaultKuboRpcClient();
2171
+ // Process all unpinning in parallel with concurrency limit
2172
+ await Promise.all(Array.from(this._cidsToUnPin.values()).map((cid) => limit(async () => {
2173
+ try {
2174
+ await kuboRpc._client.pin.rm(cid, { recursive: true });
2175
+ this._cidsToUnPin.delete(cid);
2176
+ }
2177
+ catch (e) {
2178
+ const error = e;
2179
+ if (error.message.startsWith("not pinned")) {
2180
+ this._cidsToUnPin.delete(cid);
2181
+ }
2182
+ else {
2183
+ log.trace("Failed to unpin cid", cid, "on community", this.address, "due to error", error);
2184
+ }
2185
+ }
2186
+ })));
2187
+ log(`unpinned ${sizeBefore - this._cidsToUnPin.size} stale cids from ipfs node for community (${this.address})`);
2188
+ }
2189
+ }
2190
+ async _rmUnneededMfsPaths() {
2191
+ const log = Logger("pkc-js:local-community:sync:_rmUnneededMfsPaths");
2192
+ if (this._mfsPathsToRemove.size > 0) {
2193
+ const toDeleteMfsPaths = Array.from(this._mfsPathsToRemove.values());
2194
+ const kuboRpc = this._clientsManager.getDefaultKuboRpcClient();
2195
+ try {
2196
+ await removeMfsFilesSafely({
2197
+ kuboRpcClient: kuboRpc,
2198
+ paths: toDeleteMfsPaths,
2199
+ log
2200
+ });
2201
+ toDeleteMfsPaths.forEach((path) => this._mfsPathsToRemove.delete(path));
2202
+ return toDeleteMfsPaths;
2203
+ }
2204
+ catch (e) {
2205
+ const error = e;
2206
+ if (error.message.includes("file does not exist"))
2207
+ return toDeleteMfsPaths; // file does not exist, we can return the paths that were not deleted
2208
+ else {
2209
+ log.error("Failed to remove paths from MFS", toDeleteMfsPaths, e);
2210
+ throw error;
2211
+ }
2212
+ }
2213
+ }
2214
+ else
2215
+ return [];
2216
+ }
2217
+ pubsubTopicWithfallback() {
2218
+ return this.pubsubTopic || this.address;
2219
+ }
2220
+ async _repinCommentUpdateIfNeeded() {
2221
+ const log = Logger("pkc-js:start:_repinCommentUpdateIfNeeded");
2222
+ // iterating on all comment updates is not efficient, we should figure out a better way
2223
+ // Most of the time we run this function, the comment updates are already written to ipfs rpeo
2224
+ const kuboRpc = this._clientsManager.getDefaultKuboRpcClient();
2225
+ try {
2226
+ await kuboRpc._client.files.stat(`/${this.address}`, { hash: true });
2227
+ return; // if the directory of this community exists, we assume all the comment updates are there
2228
+ }
2229
+ catch (e) {
2230
+ if (!e.message.includes("file does not exist"))
2231
+ throw e;
2232
+ }
2233
+ // community has no comment updates, we can return
2234
+ if (!this.lastCommentCid)
2235
+ return;
2236
+ log(`CommentUpdate directory`, this.address, "will republish all comment updates");
2237
+ this._dbHandler.forceUpdateOnAllComments(); // pkc-js will recalculate and publish all comment updates
2238
+ }
2239
+ async _syncPostUpdatesWithIpfs(commentUpdateRowsToPublishToIpfs) {
2240
+ const log = Logger("pkc-js:local-community:sync:_syncPostUpdatesFilesystemWithIpfs");
2241
+ const postUpdatesDirectory = `/${this.address}`;
2242
+ const commentUpdatesWithLocalPath = commentUpdateRowsToPublishToIpfs.filter((row) => typeof row.localMfsPath === "string");
2243
+ if (commentUpdatesWithLocalPath.length === 0)
2244
+ throw Error("No comment updates of posts to publish to postUpdates directory. This is a critical bug");
2245
+ const kuboRpc = this._clientsManager.getDefaultKuboRpcClient();
2246
+ const removedMfsPaths = await this._rmUnneededMfsPaths();
2247
+ let postUpdatesDirectoryCid;
2248
+ const BATCH_SIZE = 50;
2249
+ for (let index = 0; index < commentUpdatesWithLocalPath.length; index += BATCH_SIZE) {
2250
+ const batch = commentUpdatesWithLocalPath.slice(index, index + BATCH_SIZE);
2251
+ await Promise.all(batch.map(async (row) => {
2252
+ const { localMfsPath, newCommentUpdate } = row;
2253
+ const content = deterministicStringify(newCommentUpdate);
2254
+ await writeKuboFilesWithTimeout({
2255
+ ipfsClient: kuboRpc._client,
2256
+ log,
2257
+ path: localMfsPath,
2258
+ content,
2259
+ options: {
2260
+ create: true,
2261
+ truncate: true,
2262
+ parents: true,
2263
+ flush: false
2264
+ }
2265
+ });
2266
+ removedMfsPaths.push(localMfsPath);
2267
+ }));
2268
+ postUpdatesDirectoryCid = await kuboRpc._client.files.flush(postUpdatesDirectory);
2269
+ }
2270
+ const postUpdatesDirectoryCidString = postUpdatesDirectoryCid?.toString();
2271
+ log("Community", this.address, "Synced", commentUpdatesWithLocalPath.length, "post CommentUpdates", "with MFS postUpdates directory", postUpdatesDirectoryCidString);
2272
+ this._dbHandler.markCommentsAsPublishedToPostUpdates(commentUpdateRowsToPublishToIpfs.map((row) => row.newCommentUpdate.cid));
2273
+ }
2274
+ async _adjustPostUpdatesBucketsIfNeeded() {
2275
+ if (!this.postUpdates)
2276
+ return;
2277
+ // Look for posts whose buckets should be changed
2278
+ const log = Logger("pkc-js:local-community:start:_adjustPostUpdatesBucketsIfNeeded");
2279
+ const postsWithOutdatedPostUpdateBucket = this._dbHandler.queryPostsWithOutdatedBuckets(this._postUpdatesBuckets);
2280
+ if (postsWithOutdatedPostUpdateBucket.length === 0)
2281
+ return;
2282
+ this._dbHandler.forceUpdateOnAllCommentsWithCid(postsWithOutdatedPostUpdateBucket.map((post) => post.cid));
2283
+ log(`Found ${postsWithOutdatedPostUpdateBucket.length} posts with outdated buckets and forced their updates`);
2284
+ }
2285
+ async _cleanUpIpfsRepoRarely(force = false) {
2286
+ const log = Logger("pkc-js:local-community:syncIpnsWithDb:_cleanUpIpfsRepoRarely");
2287
+ if (Math.random() < 0.00001 || force) {
2288
+ let gcCids = 0;
2289
+ const kuboRpc = this._clientsManager.getDefaultKuboRpcClient();
2290
+ try {
2291
+ for await (const res of kuboRpc._client.repo.gc({ quiet: true })) {
2292
+ if (res.cid)
2293
+ gcCids++;
2294
+ else
2295
+ log.error("Failed to GC ipfs repo due to error", res.err);
2296
+ }
2297
+ }
2298
+ catch (e) {
2299
+ log.error("Failed to GC ipfs repo due to error", e);
2300
+ }
2301
+ log("GC cleaned", gcCids, "cids out of the IPFS node");
2302
+ }
2303
+ }
2304
+ async _providePubsubTopicRoutingCidsIfNeeded(force = false) {
2305
+ const log = Logger("pkc-js:local-community:_providePubsubTopicRoutingCidsIfNeeded");
2306
+ const reprovideIntervalMs = 6 * 60 * 60 * 1000;
2307
+ const now = Date.now();
2308
+ if (!force && this._lastPubsubTopicRoutingProvideAt && now - this._lastPubsubTopicRoutingProvideAt < reprovideIntervalMs)
2309
+ return;
2310
+ const pubsubTopic = this.pubsubTopicWithfallback();
2311
+ const topics = [pubsubTopic, this.ipnsPubsubTopic].filter((topic) => typeof topic === "string");
2312
+ if (topics.length === 0)
2313
+ return;
2314
+ this._lastPubsubTopicRoutingProvideAt = now;
2315
+ const kuboRpcClient = this._clientsManager.getDefaultKuboRpcClient()._client;
2316
+ for (const topic of topics) {
2317
+ try {
2318
+ await retryKuboBlockPutPinAndProvidePubsubTopic({
2319
+ ipfsClient: kuboRpcClient,
2320
+ log,
2321
+ pubsubTopic: topic
2322
+ });
2323
+ }
2324
+ catch (error) {
2325
+ log.error("Failed to reprovide pubsub topic routing block", { topic, error });
2326
+ }
2327
+ }
2328
+ }
2329
+ async _addAllCidsUnderPurgedCommentToBeRemoved(purgedCommentAndCommentUpdate) {
2330
+ const log = Logger("pkc-js:_addAllCidsUnderPurgedCommentToBeRemoved");
2331
+ this._cidsToUnPin.add(purgedCommentAndCommentUpdate.commentTableRow.cid);
2332
+ this._blocksToRm.push(purgedCommentAndCommentUpdate.commentTableRow.cid);
2333
+ if (typeof purgedCommentAndCommentUpdate.commentUpdateTableRow?.postUpdatesBucket === "number") {
2334
+ const localCommentUpdatePath = this._calculateLocalMfsPathForCommentUpdate(purgedCommentAndCommentUpdate.commentTableRow, purgedCommentAndCommentUpdate.commentUpdateTableRow?.postUpdatesBucket);
2335
+ this._mfsPathsToRemove.add(localCommentUpdatePath);
2336
+ }
2337
+ if (purgedCommentAndCommentUpdate?.commentUpdateTableRow?.replies)
2338
+ await this._addOldPageCidsToCidsToUnpin(purgedCommentAndCommentUpdate?.commentUpdateTableRow?.replies, undefined, true).catch((err) => log.error("Failed to add purged page cids to be unpinned and removed", err));
2339
+ }
2340
+ async _purgeDisapprovedCommentsOlderThan() {
2341
+ if (typeof this.settings?.purgeDisapprovedCommentsOlderThan !== "number")
2342
+ return;
2343
+ const log = Logger("pkc-js:local-community:_purgeDisapprovedCommentsOlderThan");
2344
+ const purgedComments = this._dbHandler.purgeDisapprovedCommentsOlderThan(this.settings.purgeDisapprovedCommentsOlderThan);
2345
+ if (!purgedComments || purgedComments.length === 0)
2346
+ return;
2347
+ log("Purged disapproved comments", purgedComments, "because retention time has passed and it's time to purge them from DB and pages");
2348
+ // need to clear out any commentUpdate.postUpdatesBucket
2349
+ // need to clear out any comment.cid
2350
+ // need to clear out any commentUpdate.replies
2351
+ for (const purgedComment of purgedComments)
2352
+ for (const purgedCommentAndCommentUpdate of purgedComment.purgedTableRows)
2353
+ await this._addAllCidsUnderPurgedCommentToBeRemoved(purgedCommentAndCommentUpdate);
2354
+ if (this._mfsPathsToRemove.size > 0)
2355
+ await this._rmUnneededMfsPaths();
2356
+ if (this.updateCid) {
2357
+ this._blocksToRm.push(this.updateCid); // we need to remove current updateCid which references purged comments
2358
+ this._cidsToUnPin.add(this.updateCid);
2359
+ }
2360
+ }
2361
+ async syncIpnsWithDb() {
2362
+ const log = Logger("pkc-js:local-community:sync");
2363
+ const kuboRpc = this._clientsManager.getDefaultKuboRpcClient();
2364
+ try {
2365
+ await this._listenToIncomingRequests();
2366
+ await this._providePubsubTopicRoutingCidsIfNeeded();
2367
+ await this._adjustPostUpdatesBucketsIfNeeded();
2368
+ this._setStartedStateWithEmission("publishing-ipns");
2369
+ this._clientsManager.updateKuboRpcState("publishing-ipns", kuboRpc.url);
2370
+ await this._purgeDisapprovedCommentsOlderThan();
2371
+ const commentUpdateRows = await this._updateCommentsThatNeedToBeUpdated();
2372
+ this._requireCommunityUpdateIfModQueueChanged();
2373
+ await this.updateCommunityIpnsIfNeeded(commentUpdateRows);
2374
+ await this._cleanUpIpfsRepoRarely();
2375
+ }
2376
+ catch (e) {
2377
+ //@ts-expect-error
2378
+ e.details = { ...e.details, communityAddress: this.address };
2379
+ const errorTyped = e;
2380
+ this._setStartedStateWithEmission("failed");
2381
+ this._clientsManager.updateKuboRpcState("stopped", kuboRpc.url);
2382
+ log.error(`Failed to sync community`, this.address, `due to error,`, errorTyped, "Error.message", errorTyped.message, "Error keys", Object.keys(errorTyped));
2383
+ throw e;
2384
+ }
2385
+ }
2386
+ async _assertDomainResolvesCorrectly(newAddressAsDomain) {
2387
+ if (isStringDomain(newAddressAsDomain)) {
2388
+ const resolvedIpnsFromNewDomain = await this._clientsManager.resolveCommunityNameIfNeeded({
2389
+ communityAddress: newAddressAsDomain
2390
+ });
2391
+ if (resolvedIpnsFromNewDomain !== this.signer.address)
2392
+ throw new PKCError("ERR_DOMAIN_COMMUNITY_ADDRESS_TXT_RECORD_POINT_TO_DIFFERENT_ADDRESS", {
2393
+ currentCommunityAddress: this.address,
2394
+ newAddressAsDomain,
2395
+ resolvedIpnsFromNewDomain,
2396
+ signerAddress: this.signer.address,
2397
+ started: this.started
2398
+ });
2399
+ }
2400
+ }
2401
+ async _initSignerProps(newSignerProps) {
2402
+ this.signer = new SignerWithPublicKeyAddress(newSignerProps);
2403
+ if (!this.signer?.ipfsKey?.byteLength || this.signer?.ipfsKey?.byteLength <= 0)
2404
+ this.signer.ipfsKey = new Uint8Array(await getIpfsKeyFromPrivateKey(this.signer.privateKey));
2405
+ if (!this.signer.ipnsKeyName)
2406
+ this.signer.ipnsKeyName = this.signer.address;
2407
+ if (!this.signer.publicKey)
2408
+ this.signer.publicKey = await getPublicKeyFromPrivateKey(this.signer.privateKey);
2409
+ this.encryption = {
2410
+ type: "ed25519-aes-gcm",
2411
+ publicKey: this.signer.publicKey
2412
+ };
2413
+ }
2414
+ async _publishLoop(syncIntervalMs) {
2415
+ const log = Logger("pkc-js:local-community:_publishLoop");
2416
+ // we need to continue the loop if there's at least one pending edit
2417
+ const shouldStopPublishLoop = () => {
2418
+ return this.state !== "started" || (this._stopHasBeenCalled && this._pendingEditProps.length === 0);
2419
+ };
2420
+ const waitUntilNextSync = async () => {
2421
+ const doneWithLoopTime = Date.now();
2422
+ await new Promise((resolve) => {
2423
+ const checkInterval = setInterval(() => {
2424
+ const syncIntervalMsPassedSinceDoneWithLoop = Date.now() - doneWithLoopTime >= syncIntervalMs;
2425
+ this._calculateLatestUpdateTrigger(); // will update this._communityUpdateTrigger
2426
+ if (this._communityUpdateTrigger || shouldStopPublishLoop() || syncIntervalMsPassedSinceDoneWithLoop) {
2427
+ clearInterval(checkInterval);
2428
+ resolve(1);
2429
+ }
2430
+ }, 100);
2431
+ });
2432
+ };
2433
+ while (!shouldStopPublishLoop()) {
2434
+ try {
2435
+ await this.syncIpnsWithDb();
2436
+ }
2437
+ catch (e) {
2438
+ this.emit("error", e);
2439
+ }
2440
+ finally {
2441
+ await waitUntilNextSync();
2442
+ }
2443
+ }
2444
+ log("Stopping the publishing loop of community", this.address);
2445
+ }
2446
+ async _initBeforeStarting() {
2447
+ this.protocolVersion = env.PROTOCOL_VERSION;
2448
+ if (!this.signer?.address)
2449
+ throw new PKCError("ERR_COMMUNITY_SIGNER_NOT_DEFINED");
2450
+ if (!this._challengeAnswerPromises)
2451
+ this._challengeAnswerPromises = new LRUCache({
2452
+ max: 1000,
2453
+ ttl: 600000
2454
+ });
2455
+ if (!this._challengeAnswerResolveReject)
2456
+ this._challengeAnswerResolveReject = new LRUCache({
2457
+ max: 1000,
2458
+ ttl: 600000
2459
+ });
2460
+ if (!this._ongoingChallengeExchanges)
2461
+ this._ongoingChallengeExchanges = new LRUCache({
2462
+ max: 1000,
2463
+ ttl: 600000
2464
+ });
2465
+ if (!this._duplicatePublicationAttempts)
2466
+ this._duplicatePublicationAttempts = new LRUCache({
2467
+ max: 1000,
2468
+ ttl: 600000
2469
+ });
2470
+ await this._dbHandler.initDbIfNeeded();
2471
+ }
2472
+ async _parseRolesToEdit(newRawRoles) {
2473
+ for (const [roleAddress, roleValue] of Object.entries(newRawRoles)) {
2474
+ if (roleValue === undefined || roleValue === null)
2475
+ continue; // skip removals
2476
+ if (isStringDomain(roleAddress)) {
2477
+ let resolved;
2478
+ try {
2479
+ resolved = await this._clientsManager.resolveAuthorNameIfNeeded({ authorAddress: roleAddress });
2480
+ }
2481
+ catch {
2482
+ resolved = null;
2483
+ }
2484
+ if (!resolved)
2485
+ throw new PKCError("ERR_ROLE_ADDRESS_DOMAIN_COULD_NOT_BE_RESOLVED", { roleAddress });
2486
+ }
2487
+ }
2488
+ return remeda.omitBy(newRawRoles, (val, key) => val === undefined || val === null);
2489
+ }
2490
+ async _parseChallengesToEdit(newChallengeSettings) {
2491
+ return {
2492
+ challenges: await Promise.all(newChallengeSettings.map((cs) => getCommunityChallengeFromCommunityChallengeSettings(cs, this._pkc))),
2493
+ _usingDefaultChallenge: remeda.isDeepEqual(newChallengeSettings, this._defaultCommunityChallenges)
2494
+ };
2495
+ }
2496
+ async _validateNewAddressBeforeEditing(newAddress, log) {
2497
+ if (doesDomainAddressHaveCapitalLetter(newAddress))
2498
+ throw new PKCError("ERR_COMMUNITY_NAME_HAS_CAPITAL_LETTER", { communityAddress: newAddress });
2499
+ // Check if any existing community (other than this one) already has an equivalent address
2500
+ // This handles both exact matches and .eth/.bso alias equivalence
2501
+ const existingEquivalent = this._pkc.communities.find((existing) => areEquivalentCommunityAddresses(existing, newAddress) && !areEquivalentCommunityAddresses(existing, this.address));
2502
+ if (existingEquivalent)
2503
+ throw new PKCError("ERR_COMMUNITY_OWNER_ATTEMPTED_EDIT_NEW_ADDRESS_THAT_ALREADY_EXISTS", {
2504
+ currentCommunityAddress: this.address,
2505
+ newCommunityAddress: newAddress,
2506
+ currentSubs: this._pkc.communities
2507
+ });
2508
+ this._assertDomainResolvesCorrectly(newAddress).catch((err) => {
2509
+ log.error(err);
2510
+ this.emit("error", err);
2511
+ });
2512
+ }
2513
+ async _editPropsOnStartedCommunity(parsedEditOptions) {
2514
+ // 'this' is the started community with state="started"
2515
+ // this._pkc._startedCommunities[this.address] === this
2516
+ const log = Logger("pkc-js:local-community:start:editPropsOnStartedCommunity");
2517
+ const oldAddress = remeda.clone(this.address);
2518
+ if (typeof parsedEditOptions.address === "string" && this.address !== parsedEditOptions.address) {
2519
+ await this._validateNewAddressBeforeEditing(parsedEditOptions.address, log);
2520
+ log(`Attempting to edit community.address from ${oldAddress} to ${parsedEditOptions.address}. We will stop community first`);
2521
+ await this.stop();
2522
+ await this._dbHandler.changeDbFilename(oldAddress, parsedEditOptions.address);
2523
+ this.setAddress(parsedEditOptions.address);
2524
+ await this._dbHandler.initDbIfNeeded();
2525
+ await this.start();
2526
+ await this._movePostUpdatesFolderToNewAddress(oldAddress, parsedEditOptions.address);
2527
+ }
2528
+ const uniqueEditId = sha256(deterministicStringify(parsedEditOptions));
2529
+ this._pendingEditProps.push({ ...parsedEditOptions, editId: uniqueEditId });
2530
+ if (this.updateCid)
2531
+ await this.initInternalCommunityAfterFirstUpdateNoMerge({
2532
+ ...this.toJSONInternalAfterFirstUpdate(),
2533
+ ...parsedEditOptions,
2534
+ _internalStateUpdateId: uniqueEditId
2535
+ });
2536
+ else
2537
+ await this.initInternalCommunityBeforeFirstUpdateNoMerge({
2538
+ ...this.toJSONInternalBeforeFirstUpdate(),
2539
+ ...parsedEditOptions,
2540
+ _internalStateUpdateId: uniqueEditId
2541
+ });
2542
+ this._communityUpdateTrigger = true;
2543
+ log(`Community (${this.address}) props (${remeda.keys.strict(parsedEditOptions)}) has been edited. Will be including edited props in next update: `, remeda.pick(this, remeda.keys.strict(parsedEditOptions)));
2544
+ this.emit("update", this);
2545
+ if (this.address !== oldAddress) {
2546
+ trackStartedCommunity(this._pkc, this);
2547
+ syncCommunityRegistryEntry(processStartedCommunities, this);
2548
+ }
2549
+ return this;
2550
+ }
2551
+ async _editPropsOnNotStartedCommunity(parsedEditOptions) {
2552
+ // sceneario 3, the community is not running anywhere, we need to edit the db and update this instance
2553
+ const log = Logger("pkc-js:local-community:edit:editPropsOnNotStartedCommunity");
2554
+ const oldAddress = remeda.clone(this.address);
2555
+ await this.initDbHandlerIfNeeded();
2556
+ await this._dbHandler.initDbIfNeeded();
2557
+ if (typeof parsedEditOptions.address === "string" && this.address !== parsedEditOptions.address) {
2558
+ await this._validateNewAddressBeforeEditing(parsedEditOptions.address, log);
2559
+ log(`Attempting to edit community.address from ${oldAddress} to ${parsedEditOptions.address}`);
2560
+ // in this sceneario we're editing a community that's not started anywhere
2561
+ log("will rename the community", this.address, "db in edit() because the community is not being ran anywhere else");
2562
+ await this._movePostUpdatesFolderToNewAddress(this.address, parsedEditOptions.address);
2563
+ this._dbHandler.destoryConnection();
2564
+ await this._dbHandler.changeDbFilename(this.address, parsedEditOptions.address);
2565
+ await this._dbHandler.initDbIfNeeded();
2566
+ this.setAddress(parsedEditOptions.address);
2567
+ }
2568
+ const mergedInternalState = await this._updateDbInternalState(parsedEditOptions);
2569
+ if ("updatedAt" in mergedInternalState && mergedInternalState.updatedAt)
2570
+ await this.initInternalCommunityAfterFirstUpdateNoMerge(mergedInternalState);
2571
+ else
2572
+ await this.initInternalCommunityBeforeFirstUpdateNoMerge(mergedInternalState);
2573
+ await this._dbHandler.destoryConnection();
2574
+ this.emit("update", this);
2575
+ return this;
2576
+ }
2577
+ async edit(newCommunityOptions) {
2578
+ // scenearios
2579
+ // 1 - calling edit() on a community instance that's not running, but the it's started in pkc._startedCommunities (should edit the started community)
2580
+ // 2 - calling edit() on a community that's started in another process (should throw)
2581
+ // 3 - calling edit() on a community that's not started (should load db and edit it)
2582
+ // 4 - calling edit() on the community that's started (should edit the started community)
2583
+ const startedCommunity = ((findStartedCommunity(this._pkc, { address: this.address }) ||
2584
+ findCommunityInRegistry(processStartedCommunities, { address: this.address })));
2585
+ if (startedCommunity && this.state !== "started") {
2586
+ // sceneario 1
2587
+ const editRes = await startedCommunity.edit(newCommunityOptions);
2588
+ this.setAddress(editRes.address); // need to force an update of the address for this instance
2589
+ await this._updateInstancePropsWithStartedCommunityOrDb();
2590
+ return this;
2591
+ }
2592
+ await this.initDbHandlerIfNeeded();
2593
+ await this._updateStartedValue();
2594
+ if (this.started && this.state !== "started") {
2595
+ // sceneario 2
2596
+ this._dbHandler.destoryConnection();
2597
+ throw new PKCError("ERR_CAN_NOT_EDIT_A_LOCAL_COMMUNITY_THAT_IS_ALREADY_STARTED_IN_ANOTHER_PROCESS", {
2598
+ address: this.address,
2599
+ dataPath: this._pkc.dataPath
2600
+ });
2601
+ }
2602
+ const parsedEditOptions = parseCommunityEditOptionsSchemaWithPKCErrorIfItFails(newCommunityOptions);
2603
+ // Convert backward-compat address → name for wire format when address is a domain
2604
+ const editWithDerivedName = typeof parsedEditOptions.address === "string" && isStringDomain(parsedEditOptions.address)
2605
+ ? { ...parsedEditOptions, name: parsedEditOptions.address }
2606
+ : parsedEditOptions;
2607
+ const newInternalProps = {
2608
+ ...(editWithDerivedName.roles ? { roles: await this._parseRolesToEdit(editWithDerivedName.roles) } : undefined),
2609
+ ...(editWithDerivedName?.settings?.challenges
2610
+ ? await this._parseChallengesToEdit(editWithDerivedName.settings.challenges)
2611
+ : undefined)
2612
+ };
2613
+ const newProps = {
2614
+ ...remeda.omit(editWithDerivedName, ["roles"]), // we omit here to make tsc shut up
2615
+ ...newInternalProps
2616
+ };
2617
+ if (!this.started && !startedCommunity) {
2618
+ // sceneario 3
2619
+ return this._editPropsOnNotStartedCommunity(newProps);
2620
+ }
2621
+ if (findStartedCommunity(this._pkc, { address: this.address }) === this) {
2622
+ // sceneario 4
2623
+ return this._editPropsOnStartedCommunity(newProps);
2624
+ }
2625
+ throw new Error("Can't edit a community that's started in another process");
2626
+ }
2627
+ async start() {
2628
+ const log = Logger("pkc-js:local-community:start");
2629
+ if (this.state === "updating")
2630
+ throw new PKCError("ERR_NEED_TO_STOP_UPDATING_COMMUNITY_BEFORE_STARTING", { address: this.address });
2631
+ this._stopHasBeenCalled = false;
2632
+ this._firstUpdateAfterStart = true;
2633
+ if (!this._clientsManager.getDefaultKuboRpcClientOrHelia())
2634
+ throw Error("You need to define an IPFS client in your pkc instance to be able to start a local community");
2635
+ await this.initDbHandlerIfNeeded();
2636
+ await this._updateStartedValue();
2637
+ if (this.started ||
2638
+ findStartedCommunity(this._pkc, { address: this.address }) ||
2639
+ findCommunityInRegistry(processStartedCommunities, { address: this.address }))
2640
+ throw new PKCError("ERR_COMMUNITY_ALREADY_STARTED", { address: this.address });
2641
+ try {
2642
+ await this._initBeforeStarting();
2643
+ // update started value twice because it could be started prior lockCommunityStart
2644
+ this._setState("started");
2645
+ await this._updateStartedValue();
2646
+ await this._dbHandler.lockCommunityStart(); // Will throw if community is locked already
2647
+ trackStartedCommunity(this._pkc, this);
2648
+ syncCommunityRegistryEntry(processStartedCommunities, this);
2649
+ await this._updateStartedValue();
2650
+ await this._dbHandler.initDbIfNeeded();
2651
+ await this._dbHandler.createOrMigrateTablesIfNeeded();
2652
+ await this._updateInstanceStateWithDbState(); // sync in-memory state after potential migration
2653
+ await this._setChallengesToDefaultIfNotDefined(log);
2654
+ // Import community keys onto ipfs node
2655
+ await this._importCommunitySignerIntoIpfsIfNeeded();
2656
+ await this._providePubsubTopicRoutingCidsIfNeeded(true);
2657
+ this._communityUpdateTrigger = true;
2658
+ this._setStartedStateWithEmission("publishing-ipns");
2659
+ await this._repinCommentsIPFSIfNeeded();
2660
+ await this._repinCommentUpdateIfNeeded();
2661
+ await this._listenToIncomingRequests();
2662
+ this.challenges = await Promise.all(this.settings.challenges.map((cs) => getCommunityChallengeFromCommunityChallengeSettings(cs, this._pkc))); // make sure community.challenges is using latest props from settings.challenges
2663
+ }
2664
+ catch (e) {
2665
+ await this.stop(); // Make sure to reset the community state
2666
+ //@ts-expect-error
2667
+ e.details = { ...e.details, subAddress: this.address };
2668
+ throw e;
2669
+ }
2670
+ this._publishLoopPromise = this._publishLoop(this._pkc.publishInterval).catch((err) => {
2671
+ log.error(err);
2672
+ this.emit("error", err);
2673
+ });
2674
+ }
2675
+ async _initMirroringStartedOrUpdatingCommunity(startedCommunity) {
2676
+ const updatingStateChangeListener = (newState) => {
2677
+ this._setUpdatingStateWithEventEmissionIfNewState(newState);
2678
+ };
2679
+ const startedStateChangeListener = (newState) => {
2680
+ this._setStartedStateWithEmission(newState);
2681
+ updatingStateChangeListener(newState);
2682
+ };
2683
+ const updateListener = async (updatedCommunity) => {
2684
+ const startedCommunity = updatedCommunity;
2685
+ if (startedCommunity.updateCid)
2686
+ await this.initInternalCommunityAfterFirstUpdateNoMerge(startedCommunity.toJSONInternalAfterFirstUpdate());
2687
+ else
2688
+ await this.initInternalCommunityBeforeFirstUpdateNoMerge(startedCommunity.toJSONInternalBeforeFirstUpdate());
2689
+ this.started = startedCommunity.started;
2690
+ this.emit("update", this);
2691
+ };
2692
+ const stateChangeListener = async (newState) => {
2693
+ // pkc._startedCommunities[address].stop() has been called, we need to stop mirroring
2694
+ // or pkc._updatingCommunities[address].stop(), we need to stop mirroring
2695
+ if (newState === "stopped")
2696
+ await this._cleanUpMirroredStartedOrUpdatingCommunity();
2697
+ };
2698
+ this._mirroredStartedOrUpdatingCommunity = {
2699
+ community: startedCommunity,
2700
+ updatingstatechange: updatingStateChangeListener,
2701
+ update: updateListener,
2702
+ statechange: stateChangeListener,
2703
+ startedstatechange: startedStateChangeListener,
2704
+ error: (err) => this.emit("error", err),
2705
+ challengerequest: (challengeRequest) => this.emit("challengerequest", challengeRequest),
2706
+ challengeverification: (challengeVerification) => this.emit("challengeverification", challengeVerification),
2707
+ challengeanswer: (challengeAnswer) => this.emit("challengeanswer", challengeAnswer),
2708
+ challenge: (challenge) => this.emit("challenge", challenge)
2709
+ };
2710
+ this._mirroredStartedOrUpdatingCommunity.community.on("update", this._mirroredStartedOrUpdatingCommunity.update);
2711
+ this._mirroredStartedOrUpdatingCommunity.community.on("startedstatechange", this._mirroredStartedOrUpdatingCommunity.startedstatechange);
2712
+ this._mirroredStartedOrUpdatingCommunity.community.on("updatingstatechange", this._mirroredStartedOrUpdatingCommunity.updatingstatechange);
2713
+ this._mirroredStartedOrUpdatingCommunity.community.on("statechange", this._mirroredStartedOrUpdatingCommunity.statechange);
2714
+ this._mirroredStartedOrUpdatingCommunity.community.on("error", this._mirroredStartedOrUpdatingCommunity.error);
2715
+ this._mirroredStartedOrUpdatingCommunity.community.on("challengerequest", this._mirroredStartedOrUpdatingCommunity.challengerequest);
2716
+ this._mirroredStartedOrUpdatingCommunity.community.on("challengeverification", this._mirroredStartedOrUpdatingCommunity.challengeverification);
2717
+ this._mirroredStartedOrUpdatingCommunity.community.on("challengeanswer", this._mirroredStartedOrUpdatingCommunity.challengeanswer);
2718
+ this._mirroredStartedOrUpdatingCommunity.community.on("challenge", this._mirroredStartedOrUpdatingCommunity.challenge);
2719
+ const clientKeys = remeda.keys.strict(this.clients);
2720
+ for (const clientType of clientKeys)
2721
+ if (this.clients[clientType])
2722
+ for (const clientUrl of Object.keys(this.clients[clientType]))
2723
+ if (clientUrl in this._mirroredStartedOrUpdatingCommunity.community.clients[clientType])
2724
+ this.clients[clientType][clientUrl].mirror(this._mirroredStartedOrUpdatingCommunity.community.clients[clientType][clientUrl]);
2725
+ if (startedCommunity.updateCid)
2726
+ await this.initInternalCommunityAfterFirstUpdateNoMerge(startedCommunity.toJSONInternalAfterFirstUpdate());
2727
+ else
2728
+ await this.initInternalCommunityBeforeFirstUpdateNoMerge(startedCommunity.toJSONInternalBeforeFirstUpdate());
2729
+ this.emit("update", this);
2730
+ }
2731
+ async _cleanUpMirroredStartedOrUpdatingCommunity() {
2732
+ if (!this._mirroredStartedOrUpdatingCommunity)
2733
+ return;
2734
+ this._mirroredStartedOrUpdatingCommunity.community.removeListener("update", this._mirroredStartedOrUpdatingCommunity.update);
2735
+ this._mirroredStartedOrUpdatingCommunity.community.removeListener("updatingstatechange", this._mirroredStartedOrUpdatingCommunity.updatingstatechange);
2736
+ this._mirroredStartedOrUpdatingCommunity.community.removeListener("startedstatechange", this._mirroredStartedOrUpdatingCommunity.startedstatechange);
2737
+ this._mirroredStartedOrUpdatingCommunity.community.removeListener("statechange", this._mirroredStartedOrUpdatingCommunity.statechange);
2738
+ this._mirroredStartedOrUpdatingCommunity.community.removeListener("error", this._mirroredStartedOrUpdatingCommunity.error);
2739
+ this._mirroredStartedOrUpdatingCommunity.community.removeListener("challengerequest", this._mirroredStartedOrUpdatingCommunity.challengerequest);
2740
+ this._mirroredStartedOrUpdatingCommunity.community.removeListener("challengeverification", this._mirroredStartedOrUpdatingCommunity.challengeverification);
2741
+ this._mirroredStartedOrUpdatingCommunity.community.removeListener("challengeanswer", this._mirroredStartedOrUpdatingCommunity.challengeanswer);
2742
+ this._mirroredStartedOrUpdatingCommunity.community.removeListener("challenge", this._mirroredStartedOrUpdatingCommunity.challenge);
2743
+ const clientKeys = remeda.keys.strict(this.clients);
2744
+ for (const clientType of clientKeys)
2745
+ if (this.clients[clientType])
2746
+ for (const clientUrl of Object.keys(this.clients[clientType]))
2747
+ this.clients[clientType][clientUrl].unmirror();
2748
+ this._mirroredStartedOrUpdatingCommunity = undefined;
2749
+ }
2750
+ async _updateOnce() {
2751
+ const log = Logger("pkc-js:local-community:_updateOnce");
2752
+ await this.initDbHandlerIfNeeded();
2753
+ await this._updateStartedValue();
2754
+ const startedCommunity = ((findStartedCommunity(this._pkc, { address: this.address }) ||
2755
+ findCommunityInRegistry(processStartedCommunities, { address: this.address })));
2756
+ if (this._mirroredStartedOrUpdatingCommunity)
2757
+ return; // we're already mirroring a started or updating community
2758
+ else if (startedCommunity) {
2759
+ // let's mirror the started community in this process
2760
+ await this._initMirroringStartedOrUpdatingCommunity(startedCommunity);
2761
+ untrackUpdatingCommunity(this._pkc, this);
2762
+ return;
2763
+ }
2764
+ else {
2765
+ const updatingCommunity = findUpdatingCommunity(this._pkc, { address: this.address });
2766
+ if (updatingCommunity instanceof LocalCommunity && updatingCommunity !== this) {
2767
+ // different instance is updating, let's mirror it
2768
+ await this._initMirroringStartedOrUpdatingCommunity(updatingCommunity);
2769
+ return;
2770
+ }
2771
+ // this community is not started or updated anywhere, but maybe another process will call edit() on it
2772
+ trackUpdatingCommunity(this._pkc, this);
2773
+ const oldUpdateId = remeda.clone(this._internalStateUpdateId);
2774
+ await this._updateInstancePropsWithStartedCommunityOrDb(); // will update this instance props with DB
2775
+ if (this._internalStateUpdateId !== oldUpdateId) {
2776
+ log(`Local Community (${this.address}) received a new update from db with updatedAt (${this.updatedAt}). Will emit an update event`);
2777
+ this._changeStateEmitEventEmitStateChangeEvent({
2778
+ event: { name: "update", args: [this] },
2779
+ newUpdatingState: "succeeded"
2780
+ });
2781
+ }
2782
+ }
2783
+ }
2784
+ async _updateLoop() {
2785
+ const log = Logger("pkc-js:local-community:update:_updateLoop");
2786
+ while (this.state === "updating" && !this._stopHasBeenCalled) {
2787
+ try {
2788
+ await this._updateOnce();
2789
+ }
2790
+ catch (e) {
2791
+ log.error("Error in update loop", e);
2792
+ this.emit("error", e);
2793
+ }
2794
+ finally {
2795
+ await new Promise((resolve) => {
2796
+ if (this._updateLoopAbortController?.signal.aborted)
2797
+ return resolve();
2798
+ const timer = setTimeout(resolve, this._pkc.updateInterval);
2799
+ this._updateLoopAbortController?.signal.addEventListener("abort", () => {
2800
+ clearTimeout(timer);
2801
+ resolve();
2802
+ }, { once: true });
2803
+ });
2804
+ }
2805
+ }
2806
+ }
2807
+ async update() {
2808
+ if (this.state === "started")
2809
+ throw new PKCError("ERR_COMMUNITY_ALREADY_STARTED", { address: this.address });
2810
+ if (this.state === "updating")
2811
+ return;
2812
+ this._stopHasBeenCalled = false;
2813
+ this._setState("updating");
2814
+ try {
2815
+ await this._updateOnce();
2816
+ }
2817
+ catch (e) {
2818
+ this.emit("error", e);
2819
+ }
2820
+ this._updateLoopAbortController = new AbortController();
2821
+ this._updateLoopPromise = this._updateLoop();
2822
+ }
2823
+ async stop() {
2824
+ const log = Logger("pkc-js:local-community:stop");
2825
+ this._stopHasBeenCalled = true;
2826
+ if (this._updateLoopAbortController) {
2827
+ this._updateLoopAbortController.abort();
2828
+ }
2829
+ this.posts._stop();
2830
+ if (this.state === "started") {
2831
+ log("Stopping running community", this.address);
2832
+ try {
2833
+ await this._clientsManager.pubsubUnsubscribe(this.pubsubTopicWithfallback(), this.handleChallengeExchange);
2834
+ }
2835
+ catch (e) {
2836
+ log.error("Failed to unsubscribe from challenge exchange pubsub when stopping community", e);
2837
+ }
2838
+ if (this._publishLoopPromise) {
2839
+ try {
2840
+ await this._publishLoopPromise;
2841
+ }
2842
+ catch (e) {
2843
+ log.error(`Failed to stop community publish loop`, e);
2844
+ }
2845
+ this._publishLoopPromise = undefined;
2846
+ }
2847
+ try {
2848
+ await this._unpinStaleCids();
2849
+ }
2850
+ catch (e) {
2851
+ log.error("Failed to unpin stale cids and remove mfs paths before stopping", e);
2852
+ }
2853
+ try {
2854
+ await this._updateDbInternalState(this.updateCid ? this.toJSONInternalAfterFirstUpdate() : this.toJSONInternalBeforeFirstUpdate());
2855
+ }
2856
+ catch (e) {
2857
+ log.error("Failed to update db internal state before stopping", e);
2858
+ }
2859
+ try {
2860
+ await this._dbHandler.unlockCommunityStart();
2861
+ }
2862
+ catch (e) {
2863
+ log.error(`Failed to unlock start lock on community (${this.address})`, e);
2864
+ }
2865
+ const kuboRpcClient = this._clientsManager.getDefaultKuboRpcClient();
2866
+ const pubsubClient = this._clientsManager.getDefaultKuboPubsubClient();
2867
+ this._setStartedStateWithEmission("stopped");
2868
+ untrackStartedCommunity(this._pkc, this);
2869
+ processStartedCommunities.untrack(this);
2870
+ this._duplicatePublicationAttempts?.clear();
2871
+ await this._dbHandler.rollbackAllTransactions();
2872
+ await this._dbHandler.unlockCommunityState();
2873
+ await this._updateStartedValue();
2874
+ this._clientsManager.updateKuboRpcState("stopped", kuboRpcClient.url);
2875
+ this._clientsManager.updateKuboRpcPubsubState("stopped", pubsubClient.url);
2876
+ if (this._dbHandler)
2877
+ this._dbHandler.destoryConnection();
2878
+ log(`Stopped the running of local community (${this.address})`);
2879
+ this._setState("stopped");
2880
+ }
2881
+ else if (this.state === "updating") {
2882
+ if (this._updateLoopPromise) {
2883
+ await this._updateLoopPromise;
2884
+ this._updateLoopPromise = undefined;
2885
+ }
2886
+ this._updateLoopAbortController = undefined;
2887
+ if (this._dbHandler)
2888
+ this._dbHandler.destoryConnection();
2889
+ if (this._mirroredStartedOrUpdatingCommunity)
2890
+ await this._cleanUpMirroredStartedOrUpdatingCommunity();
2891
+ if (findUpdatingCommunity(this._pkc, { address: this.address }) === this)
2892
+ untrackUpdatingCommunity(this._pkc, this);
2893
+ this._setUpdatingStateWithEventEmissionIfNewState("stopped");
2894
+ log(`Stopped the updating of local community (${this.address})`);
2895
+ this._setState("stopped");
2896
+ }
2897
+ }
2898
+ async delete() {
2899
+ const log = Logger("pkc-js:local-community:delete");
2900
+ log.trace(`Attempting to stop the community (${this.address}) before deleting, if needed`);
2901
+ const startedCommunity = ((findStartedCommunity(this._pkc, { address: this.address }) ||
2902
+ findCommunityInRegistry(processStartedCommunities, { address: this.address })));
2903
+ if (startedCommunity && startedCommunity !== this) {
2904
+ await startedCommunity.delete();
2905
+ await this.stop();
2906
+ return;
2907
+ }
2908
+ if (this.state === "updating" || this.state === "started")
2909
+ await this.stop();
2910
+ const kuboClient = this._clientsManager.getDefaultKuboRpcClient();
2911
+ if (!kuboClient)
2912
+ throw Error("Ipfs client is not defined");
2913
+ if (typeof this.signer?.ipnsKeyName === "string")
2914
+ // Key may not exist on ipfs node
2915
+ try {
2916
+ await kuboClient._client.key.rm(this.signer.ipnsKeyName);
2917
+ }
2918
+ catch (e) {
2919
+ log.error("Failed to delete ipns key", this.signer.ipnsKeyName, e);
2920
+ }
2921
+ try {
2922
+ await removeMfsFilesSafely({ kuboRpcClient: kuboClient, paths: ["/" + this.address], log });
2923
+ }
2924
+ catch (e) {
2925
+ log.error("Failed to delete community mfs folder", "/" + this.address, e);
2926
+ }
2927
+ // sceneario 1: we call delete() on a community that is not started or updating
2928
+ // scenario 2: we call delete() on a community that is updating
2929
+ // scenario 3: we call delete() on a community that is started
2930
+ // scenario 4: we call delete() on a community that is not started, but the same community is started in pkc._startedCommunities[address]
2931
+ try {
2932
+ await this._addOldPageCidsToCidsToUnpin(this.raw?.communityIpfs?.posts, undefined);
2933
+ }
2934
+ catch (e) {
2935
+ log.error("Failed to add old page cids from community.posts to be unpinned", e);
2936
+ }
2937
+ if (this.ipnsPubsubTopicRoutingCid)
2938
+ this._cidsToUnPin.add(this.ipnsPubsubTopicRoutingCid);
2939
+ if (this.pubsubTopicRoutingCid)
2940
+ this._cidsToUnPin.add(this.pubsubTopicRoutingCid);
2941
+ try {
2942
+ await this.initDbHandlerIfNeeded();
2943
+ await this._dbHandler.initDbIfNeeded();
2944
+ const cidsAndReplies = this._dbHandler.queryAllCommentCidsAndTheirReplies();
2945
+ cidsAndReplies.forEach((comment) => this._cidsToUnPin.add(comment.cid));
2946
+ await Promise.all(cidsAndReplies
2947
+ .filter((comment) => comment.replies)
2948
+ .map(async (commentWithReplies) => {
2949
+ await this._addOldPageCidsToCidsToUnpin(commentWithReplies.replies, undefined);
2950
+ }));
2951
+ }
2952
+ catch (e) {
2953
+ log.error("Failed to query all cids under this community to delete them", e);
2954
+ }
2955
+ if (this.updateCid)
2956
+ this._cidsToUnPin.add(this.updateCid);
2957
+ if (this.statsCid)
2958
+ this._cidsToUnPin.add(this.statsCid);
2959
+ try {
2960
+ await this._unpinStaleCids();
2961
+ }
2962
+ catch (e) {
2963
+ log.error("Failed to unpin stale cids before deleting", e);
2964
+ }
2965
+ try {
2966
+ await this._updateDbInternalState(typeof this.updatedAt === "number" ? this.toJSONInternalAfterFirstUpdate() : this.toJSONInternalBeforeFirstUpdate());
2967
+ }
2968
+ catch (e) {
2969
+ log.error("Failed to update db internal state before deleting", e);
2970
+ }
2971
+ finally {
2972
+ this._dbHandler.destoryConnection();
2973
+ }
2974
+ await moveCommunityDbToDeletedDirectory(this.address, this._pkc);
2975
+ log(`Deleted community (${this.address}) successfully`);
2976
+ }
2977
+ }
2978
+ //# sourceMappingURL=local-community.js.map