fedipod-server 0.28.1 → 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 (438) hide show
  1. package/README.md +38 -34
  2. package/config/server.json +22 -0
  3. package/dist/claims.js +3 -2
  4. package/dist/directory.d.ts +2 -0
  5. package/dist/door.d.ts +4 -0
  6. package/dist/door.js +31 -0
  7. package/dist/fedipod.d.ts +16 -0
  8. package/dist/fedipod.js +13 -0
  9. package/dist/front.d.ts +3 -0
  10. package/dist/front.js +94 -0
  11. package/dist/handler.d.ts +39 -101
  12. package/dist/handler.js +86 -617
  13. package/dist/handler.jsonld +6 -30
  14. package/dist/identities.d.ts +62 -0
  15. package/dist/identities.js +236 -0
  16. package/dist/mounts.d.ts +43 -0
  17. package/dist/mounts.js +123 -0
  18. package/dist/opt-in.d.ts +46 -0
  19. package/dist/opt-in.js +176 -0
  20. package/dist/store-pod.js +37 -0
  21. package/package.json +10 -18
  22. package/lib/client/c2s.mjs +0 -582
  23. package/lib/client/localapi.mjs +0 -87
  24. package/lib/client/masto/accounts.mjs +0 -309
  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 -371
  65. package/lib/core/graphview.mjs +0 -327
  66. package/lib/core/intake/activities.mjs +0 -633
  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 -256
  70. package/lib/core/intake/index.mjs +0 -701
  71. package/lib/core/intake/notes.mjs +0 -360
  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/polls.mjs +0 -105
  76. package/lib/core/profile-page.mjs +0 -104
  77. package/lib/core/proof.mjs +0 -79
  78. package/lib/core/publisher/collections.mjs +0 -237
  79. package/lib/core/publisher/index.mjs +0 -500
  80. package/lib/core/publisher/notes.mjs +0 -453
  81. package/lib/core/publisher/questions.mjs +0 -239
  82. package/lib/core/publisher/restore.mjs +0 -224
  83. package/lib/core/shapes/activitystreams.ttl +0 -157
  84. package/lib/core/shapes/index.mjs +0 -132
  85. package/lib/core/shapes/shapes-text.mjs +0 -13
  86. package/lib/core/social.mjs +0 -531
  87. package/lib/core/storage.mjs +0 -176
  88. package/lib/core/store.mjs +0 -568
  89. package/lib/core/wire-quotes.mjs +0 -71
  90. package/lib/core/wire.mjs +0 -889
  91. package/lib/device/account.mjs +0 -87
  92. package/lib/device/admin/index.mjs +0 -13
  93. package/lib/device/admin/origins.mjs +0 -35
  94. package/lib/device/admin/routes/connections.mjs +0 -144
  95. package/lib/device/admin/routes/gateway.mjs +0 -199
  96. package/lib/device/admin/routes/lifecycle.mjs +0 -200
  97. package/lib/device/admin/routes/owner.mjs +0 -322
  98. package/lib/device/admin/routes/setup.mjs +0 -412
  99. package/lib/device/admin/routes/social.mjs +0 -188
  100. package/lib/device/admin/server.mjs +0 -95
  101. package/lib/device/admin/static.mjs +0 -247
  102. package/lib/device/admin/surface.mjs +0 -315
  103. package/lib/device/certs.mjs +0 -281
  104. package/lib/device/cli/commands/account.mjs +0 -586
  105. package/lib/device/cli/commands/run.mjs +0 -278
  106. package/lib/device/cli/commands/service.mjs +0 -221
  107. package/lib/device/cli/commands/setup.mjs +0 -447
  108. package/lib/device/cli/commands/state.mjs +0 -561
  109. package/lib/device/cli/context.mjs +0 -288
  110. package/lib/device/export-collections.mjs +0 -161
  111. package/lib/device/gateway-move.mjs +0 -58
  112. package/lib/device/home.mjs +0 -161
  113. package/lib/device/migrate.mjs +0 -115
  114. package/lib/device/ports.mjs +0 -27
  115. package/lib/device/remote.mjs +0 -54
  116. package/lib/device/setup.mjs +0 -459
  117. package/lib/device/update.mjs +0 -96
  118. package/lib/gateway/caches.mjs +0 -36
  119. package/lib/gateway/directory.mjs +0 -90
  120. package/lib/gateway/front-core.mjs +0 -970
  121. package/lib/gateway/gateway-core.mjs +0 -165
  122. package/lib/gateway/headers.mjs +0 -49
  123. package/lib/gateway/httpsig.mjs +0 -159
  124. package/lib/gateway/notices.mjs +0 -84
  125. package/lib/gateway/quiet.mjs +0 -189
  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/media.mjs +0 -27
  138. package/lib/pod/notes.mjs +0 -80
  139. package/lib/pod/notifications.mjs +0 -89
  140. package/lib/pod/outbox.mjs +0 -18
  141. package/lib/pod/package.json +0 -19
  142. package/lib/pod/policy.mjs +0 -58
  143. package/lib/pod/private.mjs +0 -19
  144. package/lib/pod/root.mjs +0 -104
  145. package/lib/pod/state.mjs +0 -40
  146. package/lib/pod/transport.mjs +0 -525
  147. package/lib/pod/urls.mjs +0 -85
  148. package/lib/server/embed.mjs +0 -423
  149. package/lib/session/README.md +0 -137
  150. package/lib/session/demo.html +0 -94
  151. package/lib/session/fedi-account.mjs +0 -408
  152. package/lib/session/fedi-login.mjs +0 -123
  153. package/lib/session/oidc-session.mjs +0 -233
  154. package/lib/session/package.json +0 -49
  155. package/lib/shared/guard.mjs +0 -213
  156. package/lib/shared/links.mjs +0 -9
  157. package/lib/shared/safefetch.mjs +0 -235
  158. package/lib/shared/ua.mjs +0 -29
  159. package/phanpy/dist/404.html +0 -40
  160. package/phanpy/dist/_headers +0 -2
  161. package/phanpy/dist/apple-touch-icon.png +0 -0
  162. package/phanpy/dist/assets/ICONS-DIPJeU0C.js +0 -2
  163. package/phanpy/dist/assets/ICONS-DIPJeU0C.js.map +0 -1
  164. package/phanpy/dist/assets/ScheduledAtField-D-Abfo4J.js +0 -183
  165. package/phanpy/dist/assets/ScheduledAtField-D-Abfo4J.js.map +0 -1
  166. package/phanpy/dist/assets/Temml-CCjoacWt.woff2 +0 -0
  167. package/phanpy/dist/assets/__vite-browser-external-C7iut881.js +0 -2
  168. package/phanpy/dist/assets/__vite-browser-external-C7iut881.js.map +0 -1
  169. package/phanpy/dist/assets/boosts-carousel-BiOaNdMT.jpg +0 -0
  170. package/phanpy/dist/assets/catch-up-O5q5C75Z.png +0 -0
  171. package/phanpy/dist/assets/chunk-aKtaBQYM.js +0 -1
  172. package/phanpy/dist/assets/compose-CFEx7wb4.js +0 -2
  173. package/phanpy/dist/assets/compose-CFEx7wb4.js.map +0 -1
  174. package/phanpy/dist/assets/compose-ZJN5xSb4.js +0 -35
  175. package/phanpy/dist/assets/compose-ZJN5xSb4.js.map +0 -1
  176. package/phanpy/dist/assets/debug.module-YFQgwnIr.js +0 -52
  177. package/phanpy/dist/assets/debug.module-YFQgwnIr.js.map +0 -1
  178. package/phanpy/dist/assets/dom-p1YcJ5Rw.js +0 -2
  179. package/phanpy/dist/assets/dom-p1YcJ5Rw.js.map +0 -1
  180. package/phanpy/dist/assets/home-mobile-dark@2x-Dc-J8-SM.png +0 -0
  181. package/phanpy/dist/assets/home-mobile-light@2x-Bd3fod8m.png +0 -0
  182. package/phanpy/dist/assets/home-tablet-dark@2x-BgcNI8le.png +0 -0
  183. package/phanpy/dist/assets/home-tablet-light@2x-BW2RV9T6.png +0 -0
  184. package/phanpy/dist/assets/icons/add-circle-line-WD0mwORD.js +0 -1
  185. package/phanpy/dist/assets/icons/alert-line-CpFUMf9s.js +0 -1
  186. package/phanpy/dist/assets/icons/android-2-line-Bzxnrd3S.js +0 -1
  187. package/phanpy/dist/assets/icons/announcement-line-5nwaJFJm.js +0 -1
  188. package/phanpy/dist/assets/icons/arrow-down-circle-line-a7Id9-Iv.js +0 -1
  189. package/phanpy/dist/assets/icons/arrow-down-line-B8uzXzq6.js +0 -1
  190. package/phanpy/dist/assets/icons/arrow-left-line-CGrGT9C-.js +0 -1
  191. package/phanpy/dist/assets/icons/arrow-right-line-CE-HERfX.js +0 -1
  192. package/phanpy/dist/assets/icons/arrow-to-up-line-CvdQjmcy.js +0 -1
  193. package/phanpy/dist/assets/icons/arrow-up-circle-line-DEparSNF.js +0 -1
  194. package/phanpy/dist/assets/icons/arrow-up-line-C7yBeugc.js +0 -1
  195. package/phanpy/dist/assets/icons/arrows-right-line-BtbXmJGP.js +0 -1
  196. package/phanpy/dist/assets/icons/at-line-CF66Bznk.js +0 -1
  197. package/phanpy/dist/assets/icons/attachment-line-BY8It-l9.js +0 -1
  198. package/phanpy/dist/assets/icons/blockquote-line-BS6pU_hm.js +0 -1
  199. package/phanpy/dist/assets/icons/board-line-C0UWXyA6.js +0 -1
  200. package/phanpy/dist/assets/icons/bookmark-line-1CtX5JLF.js +0 -1
  201. package/phanpy/dist/assets/icons/building-5-line-Dd59MzbO.js +0 -1
  202. package/phanpy/dist/assets/icons/bus-2-line-6_dvA0GG.js +0 -1
  203. package/phanpy/dist/assets/icons/calendar-day-line-Crpl_fmu.js +0 -1
  204. package/phanpy/dist/assets/icons/calendar-month-line-vommts2C.js +0 -1
  205. package/phanpy/dist/assets/icons/calendar-time-add-line-BU_DTlQk.js +0 -1
  206. package/phanpy/dist/assets/icons/camera-line-Cv69MEei.js +0 -1
  207. package/phanpy/dist/assets/icons/celebrate-line-B8DvIoi0.js +0 -1
  208. package/phanpy/dist/assets/icons/chart-bar-line-FK-QOmtF.js +0 -1
  209. package/phanpy/dist/assets/icons/chart-line-line-BGcJAFWw.js +0 -1
  210. package/phanpy/dist/assets/icons/chat-3-line-YFK2-hwz.js +0 -1
  211. package/phanpy/dist/assets/icons/check-circle-line-DsAxFr5-.js +0 -1
  212. package/phanpy/dist/assets/icons/clipboard-line-BBp364gJ.js +0 -1
  213. package/phanpy/dist/assets/icons/close-circle-line-CICdiuGz.js +0 -1
  214. package/phanpy/dist/assets/icons/close-line-DYrFWcjv.js +0 -1
  215. package/phanpy/dist/assets/icons/cloud-line-tiQAbah8.js +0 -1
  216. package/phanpy/dist/assets/icons/code-line-fAl8iiV3.js +0 -1
  217. package/phanpy/dist/assets/icons/comment-2-line-CR3g7WeB.js +0 -1
  218. package/phanpy/dist/assets/icons/copy-2-line-CLW69yF5.js +0 -1
  219. package/phanpy/dist/assets/icons/delete-2-line-B8xmbr4n.js +0 -1
  220. package/phanpy/dist/assets/icons/document-3-line-DBGNhAg2.js +0 -1
  221. package/phanpy/dist/assets/icons/document-line-C7mb9sb7.js +0 -1
  222. package/phanpy/dist/assets/icons/down-line-BGqSSThd.js +0 -1
  223. package/phanpy/dist/assets/icons/edit-4-line-DcgGaEhx.js +0 -1
  224. package/phanpy/dist/assets/icons/emoji-2-line-D8YgnM-y.js +0 -1
  225. package/phanpy/dist/assets/icons/exit-line-Dc07BS-x.js +0 -1
  226. package/phanpy/dist/assets/icons/external-link-line-BvpSkmjO.js +0 -1
  227. package/phanpy/dist/assets/icons/eye-2-line-CiWXx-f_.js +0 -1
  228. package/phanpy/dist/assets/icons/eye-close-line-BgTaSJ4P.js +0 -1
  229. package/phanpy/dist/assets/icons/filter-2-line-B_mR2usy.js +0 -1
  230. package/phanpy/dist/assets/icons/filter-line-C_nm-3cU.js +0 -1
  231. package/phanpy/dist/assets/icons/flag-1-line-C2HB05I0.js +0 -1
  232. package/phanpy/dist/assets/icons/forbid-circle-line-D3LTT9yd.js +0 -1
  233. package/phanpy/dist/assets/icons/formula-line-DLrt2d6j.js +0 -1
  234. package/phanpy/dist/assets/icons/grid-line-CgUmEikf.js +0 -1
  235. package/phanpy/dist/assets/icons/group-line-C5fnX0wU.js +0 -1
  236. package/phanpy/dist/assets/icons/hand-finger-2-line-BGgxbuL9.js +0 -1
  237. package/phanpy/dist/assets/icons/hashtag-line-CyTEEXBb.js +0 -1
  238. package/phanpy/dist/assets/icons/heart-crack-line-BZddbO2C.js +0 -1
  239. package/phanpy/dist/assets/icons/heart-line-CJ7K47J4.js +0 -1
  240. package/phanpy/dist/assets/icons/history-2-line-kWWvFzqv.js +0 -1
  241. package/phanpy/dist/assets/icons/history-line-bGQiwbTH.js +0 -1
  242. package/phanpy/dist/assets/icons/home-3-line-fRU1zeLG.js +0 -1
  243. package/phanpy/dist/assets/icons/information-line-iNk6-sOY.js +0 -1
  244. package/phanpy/dist/assets/icons/keyboard-line-DsCyuQNz.js +0 -1
  245. package/phanpy/dist/assets/icons/layout-4-line-CZkOX52Z.js +0 -1
  246. package/phanpy/dist/assets/icons/layout-5-line-B_znt5wO.js +0 -1
  247. package/phanpy/dist/assets/icons/left-line-eUw0tya4.js +0 -1
  248. package/phanpy/dist/assets/icons/lightning-line-DaKoDdix.js +0 -1
  249. package/phanpy/dist/assets/icons/link-2-line-DCqLmMA7.js +0 -1
  250. package/phanpy/dist/assets/icons/list-check-line-BAxosBh9.js +0 -1
  251. package/phanpy/dist/assets/icons/lock-line-CPoaXeUW.js +0 -1
  252. package/phanpy/dist/assets/icons/mail-line-B0P8aa1L.js +0 -1
  253. package/phanpy/dist/assets/icons/moon-line-CEqNAToP.js +0 -1
  254. package/phanpy/dist/assets/icons/more-1-fill-Dw2e5xab.js +0 -1
  255. package/phanpy/dist/assets/icons/more-3-line-BIiVjYTz.js +0 -1
  256. package/phanpy/dist/assets/icons/notification-line-BZZq2Gtu.js +0 -1
  257. package/phanpy/dist/assets/icons/pencil-line-CKh8-A1v.js +0 -1
  258. package/phanpy/dist/assets/icons/photo-album-line-Bnfg2u40.js +0 -1
  259. package/phanpy/dist/assets/icons/pin-line-DpTolyhs.js +0 -1
  260. package/phanpy/dist/assets/icons/play-fill-B0-G_8lX.js +0 -1
  261. package/phanpy/dist/assets/icons/qrcode-2-line-B6SPCaHn.js +0 -1
  262. package/phanpy/dist/assets/icons/quill-pen-line-B6LUrnCP.js +0 -1
  263. package/phanpy/dist/assets/icons/quote-left-fill-hQZ1cj_6.js +0 -1
  264. package/phanpy/dist/assets/icons/radar-line-CKyroTth.js +0 -1
  265. package/phanpy/dist/assets/icons/react-line-CoIKhhtK.js +0 -1
  266. package/phanpy/dist/assets/icons/refresh-2-line-D0mTF97S.js +0 -1
  267. package/phanpy/dist/assets/icons/right-line-B3KWJaqc.js +0 -1
  268. package/phanpy/dist/assets/icons/rocket-line-4KZl537D.js +0 -1
  269. package/phanpy/dist/assets/icons/round-fill-C69NTrZ0.js +0 -1
  270. package/phanpy/dist/assets/icons/round-line-B9k_1uo-.js +0 -1
  271. package/phanpy/dist/assets/icons/route-line-B28ReHcE.js +0 -1
  272. package/phanpy/dist/assets/icons/rows-4-line-CrBniagM.js +0 -1
  273. package/phanpy/dist/assets/icons/scan-line-DSf74YdP.js +0 -1
  274. package/phanpy/dist/assets/icons/search-2-line-B6geIU1z.js +0 -1
  275. package/phanpy/dist/assets/icons/settings-3-line-azJwK47w.js +0 -1
  276. package/phanpy/dist/assets/icons/settings-6-line-C_5zc5YR.js +0 -1
  277. package/phanpy/dist/assets/icons/share-2-line-D5MxWjrm.js +0 -1
  278. package/phanpy/dist/assets/icons/share-forward-line-Cp61fZzZ.js +0 -1
  279. package/phanpy/dist/assets/icons/sparkles-2-line-DYYyXTaW.js +0 -1
  280. package/phanpy/dist/assets/icons/sparkles-line-C2hY1s6I.js +0 -1
  281. package/phanpy/dist/assets/icons/time-line-BF3eet1v.js +0 -1
  282. package/phanpy/dist/assets/icons/transfer-4-line-oisfvTBw.js +0 -1
  283. package/phanpy/dist/assets/icons/translate-line-BFWol2Ce.js +0 -1
  284. package/phanpy/dist/assets/icons/unlock-line-X_x5vwv3.js +0 -1
  285. package/phanpy/dist/assets/icons/upload-3-line-DgzwUJeW.js +0 -1
  286. package/phanpy/dist/assets/icons/user-4-line-BVSynZCp.js +0 -1
  287. package/phanpy/dist/assets/icons/user-add-2-line-BlGS5Q8q.js +0 -1
  288. package/phanpy/dist/assets/icons/user-add-line-BbXdHG4z.js +0 -1
  289. package/phanpy/dist/assets/icons/user-edit-line-BFAHOV0X.js +0 -1
  290. package/phanpy/dist/assets/icons/user-follow-line-BXux0wSd.js +0 -1
  291. package/phanpy/dist/assets/icons/user-star-line-DdYlpHLU.js +0 -1
  292. package/phanpy/dist/assets/icons/user-warning-line-Cj43j0_E.js +0 -1
  293. package/phanpy/dist/assets/icons/user-x-line-DIDgNlPH.js +0 -1
  294. package/phanpy/dist/assets/icons/volume-line-Bs390KQt.js +0 -1
  295. package/phanpy/dist/assets/icons/volume-mute-line-DzlzIqFZ.js +0 -1
  296. package/phanpy/dist/assets/icons/walk-line-yNXfB-md.js +0 -1
  297. package/phanpy/dist/assets/icons/world-2-line-CenkGIwj.js +0 -1
  298. package/phanpy/dist/assets/icons/zoom-in-line-CaENUphf.js +0 -1
  299. package/phanpy/dist/assets/icons/zoom-out-line-BDNk0EWo.js +0 -1
  300. package/phanpy/dist/assets/instances-BNPptnn-.json +0 -333
  301. package/phanpy/dist/assets/locales/ar-SA-CfheRbCu.js +0 -1
  302. package/phanpy/dist/assets/locales/ca-ES-DHRW86Tz.js +0 -1
  303. package/phanpy/dist/assets/locales/cs-CZ-CKoqEuA1.js +0 -1
  304. package/phanpy/dist/assets/locales/de-DE-ByRPMRFy.js +0 -1
  305. package/phanpy/dist/assets/locales/eo-UY-CPC7wLCS.js +0 -1
  306. package/phanpy/dist/assets/locales/es-ES-dJa0Fd9t.js +0 -1
  307. package/phanpy/dist/assets/locales/eu-ES-C3djlPR2.js +0 -1
  308. package/phanpy/dist/assets/locales/fa-IR-DTT490Ck.js +0 -1
  309. package/phanpy/dist/assets/locales/fi-FI-B23MCPuz.js +0 -1
  310. package/phanpy/dist/assets/locales/fr-FR-BcaQsO5C.js +0 -1
  311. package/phanpy/dist/assets/locales/gl-ES-BR-CVKBN.js +0 -1
  312. package/phanpy/dist/assets/locales/he-IL-CfheRbCu.js +0 -1
  313. package/phanpy/dist/assets/locales/hu-HU-CfheRbCu.js +0 -1
  314. package/phanpy/dist/assets/locales/it-IT-C1136UFV.js +0 -1
  315. package/phanpy/dist/assets/locales/ja-JP-CRZmRAhD.js +0 -1
  316. package/phanpy/dist/assets/locales/kab-Cz8xH_TT.js +0 -1
  317. package/phanpy/dist/assets/locales/ko-KR-CG9UiY4o.js +0 -1
  318. package/phanpy/dist/assets/locales/lt-LT-CJcI6sHZ.js +0 -1
  319. package/phanpy/dist/assets/locales/nb-NO-CfheRbCu.js +0 -1
  320. package/phanpy/dist/assets/locales/nl-NL-DxN4Yc1t.js +0 -1
  321. package/phanpy/dist/assets/locales/oc-FR-CfheRbCu.js +0 -1
  322. package/phanpy/dist/assets/locales/pl-PL-DkwGXf6P.js +0 -1
  323. package/phanpy/dist/assets/locales/pseudo-LOCALE-CQ4XxkQ0.js +0 -1
  324. package/phanpy/dist/assets/locales/pt-BR-nGL0HdFS.js +0 -1
  325. package/phanpy/dist/assets/locales/pt-PT-Cp17aj2z.js +0 -1
  326. package/phanpy/dist/assets/locales/ru-RU-0Y8cIoaK.js +0 -1
  327. package/phanpy/dist/assets/locales/th-TH-CfheRbCu.js +0 -1
  328. package/phanpy/dist/assets/locales/tok-CfheRbCu.js +0 -1
  329. package/phanpy/dist/assets/locales/tr-TR-CfheRbCu.js +0 -1
  330. package/phanpy/dist/assets/locales/uk-UA-C5E3dFBf.js +0 -1
  331. package/phanpy/dist/assets/locales/zh-CN-BHJi7_et.js +0 -1
  332. package/phanpy/dist/assets/locales/zh-TW-CfheRbCu.js +0 -1
  333. package/phanpy/dist/assets/main-BdqNbG-a.js +0 -26
  334. package/phanpy/dist/assets/main-BdqNbG-a.js.map +0 -1
  335. package/phanpy/dist/assets/mock-home-DGhR__He.js +0 -2
  336. package/phanpy/dist/assets/mock-home-DGhR__He.js.map +0 -1
  337. package/phanpy/dist/assets/multi-column-ROck0NVt.jpg +0 -0
  338. package/phanpy/dist/assets/multi-hashtag-timeline-DH5INVHi.jpg +0 -0
  339. package/phanpy/dist/assets/nested-comments-thread-hv59kZLc.jpg +0 -0
  340. package/phanpy/dist/assets/polyfill-force-H8sUkLGp.js +0 -2
  341. package/phanpy/dist/assets/polyfill-force-H8sUkLGp.js.map +0 -1
  342. package/phanpy/dist/assets/pwa-viewport-ogKbF51K.js +0 -20
  343. package/phanpy/dist/assets/pwa-viewport-ogKbF51K.js.map +0 -1
  344. package/phanpy/dist/assets/style-B1iLPTPR.css +0 -2
  345. package/phanpy/dist/assets/temml-DuyIQMei.js +0 -97
  346. package/phanpy/dist/assets/temml-DuyIQMei.js.map +0 -1
  347. package/phanpy/dist/assets/tinyld.light.node-DtbBhLYC.js +0 -2
  348. package/phanpy/dist/assets/tinyld.light.node-DtbBhLYC.js.map +0 -1
  349. package/phanpy/dist/assets/year-in-posts-B2lywqdx.png +0 -0
  350. package/phanpy/dist/assets/year-in-posts-DJv_star.js +0 -3
  351. package/phanpy/dist/assets/year-in-posts-DJv_star.js.map +0 -1
  352. package/phanpy/dist/compose/index.html +0 -20
  353. package/phanpy/dist/favicon.ico +0 -0
  354. package/phanpy/dist/index.html +0 -73
  355. package/phanpy/dist/logo-192.png +0 -0
  356. package/phanpy/dist/logo-512.png +0 -0
  357. package/phanpy/dist/logo-badge-72.png +0 -0
  358. package/phanpy/dist/logo-maskable-512.png +0 -0
  359. package/phanpy/dist/logo-monochrome-512.png +0 -0
  360. package/phanpy/dist/logo-monochrome-maskable-512.png +0 -0
  361. package/phanpy/dist/manifest.webmanifest +0 -1
  362. package/phanpy/dist/og-image-2.jpg +0 -0
  363. package/phanpy/dist/og-image.png +0 -0
  364. package/phanpy/dist/robots.txt +0 -2
  365. package/phanpy/dist/sw.js +0 -2
  366. package/phanpy/dist/sw.js.map +0 -1
  367. package/phanpy/dist/version.json +0 -1
  368. package/run-agent.mjs +0 -897
  369. package/vendor/gate.cjs +0 -138
  370. package/vendor/idp-grant.cjs +0 -394
  371. package/vendor/jfetch.cjs +0 -16
  372. package/web/admin/actors.js +0 -145
  373. package/web/admin/bar.css +0 -126
  374. package/web/admin/bar.js +0 -99
  375. package/web/admin/client/client.js +0 -66
  376. package/web/admin/client/index.html +0 -69
  377. package/web/admin/client-pick.js +0 -79
  378. package/web/admin/common.js +0 -23
  379. package/web/admin/connections.js +0 -112
  380. package/web/admin/gateway.js +0 -133
  381. package/web/admin/group.js +0 -258
  382. package/web/admin/index.html +0 -603
  383. package/web/admin/notices-bar.js +0 -90
  384. package/web/admin/oauth-signin.mjs +0 -37
  385. package/web/admin/record.js +0 -390
  386. package/web/admin/setup/index.html +0 -241
  387. package/web/admin/setup/setup.js +0 -326
  388. package/web/admin/tokens.css +0 -68
  389. package/web/admin/upkeep.js +0 -177
  390. package/web/admin/window.css +0 -55
  391. package/web/admin/window.js +0 -136
  392. package/web/app/README.md +0 -81
  393. package/web/app/admin-facade.mjs +0 -588
  394. package/web/app/agent.mjs +0 -663
  395. package/web/app/atproto-browser.mjs +0 -86
  396. package/web/app/authorize_interaction.html +0 -56
  397. package/web/app/authorize_interaction.js +0 -69
  398. package/web/app/boot.mjs +0 -576
  399. package/web/app/deliver-relay.mjs +0 -129
  400. package/web/app/dist/boot.js +0 -34218
  401. package/web/app/dist/boot.js.map +0 -7
  402. package/web/app/dist/sw.js +0 -72810
  403. package/web/app/dist/sw.js.map +0 -7
  404. package/web/app/fediacct-browser.mjs +0 -146
  405. package/web/app/gateway-move.mjs +0 -60
  406. package/web/app/idb-kv.mjs +0 -35
  407. package/web/app/index.html +0 -242
  408. package/web/app/keys-browser.mjs +0 -71
  409. package/web/app/keystore.mjs +0 -82
  410. package/web/app/oidc-session.mjs +0 -6
  411. package/web/app/pod-auth.mjs +0 -248
  412. package/web/app/pod-remote.mjs +0 -84
  413. package/web/app/shims/_globals.mjs +0 -2
  414. package/web/app/shims/fedify-sig.mjs +0 -77
  415. package/web/app/shims/node-crypto.mjs +0 -113
  416. package/web/app/shims/node-fs.mjs +0 -17
  417. package/web/app/shims/node-path.mjs +0 -19
  418. package/web/app/shims/node-url.mjs +0 -7
  419. package/web/app/shims/prelude.js +0 -58
  420. package/web/app/shims/safefetch.mjs +0 -58
  421. package/web/app/shims/shapes-text.mjs +0 -8
  422. package/web/app/shims/web-push.mjs +0 -14
  423. package/web/app/signup.mjs +0 -318
  424. package/web/app/sw-src.mjs +0 -220
  425. package/web/app/update.js +0 -70
  426. package/web/front/admin.html +0 -55
  427. package/web/front/admin.js +0 -89
  428. package/web/front/install.sh +0 -69
  429. package/web/front/new-account.html +0 -35
  430. package/web/front/new-account.js +0 -13
  431. package/web/front/notices.html +0 -69
  432. package/web/front/notices.js +0 -107
  433. package/web/front/run.html +0 -58
  434. package/web/front/run.js +0 -195
  435. package/web/front/solid-oidc-client.js +0 -6
  436. package/web/icons/forum.svg +0 -11
  437. package/web/icons/gateway.svg +0 -14
  438. package/web/icons/local.svg +0 -11
@@ -1,239 +0,0 @@
1
- // questions.mjs — polls.
2
- //
3
- // A poll is published as a Question and answered by ordinary replies naming
4
- // an option, so the count is ours to keep and ours to republish. The roster
5
- // of who chose what lives in agent state (polls.VOTES_DOC); the tallies on
6
- // the status row and in the pod document are always derived from it.
7
-
8
- import crypto from 'node:crypto';
9
- import * as wire from '../wire.mjs';
10
- import * as polls from '../polls.mjs';
11
- import * as podNotes from '../../pod/notes.mjs';
12
- import { assertDirectAddressed, replyTarget, rowTags } from './notes.mjs';
13
-
14
- // How long a poll gathers votes before its Question is rewritten. Every vote
15
- // changes a number other servers re-read, and rewriting per vote would make a
16
- // busy poll a steady write stream against the pod. A burst costs one rewrite
17
- // and one Update instead.
18
- const POLL_REWRITE_MS = 10_000;
19
-
20
- /**
21
- * Publish a poll. `options` is a list of choice titles, `multiple` lets a
22
- * voter pick more than one, and `expiresAt` is when voting stops.
23
- */
24
- export async function publishQuestion(publisher, content, { options = [], multiple = false, expiresAt = null,
25
- inReplyTo = undefined, visibility = 'public', spoilerText = null, sensitive = false } = {}) {
26
- const { urls } = publisher;
27
- const priv = visibility === 'private' || visibility === 'direct';
28
- if (priv) {
29
- const ready = await publisher.privateReady();
30
- if (ready !== true) throw new Error(ready);
31
- }
32
- const titles = [].concat(options).map(o => String(o ?? '').trim()).filter(Boolean);
33
- if (titles.length < 2) throw new Error('a poll needs at least two options');
34
- // Options are matched BY NAME when a vote arrives — that is the whole of
35
- // the convention — so two options reading the same are one option that
36
- // cannot be told apart.
37
- if (new Set(titles).size !== titles.length) throw new Error('a poll’s options must differ from one another');
38
- // Bounded here as well as at the client API, because the outbox is a
39
- // second way in and an unbounded poll is a document a stranger sizes.
40
- if (titles.length > polls.MAX_OPTIONS) throw new Error(`a poll takes at most ${polls.MAX_OPTIONS} options`);
41
- if (titles.some(t => t.length > polls.MAX_OPTION_CHARS)) {
42
- throw new Error(`a poll option is at most ${polls.MAX_OPTION_CHARS} characters`);
43
- }
44
- if (expiresAt) {
45
- const ends = Date.parse(expiresAt);
46
- if (!Number.isFinite(ends)) throw new Error('the closing time is not a date');
47
- const seconds = (ends - Date.now()) / 1000;
48
- if (seconds < polls.MIN_SECONDS || seconds > polls.MAX_SECONDS) {
49
- throw new Error(`a poll runs between ${polls.MIN_SECONDS} and ${polls.MAX_SECONDS} seconds`);
50
- }
51
- }
52
-
53
- const published = new Date().toISOString();
54
- const slug = published.slice(0, 10) + '-' + crypto.randomBytes(4).toString('hex');
55
- const mentions = await publisher._mentionsFor(content, inReplyTo);
56
- if (visibility === 'direct') assertDirectAddressed(content, mentions);
57
- const reply = await replyTarget(publisher, inReplyTo, mentions, visibility);
58
- const poll = {
59
- multiple: !!multiple,
60
- expiresAt: expiresAt || null,
61
- closed: null,
62
- options: titles.map(title => ({ title, votes: 0 })),
63
- votersCount: 0,
64
- // Resolved once, here: a tally rewrite must not cost a webfinger lookup
65
- // per vote for people the poll named.
66
- mentionInboxes: [...new Set([...mentions.map(m => m.inbox), reply?.inbox].filter(Boolean))],
67
- };
68
- const question = wire.questionDoc({
69
- urls, slug, content, published, inReplyTo, attachments: [], mentions,
70
- visibility, summary: spoilerText, sensitive, also: reply ? [reply.actor] : [],
71
- container: priv ? urls.privateNotes : urls.notes,
72
- options: poll.options, multiple: poll.multiple, endTime: poll.expiresAt, votersCount: 0,
73
- });
74
-
75
- await podNotes.write(publisher.remote, question.id, question);
76
- // Empty, but present: a dangling `replies` that 404s is worse than none.
77
- await podNotes.writeEmptyReplies(publisher.remote, wire.repliesId(question.id),
78
- wire.collection(wire.repliesId(question.id), []));
79
- if (!priv) await publisher.recordOutbox(question.id);
80
- publisher.store.addStatus({
81
- noteId: question.id, actor: urls.actor, content: question.content, published,
82
- kind: 'post', slug, text: content, visibility, poll, inReplyTo,
83
- ...(reply ? { replyActor: reply.actor } : {}),
84
- ...(spoilerText ? { spoiler: spoilerText } : {}),
85
- ...(question.sensitive ? { sensitive: true } : {}),
86
- ...rowTags(question),
87
- });
88
- if (await publisher.store.commit?.() === false) publisher.log(`poll published but its timeline row was refused: ${question.id}`);
89
-
90
- const create = publisher._pollActivity('Create', question, wire.createActivityId(question.id));
91
- await podNotes.writeCreate(publisher.remote, create.id, create);
92
- const contacts = publisher.store.getContacts();
93
- const inboxes = [...new Set([
94
- ...(visibility === 'direct' ? [] : contacts.followers.map(f => f.sharedInbox || f.inbox)),
95
- ...poll.mentionInboxes,
96
- ].filter(Boolean))];
97
- await publisher.deliverer.deliverToAll(inboxes, create);
98
- publisher.log(`poll published: ${question.id} (${titles.length} options) → ${inboxes.length} inbox(es)`);
99
- return question;
100
- }
101
-
102
- // The Create or Update carrying a Question. The context is hoisted onto the
103
- // activity and the embedded object keeps none: a nested @context is legal
104
- // JSON-LD but not every server reads one, and votersCount is declared there.
105
- export function pollActivity(publisher, type, question, id) {
106
- const { '@context': ctx, ...object } = question;
107
- return {
108
- '@context': ctx, id, type,
109
- actor: publisher.urls.actor,
110
- published: question.published,
111
- to: question.to, cc: question.cc,
112
- object,
113
- };
114
- }
115
-
116
- /**
117
- * One vote on one of OUR polls, named by the option's title. Returns true
118
- * when it counted — a second answer to a single-choice poll, an option we do
119
- * not offer, or a poll that has closed all count for nothing.
120
- */
121
- export async function recordVote(publisher, questionId, actor, optionName) {
122
- const s = publisher.store.getStatuses().find(x => x.noteId === questionId);
123
- if (!s?.poll || s.kind !== 'post' || s.actor !== publisher.urls.actor) return false;
124
- if (polls.pollClosed(s.poll)) return false;
125
- const index = polls.optionIndex(s.poll, optionName);
126
- if (index < 0) return false;
127
- const all = publisher.store.read(polls.VOTES_DOC, {});
128
- const { roster, changed } = polls.addVote(all[questionId] || {}, polls.voterKey(actor), index,
129
- { multiple: !!s.poll.multiple });
130
- if (!changed) return false;
131
- publisher.store.write(polls.VOTES_DOC, { ...all, [questionId]: roster });
132
- publisher.store.updateStatus(questionId, { poll: polls.withTally(s.poll, roster) });
133
- publisher._pollDirty(questionId);
134
- return true;
135
- }
136
-
137
- // Open the rewrite window for a poll whose count moved. Already open is
138
- // already enough: the whole point is that a burst costs one rewrite.
139
- export function pollDirty(publisher, questionId) {
140
- if (publisher.pollTimers.has(questionId)) return;
141
- const t = setTimeout(() => {
142
- publisher.pollTimers.delete(questionId);
143
- publisher.republishPoll(questionId).catch(e => publisher.log(`poll rewrite: ${e.message}`));
144
- }, POLL_REWRITE_MS);
145
- t.unref?.();
146
- publisher.pollTimers.set(questionId, t);
147
- }
148
-
149
- /**
150
- * Write the poll's current count back to the pod and tell everyone who has
151
- * it. `closing` stamps it shut, which is a one-way door.
152
- */
153
- export async function republishPoll(publisher, questionId, { closing = null } = {}) {
154
- const s = publisher.store.getStatuses().find(x => x.noteId === questionId);
155
- if (!s?.poll) return null;
156
- const { urls } = publisher;
157
- const roster = publisher.store.read(polls.VOTES_DOC, {})[questionId] || {};
158
- // A shut poll whose roster has been retired keeps the counts on its row:
159
- // deriving them from an empty roster would publish a poll nobody voted in.
160
- const shut = closing || s.poll.closed;
161
- const counted = shut && !Object.keys(roster).length ? s.poll : polls.withTally(s.poll, roster);
162
- const poll = { ...counted, ...(closing ? { closed: closing } : {}) };
163
- const container = String(s.noteId).startsWith(urls.privateNotes) ? urls.privateNotes : urls.notes;
164
- const slug = s.slug || String(s.noteId).slice(container.length);
165
- const mentions = (s.mentions || []).map(m => ({
166
- handle: String(m.name || '').replace(/^@/, ''), actor: m.href, page: null, inbox: null,
167
- }));
168
- const question = wire.questionDoc({
169
- urls, slug, content: s.text ?? '', published: s.published, inReplyTo: s.inReplyTo,
170
- attachments: [], mentions, also: s.replyActor ? [s.replyActor] : [],
171
- visibility: s.visibility || 'public', summary: s.spoiler || null, sensitive: !!s.sensitive, container,
172
- options: poll.options, multiple: !!poll.multiple, endTime: poll.expiresAt,
173
- closed: poll.closed, votersCount: poll.votersCount || 0,
174
- });
175
- await podNotes.write(publisher.remote, question.id, question);
176
- // The Create is overwritten too, so a group's Announce resolves to the
177
- // current count rather than to the one the poll opened with.
178
- await podNotes.writeCreate(publisher.remote, wire.createActivityId(question.id),
179
- publisher._pollActivity('Create', question, wire.createActivityId(question.id)));
180
- publisher.store.updateStatus(questionId, { poll });
181
-
182
- // Not `updated`: a changed count is not an edit, and stamping one would
183
- // have every client show the poll as edited each time somebody voted.
184
- //
185
- // The Update is named after the STATE it carries rather than the moment it
186
- // was sent. A clock only tells two rewrites apart when they fall in
187
- // different milliseconds, and a receiving server that has seen an activity
188
- // id drops the next one wearing it — which would quietly freeze the count.
189
- // Naming the state means an id changes exactly when there is something new
190
- // to say, and two sends of the same numbers are the duplicate they look
191
- // like.
192
- const stamp = crypto.createHash('sha256').update(JSON.stringify([
193
- poll.options.map(o => o.votes || 0), poll.votersCount || 0, poll.closed || '',
194
- ])).digest('hex').slice(0, 12);
195
- const update = publisher._pollActivity('Update', question, `${question.id}#poll-${stamp}`);
196
- const contacts = publisher.store.getContacts();
197
- const inboxes = [...new Set([
198
- ...(s.visibility === 'direct' ? [] : contacts.followers.map(f => f.sharedInbox || f.inbox)),
199
- ...(s.poll.mentionInboxes || []),
200
- ].filter(Boolean))];
201
- await publisher.deliverer.deliverToAll(inboxes, update);
202
- publisher.log(`poll ${closing ? 'closed' : 'count published'}: ${question.id} → ${inboxes.length} inbox(es)`);
203
- return poll;
204
- }
205
-
206
- /**
207
- * Shut any poll whose time is up. Called from the agent's sweep. Closing is
208
- * recorded BEFORE the republish, so a failed republish cannot leave a poll
209
- * open and collecting votes it has already refused.
210
- */
211
- export async function closeDuePolls(publisher, now = Date.now()) {
212
- const due = publisher.store.getStatuses().filter(s => s.kind === 'post' && s.poll
213
- && !s.poll.closed && s.poll.expiresAt && Date.parse(s.poll.expiresAt) <= now);
214
- for (const s of due) {
215
- const closed = new Date(Math.min(now, Date.parse(s.poll.expiresAt) || now)).toISOString();
216
- clearTimeout(publisher.pollTimers.get(s.noteId));
217
- publisher.pollTimers.delete(s.noteId);
218
- publisher.store.updateStatus(s.noteId, { poll: { ...s.poll, closed } });
219
- const done = await publisher.republishPoll(s.noteId, { closing: closed })
220
- .catch(e => { publisher.log(`poll close ${s.noteId}: ${e.message}`); return null; });
221
- // The roster only ever answered one question — has this person already
222
- // voted — and a shut poll has stopped asking it. Dropping it keeps a
223
- // document we serialize whole from carrying every poll's voters forever.
224
- if (done) {
225
- const all = publisher.store.read(polls.VOTES_DOC, {});
226
- if (all[s.noteId]) {
227
- delete all[s.noteId];
228
- publisher.store.write(polls.VOTES_DOC, all);
229
- }
230
- }
231
- }
232
- return due.length;
233
- }
234
-
235
- /** Stop the pending rewrite windows. Called at shutdown. */
236
- export function stopPolls(publisher) {
237
- for (const t of publisher.pollTimers.values()) clearTimeout(t);
238
- publisher.pollTimers.clear();
239
- }
@@ -1,224 +0,0 @@
1
- // restore.mjs — a machine that is behind the pod catching up: followers and
2
- // outbox entries the pod carries and this machine never heard of, and the
3
- // actor's own posts rebuilt from the published outbox.
4
-
5
- import * as wire from '../wire.mjs';
6
- import * as podNotes from '../../pod/notes.mjs';
7
- import { readLenient } from '../as2.mjs';
8
- import { rowContent } from './notes.mjs';
9
-
10
- const ACCEPT_AP = 'application/activity+json, application/ld+json; profile="https://www.w3.org/ns/activitystreams"';
11
- const REBUILD_MAX_PER_RUN = 200;
12
-
13
- // Anyone the pod says follows us that we have no record of, and never
14
- // deliberately removed. In the steady state there is nobody: the pod's list is
15
- // written from this one. They appear when the local half is BEHIND the pod —
16
- // a restored backup, a home copied off a dead machine — and republishing
17
- // blindly would delete them from the wire and, worse, stop delivering to them.
18
- //
19
- // Removals are what makes this safe to do unconditionally: `dropFollower`
20
- // records every unfollow, ejection and account deletion, so a returning name
21
- // is either genuinely still a follower or genuinely a mistake we made.
22
- export async function reconcileFollowers(publisher, contacts) {
23
- let published = [];
24
- try {
25
- published = (await publisher.readPublishedFollowers()) || []; // walks pages
26
- } catch { return 0; } // no list to reconcile against
27
-
28
- const known = new Set(contacts.followers.map(f => f.actor));
29
- const removed = new Set((contacts.removedFollowers || []).map(r => r.actor));
30
- const missing = published.filter(a => typeof a === 'string' && !known.has(a) && !removed.has(a));
31
- if (!missing.length) return 0;
32
-
33
- // An inbox is what delivery needs, and only the actor document has it — so
34
- // recovering a follower costs one fetch each. Capped: a list this wrong is
35
- // a restore, and the rest catch up on the next publish.
36
- let recovered = 0;
37
- for (const actor of missing.slice(0, 200)) {
38
- try {
39
- const res = await publisher.deliverer.signedFetch(actor, { headers: { accept: ACCEPT_AP } });
40
- if (!res.ok) continue;
41
- // The graph, not the compacted copy: `inbox` is `ldp:inbox` rather than
42
- // an `as:` term, and what is wanted here is the statement, not a key.
43
- const { doc, view, degraded } = await readLenient(await res.json());
44
- const actorDoc = view ?? doc;
45
- if (degraded) publisher.log?.(`actor ${actor} grounded to read: ${degraded}`);
46
- if (!actorDoc?.inbox) continue;
47
- contacts.followers.push({
48
- actor, inbox: actorDoc.inbox, sharedInbox: actorDoc.endpoints?.sharedInbox || null, recovered: true,
49
- // Said explicitly, because onUndo reads it: the pod publishes WHO
50
- // follows, never the id of the Follow that did it, so a recovered
51
- // record has nothing an Undo can be matched against and must not be
52
- // evictable by one naming anything at all.
53
- followId: null,
54
- });
55
- recovered++;
56
- } catch { /* unreachable now; it will be there next time */ }
57
- }
58
- if (recovered) {
59
- publisher.store.setContacts(contacts);
60
- publisher.log(`reconciled ${recovered} follower(s) the pod knew about and this machine did not `
61
- + '— a restored or copied state was behind');
62
- }
63
- return recovered;
64
- }
65
-
66
- // The same argument as reconcileFollowers, for the other published list. It
67
- // matters more: the outbox is the INDEX a statuses rebuild reads, so a
68
- // republish from a restored-and-behind machine would destroy the record of
69
- // everything this actor ever posted — and destroy it before anyone noticed
70
- // there was anything to recover.
71
- //
72
- // Safe for the same reason: `unrecordOutbox` leaves a tombstone, so an entry
73
- // the pod still carries is one this machine has not heard of, never one it
74
- // deliberately took back.
75
- export async function reconcileOutbox(publisher, outbox) {
76
- let published = [];
77
- try {
78
- published = await publisher.readPublishedOutbox() || [];
79
- } catch { return 0; }
80
- if (!Array.isArray(published) || !published.length) return 0;
81
-
82
- const idOf = (i) => (typeof i === 'string' ? i : i?.id || null);
83
- const known = new Set(outbox.map(idOf).filter(Boolean));
84
- const removed = new Set((publisher.store.read('outbox-removed.json', [])).map(r => r.id));
85
- const missing = published.filter((i) => {
86
- const id = idOf(i);
87
- return id && !known.has(id) && !removed.has(id);
88
- });
89
- if (!missing.length) return 0;
90
- // Newest first, like recordOutbox leaves it; the pod's copy is already in
91
- // that order, so appending the tail is enough to keep both sorted.
92
- outbox.push(...missing);
93
- publisher.store.write('outbox.json', outbox);
94
- publisher.log(`reconciled ${missing.length} outbox entr(ies) the pod carried and this machine did not`);
95
- return missing.length;
96
- }
97
-
98
- // Recover this actor's own posts from the pod's public face. The private half
99
- // lives on this machine now, so a restored backup or a replaced machine loses
100
- // statuses.json while the pod still serves every note. Followers already come
101
- // back; this is the other half of the same gap.
102
- //
103
- // The INDEX is ap/outbox, not the ap/notes/ listing, and that difference is
104
- // the safety argument. Deleting a post rewrites the outbox in one PUT, so an
105
- // entry still there is a post that still stands. The note DOCUMENT can outlive
106
- // its own deletion — deleteNote's `remote.delete` is a request that can fail —
107
- // so walking the container can bring back something its author took down.
108
- // `fromNotes` is for when you would rather have that than lose the post; it is
109
- // not the default, and it says so where it is offered.
110
- //
111
- // MERGE ONLY. A status this machine already holds is left exactly as it is:
112
- // it carries local facts — favourited, reblogged, the activities an Undo has
113
- // to name — that the pod knows nothing about. That is also what makes the
114
- // failure modes harmless: a listing that fails returns nothing, and nothing
115
- // is what an empty listing recovers.
116
- export async function rebuildStatuses(publisher, { fromNotes = false } = {}) {
117
- const { urls } = publisher;
118
- const ids = new Set();
119
- const boosts = [];
120
- let indexed = false;
121
-
122
- const published = await publisher.readPublishedOutbox().catch(() => null);
123
- if (published) {
124
- indexed = true;
125
- for (const item of published) {
126
- if (typeof item === 'string') { if (item.startsWith(urls.notes)) ids.add(item); }
127
- else if (item?.type === 'Announce') boosts.push(item);
128
- }
129
- }
130
- if (fromNotes) {
131
- // Three documents are published per post — the note, `-create` and
132
- // `-replies` — plus a `.keep`. Which is which is settled by reading the
133
- // document below, not by its name: a slug is a date and eight hex
134
- // characters and says nothing about what it holds.
135
- for (const child of await podNotes.list(publisher.remote, urls).catch(() => [])) {
136
- ids.add(child.url);
137
- }
138
- indexed = true;
139
- }
140
- if (!indexed) return { indexed: 0, recovered: 0, reblogs: 0, landed: false, why: 'the pod would not answer for its outbox' };
141
-
142
- const statuses = publisher.store.getStatuses();
143
- const have = new Set(statuses.map(s => s.noteId));
144
- const removed = new Set(publisher.store.read('outbox-removed.json', []).map(r => r.id));
145
- const recovered = [];
146
- // Capped per run: a long-lived actor's rebuild is otherwise one pod request
147
- // per post it has ever made, in one burst. What is left is picked up by
148
- // running it again — the merge is idempotent, so that is safe to repeat.
149
- let budget = REBUILD_MAX_PER_RUN;
150
- for (const id of ids) {
151
- if (budget <= 0) { publisher.log(`rebuild: stopping at ${REBUILD_MAX_PER_RUN} this run — run it again for the rest`); break; }
152
- if (have.has(id) || removed.has(id)) continue;
153
- budget--;
154
- const note = await podNotes.read(publisher.remote, id).catch(() => null);
155
- // Any object of ours: a Note, a poll, or whatever a client posted.
156
- if (!note?.type || note.id !== id || note.attributedTo !== urls.actor) continue;
157
- const attachments = wire.attachmentsOf(note);
158
- const mentions = (Array.isArray(note.tag) ? note.tag : [])
159
- .filter(t => t?.type === 'Mention' && t.href)
160
- .map(t => ({ href: t.href, name: t.name }));
161
- recovered.push({
162
- noteId: note.id, actor: urls.actor, content: rowContent(note),
163
- published: note.published || null,
164
- ...(note.inReplyTo ? { inReplyTo: note.inReplyTo } : {}),
165
- kind: 'post', slug: note.id.slice(urls.notes.length),
166
- ...(attachments.length ? { attachments } : {}),
167
- ...(mentions.length ? { mentions } : {}),
168
- recovered: true,
169
- });
170
- }
171
-
172
- // A boost is only recoverable for a post we can name — the Announce carries
173
- // the activity a later Undo needs, but not the boosted post's text, which
174
- // belongs to whoever wrote it.
175
- const merged = [...statuses, ...recovered];
176
- let reblogs = 0;
177
- for (const act of boosts) {
178
- const object = typeof act.object === 'string' ? act.object : act.object?.id;
179
- const s = object && merged.find(x => x.noteId === object);
180
- if (!s || s.reblogged) continue;
181
- s.reblogged = true;
182
- s.announceActivity = act;
183
- reblogs++;
184
- }
185
- if (!recovered.length && !reblogs) return { indexed: ids.size, recovered: 0, reblogs: 0, landed: true };
186
-
187
- // Written whole and sorted: addStatus unshifts and fires the streaming
188
- // event, so a loop of it would arrive backwards and push every recovered
189
- // post at connected clients as new.
190
- merged.sort((a, b) => String(b.published || '').localeCompare(String(a.published || '')));
191
- const kept = merged.slice(0, 1000);
192
- publisher.store.write('statuses.json', kept);
193
- const landed = await publisher.store.commit();
194
-
195
- publisher.log(`rebuilt ${recovered.length} post(s) and ${reblogs} boost(s) from the pod`
196
- + `${landed ? '' : ' — THE STATE WRITE DID NOT LAND'}`);
197
- return {
198
- indexed: ids.size, recovered: recovered.length, reblogs, landed,
199
- dropped: Math.max(0, merged.length - kept.length),
200
- };
201
- }
202
-
203
- // Own posts the outbox records and the statuses index does not.
204
- //
205
- // The index row is the last thing a post writes, and it goes by a debounced
206
- // PUT: a worker killed or a write refused after the post was answered leaves a
207
- // post that stands on the pod and reached followers but never shows on its
208
- // author's own timeline. Run when an agent becomes active. Cheap when nothing
209
- // is wrong — two cached documents compared, no request — and the capped,
210
- // merge-only rebuild when something is.
211
- export async function healStatuses(publisher) {
212
- const { urls, store } = publisher;
213
- const own = store.read('outbox.json', [])
214
- .map(i => (typeof i === 'string' ? i : null))
215
- .filter(id => id && id.startsWith(urls.notes));
216
- if (!own.length) return { missing: 0, recovered: 0 };
217
- const have = new Set(store.getStatuses().map(s => s.noteId));
218
- const removed = new Set(store.read('outbox-removed.json', []).map(r => r.id));
219
- const missing = own.filter(id => !have.has(id) && !removed.has(id));
220
- if (!missing.length) return { missing: 0, recovered: 0 };
221
- const r = await rebuildStatuses(publisher);
222
- publisher.log(`timeline index healed: ${missing.length} own post(s) were missing, ${r.recovered} recovered`);
223
- return { missing: missing.length, recovered: r.recovered };
224
- }
@@ -1,157 +0,0 @@
1
- # activitystreams.ttl — shapes for the ActivityStreams documents an identity
2
- # reads from strangers.
3
- #
4
- # Shapes are identified by fragments of this document, so nothing here claims a
5
- # namespace of its own.
6
- #
7
- # These are deliberately thin. They state what the ActivityPub specification
8
- # requires and nothing more, because every fediverse implementation writes
9
- # slightly different documents and a constraint beyond the spec is a constraint
10
- # that fires on somebody's real traffic. Nothing here rejects anything: a
11
- # failure is recorded and the activity is handled. A shape earns its way to
12
- # being enforced by not firing.
13
-
14
- @prefix sh: <http://www.w3.org/ns/shacl#> .
15
- @prefix as: <https://www.w3.org/ns/activitystreams#> .
16
- @prefix ldp: <http://www.w3.org/ns/ldp#> .
17
- @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
18
-
19
- # --- the actor document ------------------------------------------------------
20
-
21
- <#Actor>
22
- a sh:NodeShape ;
23
- sh:targetClass as:Person, as:Service, as:Group, as:Organization, as:Application ;
24
- sh:name "Actor" ;
25
- sh:description "Someone or something that acts: it has an address activities are sent to, and a name it is addressed by." ;
26
- sh:property [
27
- sh:path ldp:inbox ;
28
- sh:name "inbox" ;
29
- sh:description "The collection an actor receives activities in." ;
30
- sh:nodeKind sh:IRI ;
31
- sh:minCount 1 ;
32
- sh:maxCount 1 ;
33
- ] ;
34
- sh:property [
35
- sh:path as:preferredUsername ;
36
- sh:name "preferred username" ;
37
- sh:description "The short name an actor is addressed by, the local part of its @handle@host address." ;
38
- sh:datatype xsd:string ;
39
- sh:maxCount 1 ;
40
- ] .
41
-
42
- # --- the activities an identity acts on --------------------------------------
43
- #
44
- # Each states the two things ActivityPub requires of an activity: who performed
45
- # it, and what it is about.
46
-
47
- <#Create>
48
- a sh:NodeShape ;
49
- sh:targetClass as:Create ;
50
- sh:name "Create" ;
51
- sh:description "The bringing into existence of the object it names." ;
52
- sh:property [
53
- sh:path as:actor ;
54
- sh:name "actor" ;
55
- sh:description "The one that performed the activity." ;
56
- sh:nodeKind sh:BlankNodeOrIRI ;
57
- sh:minCount 1 ;
58
- sh:maxCount 1 ;
59
- ] ;
60
- sh:property [
61
- sh:path as:object ;
62
- sh:name "object" ;
63
- sh:description "The thing the activity is about." ;
64
- sh:minCount 1 ;
65
- sh:maxCount 1 ;
66
- ] .
67
-
68
- <#Follow>
69
- a sh:NodeShape ;
70
- sh:targetClass as:Follow ;
71
- sh:name "Follow" ;
72
- sh:description "A request to receive what the object it names publishes." ;
73
- sh:property [
74
- sh:path as:actor ;
75
- sh:name "actor" ;
76
- sh:description "The one that performed the activity." ;
77
- sh:nodeKind sh:BlankNodeOrIRI ;
78
- sh:minCount 1 ;
79
- sh:maxCount 1 ;
80
- ] ;
81
- sh:property [
82
- sh:path as:object ;
83
- sh:name "object" ;
84
- sh:description "The thing the activity is about." ;
85
- sh:minCount 1 ;
86
- sh:maxCount 1 ;
87
- ] .
88
-
89
- <#Accept>
90
- a sh:NodeShape ;
91
- sh:targetClass as:Accept ;
92
- sh:name "Accept" ;
93
- sh:description "Assent to the activity it names." ;
94
- sh:property [
95
- sh:path as:actor ;
96
- sh:name "actor" ;
97
- sh:description "The one that performed the activity." ;
98
- sh:nodeKind sh:BlankNodeOrIRI ;
99
- sh:minCount 1 ;
100
- sh:maxCount 1 ;
101
- ] ;
102
- sh:property [
103
- sh:path as:object ;
104
- sh:name "object" ;
105
- sh:description "The thing the activity is about." ;
106
- sh:minCount 1 ;
107
- sh:maxCount 1 ;
108
- ] .
109
-
110
- <#Undo>
111
- a sh:NodeShape ;
112
- sh:targetClass as:Undo ;
113
- sh:name "Undo" ;
114
- sh:description "The retraction of the activity it names, by the one that performed it." ;
115
- sh:property [
116
- sh:path as:actor ;
117
- sh:name "actor" ;
118
- sh:description "The one that performed the activity." ;
119
- sh:nodeKind sh:BlankNodeOrIRI ;
120
- sh:minCount 1 ;
121
- sh:maxCount 1 ;
122
- ] ;
123
- sh:property [
124
- sh:path as:object ;
125
- sh:name "object" ;
126
- sh:description "The thing the activity is about." ;
127
- sh:minCount 1 ;
128
- sh:maxCount 1 ;
129
- ] .
130
-
131
- <#Update>
132
- a sh:NodeShape ;
133
- sh:targetClass as:Update ;
134
- sh:name "Update" ;
135
- sh:description "A change to the object it names, carrying the parts that are now different." ;
136
- sh:property [
137
- sh:path as:actor ;
138
- sh:name "actor" ;
139
- sh:description "The one that performed the activity." ;
140
- sh:nodeKind sh:BlankNodeOrIRI ;
141
- sh:minCount 1 ;
142
- sh:maxCount 1 ;
143
- ] ;
144
- sh:property [
145
- sh:path as:object ;
146
- sh:name "object" ;
147
- sh:description "The thing the activity is about." ;
148
- sh:minCount 1 ;
149
- sh:maxCount 1 ;
150
- ] ;
151
- sh:property [
152
- sh:path ( as:object as:closed ) ;
153
- sh:name "closed" ;
154
- sh:description "The time the object stopped taking anything more." ;
155
- sh:datatype xsd:dateTime ;
156
- sh:maxCount 1 ;
157
- ] .