fedipod-server 0.29.0 → 0.31.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 (444) hide show
  1. package/README.md +16 -6
  2. package/dist/claims.d.ts +2 -0
  3. package/dist/claims.js +13 -4
  4. package/dist/door.d.ts +4 -0
  5. package/dist/door.js +31 -0
  6. package/dist/fedipod.d.ts +15 -0
  7. package/dist/fedipod.js +13 -0
  8. package/dist/front.d.ts +3 -0
  9. package/dist/front.js +92 -0
  10. package/dist/handler.d.ts +36 -101
  11. package/dist/handler.js +93 -644
  12. package/dist/handler.jsonld +2 -30
  13. package/dist/identities.d.ts +71 -0
  14. package/dist/identities.js +236 -0
  15. package/dist/mounts.d.ts +43 -0
  16. package/dist/mounts.js +123 -0
  17. package/dist/opt-in.d.ts +46 -0
  18. package/dist/opt-in.js +176 -0
  19. package/dist/private-read.d.ts +4 -0
  20. package/dist/private-read.js +34 -0
  21. package/package.json +12 -20
  22. package/lib/client/c2s.mjs +0 -636
  23. package/lib/client/localapi.mjs +0 -87
  24. package/lib/client/masto/accounts.mjs +0 -310
  25. package/lib/client/masto/body.mjs +0 -156
  26. package/lib/client/masto/index.mjs +0 -206
  27. package/lib/client/masto/instance.mjs +0 -112
  28. package/lib/client/masto/media.mjs +0 -79
  29. package/lib/client/masto/oauth.mjs +0 -656
  30. package/lib/client/masto/render.mjs +0 -518
  31. package/lib/client/masto/statuses.mjs +0 -372
  32. package/lib/client/masto/timelines.mjs +0 -438
  33. package/lib/client/oidc-auth.mjs +0 -62
  34. package/lib/client/streaming.mjs +0 -154
  35. package/lib/client/webpush.mjs +0 -98
  36. package/lib/connections/acctfeed.mjs +0 -257
  37. package/lib/connections/atproto.mjs +0 -309
  38. package/lib/connections/bskyfeed.mjs +0 -243
  39. package/lib/connections/bskygroup.mjs +0 -162
  40. package/lib/connections/fediacct.mjs +0 -253
  41. package/lib/connections/import.mjs +0 -357
  42. package/lib/connections/tagfeed.mjs +0 -212
  43. package/lib/connections/vault.mjs +0 -114
  44. package/lib/core/as2.mjs +0 -206
  45. package/lib/core/contexts/activitystreams.json +0 -379
  46. package/lib/core/contexts/anno.json +0 -126
  47. package/lib/core/contexts/did-v1.json +0 -57
  48. package/lib/core/contexts/fep-5711.json +0 -36
  49. package/lib/core/contexts/gotosocial.json +0 -86
  50. package/lib/core/contexts/identity-v1.json +0 -152
  51. package/lib/core/contexts/index.mjs +0 -63
  52. package/lib/core/contexts/join-lemmy.json +0 -33
  53. package/lib/core/contexts/joinmastodon.json +0 -28
  54. package/lib/core/contexts/litepub-0.1.json +0 -60
  55. package/lib/core/contexts/map.json +0 -19
  56. package/lib/core/contexts/miscellany.json +0 -19
  57. package/lib/core/contexts/quotes.json +0 -18
  58. package/lib/core/contexts/schemaorg.json +0 -8845
  59. package/lib/core/contexts/security-data-integrity-v1.json +0 -78
  60. package/lib/core/contexts/security-data-integrity-v2.json +0 -81
  61. package/lib/core/contexts/security-multikey-v1.json +0 -35
  62. package/lib/core/contexts/security-v1.json +0 -74
  63. package/lib/core/contexts/webfinger.json +0 -10
  64. package/lib/core/deliver.mjs +0 -387
  65. package/lib/core/graphview.mjs +0 -327
  66. package/lib/core/intake/activities.mjs +0 -649
  67. package/lib/core/intake/activity.mjs +0 -318
  68. package/lib/core/intake/channel.mjs +0 -144
  69. package/lib/core/intake/group.mjs +0 -258
  70. package/lib/core/intake/index.mjs +0 -732
  71. package/lib/core/intake/notes.mjs +0 -367
  72. package/lib/core/intake/verify.mjs +0 -142
  73. package/lib/core/keys.mjs +0 -153
  74. package/lib/core/lease.mjs +0 -206
  75. package/lib/core/place.mjs +0 -82
  76. package/lib/core/polls.mjs +0 -105
  77. package/lib/core/profile-page.mjs +0 -104
  78. package/lib/core/proof.mjs +0 -79
  79. package/lib/core/publisher/collections.mjs +0 -272
  80. package/lib/core/publisher/index.mjs +0 -522
  81. package/lib/core/publisher/notes.mjs +0 -519
  82. package/lib/core/publisher/own.mjs +0 -143
  83. package/lib/core/publisher/questions.mjs +0 -241
  84. package/lib/core/publisher/restore.mjs +0 -224
  85. package/lib/core/shapes/activitystreams.ttl +0 -157
  86. package/lib/core/shapes/index.mjs +0 -132
  87. package/lib/core/shapes/shapes-text.mjs +0 -13
  88. package/lib/core/social.mjs +0 -561
  89. package/lib/core/storage.mjs +0 -176
  90. package/lib/core/store.mjs +0 -578
  91. package/lib/core/wire-quotes.mjs +0 -71
  92. package/lib/core/wire.mjs +0 -925
  93. package/lib/device/account.mjs +0 -87
  94. package/lib/device/admin/index.mjs +0 -13
  95. package/lib/device/admin/origins.mjs +0 -35
  96. package/lib/device/admin/routes/connections.mjs +0 -144
  97. package/lib/device/admin/routes/gateway.mjs +0 -199
  98. package/lib/device/admin/routes/lifecycle.mjs +0 -200
  99. package/lib/device/admin/routes/owner.mjs +0 -322
  100. package/lib/device/admin/routes/setup.mjs +0 -428
  101. package/lib/device/admin/routes/social.mjs +0 -188
  102. package/lib/device/admin/server.mjs +0 -95
  103. package/lib/device/admin/static.mjs +0 -247
  104. package/lib/device/admin/surface.mjs +0 -315
  105. package/lib/device/certs.mjs +0 -281
  106. package/lib/device/cli/commands/account.mjs +0 -586
  107. package/lib/device/cli/commands/run.mjs +0 -278
  108. package/lib/device/cli/commands/service.mjs +0 -221
  109. package/lib/device/cli/commands/setup.mjs +0 -489
  110. package/lib/device/cli/commands/state.mjs +0 -561
  111. package/lib/device/cli/context.mjs +0 -288
  112. package/lib/device/export-collections.mjs +0 -161
  113. package/lib/device/gateway-move.mjs +0 -58
  114. package/lib/device/home.mjs +0 -161
  115. package/lib/device/migrate.mjs +0 -115
  116. package/lib/device/ports.mjs +0 -27
  117. package/lib/device/remote.mjs +0 -54
  118. package/lib/device/setup.mjs +0 -496
  119. package/lib/device/update.mjs +0 -96
  120. package/lib/gateway/caches.mjs +0 -36
  121. package/lib/gateway/directory.mjs +0 -90
  122. package/lib/gateway/front-core.mjs +0 -966
  123. package/lib/gateway/gateway-core.mjs +0 -244
  124. package/lib/gateway/headers.mjs +0 -49
  125. package/lib/gateway/httpsig.mjs +0 -159
  126. package/lib/gateway/notices.mjs +0 -84
  127. package/lib/gateway/quiet.mjs +0 -189
  128. package/lib/gateway/relay-extras.mjs +0 -89
  129. package/lib/gateway/token-claims.mjs +0 -16
  130. package/lib/pod/README.md +0 -61
  131. package/lib/pod/actor.mjs +0 -96
  132. package/lib/pod/collection.mjs +0 -77
  133. package/lib/pod/containers.mjs +0 -140
  134. package/lib/pod/discovery.mjs +0 -46
  135. package/lib/pod/featured.mjs +0 -18
  136. package/lib/pod/followers.mjs +0 -24
  137. package/lib/pod/following.mjs +0 -10
  138. package/lib/pod/http.mjs +0 -74
  139. package/lib/pod/inbox.mjs +0 -166
  140. package/lib/pod/links.mjs +0 -35
  141. package/lib/pod/location.mjs +0 -52
  142. package/lib/pod/media.mjs +0 -27
  143. package/lib/pod/notes.mjs +0 -78
  144. package/lib/pod/notifications.mjs +0 -89
  145. package/lib/pod/outbox.mjs +0 -18
  146. package/lib/pod/package.json +0 -19
  147. package/lib/pod/policy.mjs +0 -58
  148. package/lib/pod/private.mjs +0 -19
  149. package/lib/pod/root.mjs +0 -104
  150. package/lib/pod/state.mjs +0 -40
  151. package/lib/pod/transport.mjs +0 -674
  152. package/lib/pod/type-index.mjs +0 -101
  153. package/lib/pod/urls.mjs +0 -91
  154. package/lib/server/embed.mjs +0 -424
  155. package/lib/session/README.md +0 -137
  156. package/lib/session/demo.html +0 -94
  157. package/lib/session/fedi-account.mjs +0 -417
  158. package/lib/session/fedi-login.mjs +0 -123
  159. package/lib/session/oidc-session.mjs +0 -233
  160. package/lib/session/package.json +0 -49
  161. package/lib/shared/guard.mjs +0 -213
  162. package/lib/shared/links.mjs +0 -9
  163. package/lib/shared/safefetch.mjs +0 -235
  164. package/lib/shared/ua.mjs +0 -29
  165. package/phanpy/dist/404.html +0 -40
  166. package/phanpy/dist/_headers +0 -2
  167. package/phanpy/dist/apple-touch-icon.png +0 -0
  168. package/phanpy/dist/assets/ICONS-DIPJeU0C.js +0 -2
  169. package/phanpy/dist/assets/ICONS-DIPJeU0C.js.map +0 -1
  170. package/phanpy/dist/assets/ScheduledAtField-D-Abfo4J.js +0 -183
  171. package/phanpy/dist/assets/ScheduledAtField-D-Abfo4J.js.map +0 -1
  172. package/phanpy/dist/assets/Temml-CCjoacWt.woff2 +0 -0
  173. package/phanpy/dist/assets/__vite-browser-external-C7iut881.js +0 -2
  174. package/phanpy/dist/assets/__vite-browser-external-C7iut881.js.map +0 -1
  175. package/phanpy/dist/assets/boosts-carousel-BiOaNdMT.jpg +0 -0
  176. package/phanpy/dist/assets/catch-up-O5q5C75Z.png +0 -0
  177. package/phanpy/dist/assets/chunk-aKtaBQYM.js +0 -1
  178. package/phanpy/dist/assets/compose-CFEx7wb4.js +0 -2
  179. package/phanpy/dist/assets/compose-CFEx7wb4.js.map +0 -1
  180. package/phanpy/dist/assets/compose-ZJN5xSb4.js +0 -35
  181. package/phanpy/dist/assets/compose-ZJN5xSb4.js.map +0 -1
  182. package/phanpy/dist/assets/debug.module-YFQgwnIr.js +0 -52
  183. package/phanpy/dist/assets/debug.module-YFQgwnIr.js.map +0 -1
  184. package/phanpy/dist/assets/dom-p1YcJ5Rw.js +0 -2
  185. package/phanpy/dist/assets/dom-p1YcJ5Rw.js.map +0 -1
  186. package/phanpy/dist/assets/home-mobile-dark@2x-Dc-J8-SM.png +0 -0
  187. package/phanpy/dist/assets/home-mobile-light@2x-Bd3fod8m.png +0 -0
  188. package/phanpy/dist/assets/home-tablet-dark@2x-BgcNI8le.png +0 -0
  189. package/phanpy/dist/assets/home-tablet-light@2x-BW2RV9T6.png +0 -0
  190. package/phanpy/dist/assets/icons/add-circle-line-WD0mwORD.js +0 -1
  191. package/phanpy/dist/assets/icons/alert-line-CpFUMf9s.js +0 -1
  192. package/phanpy/dist/assets/icons/android-2-line-Bzxnrd3S.js +0 -1
  193. package/phanpy/dist/assets/icons/announcement-line-5nwaJFJm.js +0 -1
  194. package/phanpy/dist/assets/icons/arrow-down-circle-line-a7Id9-Iv.js +0 -1
  195. package/phanpy/dist/assets/icons/arrow-down-line-B8uzXzq6.js +0 -1
  196. package/phanpy/dist/assets/icons/arrow-left-line-CGrGT9C-.js +0 -1
  197. package/phanpy/dist/assets/icons/arrow-right-line-CE-HERfX.js +0 -1
  198. package/phanpy/dist/assets/icons/arrow-to-up-line-CvdQjmcy.js +0 -1
  199. package/phanpy/dist/assets/icons/arrow-up-circle-line-DEparSNF.js +0 -1
  200. package/phanpy/dist/assets/icons/arrow-up-line-C7yBeugc.js +0 -1
  201. package/phanpy/dist/assets/icons/arrows-right-line-BtbXmJGP.js +0 -1
  202. package/phanpy/dist/assets/icons/at-line-CF66Bznk.js +0 -1
  203. package/phanpy/dist/assets/icons/attachment-line-BY8It-l9.js +0 -1
  204. package/phanpy/dist/assets/icons/blockquote-line-BS6pU_hm.js +0 -1
  205. package/phanpy/dist/assets/icons/board-line-C0UWXyA6.js +0 -1
  206. package/phanpy/dist/assets/icons/bookmark-line-1CtX5JLF.js +0 -1
  207. package/phanpy/dist/assets/icons/building-5-line-Dd59MzbO.js +0 -1
  208. package/phanpy/dist/assets/icons/bus-2-line-6_dvA0GG.js +0 -1
  209. package/phanpy/dist/assets/icons/calendar-day-line-Crpl_fmu.js +0 -1
  210. package/phanpy/dist/assets/icons/calendar-month-line-vommts2C.js +0 -1
  211. package/phanpy/dist/assets/icons/calendar-time-add-line-BU_DTlQk.js +0 -1
  212. package/phanpy/dist/assets/icons/camera-line-Cv69MEei.js +0 -1
  213. package/phanpy/dist/assets/icons/celebrate-line-B8DvIoi0.js +0 -1
  214. package/phanpy/dist/assets/icons/chart-bar-line-FK-QOmtF.js +0 -1
  215. package/phanpy/dist/assets/icons/chart-line-line-BGcJAFWw.js +0 -1
  216. package/phanpy/dist/assets/icons/chat-3-line-YFK2-hwz.js +0 -1
  217. package/phanpy/dist/assets/icons/check-circle-line-DsAxFr5-.js +0 -1
  218. package/phanpy/dist/assets/icons/clipboard-line-BBp364gJ.js +0 -1
  219. package/phanpy/dist/assets/icons/close-circle-line-CICdiuGz.js +0 -1
  220. package/phanpy/dist/assets/icons/close-line-DYrFWcjv.js +0 -1
  221. package/phanpy/dist/assets/icons/cloud-line-tiQAbah8.js +0 -1
  222. package/phanpy/dist/assets/icons/code-line-fAl8iiV3.js +0 -1
  223. package/phanpy/dist/assets/icons/comment-2-line-CR3g7WeB.js +0 -1
  224. package/phanpy/dist/assets/icons/copy-2-line-CLW69yF5.js +0 -1
  225. package/phanpy/dist/assets/icons/delete-2-line-B8xmbr4n.js +0 -1
  226. package/phanpy/dist/assets/icons/document-3-line-DBGNhAg2.js +0 -1
  227. package/phanpy/dist/assets/icons/document-line-C7mb9sb7.js +0 -1
  228. package/phanpy/dist/assets/icons/down-line-BGqSSThd.js +0 -1
  229. package/phanpy/dist/assets/icons/edit-4-line-DcgGaEhx.js +0 -1
  230. package/phanpy/dist/assets/icons/emoji-2-line-D8YgnM-y.js +0 -1
  231. package/phanpy/dist/assets/icons/exit-line-Dc07BS-x.js +0 -1
  232. package/phanpy/dist/assets/icons/external-link-line-BvpSkmjO.js +0 -1
  233. package/phanpy/dist/assets/icons/eye-2-line-CiWXx-f_.js +0 -1
  234. package/phanpy/dist/assets/icons/eye-close-line-BgTaSJ4P.js +0 -1
  235. package/phanpy/dist/assets/icons/filter-2-line-B_mR2usy.js +0 -1
  236. package/phanpy/dist/assets/icons/filter-line-C_nm-3cU.js +0 -1
  237. package/phanpy/dist/assets/icons/flag-1-line-C2HB05I0.js +0 -1
  238. package/phanpy/dist/assets/icons/forbid-circle-line-D3LTT9yd.js +0 -1
  239. package/phanpy/dist/assets/icons/formula-line-DLrt2d6j.js +0 -1
  240. package/phanpy/dist/assets/icons/grid-line-CgUmEikf.js +0 -1
  241. package/phanpy/dist/assets/icons/group-line-C5fnX0wU.js +0 -1
  242. package/phanpy/dist/assets/icons/hand-finger-2-line-BGgxbuL9.js +0 -1
  243. package/phanpy/dist/assets/icons/hashtag-line-CyTEEXBb.js +0 -1
  244. package/phanpy/dist/assets/icons/heart-crack-line-BZddbO2C.js +0 -1
  245. package/phanpy/dist/assets/icons/heart-line-CJ7K47J4.js +0 -1
  246. package/phanpy/dist/assets/icons/history-2-line-kWWvFzqv.js +0 -1
  247. package/phanpy/dist/assets/icons/history-line-bGQiwbTH.js +0 -1
  248. package/phanpy/dist/assets/icons/home-3-line-fRU1zeLG.js +0 -1
  249. package/phanpy/dist/assets/icons/information-line-iNk6-sOY.js +0 -1
  250. package/phanpy/dist/assets/icons/keyboard-line-DsCyuQNz.js +0 -1
  251. package/phanpy/dist/assets/icons/layout-4-line-CZkOX52Z.js +0 -1
  252. package/phanpy/dist/assets/icons/layout-5-line-B_znt5wO.js +0 -1
  253. package/phanpy/dist/assets/icons/left-line-eUw0tya4.js +0 -1
  254. package/phanpy/dist/assets/icons/lightning-line-DaKoDdix.js +0 -1
  255. package/phanpy/dist/assets/icons/link-2-line-DCqLmMA7.js +0 -1
  256. package/phanpy/dist/assets/icons/list-check-line-BAxosBh9.js +0 -1
  257. package/phanpy/dist/assets/icons/lock-line-CPoaXeUW.js +0 -1
  258. package/phanpy/dist/assets/icons/mail-line-B0P8aa1L.js +0 -1
  259. package/phanpy/dist/assets/icons/moon-line-CEqNAToP.js +0 -1
  260. package/phanpy/dist/assets/icons/more-1-fill-Dw2e5xab.js +0 -1
  261. package/phanpy/dist/assets/icons/more-3-line-BIiVjYTz.js +0 -1
  262. package/phanpy/dist/assets/icons/notification-line-BZZq2Gtu.js +0 -1
  263. package/phanpy/dist/assets/icons/pencil-line-CKh8-A1v.js +0 -1
  264. package/phanpy/dist/assets/icons/photo-album-line-Bnfg2u40.js +0 -1
  265. package/phanpy/dist/assets/icons/pin-line-DpTolyhs.js +0 -1
  266. package/phanpy/dist/assets/icons/play-fill-B0-G_8lX.js +0 -1
  267. package/phanpy/dist/assets/icons/qrcode-2-line-B6SPCaHn.js +0 -1
  268. package/phanpy/dist/assets/icons/quill-pen-line-B6LUrnCP.js +0 -1
  269. package/phanpy/dist/assets/icons/quote-left-fill-hQZ1cj_6.js +0 -1
  270. package/phanpy/dist/assets/icons/radar-line-CKyroTth.js +0 -1
  271. package/phanpy/dist/assets/icons/react-line-CoIKhhtK.js +0 -1
  272. package/phanpy/dist/assets/icons/refresh-2-line-D0mTF97S.js +0 -1
  273. package/phanpy/dist/assets/icons/right-line-B3KWJaqc.js +0 -1
  274. package/phanpy/dist/assets/icons/rocket-line-4KZl537D.js +0 -1
  275. package/phanpy/dist/assets/icons/round-fill-C69NTrZ0.js +0 -1
  276. package/phanpy/dist/assets/icons/round-line-B9k_1uo-.js +0 -1
  277. package/phanpy/dist/assets/icons/route-line-B28ReHcE.js +0 -1
  278. package/phanpy/dist/assets/icons/rows-4-line-CrBniagM.js +0 -1
  279. package/phanpy/dist/assets/icons/scan-line-DSf74YdP.js +0 -1
  280. package/phanpy/dist/assets/icons/search-2-line-B6geIU1z.js +0 -1
  281. package/phanpy/dist/assets/icons/settings-3-line-azJwK47w.js +0 -1
  282. package/phanpy/dist/assets/icons/settings-6-line-C_5zc5YR.js +0 -1
  283. package/phanpy/dist/assets/icons/share-2-line-D5MxWjrm.js +0 -1
  284. package/phanpy/dist/assets/icons/share-forward-line-Cp61fZzZ.js +0 -1
  285. package/phanpy/dist/assets/icons/sparkles-2-line-DYYyXTaW.js +0 -1
  286. package/phanpy/dist/assets/icons/sparkles-line-C2hY1s6I.js +0 -1
  287. package/phanpy/dist/assets/icons/time-line-BF3eet1v.js +0 -1
  288. package/phanpy/dist/assets/icons/transfer-4-line-oisfvTBw.js +0 -1
  289. package/phanpy/dist/assets/icons/translate-line-BFWol2Ce.js +0 -1
  290. package/phanpy/dist/assets/icons/unlock-line-X_x5vwv3.js +0 -1
  291. package/phanpy/dist/assets/icons/upload-3-line-DgzwUJeW.js +0 -1
  292. package/phanpy/dist/assets/icons/user-4-line-BVSynZCp.js +0 -1
  293. package/phanpy/dist/assets/icons/user-add-2-line-BlGS5Q8q.js +0 -1
  294. package/phanpy/dist/assets/icons/user-add-line-BbXdHG4z.js +0 -1
  295. package/phanpy/dist/assets/icons/user-edit-line-BFAHOV0X.js +0 -1
  296. package/phanpy/dist/assets/icons/user-follow-line-BXux0wSd.js +0 -1
  297. package/phanpy/dist/assets/icons/user-star-line-DdYlpHLU.js +0 -1
  298. package/phanpy/dist/assets/icons/user-warning-line-Cj43j0_E.js +0 -1
  299. package/phanpy/dist/assets/icons/user-x-line-DIDgNlPH.js +0 -1
  300. package/phanpy/dist/assets/icons/volume-line-Bs390KQt.js +0 -1
  301. package/phanpy/dist/assets/icons/volume-mute-line-DzlzIqFZ.js +0 -1
  302. package/phanpy/dist/assets/icons/walk-line-yNXfB-md.js +0 -1
  303. package/phanpy/dist/assets/icons/world-2-line-CenkGIwj.js +0 -1
  304. package/phanpy/dist/assets/icons/zoom-in-line-CaENUphf.js +0 -1
  305. package/phanpy/dist/assets/icons/zoom-out-line-BDNk0EWo.js +0 -1
  306. package/phanpy/dist/assets/instances-BNPptnn-.json +0 -333
  307. package/phanpy/dist/assets/locales/ar-SA-CfheRbCu.js +0 -1
  308. package/phanpy/dist/assets/locales/ca-ES-DHRW86Tz.js +0 -1
  309. package/phanpy/dist/assets/locales/cs-CZ-CKoqEuA1.js +0 -1
  310. package/phanpy/dist/assets/locales/de-DE-ByRPMRFy.js +0 -1
  311. package/phanpy/dist/assets/locales/eo-UY-CPC7wLCS.js +0 -1
  312. package/phanpy/dist/assets/locales/es-ES-dJa0Fd9t.js +0 -1
  313. package/phanpy/dist/assets/locales/eu-ES-C3djlPR2.js +0 -1
  314. package/phanpy/dist/assets/locales/fa-IR-DTT490Ck.js +0 -1
  315. package/phanpy/dist/assets/locales/fi-FI-B23MCPuz.js +0 -1
  316. package/phanpy/dist/assets/locales/fr-FR-BcaQsO5C.js +0 -1
  317. package/phanpy/dist/assets/locales/gl-ES-BR-CVKBN.js +0 -1
  318. package/phanpy/dist/assets/locales/he-IL-CfheRbCu.js +0 -1
  319. package/phanpy/dist/assets/locales/hu-HU-CfheRbCu.js +0 -1
  320. package/phanpy/dist/assets/locales/it-IT-C1136UFV.js +0 -1
  321. package/phanpy/dist/assets/locales/ja-JP-CRZmRAhD.js +0 -1
  322. package/phanpy/dist/assets/locales/kab-Cz8xH_TT.js +0 -1
  323. package/phanpy/dist/assets/locales/ko-KR-CG9UiY4o.js +0 -1
  324. package/phanpy/dist/assets/locales/lt-LT-CJcI6sHZ.js +0 -1
  325. package/phanpy/dist/assets/locales/nb-NO-CfheRbCu.js +0 -1
  326. package/phanpy/dist/assets/locales/nl-NL-DxN4Yc1t.js +0 -1
  327. package/phanpy/dist/assets/locales/oc-FR-CfheRbCu.js +0 -1
  328. package/phanpy/dist/assets/locales/pl-PL-DkwGXf6P.js +0 -1
  329. package/phanpy/dist/assets/locales/pseudo-LOCALE-CQ4XxkQ0.js +0 -1
  330. package/phanpy/dist/assets/locales/pt-BR-nGL0HdFS.js +0 -1
  331. package/phanpy/dist/assets/locales/pt-PT-Cp17aj2z.js +0 -1
  332. package/phanpy/dist/assets/locales/ru-RU-0Y8cIoaK.js +0 -1
  333. package/phanpy/dist/assets/locales/th-TH-CfheRbCu.js +0 -1
  334. package/phanpy/dist/assets/locales/tok-CfheRbCu.js +0 -1
  335. package/phanpy/dist/assets/locales/tr-TR-CfheRbCu.js +0 -1
  336. package/phanpy/dist/assets/locales/uk-UA-C5E3dFBf.js +0 -1
  337. package/phanpy/dist/assets/locales/zh-CN-BHJi7_et.js +0 -1
  338. package/phanpy/dist/assets/locales/zh-TW-CfheRbCu.js +0 -1
  339. package/phanpy/dist/assets/main-BdqNbG-a.js +0 -26
  340. package/phanpy/dist/assets/main-BdqNbG-a.js.map +0 -1
  341. package/phanpy/dist/assets/mock-home-DGhR__He.js +0 -2
  342. package/phanpy/dist/assets/mock-home-DGhR__He.js.map +0 -1
  343. package/phanpy/dist/assets/multi-column-ROck0NVt.jpg +0 -0
  344. package/phanpy/dist/assets/multi-hashtag-timeline-DH5INVHi.jpg +0 -0
  345. package/phanpy/dist/assets/nested-comments-thread-hv59kZLc.jpg +0 -0
  346. package/phanpy/dist/assets/polyfill-force-H8sUkLGp.js +0 -2
  347. package/phanpy/dist/assets/polyfill-force-H8sUkLGp.js.map +0 -1
  348. package/phanpy/dist/assets/pwa-viewport-ogKbF51K.js +0 -20
  349. package/phanpy/dist/assets/pwa-viewport-ogKbF51K.js.map +0 -1
  350. package/phanpy/dist/assets/style-B1iLPTPR.css +0 -2
  351. package/phanpy/dist/assets/temml-DuyIQMei.js +0 -97
  352. package/phanpy/dist/assets/temml-DuyIQMei.js.map +0 -1
  353. package/phanpy/dist/assets/tinyld.light.node-DtbBhLYC.js +0 -2
  354. package/phanpy/dist/assets/tinyld.light.node-DtbBhLYC.js.map +0 -1
  355. package/phanpy/dist/assets/year-in-posts-B2lywqdx.png +0 -0
  356. package/phanpy/dist/assets/year-in-posts-DJv_star.js +0 -3
  357. package/phanpy/dist/assets/year-in-posts-DJv_star.js.map +0 -1
  358. package/phanpy/dist/compose/index.html +0 -20
  359. package/phanpy/dist/favicon.ico +0 -0
  360. package/phanpy/dist/index.html +0 -73
  361. package/phanpy/dist/logo-192.png +0 -0
  362. package/phanpy/dist/logo-512.png +0 -0
  363. package/phanpy/dist/logo-badge-72.png +0 -0
  364. package/phanpy/dist/logo-maskable-512.png +0 -0
  365. package/phanpy/dist/logo-monochrome-512.png +0 -0
  366. package/phanpy/dist/logo-monochrome-maskable-512.png +0 -0
  367. package/phanpy/dist/manifest.webmanifest +0 -1
  368. package/phanpy/dist/og-image-2.jpg +0 -0
  369. package/phanpy/dist/og-image.png +0 -0
  370. package/phanpy/dist/robots.txt +0 -2
  371. package/phanpy/dist/sw.js +0 -2
  372. package/phanpy/dist/sw.js.map +0 -1
  373. package/phanpy/dist/version.json +0 -1
  374. package/run-agent.mjs +0 -899
  375. package/vendor/gate.cjs +0 -138
  376. package/vendor/idp-grant.cjs +0 -394
  377. package/vendor/jfetch.cjs +0 -16
  378. package/web/admin/actors.js +0 -147
  379. package/web/admin/bar.css +0 -126
  380. package/web/admin/bar.js +0 -99
  381. package/web/admin/client/client.js +0 -66
  382. package/web/admin/client/index.html +0 -69
  383. package/web/admin/client-pick.js +0 -79
  384. package/web/admin/common.js +0 -23
  385. package/web/admin/connections.js +0 -112
  386. package/web/admin/gateway.js +0 -133
  387. package/web/admin/group.js +0 -258
  388. package/web/admin/index.html +0 -612
  389. package/web/admin/notices-bar.js +0 -90
  390. package/web/admin/oauth-signin.mjs +0 -37
  391. package/web/admin/record.js +0 -393
  392. package/web/admin/setup/index.html +0 -257
  393. package/web/admin/setup/setup.js +0 -339
  394. package/web/admin/tokens.css +0 -68
  395. package/web/admin/upkeep.js +0 -177
  396. package/web/admin/window.css +0 -55
  397. package/web/admin/window.js +0 -136
  398. package/web/app/README.md +0 -81
  399. package/web/app/admin-facade.mjs +0 -588
  400. package/web/app/agent.mjs +0 -672
  401. package/web/app/atproto-browser.mjs +0 -86
  402. package/web/app/authorize_interaction.html +0 -56
  403. package/web/app/authorize_interaction.js +0 -69
  404. package/web/app/boot.mjs +0 -616
  405. package/web/app/deliver-relay.mjs +0 -129
  406. package/web/app/dist/boot.js +0 -34562
  407. package/web/app/dist/boot.js.map +0 -7
  408. package/web/app/dist/sw.js +0 -73456
  409. package/web/app/dist/sw.js.map +0 -7
  410. package/web/app/fediacct-browser.mjs +0 -146
  411. package/web/app/gateway-move.mjs +0 -60
  412. package/web/app/idb-kv.mjs +0 -35
  413. package/web/app/index.html +0 -258
  414. package/web/app/keys-browser.mjs +0 -71
  415. package/web/app/keystore.mjs +0 -82
  416. package/web/app/oidc-session.mjs +0 -6
  417. package/web/app/pod-auth.mjs +0 -248
  418. package/web/app/pod-remote.mjs +0 -84
  419. package/web/app/shims/_globals.mjs +0 -2
  420. package/web/app/shims/fedify-sig.mjs +0 -77
  421. package/web/app/shims/node-crypto.mjs +0 -113
  422. package/web/app/shims/node-fs.mjs +0 -17
  423. package/web/app/shims/node-path.mjs +0 -19
  424. package/web/app/shims/node-url.mjs +0 -7
  425. package/web/app/shims/prelude.js +0 -58
  426. package/web/app/shims/safefetch.mjs +0 -58
  427. package/web/app/shims/shapes-text.mjs +0 -8
  428. package/web/app/shims/web-push.mjs +0 -14
  429. package/web/app/signup.mjs +0 -357
  430. package/web/app/sw-src.mjs +0 -220
  431. package/web/app/update.js +0 -70
  432. package/web/front/admin.html +0 -55
  433. package/web/front/admin.js +0 -89
  434. package/web/front/install.sh +0 -69
  435. package/web/front/new-account.html +0 -35
  436. package/web/front/new-account.js +0 -13
  437. package/web/front/notices.html +0 -69
  438. package/web/front/notices.js +0 -107
  439. package/web/front/run.html +0 -64
  440. package/web/front/run.js +0 -221
  441. package/web/front/solid-oidc-client.js +0 -6
  442. package/web/icons/forum.svg +0 -11
  443. package/web/icons/gateway.svg +0 -14
  444. package/web/icons/local.svg +0 -11
@@ -1,732 +0,0 @@
1
- // intake.mjs — drains the remote pod's public-append inbox and applies side
2
- // effects. Inbound authenticity: LDN bodies don't carry the delivery's
3
- // HTTP-Signature headers, so instead of verifying signatures we VERIFY BY
4
- // DEREFERENCING — re-fetch the claimed object/actor from its origin (signed
5
- // GET, so authorized-fetch instances answer) and trust only what the origin
6
- // itself serves.
7
- //
8
- // Failure policy: a REJECTED item (verification says no) goes to the
9
- // dead-letter store and leaves the inbox; a FAILING item (exception —
10
- // network, remote 5xx) stays in the inbox for the next drain, and moves to
11
- // the dead-letter store after MAX_ITEM_ATTEMPTS. Nothing is silently
12
- // destroyed.
13
- //
14
- // Wake-up: WebSocketChannel2023 push on the inbox container (probe P4), plus
15
- // a poll every POLL_MS as fallback, plus a drain at startup.
16
- //
17
- // This file is the drain and the dispatcher. What it does with an activity
18
- // once it has one is in the modules beside it — activity.mjs (what an activity
19
- // is), channel.mjs (the push socket), verify.mjs (what to believe),
20
- // activities.mjs (one handler per type), group.mjs (FEP-1b12), notes.mjs (a
21
- // note on its way in) — each a set of functions taking the Intake as their
22
- // first argument, reached here through one-line delegations so that every
23
- // caller, and every test that overrides a method, sees one object.
24
-
25
- import * as podInbox from '../../pod/inbox.mjs';
26
- import { readCapped } from '../../shared/safefetch.mjs';
27
- import { trimActivity, sameIdentity, sameOrigin, httpUrl, reactionOf, MAX_EMOJIS, MAX_ITEM_BYTES, MAX_FORWARDS_PER_DRAIN } from './activity.mjs';
28
- import * as channel from './channel.mjs';
29
- import * as verify from './verify.mjs';
30
- import * as group from './group.mjs';
31
- import * as activities from './activities.mjs';
32
- import * as notes from './notes.mjs';
33
- import { readLenient } from '../as2.mjs';
34
- import { checkShapes, describeShapeFailure } from '../shapes/index.mjs';
35
- export { isContentType, trimActivity, sameIdentity, sameOrigin, sameSocketOrigin, httpUrl, authorOf } from './activity.mjs';
36
-
37
- const POLL_MS = 2 * 60_000; // fallback cadence when there is no push
38
- // With a live socket the poll is pure redundancy: it exists for the case where
39
- // push is down, so it slows right down while push is up.
40
- const POLL_PUSH_OK_MS = 10 * 60_000;
41
- // A container that times out will time out again in two minutes, and each
42
- // attempt holds one of the pod's workers for the full timeout. Sweeping stops
43
- // for a while instead, doubling up to half an hour.
44
- const DRAIN_COOLDOWN_MIN_MS = 2 * 60_000;
45
- const DRAIN_COOLDOWN_MAX_MS = 30 * 60_000;
46
- // Our DELETEs take the same container write lock as the deliveries arriving
47
- // into it — a gap between them keeps a sweep from convoying against inbound.
48
- const DELETE_GAP_MS = 150;
49
- const CHAIN_GAP_MS = 5_000; // pause between chained backlog sweeps
50
- // How many handled items ride on one commit before they are deleted. Small
51
- // enough that a crash re-does little, large enough that a flood of fast
52
- // rejections does not become a pod write per item.
53
- const DELETE_BATCH = 10;
54
- const ORPHAN_RECEIPTS_PER_SWEEP = 20; // strays from before receipts were deleted with their items
55
- // Attempt counts live in pod state, not in memory: a restart used to hand every
56
- // poison item five fresh tries, and under a crash loop that is unbounded.
57
- const ATTEMPTS_DOC = 'intake-attempts.json';
58
- const ATTEMPTS_TTL_MS = 7 * 24 * 60 * 60_000;
59
- const MAX_ITEM_ATTEMPTS = 5;
60
- const MAX_ITEMS_PER_DRAIN = 50;
61
-
62
- export class Intake {
63
- // `ownerPost(activity, { raw, slug })` is the client-to-server dispatcher,
64
- // for an item the Gateway's outbox door took on the owner's behalf.
65
- constructor({ config, urls, remote, store, deliverer, publisher, log = console.log, lease = null, archive = null, push = true, pollSeconds = null, ownerPost = null }) {
66
- Object.assign(this, { config, urls, remote, store, deliverer, publisher, log, lease, archive, push, pollSeconds, ownerPost });
67
- this.serial = Date.now();
68
- this.stopped = false;
69
- // (attempt counts are kept in pod state — see _bumpAttempt)
70
- this.lastDrain = null;
71
- this._forwardBudget = MAX_FORWARDS_PER_DRAIN;
72
- this.lastDrainAtMs = 0;
73
- this.reconnectTries = 0;
74
- this.drainCooldownUntil = 0;
75
- this.drainFailures = 0;
76
- this.wsState = 'never-connected';
77
- }
78
-
79
- // Draining is a destructive read — an item is gone from the pod once we
80
- // DELETE it — so the result of handling it must be on disk first.
81
- //
82
- // This used to be skipped whenever the state and the inbox shared an origin,
83
- // on the reasoning that a pod we cannot write to is a pod we cannot list
84
- // either, so the drain never starts. That covers the pod being unreachable
85
- // and nothing else: it does not cover a crash inside the 300ms debounce
86
- // window, and it does not cover a pod that refuses a write while still
87
- // serving reads and deletes — a quota, a 507, a 403 on one document. In
88
- // either case every item drained since the last successful write is gone,
89
- // and what goes is the mentions, replies, join requests and dead-letter
90
- // records that nothing else can rebuild.
91
- async _persisted() {
92
- return this.store.commit();
93
- }
94
-
95
- _backOff(why) {
96
- this.drainFailures++;
97
- const capped = Math.min(DRAIN_COOLDOWN_MIN_MS * 2 ** (this.drainFailures - 1), DRAIN_COOLDOWN_MAX_MS);
98
- this.drainCooldownUntil = Date.now() + Math.round(capped * (0.85 + Math.random() * 0.3));
99
- this.log(`${why} — next sweep in ${Math.round(capped / 1000)}s`);
100
- }
101
-
102
- // Sweep cadence: a configured interval wins, otherwise a live push channel —
103
- // a notification socket, or the store's own change events in-process — is
104
- // what makes the fallback poll a slow one.
105
- _pollMs() {
106
- if (this.pollSeconds) return this.pollSeconds * 1000;
107
- return this.wsState === 'open' || this.wsState === 'in-process' ? POLL_PUSH_OK_MS : POLL_MS;
108
- }
109
-
110
- async start() {
111
- this.stopped = false; // restartable across demote/takeover cycles
112
- await this.drain().catch(e => this.log(`drain: ${e.message}`));
113
- const tick = () => {
114
- this.pollTimer = setTimeout(() => {
115
- this.drain().catch(e => this.log(`drain: ${e.message}`)).finally(() => { if (!this.stopped) tick(); });
116
- }, Math.round(this._pollMs() * (0.85 + Math.random() * 0.3)));
117
- this.pollTimer.unref?.();
118
- };
119
- tick();
120
- // Embedded in the pod server, a notification socket back to that same
121
- // server buys nothing — the store's own change events wake the drain.
122
- if (this.push) this.subscribe().catch(e => this.log(`subscribe: ${e.message}`));
123
- else this.wsState = 'in-process';
124
- }
125
-
126
- stop() { this.stopped = true; clearTimeout(this.pollTimer); clearTimeout(this.resubTimer); this.ws?.close(); }
127
-
128
- // Attempt bookkeeping, persisted. Written only when an item fails, so a
129
- // healthy inbox never touches this document.
130
- _bumpAttempt(url, message) {
131
- const all = this.store.read(ATTEMPTS_DOC, {});
132
- const rec = all[url] || { n: 0 };
133
- rec.n += 1;
134
- rec.at = new Date().toISOString();
135
- rec.last = String(message || '').slice(0, 200);
136
- all[url] = rec;
137
- this.store.write(ATTEMPTS_DOC, all);
138
- return rec.n;
139
- }
140
-
141
- _clearAttempt(url) {
142
- const all = this.store.read(ATTEMPTS_DOC, {});
143
- if (!all[url]) return;
144
- delete all[url];
145
- this.store.write(ATTEMPTS_DOC, all);
146
- }
147
-
148
- // Items deleted long ago would otherwise accumulate here forever.
149
- _pruneAttempts() {
150
- const all = this.store.read(ATTEMPTS_DOC, {});
151
- const cutoff = Date.now() - ATTEMPTS_TTL_MS;
152
- let dropped = 0;
153
- for (const [url, rec] of Object.entries(all)) {
154
- if (!rec?.at || Date.parse(rec.at) < cutoff) { delete all[url]; dropped++; }
155
- }
156
- if (dropped) { this.store.write(ATTEMPTS_DOC, all); this.log(`pruned ${dropped} stale inbox attempt record(s)`); }
157
- }
158
-
159
- // --- drain + dispatch ---
160
- // Serialized: push events, polls, and manual /drain calls can fire
161
- // concurrently, and overlapping sweeps double-process items (observed as
162
- // duplicate Accepts/timeline writes). One sweep at a time; callers that
163
- // arrive mid-sweep get one follow-up sweep.
164
- async drain() {
165
- if (this._draining) { this._drainAgain = true; return this._draining; }
166
- // Held for the whole sweep, released however it ends. The debounce cannot
167
- // coalesce a drain — every handler awaits somebody else's server first — so
168
- // the writes are left to the commit boundaries the drain already has. See
169
- // PodStore.hold.
170
- // Optional: this is a throughput hint, not part of the commit-before-delete
171
- // invariant — commit() flushes whatever is pending either way — so a store
172
- // that does not implement it behaves exactly as before.
173
- this._inSweep = true;
174
- // Fresh per sweep: the cap is on how much this drain may amplify, not a
175
- // lifetime total (see _maybeForward).
176
- this._forwardBudget = MAX_FORWARDS_PER_DRAIN;
177
- this.store.hold?.();
178
- this._draining = this._drainOnce().finally(async () => {
179
- this._inSweep = false;
180
- await this._publishPending();
181
- this.store.release?.();
182
- this._draining = null;
183
- if (this._drainAgain) {
184
- this._drainAgain = false;
185
- // Paced, not immediate: chained sweeps put a ceiling on work per unit
186
- // time, so a delivery flood cannot run the drain back-to-back.
187
- const t = setTimeout(() => this.drain().catch(e => this.log(`drain: ${e.message}`)), CHAIN_GAP_MS);
188
- t.unref?.();
189
- }
190
- });
191
- return this._draining;
192
- }
193
-
194
- // Discard the content waiting in the inbox from before `before`, on the
195
- // owner's say-so — the admin page asks, this does it. NOT a blind sweep:
196
- // every item is read, because the type is the only thing that decides its
197
- // fate and size does not predict it. A Follow, Undo, Accept or Delete is
198
- // APPLIED whatever it weighs, so the follow graph stays correct and a post
199
- // its author retracted still goes; only a Create is dropped. Judging by size
200
- // instead saved one request per item and lost any control activity that
201
- // happened to be large, silently and permanently.
202
- //
203
- // An item past the drain's own byte cap is deleted unread: it could not be
204
- // handled if it were read, so there is nothing to lose by not reading it.
205
- //
206
- // `keepConcerning` narrows the discard to noise: every item is passed to
207
- // handle(), which ingests a Create only when concernsUs passes — addressed to
208
- // us, a mention, a reply to ours, or from someone we follow — and drops the
209
- // rest.
210
- async prune({ before, keepConcerning = false } = {}) {
211
- const cutoff = Date.parse(before);
212
- if (!Number.isFinite(cutoff)) throw new Error(`"${before}" is not a date`);
213
- const all = await podInbox.list(this.remote, this.urls);
214
- const older = all.filter(e => !e.url.endsWith('.keep')
215
- && e.modified && Date.parse(e.modified) < cutoff);
216
- const out = { considered: older.length, applied: 0, dropped: 0, discarded: 0, failed: 0 };
217
-
218
- for (const item of older) {
219
- try {
220
- if (item.size > MAX_ITEM_BYTES) {
221
- await podInbox.dropHandledItem(this.remote, item.url); // unreadable by the drain either way
222
- out.discarded++;
223
- } else {
224
- // Same rule as the drain: a read we could not make is not a Create to
225
- // be dropped. readItem throws on anything but 404, so it counts as
226
- // failed and stays for the next pass.
227
- const got = await podInbox.readItem(this.remote, item.url, { maxBytes: MAX_ITEM_BYTES, readCapped });
228
- const read = got.raw === null ? null : await readLenient(got.raw);
229
- if (read?.degraded) this.log(`inbox item ${item.url} grounded to read: ${read.degraded}`);
230
- const activity = read?.view ?? read?.doc ?? null;
231
- // A Create is the content the owner just asked to be rid of. Anything
232
- // else changes state and is applied exactly as a drain would.
233
- if (keepConcerning) {
234
- const rejection = activity ? await this.handle(activity) : 'unparsable JSON';
235
- if (rejection) out.dropped++; else out.applied++;
236
- } else if (activity && activity.type !== 'Create') {
237
- await this.handle(activity);
238
- out.applied++;
239
- } else {
240
- out.dropped++;
241
- }
242
- if (!await this._persisted()) {
243
- this.log(`state not written — stopping the prune with ${older.length - out.applied - out.dropped - out.discarded} left`);
244
- break;
245
- }
246
- await podInbox.dropHandledItem(this.remote, item.url);
247
- }
248
- this._clearAttempt(item.url);
249
- await new Promise(r => setTimeout(r, DELETE_GAP_MS));
250
- } catch (e) {
251
- out.failed++;
252
- this.log(`prune ${item.url}: ${e.message}`);
253
- }
254
- }
255
- this.log(`pruned before ${before}: applied ${out.applied}, dropped ${out.dropped} `
256
- + `small Create(s), discarded ${out.discarded} unread${out.failed ? `, ${out.failed} failed` : ''}`);
257
- await this.store.flush();
258
- // Adjust the measurement in place rather than kicking a drain to re-take
259
- // it: an un-awaited drain would still be running when this returns, which
260
- // races whoever called us. The poll picks the rest up soon enough.
261
- const removed = out.applied + out.dropped + out.discarded;
262
- if (this.inboxStats && removed) {
263
- this.inboxStats = { ...this.inboxStats, count: Math.max(0, this.inboxStats.count - removed) };
264
- }
265
- return out;
266
- }
267
-
268
- // The sender's original bytes, kept after the activity was verified and
269
- // applied and before the pod DELETE erases the only other copy. The filename
270
- // is the content's own hash, so a re-delivered activity lands on the same
271
- // file instead of duplicating. Best-effort history: a failed write logs and
272
- // the drain goes on — mail must never stall on its own receipt. This is the
273
- // one category of account data that lives only in the private half; the pod
274
- // cannot rebuild it because the pod never kept it.
275
- //
276
- // The record is JSON-LD: a cnt:ContentAsText whose cnt:chars are the raw
277
- // bytes, stamped prov:generatedAtTime / prov:wasDerivedFrom / as:actor —
278
- // plain JSON to everything here, a graph to any RDF reader.
279
- async _archive(sourceUrl, raw, activity) {
280
- try {
281
- if (!this.archive || this.store.getConfig()?.archiveInbox === false) return;
282
- const { createHash } = await import('node:crypto');
283
- const hash = createHash('sha256').update(raw).digest('hex').slice(0, 16);
284
- const receivedAt = new Date().toISOString();
285
- const rec = {
286
- '@context': {
287
- prov: 'http://www.w3.org/ns/prov#',
288
- cnt: 'http://www.w3.org/2011/content#',
289
- as: 'https://www.w3.org/ns/activitystreams#',
290
- xsd: 'http://www.w3.org/2001/XMLSchema#',
291
- receivedAt: { '@id': 'prov:generatedAtTime', '@type': 'xsd:dateTime' },
292
- source: { '@id': 'prov:wasDerivedFrom', '@type': '@id' },
293
- actor: { '@id': 'as:actor', '@type': '@id' },
294
- raw: 'cnt:chars',
295
- },
296
- '@id': '',
297
- '@type': 'cnt:ContentAsText',
298
- receivedAt,
299
- actor: typeof activity?.actor === 'string' ? activity.actor : activity?.actor?.id || null,
300
- source: sourceUrl,
301
- raw,
302
- };
303
- const w = await this.archive.write(`${receivedAt.slice(0, 7)}/${hash}.json`,
304
- JSON.stringify(rec, null, 2), 'application/ld+json');
305
- if (!w.ok) this.log(`inbox archive: ${w.why || 'write failed'}`);
306
- } catch (e) {
307
- this.log(`inbox archive: ${e.message}`);
308
- }
309
- }
310
-
311
- async _drainOnce() {
312
- const cooling = this.drainCooldownUntil - Date.now();
313
- if (cooling > 0) {
314
- this.log(`inbox sweep skipped — backing off for another ${Math.ceil(cooling / 1000)}s`);
315
- return;
316
- }
317
- // Draining DELETES from the pod, so it must not run on a lease that has
318
- // quietly expired. renewOnce notices at its own cadence — up to ~117s — and
319
- // after the TTL another agent is entitled to start draining the same inbox.
320
- if (this.lease && !this.lease.stillHeld()) {
321
- this.log('lease is no longer held — not draining');
322
- return;
323
- }
324
- this.lastDrain = new Date().toISOString();
325
- this.lastDrainAtMs = Date.now();
326
- // What a drain leaves behind may need acting on: a forum's queue of asks,
327
- // which is filled here and emptied by whoever can vouch for them.
328
- if (this.afterDrain) setTimeout(() => this.afterDrain(), 0);
329
- this._pruneAttempts();
330
- let all;
331
- try {
332
- all = await podInbox.list(this.remote, this.urls);
333
- this.drainFailures = 0;
334
- } catch (e) {
335
- this._backOff(`inbox unreadable (${e.message})`);
336
- return;
337
- }
338
- // What is waiting, measured from the listing we already fetched: no extra
339
- // request, and it is what /status reports and what the admin page prompts
340
- // on. The listing arrives oldest-first (lib/remote.mjs).
341
- const real = all.filter(e => !e.url.endsWith('.keep'));
342
- this.inboxStats = {
343
- count: real.length,
344
- bytes: real.reduce((n, e) => n + e.size, 0),
345
- oldest: real[0]?.modified || null,
346
- newest: real[real.length - 1]?.modified || null,
347
- at: new Date().toISOString(),
348
- };
349
- // The inbox is public-Append: a flood must not turn one sweep into an
350
- // unbounded run. But stopping there is why a backlog never cleared — 50
351
- // items every two minutes does not converge on an agent that is only
352
- // running while a laptop is open. So a sweep that made progress and left
353
- // work behind goes straight round again.
354
- const items = all.slice(0, MAX_ITEMS_PER_DRAIN);
355
- if (all.length > items.length) this.log(`inbox has ${all.length} items — processing ${items.length} this sweep`);
356
- let handled = 0;
357
- // Deletes are batched behind ONE commit rather than a commit per item.
358
- // Per-item, the 300ms debounce that coalesces a sweep's writes never gets
359
- // to do its job: on a flood of fast rejections that is fifty writes of
360
- // deadletter.json where one would do, and a flood is exactly when the pod
361
- // should be asked for less rather than more.
362
- const pending = [];
363
- const withReceipt = new Set(); // items the listing showed a receipt beside
364
- const flush = async () => {
365
- if (!pending.length) return true;
366
- // Written down before any of them leaves the mailbox. A failure here
367
- // leaves them where they are: the next sweep sees them again, and a
368
- // re-delivered activity is handled idempotently.
369
- if (!await this._persisted()) {
370
- this._backOff(`state not written — ${pending.length} item(s) left in the inbox`);
371
- pending.length = 0;
372
- return false;
373
- }
374
- for (const url of pending.splice(0)) {
375
- if (!await podInbox.dropHandledItem(this.remote, url)) {
376
- // Still in the mailbox. Handling is idempotent so seeing it again is
377
- // harmless, but counting it would clear the attempt record and report
378
- // progress that did not happen.
379
- this.log(`inbox item ${url} was handled but NOT removed — it will be seen again`);
380
- continue;
381
- }
382
- this._clearAttempt(url);
383
- handled++;
384
- // Its receipt goes with it. Left beside a deleted item, a receipt
385
- // was one stray document per delivery, forever.
386
- if (withReceipt.has(url)) await podInbox.dropReceiptBeside(this.remote, url);
387
- await new Promise(r => setTimeout(r, DELETE_GAP_MS));
388
- }
389
- return true;
390
- };
391
-
392
- for (const { url, size, receipt: hasReceipt } of items) {
393
- if (url.endsWith('.keep')) continue;
394
- if (hasReceipt) withReceipt.add(url);
395
- // The listing already carries every child's size, so this costs nothing
396
- // to ask. An activity is a few kB; anything of this order is not one, and
397
- // reading it with an unbounded res.text() buffers whatever a stranger
398
- // chose to Append into memory.
399
- // A cheap pre-filter only: listContainer coerces a missing posix:size to
400
- // 0, so a pod that does not publish sizes would wave everything through.
401
- // The real bound is readCapped on the body below.
402
- if (size > MAX_ITEM_BYTES) {
403
- this.store.addDeadLetter({ inboxUrl: url, reason: `oversized (${size} bytes)`, activity: null });
404
- pending.push(url);
405
- continue;
406
- }
407
- let activity = null;
408
- try {
409
- const got = await podInbox.readItem(this.remote, url, { maxBytes: MAX_ITEM_BYTES, readCapped });
410
- // readItem carries the rule that matters here: a pod that would not
411
- // GIVE us the item has told us nothing about it, so anything but a 404
412
- // throws rather than reading as an empty body. Reading a 500 as empty
413
- // made it "unparsable JSON" — a REJECTION, dead-lettered with both
414
- // `activity` and `raw` null and then DELETEd, destroying a delivery on
415
- // a transient fault with no record of what it had been.
416
- const raw = got.raw;
417
- // Read as JSON-LD, so a document whose terms are aliased means what it
418
- // says. A document we cannot read that way is read the way it always
419
- // was rather than lost, and the reason is logged.
420
- const read = raw ? await readLenient(raw) : null;
421
- if (read?.degraded) this.log(`inbox item ${url} grounded to read: ${read.degraded}`);
422
- activity = read?.view ?? read?.doc ?? null;
423
- // What the shapes make of it. This decides NOTHING — the activity is
424
- // handled either way. It is written down so that which shapes fire on
425
- // real traffic is a question with an answer, rather than a guess made
426
- // in advance about implementations we have not met.
427
- if (read?.graph) {
428
- const failure = await checkShapes(read.graph);
429
- if (failure) {
430
- const said = describeShapeFailure(failure);
431
- this.log(`inbox item ${url} does not fit its shape: ${said}`);
432
- this.store.addDeadLetter({
433
- inboxUrl: url, reason: `shape: ${said}`, shapeOnly: true, activity: trimActivity(activity),
434
- });
435
- }
436
- }
437
- // A gateway that verified this delivery left a receipt beside it. Read
438
- // it only when a gateway is configured (no config → no fetch, so an
439
- // install with no gateway pays nothing) and the listing showed one
440
- // beside this item (`false` is a definite no; a listing that does not
441
- // say leaves it to the read); a missing or HMAC-invalid receipt reads
442
- // as null, which is exactly today's unverified behavior.
443
- const receipt = activity && hasReceipt !== false ? await this._readReceipt(url) : null;
444
- if (activity && this.gatewaySecret()) this._bumpGatewayStat(!!receipt?.verified);
445
- // The owner's own post, taken at the outbox door: not mail to read
446
- // but a write to make. Never archived or forwarded as if received.
447
- const owned = activity && this.isOwnerPost(receipt);
448
- const rejection = !activity ? 'unparsable JSON'
449
- : owned ? await this.ownerPostFrom(activity, raw, receipt)
450
- : await this.handle(activity, receipt);
451
- if (!rejection && raw && !owned) await this._archive(url, raw, activity);
452
- if (!rejection && !owned) await this._maybeForward(activity); // §7.1.2, only what we accepted
453
- if (rejection) {
454
- this.store.addDeadLetter({
455
- inboxUrl: url, reason: rejection, activity: trimActivity(activity),
456
- ...(activity ? {} : { raw: raw?.slice(0, 2000) ?? null }),
457
- });
458
- this.log(`rejected (${rejection}) — dead-lettered: ${url}`);
459
- }
460
- pending.push(url);
461
- } catch (e) {
462
- const n = this._bumpAttempt(url, e.message);
463
- this.log(`inbox item ${url} attempt ${n}/${MAX_ITEM_ATTEMPTS}: ${e.message}`);
464
- if (n >= MAX_ITEM_ATTEMPTS) {
465
- this.store.addDeadLetter({ inboxUrl: url, reason: `failed ${n}x: ${e.message}`, activity: trimActivity(activity) });
466
- // The dead letter IS the record of this item — deleting before it is
467
- // written down would lose the only evidence it ever arrived, so it
468
- // goes through the same commit-then-delete batch as everything else.
469
- pending.push(url);
470
- }
471
- }
472
- if (pending.length >= DELETE_BATCH && !await flush()) return;
473
- }
474
- if (!await this._finishSweep(flush)) return;
475
- // Receipts earlier drains left behind, a few per sweep until none remain.
476
- for (const stray of podInbox.orphanReceipts(this.remote, this.urls).slice(0, ORPHAN_RECEIPTS_PER_SWEEP)) {
477
- if (!await podInbox.dropStrayReceipt(this.remote, stray)) break;
478
- await new Promise(r => setTimeout(r, DELETE_GAP_MS));
479
- }
480
- // Made progress and there is more waiting: go straight round rather than
481
- // sleeping. Gated on progress so a sweep that achieved nothing — a
482
- // cooldown, an unwritable store, poison at the head — cannot spin.
483
- if (handled > 0 && all.length > items.length && !this.stopped) this._drainAgain = true;
484
- }
485
-
486
- // The end of a sweep: publish whatever the follow graph did ONCE, then flush.
487
- //
488
- // publishCollections used to run per handled item — every Follow, Undo,
489
- // Accept, Reject, admit and eject — and each one is a full GET of the pod's
490
- // followers collection plus a PUT of it. Fifty follows in a sweep were a
491
- // hundred requests where two would do, and the answer they arrive at is the
492
- // same either way, because it is built from contacts.json in memory.
493
- async _finishSweep(flush) {
494
- await this._publishPending();
495
- return flush();
496
- }
497
-
498
- // Idempotent: it clears what it takes, so the drain's own exit path calling
499
- // it again after a sweep that bailed early — an unwritable store, a delete
500
- // that failed — is a no-op in the ordinary case and the difference between
501
- // "published" and "waiting for a sweep that may never come" in the other.
502
- async _publishPending() {
503
- const want = this._republish;
504
- this._republish = null;
505
- if (!want) return;
506
- try { await this.publisher.publishCollections(want); }
507
- catch (e) { this.log(`publishing collections: ${e.message}`); }
508
- }
509
-
510
- // Ask for a collection to be republished at the end of this sweep. Outside a
511
- // sweep there is no boundary to wait for, so it happens now.
512
- async republish(which) {
513
- if (!this._inSweep) return this.publisher.publishCollections(which);
514
- this._republish = { ...(this._republish || {}), ...which };
515
- }
516
-
517
- sameOrigin(a, b) { return sameOrigin(a, b); }
518
- // Overridable in tests the same way sameOrigin is.
519
- sameIdentity(a, b) { return sameIdentity(a, b); }
520
-
521
- async handle(activity, receipt = null) { // eslint-disable-line no-unused-vars
522
- const actor = typeof activity.actor === 'string' ? activity.actor : activity.actor?.id;
523
- if (!actor) return 'no actor';
524
- // An actor is a URL that can be dereferenced. Most arms here go on to fetch
525
- // it, and safefetch refuses a bad scheme there — but Like and Announce on
526
- // one of our own notes record a notification without dereferencing
527
- // anything, so `javascript:` and `data:` reached the client as an account
528
- // url. The store already guards avatars this way (safeUrl); actors were
529
- // simply never put through it.
530
- if (!httpUrl(actor)) return `actor is not an http(s) URL (${actor})`;
531
- if (this.store.isBlocked(actor)) return `blocked sender (${actor})`;
532
-
533
- // Whether the door vouched for this sender — read by the moderation queue
534
- // just below as well as by the arms further down, so it is settled here,
535
- // before its first use.
536
- const trusted = this.receiptVouchesFor(receipt, actor)
537
- && this.store.getConfig()?.gateway?.mode === 'trust';
538
-
539
- // FEP-1b12 moderation from a LISTED moderator. A delivery proves nothing
540
- // about its sender, which is why these are QUEUED for the operator rather
541
- // than run on arrival — the queue is where a claimed moderator's word
542
- // waits for the one person who can vouch for it. Everything else about
543
- // the activity falls through to the ordinary arms.
544
- if (this.config.kind === 'group'
545
- && (this.config.moderators || []).includes(actor)
546
- && this.isModerationAsk(activity)) {
547
- return this.queueModeration(activity, actor, { trusted });
548
- }
549
-
550
- // `trusted` was settled above, before the moderation queue reads it. Why it
551
- // is not simply `receipt.verified`: that says the door checked a signature
552
- // and the signature was good; it does NOT say whose. The door reports the
553
- // signing key's owner separately, in `receipt.actor`, and nothing here used
554
- // to read it — so ANY valid fediverse signing key, over an activity whose
555
- // `actor` field named someone else entirely, arrived as trusted. In trust
556
- // mode that is one delivery to evict any of your followers, or to have a
557
- // Follow naming a third party auto-accepted. See receiptVouchesFor, which
558
- // also holds the keyId to the actor's origin: a key document is fetched
559
- // from wherever its id points, so one hosted elsewhere that merely CLAIMS
560
- // `owner: <you>` would otherwise bind.
561
-
562
- switch (activity.type) {
563
- case 'Follow': return this.onFollow(activity, actor, { trusted });
564
- case 'Undo': return this.onUndo(activity, actor, { trusted });
565
- case 'Create': return this.onCreate(activity, actor, { trusted });
566
- case 'Accept': return this.onAccept(activity, actor, { trusted });
567
- case 'QuoteRequest': return this.onQuoteRequest(activity, actor, { trusted });
568
- case 'Like': case 'Announce': case 'EmojiReact': {
569
- // FEP-1b12: a group Announces the member's whole Create, not the note.
570
- // Without unwrapping we try to ingest a Create as if it were a Note and
571
- // dead-letter every post a group ever carries — including our own.
572
- const wrapped = activity.object;
573
- // FEP-1b12: a group announces its moderation too. The one act a
574
- // follower can honor without trusting anyone new is a Delete of a
575
- // post that same group carried to us — the carrier unsaying its carry.
576
- if (activity.type === 'Announce' && wrapped && typeof wrapped === 'object'
577
- && wrapped.type === 'Delete') {
578
- return this.onAnnouncedDelete(actor, wrapped);
579
- }
580
- const inner = (wrapped && typeof wrapped === 'object'
581
- && (wrapped.type === 'Create' || wrapped.type === 'Update')) ? wrapped.object : wrapped;
582
- const objectId = typeof inner === 'string' ? inner : inner?.id;
583
- this.log(`${activity.type} from ${actor} on ${objectId}`);
584
- // An emoji on one of our posts. Recorded the way a Like is — on the
585
- // sender's word, nothing to dereference — with the emoji itself, and
586
- // a custom emoji's image added to the post's own list so a client
587
- // that draws reactions from there finds it. Anything else an
588
- // EmojiReact names is not ours to notice.
589
- const reaction = activity.type !== 'Announce' ? reactionOf(activity) : null;
590
- if (reaction) {
591
- if (!objectId || !objectId.startsWith(this.urls.notes)) return;
592
- this.store.addNotification({
593
- type: 'reaction', actor, noteId: objectId, emoji: reaction.emoji,
594
- ...(reaction.url ? { url: reaction.url } : {}),
595
- ...(this.known(actor) ? {} : { unverified: true }),
596
- });
597
- if (reaction.url) {
598
- const s = this.store.getStatuses().find(x => x.noteId === objectId);
599
- const shortcode = reaction.emoji.replace(/^:|:$/g, '');
600
- if (s && !(s.emojis || []).some(e => e.shortcode === shortcode) && (s.emojis || []).length < MAX_EMOJIS) {
601
- this.store.updateStatus(objectId, { emojis: [...(s.emojis || []), { shortcode, url: reaction.url }] });
602
- }
603
- }
604
- return;
605
- }
606
- if (activity.type === 'EmojiReact') return;
607
- if (objectId && objectId.startsWith(this.urls.notes)) {
608
- // Nothing vouches for this actor: a Like carries no signature and,
609
- // unlike a Create, has no object at the sender's origin to re-read.
610
- // `known()` is answered from local state and costs nothing — a
611
- // stranger's favourite is still recorded, it is just the first thing
612
- // the cap evicts, so a flood cannot push out real history.
613
- this.store.addNotification({
614
- type: activity.type === 'Like' ? 'favourite' : 'reblog', actor, noteId: objectId,
615
- ...(this.known(actor) ? {} : { unverified: true }),
616
- });
617
- return;
618
- }
619
- if (activity.type === 'Announce') return this.onAnnounce(activity, actor, objectId);
620
- return;
621
- }
622
- case 'Delete': return this.onDelete(activity, actor);
623
- case 'Update': return this.onUpdate(activity, actor);
624
- case 'Reject': return this.onReject(activity, actor, { trusted });
625
- case 'Move': return this.onMove(activity, actor);
626
- case 'Add': case 'Remove': return this.onAddRemove(activity, actor);
627
- case 'Block': return this.onBlock(activity, actor, { trusted });
628
- case 'Flag': if (this.onReport) return this.onReport(activity, actor, { trusted }); return;
629
- default: this.log(`ignored ${activity.type} from ${actor}`);
630
- }
631
- }
632
-
633
- // channel.mjs
634
- _reconnectDelay(...a) { return channel.reconnectDelay(this, ...a); }
635
- subscribe(...a) { return channel.subscribe(this, ...a); }
636
- _storageDescriptionUrl(...a) { return channel.storageDescriptionUrl(this, ...a); }
637
- _subscribeOnce(...a) { return channel.subscribeOnce(this, ...a); }
638
- _openSocket(...a) { return channel.openSocket(this, ...a); }
639
-
640
- // verify.mjs
641
- fetchAP(...a) { return verify.fetchAP(this, ...a); }
642
- known(...a) { return verify.known(this, ...a); }
643
- gatewaySecret(...a) { return verify.gatewaySecret(this, ...a); }
644
- _bumpGatewayStat(...a) { return verify.bumpGatewayStat(this, ...a); }
645
- _readReceipt(...a) { return verify.readReceipt(this, ...a); }
646
-
647
- // A receipt the door stamped `c2s` for THIS actor, and only a receipt whose
648
- // HMAC verified (readReceipt returns nothing else). A stranger appending an
649
- // item that claims to be ours has no such receipt and is read as mail.
650
- isOwnerPost(receipt) {
651
- return !!(this.ownerPost && receipt?.verified && receipt.method === 'c2s' && receipt.actor === this.urls.actor);
652
- }
653
-
654
- // Publish what the owner posted at the door. The bytes as the client wrote
655
- // them go to the dispatcher (an object stored as sent must be the client's
656
- // document, not our reading of it); the graph is what it decides from.
657
- // Returns null when published, else the reason it was not — a dead letter.
658
- async ownerPostFrom(activity, raw, receipt) {
659
- // The receipt names the body it vouches for; a body it does not name is
660
- // not the owner's. A body already handled — a replayed request, or an item
661
- // the pod would not let us delete — is not published twice.
662
- const hash = raw ? await sha256Hex(raw) : null;
663
- if (receipt.hash && receipt.hash !== hash) return 'owner post refused: the body is not the one its receipt names';
664
- const seen = this.store.read('c2s-seen.json', []);
665
- if (hash && seen.includes(hash)) {
666
- this.log('owner post already handled — not published again');
667
- return null;
668
- }
669
- let asSent = null;
670
- try { asSent = raw ? JSON.parse(raw) : null; } catch { asSent = null; }
671
- const r = await this.ownerPost(activity, { raw: asSent, slug: receipt.slug || null,
672
- // What the door told the client this will be called, when it said.
673
- ...(receipt.serial ? { serial: receipt.serial } : {}), ...(receipt.at ? { at: receipt.at } : {}) });
674
- // Handled, one way or the other: a second sight of it changes nothing. A
675
- // throw above is not handled, and is tried again.
676
- if (hash) this.store.write('c2s-seen.json', [hash, ...this.store.read('c2s-seen.json', [])].slice(0, C2S_SEEN_MAX));
677
- if (!r || r.status >= 300) {
678
- const why = r?.body?.error || 'no reason was given';
679
- // The door already answered the app "created"; the owner is the one
680
- // left to tell.
681
- await this.publisher.noteToSelf?.(`An app asked your account to publish ${article(activity.type)}, `
682
- + `and your account refused it: ${why}. Nothing was sent.`)
683
- .catch(e => this.log(`could not tell the owner their post was refused: ${e.message}`));
684
- return `owner post refused (${r?.status || '?'}): ${why}`;
685
- }
686
- this.log(`owner post from the outbox door published: ${r.body?.object || r.body?.id || activity.type}`);
687
- return null;
688
- }
689
- receiptVouchesFor(...a) { return verify.receiptVouchesFor(this, ...a); }
690
- isGone(...a) { return verify.isGone(this, ...a); }
691
-
692
- // group.mjs
693
- isModerationAsk(...a) { return group.isModerationAsk(this, ...a); }
694
- queueModeration(...a) { return group.queueModeration(this, ...a); }
695
- amplify(...a) { return group.amplify(this, ...a); }
696
- isCoMember(...a) { return group.isCoMember(this, ...a); }
697
- collectionMembers(...a) { return group.collectionMembers(this, ...a); }
698
- announceTargets(...a) { return group.announceTargets(this, ...a); }
699
-
700
- // activities.mjs
701
- onAddRemove(...a) { return activities.onAddRemove(this, ...a); }
702
- onFollow(...a) { return activities.onFollow(this, ...a); }
703
- onUndo(...a) { return activities.onUndo(this, ...a); }
704
- onBlock(...a) { return activities.onBlock(this, ...a); }
705
- onCreate(...a) { return activities.onCreate(this, ...a); }
706
- onAnnouncedDelete(...a) { return activities.onAnnouncedDelete(this, ...a); }
707
- onAnnounce(...a) { return activities.onAnnounce(this, ...a); }
708
- onQuoteRequest(...a) { return activities.onQuoteRequest(this, ...a); }
709
- quoteStateOf(...a) { return activities.quoteStateOf(this, ...a); }
710
- onDelete(...a) { return activities.onDelete(this, ...a); }
711
- onUpdate(...a) { return activities.onUpdate(this, ...a); }
712
- onReject(...a) { return activities.onReject(this, ...a); }
713
- onMove(...a) { return activities.onMove(this, ...a); }
714
- onAccept(...a) { return activities.onAccept(this, ...a); }
715
-
716
- // notes.mjs
717
- concernsUs(...a) { return notes.concernsUs(this, ...a); }
718
- _maybeForward(...a) { return notes.maybeForward(this, ...a); }
719
- _referencesOurObject(...a) { return notes.referencesOurObject(this, ...a); }
720
- ingestNote(...a) { return notes.ingestNote(this, ...a); }
721
- forget(...a) { return notes.forget(this, ...a); }
722
- retract(...a) { return notes.retract(this, ...a); }
723
- addReply(...a) { return notes.addReply(this, ...a); }
724
- }
725
-
726
- // "a Like", "an Update": the kind of activity, as a sentence says it.
727
- const article = (type) => `${/^[AEIOU]/u.test(String(type)) ? 'an' : 'a'} ${type || 'post'}`;
728
-
729
- // Bodies the outbox door handed over that were handled, newest first.
730
- const C2S_SEEN_MAX = 500;
731
- const sha256Hex = async (text) => [...new Uint8Array(await globalThis.crypto.subtle.digest('SHA-256',
732
- new TextEncoder().encode(text)))].map(b => b.toString(16).padStart(2, '0')).join('');