fedipod-server 0.29.0 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (440) hide show
  1. package/README.md +11 -6
  2. package/dist/door.d.ts +4 -0
  3. package/dist/door.js +31 -0
  4. package/dist/fedipod.d.ts +16 -0
  5. package/dist/fedipod.js +13 -0
  6. package/dist/front.d.ts +3 -0
  7. package/dist/front.js +94 -0
  8. package/dist/handler.d.ts +36 -101
  9. package/dist/handler.js +85 -645
  10. package/dist/handler.jsonld +2 -30
  11. package/dist/identities.d.ts +62 -0
  12. package/dist/identities.js +236 -0
  13. package/dist/mounts.d.ts +43 -0
  14. package/dist/mounts.js +123 -0
  15. package/dist/opt-in.d.ts +46 -0
  16. package/dist/opt-in.js +176 -0
  17. package/package.json +10 -18
  18. package/lib/client/c2s.mjs +0 -636
  19. package/lib/client/localapi.mjs +0 -87
  20. package/lib/client/masto/accounts.mjs +0 -310
  21. package/lib/client/masto/body.mjs +0 -156
  22. package/lib/client/masto/index.mjs +0 -206
  23. package/lib/client/masto/instance.mjs +0 -112
  24. package/lib/client/masto/media.mjs +0 -79
  25. package/lib/client/masto/oauth.mjs +0 -656
  26. package/lib/client/masto/render.mjs +0 -518
  27. package/lib/client/masto/statuses.mjs +0 -372
  28. package/lib/client/masto/timelines.mjs +0 -438
  29. package/lib/client/oidc-auth.mjs +0 -62
  30. package/lib/client/streaming.mjs +0 -154
  31. package/lib/client/webpush.mjs +0 -98
  32. package/lib/connections/acctfeed.mjs +0 -257
  33. package/lib/connections/atproto.mjs +0 -309
  34. package/lib/connections/bskyfeed.mjs +0 -243
  35. package/lib/connections/bskygroup.mjs +0 -162
  36. package/lib/connections/fediacct.mjs +0 -253
  37. package/lib/connections/import.mjs +0 -357
  38. package/lib/connections/tagfeed.mjs +0 -212
  39. package/lib/connections/vault.mjs +0 -114
  40. package/lib/core/as2.mjs +0 -206
  41. package/lib/core/contexts/activitystreams.json +0 -379
  42. package/lib/core/contexts/anno.json +0 -126
  43. package/lib/core/contexts/did-v1.json +0 -57
  44. package/lib/core/contexts/fep-5711.json +0 -36
  45. package/lib/core/contexts/gotosocial.json +0 -86
  46. package/lib/core/contexts/identity-v1.json +0 -152
  47. package/lib/core/contexts/index.mjs +0 -63
  48. package/lib/core/contexts/join-lemmy.json +0 -33
  49. package/lib/core/contexts/joinmastodon.json +0 -28
  50. package/lib/core/contexts/litepub-0.1.json +0 -60
  51. package/lib/core/contexts/map.json +0 -19
  52. package/lib/core/contexts/miscellany.json +0 -19
  53. package/lib/core/contexts/quotes.json +0 -18
  54. package/lib/core/contexts/schemaorg.json +0 -8845
  55. package/lib/core/contexts/security-data-integrity-v1.json +0 -78
  56. package/lib/core/contexts/security-data-integrity-v2.json +0 -81
  57. package/lib/core/contexts/security-multikey-v1.json +0 -35
  58. package/lib/core/contexts/security-v1.json +0 -74
  59. package/lib/core/contexts/webfinger.json +0 -10
  60. package/lib/core/deliver.mjs +0 -387
  61. package/lib/core/graphview.mjs +0 -327
  62. package/lib/core/intake/activities.mjs +0 -649
  63. package/lib/core/intake/activity.mjs +0 -318
  64. package/lib/core/intake/channel.mjs +0 -144
  65. package/lib/core/intake/group.mjs +0 -258
  66. package/lib/core/intake/index.mjs +0 -732
  67. package/lib/core/intake/notes.mjs +0 -367
  68. package/lib/core/intake/verify.mjs +0 -142
  69. package/lib/core/keys.mjs +0 -153
  70. package/lib/core/lease.mjs +0 -206
  71. package/lib/core/place.mjs +0 -82
  72. package/lib/core/polls.mjs +0 -105
  73. package/lib/core/profile-page.mjs +0 -104
  74. package/lib/core/proof.mjs +0 -79
  75. package/lib/core/publisher/collections.mjs +0 -272
  76. package/lib/core/publisher/index.mjs +0 -522
  77. package/lib/core/publisher/notes.mjs +0 -519
  78. package/lib/core/publisher/own.mjs +0 -143
  79. package/lib/core/publisher/questions.mjs +0 -241
  80. package/lib/core/publisher/restore.mjs +0 -224
  81. package/lib/core/shapes/activitystreams.ttl +0 -157
  82. package/lib/core/shapes/index.mjs +0 -132
  83. package/lib/core/shapes/shapes-text.mjs +0 -13
  84. package/lib/core/social.mjs +0 -561
  85. package/lib/core/storage.mjs +0 -176
  86. package/lib/core/store.mjs +0 -578
  87. package/lib/core/wire-quotes.mjs +0 -71
  88. package/lib/core/wire.mjs +0 -925
  89. package/lib/device/account.mjs +0 -87
  90. package/lib/device/admin/index.mjs +0 -13
  91. package/lib/device/admin/origins.mjs +0 -35
  92. package/lib/device/admin/routes/connections.mjs +0 -144
  93. package/lib/device/admin/routes/gateway.mjs +0 -199
  94. package/lib/device/admin/routes/lifecycle.mjs +0 -200
  95. package/lib/device/admin/routes/owner.mjs +0 -322
  96. package/lib/device/admin/routes/setup.mjs +0 -428
  97. package/lib/device/admin/routes/social.mjs +0 -188
  98. package/lib/device/admin/server.mjs +0 -95
  99. package/lib/device/admin/static.mjs +0 -247
  100. package/lib/device/admin/surface.mjs +0 -315
  101. package/lib/device/certs.mjs +0 -281
  102. package/lib/device/cli/commands/account.mjs +0 -586
  103. package/lib/device/cli/commands/run.mjs +0 -278
  104. package/lib/device/cli/commands/service.mjs +0 -221
  105. package/lib/device/cli/commands/setup.mjs +0 -489
  106. package/lib/device/cli/commands/state.mjs +0 -561
  107. package/lib/device/cli/context.mjs +0 -288
  108. package/lib/device/export-collections.mjs +0 -161
  109. package/lib/device/gateway-move.mjs +0 -58
  110. package/lib/device/home.mjs +0 -161
  111. package/lib/device/migrate.mjs +0 -115
  112. package/lib/device/ports.mjs +0 -27
  113. package/lib/device/remote.mjs +0 -54
  114. package/lib/device/setup.mjs +0 -496
  115. package/lib/device/update.mjs +0 -96
  116. package/lib/gateway/caches.mjs +0 -36
  117. package/lib/gateway/directory.mjs +0 -90
  118. package/lib/gateway/front-core.mjs +0 -966
  119. package/lib/gateway/gateway-core.mjs +0 -244
  120. package/lib/gateway/headers.mjs +0 -49
  121. package/lib/gateway/httpsig.mjs +0 -159
  122. package/lib/gateway/notices.mjs +0 -84
  123. package/lib/gateway/quiet.mjs +0 -189
  124. package/lib/gateway/relay-extras.mjs +0 -89
  125. package/lib/gateway/token-claims.mjs +0 -16
  126. package/lib/pod/README.md +0 -61
  127. package/lib/pod/actor.mjs +0 -96
  128. package/lib/pod/collection.mjs +0 -77
  129. package/lib/pod/containers.mjs +0 -140
  130. package/lib/pod/discovery.mjs +0 -46
  131. package/lib/pod/featured.mjs +0 -18
  132. package/lib/pod/followers.mjs +0 -24
  133. package/lib/pod/following.mjs +0 -10
  134. package/lib/pod/http.mjs +0 -74
  135. package/lib/pod/inbox.mjs +0 -166
  136. package/lib/pod/links.mjs +0 -35
  137. package/lib/pod/location.mjs +0 -52
  138. package/lib/pod/media.mjs +0 -27
  139. package/lib/pod/notes.mjs +0 -78
  140. package/lib/pod/notifications.mjs +0 -89
  141. package/lib/pod/outbox.mjs +0 -18
  142. package/lib/pod/package.json +0 -19
  143. package/lib/pod/policy.mjs +0 -58
  144. package/lib/pod/private.mjs +0 -19
  145. package/lib/pod/root.mjs +0 -104
  146. package/lib/pod/state.mjs +0 -40
  147. package/lib/pod/transport.mjs +0 -674
  148. package/lib/pod/type-index.mjs +0 -101
  149. package/lib/pod/urls.mjs +0 -91
  150. package/lib/server/embed.mjs +0 -424
  151. package/lib/session/README.md +0 -137
  152. package/lib/session/demo.html +0 -94
  153. package/lib/session/fedi-account.mjs +0 -417
  154. package/lib/session/fedi-login.mjs +0 -123
  155. package/lib/session/oidc-session.mjs +0 -233
  156. package/lib/session/package.json +0 -49
  157. package/lib/shared/guard.mjs +0 -213
  158. package/lib/shared/links.mjs +0 -9
  159. package/lib/shared/safefetch.mjs +0 -235
  160. package/lib/shared/ua.mjs +0 -29
  161. package/phanpy/dist/404.html +0 -40
  162. package/phanpy/dist/_headers +0 -2
  163. package/phanpy/dist/apple-touch-icon.png +0 -0
  164. package/phanpy/dist/assets/ICONS-DIPJeU0C.js +0 -2
  165. package/phanpy/dist/assets/ICONS-DIPJeU0C.js.map +0 -1
  166. package/phanpy/dist/assets/ScheduledAtField-D-Abfo4J.js +0 -183
  167. package/phanpy/dist/assets/ScheduledAtField-D-Abfo4J.js.map +0 -1
  168. package/phanpy/dist/assets/Temml-CCjoacWt.woff2 +0 -0
  169. package/phanpy/dist/assets/__vite-browser-external-C7iut881.js +0 -2
  170. package/phanpy/dist/assets/__vite-browser-external-C7iut881.js.map +0 -1
  171. package/phanpy/dist/assets/boosts-carousel-BiOaNdMT.jpg +0 -0
  172. package/phanpy/dist/assets/catch-up-O5q5C75Z.png +0 -0
  173. package/phanpy/dist/assets/chunk-aKtaBQYM.js +0 -1
  174. package/phanpy/dist/assets/compose-CFEx7wb4.js +0 -2
  175. package/phanpy/dist/assets/compose-CFEx7wb4.js.map +0 -1
  176. package/phanpy/dist/assets/compose-ZJN5xSb4.js +0 -35
  177. package/phanpy/dist/assets/compose-ZJN5xSb4.js.map +0 -1
  178. package/phanpy/dist/assets/debug.module-YFQgwnIr.js +0 -52
  179. package/phanpy/dist/assets/debug.module-YFQgwnIr.js.map +0 -1
  180. package/phanpy/dist/assets/dom-p1YcJ5Rw.js +0 -2
  181. package/phanpy/dist/assets/dom-p1YcJ5Rw.js.map +0 -1
  182. package/phanpy/dist/assets/home-mobile-dark@2x-Dc-J8-SM.png +0 -0
  183. package/phanpy/dist/assets/home-mobile-light@2x-Bd3fod8m.png +0 -0
  184. package/phanpy/dist/assets/home-tablet-dark@2x-BgcNI8le.png +0 -0
  185. package/phanpy/dist/assets/home-tablet-light@2x-BW2RV9T6.png +0 -0
  186. package/phanpy/dist/assets/icons/add-circle-line-WD0mwORD.js +0 -1
  187. package/phanpy/dist/assets/icons/alert-line-CpFUMf9s.js +0 -1
  188. package/phanpy/dist/assets/icons/android-2-line-Bzxnrd3S.js +0 -1
  189. package/phanpy/dist/assets/icons/announcement-line-5nwaJFJm.js +0 -1
  190. package/phanpy/dist/assets/icons/arrow-down-circle-line-a7Id9-Iv.js +0 -1
  191. package/phanpy/dist/assets/icons/arrow-down-line-B8uzXzq6.js +0 -1
  192. package/phanpy/dist/assets/icons/arrow-left-line-CGrGT9C-.js +0 -1
  193. package/phanpy/dist/assets/icons/arrow-right-line-CE-HERfX.js +0 -1
  194. package/phanpy/dist/assets/icons/arrow-to-up-line-CvdQjmcy.js +0 -1
  195. package/phanpy/dist/assets/icons/arrow-up-circle-line-DEparSNF.js +0 -1
  196. package/phanpy/dist/assets/icons/arrow-up-line-C7yBeugc.js +0 -1
  197. package/phanpy/dist/assets/icons/arrows-right-line-BtbXmJGP.js +0 -1
  198. package/phanpy/dist/assets/icons/at-line-CF66Bznk.js +0 -1
  199. package/phanpy/dist/assets/icons/attachment-line-BY8It-l9.js +0 -1
  200. package/phanpy/dist/assets/icons/blockquote-line-BS6pU_hm.js +0 -1
  201. package/phanpy/dist/assets/icons/board-line-C0UWXyA6.js +0 -1
  202. package/phanpy/dist/assets/icons/bookmark-line-1CtX5JLF.js +0 -1
  203. package/phanpy/dist/assets/icons/building-5-line-Dd59MzbO.js +0 -1
  204. package/phanpy/dist/assets/icons/bus-2-line-6_dvA0GG.js +0 -1
  205. package/phanpy/dist/assets/icons/calendar-day-line-Crpl_fmu.js +0 -1
  206. package/phanpy/dist/assets/icons/calendar-month-line-vommts2C.js +0 -1
  207. package/phanpy/dist/assets/icons/calendar-time-add-line-BU_DTlQk.js +0 -1
  208. package/phanpy/dist/assets/icons/camera-line-Cv69MEei.js +0 -1
  209. package/phanpy/dist/assets/icons/celebrate-line-B8DvIoi0.js +0 -1
  210. package/phanpy/dist/assets/icons/chart-bar-line-FK-QOmtF.js +0 -1
  211. package/phanpy/dist/assets/icons/chart-line-line-BGcJAFWw.js +0 -1
  212. package/phanpy/dist/assets/icons/chat-3-line-YFK2-hwz.js +0 -1
  213. package/phanpy/dist/assets/icons/check-circle-line-DsAxFr5-.js +0 -1
  214. package/phanpy/dist/assets/icons/clipboard-line-BBp364gJ.js +0 -1
  215. package/phanpy/dist/assets/icons/close-circle-line-CICdiuGz.js +0 -1
  216. package/phanpy/dist/assets/icons/close-line-DYrFWcjv.js +0 -1
  217. package/phanpy/dist/assets/icons/cloud-line-tiQAbah8.js +0 -1
  218. package/phanpy/dist/assets/icons/code-line-fAl8iiV3.js +0 -1
  219. package/phanpy/dist/assets/icons/comment-2-line-CR3g7WeB.js +0 -1
  220. package/phanpy/dist/assets/icons/copy-2-line-CLW69yF5.js +0 -1
  221. package/phanpy/dist/assets/icons/delete-2-line-B8xmbr4n.js +0 -1
  222. package/phanpy/dist/assets/icons/document-3-line-DBGNhAg2.js +0 -1
  223. package/phanpy/dist/assets/icons/document-line-C7mb9sb7.js +0 -1
  224. package/phanpy/dist/assets/icons/down-line-BGqSSThd.js +0 -1
  225. package/phanpy/dist/assets/icons/edit-4-line-DcgGaEhx.js +0 -1
  226. package/phanpy/dist/assets/icons/emoji-2-line-D8YgnM-y.js +0 -1
  227. package/phanpy/dist/assets/icons/exit-line-Dc07BS-x.js +0 -1
  228. package/phanpy/dist/assets/icons/external-link-line-BvpSkmjO.js +0 -1
  229. package/phanpy/dist/assets/icons/eye-2-line-CiWXx-f_.js +0 -1
  230. package/phanpy/dist/assets/icons/eye-close-line-BgTaSJ4P.js +0 -1
  231. package/phanpy/dist/assets/icons/filter-2-line-B_mR2usy.js +0 -1
  232. package/phanpy/dist/assets/icons/filter-line-C_nm-3cU.js +0 -1
  233. package/phanpy/dist/assets/icons/flag-1-line-C2HB05I0.js +0 -1
  234. package/phanpy/dist/assets/icons/forbid-circle-line-D3LTT9yd.js +0 -1
  235. package/phanpy/dist/assets/icons/formula-line-DLrt2d6j.js +0 -1
  236. package/phanpy/dist/assets/icons/grid-line-CgUmEikf.js +0 -1
  237. package/phanpy/dist/assets/icons/group-line-C5fnX0wU.js +0 -1
  238. package/phanpy/dist/assets/icons/hand-finger-2-line-BGgxbuL9.js +0 -1
  239. package/phanpy/dist/assets/icons/hashtag-line-CyTEEXBb.js +0 -1
  240. package/phanpy/dist/assets/icons/heart-crack-line-BZddbO2C.js +0 -1
  241. package/phanpy/dist/assets/icons/heart-line-CJ7K47J4.js +0 -1
  242. package/phanpy/dist/assets/icons/history-2-line-kWWvFzqv.js +0 -1
  243. package/phanpy/dist/assets/icons/history-line-bGQiwbTH.js +0 -1
  244. package/phanpy/dist/assets/icons/home-3-line-fRU1zeLG.js +0 -1
  245. package/phanpy/dist/assets/icons/information-line-iNk6-sOY.js +0 -1
  246. package/phanpy/dist/assets/icons/keyboard-line-DsCyuQNz.js +0 -1
  247. package/phanpy/dist/assets/icons/layout-4-line-CZkOX52Z.js +0 -1
  248. package/phanpy/dist/assets/icons/layout-5-line-B_znt5wO.js +0 -1
  249. package/phanpy/dist/assets/icons/left-line-eUw0tya4.js +0 -1
  250. package/phanpy/dist/assets/icons/lightning-line-DaKoDdix.js +0 -1
  251. package/phanpy/dist/assets/icons/link-2-line-DCqLmMA7.js +0 -1
  252. package/phanpy/dist/assets/icons/list-check-line-BAxosBh9.js +0 -1
  253. package/phanpy/dist/assets/icons/lock-line-CPoaXeUW.js +0 -1
  254. package/phanpy/dist/assets/icons/mail-line-B0P8aa1L.js +0 -1
  255. package/phanpy/dist/assets/icons/moon-line-CEqNAToP.js +0 -1
  256. package/phanpy/dist/assets/icons/more-1-fill-Dw2e5xab.js +0 -1
  257. package/phanpy/dist/assets/icons/more-3-line-BIiVjYTz.js +0 -1
  258. package/phanpy/dist/assets/icons/notification-line-BZZq2Gtu.js +0 -1
  259. package/phanpy/dist/assets/icons/pencil-line-CKh8-A1v.js +0 -1
  260. package/phanpy/dist/assets/icons/photo-album-line-Bnfg2u40.js +0 -1
  261. package/phanpy/dist/assets/icons/pin-line-DpTolyhs.js +0 -1
  262. package/phanpy/dist/assets/icons/play-fill-B0-G_8lX.js +0 -1
  263. package/phanpy/dist/assets/icons/qrcode-2-line-B6SPCaHn.js +0 -1
  264. package/phanpy/dist/assets/icons/quill-pen-line-B6LUrnCP.js +0 -1
  265. package/phanpy/dist/assets/icons/quote-left-fill-hQZ1cj_6.js +0 -1
  266. package/phanpy/dist/assets/icons/radar-line-CKyroTth.js +0 -1
  267. package/phanpy/dist/assets/icons/react-line-CoIKhhtK.js +0 -1
  268. package/phanpy/dist/assets/icons/refresh-2-line-D0mTF97S.js +0 -1
  269. package/phanpy/dist/assets/icons/right-line-B3KWJaqc.js +0 -1
  270. package/phanpy/dist/assets/icons/rocket-line-4KZl537D.js +0 -1
  271. package/phanpy/dist/assets/icons/round-fill-C69NTrZ0.js +0 -1
  272. package/phanpy/dist/assets/icons/round-line-B9k_1uo-.js +0 -1
  273. package/phanpy/dist/assets/icons/route-line-B28ReHcE.js +0 -1
  274. package/phanpy/dist/assets/icons/rows-4-line-CrBniagM.js +0 -1
  275. package/phanpy/dist/assets/icons/scan-line-DSf74YdP.js +0 -1
  276. package/phanpy/dist/assets/icons/search-2-line-B6geIU1z.js +0 -1
  277. package/phanpy/dist/assets/icons/settings-3-line-azJwK47w.js +0 -1
  278. package/phanpy/dist/assets/icons/settings-6-line-C_5zc5YR.js +0 -1
  279. package/phanpy/dist/assets/icons/share-2-line-D5MxWjrm.js +0 -1
  280. package/phanpy/dist/assets/icons/share-forward-line-Cp61fZzZ.js +0 -1
  281. package/phanpy/dist/assets/icons/sparkles-2-line-DYYyXTaW.js +0 -1
  282. package/phanpy/dist/assets/icons/sparkles-line-C2hY1s6I.js +0 -1
  283. package/phanpy/dist/assets/icons/time-line-BF3eet1v.js +0 -1
  284. package/phanpy/dist/assets/icons/transfer-4-line-oisfvTBw.js +0 -1
  285. package/phanpy/dist/assets/icons/translate-line-BFWol2Ce.js +0 -1
  286. package/phanpy/dist/assets/icons/unlock-line-X_x5vwv3.js +0 -1
  287. package/phanpy/dist/assets/icons/upload-3-line-DgzwUJeW.js +0 -1
  288. package/phanpy/dist/assets/icons/user-4-line-BVSynZCp.js +0 -1
  289. package/phanpy/dist/assets/icons/user-add-2-line-BlGS5Q8q.js +0 -1
  290. package/phanpy/dist/assets/icons/user-add-line-BbXdHG4z.js +0 -1
  291. package/phanpy/dist/assets/icons/user-edit-line-BFAHOV0X.js +0 -1
  292. package/phanpy/dist/assets/icons/user-follow-line-BXux0wSd.js +0 -1
  293. package/phanpy/dist/assets/icons/user-star-line-DdYlpHLU.js +0 -1
  294. package/phanpy/dist/assets/icons/user-warning-line-Cj43j0_E.js +0 -1
  295. package/phanpy/dist/assets/icons/user-x-line-DIDgNlPH.js +0 -1
  296. package/phanpy/dist/assets/icons/volume-line-Bs390KQt.js +0 -1
  297. package/phanpy/dist/assets/icons/volume-mute-line-DzlzIqFZ.js +0 -1
  298. package/phanpy/dist/assets/icons/walk-line-yNXfB-md.js +0 -1
  299. package/phanpy/dist/assets/icons/world-2-line-CenkGIwj.js +0 -1
  300. package/phanpy/dist/assets/icons/zoom-in-line-CaENUphf.js +0 -1
  301. package/phanpy/dist/assets/icons/zoom-out-line-BDNk0EWo.js +0 -1
  302. package/phanpy/dist/assets/instances-BNPptnn-.json +0 -333
  303. package/phanpy/dist/assets/locales/ar-SA-CfheRbCu.js +0 -1
  304. package/phanpy/dist/assets/locales/ca-ES-DHRW86Tz.js +0 -1
  305. package/phanpy/dist/assets/locales/cs-CZ-CKoqEuA1.js +0 -1
  306. package/phanpy/dist/assets/locales/de-DE-ByRPMRFy.js +0 -1
  307. package/phanpy/dist/assets/locales/eo-UY-CPC7wLCS.js +0 -1
  308. package/phanpy/dist/assets/locales/es-ES-dJa0Fd9t.js +0 -1
  309. package/phanpy/dist/assets/locales/eu-ES-C3djlPR2.js +0 -1
  310. package/phanpy/dist/assets/locales/fa-IR-DTT490Ck.js +0 -1
  311. package/phanpy/dist/assets/locales/fi-FI-B23MCPuz.js +0 -1
  312. package/phanpy/dist/assets/locales/fr-FR-BcaQsO5C.js +0 -1
  313. package/phanpy/dist/assets/locales/gl-ES-BR-CVKBN.js +0 -1
  314. package/phanpy/dist/assets/locales/he-IL-CfheRbCu.js +0 -1
  315. package/phanpy/dist/assets/locales/hu-HU-CfheRbCu.js +0 -1
  316. package/phanpy/dist/assets/locales/it-IT-C1136UFV.js +0 -1
  317. package/phanpy/dist/assets/locales/ja-JP-CRZmRAhD.js +0 -1
  318. package/phanpy/dist/assets/locales/kab-Cz8xH_TT.js +0 -1
  319. package/phanpy/dist/assets/locales/ko-KR-CG9UiY4o.js +0 -1
  320. package/phanpy/dist/assets/locales/lt-LT-CJcI6sHZ.js +0 -1
  321. package/phanpy/dist/assets/locales/nb-NO-CfheRbCu.js +0 -1
  322. package/phanpy/dist/assets/locales/nl-NL-DxN4Yc1t.js +0 -1
  323. package/phanpy/dist/assets/locales/oc-FR-CfheRbCu.js +0 -1
  324. package/phanpy/dist/assets/locales/pl-PL-DkwGXf6P.js +0 -1
  325. package/phanpy/dist/assets/locales/pseudo-LOCALE-CQ4XxkQ0.js +0 -1
  326. package/phanpy/dist/assets/locales/pt-BR-nGL0HdFS.js +0 -1
  327. package/phanpy/dist/assets/locales/pt-PT-Cp17aj2z.js +0 -1
  328. package/phanpy/dist/assets/locales/ru-RU-0Y8cIoaK.js +0 -1
  329. package/phanpy/dist/assets/locales/th-TH-CfheRbCu.js +0 -1
  330. package/phanpy/dist/assets/locales/tok-CfheRbCu.js +0 -1
  331. package/phanpy/dist/assets/locales/tr-TR-CfheRbCu.js +0 -1
  332. package/phanpy/dist/assets/locales/uk-UA-C5E3dFBf.js +0 -1
  333. package/phanpy/dist/assets/locales/zh-CN-BHJi7_et.js +0 -1
  334. package/phanpy/dist/assets/locales/zh-TW-CfheRbCu.js +0 -1
  335. package/phanpy/dist/assets/main-BdqNbG-a.js +0 -26
  336. package/phanpy/dist/assets/main-BdqNbG-a.js.map +0 -1
  337. package/phanpy/dist/assets/mock-home-DGhR__He.js +0 -2
  338. package/phanpy/dist/assets/mock-home-DGhR__He.js.map +0 -1
  339. package/phanpy/dist/assets/multi-column-ROck0NVt.jpg +0 -0
  340. package/phanpy/dist/assets/multi-hashtag-timeline-DH5INVHi.jpg +0 -0
  341. package/phanpy/dist/assets/nested-comments-thread-hv59kZLc.jpg +0 -0
  342. package/phanpy/dist/assets/polyfill-force-H8sUkLGp.js +0 -2
  343. package/phanpy/dist/assets/polyfill-force-H8sUkLGp.js.map +0 -1
  344. package/phanpy/dist/assets/pwa-viewport-ogKbF51K.js +0 -20
  345. package/phanpy/dist/assets/pwa-viewport-ogKbF51K.js.map +0 -1
  346. package/phanpy/dist/assets/style-B1iLPTPR.css +0 -2
  347. package/phanpy/dist/assets/temml-DuyIQMei.js +0 -97
  348. package/phanpy/dist/assets/temml-DuyIQMei.js.map +0 -1
  349. package/phanpy/dist/assets/tinyld.light.node-DtbBhLYC.js +0 -2
  350. package/phanpy/dist/assets/tinyld.light.node-DtbBhLYC.js.map +0 -1
  351. package/phanpy/dist/assets/year-in-posts-B2lywqdx.png +0 -0
  352. package/phanpy/dist/assets/year-in-posts-DJv_star.js +0 -3
  353. package/phanpy/dist/assets/year-in-posts-DJv_star.js.map +0 -1
  354. package/phanpy/dist/compose/index.html +0 -20
  355. package/phanpy/dist/favicon.ico +0 -0
  356. package/phanpy/dist/index.html +0 -73
  357. package/phanpy/dist/logo-192.png +0 -0
  358. package/phanpy/dist/logo-512.png +0 -0
  359. package/phanpy/dist/logo-badge-72.png +0 -0
  360. package/phanpy/dist/logo-maskable-512.png +0 -0
  361. package/phanpy/dist/logo-monochrome-512.png +0 -0
  362. package/phanpy/dist/logo-monochrome-maskable-512.png +0 -0
  363. package/phanpy/dist/manifest.webmanifest +0 -1
  364. package/phanpy/dist/og-image-2.jpg +0 -0
  365. package/phanpy/dist/og-image.png +0 -0
  366. package/phanpy/dist/robots.txt +0 -2
  367. package/phanpy/dist/sw.js +0 -2
  368. package/phanpy/dist/sw.js.map +0 -1
  369. package/phanpy/dist/version.json +0 -1
  370. package/run-agent.mjs +0 -899
  371. package/vendor/gate.cjs +0 -138
  372. package/vendor/idp-grant.cjs +0 -394
  373. package/vendor/jfetch.cjs +0 -16
  374. package/web/admin/actors.js +0 -147
  375. package/web/admin/bar.css +0 -126
  376. package/web/admin/bar.js +0 -99
  377. package/web/admin/client/client.js +0 -66
  378. package/web/admin/client/index.html +0 -69
  379. package/web/admin/client-pick.js +0 -79
  380. package/web/admin/common.js +0 -23
  381. package/web/admin/connections.js +0 -112
  382. package/web/admin/gateway.js +0 -133
  383. package/web/admin/group.js +0 -258
  384. package/web/admin/index.html +0 -612
  385. package/web/admin/notices-bar.js +0 -90
  386. package/web/admin/oauth-signin.mjs +0 -37
  387. package/web/admin/record.js +0 -393
  388. package/web/admin/setup/index.html +0 -257
  389. package/web/admin/setup/setup.js +0 -339
  390. package/web/admin/tokens.css +0 -68
  391. package/web/admin/upkeep.js +0 -177
  392. package/web/admin/window.css +0 -55
  393. package/web/admin/window.js +0 -136
  394. package/web/app/README.md +0 -81
  395. package/web/app/admin-facade.mjs +0 -588
  396. package/web/app/agent.mjs +0 -672
  397. package/web/app/atproto-browser.mjs +0 -86
  398. package/web/app/authorize_interaction.html +0 -56
  399. package/web/app/authorize_interaction.js +0 -69
  400. package/web/app/boot.mjs +0 -616
  401. package/web/app/deliver-relay.mjs +0 -129
  402. package/web/app/dist/boot.js +0 -34562
  403. package/web/app/dist/boot.js.map +0 -7
  404. package/web/app/dist/sw.js +0 -73456
  405. package/web/app/dist/sw.js.map +0 -7
  406. package/web/app/fediacct-browser.mjs +0 -146
  407. package/web/app/gateway-move.mjs +0 -60
  408. package/web/app/idb-kv.mjs +0 -35
  409. package/web/app/index.html +0 -258
  410. package/web/app/keys-browser.mjs +0 -71
  411. package/web/app/keystore.mjs +0 -82
  412. package/web/app/oidc-session.mjs +0 -6
  413. package/web/app/pod-auth.mjs +0 -248
  414. package/web/app/pod-remote.mjs +0 -84
  415. package/web/app/shims/_globals.mjs +0 -2
  416. package/web/app/shims/fedify-sig.mjs +0 -77
  417. package/web/app/shims/node-crypto.mjs +0 -113
  418. package/web/app/shims/node-fs.mjs +0 -17
  419. package/web/app/shims/node-path.mjs +0 -19
  420. package/web/app/shims/node-url.mjs +0 -7
  421. package/web/app/shims/prelude.js +0 -58
  422. package/web/app/shims/safefetch.mjs +0 -58
  423. package/web/app/shims/shapes-text.mjs +0 -8
  424. package/web/app/shims/web-push.mjs +0 -14
  425. package/web/app/signup.mjs +0 -357
  426. package/web/app/sw-src.mjs +0 -220
  427. package/web/app/update.js +0 -70
  428. package/web/front/admin.html +0 -55
  429. package/web/front/admin.js +0 -89
  430. package/web/front/install.sh +0 -69
  431. package/web/front/new-account.html +0 -35
  432. package/web/front/new-account.js +0 -13
  433. package/web/front/notices.html +0 -69
  434. package/web/front/notices.js +0 -107
  435. package/web/front/run.html +0 -64
  436. package/web/front/run.js +0 -221
  437. package/web/front/solid-oidc-client.js +0 -6
  438. package/web/icons/forum.svg +0 -11
  439. package/web/icons/gateway.svg +0 -14
  440. package/web/icons/local.svg +0 -11
@@ -1,438 +0,0 @@
1
- // timelines.mjs — what a client reads by the page: the home and public
2
- // timelines, hashtags and the tag feed, lists and their timelines, the v2
3
- // filters, conversations, notifications, and search.
4
-
5
- import crypto from 'node:crypto';
6
- import * as social from '../../core/social.mjs';
7
- import { sanitizeHtml } from '../../core/wire.mjs';
8
- import { authorOf } from '../../core/intake/index.mjs';
9
- import { readBody } 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
- // home + public = everything known (follows, boosts, tag feed, own);
15
- // public?local=true = own posts; trends/statuses = the same activity (a
16
- // single-actor instance has no firehose — what it knows IS its public
17
- // face). Sorted by publish time so tag-feed backfill interleaves.
18
- if (pathname === '/api/v1/timelines/home' || pathname === '/api/v1/timelines/public'
19
- || pathname === '/api/v1/trends/statuses') {
20
- const localOnly = pathname === '/api/v1/timelines/public' && url.searchParams.get('local') === 'true';
21
- // The UNFILTERED list, both as the source and as the reply-count corpus:
22
- // a reply can be a mention, and mentions are filtered out of timelines
23
- // below — counting against the filtered set would undercount them.
24
- const all = api.store.getStatuses();
25
- const muted = new Set(api.store.getMuted().actors);
26
- let items = all
27
- // search ingests and strangers' mentions stay out of the timelines
28
- // (mentions remain in notifications and /api/v1/statuses); direct
29
- // posts belong to the conversations view, not a timeline; and the
30
- // local/public view shows only what is actually public-facing.
31
- .filter(s => s.kind !== 'remote' && s.kind !== 'mention')
32
- .filter(s => !s.direct && s.visibility !== 'direct')
33
- .filter(s => !muted.has(s.actor) && !muted.has(s.via))
34
- .filter(s => !localOnly || (s.kind === 'post' && s.visibility !== 'private'))
35
- .sort((a, b) => String(b.published || '').localeCompare(String(a.published || '')));
36
- const { items: page, headers } = api.page(items, url);
37
- return send(200, page.map(s => api.statusOrBoost(s, { all })), headers);
38
- }
39
-
40
- // --- hashtags: the client's Followed Hashtags surface drives the tag feed ---
41
- // followed_tags is what the feed currently pulls; follow/unfollow add and
42
- // remove a hashtag (unfollow the last and the topical feed goes quiet); the
43
- // tag timeline is the mirrored notes carrying that tag.
44
- if (pathname === '/api/v1/followed_tags') {
45
- const tags = api.agent.tagfeed?.config().tags || [];
46
- return send(200, tags.map(t => api.tagObject(t, true, req)));
47
- }
48
- const mTagFollow = pathname.match(/^\/api\/v1\/tags\/([^/]+)\/(follow|unfollow)$/);
49
- if (mTagFollow && req.method === 'POST') {
50
- const name = decodeURIComponent(mTagFollow[1]).replace(/^#/, '').toLowerCase();
51
- const follow = mTagFollow[2] === 'follow';
52
- const tf = api.agent.tagfeed;
53
- if (tf) {
54
- const cur = tf.config().tags;
55
- tf.setConfig({ tags: follow ? [...new Set([...cur, name])] : cur.filter(t => t !== name) });
56
- }
57
- return send(200, api.tagObject(name, follow, req));
58
- }
59
- const mTagGet = pathname.match(/^\/api\/v1\/tags\/([^/]+)$/);
60
- if (mTagGet && req.method === 'GET') {
61
- const name = decodeURIComponent(mTagGet[1]).replace(/^#/, '').toLowerCase();
62
- const following = (api.agent.tagfeed?.config().tags || []).includes(name);
63
- return send(200, api.tagObject(name, following, req));
64
- }
65
- // --- trending tags: counted off the notes already mirrored ---
66
- //
67
- // A single-actor instance has no firehose to measure, so "trending" here can
68
- // only mean what the tag feed has actually brought in — which is the honest
69
- // answer, and the one that makes a client's Explore surface show something
70
- // instead of an empty list. `/api/v1/trends` is Mastodon's older name for the
71
- // same thing.
72
- if (pathname === '/api/v1/trends/tags' || pathname === '/api/v1/trends') {
73
- const DAY = 86400000;
74
- const today = Math.floor(Date.now() / DAY) * DAY;
75
- // tag -> day -> { uses, accounts }. Accounts are counted distinctly, which
76
- // is what the number means in Mastodon: one person posting nine times is
77
- // nine uses by one account, and a client shows the second number.
78
- const seen = new Map();
79
- for (const st of api.store.getStatuses()) {
80
- if (st.kind !== 'tag' || !st.tag) continue;
81
- const at = Date.parse(st.published || '');
82
- if (!Number.isFinite(at) || at < today - 6 * DAY) continue;
83
- const day = Math.floor(at / DAY) * DAY;
84
- if (!seen.has(st.tag)) seen.set(st.tag, new Map());
85
- const days = seen.get(st.tag);
86
- if (!days.has(day)) days.set(day, { uses: 0, accounts: new Set() });
87
- const rec = days.get(day);
88
- rec.uses++;
89
- if (st.actor) rec.accounts.add(st.actor);
90
- }
91
- const followed = new Set(api.agent.tagfeed?.config().tags || []);
92
- const ranked = [...seen.entries()].map(([name, days]) => {
93
- // Seven entries, newest first, gaps filled with zeroes — a client draws
94
- // the sparkline straight off this and a short array draws a short line.
95
- const history = [];
96
- for (let i = 0; i < 7; i++) {
97
- const rec = days.get(today - i * DAY);
98
- history.push({ day: String(Math.floor((today - i * DAY) / 1000)),
99
- uses: String(rec ? rec.uses : 0), accounts: String(rec ? rec.accounts.size : 0) });
100
- }
101
- return { total: history.reduce((n, h) => n + Number(h.uses), 0),
102
- tag: api.tagObject(name, followed.has(name), req, history) };
103
- }).sort((a, b) => b.total - a.total);
104
- const limit = Math.min(Math.max(Number(url.searchParams.get('limit')) || 10, 1), 40);
105
- return send(200, ranked.slice(0, limit).map(x => x.tag));
106
- }
107
-
108
- const mTagTl = pathname.match(/^\/api\/v1\/timelines\/tag\/([^/]+)$/);
109
- if (mTagTl && req.method === 'GET') {
110
- const name = decodeURIComponent(mTagTl[1]).replace(/^#/, '').toLowerCase();
111
- const all = api.store.getStatuses();
112
- const items = all.filter(s => s.kind === 'tag' && s.tag === name)
113
- .sort((a, b) => String(b.published || '').localeCompare(String(a.published || '')));
114
- const { items: page, headers } = api.page(items, url);
115
- return send(200, page.map(s => api.statusOrBoost(s, { all })), headers);
116
- }
117
-
118
- // Lists, filters and scheduled posts are the client's own arrangements —
119
- // kept in local state, nothing federates.
120
- const listJson = (l) => ({ id: l.id, title: l.title, replies_policy: l.repliesPolicy || 'list', exclusive: false });
121
- if (pathname === '/api/v1/lists' && req.method === 'GET') {
122
- return send(200, api.store.getLists().map(listJson));
123
- }
124
- if (pathname === '/api/v1/lists' && req.method === 'POST') {
125
- const body = await readBody(req);
126
- const title = String(body.title || '').trim();
127
- if (!title) return send(422, { error: 'a title is required' });
128
- const lists = api.store.getLists();
129
- const l = { id: crypto.randomBytes(8).toString('hex'), title, repliesPolicy: body.replies_policy || 'list', members: [] };
130
- lists.push(l);
131
- api.store.setLists(lists);
132
- return send(200, listJson(l));
133
- }
134
- const mList = /^\/api\/v1\/lists\/([a-f0-9]+)$/.exec(pathname);
135
- if (mList) {
136
- const lists = api.store.getLists();
137
- const l = lists.find(x => x.id === mList[1]);
138
- if (!l) return send(404, { error: 'Record not found' });
139
- if (req.method === 'DELETE') {
140
- api.store.setLists(lists.filter(x => x.id !== mList[1]));
141
- return send(200, {});
142
- }
143
- if (req.method === 'PUT') {
144
- const body = await readBody(req);
145
- if (body.title) l.title = String(body.title);
146
- if (body.replies_policy) l.repliesPolicy = body.replies_policy;
147
- api.store.setLists(lists);
148
- }
149
- return send(200, listJson(l));
150
- }
151
- const mListAcc = /^\/api\/v1\/lists\/([a-f0-9]+)\/accounts$/.exec(pathname);
152
- if (mListAcc) {
153
- const lists = api.store.getLists();
154
- const l = lists.find(x => x.id === mListAcc[1]);
155
- if (!l) return send(404, { error: 'Record not found' });
156
- if (req.method === 'GET') return send(200, (l.members || []).map(a => api.account(a)));
157
- const body = await readBody(req).catch(() => ({}));
158
- const ids = [].concat(body.account_ids || body['account_ids[]'] || url.searchParams.getAll('account_ids[]')).filter(Boolean);
159
- const actors = ids.map(id => api.store.urlFor(id)).filter(Boolean);
160
- if (req.method === 'POST') l.members = [...new Set([...(l.members || []), ...actors])];
161
- if (req.method === 'DELETE') l.members = (l.members || []).filter(a => !actors.includes(a));
162
- api.store.setLists(lists);
163
- return send(200, {});
164
- }
165
- const mListTl = /^\/api\/v1\/timelines\/list\/([a-f0-9]+)$/.exec(pathname);
166
- if (mListTl) {
167
- const l = api.store.getLists().find(x => x.id === mListTl[1]);
168
- if (!l) return send(404, { error: 'Record not found' });
169
- const members = new Set(l.members || []);
170
- const all = api.store.getStatuses();
171
- const items = all.filter(s => members.has(s.actor) || members.has(s.via))
172
- .sort((a, b) => String(b.published || '').localeCompare(String(a.published || '')));
173
- const { items: page, headers } = api.page(items, url);
174
- return send(200, page.map(s => api.statusOrBoost(s, { all })), headers);
175
- }
176
-
177
- // v2 filters: stored, served, and APPLIED — every status carries what it
178
- // matched in `filtered` (see filtersFor), because no client matches
179
- // keywords for itself.
180
- const filterJson = (f) => ({
181
- id: f.id, title: f.title, context: f.context || ['home'],
182
- expires_at: f.expiresAt || null, filter_action: f.action || 'warn',
183
- keywords: (f.keywords || []).map((k, i) => ({ id: `${f.id}-${i}`, keyword: k.keyword, whole_word: !!k.wholeWord })),
184
- statuses: [],
185
- });
186
- const keywordsOf = (attrs) => [].concat(attrs || [])
187
- .filter(k => k?.keyword && !(k._destroy === true || k._destroy === 'true'))
188
- .map(k => ({ keyword: String(k.keyword), wholeWord: k.whole_word === true || k.whole_word === 'true' }));
189
- if (pathname === '/api/v2/filters' && req.method === 'GET') {
190
- return send(200, api.store.getFilters().map(filterJson));
191
- }
192
- if (pathname === '/api/v2/filters' && req.method === 'POST') {
193
- const body = await readBody(req);
194
- const title = String(body.title || '').trim();
195
- if (!title) return send(422, { error: 'a title is required' });
196
- const filters = api.store.getFilters();
197
- const f = {
198
- id: crypto.randomBytes(8).toString('hex'), title,
199
- context: [].concat(body.context || ['home']),
200
- action: body.filter_action || 'warn', expiresAt: null,
201
- keywords: keywordsOf(body.keywords_attributes),
202
- };
203
- filters.push(f);
204
- api.store.setFilters(filters);
205
- return send(200, filterJson(f));
206
- }
207
- const mFilter = /^\/api\/v2\/filters\/([a-f0-9]+)$/.exec(pathname);
208
- if (mFilter) {
209
- const filters = api.store.getFilters();
210
- const f = filters.find(x => x.id === mFilter[1]);
211
- if (!f) return send(404, { error: 'Record not found' });
212
- if (req.method === 'DELETE') {
213
- api.store.setFilters(filters.filter(x => x.id !== mFilter[1]));
214
- return send(200, {});
215
- }
216
- if (req.method === 'PUT') {
217
- const body = await readBody(req);
218
- if (body.title) f.title = String(body.title);
219
- if (body.context) f.context = [].concat(body.context);
220
- if (body.filter_action) f.action = body.filter_action;
221
- if (body.keywords_attributes) f.keywords = keywordsOf(body.keywords_attributes);
222
- api.store.setFilters(filters);
223
- }
224
- return send(200, filterJson(f));
225
- }
226
-
227
- // Conversations: direct posts, grouped by who is in them.
228
- if (pathname === '/api/v1/conversations' && req.method === 'GET') {
229
- const all = api.store.getStatuses();
230
- const me = api.urls.actor;
231
- const convos = new Map();
232
- for (const s of all) {
233
- if (!(s.direct || s.visibility === 'direct')) continue;
234
- const others = [...new Set([
235
- ...(s.actor !== me ? [s.actor] : []),
236
- ...((s.mentions || []).map(m => m.href).filter(a => a && a !== me)),
237
- ])];
238
- const key = others.sort().join(' ') || me;
239
- const c = convos.get(key) || { accounts: new Set(), last: s };
240
- for (const o of others) c.accounts.add(o);
241
- if (String(s.published || '') > String(c.last.published || '')) c.last = s;
242
- convos.set(key, c);
243
- }
244
- const items = [...convos.entries()]
245
- .sort((a, b) => String(b[1].last.published || '').localeCompare(String(a[1].last.published || '')));
246
- return send(200, items.map(([key, c]) => ({
247
- id: api.store.idFor('conversation:' + key),
248
- unread: false,
249
- accounts: [...(c.accounts.size ? c.accounts : [me])].map(a => api.account(a)),
250
- last_status: api.status(c.last, { all }),
251
- })));
252
- }
253
-
254
- // The direct timeline: every direct post, newest first, paged like the rest.
255
- if (pathname === '/api/v1/timelines/direct' && req.method === 'GET') {
256
- const all = api.store.getStatuses();
257
- const items = all.filter(s => s.direct || s.visibility === 'direct');
258
- const { items: page, headers } = api.page(items, url);
259
- return send(200, page.map(s => api.status(s, { all })), headers);
260
- }
261
-
262
- // One notification by id — what a push notification tap opens — and the
263
- // two ways of being rid of them.
264
- if (pathname === '/api/v1/notifications/clear' && req.method === 'POST') {
265
- api.store.removeNotifications(() => true);
266
- return send(200, {});
267
- }
268
- const mNotif = /^\/api\/v1\/notifications\/([A-Za-z0-9_-]+)(\/dismiss)?$/.exec(pathname);
269
- if (mNotif) {
270
- const n = api.store.getNotifications().find(x => x.id === mNotif[1]);
271
- if (!n) return send(404, { error: 'Record not found' });
272
- if (mNotif[2] && req.method === 'POST') { api.store.removeNotifications(x => x.id === n.id); return send(200, {}); }
273
- if (!mNotif[2] && req.method === 'GET') return send(200, api.notification(n));
274
- }
275
-
276
- // Grouped notifications, the shape a client reads from API version 2 on.
277
- // One group per notification: the client does its own gathering of
278
- // favourites and boosts on a post, and every field it joins on — the
279
- // account and status ids — is here.
280
- const mGroup = /^\/api\/v2\/notifications\/([A-Za-z0-9_-]+)(\/accounts|\/dismiss)?$/.exec(pathname);
281
- if (mGroup) {
282
- const n = api.store.getNotifications().find(x => x.id === mGroup[1]);
283
- if (!n) return send(404, { error: 'Record not found' });
284
- if (mGroup[2] === '/dismiss' && req.method === 'POST') { api.store.removeNotifications(x => x.id === n.id); return send(200, {}); }
285
- if (mGroup[2] === '/accounts' && req.method === 'GET') return send(200, [api.account(n.actor)]);
286
- if (!mGroup[2] && req.method === 'GET') return send(200, groupedNotifications(api, [n]));
287
- }
288
- if (pathname === '/api/v2/notifications') {
289
- const { page, headers } = notificationPage(api, ctx);
290
- return send(200, groupedNotifications(api, page), headers);
291
- }
292
-
293
- if (pathname === '/api/v1/notifications') {
294
- const { page, headers } = notificationPage(api, ctx);
295
- return send(200, page.map((n) => api.notification(n)), headers);
296
- }
297
-
298
- // Search: @user@host → webfinger resolve; URL → actor or note ingest;
299
- // plain text → local mirror + actor-cache scan.
300
- if (pathname === '/api/v2/search' || pathname === '/api/v1/search') {
301
- return searchHandler(api, ctx);
302
- }
303
- return false;
304
- }
305
-
306
- function groupedNotifications(api, page) {
307
- const accounts = new Map();
308
- const statuses = new Map();
309
- const groups = page.map((n) => {
310
- const acct = api.account(n.actor);
311
- accounts.set(acct.id, acct);
312
- const s = n.noteId && api.store.getStatuses().find(x => x.noteId === n.noteId);
313
- let statusId = null;
314
- if (s) { const j = api.status(s); statuses.set(j.id, j); statusId = j.id; }
315
- return {
316
- group_key: n.id, notifications_count: 1, type: api.notificationType(n.type),
317
- most_recent_notification_id: n.id, page_min_id: n.id, page_max_id: n.id,
318
- latest_page_notification_at: n.at, sample_account_ids: [acct.id],
319
- ...(statusId ? { status_id: statusId } : {}),
320
- ...(n.type === 'reaction' ? { emoji: n.emoji, ...(n.url ? { emoji_url: n.url } : {}) } : {}),
321
- };
322
- });
323
- return { accounts: [...accounts.values()], statuses: [...statuses.values()], notification_groups: groups };
324
- }
325
-
326
- // The slice of notifications a request asks for, and the Link header that
327
- // pages it. Shared by the flat list and the grouped one.
328
- function notificationPage(api, ctx) {
329
- const { req, pathname, url } = ctx;
330
- {
331
- const limit = Math.min(Number(url.searchParams.get('limit')) || 30, 60);
332
- const q = url.searchParams;
333
- // Newest first, which is what every id rule below assumes.
334
- let items = api.store.getNotifications();
335
-
336
- // Which KINDS. A client that asks for mentions and is handed favourites
337
- // and boosts renders them as mentions — Phanpy's Mentions column did
338
- // exactly that, and re-pilled on every poll. Both spellings: clients send
339
- // `types[]=mention` and some send bare `types=mention`.
340
- const listParam = (name) => {
341
- const all = [...q.getAll(`${name}[]`), ...q.getAll(name)]
342
- .flatMap((v) => String(v).split(',')).map((v) => v.trim()).filter(Boolean);
343
- return all.length ? new Set(all) : null;
344
- };
345
- const want = listParam('types');
346
- const skip = listParam('exclude_types');
347
- // Mastodon spells the type with an underscore; ours is stored with a
348
- // hyphen, and notification() translates on the way out — so match on what
349
- // the client would have been given, not on what is on disk.
350
- const shown = (n) => api.notificationType(n.type);
351
- if (want) items = items.filter((n) => want.has(shown(n)));
352
- if (skip) items = items.filter((n) => !skip.has(shown(n)));
353
-
354
- // Which SLICE. `max_id` walks backwards into history; `since_id` and
355
- // `min_id` both mean "newer than this", which is how a client asks
356
- // "anything since I last looked?". Ignoring them meant every poll
357
- // returned the whole list, so the client always saw unread items and the
358
- // bell never went out.
359
- const cut = (id, keepNewer) => {
360
- const i = items.findIndex((n) => n.id === id);
361
- if (i < 0) return;
362
- items = keepNewer ? items.slice(0, i) : items.slice(i + 1);
363
- };
364
- const maxId = q.get('max_id');
365
- if (maxId) cut(maxId, false);
366
- const sinceId = q.get('since_id') || q.get('min_id');
367
- if (sinceId) cut(sinceId, true);
368
- // min_id asks for the ones IMMEDIATELY newer, i.e. the oldest end of
369
- // what is newer; since_id asks for the newest. Only the window differs.
370
- const page = q.get('min_id') && !q.get('since_id')
371
- ? items.slice(Math.max(0, items.length - limit))
372
- : items.slice(0, limit);
373
-
374
- // A client pages by following these rather than by guessing ids.
375
- if (page.length) {
376
- const base = `${api.scheme || (req.socket?.encrypted ? 'https' : 'http')}://${req.headers.host}${api.mount || ''}${pathname}`;
377
- const link = (params) => {
378
- const u = new URL(base);
379
- for (const [k, v] of q) if (k !== 'max_id' && k !== 'since_id' && k !== 'min_id') u.searchParams.append(k, v);
380
- for (const [k, v] of Object.entries(params)) u.searchParams.set(k, v);
381
- return u.href;
382
- };
383
- return { page, headers: {
384
- link: `<${link({ max_id: page[page.length - 1].id })}>; rel="next", `
385
- + `<${link({ min_id: page[0].id })}>; rel="prev"`,
386
- } };
387
- }
388
- return { page: [], headers: undefined };
389
- }
390
- }
391
-
392
- // Search: @user@host → webfinger resolve; URL → actor or note ingest;
393
- // plain text → local mirror + actor-cache scan.
394
- async function searchHandler(api, ctx) {
395
- const { req, res, pathname, url, send } = ctx; // eslint-disable-line no-unused-vars
396
- {
397
- const q = String(url.searchParams.get('q') || '').trim();
398
- const type = url.searchParams.get('type');
399
- const out = { accounts: [], statuses: [], hashtags: [] };
400
- const asHandle = /^@?[^@\s]+@[^@\s.]+\.[^@\s]+$/.test(q);
401
- if (asHandle && type !== 'statuses') {
402
- try {
403
- const doc = await social.resolveHandle(api.agent, q);
404
- out.accounts.push(api.account(doc.id));
405
- } catch (e) { api.log(`search resolve ${q}: ${e.message}`); }
406
- } else if (/^https?:\/\//.test(q)) {
407
- const doc = await api.agent.intake.fetchAP(q).catch(() => null);
408
- if (doc?.type === 'Person' && doc.id) out.accounts.push(api.account(doc.id));
409
- else if (doc?.type === 'Note' && doc.id) {
410
- let s = api.store.getStatuses().find(x => x.noteId === doc.id);
411
- // Third site of the same rule: the note's own origin has to vouch for
412
- // the author before we store one. The owner chose the URL, but the
413
- // document at it still names whoever it likes.
414
- const author = authorOf(doc);
415
- if (!s && author) {
416
- s = {
417
- noteId: doc.id, actor: author, content: sanitizeHtml(doc.content),
418
- published: doc.published, inReplyTo: doc.inReplyTo, kind: 'remote',
419
- };
420
- api.store.addStatus(s);
421
- }
422
- if (s) out.statuses.push(api.status(s));
423
- }
424
- } else if (q) {
425
- const needle = q.toLowerCase();
426
- if (type !== 'accounts') {
427
- const all = api.store.getStatuses();
428
- out.statuses = all
429
- .filter(s => (s.content || '').toLowerCase().includes(needle)).slice(0, 20)
430
- .map(s => api.status(s, { all }));
431
- }
432
- if (type !== 'statuses') out.accounts = await api.accountSearch(q);
433
- }
434
- return send(200, out);
435
- }
436
-
437
- return false;
438
- }
@@ -1,62 +0,0 @@
1
- // oidc-auth.mjs — who may drive the C2S surface. Two proofs are accepted:
2
- // the Mastodon facade's bearer (minted on this machine, so its holder is the
3
- // operator by construction), or a Solid-OIDC token with its DPoP proof,
4
- // which Solid-OIDC requires, verified by the same library the wider
5
- // Solid world uses. The token alone is not enough: the WebID it names must
6
- // be THIS identity's owner, an authorization step the token does not carry.
7
- //
8
- // The verifier is injected so offline tests stub it, and wrapped so the
9
- // library (CJS, older jose) can be replaced without touching any caller.
10
-
11
- export function makeC2sAuth({ agent, masto = null, verifier = null, log = () => {}, scheme = null, mount = '' }) {
12
- let verify = verifier;
13
- const loadVerifier = async () => {
14
- if (!verify) {
15
- const { createSolidTokenVerifier } = await import('@solid/access-token-verifier');
16
- verify = createSolidTokenVerifier();
17
- }
18
- return verify;
19
- };
20
-
21
- return async function authenticate(req, pathname) {
22
- if (masto?.authed(req)) {
23
- return { ok: true, webid: agent.remote?.webId || null, via: 'bearer' };
24
- }
25
- if (!req.headers.authorization) {
26
- return { ok: false, status: 401, error: 'authentication required: a Solid-OIDC token (DPoP) or this agent\'s own bearer' };
27
- }
28
- // Solid-OIDC binds the token to a key and has the client prove it holds
29
- // that key on every request. A token shown without its proof is a token
30
- // anyone who saw it could show.
31
- if (!req.headers.dpop) {
32
- return { ok: false, status: 401, error: 'a Solid-OIDC token must come with its DPoP proof',
33
- headers: { 'www-authenticate': 'DPoP' } };
34
- }
35
- let webid;
36
- try {
37
- const v = await loadVerifier();
38
- // The URL the client signed its proof over. The Host header already
39
- // passed the Authorities firewall, so whichever alias the client used
40
- // (localhost, 127.0.0.1, the named origin) is one this agent answers on;
41
- // the scheme is whichever listener the request arrived on. `pathname` is
42
- // relative to this identity's mount, so a suffix pod folds the mount back
43
- // in — the client signed over the full path it actually requested.
44
- const htu = `${scheme ? scheme.replace(/:$/u, '') : req.socket?.encrypted ? 'https' : 'http'
45
- }://${req.headers.host}${mount}${pathname}`;
46
- ({ webid } = await v(
47
- req.headers.authorization,
48
- { header: req.headers.dpop, method: req.method, url: htu },
49
- ));
50
- } catch (e) {
51
- log(`c2s auth: token rejected — ${e.message}`);
52
- return { ok: false, status: 401, error: `token rejected: ${e.message}` };
53
- }
54
- const owner = agent.remote?.webId;
55
- if (!owner || webid !== owner) {
56
- // Verified is not authorized: a valid token from ANY WebID must not
57
- // post as this actor. (The gap jg10's outbox leaves open.)
58
- return { ok: false, status: 403, error: 'authenticated, but this outbox belongs to its owner alone' };
59
- }
60
- return { ok: true, webid, via: 'oidc' };
61
- };
62
- }
@@ -1,154 +0,0 @@
1
- // streaming.mjs — the Mastodon streaming API over a hand-rolled WebSocket
2
- // server (send-mostly; we answer pings and ignore everything else a client
3
- // frames at us). No dependencies — the handshake is one SHA-1 and text
4
- // frames are trivial to emit. Clients connect to
5
- // ws://host/api/v1/streaming?access_token=…&stream=user
6
- // and receive {stream, event, payload} messages for new statuses and
7
- // notifications the moment the agent learns of them.
8
-
9
- import crypto from 'node:crypto';
10
-
11
- const WS_MAGIC = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
12
-
13
- // One identity's live feed is a handful of that owner's clients, so these are
14
- // generous. Without them a single token could hold every file descriptor open,
15
- // and one client that stops reading would grow its write buffer forever.
16
- const MAX_SOCKETS = 20; // matches the client-token cap
17
- const MAX_BUFFERED_BYTES = 1024 * 1024; // a consumer this far behind is gone
18
-
19
- import { checkRequest } from '../shared/guard.mjs';
20
-
21
- export class Streaming {
22
- constructor({ masto, log = console.log, allowed = null, gate = null, gateOptional = false }) {
23
- this.masto = masto;
24
- this.log = log;
25
- this.allowed = allowed;
26
- this.gate = gate;
27
- // A pod-hosted identity is a public instance: a phone app arrives with its
28
- // bearer and no way to send the operator's header, so there the bearer is
29
- // the credential. Locally the gate stands in front of the socket as well.
30
- this.gateOptional = gateOptional;
31
- this.sockets = new Set(); // entries: { send(string), close() }
32
- }
33
-
34
- // Whether a client may open the live feed, asked of the upgrade request
35
- // alone. Shared with the pod-server front, which completes the handshake
36
- // before any of this runs and so must answer by closing rather than refusing.
37
- authorizeUpgrade(req, url) {
38
- const proto = String(req.headers['sec-websocket-protocol'] || '').split(',')[0].trim();
39
- const token = url.searchParams.get('access_token') || proto;
40
- const hasToken = this.masto.tokens().includes(token);
41
- // WebSockets are exempt from CORS. A live bearer is the credential — a
42
- // web client on its own origin connects the way it does to any Mastodon
43
- // server — so with one, only the Host check stands (it is what stops DNS
44
- // rebinding); without one the full Host/Origin firewall applies.
45
- const bad = this.allowed ? checkRequest(req, this.allowed, { ignoreOrigin: hasToken }) : null;
46
- if (bad) return `streaming refused: ${bad}`;
47
- // A gate token covers every other route; an upgrade that skipped it was a
48
- // hole in a control the operator had switched on. A client bearer is the
49
- // other way in — a phone app has no way to send the operator's header.
50
- if (this.gate && !this.gate.upgradeOk(req) && !(this.gateOptional && hasToken)) {
51
- return 'streaming refused: no gate token';
52
- }
53
- if (!hasToken) return 'streaming refused: not a live client token';
54
- if (this.sockets.size >= MAX_SOCKETS) return 'streaming refused: too many live connections';
55
- return null;
56
- }
57
-
58
- // Adopt a WebSocket somebody else finished the handshake for — the pod
59
- // server owns the socket, and hands over an object that already frames.
60
- adopt(ws) {
61
- const entry = {
62
- send: (str) => {
63
- // A consumer that stops reading must not keep costing memory.
64
- if ((ws.bufferedAmount ?? 0) > MAX_BUFFERED_BYTES) {
65
- this.sockets.delete(entry);
66
- try { ws.close(); } catch { /* already gone */ }
67
- return;
68
- }
69
- ws.send(str);
70
- },
71
- close: () => ws.close(),
72
- };
73
- this.sockets.add(entry);
74
- const drop = () => this.sockets.delete(entry);
75
- ws.on('close', drop);
76
- ws.on('error', drop);
77
- return entry;
78
- }
79
-
80
- // Let go of every client: the server is stopping, not the connection failing.
81
- stop() {
82
- for (const s of this.sockets) { try { s.close(); } catch { /* already gone */ } }
83
- this.sockets.clear();
84
- }
85
-
86
- // Attach the upgrade handler to an http.Server.
87
- attach(server) {
88
- server.on('upgrade', (req, socket) => {
89
- const url = new URL(req.url, 'http://localhost');
90
- if (!url.pathname.startsWith('/api/v1/streaming')) { socket.destroy(); return; }
91
- // WebSockets are exempt from CORS, so the Host/Origin firewall is the
92
- // only thing standing between a visited page and the live feed.
93
- const refusal = this.authorizeUpgrade(req, url);
94
- if (refusal) {
95
- this.log(refusal);
96
- socket.write(refusal.includes('refused:') && !refusal.includes('token')
97
- ? 'HTTP/1.1 403 Forbidden\r\n\r\n'
98
- : 'HTTP/1.1 401 Unauthorized\r\n\r\n');
99
- socket.destroy();
100
- return;
101
- }
102
- const proto = String(req.headers['sec-websocket-protocol'] || '').split(',')[0].trim();
103
- const accept = crypto.createHash('sha1')
104
- .update(req.headers['sec-websocket-key'] + WS_MAGIC).digest('base64');
105
- socket.write('HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\n'
106
- + `Sec-WebSocket-Accept: ${accept}\r\n`
107
- + (proto ? `Sec-WebSocket-Protocol: ${proto}\r\n` : '') + '\r\n');
108
- socket.setNoDelay(true);
109
- const entry = {
110
- send: (str) => {
111
- // A consumer that stops reading must not keep costing memory.
112
- if (socket.writableLength > MAX_BUFFERED_BYTES) {
113
- this.sockets.delete(entry);
114
- try { socket.destroy(); } catch { /* already gone */ }
115
- return;
116
- }
117
- socket.write(this.frame(str));
118
- },
119
- close: () => { try { socket.end(); } catch { /* already gone */ } },
120
- };
121
- this.sockets.add(entry);
122
- const drop = () => this.sockets.delete(entry);
123
- socket.on('close', drop);
124
- socket.on('error', drop);
125
- socket.on('data', (buf) => {
126
- if (buf.length && (buf[0] & 0x0f) === 0x9) { // ping → pong
127
- try { socket.write(Buffer.from([0x8a, 0x00])); } catch { drop(); }
128
- }
129
- if (buf.length && (buf[0] & 0x0f) === 0x8) { drop(); try { socket.end(); } catch {} } // close
130
- });
131
- });
132
- }
133
-
134
- // Server→client unmasked text frame.
135
- frame(str) {
136
- const payload = Buffer.from(str);
137
- let header;
138
- if (payload.length < 126) header = Buffer.from([0x81, payload.length]);
139
- else if (payload.length < 65536) {
140
- header = Buffer.alloc(4); header[0] = 0x81; header[1] = 126; header.writeUInt16BE(payload.length, 2);
141
- } else {
142
- header = Buffer.alloc(10); header[0] = 0x81; header[1] = 127; header.writeBigUInt64BE(BigInt(payload.length), 2);
143
- }
144
- return Buffer.concat([header, payload]);
145
- }
146
-
147
- broadcast(event, payloadObj, streams = ['user']) {
148
- if (!this.sockets.size) return;
149
- const message = JSON.stringify({ stream: streams, event, payload: JSON.stringify(payloadObj) });
150
- for (const s of this.sockets) {
151
- try { s.send(message); } catch { this.sockets.delete(s); }
152
- }
153
- }
154
- }