fedipod-server 0.29.0 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (440) hide show
  1. package/README.md +11 -6
  2. package/dist/door.d.ts +4 -0
  3. package/dist/door.js +31 -0
  4. package/dist/fedipod.d.ts +16 -0
  5. package/dist/fedipod.js +13 -0
  6. package/dist/front.d.ts +3 -0
  7. package/dist/front.js +94 -0
  8. package/dist/handler.d.ts +36 -101
  9. package/dist/handler.js +85 -645
  10. package/dist/handler.jsonld +2 -30
  11. package/dist/identities.d.ts +62 -0
  12. package/dist/identities.js +236 -0
  13. package/dist/mounts.d.ts +43 -0
  14. package/dist/mounts.js +123 -0
  15. package/dist/opt-in.d.ts +46 -0
  16. package/dist/opt-in.js +176 -0
  17. package/package.json +10 -18
  18. package/lib/client/c2s.mjs +0 -636
  19. package/lib/client/localapi.mjs +0 -87
  20. package/lib/client/masto/accounts.mjs +0 -310
  21. package/lib/client/masto/body.mjs +0 -156
  22. package/lib/client/masto/index.mjs +0 -206
  23. package/lib/client/masto/instance.mjs +0 -112
  24. package/lib/client/masto/media.mjs +0 -79
  25. package/lib/client/masto/oauth.mjs +0 -656
  26. package/lib/client/masto/render.mjs +0 -518
  27. package/lib/client/masto/statuses.mjs +0 -372
  28. package/lib/client/masto/timelines.mjs +0 -438
  29. package/lib/client/oidc-auth.mjs +0 -62
  30. package/lib/client/streaming.mjs +0 -154
  31. package/lib/client/webpush.mjs +0 -98
  32. package/lib/connections/acctfeed.mjs +0 -257
  33. package/lib/connections/atproto.mjs +0 -309
  34. package/lib/connections/bskyfeed.mjs +0 -243
  35. package/lib/connections/bskygroup.mjs +0 -162
  36. package/lib/connections/fediacct.mjs +0 -253
  37. package/lib/connections/import.mjs +0 -357
  38. package/lib/connections/tagfeed.mjs +0 -212
  39. package/lib/connections/vault.mjs +0 -114
  40. package/lib/core/as2.mjs +0 -206
  41. package/lib/core/contexts/activitystreams.json +0 -379
  42. package/lib/core/contexts/anno.json +0 -126
  43. package/lib/core/contexts/did-v1.json +0 -57
  44. package/lib/core/contexts/fep-5711.json +0 -36
  45. package/lib/core/contexts/gotosocial.json +0 -86
  46. package/lib/core/contexts/identity-v1.json +0 -152
  47. package/lib/core/contexts/index.mjs +0 -63
  48. package/lib/core/contexts/join-lemmy.json +0 -33
  49. package/lib/core/contexts/joinmastodon.json +0 -28
  50. package/lib/core/contexts/litepub-0.1.json +0 -60
  51. package/lib/core/contexts/map.json +0 -19
  52. package/lib/core/contexts/miscellany.json +0 -19
  53. package/lib/core/contexts/quotes.json +0 -18
  54. package/lib/core/contexts/schemaorg.json +0 -8845
  55. package/lib/core/contexts/security-data-integrity-v1.json +0 -78
  56. package/lib/core/contexts/security-data-integrity-v2.json +0 -81
  57. package/lib/core/contexts/security-multikey-v1.json +0 -35
  58. package/lib/core/contexts/security-v1.json +0 -74
  59. package/lib/core/contexts/webfinger.json +0 -10
  60. package/lib/core/deliver.mjs +0 -387
  61. package/lib/core/graphview.mjs +0 -327
  62. package/lib/core/intake/activities.mjs +0 -649
  63. package/lib/core/intake/activity.mjs +0 -318
  64. package/lib/core/intake/channel.mjs +0 -144
  65. package/lib/core/intake/group.mjs +0 -258
  66. package/lib/core/intake/index.mjs +0 -732
  67. package/lib/core/intake/notes.mjs +0 -367
  68. package/lib/core/intake/verify.mjs +0 -142
  69. package/lib/core/keys.mjs +0 -153
  70. package/lib/core/lease.mjs +0 -206
  71. package/lib/core/place.mjs +0 -82
  72. package/lib/core/polls.mjs +0 -105
  73. package/lib/core/profile-page.mjs +0 -104
  74. package/lib/core/proof.mjs +0 -79
  75. package/lib/core/publisher/collections.mjs +0 -272
  76. package/lib/core/publisher/index.mjs +0 -522
  77. package/lib/core/publisher/notes.mjs +0 -519
  78. package/lib/core/publisher/own.mjs +0 -143
  79. package/lib/core/publisher/questions.mjs +0 -241
  80. package/lib/core/publisher/restore.mjs +0 -224
  81. package/lib/core/shapes/activitystreams.ttl +0 -157
  82. package/lib/core/shapes/index.mjs +0 -132
  83. package/lib/core/shapes/shapes-text.mjs +0 -13
  84. package/lib/core/social.mjs +0 -561
  85. package/lib/core/storage.mjs +0 -176
  86. package/lib/core/store.mjs +0 -578
  87. package/lib/core/wire-quotes.mjs +0 -71
  88. package/lib/core/wire.mjs +0 -925
  89. package/lib/device/account.mjs +0 -87
  90. package/lib/device/admin/index.mjs +0 -13
  91. package/lib/device/admin/origins.mjs +0 -35
  92. package/lib/device/admin/routes/connections.mjs +0 -144
  93. package/lib/device/admin/routes/gateway.mjs +0 -199
  94. package/lib/device/admin/routes/lifecycle.mjs +0 -200
  95. package/lib/device/admin/routes/owner.mjs +0 -322
  96. package/lib/device/admin/routes/setup.mjs +0 -428
  97. package/lib/device/admin/routes/social.mjs +0 -188
  98. package/lib/device/admin/server.mjs +0 -95
  99. package/lib/device/admin/static.mjs +0 -247
  100. package/lib/device/admin/surface.mjs +0 -315
  101. package/lib/device/certs.mjs +0 -281
  102. package/lib/device/cli/commands/account.mjs +0 -586
  103. package/lib/device/cli/commands/run.mjs +0 -278
  104. package/lib/device/cli/commands/service.mjs +0 -221
  105. package/lib/device/cli/commands/setup.mjs +0 -489
  106. package/lib/device/cli/commands/state.mjs +0 -561
  107. package/lib/device/cli/context.mjs +0 -288
  108. package/lib/device/export-collections.mjs +0 -161
  109. package/lib/device/gateway-move.mjs +0 -58
  110. package/lib/device/home.mjs +0 -161
  111. package/lib/device/migrate.mjs +0 -115
  112. package/lib/device/ports.mjs +0 -27
  113. package/lib/device/remote.mjs +0 -54
  114. package/lib/device/setup.mjs +0 -496
  115. package/lib/device/update.mjs +0 -96
  116. package/lib/gateway/caches.mjs +0 -36
  117. package/lib/gateway/directory.mjs +0 -90
  118. package/lib/gateway/front-core.mjs +0 -966
  119. package/lib/gateway/gateway-core.mjs +0 -244
  120. package/lib/gateway/headers.mjs +0 -49
  121. package/lib/gateway/httpsig.mjs +0 -159
  122. package/lib/gateway/notices.mjs +0 -84
  123. package/lib/gateway/quiet.mjs +0 -189
  124. package/lib/gateway/relay-extras.mjs +0 -89
  125. package/lib/gateway/token-claims.mjs +0 -16
  126. package/lib/pod/README.md +0 -61
  127. package/lib/pod/actor.mjs +0 -96
  128. package/lib/pod/collection.mjs +0 -77
  129. package/lib/pod/containers.mjs +0 -140
  130. package/lib/pod/discovery.mjs +0 -46
  131. package/lib/pod/featured.mjs +0 -18
  132. package/lib/pod/followers.mjs +0 -24
  133. package/lib/pod/following.mjs +0 -10
  134. package/lib/pod/http.mjs +0 -74
  135. package/lib/pod/inbox.mjs +0 -166
  136. package/lib/pod/links.mjs +0 -35
  137. package/lib/pod/location.mjs +0 -52
  138. package/lib/pod/media.mjs +0 -27
  139. package/lib/pod/notes.mjs +0 -78
  140. package/lib/pod/notifications.mjs +0 -89
  141. package/lib/pod/outbox.mjs +0 -18
  142. package/lib/pod/package.json +0 -19
  143. package/lib/pod/policy.mjs +0 -58
  144. package/lib/pod/private.mjs +0 -19
  145. package/lib/pod/root.mjs +0 -104
  146. package/lib/pod/state.mjs +0 -40
  147. package/lib/pod/transport.mjs +0 -674
  148. package/lib/pod/type-index.mjs +0 -101
  149. package/lib/pod/urls.mjs +0 -91
  150. package/lib/server/embed.mjs +0 -424
  151. package/lib/session/README.md +0 -137
  152. package/lib/session/demo.html +0 -94
  153. package/lib/session/fedi-account.mjs +0 -417
  154. package/lib/session/fedi-login.mjs +0 -123
  155. package/lib/session/oidc-session.mjs +0 -233
  156. package/lib/session/package.json +0 -49
  157. package/lib/shared/guard.mjs +0 -213
  158. package/lib/shared/links.mjs +0 -9
  159. package/lib/shared/safefetch.mjs +0 -235
  160. package/lib/shared/ua.mjs +0 -29
  161. package/phanpy/dist/404.html +0 -40
  162. package/phanpy/dist/_headers +0 -2
  163. package/phanpy/dist/apple-touch-icon.png +0 -0
  164. package/phanpy/dist/assets/ICONS-DIPJeU0C.js +0 -2
  165. package/phanpy/dist/assets/ICONS-DIPJeU0C.js.map +0 -1
  166. package/phanpy/dist/assets/ScheduledAtField-D-Abfo4J.js +0 -183
  167. package/phanpy/dist/assets/ScheduledAtField-D-Abfo4J.js.map +0 -1
  168. package/phanpy/dist/assets/Temml-CCjoacWt.woff2 +0 -0
  169. package/phanpy/dist/assets/__vite-browser-external-C7iut881.js +0 -2
  170. package/phanpy/dist/assets/__vite-browser-external-C7iut881.js.map +0 -1
  171. package/phanpy/dist/assets/boosts-carousel-BiOaNdMT.jpg +0 -0
  172. package/phanpy/dist/assets/catch-up-O5q5C75Z.png +0 -0
  173. package/phanpy/dist/assets/chunk-aKtaBQYM.js +0 -1
  174. package/phanpy/dist/assets/compose-CFEx7wb4.js +0 -2
  175. package/phanpy/dist/assets/compose-CFEx7wb4.js.map +0 -1
  176. package/phanpy/dist/assets/compose-ZJN5xSb4.js +0 -35
  177. package/phanpy/dist/assets/compose-ZJN5xSb4.js.map +0 -1
  178. package/phanpy/dist/assets/debug.module-YFQgwnIr.js +0 -52
  179. package/phanpy/dist/assets/debug.module-YFQgwnIr.js.map +0 -1
  180. package/phanpy/dist/assets/dom-p1YcJ5Rw.js +0 -2
  181. package/phanpy/dist/assets/dom-p1YcJ5Rw.js.map +0 -1
  182. package/phanpy/dist/assets/home-mobile-dark@2x-Dc-J8-SM.png +0 -0
  183. package/phanpy/dist/assets/home-mobile-light@2x-Bd3fod8m.png +0 -0
  184. package/phanpy/dist/assets/home-tablet-dark@2x-BgcNI8le.png +0 -0
  185. package/phanpy/dist/assets/home-tablet-light@2x-BW2RV9T6.png +0 -0
  186. package/phanpy/dist/assets/icons/add-circle-line-WD0mwORD.js +0 -1
  187. package/phanpy/dist/assets/icons/alert-line-CpFUMf9s.js +0 -1
  188. package/phanpy/dist/assets/icons/android-2-line-Bzxnrd3S.js +0 -1
  189. package/phanpy/dist/assets/icons/announcement-line-5nwaJFJm.js +0 -1
  190. package/phanpy/dist/assets/icons/arrow-down-circle-line-a7Id9-Iv.js +0 -1
  191. package/phanpy/dist/assets/icons/arrow-down-line-B8uzXzq6.js +0 -1
  192. package/phanpy/dist/assets/icons/arrow-left-line-CGrGT9C-.js +0 -1
  193. package/phanpy/dist/assets/icons/arrow-right-line-CE-HERfX.js +0 -1
  194. package/phanpy/dist/assets/icons/arrow-to-up-line-CvdQjmcy.js +0 -1
  195. package/phanpy/dist/assets/icons/arrow-up-circle-line-DEparSNF.js +0 -1
  196. package/phanpy/dist/assets/icons/arrow-up-line-C7yBeugc.js +0 -1
  197. package/phanpy/dist/assets/icons/arrows-right-line-BtbXmJGP.js +0 -1
  198. package/phanpy/dist/assets/icons/at-line-CF66Bznk.js +0 -1
  199. package/phanpy/dist/assets/icons/attachment-line-BY8It-l9.js +0 -1
  200. package/phanpy/dist/assets/icons/blockquote-line-BS6pU_hm.js +0 -1
  201. package/phanpy/dist/assets/icons/board-line-C0UWXyA6.js +0 -1
  202. package/phanpy/dist/assets/icons/bookmark-line-1CtX5JLF.js +0 -1
  203. package/phanpy/dist/assets/icons/building-5-line-Dd59MzbO.js +0 -1
  204. package/phanpy/dist/assets/icons/bus-2-line-6_dvA0GG.js +0 -1
  205. package/phanpy/dist/assets/icons/calendar-day-line-Crpl_fmu.js +0 -1
  206. package/phanpy/dist/assets/icons/calendar-month-line-vommts2C.js +0 -1
  207. package/phanpy/dist/assets/icons/calendar-time-add-line-BU_DTlQk.js +0 -1
  208. package/phanpy/dist/assets/icons/camera-line-Cv69MEei.js +0 -1
  209. package/phanpy/dist/assets/icons/celebrate-line-B8DvIoi0.js +0 -1
  210. package/phanpy/dist/assets/icons/chart-bar-line-FK-QOmtF.js +0 -1
  211. package/phanpy/dist/assets/icons/chart-line-line-BGcJAFWw.js +0 -1
  212. package/phanpy/dist/assets/icons/chat-3-line-YFK2-hwz.js +0 -1
  213. package/phanpy/dist/assets/icons/check-circle-line-DsAxFr5-.js +0 -1
  214. package/phanpy/dist/assets/icons/clipboard-line-BBp364gJ.js +0 -1
  215. package/phanpy/dist/assets/icons/close-circle-line-CICdiuGz.js +0 -1
  216. package/phanpy/dist/assets/icons/close-line-DYrFWcjv.js +0 -1
  217. package/phanpy/dist/assets/icons/cloud-line-tiQAbah8.js +0 -1
  218. package/phanpy/dist/assets/icons/code-line-fAl8iiV3.js +0 -1
  219. package/phanpy/dist/assets/icons/comment-2-line-CR3g7WeB.js +0 -1
  220. package/phanpy/dist/assets/icons/copy-2-line-CLW69yF5.js +0 -1
  221. package/phanpy/dist/assets/icons/delete-2-line-B8xmbr4n.js +0 -1
  222. package/phanpy/dist/assets/icons/document-3-line-DBGNhAg2.js +0 -1
  223. package/phanpy/dist/assets/icons/document-line-C7mb9sb7.js +0 -1
  224. package/phanpy/dist/assets/icons/down-line-BGqSSThd.js +0 -1
  225. package/phanpy/dist/assets/icons/edit-4-line-DcgGaEhx.js +0 -1
  226. package/phanpy/dist/assets/icons/emoji-2-line-D8YgnM-y.js +0 -1
  227. package/phanpy/dist/assets/icons/exit-line-Dc07BS-x.js +0 -1
  228. package/phanpy/dist/assets/icons/external-link-line-BvpSkmjO.js +0 -1
  229. package/phanpy/dist/assets/icons/eye-2-line-CiWXx-f_.js +0 -1
  230. package/phanpy/dist/assets/icons/eye-close-line-BgTaSJ4P.js +0 -1
  231. package/phanpy/dist/assets/icons/filter-2-line-B_mR2usy.js +0 -1
  232. package/phanpy/dist/assets/icons/filter-line-C_nm-3cU.js +0 -1
  233. package/phanpy/dist/assets/icons/flag-1-line-C2HB05I0.js +0 -1
  234. package/phanpy/dist/assets/icons/forbid-circle-line-D3LTT9yd.js +0 -1
  235. package/phanpy/dist/assets/icons/formula-line-DLrt2d6j.js +0 -1
  236. package/phanpy/dist/assets/icons/grid-line-CgUmEikf.js +0 -1
  237. package/phanpy/dist/assets/icons/group-line-C5fnX0wU.js +0 -1
  238. package/phanpy/dist/assets/icons/hand-finger-2-line-BGgxbuL9.js +0 -1
  239. package/phanpy/dist/assets/icons/hashtag-line-CyTEEXBb.js +0 -1
  240. package/phanpy/dist/assets/icons/heart-crack-line-BZddbO2C.js +0 -1
  241. package/phanpy/dist/assets/icons/heart-line-CJ7K47J4.js +0 -1
  242. package/phanpy/dist/assets/icons/history-2-line-kWWvFzqv.js +0 -1
  243. package/phanpy/dist/assets/icons/history-line-bGQiwbTH.js +0 -1
  244. package/phanpy/dist/assets/icons/home-3-line-fRU1zeLG.js +0 -1
  245. package/phanpy/dist/assets/icons/information-line-iNk6-sOY.js +0 -1
  246. package/phanpy/dist/assets/icons/keyboard-line-DsCyuQNz.js +0 -1
  247. package/phanpy/dist/assets/icons/layout-4-line-CZkOX52Z.js +0 -1
  248. package/phanpy/dist/assets/icons/layout-5-line-B_znt5wO.js +0 -1
  249. package/phanpy/dist/assets/icons/left-line-eUw0tya4.js +0 -1
  250. package/phanpy/dist/assets/icons/lightning-line-DaKoDdix.js +0 -1
  251. package/phanpy/dist/assets/icons/link-2-line-DCqLmMA7.js +0 -1
  252. package/phanpy/dist/assets/icons/list-check-line-BAxosBh9.js +0 -1
  253. package/phanpy/dist/assets/icons/lock-line-CPoaXeUW.js +0 -1
  254. package/phanpy/dist/assets/icons/mail-line-B0P8aa1L.js +0 -1
  255. package/phanpy/dist/assets/icons/moon-line-CEqNAToP.js +0 -1
  256. package/phanpy/dist/assets/icons/more-1-fill-Dw2e5xab.js +0 -1
  257. package/phanpy/dist/assets/icons/more-3-line-BIiVjYTz.js +0 -1
  258. package/phanpy/dist/assets/icons/notification-line-BZZq2Gtu.js +0 -1
  259. package/phanpy/dist/assets/icons/pencil-line-CKh8-A1v.js +0 -1
  260. package/phanpy/dist/assets/icons/photo-album-line-Bnfg2u40.js +0 -1
  261. package/phanpy/dist/assets/icons/pin-line-DpTolyhs.js +0 -1
  262. package/phanpy/dist/assets/icons/play-fill-B0-G_8lX.js +0 -1
  263. package/phanpy/dist/assets/icons/qrcode-2-line-B6SPCaHn.js +0 -1
  264. package/phanpy/dist/assets/icons/quill-pen-line-B6LUrnCP.js +0 -1
  265. package/phanpy/dist/assets/icons/quote-left-fill-hQZ1cj_6.js +0 -1
  266. package/phanpy/dist/assets/icons/radar-line-CKyroTth.js +0 -1
  267. package/phanpy/dist/assets/icons/react-line-CoIKhhtK.js +0 -1
  268. package/phanpy/dist/assets/icons/refresh-2-line-D0mTF97S.js +0 -1
  269. package/phanpy/dist/assets/icons/right-line-B3KWJaqc.js +0 -1
  270. package/phanpy/dist/assets/icons/rocket-line-4KZl537D.js +0 -1
  271. package/phanpy/dist/assets/icons/round-fill-C69NTrZ0.js +0 -1
  272. package/phanpy/dist/assets/icons/round-line-B9k_1uo-.js +0 -1
  273. package/phanpy/dist/assets/icons/route-line-B28ReHcE.js +0 -1
  274. package/phanpy/dist/assets/icons/rows-4-line-CrBniagM.js +0 -1
  275. package/phanpy/dist/assets/icons/scan-line-DSf74YdP.js +0 -1
  276. package/phanpy/dist/assets/icons/search-2-line-B6geIU1z.js +0 -1
  277. package/phanpy/dist/assets/icons/settings-3-line-azJwK47w.js +0 -1
  278. package/phanpy/dist/assets/icons/settings-6-line-C_5zc5YR.js +0 -1
  279. package/phanpy/dist/assets/icons/share-2-line-D5MxWjrm.js +0 -1
  280. package/phanpy/dist/assets/icons/share-forward-line-Cp61fZzZ.js +0 -1
  281. package/phanpy/dist/assets/icons/sparkles-2-line-DYYyXTaW.js +0 -1
  282. package/phanpy/dist/assets/icons/sparkles-line-C2hY1s6I.js +0 -1
  283. package/phanpy/dist/assets/icons/time-line-BF3eet1v.js +0 -1
  284. package/phanpy/dist/assets/icons/transfer-4-line-oisfvTBw.js +0 -1
  285. package/phanpy/dist/assets/icons/translate-line-BFWol2Ce.js +0 -1
  286. package/phanpy/dist/assets/icons/unlock-line-X_x5vwv3.js +0 -1
  287. package/phanpy/dist/assets/icons/upload-3-line-DgzwUJeW.js +0 -1
  288. package/phanpy/dist/assets/icons/user-4-line-BVSynZCp.js +0 -1
  289. package/phanpy/dist/assets/icons/user-add-2-line-BlGS5Q8q.js +0 -1
  290. package/phanpy/dist/assets/icons/user-add-line-BbXdHG4z.js +0 -1
  291. package/phanpy/dist/assets/icons/user-edit-line-BFAHOV0X.js +0 -1
  292. package/phanpy/dist/assets/icons/user-follow-line-BXux0wSd.js +0 -1
  293. package/phanpy/dist/assets/icons/user-star-line-DdYlpHLU.js +0 -1
  294. package/phanpy/dist/assets/icons/user-warning-line-Cj43j0_E.js +0 -1
  295. package/phanpy/dist/assets/icons/user-x-line-DIDgNlPH.js +0 -1
  296. package/phanpy/dist/assets/icons/volume-line-Bs390KQt.js +0 -1
  297. package/phanpy/dist/assets/icons/volume-mute-line-DzlzIqFZ.js +0 -1
  298. package/phanpy/dist/assets/icons/walk-line-yNXfB-md.js +0 -1
  299. package/phanpy/dist/assets/icons/world-2-line-CenkGIwj.js +0 -1
  300. package/phanpy/dist/assets/icons/zoom-in-line-CaENUphf.js +0 -1
  301. package/phanpy/dist/assets/icons/zoom-out-line-BDNk0EWo.js +0 -1
  302. package/phanpy/dist/assets/instances-BNPptnn-.json +0 -333
  303. package/phanpy/dist/assets/locales/ar-SA-CfheRbCu.js +0 -1
  304. package/phanpy/dist/assets/locales/ca-ES-DHRW86Tz.js +0 -1
  305. package/phanpy/dist/assets/locales/cs-CZ-CKoqEuA1.js +0 -1
  306. package/phanpy/dist/assets/locales/de-DE-ByRPMRFy.js +0 -1
  307. package/phanpy/dist/assets/locales/eo-UY-CPC7wLCS.js +0 -1
  308. package/phanpy/dist/assets/locales/es-ES-dJa0Fd9t.js +0 -1
  309. package/phanpy/dist/assets/locales/eu-ES-C3djlPR2.js +0 -1
  310. package/phanpy/dist/assets/locales/fa-IR-DTT490Ck.js +0 -1
  311. package/phanpy/dist/assets/locales/fi-FI-B23MCPuz.js +0 -1
  312. package/phanpy/dist/assets/locales/fr-FR-BcaQsO5C.js +0 -1
  313. package/phanpy/dist/assets/locales/gl-ES-BR-CVKBN.js +0 -1
  314. package/phanpy/dist/assets/locales/he-IL-CfheRbCu.js +0 -1
  315. package/phanpy/dist/assets/locales/hu-HU-CfheRbCu.js +0 -1
  316. package/phanpy/dist/assets/locales/it-IT-C1136UFV.js +0 -1
  317. package/phanpy/dist/assets/locales/ja-JP-CRZmRAhD.js +0 -1
  318. package/phanpy/dist/assets/locales/kab-Cz8xH_TT.js +0 -1
  319. package/phanpy/dist/assets/locales/ko-KR-CG9UiY4o.js +0 -1
  320. package/phanpy/dist/assets/locales/lt-LT-CJcI6sHZ.js +0 -1
  321. package/phanpy/dist/assets/locales/nb-NO-CfheRbCu.js +0 -1
  322. package/phanpy/dist/assets/locales/nl-NL-DxN4Yc1t.js +0 -1
  323. package/phanpy/dist/assets/locales/oc-FR-CfheRbCu.js +0 -1
  324. package/phanpy/dist/assets/locales/pl-PL-DkwGXf6P.js +0 -1
  325. package/phanpy/dist/assets/locales/pseudo-LOCALE-CQ4XxkQ0.js +0 -1
  326. package/phanpy/dist/assets/locales/pt-BR-nGL0HdFS.js +0 -1
  327. package/phanpy/dist/assets/locales/pt-PT-Cp17aj2z.js +0 -1
  328. package/phanpy/dist/assets/locales/ru-RU-0Y8cIoaK.js +0 -1
  329. package/phanpy/dist/assets/locales/th-TH-CfheRbCu.js +0 -1
  330. package/phanpy/dist/assets/locales/tok-CfheRbCu.js +0 -1
  331. package/phanpy/dist/assets/locales/tr-TR-CfheRbCu.js +0 -1
  332. package/phanpy/dist/assets/locales/uk-UA-C5E3dFBf.js +0 -1
  333. package/phanpy/dist/assets/locales/zh-CN-BHJi7_et.js +0 -1
  334. package/phanpy/dist/assets/locales/zh-TW-CfheRbCu.js +0 -1
  335. package/phanpy/dist/assets/main-BdqNbG-a.js +0 -26
  336. package/phanpy/dist/assets/main-BdqNbG-a.js.map +0 -1
  337. package/phanpy/dist/assets/mock-home-DGhR__He.js +0 -2
  338. package/phanpy/dist/assets/mock-home-DGhR__He.js.map +0 -1
  339. package/phanpy/dist/assets/multi-column-ROck0NVt.jpg +0 -0
  340. package/phanpy/dist/assets/multi-hashtag-timeline-DH5INVHi.jpg +0 -0
  341. package/phanpy/dist/assets/nested-comments-thread-hv59kZLc.jpg +0 -0
  342. package/phanpy/dist/assets/polyfill-force-H8sUkLGp.js +0 -2
  343. package/phanpy/dist/assets/polyfill-force-H8sUkLGp.js.map +0 -1
  344. package/phanpy/dist/assets/pwa-viewport-ogKbF51K.js +0 -20
  345. package/phanpy/dist/assets/pwa-viewport-ogKbF51K.js.map +0 -1
  346. package/phanpy/dist/assets/style-B1iLPTPR.css +0 -2
  347. package/phanpy/dist/assets/temml-DuyIQMei.js +0 -97
  348. package/phanpy/dist/assets/temml-DuyIQMei.js.map +0 -1
  349. package/phanpy/dist/assets/tinyld.light.node-DtbBhLYC.js +0 -2
  350. package/phanpy/dist/assets/tinyld.light.node-DtbBhLYC.js.map +0 -1
  351. package/phanpy/dist/assets/year-in-posts-B2lywqdx.png +0 -0
  352. package/phanpy/dist/assets/year-in-posts-DJv_star.js +0 -3
  353. package/phanpy/dist/assets/year-in-posts-DJv_star.js.map +0 -1
  354. package/phanpy/dist/compose/index.html +0 -20
  355. package/phanpy/dist/favicon.ico +0 -0
  356. package/phanpy/dist/index.html +0 -73
  357. package/phanpy/dist/logo-192.png +0 -0
  358. package/phanpy/dist/logo-512.png +0 -0
  359. package/phanpy/dist/logo-badge-72.png +0 -0
  360. package/phanpy/dist/logo-maskable-512.png +0 -0
  361. package/phanpy/dist/logo-monochrome-512.png +0 -0
  362. package/phanpy/dist/logo-monochrome-maskable-512.png +0 -0
  363. package/phanpy/dist/manifest.webmanifest +0 -1
  364. package/phanpy/dist/og-image-2.jpg +0 -0
  365. package/phanpy/dist/og-image.png +0 -0
  366. package/phanpy/dist/robots.txt +0 -2
  367. package/phanpy/dist/sw.js +0 -2
  368. package/phanpy/dist/sw.js.map +0 -1
  369. package/phanpy/dist/version.json +0 -1
  370. package/run-agent.mjs +0 -899
  371. package/vendor/gate.cjs +0 -138
  372. package/vendor/idp-grant.cjs +0 -394
  373. package/vendor/jfetch.cjs +0 -16
  374. package/web/admin/actors.js +0 -147
  375. package/web/admin/bar.css +0 -126
  376. package/web/admin/bar.js +0 -99
  377. package/web/admin/client/client.js +0 -66
  378. package/web/admin/client/index.html +0 -69
  379. package/web/admin/client-pick.js +0 -79
  380. package/web/admin/common.js +0 -23
  381. package/web/admin/connections.js +0 -112
  382. package/web/admin/gateway.js +0 -133
  383. package/web/admin/group.js +0 -258
  384. package/web/admin/index.html +0 -612
  385. package/web/admin/notices-bar.js +0 -90
  386. package/web/admin/oauth-signin.mjs +0 -37
  387. package/web/admin/record.js +0 -393
  388. package/web/admin/setup/index.html +0 -257
  389. package/web/admin/setup/setup.js +0 -339
  390. package/web/admin/tokens.css +0 -68
  391. package/web/admin/upkeep.js +0 -177
  392. package/web/admin/window.css +0 -55
  393. package/web/admin/window.js +0 -136
  394. package/web/app/README.md +0 -81
  395. package/web/app/admin-facade.mjs +0 -588
  396. package/web/app/agent.mjs +0 -672
  397. package/web/app/atproto-browser.mjs +0 -86
  398. package/web/app/authorize_interaction.html +0 -56
  399. package/web/app/authorize_interaction.js +0 -69
  400. package/web/app/boot.mjs +0 -616
  401. package/web/app/deliver-relay.mjs +0 -129
  402. package/web/app/dist/boot.js +0 -34562
  403. package/web/app/dist/boot.js.map +0 -7
  404. package/web/app/dist/sw.js +0 -73456
  405. package/web/app/dist/sw.js.map +0 -7
  406. package/web/app/fediacct-browser.mjs +0 -146
  407. package/web/app/gateway-move.mjs +0 -60
  408. package/web/app/idb-kv.mjs +0 -35
  409. package/web/app/index.html +0 -258
  410. package/web/app/keys-browser.mjs +0 -71
  411. package/web/app/keystore.mjs +0 -82
  412. package/web/app/oidc-session.mjs +0 -6
  413. package/web/app/pod-auth.mjs +0 -248
  414. package/web/app/pod-remote.mjs +0 -84
  415. package/web/app/shims/_globals.mjs +0 -2
  416. package/web/app/shims/fedify-sig.mjs +0 -77
  417. package/web/app/shims/node-crypto.mjs +0 -113
  418. package/web/app/shims/node-fs.mjs +0 -17
  419. package/web/app/shims/node-path.mjs +0 -19
  420. package/web/app/shims/node-url.mjs +0 -7
  421. package/web/app/shims/prelude.js +0 -58
  422. package/web/app/shims/safefetch.mjs +0 -58
  423. package/web/app/shims/shapes-text.mjs +0 -8
  424. package/web/app/shims/web-push.mjs +0 -14
  425. package/web/app/signup.mjs +0 -357
  426. package/web/app/sw-src.mjs +0 -220
  427. package/web/app/update.js +0 -70
  428. package/web/front/admin.html +0 -55
  429. package/web/front/admin.js +0 -89
  430. package/web/front/install.sh +0 -69
  431. package/web/front/new-account.html +0 -35
  432. package/web/front/new-account.js +0 -13
  433. package/web/front/notices.html +0 -69
  434. package/web/front/notices.js +0 -107
  435. package/web/front/run.html +0 -64
  436. package/web/front/run.js +0 -221
  437. package/web/front/solid-oidc-client.js +0 -6
  438. package/web/icons/forum.svg +0 -11
  439. package/web/icons/gateway.svg +0 -14
  440. package/web/icons/local.svg +0 -11
@@ -1,578 +0,0 @@
1
- // store.mjs — PodStore: the agent's operational state as JSON documents in a
2
- // container. The container is a Storage (lib/storage.mjs) — a pod over HTTP or
3
- // a directory — and this file does not care which. The sync read/write surface
4
- // of dk's fs Store is preserved so every consumer (publisher, intake, facade,
5
- // …) is unchanged: reads come from an in-memory cache loaded once at boot,
6
- // writes update the cache and are flushed through a serialized, debounced,
7
- // retrying queue. Everything here is rebuildable from the /fediverse/ RDF, so
8
- // a lost write degrades, it doesn't destroy. Without a storage the store is
9
- // pure memory (tests, unconfigured boot).
10
-
11
- import crypto from 'node:crypto';
12
- import { sanitizeHtml } from './wire.mjs';
13
-
14
- // A display name is text, not markup: strip tags rather than allow a subset,
15
- // because there is no tag that belongs in one.
16
- const plainText = (s) => sanitizeHtml(String(s || '')).replace(/<[^>]*>/g, '').trim();
17
-
18
- // An avatar or header URL is written straight into an <img src> by the client.
19
- // http(s) only — `javascript:` and `data:` have no business being an avatar.
20
- const safeUrl = (u) => {
21
- if (!u) return u;
22
- try {
23
- const parsed = new URL(String(u));
24
- return (parsed.protocol === 'https:' || parsed.protocol === 'http:') ? String(u) : null;
25
- } catch { return null; }
26
- };
27
-
28
- // Remote text arrives with no length of its own. `readCapped` bounds a document
29
- // at 5 MB, which is a sane ceiling for a fetch and an absurd one for a display
30
- // name — and whatever survives is then written into a document we serialize
31
- // WHOLE on every change. One planted actor with a megabyte of bio permanently
32
- // inflated actors.json, and a Follow flood re-PUT it once per commit batch.
33
- // Mastodon's own limits are the shape of the real world; a little over them is
34
- // generous rather than lossy.
35
- const MAX_NAME = 500;
36
- const MAX_SUMMARY = 5_000;
37
- const MAX_CONTENT = 100_000;
38
- const clamp = (s, n) => (typeof s === 'string' && s.length > n ? s.slice(0, n) : s);
39
-
40
- const PUT_DEBOUNCE_MS = 300;
41
- const PUT_RETRIES = 5;
42
- // This is a CACHE of other people's actor documents — every search, every
43
- // timeline post from a stranger, every hashtag sweep adds one, and nothing ever
44
- // took one out. Bounded by dropping the least recently fetched, except for
45
- // anyone we actually have a relationship with: losing a follower's display name
46
- // would be re-fetching it on the next render for nothing.
47
- const ACTOR_CACHE_MAX = 2000;
48
- const NOTIFICATIONS_MAX = 500;
49
-
50
- function prune(actors, max, contacts) {
51
- const urls = Object.keys(actors);
52
- if (urls.length <= max) return actors;
53
- const keep = new Set([
54
- ...contacts.followers.map(f => f.actor),
55
- ...contacts.following.map(f => f.actor),
56
- ]);
57
- const droppable = urls.filter(u => !keep.has(u))
58
- .sort((a, b) => String(actors[a].fetchedAt || '').localeCompare(String(actors[b].fetchedAt || '')));
59
- for (const u of droppable.slice(0, urls.length - max)) delete actors[u];
60
- return actors;
61
- }
62
-
63
- // Removing a follower is a DECISION — they unfollowed, or were ejected, or the
64
- // account is gone. Reconciling the local list against the pod's published one
65
- // would otherwise bring every one of them back, so each removal leaves a mark.
66
- // Bounded: the same actor leaving twice is still one entry.
67
- export function dropFollower(contacts, actor, why) {
68
- contacts.followers = contacts.followers.filter(f => f.actor !== actor);
69
- const gone = (contacts.removedFollowers || []).filter(r => r.actor !== actor);
70
- gone.push({ actor, why, at: new Date().toISOString() });
71
- contacts.removedFollowers = gone.slice(-500);
72
- return contacts;
73
- }
74
-
75
- // What a state document looks like on the pod; write() and _put() agree on it.
76
- const serialise = (obj) => JSON.stringify(obj, null, 2) + '\n';
77
- // The same record apart from when it was fetched.
78
- const sameButWhen = (a, b) => JSON.stringify({ ...a, fetchedAt: null }) === JSON.stringify({ ...b, fetchedAt: null });
79
-
80
- export class PodStore {
81
- constructor({ storage = null, log = console.log } = {}) {
82
- this.lastSkipped = []; // what the last load could not read, as `name (HTTP n)`
83
- this.storage = storage;
84
- this.log = log;
85
- this.cache = new Map(); // name → parsed value
86
- this.etags = new Map(); // name (or '') → last ETag, for revalidation
87
- this.lastText = new Map(); // name → the bytes last loaded from or sent to the pod
88
- this.timers = new Map(); // name → debounce timer
89
- this.dirty = new Set(); // written while held; flushed by commit/release
90
- this._held = 0;
91
- this.chain = Promise.resolve(); // serialized writes
92
- this.verdicts = new Map(); // name → did its last write land? (read by commit)
93
- }
94
-
95
- get base() { return this.storage?.base || null; }
96
-
97
- attach(storage) {
98
- // A different tree is different state: carrying the old cache and its
99
- // ETags across would serve one container's documents as another's.
100
- if (this.storage && this.storage.base !== storage.base) { this.cache.clear(); this.etags.clear(); this.lastText.clear(); }
101
- this.storage = storage;
102
- }
103
-
104
- // Load every state doc in the container into the cache. Missing container
105
- // (first run) is fine — the cache just starts empty.
106
- //
107
- // `force` skips the container short-circuit below. A container's ETag says
108
- // its CHILDREN have not changed; it does not vouch for their contents, and a
109
- // peer agent rewriting a document it already had changes no containment
110
- // triple. That is a fine trade for a viewer refreshing a display, and a bad
111
- // one at the moment a viewer is promoted and starts acting on what it holds.
112
- // Throws when the container cannot be read. An empty cache MUST mean "no
113
- // state yet", never "the pod was unreachable" — the caller treats the
114
- // former as a fresh install, and silently conflating them would look like
115
- // an un-set-up agent every time the pod hiccups.
116
- async load({ force = false } = {}) {
117
- if (!this.storage) return;
118
- // Revalidate rather than re-download: a viewer reloads this every few
119
- // minutes and it is almost always unchanged. (Only a pod answers 304; a
120
- // directory has nothing to revalidate against, so it always relists —
121
- // which is a readdir, not a request.)
122
- const listing = await this.storage.list('', { etag: force ? null : this.etags.get('') });
123
- if (listing.notModified) return; // cannot happen when forced: no etag was sent
124
- // A 404'd container over HTTP is "could not read", not "no state yet":
125
- // setup creates this container, so once a credential exists it should
126
- // answer — and a proxy fronting a dead pod 404s in exactly the same shape.
127
- // A directory has no proxy to lie for it, so its absence stays an answer.
128
- if (listing.missing && this.storage.kind === 'pod') {
129
- throw new Error(`state container missing (HTTP 404) at ${this.base} — pod unreachable or state gone`);
130
- }
131
- this.etags.set('', listing.etag);
132
- const names = listing.names.filter(n => n.endsWith('.json'));
133
- let fetched = 0;
134
- const skipped = [];
135
- for (const name of names) {
136
- const etag = force ? null : this.etags.get(name);
137
- const r = await this.storage.read(name, { etag: etag && this.cache.has(name) ? etag : null });
138
- if (r.notModified) continue; // ours is current
139
- if (!r.ok) {
140
- // One unreadable document must not restart the whole sweep — skip it,
141
- // keep whatever we already hold, and try again next load (no etag is
142
- // recorded, so the retry is unconditional). config.json is the
143
- // exception: without it the caller cannot tell "never set up" from
144
- // "could not read", and would tell the user to run setup.
145
- if (name === 'config.json' && !this.cache.has(name)) {
146
- throw new Error(`state doc ${name} unreadable (HTTP ${r.status})`);
147
- }
148
- skipped.push(`${name} (HTTP ${r.status})`);
149
- continue;
150
- }
151
- fetched++;
152
- this.etags.set(name, r.etag);
153
- try { this.cache.set(name, JSON.parse(r.body)); this.lastText.set(name, r.body); }
154
- catch (e) { this.log(`state load ${name}: unparsable (${e.message})`); }
155
- }
156
- // Kept for /status: a document skipped here is a timeline or a contact
157
- // list quietly missing, and the owner should be able to see that.
158
- this.lastSkipped = skipped;
159
- if (skipped.length) this.log(`state load skipped ${skipped.length}: ${skipped.join(', ')}`);
160
- this.log(`state loaded: ${this.cache.size} doc(s) from ${this.base} (${fetched} re-fetched)`);
161
- }
162
-
163
- has(name) { return this.cache.has(name); }
164
-
165
- // The documents held here. Callers that keep one document per thing — a
166
- // connected account, say — list them with this rather than a fixed name.
167
- names() { return [...this.cache.keys()]; }
168
-
169
- read(name, fallback) {
170
- return this.cache.has(name) ? structuredClone(this.cache.get(name)) : fallback;
171
- }
172
-
173
- write(name, obj) {
174
- this.cache.set(name, structuredClone(obj));
175
- if (!this.storage) return;
176
- // The same bytes the pod already holds are not sent again. A like, a
177
- // bookmark, a sweep that found nothing new each used to upload the whole
178
- // timeline index and the whole people cache, unchanged — the largest
179
- // write on the pod, on every small action. A write that had been armed
180
- // and is now undone is unarmed with it.
181
- if (this.lastText.get(name) === serialise(obj)) {
182
- clearTimeout(this.timers.get(name)); this.timers.delete(name); this.dirty.delete(name);
183
- return;
184
- }
185
- // Held: record it and leave the writing to the commit boundary the caller
186
- // already has. See hold().
187
- if (this._held) { this.dirty.add(name); return; }
188
- this._arm(name);
189
- }
190
-
191
- _arm(name) {
192
- clearTimeout(this.timers.get(name));
193
- this.timers.set(name, setTimeout(() => { this.timers.delete(name); this._put(name); }, PUT_DEBOUNCE_MS));
194
- this.timers.get(name).unref?.();
195
- }
196
-
197
- // Suspend the debounce for the length of a sweep.
198
- //
199
- // 300ms coalesces writes that arrive together, and the inbox drain's never
200
- // do: every handler awaits a signed fetch to somebody else's server first, so
201
- // each item's timer fires before the next item is even read. statuses.json,
202
- // actors.json and notifications.json were therefore serialized and written
203
- // WHOLE once per item — fifty times in a fifty-item sweep, where the drain
204
- // already commits every ten.
205
- //
206
- // Nested, because a drain can run a handler that starts another. release()
207
- // re-arms anything still dirty rather than dropping it, so a write made by
208
- // something else while the drain held the store is never stranded.
209
- hold() { this._held = (this._held || 0) + 1; }
210
-
211
- release() {
212
- if (this._held) this._held -= 1;
213
- if (this._held) return;
214
- for (const name of [...this.dirty]) { this.dirty.delete(name); this._arm(name); }
215
- }
216
-
217
- // Resolves true when the document is on the pod, false when it is not — a
218
- // caller that is about to destroy the only other copy of something needs to
219
- // be able to tell. `chain` stays the bare serializer; the boolean rides on
220
- // the returned promise so one failure cannot poison the queue.
221
- _put(name) {
222
- const done = this.chain.then(async () => {
223
- const body = serialise(this.cache.get(name));
224
- for (let attempt = 1; attempt <= PUT_RETRIES; attempt++) {
225
- // A storage that throws is a bug, not a hiccup — but it must not
226
- // escape into the write queue, where it would look like success.
227
- const r = await this.storage.write(name, body, 'application/json')
228
- .catch(e => ({ ok: false, retry: false, why: e.message }));
229
- if (r.ok) { this.lastText.set(name, body); return true; }
230
- // The storage says whether trying again could possibly help: a pod's
231
- // 4xx is an answer rather than a hiccup, and a directory's EACCES will
232
- // still be an EACCES in two seconds.
233
- if (!r.retry) { this.log(`state write ${name} refused (${r.why}) — not retrying`); return false; }
234
- if (attempt === PUT_RETRIES) { this.log(`state write ${name} gave up: ${r.why}`); return false; }
235
- const ladder = Math.min(attempt * 2000, 30_000);
236
- await new Promise(res => setTimeout(res, r.retryAfterMs || Math.round(ladder * (0.8 + Math.random() * 0.4))));
237
- }
238
- return false;
239
- });
240
- // Record the outcome. A write whose debounce fires on its own is not in
241
- // any caller's hands: `chain` waits for it but throws its result away, so
242
- // commit() reported a clean sweep for a document that had been refused —
243
- // and commit()'s one caller is the inbox drain, about to delete the pod's
244
- // only copy of what that document describes.
245
- const tracked = done.then((ok) => { this.verdicts.set(name, ok); return ok; });
246
- this.chain = tracked.then(() => {}, () => {});
247
- return tracked;
248
- }
249
-
250
- // Remove a state doc from the cache AND the pod (used when key material
251
- // migrates to the local machine — leaving the copy behind would defeat it).
252
- async remove(name) {
253
- this.cache.delete(name);
254
- this.lastText.delete(name);
255
- clearTimeout(this.timers.get(name));
256
- this.timers.delete(name);
257
- if (!this.storage) return true;
258
- return this.storage.remove(name);
259
- }
260
-
261
- // Force every pending write out NOW and say whether they all landed.
262
- // The caller that needs this is the inbox drain: taking an item out of the
263
- // pod's inbox is a destructive read, so it must not happen until the result
264
- // of handling it is written down. With no storage the store is pure memory
265
- // and there is nothing to land, so that counts as written.
266
- async commit() {
267
- const pending = [];
268
- // Held names too, or a sweep that suspended the debounce would delete the
269
- // pod's copy of an item whose result had not been written down — the one
270
- // thing commit() exists to prevent.
271
- for (const name of new Set([...this.timers.keys(), ...this.dirty])) {
272
- const t = this.timers.get(name);
273
- if (t) { clearTimeout(t); this.timers.delete(name); }
274
- this.dirty.delete(name);
275
- pending.push(this._put(name));
276
- }
277
- await this.chain; // includes anything already in flight
278
- await Promise.all(pending);
279
- // Every verdict since the last commit, forced and self-fired alike. Cleared
280
- // once read: a refusal is reported to the caller that can act on it, and
281
- // does not then condemn every later commit.
282
- const landed = [...this.verdicts.values()].every(Boolean);
283
- this.verdicts.clear();
284
- return landed;
285
- }
286
-
287
- // Flush pending debounced writes (shutdown path). Same work, result ignored.
288
- async flush() { await this.commit(); }
289
-
290
- // ---- the domain helpers, unchanged from dk's Store ----
291
-
292
- // config: { remotePod, handle, name, issuer } (credential lives ONLY in
293
- // the local credential file, never in pod state)
294
- getConfig() { return this.read('config.json', null); }
295
- setConfig(cfg) { this.write('config.json', cfg); }
296
-
297
- // queue: [{ inbox, activity, attempts, nextAt }]
298
- getQueue() { return this.read('queue.json', []); }
299
- setQueue(q) { this.write('queue.json', q); }
300
-
301
- // blocklist: { domains: ["spam.example", ...], actors: ["https://host/actor", ...] }
302
- // Two granularities because a whole instance is usually the wrong unit: one
303
- // bad neighbour should not cost you everyone else on their server.
304
- getBlocklist() {
305
- const b = this.read('blocklist.json', {});
306
- return { domains: b.domains || [], actors: b.actors || [] };
307
- }
308
- // Blocking someone ends their following as well: a blocked actor, or anyone
309
- // on a blocked domain, is no longer delivered to. The mark dropFollower leaves
310
- // keeps a reconcile from bringing them back.
311
- setBlocklist(b) {
312
- this.write('blocklist.json', b);
313
- const c = this.getContacts();
314
- const gone = c.followers.filter(f => f.actor && this.isBlocked(f.actor)).map(f => f.actor);
315
- if (!gone.length) return;
316
- for (const actor of gone) dropFollower(c, actor, 'blocked');
317
- this.setContacts(c);
318
- }
319
- // Takes an actor URL or an object URL: the actor list only ever matches the
320
- // former, the domain list matches either.
321
- isBlocked(url) {
322
- let host;
323
- try { host = new URL(url).hostname; } catch { return true; } // unparsable → treat as hostile
324
- const { domains, actors } = this.getBlocklist();
325
- if (actors.includes(url)) return true;
326
- return domains.some(d => host === d || host.endsWith('.' + d));
327
- }
328
-
329
- // contacts: { followers: [{actor, inbox, sharedInbox}], following: [{actor, inbox, accepted}] }
330
- getContacts() { return this.read('contacts.json', { followers: [], following: [] }); }
331
- setContacts(c) { this.write('contacts.json', c); }
332
-
333
- // muted: { actors: [...] } — members whose posts a group declines to carry.
334
- // A group cannot force an unfollow, so declining to amplify is the only
335
- // lever it actually holds.
336
- getMuted() { return this.read('muted.json', { actors: [] }); }
337
- setMuted(m) { this.write('muted.json', m); }
338
-
339
- // The client's own reading arrangements — lists, keyword filters, posts
340
- // waiting to be published. None of it federates.
341
- getLists() { return this.read('lists.json', []); }
342
- setLists(l) { this.write('lists.json', l); }
343
- getFilters() { return this.read('filters.json', []); }
344
- setFilters(f) { this.write('filters.json', f); }
345
- getScheduled() { return this.read('scheduled.json', []); }
346
- setScheduled(s) { this.write('scheduled.json', s); }
347
-
348
- // pending: [{ noteId, actor, at }] — posts a reviewed group has ingested but
349
- // not carried, awaiting the operator.
350
- getPending() { return this.read('pending.json', []); }
351
- setPending(p) { this.write('pending.json', p); }
352
-
353
- // requests: [{ actor, inbox, sharedInbox, activity, at }] — Follows a group
354
- // with approveJoins has neither accepted nor rejected. The whole Follow is
355
- // kept because the Accept or Reject has to name it.
356
- getRequests() { return this.read('requests.json', []); }
357
- setRequests(r) { this.write('requests.json', r); }
358
-
359
- // dead letters: inbox items that failed verification or exhausted retries —
360
- // kept for inspection (GET /deadletter) instead of being destroyed.
361
- getDeadLetters() { return this.read('deadletter.json', []); }
362
- addDeadLetter(entry) {
363
- const dl = this.getDeadLetters();
364
- dl.unshift({ at: new Date().toISOString(), ...entry });
365
- this.write('deadletter.json', dl.slice(0, 200));
366
- }
367
-
368
- // statuses index: operational mirror of what lives in the pod as RDF, in
369
- // arrival order — the Mastodon-API facade serves timelines from this.
370
- // [{ noteId, actor, content, published, inReplyTo, kind: 'timeline'|'post'|'tag' }]
371
- getStatuses() { return this.read('statuses.json', []); }
372
- // A count must not pay for a clone: nodeinfo and the instance document are
373
- // public and answer strangers, so they read the cache directly.
374
- countStatuses(kind = null) {
375
- const all = this.cache.get('statuses.json') || [];
376
- return kind === null ? all.length : all.reduce((n, s) => n + (s.kind === kind ? 1 : 0), 0);
377
- }
378
- addStatus(s) {
379
- const all = this.getStatuses();
380
- const at = all.findIndex(x => x.noteId === s.noteId);
381
- if (at >= 0) return this._mergeStatus(all, at, s);
382
- // Same reason as cacheActor: statuses.json is serialized whole on every
383
- // change, and remote content is only bounded by the 5 MB fetch ceiling.
384
- if (typeof s.content === 'string' && s.content.length > MAX_CONTENT) {
385
- s = { ...s, content: clamp(s.content, MAX_CONTENT), truncated: true };
386
- }
387
- all.unshift(s);
388
- this.write('statuses.json', all.slice(0, 1000));
389
- this.onEvent?.('status', s); // streaming subscribers
390
- return { added: true, merged: false, status: s };
391
- }
392
-
393
- // The same post arrives twice when two of the owner's accounts follow its
394
- // author, and the second arrival is the only record that the other one saw
395
- // it. Merged in place — the per-kind prune tails take the tail to be the
396
- // oldest — and with no stream event, which would show the post twice.
397
- _mergeStatus(all, at, s) {
398
- const row = all[at];
399
- const known = new Set((row.sourceAccts || []).map(v => v.acct));
400
- const fresh = (s.sourceAccts || []).filter(v => v && !known.has(v.acct));
401
- // Only our own verified intake may raise a row's kind. A general ladder
402
- // would let a source that merely SAW a post promote a stranger's mention
403
- // into the home timeline, which is the route tagfeed had to close.
404
- const first = (k) => k === 'post' || k === 'timeline';
405
- const raise = first(s.kind) && !first(row.kind);
406
- if (!fresh.length && !raise) return { added: false, merged: false, status: row };
407
- all[at] = {
408
- ...row,
409
- ...(fresh.length ? { sourceAccts: [...(row.sourceAccts || []), ...fresh] } : {}),
410
- ...(raise ? { kind: s.kind, ...(s.slug ? { slug: s.slug } : {}) } : {}),
411
- };
412
- this.write('statuses.json', all);
413
- return { added: false, merged: true, status: all[at] };
414
- }
415
- updateStatus(noteId, patch) {
416
- const all = this.getStatuses();
417
- const i = all.findIndex(x => x.noteId === noteId);
418
- if (i < 0) return null;
419
- all[i] = { ...all[i], ...patch };
420
- this.write('statuses.json', all);
421
- return all[i];
422
- }
423
- removeStatus(noteId) {
424
- this.write('statuses.json', this.getStatuses().filter(x => x.noteId !== noteId));
425
- }
426
-
427
- // notifications: what other actors did to us — the facade serves
428
- // /api/v1/notifications from this. [{ id, type, actor, noteId?, at }]
429
- // The id is a content hash, so a re-delivered activity dedupes.
430
- getNotifications() { return this.read('notifications.json', []); }
431
-
432
- // `unverified` marks one whose actor nothing vouched for — a Like or Announce
433
- // arrives with no signature and, unlike a Create, has no object at the
434
- // sender's origin to re-read, so the actor is whatever the body claimed.
435
- //
436
- // It decides what the cap evicts. The id is a content hash, so changing one
437
- // character of the actor gives a fresh entry: ~500 appends into the pod's
438
- // public-append inbox used to push out every real favourite, boost, mention
439
- // and follow request, and notifications are the one thing nothing can rebuild
440
- // — not from the pod, not from the RDF. Unverified entries are dropped first
441
- // now, so a flood can only ever evict itself, and a stranger's genuine
442
- // favourite still shows up, which is what recording them at all is for.
443
- // What an Undo unsays: the favourite or boost it names, matched by a
444
- // predicate on what was recorded. Returns how many went.
445
- removeNotifications(pred) {
446
- const all = this.getNotifications();
447
- const kept = all.filter(n => !pred(n));
448
- if (kept.length !== all.length) this.write('notifications.json', kept);
449
- return all.length - kept.length;
450
- }
451
-
452
- addNotification(n) {
453
- const all = this.getNotifications();
454
- const id = crypto.createHash('sha256').update(JSON.stringify(n)).digest('hex').slice(0, 16);
455
- if (all.some(x => x.id === id)) return;
456
- const entry = { id, at: new Date().toISOString(), ...n };
457
- all.unshift(entry);
458
- let kept = all;
459
- if (kept.length > NOTIFICATIONS_MAX) {
460
- const solid = kept.filter(x => !x.unverified);
461
- const loose = kept.filter(x => x.unverified);
462
- kept = solid.length >= NOTIFICATIONS_MAX
463
- ? solid.slice(0, NOTIFICATIONS_MAX)
464
- : [...solid, ...loose.slice(0, NOTIFICATIONS_MAX - solid.length)]
465
- .sort((a, b) => String(b.at).localeCompare(String(a.at)));
466
- }
467
- this.write('notifications.json', kept);
468
- this.onEvent?.('notification', entry); // streaming subscribers
469
- }
470
-
471
- // uploaded media registry: opaque id → { url, mediaType, description }
472
- getMedia() { return this.read('media.json', {}); }
473
- setMedia(id, entry) {
474
- const m = this.getMedia();
475
- m[id] = entry;
476
- this.write('media.json', m);
477
- }
478
-
479
- // actor-doc cache for account rendering (display name, avatar).
480
- //
481
- // Everything here comes from a document at someone else's origin, and the
482
- // facade serves `summary` back to the client as a status account's `note`,
483
- // which every Mastodon client renders as HTML. Note CONTENT was sanitized at
484
- // all four of its entry points and this, the other thing a remote party
485
- // writes, was not — so looking anyone up handed their markup to the client.
486
- // Sanitized at the boundary where it enters our store, so nothing downstream
487
- // has to remember. Names are plain text and are stripped outright.
488
- getActors() { return this.read('actors.json', {}); }
489
- cacheActor(url, doc) {
490
- const a = this.getActors();
491
- const known = a[url];
492
- a[url] = {
493
- name: clamp(plainText(doc.name || doc.preferredUsername || ''), MAX_NAME),
494
- preferredUsername: clamp(plainText(doc.preferredUsername || ''), MAX_NAME),
495
- icon: safeUrl(typeof doc.icon === 'object' ? doc.icon?.url : doc.icon),
496
- image: safeUrl(typeof doc.image === 'object' ? doc.image?.url : doc.image),
497
- summary: clamp(sanitizeHtml(doc.summary || ''), MAX_SUMMARY),
498
- // A Group is an actor too, and a client that cannot tell shows it as a
499
- // person. The counts are NOT in this document — they are the collections'
500
- // totalItems, filled in only when someone asks about this actor by name.
501
- type: doc.type || 'Person',
502
- followers: doc.followers || null,
503
- following: doc.following || null,
504
- ...(known?.counts ? { counts: known.counts } : {}),
505
- fetchedAt: new Date().toISOString(),
506
- };
507
- // Fetched again and found the same: nothing to write. Only the time of
508
- // asking differs, and that is not worth the whole cache going to the pod.
509
- if (known && sameButWhen(known, a[url])) { a[url] = known; return; }
510
- this.write('actors.json', prune(a, ACTOR_CACHE_MAX, this.getContacts()));
511
- }
512
-
513
- // @user@host for an actor we have cached. Null when we have not, rather than
514
- // a last-segment guess off the URL: that guess is what rendered a group as
515
- // @actor@host, because our own actors all end in /actor.
516
- handleOf(actorUrl) {
517
- const user = this.getActors()[actorUrl]?.preferredUsername;
518
- if (!user) return null;
519
- try { return `@${user}@${new URL(actorUrl).host}`; } catch { return null; }
520
- }
521
-
522
- // Mastodon-API opaque ids ↔ URLs (snac-style: hashes are fine for clients).
523
- getIds() { return this.read('ids.json', {}); }
524
- // The id is a hash OF the url, so the mapping is computable — the scan was
525
- // only ever finding what the hash already tells us. It ran on the status
526
- // render path, once per rendered status, over a map that never shrinks.
527
- //
528
- // The map is still kept, because urlFor has to answer for ids a client is
529
- // still holding, and it is still consulted first so an entry written under
530
- // some older scheme keeps resolving.
531
- idFor(url) {
532
- const id = crypto.createHash('sha256').update(url).digest('hex').slice(0, 16);
533
- const ids = this.getIds();
534
- if (ids[id] === url) return id; // already known, nothing to write
535
- const legacy = Object.entries(ids).find(([, u]) => u === url);
536
- if (legacy) return legacy[0]; // pre-hash entry a client may hold
537
- // In memory only. This runs on the RENDER path — once per status, per
538
- // account, per attachment, every time a timeline is drawn — and `write()`
539
- // is write-through, so on the browser build scrolling a timeline was a pod
540
- // PUT of this whole document. Nothing durable is lost: the id IS the hash
541
- // of the url, so idFor recomputes it identically on any later run, and the
542
- // map exists only so urlFor can still answer for ids minted under an older
543
- // scheme (which are loaded from the pod and never written here).
544
- ids[id] = url;
545
- this.cache.set('ids.json', ids);
546
- return id;
547
- }
548
- // The map first, for ids minted under an older scheme. Then, because the
549
- // id IS the hash of the url, whatever this store knows is scanned for the
550
- // url that hashes to it — actors, posts, contacts, requests, media. The
551
- // browser build's worker is stopped whenever it idles, and the in-memory
552
- // map went with it: a client clicking an account it had just been shown
553
- // reached a fresh worker that held the actor and could not name it.
554
- urlFor(id) {
555
- const ids = this.getIds();
556
- if (ids[id]) return ids[id];
557
- if (!/^[a-f0-9]{16}$/u.test(String(id))) return null;
558
- const hash = (u) => crypto.createHash('sha256').update(u).digest('hex').slice(0, 16);
559
- const seen = new Set();
560
- const candidates = function* (store) {
561
- for (const u of Object.keys(store.getActors())) yield u;
562
- for (const st of store.getStatuses()) {
563
- yield st.noteId; yield st.actor;
564
- for (const a of st.attachments || []) if (a?.url) yield a.url;
565
- }
566
- const c = store.getContacts();
567
- for (const f of [...c.followers, ...c.following]) if (f?.actor) yield f.actor;
568
- for (const r of store.getRequests()) if (r?.actor) yield r.actor;
569
- for (const u of Object.keys(store.getMedia())) yield u;
570
- };
571
- for (const u of candidates(this)) {
572
- if (typeof u !== 'string' || seen.has(u)) continue;
573
- seen.add(u);
574
- if (hash(u) === id) { ids[id] = u; this.cache.set('ids.json', ids); return u; }
575
- }
576
- return null;
577
- }
578
- }
@@ -1,71 +0,0 @@
1
- // wire-quotes.mjs — the documents and activities a quote is made of on the
2
- // wire (FEP-044f): the request to the quoted author, the authorization they
3
- // publish, and the Accept or Reject that answers. The quote terms a note
4
- // itself carries live in wire.mjs with the note.
5
-
6
- import crypto from 'node:crypto';
7
- import { AS_CTX, QUOTE_CTX, POLICY_CTX } from './wire.mjs';
8
-
9
- // The activity and object types, and the two terms an authorization names
10
- // its parties by — GoToSocial's, declared as Mastodon declares them.
11
- export const QUOTE_TYPES_CTX = {
12
- QuoteRequest: 'https://w3id.org/fep/044f#QuoteRequest',
13
- QuoteAuthorization: 'https://w3id.org/fep/044f#QuoteAuthorization',
14
- gts: 'https://gotosocial.org/ns#',
15
- interactingObject: { '@id': 'gts:interactingObject', '@type': '@id' },
16
- interactionTarget: { '@id': 'gts:interactionTarget', '@type': '@id' },
17
- };
18
-
19
- // FEP-044f: asking the quoted post's author to allow the quote. The whole
20
- // quoting note rides along as the instrument, so their server can see what
21
- // is being asked without fetching it; `object` is their post.
22
- export function quoteRequestActivity({ urls, note, quoted, quotedActor, serial }) {
23
- const { '@context': ctx, ...instrument } = note; // eslint-disable-line no-unused-vars
24
- return {
25
- '@context': [AS_CTX, { ...QUOTE_CTX, ...POLICY_CTX, QuoteRequest: QUOTE_TYPES_CTX.QuoteRequest }],
26
- id: urls.actor + '#quote-request-' + serial,
27
- type: 'QuoteRequest',
28
- actor: urls.actor,
29
- to: [quotedActor],
30
- object: quoted,
31
- instrument,
32
- };
33
- }
34
-
35
- // The authorization a quoted author publishes (FEP-044f): a document of its
36
- // own, at the quoted post's origin, naming the quoting post and the quoted
37
- // one. A receiver checks the quote by fetching this and comparing both.
38
- export function quoteAuthorizationId(noteId, instrumentId) {
39
- return noteId + '-quote-' + crypto.createHash('sha256').update(String(instrumentId)).digest('hex').slice(0, 16);
40
- }
41
-
42
- export function quoteAuthorizationDoc({ urls, id, instrument, target }) {
43
- return {
44
- '@context': [AS_CTX, QUOTE_TYPES_CTX],
45
- id,
46
- type: 'QuoteAuthorization',
47
- attributedTo: urls.actor,
48
- interactingObject: instrument,
49
- interactionTarget: target,
50
- };
51
- }
52
-
53
- // The answer to a QuoteRequest: Accept, carrying the authorization as its
54
- // result, or Reject. The request is echoed by its id, type, actor, object
55
- // and the instrument's id — enough for the asker to match it, and nothing a
56
- // stranger wrote is repeated whole.
57
- export function quoteAnswerActivity({ urls, type, request, result = null, to, serial }) {
58
- const idOf = (v) => (typeof v === 'string' ? v : v?.id);
59
- return {
60
- '@context': [AS_CTX, { QuoteRequest: QUOTE_TYPES_CTX.QuoteRequest }],
61
- id: urls.actor + '#' + type.toLowerCase() + '-' + serial,
62
- type,
63
- actor: urls.actor,
64
- to: [to],
65
- object: {
66
- id: idOf(request), type: 'QuoteRequest', actor: idOf(request?.actor),
67
- object: idOf(request?.object), instrument: idOf(request?.instrument),
68
- },
69
- ...(result ? { result } : {}),
70
- };
71
- }