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,633 +0,0 @@
1
- // activities.mjs — one handler per inbound activity type. Each is reached
2
- // from Intake.handle() after the sender passed the door checks, and returns a
3
- // rejection reason or nothing.
4
-
5
- import { dropFollower } from '../store.mjs';
6
- import { emojisOf, pollOf, isContentType, trimActivity, reactionOf, quoteOf, quotePolicyOf, authorOf } from './activity.mjs';
7
-
8
- const idOf = (v) => (typeof v === 'string' ? v : v?.id);
9
-
10
- // §7.6 Add / §7.9 Remove. The side effect would be to add or remove the object
11
- // to/from the collection named in `target` — but only a collection we own AND
12
- // that the sender is authorised to modify. No remote is granted write to our
13
- // collections (membership is Follow/Undo, pins are ours to set), so there is
14
- // nothing an inbound Add or Remove may change here. It is a valid activity,
15
- // not garbage: acknowledge it, make no change, and never dead-letter it.
16
- export function onAddRemove(intake, activity, actor) {
17
- const target = typeof activity.target === 'string' ? activity.target : activity.target?.id;
18
- const ours = target && [intake.urls.followers, intake.urls.following, intake.urls.featured]
19
- .filter(Boolean).includes(target);
20
- intake.log(ours
21
- ? `${activity.type} from ${actor} targets our ${target} — no remote may modify it; acknowledged`
22
- : `${activity.type} from ${actor} targets ${target || 'no collection of ours'}; nothing here to change`);
23
- return; // accepted, no side effect
24
- }
25
-
26
- export async function onFollow(intake, activity, actor, { trusted = false } = {}) {
27
- const doc = await intake.fetchAP(actor); // origin must vouch for the actor
28
- if (!doc) return `actor fetch failed (${actor})`;
29
- if (doc.id !== actor) return `actor id mismatch (${actor} vs ${doc.id})`;
30
- if (!doc.inbox) return `actor has no inbox (${actor})`;
31
- const contacts = intake.store.getContacts();
32
- const existing = contacts.followers.find(f => f.actor === actor);
33
- // NOTHING binds a delivered Follow to the actor it names. LDN bodies carry
34
- // no signature, and unlike Create, Delete and Update there is no object at
35
- // the origin to re-fetch and compare — dereferencing the actor proves only
36
- // that the actor EXISTS. So anyone at all could Append a Follow naming
37
- // anyone at all, and we would sign an Accept, deliver it to that person,
38
- // and send them everything published from then on.
39
- //
40
- // Until deliveries terminate somewhere their signature survives, a follow
41
- // we cannot verify is a REQUEST, waiting in the same queue a gated group
42
- // uses. The requester's client shows "Requested", which is the ordinary
43
- // locked-account state that manuallyApprovesFollowers tells it to expect.
44
- // `autoAcceptFollows: true` in config restores the old behaviour.
45
- // A GROUP is left alone: `approveJoins: false` is its operator saying, in
46
- // as many words, that anyone may join, and mute/eject are the remedy there.
47
- // A person has no such setting, so this is their default.
48
- //
49
- // A gateway-verified Follow (trust mode) is no longer unverifiable — the
50
- // door proved the sender — so it does not need the OK that unverifiability
51
- // alone demanded. An explicit `approveJoins` still holds: verified or not,
52
- // the operator asked to see joins.
53
- const unverifiedNeedsOk = intake.config.kind !== 'group' && !intake.config.autoAcceptFollows && !trusted;
54
- const mustApprove = intake.config.approveJoins || unverifiedNeedsOk;
55
- if (mustApprove && !existing) {
56
- const reqs = intake.store.getRequests();
57
- if (!reqs.some(r => r.actor === actor)) {
58
- reqs.unshift({
59
- actor, inbox: doc.inbox, sharedInbox: doc.endpoints?.sharedInbox,
60
- activity: trimActivity(activity), at: new Date().toISOString(),
61
- });
62
- intake.store.setRequests(reqs.slice(0, 500));
63
- intake.store.addNotification({ type: 'follow-request', actor });
64
- await intake.republish({ pending: true });
65
- }
66
- intake.log(`join requested: ${actor}`);
67
- return;
68
- }
69
- if (existing) {
70
- // Deliberately NOT updating followId. An inbound Follow is unverifiable —
71
- // that is what the queue above exists for — so letting one rewrite the id
72
- // of a follower we already hold hands an attacker the exact value onUndo
73
- // matches on: POST a Follow naming any follower in the published
74
- // collection, then POST an Undo naming the id you just chose, and they are
75
- // gone permanently. A genuine refollow needs nothing from us but the
76
- // Accept below, which is idempotent.
77
- } else {
78
- // A Bluesky member who bridges later arrives here as a second, different
79
- // actor: the bridge follows on their behalf from bsky.brid.gy/ap/<did>,
80
- // while the native join is recorded under bsky.app/profile/<did>. Left
81
- // alone that is one person listed twice, carried twice, and ejectable
82
- // only half at a time. The bridged record supersedes the native one —
83
- // it reaches the fediverse side, which the native one never could.
84
- const bridgedDid = /^https:\/\/bsky\.brid\.gy\/ap\/(did:[^/]+)$/.exec(actor)?.[1];
85
- if (bridgedDid) {
86
- const before = contacts.followers.length;
87
- contacts.followers = contacts.followers.filter(f => f.bsky?.did !== bridgedDid);
88
- if (contacts.followers.length < before) {
89
- intake.log(`bluesky member ${bridgedDid} is bridged now — the native record gives way to it`);
90
- }
91
- }
92
- contacts.followers.push({
93
- actor, inbox: doc.inbox, sharedInbox: doc.endpoints?.sharedInbox, followId: activity.id,
94
- ...(bridgedDid ? { bsky: { did: bridgedDid, bridged: true } } : {}),
95
- });
96
- intake.store.setContacts(contacts);
97
- intake.store.addNotification({ type: 'follow', actor });
98
- await intake.republish({ followers: true });
99
- intake.log(`new follower: ${actor}`);
100
- }
101
- const { acceptActivity } = await import('../wire.mjs');
102
- await intake.deliverer.deliver(doc.inbox,
103
- acceptActivity({ urls: intake.urls, followActivity: activity, serial: intake.serial++ }));
104
- intake.log(`Accept sent → ${doc.inbox}`);
105
- }
106
-
107
- export async function onUndo(intake, activity, actor, { trusted = false } = {}) {
108
- const inner = activity.object && typeof activity.object === 'object' ? activity.object : null;
109
- if (inner?.type === 'Like' || inner?.type === 'Announce' || inner?.type === 'EmojiReact') return onUndoReaction(intake, inner, actor, { trusted });
110
- // AS2 allows `object` to be a bare IRI, and that IRI is exactly the Follow
111
- // id we stored. Reading `.type` off a string gives undefined, so the whole
112
- // Undo was dropped — silently, since handle() reads that as handled, so no
113
- // dead letter was kept and the item was DELETEd. The follower stayed, we
114
- // kept delivering to them, and their server had recorded the unfollow as
115
- // done and would never resend. Only a TYPED non-Follow is not ours.
116
- if (typeof activity.object === 'object' && activity.object?.type
117
- && activity.object.type !== 'Follow') return;
118
- // And it must NAME something. Widening the type test to admit a bare IRI
119
- // also admitted `object: undefined`, `null` and `{}` — which land on the
120
- // no-followId carve-out below and evict, which is the very hole the
121
- // followId check was added to close. An Undo that identifies nothing is
122
- // not an Undo of ours.
123
- const named = typeof activity.object === 'string' ? activity.object : activity.object?.id;
124
- // A gateway-verified Undo need not name a stored id — the door proved the
125
- // sender, so an Undo{Follow} that names us as its object is enough. An
126
- // unverified one must still identify something (the eviction-hole guard).
127
- if (!named && !trusted) return;
128
- // Deliveries arrive unordered: an Undo may land AFTER the refollow it
129
- // predates. It names the Follow id it revokes — only honor it when it
130
- // matches the follow we currently hold for that actor.
131
- const undoneId = typeof activity.object === 'string' ? activity.object : activity.object?.id;
132
- const contacts = intake.store.getContacts();
133
- const rec = contacts.followers.find(f => f.actor === actor);
134
- // Withdrawing a request that was never answered: drop it, or it sits in the
135
- // operator's queue forever asking about someone who left.
136
- if (!rec) {
137
- const reqs = intake.store.getRequests();
138
- const pending = reqs.find(r => r.actor === actor);
139
- if (pending) {
140
- // Bound the same way the follower eviction below is, and for the same
141
- // reason: `actor` is a field in an unsigned body, so without a match
142
- // anyone could withdraw anyone else's waiting request. The victim's
143
- // server believes the Follow is still pending and never resends, so
144
- // they simply never get followed and nobody sees why.
145
- const theirs = pending.activity?.id;
146
- if (!trusted && (!theirs || named !== theirs)) {
147
- intake.log(`Undo from ${actor} does not name the request we hold — ignored`);
148
- return;
149
- }
150
- intake.store.setRequests(reqs.filter(r => r.actor !== actor));
151
- await intake.republish({ pending: true });
152
- intake.log(`join request withdrawn: ${actor}`);
153
- }
154
- return;
155
- }
156
- // An Undo must NAME the Follow it revokes, and name the one we hold.
157
- //
158
- // The follow id is the ONLY thing binding an Undo to the follower. LDN
159
- // bodies carry no signature, and unlike every other inbound type this path
160
- // dereferences nothing, so there is no origin to disagree. Matching works
161
- // because the id was chosen by their server and delivered in a Follow we
162
- // accepted: we publish the followers collection, but never the ids.
163
- //
164
- // Which means a record with NO id cannot be matched at all — and the
165
- // carve-out that used to let those through turned "we cannot tell" into
166
- // "anyone may evict". reconcileFollowers writes exactly such records when a
167
- // restored machine recovers its followers from the pod, so after a restore
168
- // every follower could be removed by one unauthenticated POST, permanently:
169
- // dropFollower leaves a mark and the next reconcile will not bring them
170
- // back, their server recorded no unfollow so it never resends, and neither
171
- // side has anything to notice.
172
- //
173
- // Unmatchable is refused now. The cost is a follower who really did leave
174
- // staying on the list until the operator ejects them, which is the right way
175
- // round: `eject` is one command, and the alternative was silent, permanent,
176
- // and available to anyone.
177
- // A gateway-verified Undo carries the sender's proof, so it is honored on
178
- // its own — the followId match below exists only because an UNVERIFIED Undo
179
- // is otherwise unbindable. A verified one needs no such crutch.
180
- if (trusted) {
181
- dropFollower(contacts, actor, 'undo-follow');
182
- intake.store.setContacts(contacts);
183
- await intake.republish({ followers: true });
184
- intake.log(`unfollowed by ${actor} (gateway-verified)`);
185
- return;
186
- }
187
- if (!rec.followId) {
188
- intake.log(`Undo from ${actor} cannot be matched — this follower was `
189
- + `${rec.recovered ? 'recovered from the pod' : 'recorded before follow ids were kept'}, `
190
- + `so its follow id is unknown. Ignored; \`fedipod eject ${actor}\` if they did leave.`);
191
- return;
192
- }
193
- if (undoneId !== rec.followId) {
194
- intake.log(`Undo from ${actor} does not name the follow we hold `
195
- + `(revokes ${undoneId || 'nothing'}, current is ${rec.followId}) — ignored`);
196
- return;
197
- }
198
- dropFollower(contacts, actor, 'undo-follow');
199
- intake.store.setContacts(contacts);
200
- await intake.republish({ followers: true });
201
- intake.log(`unfollowed by ${actor}`);
202
- }
203
-
204
- export async function onCreate(intake, activity, actor, { trusted = false } = {}) {
205
- const objectId = typeof activity.object === 'string' ? activity.object : activity.object?.id;
206
- if (!objectId) return 'Create without object id';
207
- if (intake.store.isBlocked(objectId)) return `blocked domain (${objectId})`;
208
- if (!intake.sameIdentity(objectId, actor)) return `object/actor identity mismatch (${objectId})`;
209
- // The delivered copy is untrusted for CONTENT, but its addressing is
210
- // enough to decide whether to bother fetching the origin's copy.
211
- const envelope = typeof activity.object === 'object' ? { ...activity, ...activity.object } : activity;
212
- if (!intake.concernsUs(envelope, actor)) return `not addressed to us (${objectId})`;
213
- // The check onAnnounce has had all along. A re-delivered Create — a remote
214
- // retry, a group fan-out, or our own sweep seeing an item whose DELETE was
215
- // refused — cost a fresh signed GET to the origin and rewrote the private
216
- // RDF note every time, because addStatus only dedupes AFTER the deref.
217
- // Gated around the INGEST alone: a group must still reach amplify below,
218
- // which is separately idempotent on announcedAt.
219
- // Only a status that came from an INGEST counts as already done. TagFeed
220
- // writes a bare `kind:'tag'` row straight into the index — no pod RDF note,
221
- // no mention notification, no replies-collection entry — so treating that
222
- // as ingested loses all three when the same note is then delivered to us.
223
- const ingested = intake.store.getStatuses()
224
- .some(x => x.noteId === objectId && (x.kind === 'timeline' || x.kind === 'mention'));
225
- if (!ingested) {
226
- // A verified delivery that carries the note inline is read from that
227
- // copy: the door checked the sender's signature over these very bytes,
228
- // and the receipt vouches for this actor. That is the only way a direct
229
- // or followers-only post can ever be read — it lives in the sender's
230
- // owner-only container, and fetching it from there answers 401 to
231
- // everyone. An unverified delivery still fetches, as it always did.
232
- const inline = trusted && typeof activity.object === 'object' && activity.object?.id === objectId
233
- ? activity.object : null;
234
- const rejected = await intake.ingestNote(objectId, actor, { inline });
235
- if (rejected) return rejected;
236
- }
237
- // A group carries its members' posts onward. Only reached from Create, so an
238
- // inbound Announce is never re-announced. The activity is passed through
239
- // untouched — FEP-1b12 wants the original wrapped, not a summary of it.
240
- if (intake.config.kind === 'group') await intake.amplify(objectId, { activity });
241
- }
242
-
243
- // A boost: ingest the boosted note when the booster is someone we follow —
244
- // that's what following means, their boosts widen the timeline. Anything
245
- // else is unsolicited and only logged.
246
- // A group we follow announces a Delete: the carrier moderating away a post
247
- // it carried. Honored only within what the carry itself established — the
248
- // announcer is a group we follow AND the post reached us via that same
249
- // group — so no new party is trusted and nothing is dereferenced. Our own
250
- // posts are never removed by anyone's moderation.
251
- export async function onAnnouncedDelete(intake, actor, del) {
252
- const followed = intake.store.getContacts().following.some(f => f.actor === actor && f.accepted);
253
- if (!followed) { intake.log(`announced Delete from unfollowed ${actor} — ignored`); return; }
254
- const targetId = typeof del.object === 'string' ? del.object : del.object?.id;
255
- if (!targetId) return 'announced Delete without an object';
256
- const s = intake.store.getStatuses().find(x => x.noteId === targetId);
257
- if (!s || s.kind === 'post' || s.via !== actor) return;
258
- await intake.forget(s);
259
- intake.log(`moderated away by ${actor}: ${targetId}`);
260
- }
261
-
262
- export async function onAnnounce(intake, activity, actor, objectId) {
263
- if (!objectId) return 'Announce without object id';
264
- const followed = intake.store.getContacts().following.some(f => f.actor === actor && f.accepted);
265
- if (!followed) { intake.log(`Announce from unfollowed ${actor} — ignored`); return; }
266
- if (intake.store.isBlocked(objectId)) return `blocked domain (${objectId})`;
267
- const existing = intake.store.getStatuses().find(s => s.noteId === objectId);
268
- if (existing) {
269
- // Known, but possibly as a lesser kind — a stranger's mention, a tag or
270
- // search mirror — none of which the home timeline shows. A carry from
271
- // someone we follow is exactly what promotes it there.
272
- if (!['timeline', 'post'].includes(existing.kind)) {
273
- intake.store.updateStatus(objectId, { kind: 'timeline', via: actor });
274
- intake.log(`promoted to timeline (carried by ${actor}): ${objectId}`);
275
- }
276
- return;
277
- }
278
- return intake.ingestNote(objectId, actor, { via: actor });
279
- }
280
-
281
- // Undo{Like} and Undo{Announce}: the sender unsaying a favourite or a boost.
282
- // The Like it unsays was recorded on the sender's word alone, so its undoing
283
- // is taken the same way: the notification it left goes. A boost unsaid by
284
- // someone we follow takes the post they carried off the timeline — the carry
285
- // was accepted on that follow, and the retraction is honoured on it.
286
- export async function onUndoReaction(intake, inner, actor, { trusted = false } = {}) {
287
- const innerActor = typeof inner.actor === 'string' ? inner.actor : inner.actor?.id;
288
- if (innerActor && innerActor !== actor) return; // one cannot unsay another's
289
- const wrapped = inner.object;
290
- const target = (wrapped && typeof wrapped === 'object'
291
- && (wrapped.type === 'Create' || wrapped.type === 'Update')) ? wrapped.object : wrapped;
292
- const objectId = typeof target === 'string' ? target : target?.id;
293
- if (!objectId) return;
294
- if (objectId.startsWith(intake.urls.notes)) {
295
- // An emoji taken back: the one reaction it names, by that sender.
296
- const reaction = reactionOf(inner);
297
- if (reaction) {
298
- const gone = intake.store.removeNotifications(n => n.type === 'reaction' && n.actor === actor
299
- && n.noteId === objectId && n.emoji === reaction.emoji);
300
- if (gone) intake.log(`reaction ${reaction.emoji} withdrawn by ${actor} on ${objectId}`);
301
- return;
302
- }
303
- if (inner.type === 'EmojiReact') return;
304
- const type = inner.type === 'Like' ? 'favourite' : 'reblog';
305
- const gone = intake.store.removeNotifications(n => n.type === type && n.actor === actor && n.noteId === objectId);
306
- if (gone) intake.log(`${inner.type} withdrawn by ${actor} on ${objectId}`);
307
- return;
308
- }
309
- if (inner.type !== 'Announce') return;
310
- const followed = trusted || intake.store.getContacts().following.some(f => f.actor === actor && f.accepted);
311
- if (!followed) return;
312
- const s = intake.store.getStatuses().find(x => x.noteId === objectId);
313
- if (!s || s.kind === 'post' || s.via !== actor) return;
314
- await intake.forget(s);
315
- intake.log(`boost withdrawn by ${actor}: ${objectId} left the timeline`);
316
- }
317
-
318
- // Block: the sender wants nothing more to do with this account. Mastodon
319
- // drops the follow both ways on receipt, and so does this — on a delivery
320
- // the door verified. A Block carries nothing to check and names nothing we
321
- // hold, so one taken from the public-append inbox unverified would let
322
- // anybody evict any follower, the hole the Undo handler closes with the
323
- // follow id. Unverified: logged, nothing else.
324
- export async function onBlock(intake, activity, actor, { trusted = false } = {}) {
325
- const objectId = typeof activity.object === 'string' ? activity.object : activity.object?.id;
326
- if (objectId !== intake.urls.actor) return;
327
- if (!trusted) { intake.log(`Block from ${actor} arrived unverified — ignored`); return; }
328
- const contacts = intake.store.getContacts();
329
- const wasFollower = contacts.followers.some(f => f.actor === actor);
330
- const wasFollowing = contacts.following.some(f => f.actor === actor);
331
- if (wasFollower) dropFollower(contacts, actor, 'blocked-us');
332
- contacts.following = contacts.following.filter(f => f.actor !== actor);
333
- intake.store.setContacts(contacts);
334
- const reqs = intake.store.getRequests();
335
- const left = reqs.filter(r => r.actor !== actor);
336
- if (left.length !== reqs.length) intake.store.setRequests(left);
337
- await intake.republish({ followers: wasFollower, following: wasFollowing, pending: true });
338
- const what = [wasFollower && 'their follow of us', wasFollowing && 'our follow of them'].filter(Boolean).join(' and ');
339
- intake.log(`blocked by ${actor}: ${what || 'no relationship'} dropped`);
340
- }
341
-
342
- // Mastodon sends these constantly; ignoring them left deleted posts standing
343
- // for good. Two guards, because a forged Delete would otherwise erase anyone's
344
- // content: it must come from the object's own origin, and the object must
345
- // really be gone there. An origin we cannot reach is a retry, never a delete.
346
- export async function onDelete(intake, activity, actor) {
347
- const objectId = typeof activity.object === 'string' ? activity.object : activity.object?.id;
348
- if (!objectId) return 'Delete without object id';
349
- if (!intake.sameIdentity(objectId, actor)) return `Delete crosses identities (${objectId})`;
350
- // `objectId === actor` is NOT evidence we care: it is true of EVERY account
351
- // deletion, and Mastodon broadcasts those constantly. Taking it as known
352
- // meant a signed dereference to a stranger's server for each one.
353
- if (!intake.known(objectId)) return; // nothing of ours to remove
354
- const gone = await intake.isGone(objectId);
355
- if (gone === null) throw new Error(`cannot confirm ${objectId} is gone — will retry`);
356
- if (!gone) return `Delete for something still published (${objectId})`;
357
-
358
- if (objectId === actor) { // the account itself
359
- const contacts = intake.store.getContacts();
360
- dropFollower(contacts, actor, 'account-deleted');
361
- contacts.following = contacts.following.filter(f => f.actor !== actor);
362
- intake.store.setContacts(contacts);
363
- // One publish for the lot. Each forget() used to run its own
364
- // unrecordOutbox, and each of those republished the outbox — so a group
365
- // that had carried M of this actor's posts paid M full page sweeps for a
366
- // single inbox item. The Undo deliveries stay per-Announce, because each
367
- // Announce needs its own; only the pod write is collected.
368
- const retracted = [];
369
- for (const s of intake.store.getStatuses().filter(s => s.actor === actor)) {
370
- await intake.forget(s, { collect: retracted });
371
- }
372
- if (retracted.length) {
373
- const gone = new Set(retracted);
374
- await intake.publisher.unrecordOutbox(i => gone.has(i?.id));
375
- }
376
- // Both: an account deletion drops them from followers AND following.
377
- await intake.republish({ followers: true, following: true });
378
- intake.log(`account deleted upstream: ${actor}`);
379
- return;
380
- }
381
- const s = intake.store.getStatuses().find(x => x.noteId === objectId);
382
- if (s) await intake.forget(s);
383
- if (s && intake.onCarriedGone) {
384
- await intake.onCarriedGone({ noteId: objectId, actor }).catch(e => intake.log(`carried gone ${objectId}: ${e.message}`));
385
- }
386
- intake.log(`deleted upstream: ${objectId}`);
387
- }
388
-
389
- // An edited post, or a changed profile. Verified the only way we can: by
390
- // refetching at the origin and believing that, not the delivered copy.
391
- export async function onUpdate(intake, activity, actor) {
392
- const objectId = typeof activity.object === 'string' ? activity.object : activity.object?.id;
393
- if (!objectId) return 'Update without object id';
394
- if (!intake.sameIdentity(objectId, actor)) return `Update crosses identities (${objectId})`;
395
- if (objectId === actor) { // display name, avatar, bio
396
- // The same guard onDelete has, for the same reason: the inbox is
397
- // public-Append, so without it anyone can name any host and make us spend
398
- // a signed GET on it — and because the failure below THROWS rather than
399
- // returning a rejection, one planted item buys five of them, plus five
400
- // pod reads and the head of the inbox held for five sweeps.
401
- if (!intake.known(actor)) return; // nothing of ours to update
402
- const doc = await intake.fetchAP(actor);
403
- if (!doc) throw new Error(`cannot refetch ${actor} — will retry`);
404
- intake.store.cacheActor(actor, doc); // fetchAP caches Persons; Groups too
405
- intake.log(`profile updated: ${actor}`);
406
- return;
407
- }
408
- const s = intake.store.getStatuses().find(x => x.noteId === objectId);
409
- if (!s) return; // not one we hold
410
- const note = await intake.fetchAP(objectId);
411
- if (!note) throw new Error(`cannot refetch ${objectId} — will retry`);
412
- if (note.id !== objectId || !isContentType(note.type)) return `object not verifiable content (${objectId}, ${note.type})`;
413
- const { attachmentsOf, titledContent } = await import('../wire.mjs');
414
- const content = titledContent(note);
415
- const attachments = attachmentsOf(note);
416
- const freshPoll = pollOf(note);
417
- const freshEmojis = emojisOf(note);
418
- // A quote's authorization arrives on an Update, once the quoted author
419
- // has answered: the quote is checked again, and the policy follows the edit.
420
- const quote = quoteOf(note);
421
- const quoteState = quote ? await intake.quoteStateOf(note, quote) : null;
422
- const quotePolicy = quotePolicyOf(note, s.actor);
423
- intake.store.updateStatus(objectId, {
424
- content, ...(attachments.length ? { attachments } : {}),
425
- emojis: freshEmojis.length ? freshEmojis : undefined,
426
- quote: quote || undefined, quoteState: quoteState || undefined,
427
- quotePolicy: quotePolicy || undefined,
428
- // The edit's own stamp when the note carries one; tallies and the
429
- // content warning follow the edit too. A poll refresh keeps our vote.
430
- editedAt: note.updated || new Date().toISOString(),
431
- spoiler: note.summary ? String(note.summary).replace(/<[^>]*>/g, '') : undefined,
432
- ...(freshPoll ? {
433
- poll: { ...freshPoll, voted: !!s.poll?.voted, ownVotes: s.poll?.ownVotes || [] },
434
- } : {}),
435
- });
436
- if (intake.onCarriedEdit) {
437
- await intake.onCarriedEdit({ noteId: objectId, actor, note }).catch(e => intake.log(`carried edit ${objectId}: ${e.message}`));
438
- }
439
- intake.log(`edited upstream: ${objectId}`);
440
- }
441
-
442
- // The other answer to a Follow, and it was dropped on the floor. Their server
443
- // has recorded that we do not follow them; ours went on saying we did, and
444
- // published it — so the two disagreed permanently, and a retry would never
445
- // come because as far as they are concerned the question was answered.
446
- //
447
- // It has to answer the Follow we actually SENT. Only the type was checked, so
448
- // one Append per account you follow — from anyone, naming no particular
449
- // follow — severed every one of them at once, and silently: their server
450
- // never hears about it, so nothing ever retries and nothing looks wrong until
451
- // the timeline goes quiet. `followActivity` is stored by followActor
452
- // (lib/social.mjs) for exactly this kind of comparison.
453
- export async function onReject(intake, activity, actor, { trusted = false } = {}) {
454
- const asked = quoteAsked(intake, activity);
455
- if (asked) return onQuoteAnswered(intake, asked, activity, actor, 'Reject');
456
- if (activity.object?.type && activity.object.type !== 'Follow') return;
457
- const contacts = intake.store.getContacts();
458
- const rec = contacts.following.find(f => f.actor === actor);
459
- if (!rec) return; // nothing of ours to undo
460
- const named = typeof activity.object === 'string' ? activity.object : activity.object?.id;
461
- const ours = rec.followActivity?.id;
462
- // A gateway receipt bound to this actor is the other way to believe it —
463
- // the door checked a signature and said whose (see receiptVouchesFor).
464
- if (!trusted) {
465
- if (!ours) {
466
- intake.log(`Reject from ${actor}: no follow id on record to match it against — ignored`);
467
- return;
468
- }
469
- if (named !== ours) {
470
- intake.log(`Reject from ${actor} answers ${named || 'nothing'}, not the follow we sent — ignored`);
471
- return;
472
- }
473
- }
474
- contacts.following = contacts.following.filter(f => f.actor !== actor);
475
- intake.store.setContacts(contacts);
476
- await intake.republish({ following: true, pending: true });
477
- intake.log(`follow rejected by ${actor}`);
478
- }
479
-
480
- // Someone we follow has moved. Their server will stop delivering from the old
481
- // actor, so without this we keep an entry that can never produce another post
482
- // and never learn where they went. The new account is not followed
483
- // automatically — that is a Follow only the owner should send — but it is
484
- // recorded and raised, so it can be acted on.
485
- export async function onMove(intake, activity, actor) {
486
- const target = typeof activity.target === 'string' ? activity.target : activity.target?.id;
487
- if (!target) return 'Move without a target';
488
- const contacts = intake.store.getContacts();
489
- const rec = contacts.following.find(f => f.actor === actor);
490
- if (!rec) return; // not someone we follow
491
- // Believed only if the actor we follow says so at its OWN origin: a Move is
492
- // otherwise a redirect anyone could Append.
493
- const doc = await intake.fetchAP(actor);
494
- if (!doc) throw new Error(`cannot confirm ${actor} moved — will retry`);
495
- const movedTo = typeof doc.movedTo === 'string' ? doc.movedTo : doc.movedTo?.id;
496
- if (movedTo !== target) return `Move not corroborated by ${actor} (says ${movedTo || 'nothing'})`;
497
- rec.movedTo = target;
498
- intake.store.setContacts(contacts);
499
- intake.store.addNotification({ type: 'move', actor, target });
500
- intake.log(`${actor} moved to ${target} — follow the new account to keep seeing them`);
501
- }
502
-
503
- export async function onAccept(intake, activity, actor, { trusted = false } = {}) {
504
- const asked = quoteAsked(intake, activity);
505
- if (asked) return onQuoteAnswered(intake, asked, activity, actor, 'Accept');
506
- const contacts = intake.store.getContacts();
507
- const rec = contacts.following.find(f => f.actor === actor);
508
- // It has to answer the Follow we actually sent. followActor stores that
509
- // activity for the later Undo, so the id is here to compare against; without
510
- // the check any Accept from an actor we happen to follow flips the flag,
511
- // including one answering a Follow we never made.
512
- const named = typeof activity.object === 'string' ? activity.object : activity.object?.id;
513
- const ours = rec?.followActivity?.id;
514
- // `named &&` used to be part of this, so an Accept naming NOTHING sailed
515
- // past — which is the easy one to send, and it marks a request to a locked
516
- // account as accepted when it is still sitting in their queue.
517
- if (ours && named !== ours && !trusted) {
518
- intake.log(`Accept from ${actor} answers ${named || 'nothing'}, not the follow we sent — ignored`);
519
- return;
520
- }
521
- if (rec && !rec.accepted) {
522
- rec.accepted = true;
523
- intake.store.setContacts(contacts);
524
- await intake.republish({ following: true, pending: true });
525
- intake.log(`follow accepted by ${actor}`);
526
- }
527
- }
528
-
529
- // --- quotes (FEP-044f) ---
530
-
531
- // The post of ours whose quote request this Accept or Reject answers, if any.
532
- // Matched on the request id we sent, which only the quoted author was given.
533
- function quoteAsked(intake, activity) {
534
- const named = idOf(activity.object);
535
- if (!named) return null;
536
- return intake.store.getStatuses().find(x => x.kind === 'post' && x.quoteRequest?.id === named) || null;
537
- }
538
-
539
- // The quoted author's answer. A yes carries their authorization, a document
540
- // at their origin naming our post and theirs — fetched and compared, not
541
- // believed from the envelope; then the note says so to everyone who has it.
542
- // A no takes the quote off the note.
543
- async function onQuoteAnswered(intake, s, activity, actor, type) {
544
- if (actor !== s.quoteRequest?.actor) {
545
- intake.log(`${type} of our quote request from ${actor}, who is not ${s.quoteRequest?.actor} — ignored`);
546
- return;
547
- }
548
- if (type === 'Reject') {
549
- if (s.quoteState === 'rejected') return;
550
- await intake.publisher.withdrawQuote(s);
551
- return;
552
- }
553
- if (s.quoteState === 'accepted') return;
554
- const resultId = idOf(activity.result);
555
- if (!resultId || !intake.sameIdentity(resultId, actor)) return `Accept of a quote request carries no authorization at ${actor}`;
556
- const auth = await intake.fetchAP(resultId);
557
- if (!auth) throw new Error(`cannot fetch quote authorization ${resultId} — will retry`);
558
- const ok = auth.id === resultId && auth.type === 'QuoteAuthorization'
559
- && [].concat(auth.attributedTo || []).map(idOf).includes(actor)
560
- && idOf(auth.interactingObject) === s.noteId && idOf(auth.interactionTarget) === s.quote;
561
- if (!ok) return `quote authorization ${resultId} does not say ${s.noteId} may quote ${s.quote}`;
562
- await intake.publisher.authorizeQuote(s, resultId);
563
- }
564
-
565
- // Someone asks to quote one of our posts. Their post is read at its origin,
566
- // like every note that arrives — it has to be theirs, and has to quote ours.
567
- // A post the world can read may be quoted by anyone; a followers-only or
568
- // direct one by nobody. The answer is an Accept carrying our authorization,
569
- // written beside the post, or a Reject. The quoting post is then kept the
570
- // way a mention of ours is, under the one notification a quote raises.
571
- export async function onQuoteRequest(intake, activity, actor) {
572
- const objectId = idOf(activity.object);
573
- const instrumentId = idOf(activity.instrument);
574
- if (!objectId || !instrumentId) return 'QuoteRequest without object or instrument';
575
- // A followers-only post lives in the private container; a follower may
576
- // know its id and ask. It is ours, and the answer below is no.
577
- const ours = objectId.startsWith(intake.urls.notes)
578
- || (intake.urls.privateNotes && objectId.startsWith(intake.urls.privateNotes));
579
- if (!ours) {
580
- intake.log(`QuoteRequest from ${actor} names ${objectId}, not a post of ours — ignored`);
581
- return;
582
- }
583
- if (!intake.sameIdentity(instrumentId, actor)) return `QuoteRequest instrument/actor identity mismatch (${instrumentId})`;
584
- const s = intake.store.getStatuses().find(x => x.noteId === objectId && x.kind === 'post');
585
- if (!s) return `QuoteRequest for a post we do not hold (${objectId})`;
586
- // Not retried on failure: the instrument is a URL a stranger chose.
587
- const note = await intake.fetchAP(instrumentId);
588
- if (!note) return `quote request: instrument fetch failed (${instrumentId})`;
589
- if (note.id !== instrumentId || !isContentType(note.type)) return `quote request: instrument is not verifiable content (${instrumentId})`;
590
- if (authorOf(note, actor) !== actor) return `quote request: ${instrumentId} is not by ${actor}`;
591
- if (quoteOf(note) !== objectId) return `quote request: ${instrumentId} does not quote ${objectId}`;
592
- const doc = await intake.fetchAP(actor);
593
- const inbox = doc?.endpoints?.sharedInbox || doc?.inbox;
594
- if (!inbox) return `quote request: no inbox for ${actor}`;
595
- const { quoteAnswerActivity } = await import('../wire-quotes.mjs');
596
- const open = !s.visibility || s.visibility === 'public' || s.visibility === 'unlisted';
597
- if (!open) {
598
- await intake.deliverer.deliver(inbox,
599
- quoteAnswerActivity({ urls: intake.urls, type: 'Reject', request: activity, to: actor, serial: intake.serial++ }));
600
- intake.log(`quote refused: ${instrumentId} asked to quote ${objectId}, which is not public`);
601
- return;
602
- }
603
- const authorization = await intake.publisher.grantQuote(objectId, instrumentId);
604
- await intake.deliverer.deliver(inbox,
605
- quoteAnswerActivity({ urls: intake.urls, type: 'Accept', request: activity, result: authorization, to: actor, serial: intake.serial++ }));
606
- const held = intake.store.getStatuses().find(x => x.noteId === instrumentId);
607
- if (!held) {
608
- // The origin's copy, already fetched above.
609
- const rejected = await intake.ingestNote(instrumentId, actor, { inline: note });
610
- if (rejected) intake.log(`quote of ${objectId} by ${actor}: ${rejected}`);
611
- } else if (held.quoteState !== 'accepted') {
612
- intake.store.updateStatus(instrumentId, { quote: objectId, quoteState: 'accepted' });
613
- }
614
- intake.store.addNotification({ type: 'quote', actor, noteId: instrumentId });
615
- intake.log(`quote allowed: ${instrumentId} quotes ${objectId}`);
616
- }
617
-
618
- // What can be said about a note's quote. The quoted post is ours: allowed if
619
- // we granted it. Elsewhere: allowed when the note carries an authorization
620
- // that, fetched from the quoted post's own origin, names this note and that
621
- // one. Otherwise pending — the honest word for a quote nobody has vouched for.
622
- export async function quoteStateOf(intake, note, quote) {
623
- if (quote.startsWith(intake.urls.notes)) {
624
- const target = intake.store.getStatuses().find(x => x.noteId === quote);
625
- return (target?.quotedBy || []).some(q => q.note === note.id) ? 'accepted' : 'pending';
626
- }
627
- const authId = idOf(note.quoteAuthorization);
628
- if (!authId || !intake.sameIdentity(authId, quote)) return 'pending';
629
- const auth = await intake.fetchAP(authId).catch(() => null);
630
- const ok = auth && auth.id === authId && auth.type === 'QuoteAuthorization'
631
- && idOf(auth.interactingObject) === note.id && idOf(auth.interactionTarget) === quote;
632
- return ok ? 'accepted' : 'pending';
633
- }