fedipod-server 0.21.0 → 0.28.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 (332) hide show
  1. package/README.md +5 -4
  2. package/dist/claims.d.ts +3 -1
  3. package/dist/claims.js +20 -7
  4. package/dist/components/context.jsonld +6 -0
  5. package/dist/handler.d.ts +29 -1
  6. package/dist/handler.js +89 -4
  7. package/dist/handler.jsonld +36 -1
  8. package/lib/client/c2s.mjs +100 -3
  9. package/lib/client/masto/accounts.mjs +44 -4
  10. package/lib/client/masto/body.mjs +90 -3
  11. package/lib/client/masto/index.mjs +5 -1
  12. package/lib/client/masto/instance.mjs +11 -3
  13. package/lib/client/masto/media.mjs +3 -57
  14. package/lib/client/masto/oauth.mjs +66 -9
  15. package/lib/client/masto/render.mjs +62 -5
  16. package/lib/client/masto/statuses.mjs +25 -1
  17. package/lib/client/masto/timelines.mjs +128 -6
  18. package/lib/connections/tagfeed.mjs +42 -5
  19. package/lib/core/as2.mjs +36 -5
  20. package/lib/core/contexts/index.mjs +14 -0
  21. package/lib/core/contexts/litepub-0.1.json +60 -0
  22. package/lib/core/contexts/map.json +3 -1
  23. package/lib/core/contexts/quotes.json +18 -0
  24. package/lib/core/graphview.mjs +41 -3
  25. package/lib/core/intake/activities.mjs +136 -2
  26. package/lib/core/intake/activity.mjs +79 -1
  27. package/lib/core/intake/group.mjs +37 -3
  28. package/lib/core/intake/index.mjs +32 -2
  29. package/lib/core/intake/notes.mjs +60 -3
  30. package/lib/core/lease.mjs +12 -2
  31. package/lib/core/profile-page.mjs +104 -0
  32. package/lib/core/publisher/collections.mjs +6 -1
  33. package/lib/core/publisher/index.mjs +35 -17
  34. package/lib/core/publisher/notes.mjs +104 -3
  35. package/lib/core/shapes/activitystreams.ttl +28 -0
  36. package/lib/core/shapes/index.mjs +26 -1
  37. package/lib/core/wire-quotes.mjs +71 -0
  38. package/lib/core/wire.mjs +59 -103
  39. package/lib/device/admin/routes/lifecycle.mjs +9 -0
  40. package/lib/device/admin/static.mjs +4 -1
  41. package/lib/device/admin/surface.mjs +26 -2
  42. package/lib/device/certs.mjs +18 -2
  43. package/lib/device/cli/commands/setup.mjs +6 -2
  44. package/lib/device/cli/commands/state.mjs +3 -1
  45. package/lib/device/gateway-move.mjs +58 -0
  46. package/lib/device/migrate.mjs +3 -1
  47. package/lib/device/setup.mjs +67 -7
  48. package/lib/gateway/front-core.mjs +307 -36
  49. package/lib/gateway/gateway-core.mjs +15 -6
  50. package/lib/gateway/notices.mjs +84 -0
  51. package/lib/gateway/quiet.mjs +184 -0
  52. package/lib/pod/root.mjs +6 -3
  53. package/lib/pod/state.mjs +8 -7
  54. package/lib/pod/transport.mjs +5 -1
  55. package/lib/session/README.md +123 -0
  56. package/lib/session/demo.html +94 -0
  57. package/lib/session/fedi-account.mjs +346 -0
  58. package/lib/session/fedi-login.mjs +123 -0
  59. package/lib/session/oidc-session.mjs +233 -0
  60. package/lib/session/package.json +41 -0
  61. package/package.json +1 -1
  62. package/run-agent.mjs +28 -0
  63. package/vendor/gate.cjs +6 -3
  64. package/web/admin/bar.css +66 -0
  65. package/web/admin/bar.js +38 -0
  66. package/web/admin/client/index.html +31 -3
  67. package/web/admin/client-pick.js +79 -0
  68. package/web/admin/gateway.js +22 -0
  69. package/web/admin/index.html +54 -13
  70. package/web/admin/notices-bar.js +90 -0
  71. package/web/admin/oauth-signin.mjs +37 -0
  72. package/web/admin/record.js +13 -1
  73. package/web/admin/setup/index.html +31 -3
  74. package/web/admin/tokens.css +15 -3
  75. package/web/admin/upkeep.js +4 -5
  76. package/web/app/README.md +14 -10
  77. package/web/app/admin-facade.mjs +51 -12
  78. package/web/app/agent.mjs +157 -19
  79. package/web/app/authorize_interaction.html +56 -0
  80. package/web/app/authorize_interaction.js +69 -0
  81. package/web/app/boot.mjs +233 -95
  82. package/web/app/dist/boot.js +477 -569
  83. package/web/app/dist/boot.js.map +4 -4
  84. package/web/app/dist/sw.js +1861 -532
  85. package/web/app/dist/sw.js.map +4 -4
  86. package/web/app/gateway-move.mjs +60 -0
  87. package/web/app/index.html +40 -49
  88. package/web/app/keys-browser.mjs +12 -15
  89. package/web/app/keystore.mjs +19 -36
  90. package/web/app/oidc-session.mjs +5 -188
  91. package/web/app/signup.mjs +172 -132
  92. package/web/app/sw-src.mjs +1 -1
  93. package/web/app/update.js +66 -0
  94. package/web/front/admin.html +5 -3
  95. package/web/front/new-account.html +2 -2
  96. package/web/front/notices.html +69 -0
  97. package/web/front/notices.js +107 -0
  98. package/web/front/run.html +15 -8
  99. package/web/front/run.js +124 -36
  100. package/web/icons/forum.svg +11 -0
  101. package/web/icons/gateway.svg +14 -0
  102. package/web/icons/local.svg +11 -0
  103. package/web/app/index.html~ +0 -152
  104. package/web/app/site/_headers +0 -9
  105. package/web/app/site/_redirects +0 -22
  106. package/web/app/site/admin/actors.js +0 -145
  107. package/web/app/site/admin/bar.css +0 -60
  108. package/web/app/site/admin/bar.js +0 -61
  109. package/web/app/site/admin/client/client.js +0 -66
  110. package/web/app/site/admin/client/index.html +0 -43
  111. package/web/app/site/admin/common.js +0 -23
  112. package/web/app/site/admin/connections.js +0 -112
  113. package/web/app/site/admin/gateway.js +0 -111
  114. package/web/app/site/admin/group.js +0 -258
  115. package/web/app/site/admin/index.html +0 -564
  116. package/web/app/site/admin/record.js +0 -378
  117. package/web/app/site/admin/setup/index.html +0 -215
  118. package/web/app/site/admin/setup/setup.js +0 -326
  119. package/web/app/site/admin/tokens.css +0 -56
  120. package/web/app/site/admin/upkeep.js +0 -178
  121. package/web/app/site/admin/window.css +0 -55
  122. package/web/app/site/admin/window.js +0 -136
  123. package/web/app/site/app/404.html +0 -40
  124. package/web/app/site/app/_headers +0 -2
  125. package/web/app/site/app/apple-touch-icon.png +0 -0
  126. package/web/app/site/app/assets/ICONS-DIPJeU0C.js +0 -2
  127. package/web/app/site/app/assets/ICONS-DIPJeU0C.js.map +0 -1
  128. package/web/app/site/app/assets/ScheduledAtField-D-Abfo4J.js +0 -183
  129. package/web/app/site/app/assets/ScheduledAtField-D-Abfo4J.js.map +0 -1
  130. package/web/app/site/app/assets/Temml-CCjoacWt.woff2 +0 -0
  131. package/web/app/site/app/assets/__vite-browser-external-C7iut881.js +0 -2
  132. package/web/app/site/app/assets/__vite-browser-external-C7iut881.js.map +0 -1
  133. package/web/app/site/app/assets/boosts-carousel-BiOaNdMT.jpg +0 -0
  134. package/web/app/site/app/assets/catch-up-O5q5C75Z.png +0 -0
  135. package/web/app/site/app/assets/chunk-aKtaBQYM.js +0 -1
  136. package/web/app/site/app/assets/compose-CFEx7wb4.js +0 -2
  137. package/web/app/site/app/assets/compose-CFEx7wb4.js.map +0 -1
  138. package/web/app/site/app/assets/compose-ZJN5xSb4.js +0 -35
  139. package/web/app/site/app/assets/compose-ZJN5xSb4.js.map +0 -1
  140. package/web/app/site/app/assets/debug.module-YFQgwnIr.js +0 -52
  141. package/web/app/site/app/assets/debug.module-YFQgwnIr.js.map +0 -1
  142. package/web/app/site/app/assets/dom-p1YcJ5Rw.js +0 -2
  143. package/web/app/site/app/assets/dom-p1YcJ5Rw.js.map +0 -1
  144. package/web/app/site/app/assets/home-mobile-dark@2x-Dc-J8-SM.png +0 -0
  145. package/web/app/site/app/assets/home-mobile-light@2x-Bd3fod8m.png +0 -0
  146. package/web/app/site/app/assets/home-tablet-dark@2x-BgcNI8le.png +0 -0
  147. package/web/app/site/app/assets/home-tablet-light@2x-BW2RV9T6.png +0 -0
  148. package/web/app/site/app/assets/icons/add-circle-line-WD0mwORD.js +0 -1
  149. package/web/app/site/app/assets/icons/alert-line-CpFUMf9s.js +0 -1
  150. package/web/app/site/app/assets/icons/android-2-line-Bzxnrd3S.js +0 -1
  151. package/web/app/site/app/assets/icons/announcement-line-5nwaJFJm.js +0 -1
  152. package/web/app/site/app/assets/icons/arrow-down-circle-line-a7Id9-Iv.js +0 -1
  153. package/web/app/site/app/assets/icons/arrow-down-line-B8uzXzq6.js +0 -1
  154. package/web/app/site/app/assets/icons/arrow-left-line-CGrGT9C-.js +0 -1
  155. package/web/app/site/app/assets/icons/arrow-right-line-CE-HERfX.js +0 -1
  156. package/web/app/site/app/assets/icons/arrow-to-up-line-CvdQjmcy.js +0 -1
  157. package/web/app/site/app/assets/icons/arrow-up-circle-line-DEparSNF.js +0 -1
  158. package/web/app/site/app/assets/icons/arrow-up-line-C7yBeugc.js +0 -1
  159. package/web/app/site/app/assets/icons/arrows-right-line-BtbXmJGP.js +0 -1
  160. package/web/app/site/app/assets/icons/at-line-CF66Bznk.js +0 -1
  161. package/web/app/site/app/assets/icons/attachment-line-BY8It-l9.js +0 -1
  162. package/web/app/site/app/assets/icons/blockquote-line-BS6pU_hm.js +0 -1
  163. package/web/app/site/app/assets/icons/board-line-C0UWXyA6.js +0 -1
  164. package/web/app/site/app/assets/icons/bookmark-line-1CtX5JLF.js +0 -1
  165. package/web/app/site/app/assets/icons/building-5-line-Dd59MzbO.js +0 -1
  166. package/web/app/site/app/assets/icons/bus-2-line-6_dvA0GG.js +0 -1
  167. package/web/app/site/app/assets/icons/calendar-day-line-Crpl_fmu.js +0 -1
  168. package/web/app/site/app/assets/icons/calendar-month-line-vommts2C.js +0 -1
  169. package/web/app/site/app/assets/icons/calendar-time-add-line-BU_DTlQk.js +0 -1
  170. package/web/app/site/app/assets/icons/camera-line-Cv69MEei.js +0 -1
  171. package/web/app/site/app/assets/icons/celebrate-line-B8DvIoi0.js +0 -1
  172. package/web/app/site/app/assets/icons/chart-bar-line-FK-QOmtF.js +0 -1
  173. package/web/app/site/app/assets/icons/chart-line-line-BGcJAFWw.js +0 -1
  174. package/web/app/site/app/assets/icons/chat-3-line-YFK2-hwz.js +0 -1
  175. package/web/app/site/app/assets/icons/check-circle-line-DsAxFr5-.js +0 -1
  176. package/web/app/site/app/assets/icons/clipboard-line-BBp364gJ.js +0 -1
  177. package/web/app/site/app/assets/icons/close-circle-line-CICdiuGz.js +0 -1
  178. package/web/app/site/app/assets/icons/close-line-DYrFWcjv.js +0 -1
  179. package/web/app/site/app/assets/icons/cloud-line-tiQAbah8.js +0 -1
  180. package/web/app/site/app/assets/icons/code-line-fAl8iiV3.js +0 -1
  181. package/web/app/site/app/assets/icons/comment-2-line-CR3g7WeB.js +0 -1
  182. package/web/app/site/app/assets/icons/copy-2-line-CLW69yF5.js +0 -1
  183. package/web/app/site/app/assets/icons/delete-2-line-B8xmbr4n.js +0 -1
  184. package/web/app/site/app/assets/icons/document-3-line-DBGNhAg2.js +0 -1
  185. package/web/app/site/app/assets/icons/document-line-C7mb9sb7.js +0 -1
  186. package/web/app/site/app/assets/icons/down-line-BGqSSThd.js +0 -1
  187. package/web/app/site/app/assets/icons/edit-4-line-DcgGaEhx.js +0 -1
  188. package/web/app/site/app/assets/icons/emoji-2-line-D8YgnM-y.js +0 -1
  189. package/web/app/site/app/assets/icons/exit-line-Dc07BS-x.js +0 -1
  190. package/web/app/site/app/assets/icons/external-link-line-BvpSkmjO.js +0 -1
  191. package/web/app/site/app/assets/icons/eye-2-line-CiWXx-f_.js +0 -1
  192. package/web/app/site/app/assets/icons/eye-close-line-BgTaSJ4P.js +0 -1
  193. package/web/app/site/app/assets/icons/filter-2-line-B_mR2usy.js +0 -1
  194. package/web/app/site/app/assets/icons/filter-line-C_nm-3cU.js +0 -1
  195. package/web/app/site/app/assets/icons/flag-1-line-C2HB05I0.js +0 -1
  196. package/web/app/site/app/assets/icons/forbid-circle-line-D3LTT9yd.js +0 -1
  197. package/web/app/site/app/assets/icons/formula-line-DLrt2d6j.js +0 -1
  198. package/web/app/site/app/assets/icons/grid-line-CgUmEikf.js +0 -1
  199. package/web/app/site/app/assets/icons/group-line-C5fnX0wU.js +0 -1
  200. package/web/app/site/app/assets/icons/hand-finger-2-line-BGgxbuL9.js +0 -1
  201. package/web/app/site/app/assets/icons/hashtag-line-CyTEEXBb.js +0 -1
  202. package/web/app/site/app/assets/icons/heart-crack-line-BZddbO2C.js +0 -1
  203. package/web/app/site/app/assets/icons/heart-line-CJ7K47J4.js +0 -1
  204. package/web/app/site/app/assets/icons/history-2-line-kWWvFzqv.js +0 -1
  205. package/web/app/site/app/assets/icons/history-line-bGQiwbTH.js +0 -1
  206. package/web/app/site/app/assets/icons/home-3-line-fRU1zeLG.js +0 -1
  207. package/web/app/site/app/assets/icons/information-line-iNk6-sOY.js +0 -1
  208. package/web/app/site/app/assets/icons/keyboard-line-DsCyuQNz.js +0 -1
  209. package/web/app/site/app/assets/icons/layout-4-line-CZkOX52Z.js +0 -1
  210. package/web/app/site/app/assets/icons/layout-5-line-B_znt5wO.js +0 -1
  211. package/web/app/site/app/assets/icons/left-line-eUw0tya4.js +0 -1
  212. package/web/app/site/app/assets/icons/lightning-line-DaKoDdix.js +0 -1
  213. package/web/app/site/app/assets/icons/link-2-line-DCqLmMA7.js +0 -1
  214. package/web/app/site/app/assets/icons/list-check-line-BAxosBh9.js +0 -1
  215. package/web/app/site/app/assets/icons/lock-line-CPoaXeUW.js +0 -1
  216. package/web/app/site/app/assets/icons/mail-line-B0P8aa1L.js +0 -1
  217. package/web/app/site/app/assets/icons/moon-line-CEqNAToP.js +0 -1
  218. package/web/app/site/app/assets/icons/more-1-fill-Dw2e5xab.js +0 -1
  219. package/web/app/site/app/assets/icons/more-3-line-BIiVjYTz.js +0 -1
  220. package/web/app/site/app/assets/icons/notification-line-BZZq2Gtu.js +0 -1
  221. package/web/app/site/app/assets/icons/pencil-line-CKh8-A1v.js +0 -1
  222. package/web/app/site/app/assets/icons/photo-album-line-Bnfg2u40.js +0 -1
  223. package/web/app/site/app/assets/icons/pin-line-DpTolyhs.js +0 -1
  224. package/web/app/site/app/assets/icons/play-fill-B0-G_8lX.js +0 -1
  225. package/web/app/site/app/assets/icons/qrcode-2-line-B6SPCaHn.js +0 -1
  226. package/web/app/site/app/assets/icons/quill-pen-line-B6LUrnCP.js +0 -1
  227. package/web/app/site/app/assets/icons/quote-left-fill-hQZ1cj_6.js +0 -1
  228. package/web/app/site/app/assets/icons/radar-line-CKyroTth.js +0 -1
  229. package/web/app/site/app/assets/icons/react-line-CoIKhhtK.js +0 -1
  230. package/web/app/site/app/assets/icons/refresh-2-line-D0mTF97S.js +0 -1
  231. package/web/app/site/app/assets/icons/right-line-B3KWJaqc.js +0 -1
  232. package/web/app/site/app/assets/icons/rocket-line-4KZl537D.js +0 -1
  233. package/web/app/site/app/assets/icons/round-fill-C69NTrZ0.js +0 -1
  234. package/web/app/site/app/assets/icons/round-line-B9k_1uo-.js +0 -1
  235. package/web/app/site/app/assets/icons/route-line-B28ReHcE.js +0 -1
  236. package/web/app/site/app/assets/icons/rows-4-line-CrBniagM.js +0 -1
  237. package/web/app/site/app/assets/icons/scan-line-DSf74YdP.js +0 -1
  238. package/web/app/site/app/assets/icons/search-2-line-B6geIU1z.js +0 -1
  239. package/web/app/site/app/assets/icons/settings-3-line-azJwK47w.js +0 -1
  240. package/web/app/site/app/assets/icons/settings-6-line-C_5zc5YR.js +0 -1
  241. package/web/app/site/app/assets/icons/share-2-line-D5MxWjrm.js +0 -1
  242. package/web/app/site/app/assets/icons/share-forward-line-Cp61fZzZ.js +0 -1
  243. package/web/app/site/app/assets/icons/sparkles-2-line-DYYyXTaW.js +0 -1
  244. package/web/app/site/app/assets/icons/sparkles-line-C2hY1s6I.js +0 -1
  245. package/web/app/site/app/assets/icons/time-line-BF3eet1v.js +0 -1
  246. package/web/app/site/app/assets/icons/transfer-4-line-oisfvTBw.js +0 -1
  247. package/web/app/site/app/assets/icons/translate-line-BFWol2Ce.js +0 -1
  248. package/web/app/site/app/assets/icons/unlock-line-X_x5vwv3.js +0 -1
  249. package/web/app/site/app/assets/icons/upload-3-line-DgzwUJeW.js +0 -1
  250. package/web/app/site/app/assets/icons/user-4-line-BVSynZCp.js +0 -1
  251. package/web/app/site/app/assets/icons/user-add-2-line-BlGS5Q8q.js +0 -1
  252. package/web/app/site/app/assets/icons/user-add-line-BbXdHG4z.js +0 -1
  253. package/web/app/site/app/assets/icons/user-edit-line-BFAHOV0X.js +0 -1
  254. package/web/app/site/app/assets/icons/user-follow-line-BXux0wSd.js +0 -1
  255. package/web/app/site/app/assets/icons/user-star-line-DdYlpHLU.js +0 -1
  256. package/web/app/site/app/assets/icons/user-warning-line-Cj43j0_E.js +0 -1
  257. package/web/app/site/app/assets/icons/user-x-line-DIDgNlPH.js +0 -1
  258. package/web/app/site/app/assets/icons/volume-line-Bs390KQt.js +0 -1
  259. package/web/app/site/app/assets/icons/volume-mute-line-DzlzIqFZ.js +0 -1
  260. package/web/app/site/app/assets/icons/walk-line-yNXfB-md.js +0 -1
  261. package/web/app/site/app/assets/icons/world-2-line-CenkGIwj.js +0 -1
  262. package/web/app/site/app/assets/icons/zoom-in-line-CaENUphf.js +0 -1
  263. package/web/app/site/app/assets/icons/zoom-out-line-BDNk0EWo.js +0 -1
  264. package/web/app/site/app/assets/instances-BNPptnn-.json +0 -333
  265. package/web/app/site/app/assets/locales/ar-SA-CfheRbCu.js +0 -1
  266. package/web/app/site/app/assets/locales/ca-ES-DHRW86Tz.js +0 -1
  267. package/web/app/site/app/assets/locales/cs-CZ-CKoqEuA1.js +0 -1
  268. package/web/app/site/app/assets/locales/de-DE-ByRPMRFy.js +0 -1
  269. package/web/app/site/app/assets/locales/eo-UY-CPC7wLCS.js +0 -1
  270. package/web/app/site/app/assets/locales/es-ES-dJa0Fd9t.js +0 -1
  271. package/web/app/site/app/assets/locales/eu-ES-C3djlPR2.js +0 -1
  272. package/web/app/site/app/assets/locales/fa-IR-DTT490Ck.js +0 -1
  273. package/web/app/site/app/assets/locales/fi-FI-B23MCPuz.js +0 -1
  274. package/web/app/site/app/assets/locales/fr-FR-BcaQsO5C.js +0 -1
  275. package/web/app/site/app/assets/locales/gl-ES-BR-CVKBN.js +0 -1
  276. package/web/app/site/app/assets/locales/he-IL-CfheRbCu.js +0 -1
  277. package/web/app/site/app/assets/locales/hu-HU-CfheRbCu.js +0 -1
  278. package/web/app/site/app/assets/locales/it-IT-C1136UFV.js +0 -1
  279. package/web/app/site/app/assets/locales/ja-JP-CRZmRAhD.js +0 -1
  280. package/web/app/site/app/assets/locales/kab-Cz8xH_TT.js +0 -1
  281. package/web/app/site/app/assets/locales/ko-KR-CG9UiY4o.js +0 -1
  282. package/web/app/site/app/assets/locales/lt-LT-CJcI6sHZ.js +0 -1
  283. package/web/app/site/app/assets/locales/nb-NO-CfheRbCu.js +0 -1
  284. package/web/app/site/app/assets/locales/nl-NL-DxN4Yc1t.js +0 -1
  285. package/web/app/site/app/assets/locales/oc-FR-CfheRbCu.js +0 -1
  286. package/web/app/site/app/assets/locales/pl-PL-DkwGXf6P.js +0 -1
  287. package/web/app/site/app/assets/locales/pseudo-LOCALE-CQ4XxkQ0.js +0 -1
  288. package/web/app/site/app/assets/locales/pt-BR-nGL0HdFS.js +0 -1
  289. package/web/app/site/app/assets/locales/pt-PT-Cp17aj2z.js +0 -1
  290. package/web/app/site/app/assets/locales/ru-RU-0Y8cIoaK.js +0 -1
  291. package/web/app/site/app/assets/locales/th-TH-CfheRbCu.js +0 -1
  292. package/web/app/site/app/assets/locales/tok-CfheRbCu.js +0 -1
  293. package/web/app/site/app/assets/locales/tr-TR-CfheRbCu.js +0 -1
  294. package/web/app/site/app/assets/locales/uk-UA-C5E3dFBf.js +0 -1
  295. package/web/app/site/app/assets/locales/zh-CN-BHJi7_et.js +0 -1
  296. package/web/app/site/app/assets/locales/zh-TW-CfheRbCu.js +0 -1
  297. package/web/app/site/app/assets/main-BdqNbG-a.js +0 -26
  298. package/web/app/site/app/assets/main-BdqNbG-a.js.map +0 -1
  299. package/web/app/site/app/assets/mock-home-DGhR__He.js +0 -2
  300. package/web/app/site/app/assets/mock-home-DGhR__He.js.map +0 -1
  301. package/web/app/site/app/assets/multi-column-ROck0NVt.jpg +0 -0
  302. package/web/app/site/app/assets/multi-hashtag-timeline-DH5INVHi.jpg +0 -0
  303. package/web/app/site/app/assets/nested-comments-thread-hv59kZLc.jpg +0 -0
  304. package/web/app/site/app/assets/polyfill-force-H8sUkLGp.js +0 -2
  305. package/web/app/site/app/assets/polyfill-force-H8sUkLGp.js.map +0 -1
  306. package/web/app/site/app/assets/pwa-viewport-ogKbF51K.js +0 -20
  307. package/web/app/site/app/assets/pwa-viewport-ogKbF51K.js.map +0 -1
  308. package/web/app/site/app/assets/style-B1iLPTPR.css +0 -2
  309. package/web/app/site/app/assets/temml-DuyIQMei.js +0 -97
  310. package/web/app/site/app/assets/temml-DuyIQMei.js.map +0 -1
  311. package/web/app/site/app/assets/tinyld.light.node-DtbBhLYC.js +0 -2
  312. package/web/app/site/app/assets/tinyld.light.node-DtbBhLYC.js.map +0 -1
  313. package/web/app/site/app/assets/year-in-posts-B2lywqdx.png +0 -0
  314. package/web/app/site/app/assets/year-in-posts-DJv_star.js +0 -3
  315. package/web/app/site/app/assets/year-in-posts-DJv_star.js.map +0 -1
  316. package/web/app/site/app/compose/index.html +0 -20
  317. package/web/app/site/app/favicon.ico +0 -0
  318. package/web/app/site/app/index.html +0 -73
  319. package/web/app/site/app/logo-192.png +0 -0
  320. package/web/app/site/app/logo-512.png +0 -0
  321. package/web/app/site/app/logo-badge-72.png +0 -0
  322. package/web/app/site/app/logo-maskable-512.png +0 -0
  323. package/web/app/site/app/logo-monochrome-512.png +0 -0
  324. package/web/app/site/app/logo-monochrome-maskable-512.png +0 -0
  325. package/web/app/site/app/manifest.webmanifest +0 -1
  326. package/web/app/site/app/og-image-2.jpg +0 -0
  327. package/web/app/site/app/og-image.png +0 -0
  328. package/web/app/site/app/robots.txt +0 -2
  329. package/web/app/site/app/version.json +0 -1
  330. package/web/app/site/boot.js +0 -34277
  331. package/web/app/site/index.html +0 -251
  332. package/web/app/site/sw.js +0 -71331
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  This is a CSS component. Install it in a fresh server instance or an instance
6
6
  you already run for pods, and turn sign-up on. A pod owner signs in at the
7
- server's `/run` page and opts in; from then on their pod is a full fediverse
7
+ server's `/.fediverse-account` page and opts in; from then on their pod is a full fediverse
8
8
  account: it accepts follows, delivers their posts, and serves their Mastodon
9
9
  client at the pod's own address. Signing up is the only way an account is
10
10
  made, and opting out is the only way one ends.
@@ -110,7 +110,8 @@ naming the pod the identity runs on. Nothing private is in it.
110
110
  | `agentDataDir` | Where each identity keeps the file naming its pod; its log lines go to the server's own log. Required whenever sign-up is on. |
111
111
  | `agentUiPath` | Where the owner's pages live on the pod's origin. `/fp/` by default; empty serves no pages. On a suffix pod it is under the pod's path, e.g. `/aisha/fp/`. |
112
112
  | `agentRegistryContainer` | The internal container holding the sign-up rows. |
113
- | `runPage` | The HTML served at `/run`: the page where a pod owner opts in or out. The package's own `web/front/run.html` is served unless you set this. |
113
+ | `runPage` | The HTML of the page where a pod owner opts in or out. The package's own `web/front/run.html` is served unless you set this. |
114
+ | `runPath` | Where that page answers. `/.fediverse-account` unless you set it. The path it takes is one the pod no longer serves, so it is yours to name; the dot keeps it out of the way of anybody's data. |
114
115
  | `agentWebIdSuffix` | Path from a pod's base to its owner's WebID. Defaults to `profile/card#me`. |
115
116
  | `agentPollSeconds` | How often the inbox is swept. Deliveries also wake the sweep as they land, so this is the fallback. |
116
117
  | `agentAutoAcceptFollows` | Whether a newly provisioned identity accepts follows without review. On by default. |
@@ -122,7 +123,7 @@ naming the pod the identity runs on. Nothing private is in it.
122
123
  | `directoryContainer` | The internal container holding the handle directory. |
123
124
  | `agentAutoFront` | Whether every identity that starts also gets a `@handle@frontHost` address — an inbox-only directory row, written once. Off by default. |
124
125
  | `offersPods` | Whether the signup page offers to create pods here. |
125
- | `signupPage` | The signup page served at `/`, `/signup` and `/new-account`. The package's own is served unless you set this. |
126
+ | `signupPage` | Unused on a pod server: making an account and a pod is the server's own, and this component does not answer `/`, `/signup` or `/new-account`. A gateway, which has no pod server behind it, serves this page. |
126
127
  | `gatewayWebId` | The WebID stamped on verification receipts. |
127
128
 
128
129
  `signupPage` and `runPage` are the page's HTML itself, not a path to a file.
@@ -195,7 +196,7 @@ as before.
195
196
 
196
197
  ## How sign-up works
197
198
 
198
- A pod owner opens the `/run` page, signs in with their pod,
199
+ A pod owner opens the `/.fediverse-account` page, signs in with their pod,
199
200
  and opts in or out. Underneath it is one endpoint:
200
201
 
201
202
  ```
package/dist/claims.d.ts CHANGED
@@ -1,7 +1,9 @@
1
+ /** Where a pod owner opts their identity in, when the operator named it. */
2
+ export declare const DEFAULT_RUN_PATH = "/.fediverse-account";
1
3
  export declare function claims(input: {
2
4
  host?: string;
3
5
  pathname: string;
4
- }, frontHost: string): boolean;
6
+ }, frontHost: string, runPath?: string): boolean;
5
7
  export declare const isInboxPath: (pathname: string) => boolean;
6
8
  /**
7
9
  * True when this path belongs to an identity's client surface.
package/dist/claims.js CHANGED
@@ -4,22 +4,35 @@
4
4
  // front's apex the gateway answers the fediverse routes; a pod subdomain is a
5
5
  // real Solid pod and is never claimed.
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.isInboxPath = void 0;
7
+ exports.isInboxPath = exports.DEFAULT_RUN_PATH = void 0;
8
8
  exports.claims = claims;
9
9
  exports.agentClaims = agentClaims;
10
- const FRONT_PATHS = new Set(['/', '/signup', '/new-account', '/run', '/roster',
10
+ // Signing up is CSS's, untouched: a person makes an account and a pod the way
11
+ // this server already does it, and nothing here needs saying to them about
12
+ // FediPod. Becoming a fediverse identity comes later and separately — they
13
+ // have a pod, they are logged in, and they opt it in at /run. So none of '/',
14
+ // '/signup' or '/new-account' is claimed on a pod server. A gateway is the
15
+ // other case: it has no pod server behind it, arranging the pod IS its job,
16
+ // and its own front keeps those pages.
17
+ const FRONT_PATHS = new Set(['/roster',
11
18
  '/.well-known/webfinger', '/api/handle', '/api/attach', '/api/agent',
12
19
  '/api/roster', '/api/revoke',
13
- // What the pages load: the sign-in library the /run and /admin pages use, and
14
- // the installer the signup page hands every new user.
15
- '/solid-oidc-client.js', '/install']);
16
- function claims(input, frontHost) {
20
+ // What the pages that remain load: the sign-in library the opt-in and
21
+ // roster pages use, and the installer, which is FediPod's own to hand out.
22
+ '/solid-oidc-client.js', '/install',
23
+ // Each page's own script, served beside it; a page without its script has
24
+ // buttons that never wake up.
25
+ '/new-account.js', '/run.js', '/admin.js']);
26
+ /** Where a pod owner opts their identity in, when the operator named it. */
27
+ exports.DEFAULT_RUN_PATH = '/.fediverse-account';
28
+ function claims(input, frontHost, runPath = exports.DEFAULT_RUN_PATH) {
17
29
  if (!input.host || !frontHost)
18
30
  return false;
19
31
  const bare = String(input.host).split(':')[0].toLowerCase();
20
32
  if (bare !== String(frontHost).toLowerCase())
21
33
  return false; // a pod subdomain → not ours
22
- return FRONT_PATHS.has(input.pathname) || input.pathname.startsWith('/u/')
34
+ return FRONT_PATHS.has(input.pathname) || input.pathname === runPath
35
+ || input.pathname.startsWith('/u/')
23
36
  || input.pathname.startsWith('/@'); // the short profile address
24
37
  }
25
38
  // What an identity answers on its own pod's origin: the protocol routes other
@@ -32,6 +32,9 @@
32
32
  "args_runPage": {
33
33
  "@id": "fps:dist/handler.jsonld#FediPodServerHandler_args_runPage"
34
34
  },
35
+ "args_runPath": {
36
+ "@id": "fps:dist/handler.jsonld#FediPodServerHandler_args_runPath"
37
+ },
35
38
  "args_adminPage": {
36
39
  "@id": "fps:dist/handler.jsonld#FediPodServerHandler_args_adminPage"
37
40
  },
@@ -86,6 +89,9 @@
86
89
  "runPage": {
87
90
  "@id": "fps:dist/handler.jsonld#FediPodServerHandler_args_runPage"
88
91
  },
92
+ "runPath": {
93
+ "@id": "fps:dist/handler.jsonld#FediPodServerHandler_args_runPath"
94
+ },
89
95
  "adminPage": {
90
96
  "@id": "fps:dist/handler.jsonld#FediPodServerHandler_args_adminPage"
91
97
  },
package/dist/handler.d.ts CHANGED
@@ -16,8 +16,10 @@ export interface FediPodServerArgs {
16
16
  offersPods?: boolean;
17
17
  /** The new-account page HTML served at the root. */
18
18
  signupPage?: string;
19
- /** The run-your-identity page HTML served at /run. */
19
+ /** The run-your-identity page HTML. */
20
20
  runPage?: string;
21
+ /** Where that page answers. Defaults to `/.fediverse-account`; the path it takes is one the pod server no longer serves, so an operator may name it. */
22
+ runPath?: string;
21
23
  /** The accounts-roster page HTML served at /roster. */
22
24
  adminPage?: string;
23
25
  /** Directory holding each agent identity's signing key and log. Required when runtime opt-in is on. */
@@ -63,6 +65,7 @@ export declare class FediPodServerHandler extends HttpHandler implements Initial
63
65
  private readonly args;
64
66
  private readonly io;
65
67
  readonly dir: Directory;
68
+ private readonly runPath;
66
69
  private readonly podPut;
67
70
  private readonly logger;
68
71
  private readonly claimed;
@@ -149,12 +152,37 @@ export declare class FediPodServerHandler extends HttpHandler implements Initial
149
152
  * reply and nowhere else. Re-opting-in rotates the secret — that is how a
150
153
  * lost one is recovered.
151
154
  */
155
+ /**
156
+ * The WebIDs behind the account session in the request, or none. The server
157
+ * keeps that session in a `css-account` cookie; its own account API is what
158
+ * says which WebIDs the session owns, and it takes the same cookie. Nothing
159
+ * here trusts the cookie's presence — the account API does the deciding, and
160
+ * the caller picks the WebID that owns the pod being claimed.
161
+ */
162
+ webIdsFromSession(request: {
163
+ headers: {
164
+ get(name: string): string | null;
165
+ };
166
+ }): Promise<string[]>;
152
167
  optInPod({ podBase, webId }: {
153
168
  podBase: string;
154
169
  webId: string;
155
170
  }): Promise<Record<string, unknown> & {
156
171
  httpStatus: number;
157
172
  }>;
173
+ /**
174
+ * What a pod would be as an identity here, without changing anything: its
175
+ * handle, the host its address carries, and whether it runs here already.
176
+ */
177
+ describePod({ podBase }: {
178
+ podBase: string;
179
+ }): Promise<{
180
+ handle: string;
181
+ host: string;
182
+ address: string;
183
+ running: boolean;
184
+ manage: string;
185
+ }>;
158
186
  /** The reverse: stop the identity and let the pod be plain LDP again. */
159
187
  optOutPod({ podBase }: {
160
188
  podBase: string;
package/dist/handler.js CHANGED
@@ -58,6 +58,13 @@ function deriveHandle(podBase) {
58
58
  const segments = u.pathname.split('/').filter((seg) => seg.length > 0);
59
59
  return segments.length > 0 ? segments[segments.length - 1] : u.hostname.split('.')[0];
60
60
  }
61
+ /** The opt-in page's path: leading slash, no trailing one, the default when unset. */
62
+ function normalizeRunPath(raw) {
63
+ const said = String(raw ?? '').trim().replace(/\/+$/u, '');
64
+ if (!said)
65
+ return claims_1.DEFAULT_RUN_PATH;
66
+ return said.startsWith('/') ? said : `/${said}`;
67
+ }
61
68
  /** A door path always has both slashes, so claiming and stripping agree. */
62
69
  function normalizeUiPath(raw) {
63
70
  if (raw === '')
@@ -69,6 +76,7 @@ class FediPodServerHandler extends community_server_1.HttpHandler {
69
76
  args;
70
77
  io;
71
78
  dir;
79
+ runPath;
72
80
  podPut;
73
81
  logger = (0, community_server_1.getLoggerFor)(this);
74
82
  // Every pod whose routes this server claims, keyed by pod base. A claim
@@ -107,6 +115,9 @@ class FediPodServerHandler extends community_server_1.HttpHandler {
107
115
  // hostname, not host: the claim check compares bare hostnames, so a port
108
116
  // carried here would stop the front matching its own requests.
109
117
  this.frontHost = args.frontHost || new URL(args.frontOrigin).hostname;
118
+ // One answer for both the claim and the route, so a server cannot answer
119
+ // at one path and refuse at another.
120
+ this.runPath = normalizeRunPath(args.runPath);
110
121
  this.uiPath = normalizeUiPath(args.agentUiPath);
111
122
  this.registry = args.agentRuntimeOptIn
112
123
  ? (0, directory_1.makeAgentRegistry)(this.io, absolute(args.agentRegistryContainer ?? '/.internal/fedipod/agents/'))
@@ -391,7 +402,7 @@ class FediPodServerHandler extends community_server_1.HttpHandler {
391
402
  // The front's own routes win first, so a suffix pod can never shadow the
392
403
  // door's dispatch, its WebFinger or its API even where its mount would
393
404
  // otherwise contain that path.
394
- if ((0, claims_1.claims)({ host, pathname }, this.frontHost))
405
+ if ((0, claims_1.claims)({ host, pathname }, this.frontHost, this.runPath))
395
406
  return;
396
407
  // Claimed from the opt-in roster, never from what is running: a pod resource
397
408
  // must not be served by CSS for the seconds before an identity finishes
@@ -432,6 +443,37 @@ class FediPodServerHandler extends community_server_1.HttpHandler {
432
443
  * reply and nowhere else. Re-opting-in rotates the secret — that is how a
433
444
  * lost one is recovered.
434
445
  */
446
+ /**
447
+ * The WebIDs behind the account session in the request, or none. The server
448
+ * keeps that session in a `css-account` cookie; its own account API is what
449
+ * says which WebIDs the session owns, and it takes the same cookie. Nothing
450
+ * here trusts the cookie's presence — the account API does the deciding, and
451
+ * the caller picks the WebID that owns the pod being claimed.
452
+ */
453
+ async webIdsFromSession(request) {
454
+ const cookie = request.headers.get('cookie');
455
+ if (!cookie || !/(?:^|;\s*)css-account=/u.test(cookie))
456
+ return [];
457
+ const base = this.args.frontOrigin.replace(/\/$/u, '');
458
+ const asJson = { cookie, accept: 'application/json' };
459
+ try {
460
+ const index = await fetch(`${base}/.account/`, { headers: asJson });
461
+ if (!index.ok)
462
+ return [];
463
+ const controls = (await index.json()).controls;
464
+ const webIdLink = controls?.account?.webId;
465
+ if (!webIdLink)
466
+ return [];
467
+ const linked = await fetch(webIdLink, { headers: asJson });
468
+ if (!linked.ok)
469
+ return [];
470
+ const links = (await linked.json()).webIdLinks ?? {};
471
+ return Object.keys(links);
472
+ }
473
+ catch {
474
+ return [];
475
+ }
476
+ }
435
477
  async optInPod({ podBase, webId }) {
436
478
  if (!this.registry)
437
479
  return { httpStatus: 501, error: 'this server does not offer runtime opt-in' };
@@ -474,6 +516,39 @@ class FediPodServerHandler extends community_server_1.HttpHandler {
474
516
  return { httpStatus: 201, ok: true, handle, host,
475
517
  doorSecret: door.secret, doorPath: this.uiPath, status: 'starting' };
476
518
  }
519
+ /**
520
+ * What a pod would be as an identity here, without changing anything: its
521
+ * handle, the host its address carries, and whether it runs here already.
522
+ */
523
+ async describePod({ podBase }) {
524
+ const base = podBase.endsWith('/') ? podBase : `${podBase}/`;
525
+ const handle = deriveHandle(base);
526
+ let host;
527
+ try {
528
+ host = this.validateAgentPod(base).host;
529
+ }
530
+ catch {
531
+ host = new URL(base).host.toLowerCase();
532
+ }
533
+ const running = this.agentHandles.get(handle) === base;
534
+ // The management page's door takes its key once in the address and keeps
535
+ // the browser in with a cookie. Only the owner's own page ever asks for
536
+ // this description, so the key rides on the link for a running account.
537
+ // Read from the pod, where the key always is, rather than from what this
538
+ // process happens to hold: an account still starting has none in memory.
539
+ let key;
540
+ if (running) {
541
+ try {
542
+ key = (await this.doorSecretFor(base)).secret;
543
+ }
544
+ catch {
545
+ key = this.doorSecrets.get(base);
546
+ }
547
+ }
548
+ const door = base.replace(/\/$/u, '') + this.uiPath;
549
+ return { handle, host, address: `@${handle}@${host}`, running,
550
+ manage: key ? `${door}?dk-token=${encodeURIComponent(key)}` : door };
551
+ }
477
552
  /** The reverse: stop the identity and let the pod be plain LDP again. */
478
553
  async optOutPod({ podBase }) {
479
554
  if (!this.registry)
@@ -530,7 +605,7 @@ class FediPodServerHandler extends community_server_1.HttpHandler {
530
605
  // The front's own routes win first — its dispatch, WebFinger and API sit at
531
606
  // the apex above every suffix pod — so a claim is consulted only where the
532
607
  // front does not answer.
533
- const claimed = (0, claims_1.claims)({ host, pathname }, this.frontHost) ? null : this.resolveClaim(host, pathname);
608
+ const claimed = (0, claims_1.claims)({ host, pathname }, this.frontHost, this.runPath) ? null : this.resolveClaim(host, pathname);
534
609
  if (claimed) {
535
610
  const identity = this.surfaces.get(claimed.podBase);
536
611
  if (!identity) {
@@ -572,13 +647,21 @@ class FediPodServerHandler extends community_server_1.HttpHandler {
572
647
  const out = await routeFront(whatwg, {
573
648
  host: this.frontHost,
574
649
  frontOrigin: this.args.frontOrigin,
650
+ // This server issued the session the reader is already using, so it can
651
+ // read it rather than sending them to an identity provider that has just
652
+ // told them who they are. Same-origin only; front-core decides that.
653
+ webIdsFromSession: (req) => this.webIdsFromSession(req),
575
654
  gatewayWebId: this.args.gatewayWebId,
576
655
  offersPods: !!this.args.offersPods,
577
656
  signupPage: this.args.signupPage || webFile('new-account.html'),
578
657
  runPage: this.args.runPage || webFile('run.html'),
658
+ runPath: this.runPath,
579
659
  adminPage: this.args.adminPage || webFile('admin.html'),
580
- // The /run and /roster pages load the sign-in library, and the signup page
581
- // hands out the installer command; without these the pages render but
660
+ // The notices page renders here too, and says the server keeps none:
661
+ // a notices store is the Netlify front's, not this component's.
662
+ noticesPage: webFile('notices.html'),
663
+ // The opt-in and roster pages load the sign-in library, and the installer
664
+ // command is handed out with it; without these the pages render but
582
665
  // cannot be used.
583
666
  authBundle: webFile('solid-oidc-client.js'),
584
667
  // Each page's own script — inline until 2026-09-09, so that the pages can
@@ -587,6 +670,7 @@ class FediPodServerHandler extends community_server_1.HttpHandler {
587
670
  'new-account.js': webFile('new-account.js'),
588
671
  'run.js': webFile('run.js'),
589
672
  'admin.js': webFile('admin.js'),
673
+ 'notices.js': webFile('notices.js'),
590
674
  },
591
675
  installScript: webFile('install.sh'),
592
676
  lookup: (h) => this.dir.lookup(h),
@@ -628,6 +712,7 @@ class FediPodServerHandler extends community_server_1.HttpHandler {
628
712
  ? {
629
713
  optIn: (a) => this.optInPod(a),
630
714
  optOut: (a) => this.optOutPod(a),
715
+ describe: (a) => this.describePod(a),
631
716
  }
632
717
  : undefined,
633
718
  });
@@ -93,7 +93,20 @@
93
93
  }
94
94
  ]
95
95
  },
96
- "comment": "The run-your-identity page HTML served at /run."
96
+ "comment": "The run-your-identity page HTML."
97
+ },
98
+ {
99
+ "@id": "fps:dist/handler.jsonld#FediPodServerHandler_args_runPath",
100
+ "range": {
101
+ "@type": "ParameterRangeUnion",
102
+ "parameterRangeElements": [
103
+ "xsd:string",
104
+ {
105
+ "@type": "ParameterRangeUndefined"
106
+ }
107
+ ]
108
+ },
109
+ "comment": "Where that page answers. Defaults to `/.fediverse-account`; the path it takes is one the pod server no longer serves, so an operator may name it."
97
110
  },
98
111
  {
99
112
  "@id": "fps:dist/handler.jsonld#FediPodServerHandler_args_adminPage",
@@ -240,6 +253,10 @@
240
253
  "memberFieldName": "dir",
241
254
  "range": "fps:dist/directory.jsonld#Directory"
242
255
  },
256
+ {
257
+ "@id": "fps:dist/handler.jsonld#FediPodServerHandler__member_runPath",
258
+ "memberFieldName": "runPath"
259
+ },
243
260
  {
244
261
  "@id": "fps:dist/handler.jsonld#FediPodServerHandler__member_podPut",
245
262
  "memberFieldName": "podPut"
@@ -344,10 +361,18 @@
344
361
  "@id": "fps:dist/handler.jsonld#FediPodServerHandler__member_matchIdentity",
345
362
  "memberFieldName": "matchIdentity"
346
363
  },
364
+ {
365
+ "@id": "fps:dist/handler.jsonld#FediPodServerHandler__member_webIdsFromSession",
366
+ "memberFieldName": "webIdsFromSession"
367
+ },
347
368
  {
348
369
  "@id": "fps:dist/handler.jsonld#FediPodServerHandler__member_optInPod",
349
370
  "memberFieldName": "optInPod"
350
371
  },
372
+ {
373
+ "@id": "fps:dist/handler.jsonld#FediPodServerHandler__member_describePod",
374
+ "memberFieldName": "describePod"
375
+ },
351
376
  {
352
377
  "@id": "fps:dist/handler.jsonld#FediPodServerHandler__member_optOutPod",
353
378
  "memberFieldName": "optOutPod"
@@ -413,6 +438,12 @@
413
438
  "@id": "fps:dist/handler.jsonld#FediPodServerHandler_args_runPage"
414
439
  }
415
440
  },
441
+ {
442
+ "keyRaw": "runPath",
443
+ "value": {
444
+ "@id": "fps:dist/handler.jsonld#FediPodServerHandler_args_runPath"
445
+ }
446
+ },
416
447
  {
417
448
  "keyRaw": "adminPage",
418
449
  "value": {
@@ -515,6 +546,10 @@
515
546
  "@id": "fps:dist/handler.jsonld#FediPodServerArgs__member_runPage",
516
547
  "memberFieldName": "runPage"
517
548
  },
549
+ {
550
+ "@id": "fps:dist/handler.jsonld#FediPodServerArgs__member_runPath",
551
+ "memberFieldName": "runPath"
552
+ },
518
553
  {
519
554
  "@id": "fps:dist/handler.jsonld#FediPodServerArgs__member_adminPage",
520
555
  "memberFieldName": "adminPage"
@@ -5,8 +5,12 @@
5
5
  // SAME helper the facade and admin surfaces use — this module re-implements
6
6
  // no persistence and no delivery, so one write path stays one.
7
7
  //
8
- // GETs on the actor and outbox are redirects: the pod's documents are the
9
- // canonical ones, and a second renderer here would only drift from them.
8
+ // GETs on the actor and outbox are redirects to the pod's documents wherever
9
+ // those are reachable, which is the Server. Standalone they are not: the actor
10
+ // published there names no address a local client could write to, and on a
11
+ // fronted account the published outbox address is the Gateway's write door,
12
+ // which does not answer a read. So on that build both are served here, to the
13
+ // client already talking to this agent.
10
14
  //
11
15
  // `dispatch` takes an activity and answers with { status, body, headers }; it
12
16
  // touches no request or response. The HTTP handler below is one caller. The
@@ -32,6 +36,9 @@ const MAX_BODY = 512 * 1024; // same ceiling the inbox drain enforces
32
36
  // the pod an unbounded read; the log says when that happened.
33
37
  const MAX_INBOX_PAGE = 500;
34
38
 
39
+ // How many recorded activities one page of the locally-served outbox carries.
40
+ const MAX_OUTBOX_PAGE = 50;
41
+
35
42
  // §6 names activities; anything else with a type is an object to wrap.
36
43
  const ACTIVITY_TYPES = new Set([
37
44
  'Create', 'Update', 'Delete', 'Follow', 'Like', 'Announce', 'Undo',
@@ -56,10 +63,15 @@ function readBody(req) {
56
63
  }
57
64
 
58
65
  export class C2S {
59
- constructor({ agent, log = console.log, auth }) {
66
+ constructor({ agent, log = console.log, auth, scheme = null, mount = '' }) {
60
67
  this.agent = agent;
61
68
  this.log = log;
62
69
  this.auth = auth;
70
+ // What this agent calls itself, for the actor it serves a local client.
71
+ // The same two values oidc-auth rebuilds the DPoP `htu` from, so the two
72
+ // surfaces agree on the address a client just used.
73
+ this.scheme = scheme;
74
+ this.mount = mount;
63
75
  }
64
76
 
65
77
  get store() { return this.agent.store; }
@@ -157,6 +169,85 @@ export class C2S {
157
169
  });
158
170
  }
159
171
 
172
+ // The actor a client-to-server client on this machine reads. The same
173
+ // document the pod holds, but for the fields naming where a write goes —
174
+ // the outbox and the two OAuth endpoints — and for `id`, which names this
175
+ // address so the client files its credential here and comes back. The pod's
176
+ // id stays on as an alias; the two are one identity.
177
+ // What this agent calls itself, to the client that just asked. The Host
178
+ // passed the Authorities firewall before any route ran, so whichever alias
179
+ // the client used is one this agent answers on. The scheme is read off the
180
+ // connection unless the caller named one: only the Server passes a scheme,
181
+ // and standalone this listener is its own TLS.
182
+ localOrigin(req) {
183
+ const host = req.headers.host;
184
+ if (!host) return null;
185
+ const scheme = this.scheme
186
+ || (req.socket?.encrypted || req.headers['x-forwarded-proto'] === 'https' ? 'https' : 'http');
187
+ return `${scheme}://${host}${this.mount}/`;
188
+ }
189
+
190
+ /**
191
+ * The owner's own outbox, from what this agent recorded. The pod holds the
192
+ * published copy and it stays the canonical one; this is served only where
193
+ * that copy cannot be read by the client asking.
194
+ */
195
+ sendLocalOutbox(res, url, origin) {
196
+ const id = `${origin}ap/outbox`;
197
+ const outbox = this.store.read('outbox.json', []); // newest first
198
+ const page = url?.searchParams?.get('page') || null;
199
+ const ct = { 'content-type': 'application/activity+json; charset=utf-8' };
200
+ if (!page) {
201
+ return this.send(res, 200, {
202
+ '@context': wire.AS_CTX, id, type: 'OrderedCollection', totalItems: outbox.length,
203
+ ...(outbox.length ? { first: `${id}?page=1` } : { orderedItems: [] }),
204
+ }, ct);
205
+ }
206
+ const n = Number(page);
207
+ if (!Number.isInteger(n) || n < 1) {
208
+ return this.send(res, 400, { error: 'page is a whole number from 1' });
209
+ }
210
+ const start = (n - 1) * MAX_OUTBOX_PAGE;
211
+ const items = outbox.slice(start, start + MAX_OUTBOX_PAGE);
212
+ return this.send(res, 200, {
213
+ '@context': wire.AS_CTX,
214
+ id: `${id}?page=${n}`,
215
+ type: 'OrderedCollectionPage',
216
+ partOf: id,
217
+ ...(start + items.length < outbox.length ? { next: `${id}?page=${n + 1}` } : {}),
218
+ // A post is recorded by its object id and published as the Create beside
219
+ // it — the same mapping the pod's copy uses, from the same helper.
220
+ orderedItems: items.map(wire.outboxWireItem),
221
+ }, ct);
222
+ }
223
+
224
+ sendLocalActor(req, res, origin) {
225
+ const pub = this.agent.publisher;
226
+ const cfg = pub.config || {};
227
+ const gw = cfg.gateway;
228
+ const gwActive = gw && gw.url && gw.mode && gw.mode !== 'off';
229
+ const doc = pub.actorDocFor({
230
+ inbox: gwActive ? gw.url : (cfg.inboxUrl || null),
231
+ clientOrigin: origin,
232
+ });
233
+ // A client files its credential under the id it reads here and comes back
234
+ // to that address for everything after, so this document has to name
235
+ // itself. The pod's id stays on as an alias rather than being dropped —
236
+ // the two are one identity. Nothing published ever carries this id: every
237
+ // activity the dispatcher builds names the pod's actor.
238
+ const canonical = doc.id;
239
+ doc.id = `${origin}ap/actor`;
240
+ doc.alsoKnownAs = [...new Set([...(doc.alsoKnownAs || []), canonical])];
241
+ const body = JSON.stringify(doc);
242
+ res.writeHead(200, {
243
+ 'content-type': 'application/activity+json; charset=utf-8',
244
+ 'cache-control': 'no-store',
245
+ 'content-length': Buffer.byteLength(body),
246
+ });
247
+ res.end(req.method === 'HEAD' ? undefined : body);
248
+ return true;
249
+ }
250
+
160
251
  async handle(req, res, pathname, url) { // eslint-disable-line no-unused-vars
161
252
  if (pathname !== '/ap/outbox' && pathname !== '/ap/actor' && pathname !== '/ap/inbox') return false;
162
253
  if (req.method === 'OPTIONS') {
@@ -179,6 +270,12 @@ export class C2S {
179
270
  return this.sendInbox(res, url);
180
271
  }
181
272
  if (req.method === 'GET' || req.method === 'HEAD') {
273
+ // Where the client surface answers on an address a stranger can reach,
274
+ // the pod's copies already name it and are the documents. Standalone
275
+ // they are not reachable by the client asking, so it is answered here.
276
+ const local = this.agent.publisher?.clientOrigin ? null : this.localOrigin(req);
277
+ if (local && pathname === '/ap/actor') return this.sendLocalActor(req, res, local);
278
+ if (local && pathname === '/ap/outbox') return this.sendLocalOutbox(res, url, local);
182
279
  // The pod's copy is the document; send the reader there.
183
280
  const target = pathname === '/ap/actor' ? this.urls.actor : this.urls.outbox;
184
281
  res.writeHead(303, { location: target, 'cache-control': 'no-store' });
@@ -6,8 +6,7 @@
6
6
  import crypto from 'node:crypto';
7
7
  import * as podMedia from '../../pod/media.mjs';
8
8
  import * as social from '../../core/social.mjs';
9
- import { readBody } from './body.mjs';
10
- import { readMultipart } from './media.mjs';
9
+ import { readBody, readMultipart } from './body.mjs';
11
10
 
12
11
  export async function handle(api, ctx) {
13
12
  const { req, res, pathname, url, send } = ctx; // eslint-disable-line no-unused-vars
@@ -226,6 +225,39 @@ export async function handle(api, ctx) {
226
225
 
227
226
  if (/^\/api\/v1\/accounts\/[a-f0-9]+\/featured_tags$/.test(pathname)) return send(200, []);
228
227
 
228
+ // The instance's own directory is its one account; there is nothing to
229
+ // suggest and nothing trending, so those screens are empty rather than 404.
230
+ if (pathname === '/api/v1/directory') return send(200, api.urls?.actor ? [api.account(api.urls.actor)] : []);
231
+ if (pathname === '/api/v1/suggestions') return send(200, []);
232
+
233
+ // The lists this account is on — what "add to list" from a profile reads.
234
+ const mAccLists = /^\/api\/v1\/accounts\/([a-f0-9]+)\/lists$/.exec(pathname);
235
+ if (mAccLists && req.method === 'GET') {
236
+ const actorUrl = api.store.urlFor(mAccLists[1]);
237
+ if (!actorUrl) return send(404, { error: 'Record not found' });
238
+ return send(200, api.store.getLists().filter(l => (l.members || []).includes(actorUrl))
239
+ .map(l => ({ id: l.id, title: l.title, replies_policy: l.repliesPolicy || 'list', exclusive: false })));
240
+ }
241
+
242
+ // The people muted and blocked, as accounts; the domains blocked, as names.
243
+ if (pathname === '/api/v1/mutes' && req.method === 'GET') {
244
+ return send(200, (api.store.getMuted().actors || []).map(a => api.account(a)));
245
+ }
246
+ if (pathname === '/api/v1/blocks' && req.method === 'GET') {
247
+ return send(200, (api.store.getBlocklist().actors || []).map(a => api.account(a)));
248
+ }
249
+ if (pathname === '/api/v1/domain_blocks') {
250
+ const b = api.store.getBlocklist();
251
+ if (req.method === 'GET') return send(200, [...b.domains]);
252
+ const body = await readBody(req);
253
+ const domain = String(body.domain || url.searchParams.get('domain') || '').trim().toLowerCase();
254
+ if (!/^[a-z0-9.-]+\.[a-z]{2,}$/u.test(domain)) return send(422, { error: 'a domain is required' });
255
+ if (req.method === 'POST' && !b.domains.includes(domain)) b.domains.push(domain);
256
+ if (req.method === 'DELETE') b.domains = b.domains.filter(d => d !== domain);
257
+ api.store.setBlocklist(b);
258
+ return send(200, {});
259
+ }
260
+
229
261
  // The counts these back are rendered from the same two arrays (see
230
262
  // `account`), so a client that shows a number here can always open it.
231
263
  const mAccList = /^\/api\/v1\/accounts\/([a-f0-9]+)\/(following|followers)$/.exec(pathname);
@@ -259,9 +291,17 @@ export async function handle(api, ctx) {
259
291
  if (mAccStatuses) {
260
292
  const actorUrl = api.store.urlFor(mAccStatuses[1]);
261
293
  const all = api.store.getStatuses();
262
- const pinnedOnly = url.searchParams.get('pinned') === 'true';
294
+ const q = url.searchParams;
295
+ const is = (name) => q.get(name) === 'true' || q.get(name) === '1';
296
+ const pinnedOnly = is('pinned');
297
+ const tagged = String(q.get('tagged') || '').replace(/^#/, '').toLowerCase();
298
+ const isBoost = (s) => !!s.via && s.via !== s.actor;
263
299
  const { items, headers } = api.page(
264
- all.filter(s => s.actor === actorUrl && (!pinnedOnly || s.pinned)), url);
300
+ all.filter(s => s.actor === actorUrl && (!pinnedOnly || s.pinned))
301
+ .filter(s => !is('exclude_replies') || !s.inReplyTo)
302
+ .filter(s => !is('exclude_reblogs') || !isBoost(s))
303
+ .filter(s => !is('only_media') || (s.attachments || []).length > 0)
304
+ .filter(s => !tagged || (s.tags || []).some(t => String(t.name).toLowerCase() === tagged)), url);
265
305
  return send(200, items.map(s => api.statusOrBoost(s, { all })), headers);
266
306
  }
267
307