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,309 +0,0 @@
1
- // accounts.mjs — the account endpoints: the owner's credentials and profile
2
- // editor, follow requests, markers, relationships, search and lookup, block
3
- // and mute, follow and unfollow, an account and its lists and statuses, and
4
- // the web-push subscription a client login holds.
5
-
6
- import crypto from 'node:crypto';
7
- import * as podMedia from '../../pod/media.mjs';
8
- import * as social from '../../core/social.mjs';
9
- import { readBody, readMultipart } from './body.mjs';
10
-
11
- export async function handle(api, ctx) {
12
- const { req, res, pathname, url, send } = ctx; // eslint-disable-line no-unused-vars
13
-
14
- if (pathname === '/api/v1/accounts/verify_credentials') {
15
- const cfg0 = api.store.getConfig() || {};
16
- return send(200, {
17
- ...api.selfAccount(),
18
- // `source` is what the editor fills its inputs from: the raw text it
19
- // will send back, not the HTML the profile renders.
20
- source: {
21
- privacy: 'public', sensitive: false, language: 'en',
22
- note: cfg0.summary || '',
23
- fields: (cfg0.fields || []).map(f => ({ name: f.name, value: f.value })),
24
- },
25
- });
26
- }
27
-
28
- // The profile editor. Everything it can send is carried: the name and bio,
29
- // both pictures, and the extra fields. An avatar or header arrives as file
30
- // bytes, so it goes to the pod's media container first and the actor gets
31
- // the URL — the same path a posted attachment takes.
32
- if (pathname === '/api/v1/accounts/update_credentials') {
33
- if (req.method !== 'PATCH' && req.method !== 'POST') return send(405, { error: 'PATCH expected' });
34
- const ct = String(req.headers['content-type'] || '');
35
- // readBody already covers JSON and urlencoded; only the file case differs.
36
- let form = {}, files = {};
37
- if (ct.includes('multipart/form-data')) ({ fields: form, files } = await readMultipart(req));
38
- else form = await readBody(req);
39
-
40
- const cfg = { ...api.store.getConfig() };
41
- const putImage = async (f) => {
42
- const ext = (f.filename || '').includes('.')
43
- ? f.filename.split('.').pop().replace(/[^\w]/g, '') : 'bin';
44
- const slug = new Date().toISOString().slice(0, 10) + '-' + crypto.randomBytes(4).toString('hex') + '.' + ext;
45
- const url = api.urls.media + slug;
46
- await api.agent.publisher.ensureMediaContainer();
47
- await podMedia.write(api.agent.remote, url, f.data, f.contentType);
48
- return url;
49
- };
50
-
51
- if ('display_name' in form) cfg.name = String(form.display_name).trim() || cfg.handle;
52
- if ('note' in form) cfg.summary = String(form.note) || undefined;
53
- if ('locked' in form) cfg.approveJoins = form.locked === 'true' || form.locked === true;
54
- if (files.avatar?.data?.length) cfg.icon = await putImage(files.avatar);
55
- if (files.header?.data?.length) cfg.image = await putImage(files.header);
56
-
57
- // fields_attributes arrives as fields_attributes[0][name] etc. A row with
58
- // no name is how the editor says "delete this one", so it is dropped.
59
- const rows = [];
60
- for (const [k, v] of Object.entries(form)) {
61
- const m = /^fields_attributes\[(\d+)\]\[(name|value)\]$/.exec(k);
62
- if (!m) continue;
63
- (rows[Number(m[1])] ||= {})[m[2]] = String(v);
64
- }
65
- if (rows.length) cfg.fields = rows.filter(r => r && r.name?.trim())
66
- .map(r => ({ name: r.name.trim(), value: (r.value || '').trim() }));
67
-
68
- api.store.setConfig(cfg);
69
- Object.assign(api.agent.publisher.config, {
70
- name: cfg.name, summary: cfg.summary, icon: cfg.icon, image: cfg.image,
71
- fields: cfg.fields, approveJoins: !!cfg.approveJoins,
72
- });
73
- await api.store.flush();
74
- // publishProfile says whether the world can actually read the actor it
75
- // just wrote. Discarding that reported success for a save that left the
76
- // account undiscoverable — the one outcome the caller needed to hear.
77
- const published = await api.agent.publisher.publishProfile();
78
- const unreachable = published?.unreachable;
79
- if (unreachable?.length) {
80
- api.log(`profile saved but NOT publicly readable: ${unreachable.join(', ')}`);
81
- }
82
- api.log(`profile updated from a client: ${Object.keys(form).join(', ') || '(files only)'}`);
83
- return send(200, api.selfAccount());
84
- }
85
-
86
- // Follow requests. The queue, and the two answers to it, have existed since
87
- // groups did — `agent.store.getRequests()`, `admitRequest`, `refuseRequest`,
88
- // all driven from the record page — but the facade stubbed the list to `[]`
89
- // and offered no authorize/reject. So a locked account could see and answer
90
- // its requests in FediPod's own page and in NO Mastodon client: Phanpy,
91
- // Tuba and Whalebird all showed nothing waiting.
92
- if (pathname === '/api/v1/follow_requests' && req.method === 'GET') {
93
- const limit = Math.min(Number(url.searchParams.get('limit')) || 40, 80);
94
- return send(200, api.store.getRequests().slice(0, limit)
95
- .map((r) => api.account(r.actor)));
96
- }
97
- const mReq = /^\/api\/v1\/follow_requests\/([a-f0-9]+)\/(authorize|reject)$/.exec(pathname);
98
- if (mReq && req.method === 'POST') {
99
- // The client addresses an account by the id it was given for it, which is
100
- // this store's own hash of the actor URL — the same one every other
101
- // account route here uses.
102
- const actorUrl = api.store.urlFor(mReq[1]);
103
- if (!actorUrl) return send(404, { error: 'Record not found' });
104
- if (!api.store.getRequests().some((r) => r.actor === actorUrl)) {
105
- return send(404, { error: 'Record not found' });
106
- }
107
- try {
108
- if (mReq[2] === 'authorize') await social.admitRequest(api.agent, actorUrl);
109
- else await social.refuseRequest(api.agent, actorUrl);
110
- } catch (e) { return send(422, { error: e.message }); }
111
- await api.store.flush();
112
- return send(200, api.relationship(actorUrl));
113
- }
114
-
115
- if (pathname === '/api/v1/markers') {
116
- if (req.method === 'POST') {
117
- const body = await readBody(req);
118
- const markers = api.store.read('masto-markers.json', {});
119
- for (const [k, v] of Object.entries(body)) {
120
- const lastId = v?.last_read_id || v;
121
- if (typeof lastId === 'string') {
122
- markers[k] = { last_read_id: lastId, version: (markers[k]?.version || 0) + 1, updated_at: new Date().toISOString() };
123
- }
124
- }
125
- api.store.write('masto-markers.json', markers);
126
- return send(200, markers);
127
- }
128
- return send(200, api.store.read('masto-markers.json', {}));
129
- }
130
-
131
- if (pathname === '/api/v1/accounts/relationships') {
132
- const ids = [...url.searchParams.getAll('id[]'), ...url.searchParams.getAll('id')];
133
- const rels = ids.map(id => api.store.urlFor(id)).filter(Boolean).map(u => api.relationship(u));
134
- return send(200, rels);
135
- }
136
-
137
- if (pathname === '/api/v1/accounts/search') {
138
- return send(200, await api.accountSearch(url.searchParams.get('q')));
139
- }
140
-
141
- if (pathname === '/api/v1/accounts/lookup') {
142
- const acct = String(url.searchParams.get('acct') || '').replace(/^@/, '');
143
- const cfg = api.store.getConfig();
144
- if (acct === cfg?.handle || acct === `${cfg?.handle}@${api.host}`) {
145
- return send(200, api.selfAccount());
146
- }
147
- // By host with its port, as the account is shown; and by hostname alone,
148
- // for a client that drops the port from an address it was given.
149
- const hit = Object.entries(api.store.getActors()).find(([u, a]) => {
150
- try {
151
- const at = new URL(u);
152
- return `${a.preferredUsername}@${at.host}` === acct || `${a.preferredUsername}@${at.hostname}` === acct;
153
- } catch { return false; }
154
- });
155
- return hit ? send(200, api.account(hit[0])) : send(404, { error: 'Record not found' });
156
- }
157
-
158
- // Block and mute, from where the trouble is seen. A block also unfollows —
159
- // intake refuses a blocked author already — and a mute is view-only: their
160
- // posts stay out of the timelines, nothing federates.
161
- const mRel = /^\/api\/v1\/accounts\/([a-f0-9]+)\/(block|unblock|mute|unmute)$/.exec(pathname);
162
- if (mRel && req.method === 'POST') {
163
- const actorUrl = api.store.urlFor(mRel[1]);
164
- if (!actorUrl) return send(404, { error: 'Record not found' });
165
- if (mRel[2] === 'block' || mRel[2] === 'unblock') {
166
- if (mRel[2] === 'block') await social.blockActor(api.agent, actorUrl);
167
- else await social.unblockActor(api.agent, actorUrl);
168
- } else {
169
- const m = api.store.getMuted();
170
- if (mRel[2] === 'mute' && !m.actors.includes(actorUrl)) m.actors.push(actorUrl);
171
- if (mRel[2] === 'unmute') m.actors = m.actors.filter(a => a !== actorUrl);
172
- api.store.setMuted(m);
173
- }
174
- return send(200, api.relationship(actorUrl));
175
- }
176
-
177
- const mFollow = /^\/api\/v1\/accounts\/([a-f0-9]+)\/(follow|unfollow)$/.exec(pathname);
178
- if (mFollow && req.method === 'POST') {
179
- const actorUrl = api.store.urlFor(mFollow[1]);
180
- if (!actorUrl) return send(404, { error: 'Record not found' });
181
- if (mFollow[2] === 'follow') await social.followActor(api.agent, actorUrl);
182
- else await social.unfollowActor(api.agent, actorUrl).catch(() => {}); // already-gone is fine
183
- return send(200, api.relationship(actorUrl));
184
- }
185
-
186
- const mAccount = /^\/api\/v1\/accounts\/([a-f0-9]+)$/.exec(pathname);
187
- if (mAccount && req.method === 'GET') {
188
- const actorUrl = api.store.urlFor(mAccount[1]);
189
- return actorUrl ? send(200, api.account(actorUrl)) : send(404, { error: 'Record not found' });
190
- }
191
-
192
- // Web push: one subscription per client login. The agent pushes payloads
193
- // to the browser's push service itself, so a closed client still hears.
194
- if (pathname === '/api/v1/push/subscription') {
195
- const token = (/^Bearer (.+)$/.exec(req.headers.authorization || '') || [])[1];
196
- if (!token) return send(401, { error: 'The access token is invalid' });
197
- // A client that ignores the missing `vapid` and subscribes anyway must
198
- // not be told it worked — a stored subscription nothing ever pushes to is
199
- // the same silent nothing the toggle was.
200
- if (!api.webPush) return send(422, { error: 'this instance does not send web push' });
201
- if (req.method === 'GET') {
202
- const sub = api.push.get(token);
203
- return sub ? send(200, api.push.json(token, sub)) : send(404, { error: 'Record not found' });
204
- }
205
- if (req.method === 'POST') {
206
- const body = await readBody(req);
207
- const sub = api.push.set(token, {
208
- endpoint: body.subscription?.endpoint,
209
- keys: body.subscription?.keys,
210
- alerts: body.data?.alerts,
211
- });
212
- if (!sub) return send(422, { error: 'a https endpoint and p256dh/auth keys are required' });
213
- return send(200, api.push.json(token, sub));
214
- }
215
- if (req.method === 'PUT') {
216
- const body = await readBody(req);
217
- const sub = api.push.setAlerts(token, body.data?.alerts);
218
- return sub ? send(200, api.push.json(token, sub)) : send(404, { error: 'Record not found' });
219
- }
220
- if (req.method === 'DELETE') {
221
- api.push.drop(token);
222
- return send(200, {});
223
- }
224
- }
225
-
226
- if (/^\/api\/v1\/accounts\/[a-f0-9]+\/featured_tags$/.test(pathname)) return send(200, []);
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
-
261
- // The counts these back are rendered from the same two arrays (see
262
- // `account`), so a client that shows a number here can always open it.
263
- const mAccList = /^\/api\/v1\/accounts\/([a-f0-9]+)\/(following|followers)$/.exec(pathname);
264
- if (mAccList && req.method === 'GET') {
265
- const actorUrl = api.store.urlFor(mAccList[1]);
266
- if (!actorUrl) return send(404, { error: 'Record not found' });
267
- // Only our own lists are known. A remote actor's collections live on its
268
- // own server, and opening a profile is not worth a fetch of a stranger's
269
- // pod — an empty list, not an error, is what the API can honestly say.
270
- const mine = actorUrl === api.urls?.actor;
271
- const c = api.store.getContacts();
272
- // Reversed: the contact arrays append, and page()'s cursors read
273
- // newest-first — fed as stored, since_id answered with its complement.
274
- const recs = (!mine ? []
275
- : mAccList[2] === 'followers' ? c.followers
276
- : c.following.filter(f => f.accepted)) // pending is not following
277
- .slice().reverse();
278
- const { items, headers } = api.page(recs, url,
279
- { limit: 40, max: 80, idOf: (r) => api.store.idFor(r.actor) });
280
- return send(200, items.map(r => api.account(r.actor)), headers);
281
- }
282
-
283
- // Who among the people I follow also follows THEM — a graph we do not hold,
284
- // and the shape is an entry per requested account, not a bare list.
285
- if (pathname === '/api/v1/accounts/familiar_followers') {
286
- const ids = [...url.searchParams.getAll('id[]'), ...url.searchParams.getAll('id')];
287
- return send(200, ids.map(id => ({ id, accounts: [] })));
288
- }
289
-
290
- const mAccStatuses = /^\/api\/v1\/accounts\/([a-f0-9]+)\/statuses$/.exec(pathname);
291
- if (mAccStatuses) {
292
- const actorUrl = api.store.urlFor(mAccStatuses[1]);
293
- const all = api.store.getStatuses();
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;
299
- const { items, headers } = api.page(
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);
305
- return send(200, items.map(s => api.statusOrBoost(s, { all })), headers);
306
- }
307
-
308
- return false;
309
- }
@@ -1,156 +0,0 @@
1
- // body.mjs — reading what a client sent: JSON, form-encoded or multipart
2
- // bodies, the poll a compose request carries, and the text of a post back out
3
- // of its HTML.
4
-
5
-
6
-
7
- /**
8
- * A poll out of a compose request, or null when there is none. A JSON client
9
- * sends a `poll` object; a form-encoded one spells the same thing out in
10
- * Rails's bracket notation, which is the shape the option list arrives in.
11
- */
12
- export function pollParams(body) {
13
- const nested = body?.poll && typeof body.poll === 'object' ? body.poll : null;
14
- const options = [].concat(nested?.options ?? body?.['poll[options][]'] ?? [])
15
- .map(o => String(o ?? '').trim()).filter(Boolean);
16
- const rawExpiry = nested?.expires_in ?? body?.['poll[expires_in]'];
17
- const rawMultiple = nested?.multiple ?? body?.['poll[multiple]'];
18
- if (!options.length && rawExpiry === undefined) return null;
19
- return {
20
- options,
21
- expiresIn: rawExpiry === undefined || rawExpiry === null || rawExpiry === ''
22
- ? null : Number(rawExpiry),
23
- multiple: rawMultiple === true || rawMultiple === 'true' || rawMultiple === '1',
24
- };
25
- }
26
-
27
- // Accepts JSON or form-encoded bodies (OAuth posts are often form-encoded).
28
- // The source of a post that predates raw-text storage: its HTML back to
29
- // typed text, near enough to edit.
30
- export function htmlToText(html) {
31
- return String(html)
32
- .replace(/<br\s*\/?>/gi, '\n')
33
- .replace(/<\/p>\s*<p[^>]*>/gi, '\n\n')
34
- .replace(/<[^>]+>/g, '')
35
- .replace(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>')
36
- .replace(/&quot;/g, '"').replace(/&#39;/g, "'")
37
- .trim();
38
- }
39
-
40
- export function readBody(req) {
41
- return new Promise((resolve, reject) => {
42
- let data = '';
43
- req.on('data', c => {
44
- data += c;
45
- // Destroying the socket without settling left the awaiting handler
46
- // pending for the life of the process and the client waiting on a
47
- // response that would never come.
48
- if (data.length > 1e6) { req.destroy(); reject(new Error('request body too large')); }
49
- });
50
- req.on('end', () => {
51
- const ct = String(req.headers['content-type'] || '');
52
- try {
53
- if (ct.includes('application/json')) return resolve(data ? JSON.parse(data) : {});
54
- // A multipart body is not a query string: read as one it becomes a
55
- // single nonsense key, so every field a client sent that way arrived
56
- // undefined. Sengi registers its OAuth client with a FormData, which
57
- // is multipart, so /api/v1/apps saw no redirect_uris and handed back
58
- // the out-of-band URN, leaving the client nowhere to come back to.
59
- if (ct.includes('multipart/form-data')) return resolve(textFields(data, ct));
60
- // A form-encoded list is the same key repeated, spelled with a
61
- // trailing `[]`. Reading it as a plain object kept only the last one,
62
- // so a client sending its poll or its media that way lost all but the
63
- // final value. Only the `[]` keys become lists: everything else keeps
64
- // the single value the rest of this file reads.
65
- const form = new URLSearchParams(data);
66
- const out = {};
67
- for (const key of new Set(form.keys())) {
68
- out[key] = key.endsWith('[]') ? form.getAll(key) : form.get(key);
69
- }
70
- resolve(out);
71
- } catch (e) { reject(e); }
72
- });
73
- req.on('error', reject);
74
- });
75
- }
76
-
77
- /**
78
- * The text fields of a multipart body that arrived as text. readBody
79
- * accumulates the body as a string, so this reads the fields out of that
80
- * string rather than out of bytes — which is all a client sending a plain
81
- * form this way has in it. A body with a FILE in it is not this reader's:
82
- * a text round trip corrupts binary, and the upload endpoints take their
83
- * bytes from readMultipart below.
84
- */
85
- function textFields(data, contentType) {
86
- const m = /boundary=(?:"([^"]+)"|([^;]+))/.exec(contentType);
87
- if (!m) return {};
88
- const out = {};
89
- for (const part of data.split('--' + (m[1] || m[2]).trim())) {
90
- const sep = part.indexOf('\r\n\r\n');
91
- if (sep < 0) continue;
92
- const head = part.slice(0, sep);
93
- const name = /name="([^"]*)"/.exec(head)?.[1];
94
- // A file part is skipped rather than kept as mangled text.
95
- if (!name || /filename="/.test(head)) continue;
96
- const value = part.slice(sep + 4).replace(/\r\n$/, '');
97
- if (name.endsWith('[]')) (out[name] ||= []).push(value);
98
- else out[name] = value;
99
- }
100
- return out;
101
- }
102
-
103
- // Minimal multipart/form-data reader for media uploads: string fields plus
104
- // at most one file part (Mastodon's media endpoints send exactly one).
105
- export function readMultipart(req, limit = 12e6) {
106
- return new Promise((resolve, reject) => {
107
- const chunks = [];
108
- let n = 0;
109
- req.on('data', c => {
110
- n += c.length;
111
- if (n > limit) { reject(new Error('upload too large')); req.destroy(); return; }
112
- chunks.push(c);
113
- });
114
- req.on('error', reject);
115
- req.on('end', () => {
116
- try {
117
- const m = /boundary=(?:"([^"]+)"|([^;]+))/.exec(String(req.headers['content-type'] || ''));
118
- if (!m) return resolve({ fields: {}, file: null });
119
- const buf = Buffer.concat(chunks);
120
- const boundary = Buffer.from('--' + (m[1] || m[2]).trim());
121
- const fields = {};
122
- // `file` is the last one seen, which is what the single-file media
123
- // upload wants. `files` keys them by field name, because the profile
124
- // editor submits an avatar and a header in one request.
125
- const files = {};
126
- let file = null;
127
- let i = buf.indexOf(boundary);
128
- while (i >= 0) {
129
- const start = i + boundary.length;
130
- if (buf.slice(start, start + 2).toString() === '--') break;
131
- const next = buf.indexOf(boundary, start);
132
- if (next < 0) break;
133
- const part = buf.slice(start + 2, next - 2); // strip the CRLFs framing the part
134
- const sep = part.indexOf('\r\n\r\n');
135
- if (sep >= 0) {
136
- const head = part.slice(0, sep).toString();
137
- const body = part.slice(sep + 4);
138
- const name = /name="([^"]*)"/.exec(head)?.[1];
139
- const filename = /filename="([^"]*)"/.exec(head)?.[1];
140
- if (filename !== undefined) {
141
- file = {
142
- filename,
143
- contentType: /content-type:\s*([^\r\n]+)/i.exec(head)?.[1]?.trim() || 'application/octet-stream',
144
- data: body,
145
- };
146
- if (name) files[name] = file;
147
- } else if (name) fields[name] = body.toString();
148
- }
149
- i = next;
150
- }
151
- resolve({ fields, file, files });
152
- } catch (e) { reject(e); }
153
- });
154
- });
155
- }
156
-
@@ -1,206 +0,0 @@
1
- // mastoapi.mjs — Mastodon client-API facade over ap-agent (M1: read + post).
2
- // Modeled on snac2's approach: real implementations for the endpoints
3
- // clients actually exercise, empty-collection stubs for the rest. Reached
4
- // through the router (/api/*, /oauth/* → this port), so requests carry the
5
- // gate; OAuth here is theater for a single already-trusted local user.
6
- //
7
- // Surface: oauth trio · instance v1/v2 · verify_credentials · timelines/home
8
- // (M1) · notifications, relationships, lookup, follow/unfollow, thread
9
- // context, /v2/search (M2) · favourite/reblog, media upload, markers,
10
- // DELETE status (M3) · stub farm. Unknown /api/* GETs 404 and are LOGGED —
11
- // that log is the running punch list.
12
- //
13
- // This file is the class and the dispatcher. The endpoints are in the modules
14
- // beside it, one per area — oauth.mjs, instance.mjs, accounts.mjs,
15
- // timelines.mjs, statuses.mjs, media.mjs — each exporting handle(api, ctx),
16
- // which answers and returns true or returns false; handle() below walks them
17
- // in order with the bearer gate between the public ones and the rest. The
18
- // JSON shapes are render.mjs and the request readers body.mjs; every helper
19
- // the areas reach through `api.` is a one-line delegation here.
20
-
21
- import { Push } from '../webpush.mjs';
22
- import * as oauth from './oauth.mjs';
23
- import * as render from './render.mjs';
24
- import * as instance from './instance.mjs';
25
- import * as accounts from './accounts.mjs';
26
- import * as timelines from './timelines.mjs';
27
- import * as statuses from './statuses.mjs';
28
- import * as media from './media.mjs';
29
- export { hashPassword } from './oauth.mjs';
30
- export { pollParams } from './body.mjs';
31
- export { attachmentType, extensionFor } from './media.mjs';
32
-
33
- export class MastoApi {
34
- constructor({ agent, log = console.log, allowed = null, scheme = null, embedded = false, throughDoor = null,
35
- mount = '', streaming = true, webPush = true, scheduling = true }) {
36
- this.agent = agent;
37
- // The path this identity's surface answers under, when it shares its origin
38
- // with others (a suffix pod, e.g. `/aisha`). Empty for a host-root or
39
- // subdomain pod. Folded into the self-URLs the client is handed —
40
- // pagination links, the OAuth issuer — so they name the address the client
41
- // actually reached.
42
- this.mount = mount;
43
- // A server-hosted identity has no CLI of its own, so the advice this gives
44
- // when it refuses has to name the route that identity really has.
45
- this.embedded = embedded;
46
- this.log = log;
47
- this.allowed = allowed; // authorities a redirect_uri may name
48
- // Whether a request already passed the owner's door (its key or cookie):
49
- // an owner who opened the management page is the owner at the sign-in too.
50
- this.throughDoor = throughDoor;
51
- // The scheme this identity is reached on, when the socket cannot say —
52
- // a server behind a TLS proxy terminates cleartext and still is https.
53
- this.scheme = scheme;
54
- // Whether this deployment can serve the Mastodon streaming WebSocket. A
55
- // Node agent can; the in-browser service-worker facade cannot (it is
56
- // fetch-only), so it advertises NO streaming URL. Otherwise the client
57
- // opens wss://localhost/... (the worker has no request host to name), which
58
- // not only fails but, from a public origin, trips Chrome's private-network
59
- // permission prompt ("access other apps and services on this device").
60
- // With no streaming URL the client falls back to polling, which is served.
61
- this.streaming = streaming;
62
- // The same idea for two more capabilities the browser build does not have.
63
- // A client decides what to offer from what the instance document says, so
64
- // the honest thing is to say nothing rather than advertise and no-op.
65
- //
66
- // `webPush`: the browser build's web-push is a shim that mints a random
67
- // placeholder VAPID key and whose sendNotification does nothing. Advertised,
68
- // the client shows a notifications toggle that looks on and never fires.
69
- // Omit `vapid` and it hides the toggle instead.
70
- this.webPush = webPush;
71
- // `scheduling`: a scheduled post is only a stored row until something
72
- // publishes it when its time comes, and the only such tick is the Node
73
- // agent's (run-agent.mjs). Where nothing ticks, accepting one is silent
74
- // loss — the client says "scheduled" and the post never appears. Refusing
75
- // it is worse UX and better behaviour.
76
- this.scheduling = scheduling;
77
- this.authzAttempts = []; // password-attempt timestamps
78
- }
79
-
80
- get store() { return this.agent.store; }
81
- get urls() { return this.agent.publisher?.urls; }
82
- get push() {
83
- this._push ||= new Push({
84
- store: this.store,
85
- subject: () => this.urls?.actor || 'https://localhost/',
86
- log: this.log,
87
- });
88
- return this._push;
89
- }
90
- // The host in the owner's own address: the gateway's when the identity is
91
- // fronted (its documents still live on the pod, but its name does not).
92
- get host() {
93
- if (!this.urls) return 'unconfigured.invalid';
94
- return new URL(this.urls.publicHome || this.urls.base).host;
95
- }
96
-
97
- // Where the live feed is, as the CLIENT must address it: this agent's own
98
- // origin, taken from the request, not the pod's host. An instance document
99
- // that leaves it empty is not merely unhelpful — clients read it without a
100
- // guard and fall over, and every one of them loses live updates.
101
- streamingUrl(req) {
102
- if (!this.streaming) return null; // fetch-only facade: no WebSocket, no wss://localhost prompt
103
- const host = req?.headers?.host || `localhost:${this.port || ''}`;
104
- const secure = this.scheme
105
- ? this.scheme.startsWith('https')
106
- : req?.headers?.['x-forwarded-proto'] === 'https' || !!req?.socket?.encrypted;
107
- return `${secure ? 'wss' : 'ws'}://${host}/api/v1/streaming`;
108
- }
109
-
110
- static scopeFor(...a) { return oauth.scopeFor(...a); }
111
- static scopeAllows(...a) { return oauth.scopeAllows(...a); }
112
- static redirectMatches(...a) { return oauth.redirectMatches(...a); }
113
- static provesCode(...a) { return oauth.provesCode(...a); }
114
-
115
- // ---- request handling; returns true when handled ----
116
- async handle(req, res, pathname, url) {
117
- const send = (status, obj, headers = {}) => {
118
- const body = JSON.stringify(obj);
119
- res.writeHead(status, { 'content-type': 'application/json', ...headers });
120
- res.end(body);
121
- return true;
122
- };
123
- // The address the client reached, so every self-URL handed back — the
124
- // pagination links above all — names it rather than the pod's host.
125
- const scheme = String(this.scheme
126
- || (req.headers?.['x-forwarded-proto'] === 'https' || req.socket?.encrypted ? 'https' : 'http')).replace(/:.*$/, '');
127
- let self = url;
128
- try {
129
- self = new URL(`${scheme}://${req.headers?.host || 'localhost'}${this.mount || ''}${pathname}${url.search || ''}`);
130
- } catch { /* a Host nothing can parse: the links keep the placeholder origin */ }
131
- const ctx = { req, res, pathname, url: self, send };
132
-
133
- if (await oauth.handle(this, ctx)) return true;
134
- if (!pathname.startsWith('/api/')) return false;
135
- if (await instance.handle(this, ctx)) return true;
136
-
137
- // --- everything below needs a bearer token + a configured agent ---
138
- const bearer = this.tokenOf(req);
139
- if (!bearer) return send(401, { error: 'The access token is invalid' });
140
- // One gate for every client route below, rather than a check in each of
141
- // the fifty-odd branches — which is how the scope came to be recorded and
142
- // never consulted in the first place.
143
- const need = MastoApi.scopeFor(req.method, pathname);
144
- if (!MastoApi.scopeAllows(bearer.scope, need)) {
145
- this.log(`refused ${req.method} ${pathname}: token has "${bearer.scope}", needs "${need}"`);
146
- return send(403, { error: `This action is outside the authorized scopes (needs ${need})` });
147
- }
148
- if (!this.agent.configured()) return send(503, { error: 'agent not configured' });
149
- // A viewer-mode agent (another agent holds the drain lease) may not act —
150
- // but a user acting HERE outranks the idle active agent elsewhere, so a
151
- // write attempt claims the lease and proceeds. Only a failed claim 503s.
152
- if (this.agent.viewer && req.method !== 'GET' && req.method !== 'HEAD') {
153
- const took = await this.agent.requestTakeover?.();
154
- if (!took) return send(503, { error: 'another agent is active for this pod — takeover failed, try again' });
155
- }
156
-
157
- for (const area of [accounts, timelines, statuses, media]) {
158
- if (await area.handle(this, ctx)) return true;
159
- }
160
-
161
- this.log(`mastoapi: unhandled ${req.method} ${pathname} — punch list`);
162
- return send(404, { error: `Unimplemented: ${req.method} ${pathname}` });
163
- }
164
-
165
- // oauth.mjs
166
- tokenRecords(...a) { return oauth.tokenRecords(this, ...a); }
167
- tokens(...a) { return oauth.tokens(this, ...a); }
168
- mintToken(...a) { return oauth.mintToken(this, ...a); }
169
- apps(...a) { return oauth.apps(this, ...a); }
170
- authorizationServerMetadata(...a) { return oauth.authorizationServerMetadata(this, ...a); }
171
- findApp(...a) { return oauth.findApp(this, ...a); }
172
- resolveClientDocument(...a) { return oauth.resolveClientDocument(this, ...a); }
173
- registerApp(...a) { return oauth.registerApp(this, ...a); }
174
- mintCode(...a) { return oauth.mintCode(this, ...a); }
175
- consumeCode(...a) { return oauth.consumeCode(this, ...a); }
176
- tokenOf(...a) { return oauth.tokenOf(this, ...a); }
177
- authed(...a) { return oauth.authed(this, ...a); }
178
- redirectAllowed(...a) { return oauth.redirectAllowed(this, ...a); }
179
- rateLimited(...a) { return oauth.rateLimited(this, ...a); }
180
-
181
- // render.mjs
182
- selfAccount(...a) { return render.selfAccount(this, ...a); }
183
- account(...a) { return render.account(this, ...a); }
184
- page(...a) { return render.page(this, ...a); }
185
- bskyReply(...a) { return render.bskyReply(this, ...a); }
186
- acctAction(...a) { return render.acctAction(this, ...a); }
187
- acctReply(...a) { return render.acctReply(this, ...a); }
188
- status(...a) { return render.status(this, ...a); }
189
- quoteApproval(...a) { return render.quoteApproval(this, ...a); }
190
- filtersFor(...a) { return render.filtersFor(this, ...a); }
191
- lookup(...a) { return render.lookup(this, ...a); }
192
- statusOrBoost(...a) { return render.statusOrBoost(this, ...a); }
193
- pushNotify(...a) { return render.pushNotify(this, ...a); }
194
- scheduledJson(...a) { return render.scheduledJson(this, ...a); }
195
- pollJson(...a) { return render.pollJson(this, ...a); }
196
- mediaJson(...a) { return render.mediaJson(this, ...a); }
197
- relationship(...a) { return render.relationship(this, ...a); }
198
- notificationType(...a) { return render.notificationType(this, ...a); }
199
- notification(...a) { return render.notification(this, ...a); }
200
- accountSearch(...a) { return render.accountSearch(this, ...a); }
201
-
202
- // instance.mjs
203
- instanceTitle(...a) { return instance.instanceTitle(this, ...a); }
204
- tagObject(...a) { return instance.tagObject(this, ...a); }
205
- instanceBlurb(...a) { return instance.instanceBlurb(this, ...a); }
206
- }