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
package/lib/core/wire.mjs DELETED
@@ -1,889 +0,0 @@
1
- // wire.mjs — builders for the opaque AS2/JSON wire documents the remote pod
2
- // serves (see claude/plans/ap-pod-mapping.md, "Remote pod layout"). These are
3
- // protocol documents, not our RDF.
4
-
5
- export const AS_CTX = 'https://www.w3.org/ns/activitystreams';
6
- // Lemmy's own context, held locally (lib/core/contexts/join-lemmy.json). It
7
- // names the three terms a Lemmy reader looks for on a community and its
8
- // posts: postingRestrictedToMods, stickied, commentsEnabled.
9
- export const LEMMY_CTX = 'https://join-lemmy.org/context.json';
10
- export const SEC_CTX = 'https://w3id.org/security/v1';
11
- export const PUBLIC = 'https://www.w3.org/ns/activitystreams#Public';
12
-
13
- // The pod LAYOUT — which container each document lives in — moved into the pod
14
- // library (lib/pod/urls.mjs), which knows nothing about FediPod and requires
15
- // the container root to be stated rather than defaulted. What stays here is
16
- // FediPod's own answer to "which root", and the handle rule, both of which read
17
- // this application's config shape.
18
- import { apUrls as podUrls } from '../pod/urls.mjs';
19
-
20
- export { webfingerHost } from '../pod/urls.mjs';
21
-
22
- // The container the Node agent has always used. Older than the rename to
23
- // FediPod and kept because it is what every existing Node-side pod actually
24
- // contains; the browser build states `fedipod/` on its own configs. A config
25
- // that names a root is always believed — this is only the answer for one that
26
- // does not.
27
- // Every build writes its data into a `fedipod/` container in the pod. This is
28
- // the answer for a config that names no root; a config that names one is always
29
- // believed. (`activitypods-js/` was an earlier name, now abandoned.)
30
- export const DEFAULT_ROOT = 'fedipod/';
31
-
32
- // The handle the fediverse sees: a fronted identity's name is the front's.
33
- export function publicHandle(config) {
34
- return config?.gateway?.frontActor?.match(/\/u\/([^/]+)\/ap\/actor\/?$/)?.[1]
35
- || config?.handle || null;
36
- }
37
-
38
- /**
39
- * The AP URLs for an identity, with FediPod's default root applied.
40
- *
41
- * The library underneath refuses to guess a root. Every caller here goes
42
- * through this wrapper so there is exactly ONE answer to "which container",
43
- * which is the thing that was wrong: the browser agent decided `fedipod/` for
44
- * its own urls while the publisher, handed a config with no `root`, decided
45
- * `activitypods-js/` — putting an identity's state in one container and its
46
- * published documents in another.
47
- */
48
- export function apUrls(remotePod, root, opts = {}) {
49
- return podUrls(remotePod, root || DEFAULT_ROOT, opts);
50
- }
51
-
52
- // RFC 6415 host-meta: several fediverse implementations discover WebFinger
53
- // through this LRDD template rather than hitting /.well-known/webfinger
54
- // directly.
55
- export function hostMeta(base) {
56
- return `<?xml version="1.0" encoding="UTF-8"?>\n<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">\n <Link rel="lrdd" template="${base}.well-known/webfinger?resource={uri}"/>\n</XRD>\n`;
57
- }
58
-
59
- export function jrd({ handle, host, actor, aliases = [], page = null }) {
60
- return {
61
- subject: `acct:${handle}@${host}`,
62
- ...(aliases.length ? { aliases } : {}),
63
- links: [
64
- { rel: 'self', type: 'application/activity+json', href: actor },
65
- ...(page ? [{ rel: 'http://webfinger.net/rel/profile-page', type: 'text/html', href: page }] : []),
66
- ],
67
- };
68
- }
69
-
70
- // `kind: 'group'` publishes a Group rather than a Person, which is what makes
71
- // Mastodon and Lemmy treat the actor as a community you join.
72
- // Whether a follow will be held for the operator instead of accepted on
73
- // arrival. A person's follows wait unless `autoAcceptFollows` says otherwise,
74
- // because a delivery on its own proves nothing about who sent it; a group is
75
- // open unless its operator asked to see joins. The actor document has to say
76
- // the same thing, or a remote server shows "Following" for a follow that is
77
- // in fact still waiting, and nothing ever tells it otherwise.
78
- export function followsNeedApproval(config = {}) {
79
- return !!config.approveJoins || (config.kind !== 'group' && !config.autoAcceptFollows);
80
- }
81
-
82
- export const assertionKeyId = (urls) => urls.actor + '#ed25519-key';
83
-
84
- export function actorDoc({ urls, handle, name, publicKeyPem, assertionKey = null, movedTo = null, kind = 'person',
85
- approveJoins = false, summary = null, icon = null, image = null, fields = [],
86
- webId = null, aliases = [], moderators = null, pendingFollowers = null, pendingFollowing = null,
87
- blocked = null, inbox = null, outbox = null, oauthAuthorize = null, oauthToken = null,
88
- postingRestrictedToMods = null }) {
89
- // manuallyApprovesFollowers is NOT in the base AS2 context, so it is declared
90
- // inline exactly as Mastodon declares it — and only when we actually use it.
91
- // It is what makes a client show "Request to follow" rather than "Follow" and
92
- // then sit in Requested with no explanation.
93
- // PropertyValue is the same case: schema.org's, declared the way Mastodon
94
- // declares it, and only when there are fields to carry. `image` (the banner)
95
- // and `attachment` are both plain AS2 and need no declaration.
96
- const context = [AS_CTX, SEC_CTX];
97
- // `featured` is Mastodon's term for the pinned-posts collection, declared
98
- // inline exactly as Mastodon declares it.
99
- context.push({ toot: 'http://joinmastodon.org/ns#', featured: { '@id': 'toot:featured', '@type': '@id' } });
100
- if (approveJoins) context.push({ manuallyApprovesFollowers: 'as:manuallyApprovesFollowers' });
101
- // FEP-8b32. The Multikey is a SECOND key beside publicKey/#main-key, not a
102
- // replacement: HTTP signatures stay RSA, and only the proof is Ed25519.
103
- // `assertionMethod` comes from the DID context and the key's own terms from
104
- // the Multikey one; the proof's terms travel with the proof, not here.
105
- if (assertionKey) context.push('https://www.w3.org/ns/did/v1',
106
- 'https://w3id.org/security/multikey/v1');
107
- // FEP-4ccd and FEP-c648 terms, declared exactly as those FEPs declare them,
108
- // and only when the collections are actually published.
109
- if (pendingFollowers || pendingFollowing) {
110
- context.push({
111
- pdg: 'https://purl.archive.org/socialweb/pending#',
112
- pendingFollowers: { '@id': 'pdg:pendingFollowers', '@type': '@id' },
113
- pendingFollowing: { '@id': 'pdg:pendingFollowing', '@type': '@id' },
114
- });
115
- }
116
- if (blocked) {
117
- context.push({
118
- bl: 'https://purl.archive.org/socialweb/blocked#',
119
- blocked: { '@id': 'bl:blocked', '@type': '@id' },
120
- });
121
- }
122
- // The actor's other identities: the account's WebID, plus any aliases —
123
- // old accounts elsewhere whose servers check for their own URL here before
124
- // they will send a Move. Declared inline exactly as Mastodon declares it.
125
- if (webId || aliases.length) context.push({ alsoKnownAs: { '@id': 'as:alsoKnownAs', '@type': '@id' } });
126
- // A community says whether only its moderators may open posts in it, in
127
- // Lemmy's term, declared through Lemmy's context. Groups only.
128
- if (kind === 'group' && postingRestrictedToMods !== null) context.push(LEMMY_CTX);
129
- if (fields.length) {
130
- context.push({ schema: 'http://schema.org#', PropertyValue: 'schema:PropertyValue', value: 'schema:value' });
131
- }
132
- return {
133
- '@context': context,
134
- id: urls.actor,
135
- // A group is a community; an application is a service that speaks for a
136
- // site rather than a person — the actor a forum publishes for itself.
137
- type: kind === 'group' ? 'Group' : kind === 'application' ? 'Application' : 'Person',
138
- ...(approveJoins ? { manuallyApprovesFollowers: true } : {}),
139
- ...(assertionKey ? { assertionMethod: [{
140
- id: assertionKeyId(urls), type: 'Multikey', controller: urls.actor,
141
- publicKeyMultibase: assertionKey,
142
- }] } : {}),
143
- ...(movedTo ? { movedTo } : {}),
144
- ...(webId || aliases.length ? { alsoKnownAs: [...(webId ? [webId] : []), ...aliases] } : {}),
145
- ...(kind === 'group' && postingRestrictedToMods !== null ? { postingRestrictedToMods: !!postingRestrictedToMods } : {}),
146
- preferredUsername: handle,
147
- name: name || handle,
148
- // The bio, and the avatar. For a group, `summary` is where what the group
149
- // is for goes — there is nowhere else for it.
150
- ...(summary ? { summary: contentHtml(summary) } : {}),
151
- ...(icon ? { icon: { type: 'Image', url: icon } } : {}),
152
- // The banner behind the avatar, and the labelled rows a client shows under
153
- // the bio — what Mastodon's profile editor calls Header picture and Extra
154
- // fields. Both are what every other server publishes for the same thing.
155
- ...(image ? { image: { type: 'Image', url: image } } : {}),
156
- // Escaped, not run through contentHtml: a field value is one inline row,
157
- // and wrapping it in <p> renders as a paragraph inside a table cell.
158
- ...(fields.length ? {
159
- attachment: fields.map(f => ({
160
- type: 'PropertyValue',
161
- name: String(f.name).replace(/[&<>]/g, c => HTML_ESCAPES[c]),
162
- value: String(f.value).replace(/[&<>]/g, c => HTML_ESCAPES[c]),
163
- })),
164
- } : {}),
165
- // The human page. Remote servers send profile clicks here, so it must be
166
- // something a person can read — the pod root is not.
167
- url: urls.profileHtml,
168
- // Normally the pod inbox (which buffers with nothing running). When an
169
- // inbox gateway is advertised, deliveries go to it instead — it verifies
170
- // and forwards into this same pod inbox for the drain.
171
- inbox: inbox || urls.inbox,
172
- // Every Mastodon actor publishes one. With a single actor per pod ours is
173
- // just the inbox, but its absence is the non-standard thing.
174
- //
175
- // The two OAuth entries are how a client-to-server client finds its way in
176
- // without being told anything by hand (ActivityPub 4.1). They appear only
177
- // where this identity's client surface answers on an address a stranger
178
- // can reach, which is why the caller supplies them rather than this
179
- // building them from the pod URL.
180
- endpoints: {
181
- sharedInbox: inbox || urls.inbox,
182
- ...(oauthAuthorize ? { oauthAuthorizationEndpoint: oauthAuthorize } : {}),
183
- ...(oauthToken ? { oauthTokenEndpoint: oauthToken } : {}),
184
- },
185
- // Where a client sends what this actor writes, which the protocol says is
186
- // this same address. The pod holds the collection and answers reads of it;
187
- // a client-to-server write has to reach the agent, and the pod cannot take
188
- // one. So where the agent is reachable it is named here, and a read of it
189
- // is sent straight on to the pod's own document.
190
- outbox: outbox || urls.outbox,
191
- // The pinned posts, as the collection other servers read when they show
192
- // this profile. Mastodon's term, declared the way Mastodon declares it.
193
- featured: urls.featured,
194
- followers: urls.followers,
195
- following: urls.following,
196
- // FEP-1b12: attributedTo names the moderators collection; recipients
197
- // validate a group's announced moderation against it.
198
- ...(moderators ? { attributedTo: moderators } : {}),
199
- ...(pendingFollowers ? { pendingFollowers } : {}),
200
- ...(pendingFollowing ? { pendingFollowing } : {}),
201
- ...(blocked ? { blocked } : {}),
202
- publicKey: { id: urls.actor + '#main-key', owner: urls.actor, publicKeyPem },
203
- };
204
- }
205
-
206
- // The human half of the actor: a page a browser can open, published beside
207
- // the actor document. It says who this is and how to follow — the handle to
208
- // paste, and the remote-follow control that hands the visitor to their own
209
- // server's follow screen.
210
- export { profilePageHtml } from './profile-page.mjs';
211
-
212
- // Retirement. An abandoned pod keeps accepting deliveries into a container
213
- // nobody will ever drain, and no remote server has any way to learn that. A
214
- // Delete of the actor is the signal to drop us; the Tombstone left in its place
215
- // answers anyone who dereferences the actor afterwards.
216
- export function deleteActorActivity(urls, stamp) {
217
- return {
218
- '@context': AS_CTX,
219
- id: `${urls.actor}#delete-${stamp}`,
220
- type: 'Delete',
221
- actor: urls.actor,
222
- object: urls.actor,
223
- to: ['https://www.w3.org/ns/activitystreams#Public'],
224
- };
225
- }
226
-
227
- // Move is the fediverse's "this account lives elsewhere now": remote servers
228
- // stop delivering here and migrate their followers to the target, while the old
229
- // handle keeps resolving. movedTo on the actor is the durable half of it.
230
- export function moveActivity(urls, target, stamp) {
231
- return {
232
- '@context': AS_CTX,
233
- id: `${urls.actor}#move-${stamp}`,
234
- type: 'Move',
235
- actor: urls.actor,
236
- object: urls.actor,
237
- target,
238
- to: ['https://www.w3.org/ns/activitystreams#Public'],
239
- };
240
- }
241
-
242
- export function tombstoneDoc(urls, deletedAt, kind = 'person') {
243
- return {
244
- '@context': AS_CTX,
245
- id: urls.actor,
246
- type: 'Tombstone',
247
- formerType: kind === 'group' ? 'Group' : 'Person',
248
- deleted: deletedAt,
249
- };
250
- }
251
-
252
- // A deleted note is left as a Tombstone at its own URL — kept fetchable — so a
253
- // peer that dereferences it learns the post was DELETED rather than getting a
254
- // bare 404 it cannot tell from "never existed" (§7.4).
255
- export function noteTombstone(noteId, deletedAt) {
256
- return {
257
- '@context': AS_CTX,
258
- id: noteId,
259
- type: 'Tombstone',
260
- formerType: 'Note',
261
- deleted: deletedAt,
262
- };
263
- }
264
-
265
- // A note's replies live in a document of their own, so a server that was never
266
- // delivered a reply can still discover it by dereferencing this.
267
- export function repliesId(noteId) { return noteId + '-replies'; }
268
-
269
- export function collection(id, items) {
270
- return {
271
- '@context': AS_CTX,
272
- id, type: 'Collection',
273
- totalItems: items.length,
274
- items,
275
- };
276
- }
277
-
278
- export function orderedCollection(id, items) {
279
- return {
280
- '@context': AS_CTX,
281
- id, type: 'OrderedCollection',
282
- totalItems: items.length,
283
- orderedItems: items,
284
- };
285
- }
286
-
287
- // A PAGED outbox, the shape Mastodon serves.
288
- //
289
- // One document holding every activity ever posted has to be rewritten whole on
290
- // every post, so the cost of saying something grows with everything you have
291
- // already said. Pages fix that without dropping anything: a new post rewrites
292
- // the newest page and a head that is four lines long, and every sealed page is
293
- // written once and never touched again.
294
- //
295
- // Pages are numbered from the OLDEST end, and that is the load-bearing part.
296
- // Number them from the newest and every page boundary shifts each time you
297
- // post, which is the problem again with more requests.
298
- export const OUTBOX_PAGE_SIZE = 20;
299
- export const outboxPageId = (outboxId, n) => `${outboxId}-${n}`;
300
-
301
- // How many pages `total` activities occupy. Always at least one, so an empty
302
- // outbox still has somewhere to point and a reader always has a `first`.
303
- export const outboxPageCount = (total) => Math.max(1, Math.ceil(total / OUTBOX_PAGE_SIZE));
304
-
305
- // `first` is the NEWEST page and `next` walks backwards in time, which is what
306
- // a client following an outbox expects.
307
- // `pageCount` is passed by anyone using outboxPaging, because once a page can
308
- // be SHORT the count is no longer ceil(total/20) — deriving it there would put
309
- // `first` below the newest page and hide everything above it.
310
- export function outboxHead(id, total, pageCount = outboxPageCount(total)) {
311
- const pages = Math.max(1, pageCount);
312
- return {
313
- '@context': AS_CTX,
314
- id, type: 'OrderedCollection',
315
- totalItems: total,
316
- first: outboxPageId(id, pages),
317
- last: outboxPageId(id, 1),
318
- };
319
- }
320
-
321
- // `items` is this page's slice, newest-first within the page. `next` points at
322
- // the older page; a sealed page never needs rewriting, so it carries no `prev`
323
- // — that would have to be written when the page AFTER it is created.
324
- export function outboxPage(outboxId, n, items) {
325
- return {
326
- '@context': AS_CTX,
327
- id: outboxPageId(outboxId, n),
328
- type: 'OrderedCollectionPage',
329
- partOf: outboxId,
330
- orderedItems: items.map(outboxWireItem),
331
- ...(n > 1 ? { next: outboxPageId(outboxId, n - 1) } : {}),
332
- };
333
- }
334
-
335
- // The slices, oldest page first. `outbox` arrives newest-first (recordOutbox
336
- // unshifts), and paging has to be anchored at the oldest end or appending one
337
- // activity moves every boundary.
338
- //
339
- // Anchoring at the oldest end is right for appends and wrong for REMOVALS,
340
- // which is why nothing calls this to publish any more — see outboxPaging.
341
- export function outboxPages(outbox) {
342
- const chron = [...outbox].reverse();
343
- const out = [];
344
- for (let i = 0; i < Math.max(chron.length, 1); i += OUTBOX_PAGE_SIZE) {
345
- out.push(chron.slice(i, i + OUTBOX_PAGE_SIZE).reverse()); // newest-first within the page
346
- }
347
- return out;
348
- }
349
-
350
- export const outboxItemId = (i) => (typeof i === 'string' ? i : i?.id || null);
351
-
352
- // The outbox lists activities. A post is recorded by its object id, which is
353
- // what every local reader keys on, and published as the Create beside it — a
354
- // document of its own. An Announce is recorded and published whole.
355
- export const outboxWireItem = (i) => (typeof i === 'string' ? createActivityId(i) : i);
356
- export const outboxLocalItem = (i) =>
357
- (typeof i === 'string' && i.endsWith('-create') ? i.slice(0, -'-create'.length) : i);
358
-
359
- // Assign entries to pages, and KEEP the assignment.
360
- //
361
- // Deriving the pages from position — which is what outboxPages does — makes an
362
- // append cheap, because only the newest page moves. It makes a REMOVAL cost the
363
- // whole history: every entry after the hole shifts back one, so every page from
364
- // there to the newest gets different contents, a different digest and a PUT.
365
- // Taking one old post down rewrote all of them, and an inbound Delete{actor}
366
- // paid that once per post the actor had.
367
- //
368
- // Holding the assignment instead means a removal touches the single page that
369
- // held it and leaves that page one item short. A short sealed page is legal
370
- // AS2 — the newest page is already partial — and `totalItems` on the head is
371
- // still the true count.
372
- //
373
- // `index` is the assignment we last published, oldest page first, ids only.
374
- // Ids it no longer contains have been removed; ids it does not know are new and
375
- // extend the newest page, spilling into fresh ones. An absent index (a first
376
- // publish, or a machine that lost its state) re-derives the same boundaries
377
- // outboxPages would have given, so upgrading rewrites nothing.
378
- // The stable-paging core, shared by the outbox and the followers collection.
379
- // `order` is the ids to place, in the order new ones should fill pages; `byId`
380
- // maps each id back to its item. Ids the `index` no longer contains are dropped
381
- // (a removal leaves that page one short); ids not in the index are appended to
382
- // the newest page. A middle hole stays a hole — never a re-slice.
383
- export function pageItems({ order, byId, index = [], pageSize }) {
384
- const next = (Array.isArray(index) ? index : [])
385
- .map(ids => (Array.isArray(ids) ? ids.filter(id => byId.has(id)) : []));
386
- const placed = new Set(next.flat());
387
- for (const id of order) {
388
- if (id == null || placed.has(id)) continue;
389
- placed.add(id);
390
- const last = next[next.length - 1];
391
- if (!last || last.length >= pageSize) next.push([id]);
392
- else last.push(id);
393
- }
394
- // A trailing empty page would leave `first`/`last` pointing at nothing; a
395
- // middle hole is the short page this exists to keep.
396
- while (next.length > 1 && !next[next.length - 1].length) next.pop();
397
- if (!next.length) next.push([]);
398
- return { index: next, pages: next.map(ids => ids.map(id => byId.get(id))) };
399
- }
400
-
401
- export function outboxPaging(outbox, index = []) {
402
- const byId = new Map();
403
- for (const it of outbox) {
404
- const id = outboxItemId(it);
405
- if (id && !byId.has(id)) byId.set(id, it);
406
- }
407
- // Fill pages oldest-first (outbox.json is newest-first), then reverse each
408
- // page back to newest-first for display.
409
- const order = [...outbox].reverse().map(outboxItemId);
410
- const { index: idx, pages } = pageItems({ order, byId, index, pageSize: OUTBOX_PAGE_SIZE });
411
- return { index: idx, pages: pages.map(p => [...p].reverse()) };
412
- }
413
-
414
- // ── followers, paged the same way ────────────────────────────────────────
415
- // A followers collection is a set of actor IRIs. Each IRI is its own id, so
416
- // there is nothing to dereference to page it, and no within-page order to keep.
417
- export const FOLLOWERS_PAGE_SIZE = 20;
418
- export const followersPageId = (id, n) => `${id}-${n}`;
419
- export const followersPageCount = (total) => Math.max(1, Math.ceil(total / FOLLOWERS_PAGE_SIZE));
420
-
421
- export function followersHead(id, total, pageCount = followersPageCount(total)) {
422
- const pages = Math.max(1, pageCount);
423
- return {
424
- '@context': AS_CTX,
425
- id, type: 'OrderedCollection',
426
- totalItems: total,
427
- first: followersPageId(id, 1),
428
- last: followersPageId(id, pages),
429
- };
430
- }
431
-
432
- export function followersPage(id, n, items, pageCount) {
433
- return {
434
- '@context': AS_CTX,
435
- id: followersPageId(id, n),
436
- type: 'OrderedCollectionPage',
437
- partOf: id,
438
- orderedItems: items,
439
- ...(n < pageCount ? { next: followersPageId(id, n + 1) } : {}),
440
- };
441
- }
442
-
443
- export function followersPaging(actors, index = []) {
444
- const list = actors.filter(a => typeof a === 'string');
445
- const byId = new Map();
446
- for (const a of list) if (!byId.has(a)) byId.set(a, a);
447
- return pageItems({ order: list, byId, index, pageSize: FOLLOWERS_PAGE_SIZE });
448
- }
449
-
450
- // NodeInfo (the fediverse's server self-description): a root .well-known
451
- // pointer plus the document it points at. Both are static, so the pod can
452
- // serve them exactly like webfinger.
453
- export function nodeinfoPointer(docHref) {
454
- return {
455
- links: [{ rel: 'http://nodeinfo.diaspora.software/ns/schema/2.0', href: docHref }],
456
- };
457
- }
458
-
459
- export function nodeinfoDoc({ version, localPosts = 0 }) {
460
- return {
461
- version: '2.0',
462
- // "hometown", not "fedipod": clients built on megalodon (Whalebird,
463
- // Fedistar) refuse any software name outside their list, and hometown —
464
- // a small Mastodon fork — is the least false of the accepted names for
465
- // what this API serves.
466
- software: { name: 'hometown', version },
467
- protocols: ['activitypub'],
468
- services: { inbound: [], outbound: [] },
469
- openRegistrations: false,
470
- usage: { users: { total: 1, activeMonth: 1, activeHalfyear: 1 }, localPosts },
471
- metadata: {},
472
- };
473
- }
474
-
475
- // Federated post bodies are attacker-controlled HTML that clients render —
476
- // and Mastodon clients (Phanpy included) trust the server's content and
477
- // innerHTML it, because Mastodon sanitizes server-side. That makes THIS the
478
- // only line of defence, so it runs on a real HTML parser (sanitize-html →
479
- // htmlparser2) rather than pattern-matching: allowlisted tags and
480
- // attributes, safe URL schemes, and script/style dropped with their text.
481
- import sanitize from 'sanitize-html';
482
-
483
- const ALLOWED_TAGS = ['p', 'br', 'a', 'span', 'em', 'strong', 'b', 'i', 'u', 'del',
484
- 'code', 'pre', 'blockquote', 'ul', 'ol', 'li', 'h1', 'h2', 'h3', 'h4'];
485
- const ALLOWED_ATTRS = ['href', 'rel', 'class', 'lang', 'title'];
486
-
487
- export function sanitizeHtml(html) {
488
- if (!html) return '';
489
- return sanitize(String(html), {
490
- allowedTags: ALLOWED_TAGS,
491
- allowedAttributes: { '*': ALLOWED_ATTRS },
492
- allowedSchemes: ['http', 'https', 'mailto'],
493
- allowedSchemesAppliedToAttributes: ['href'],
494
- allowProtocolRelative: false,
495
- disallowedTagsMode: 'discard',
496
- nonTextTags: ['script', 'style', 'textarea', 'noscript'], // drop their text too
497
- transformTags: {
498
- a: sanitize.simpleTransform('a', { rel: 'nofollow noopener noreferrer' }),
499
- },
500
- });
501
- }
502
-
503
- // A Lemmy post, a blog article, a PeerTube video and a Bookwyrm review each
504
- // carry their headline in `name` and their body in `content`. Reading content
505
- // alone loses the headline, and a link post is often nothing else — so keep it
506
- // as the first line, unless the body already opens with it.
507
- export function titledContent(note) {
508
- const html = sanitizeHtml(note?.content);
509
- const types = Array.isArray(note?.type) ? note.type : [note?.type];
510
- const title = typeof note?.name === 'string' ? note.name.trim() : '';
511
- if (!title || types.includes('Note')) return html;
512
- const opening = html.replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim().toLowerCase();
513
- if (opening.startsWith(title.toLowerCase())) return html;
514
- const esc = String(title).replace(/[&<>]/g, c => HTML_ESCAPES[c]);
515
- return `<p><strong>${esc}</strong></p>${html}`;
516
- }
517
-
518
- // Normalize a wire Note's attachment list to { url, mediaType, description }.
519
- // Capped: this list comes from somebody else's document and lands in a
520
- // statuses.json row, which is rewritten whole on every change. Twenty is more
521
- // than any client shows and more than any real post carries.
522
- const MAX_ATTACHMENTS = 20;
523
- const MAX_ATTACHMENT_URL = 2048;
524
- // http(s) only: an attachment URL becomes a client's <img src> or <video src>,
525
- // and the store has guarded avatars this way all along (safeUrl).
526
- const attachmentUrl = (u) => {
527
- if (!u) return null;
528
- const s = String(u).slice(0, MAX_ATTACHMENT_URL);
529
- try {
530
- const p = new URL(s);
531
- return (p.protocol === 'https:' || p.protocol === 'http:') ? s : null;
532
- } catch { return null; }
533
- };
534
-
535
- export function attachmentsOf(note) {
536
- const list = Array.isArray(note?.attachment) ? note.attachment : note?.attachment ? [note.attachment] : [];
537
- // A bare Link carries `href` and no `url` — Lemmy's link posts arrive that
538
- // way, and reading only `url` dropped the link the post was about.
539
- return list.slice(0, MAX_ATTACHMENTS).map(a => ({
540
- url: typeof a?.url === 'string' ? a.url : a?.url?.href || (typeof a?.href === 'string' ? a.href : undefined),
541
- mediaType: String(a?.mediaType || '').slice(0, 128),
542
- ...(a?.name ? { description: String(a.name).slice(0, 1500) } : {}),
543
- })).map(a => ({ ...a, url: attachmentUrl(a.url) })).filter(a => a.url);
544
- }
545
-
546
- const HTML_ESCAPES = { '&': '&amp;', '<': '&lt;', '>': '&gt;' };
547
- // user@host in the text, which is what a Mention tag has to agree with.
548
- export const MENTION_RE = /@([A-Za-z0-9_.-]+)@([A-Za-z0-9.-]+\.[A-Za-z]{2,}(?::\d+)?)/g;
549
-
550
- export function mentionsIn(text) {
551
- return [...new Set(String(text).match(MENTION_RE) || [])].map(m => m.slice(1));
552
- }
553
-
554
- // #word in the text: letters, digits and underscores with at least one
555
- // letter, not glued to a word or a path (so a URL's fragment is not a tag).
556
- export const HASHTAG_RE = /(^|[^\/\w)])#([\p{L}\p{N}_]*[\p{L}_][\p{L}\p{N}_]*)/gu;
557
-
558
- export function hashtagsIn(text) {
559
- const seen = new Map();
560
- for (const m of String(text).matchAll(HASHTAG_RE)) {
561
- const key = m[2].toLowerCase();
562
- if (!seen.has(key)) seen.set(key, m[2]);
563
- }
564
- return [...seen.values()];
565
- }
566
-
567
- // Where a tag's link points: this actor's own page, asked for the tag. A
568
- // Hashtag's href must be a URL; the name is what servers file it under.
569
- export function tagHref(urls, name) {
570
- return urls.profileHtml + '?tag=' + encodeURIComponent(String(name).toLowerCase());
571
- }
572
-
573
- // Escape first, then linkify: escaping cannot introduce an @handle, and the
574
- // anchors must survive it. A mention nobody could resolve stays plain text.
575
- export function contentHtml(text, mentions = [], hashtags = []) {
576
- let html = String(text).replace(/[&<>]/g, c => HTML_ESCAPES[c]);
577
- if (hashtags.length) {
578
- const byName = new Map(hashtags.filter(h => h?.name && h?.href).map(h => [h.name.toLowerCase(), h.href]));
579
- html = html.replace(HASHTAG_RE, (all, lead, name) => {
580
- const href = byName.get(name.toLowerCase());
581
- if (!href) return all;
582
- return `${lead}<a href="${String(href).replace(/[&<>"]/g, c => (HTML_ESCAPES[c] || '&quot;'))}" class="mention hashtag" rel="tag">#<span>${name}</span></a>`;
583
- });
584
- }
585
- for (const m of mentions) {
586
- if (!m?.handle || !m?.actor) continue;
587
- // The anchor is for PEOPLE — the mentioned actor's advertised page when it
588
- // has one; a browser landing on a raw actor document reads nothing. The
589
- // mention TAG keeps the actor id, which is what servers match on.
590
- const href = String(m.page || m.actor).replace(/[&<>"]/g, c => (HTML_ESCAPES[c] || '&quot;'));
591
- html = html.split('@' + m.handle).join(
592
- `<a href="${href}" class="u-url mention">@${m.handle.split('@')[0]}</a>`);
593
- }
594
- return '<p>' + html.replace(/\n+/g, '</p><p>') + '</p>';
595
- }
596
-
597
- // Mastodon's four shapes. Public and unlisted are world-readable; private
598
- // (followers-only) and direct carry no Public address at all and belong in
599
- // the owner-only container. Mastodon addresses mentions in cc (to, for a
600
- // direct post) and will not notify anyone it does not find there or in the
601
- // tags.
602
- export function addressing(urls, visibility, who = []) {
603
- return {
604
- public: { to: [PUBLIC], cc: [urls.followers, ...who] },
605
- unlisted: { to: [urls.followers], cc: [PUBLIC, ...who] },
606
- private: { to: [urls.followers], cc: who },
607
- direct: { to: who, cc: [] },
608
- }[visibility] || { to: [PUBLIC], cc: [urls.followers, ...who] };
609
- }
610
-
611
- // The quote terms, declared exactly as Mastodon declares them on a post:
612
- // FEP-044f's `quote` and `quoteAuthorization`, and the two older spellings
613
- // (Fedibird's quoteUri, Misskey's _misskey_quote) that older servers read.
614
- export const QUOTE_CTX = {
615
- quote: { '@id': 'https://w3id.org/fep/044f#quote', '@type': '@id' },
616
- quoteUri: { '@id': 'http://fedibird.com/ns#quoteUri', '@type': '@id' },
617
- _misskey_quote: { '@id': 'https://misskey-hub.net/ns#_misskey_quote', '@type': '@id' },
618
- quoteAuthorization: { '@id': 'https://w3id.org/fep/044f#quoteAuthorization', '@type': '@id' },
619
- };
620
- // The interaction policy terms, GoToSocial's, declared as Mastodon declares them.
621
- export const POLICY_CTX = {
622
- gts: 'https://gotosocial.org/ns#',
623
- interactionPolicy: { '@id': 'gts:interactionPolicy', '@type': '@id' },
624
- canQuote: { '@id': 'gts:canQuote', '@type': '@id' },
625
- automaticApproval: { '@id': 'gts:automaticApproval', '@type': '@id' },
626
- manualApproval: { '@id': 'gts:manualApproval', '@type': '@id' },
627
- };
628
-
629
- // Who may quote a post: anyone, for a post the world can read; nobody, for a
630
- // followers-only or direct one. Said on every note, because a server that
631
- // finds no policy offers no quote button.
632
- export function quotePolicy(visibility) {
633
- const open = visibility === 'public' || visibility === 'unlisted';
634
- return { canQuote: { automaticApproval: open ? [PUBLIC] : [], manualApproval: [] } };
635
- }
636
-
637
- // `also`: actors addressed without being mentioned — the parent's author on
638
- // a reply. In cc but not in tag, so their server receives the post and does
639
- // not notify them. A direct post names its audience in the text alone.
640
- // `quote`: the post this one quotes, `{ id, authorization }` — the
641
- // authorization is the quoted author's QuoteAuthorization, once granted.
642
- export function noteDoc({ urls, slug, content, published, inReplyTo, attachments, mentions = [],
643
- visibility = 'public', summary = null, sensitive = false, updated = null, container = null, also = [],
644
- quote = null }) {
645
- const id = (container || urls.notes) + slug;
646
- const who = mentions.map(m => m.actor);
647
- const extra = visibility === 'direct' ? [] : also.filter(a => a && !who.includes(a));
648
- const addressed = addressing(urls, visibility, [...who, ...extra]);
649
- const hashtags = hashtagsIn(content).map(name => ({ name, href: tagHref(urls, name) }));
650
- const note = {
651
- '@context': [AS_CTX, { ...POLICY_CTX, ...(quote?.id ? QUOTE_CTX : {}) }],
652
- id, type: 'Note',
653
- attributedTo: urls.actor,
654
- content: contentHtml(content, mentions, hashtags),
655
- published,
656
- to: addressed.to,
657
- cc: addressed.cc,
658
- replies: repliesId(id),
659
- interactionPolicy: quotePolicy(visibility),
660
- };
661
- if (quote?.id) {
662
- // All three spellings name the same post, as Mastodon writes them.
663
- note.quote = quote.id;
664
- note.quoteUri = quote.id;
665
- note._misskey_quote = quote.id;
666
- if (quote.authorization) note.quoteAuthorization = quote.authorization;
667
- }
668
- if (summary) note.summary = summary; // the content warning
669
- // A content warning is what a receiver hides behind; media marked
670
- // sensitive without one is hidden the same way.
671
- if (summary || sensitive) note.sensitive = true;
672
- if (updated) note.updated = updated; // an edit's own stamp
673
- const tag = [
674
- ...mentions.map(m => ({ type: 'Mention', href: m.actor, name: '@' + m.handle })),
675
- ...hashtags.map(h => ({ type: 'Hashtag', href: h.href, name: '#' + h.name })),
676
- ];
677
- if (tag.length) note.tag = tag;
678
- if (inReplyTo) note.inReplyTo = inReplyTo;
679
- if (attachments?.length) {
680
- note.attachment = attachments.map(a => ({
681
- type: 'Document', mediaType: a.mediaType, url: a.url,
682
- ...(a.description ? { name: a.description } : {}),
683
- }));
684
- }
685
- return note;
686
- }
687
-
688
- // A poll is a note that asks something: the same addressing, the same
689
- // mentions, the same replies collection, with the choices in `oneOf` (pick
690
- // one) or `anyOf` (pick several). Each choice carries the count as the
691
- // totalItems of its own replies collection, which is where every server that
692
- // shows a poll reads it from.
693
- //
694
- // `endTime` is when voting stops and `closed` is the stamp saying it has —
695
- // both plain AS2. `votersCount` is Mastodon's: PEOPLE rather than answers,
696
- // which differ only once a poll takes several answers each. It is not in the
697
- // base context, so it is declared inline exactly as Mastodon declares it, and
698
- // only when we actually carry it.
699
- export function questionDoc({ options = [], multiple = false, endTime = null,
700
- closed = null, votersCount = null, ...rest }) {
701
- const note = noteDoc(rest);
702
- const choices = options.map(o => ({
703
- type: 'Note',
704
- name: String(o.title),
705
- replies: { type: 'Collection', totalItems: Number(o.votes) || 0 },
706
- }));
707
- const question = {
708
- ...note,
709
- type: 'Question',
710
- [multiple ? 'anyOf' : 'oneOf']: choices,
711
- };
712
- if (endTime) question.endTime = endTime;
713
- if (closed) question.closed = closed;
714
- if (votersCount !== null && votersCount !== undefined) {
715
- question['@context'] = [...[].concat(note['@context']), {
716
- toot: 'http://joinmastodon.org/ns#',
717
- votersCount: {
718
- '@id': 'toot:votersCount',
719
- '@type': 'http://www.w3.org/2001/XMLSchema#nonNegativeInteger',
720
- },
721
- }];
722
- question.votersCount = votersCount;
723
- }
724
- return question;
725
- }
726
-
727
- // The id is a document of its own, not `note.id + '#create'`. A group wraps this
728
- // whole activity in its Announce (FEP-1b12), and the receiver resolves it by
729
- // dereferencing this id — a fragment would just serve the Note back under a
730
- // different id, which Mastodon rejects.
731
- export function createActivityId(noteId) { return noteId + '-create'; }
732
-
733
- export function createActivity(note, urls) {
734
- return {
735
- '@context': AS_CTX,
736
- id: createActivityId(note.id),
737
- type: 'Create',
738
- actor: urls.actor,
739
- published: note.published,
740
- to: note.to, cc: note.cc,
741
- object: note,
742
- };
743
- }
744
-
745
- export function acceptActivity({ urls, followActivity, serial }) {
746
- return {
747
- '@context': AS_CTX,
748
- id: urls.actor + '#accept-' + serial,
749
- type: 'Accept',
750
- actor: urls.actor,
751
- object: followActivity,
752
- };
753
- }
754
-
755
- // The other half of Accept: what a group sends to end a following it will no
756
- // longer carry. Without it an ejected member's server never learns, and keeps
757
- // receiving everything the group announces.
758
- export function rejectActivity({ urls, followActivity, serial }) {
759
- return {
760
- '@context': AS_CTX,
761
- id: urls.actor + '#reject-' + serial,
762
- type: 'Reject',
763
- actor: urls.actor,
764
- object: followActivity,
765
- };
766
- }
767
-
768
- export function followActivity({ urls, targetActor, serial }) {
769
- return {
770
- '@context': AS_CTX,
771
- id: urls.actor + '#follow-' + serial,
772
- type: 'Follow',
773
- actor: urls.actor,
774
- object: targetActor,
775
- };
776
- }
777
-
778
- export function likeActivity({ urls, noteId, serial }) {
779
- return {
780
- '@context': AS_CTX,
781
- id: urls.actor + '#like-' + serial,
782
- type: 'Like',
783
- actor: urls.actor,
784
- object: noteId,
785
- };
786
- }
787
-
788
- // `object` is a note URL for a personal boost — what Mastodon renders — or the
789
- // whole received activity when a group carries a member's post, which is what
790
- // FEP-1b12 requires and what lets a follower see the author's own activity
791
- // rather than the group's summary of it. Preserved exactly as delivered.
792
- export function announceActivity({ urls, object, serial, published = new Date().toISOString(),
793
- audience = null }) {
794
- return {
795
- '@context': AS_CTX,
796
- id: urls.actor + '#announce-' + serial,
797
- type: 'Announce',
798
- actor: urls.actor,
799
- published,
800
- to: [PUBLIC], cc: [urls.followers],
801
- // FEP-1b12: a group's carry names the group as the audience.
802
- ...(audience ? { audience } : {}),
803
- object,
804
- };
805
- }
806
-
807
- // Tell followers the actor document changed. Nothing obliges a server to fetch
808
- // an actor it already holds — Mastodon shows its cached copy, follower count
809
- // included, until something makes it look again — so a profile edit is
810
- // invisible to everyone until this goes out.
811
- //
812
- // The actor's own @context is hoisted onto the activity and the embedded copy
813
- // loses its: the document declares `schema:` when it carries PropertyValue
814
- // attachments, and an object nested under a plainer context would lose them.
815
- export function updateActorActivity({ urls, actor, serial, published = new Date().toISOString() }) {
816
- const { '@context': ctx, ...object } = actor;
817
- return {
818
- '@context': ctx || AS_CTX,
819
- id: urls.actor + '#update-' + serial,
820
- type: 'Update',
821
- actor: urls.actor,
822
- published,
823
- to: [PUBLIC], cc: [urls.followers],
824
- object,
825
- };
826
- }
827
-
828
- // An edit: same Note id, `updated` stamped, so receivers replace their copy
829
- // and show it as edited. The activity id carries the stamp — each edit is a
830
- // new activity, and a replay of an old one is already seen.
831
- // `serial` names the activity when the note is not being edited — a quote
832
- // authorization arriving on it changes what the note says about its quote,
833
- // not what it says.
834
- export function updateActivity(note, urls, { serial = null } = {}) {
835
- return {
836
- '@context': AS_CTX,
837
- id: note.id + '#update-' + (note.updated ? String(note.updated).replace(/[^0-9TZ]/g, '') : 'q' + (serial ?? Date.now())),
838
- type: 'Update',
839
- actor: urls.actor,
840
- to: note.to, cc: note.cc,
841
- object: note,
842
- };
843
- }
844
-
845
- export function deleteActivity({ urls, noteId }) {
846
- return {
847
- '@context': AS_CTX,
848
- id: noteId + '#delete',
849
- type: 'Delete',
850
- actor: urls.actor,
851
- to: [PUBLIC],
852
- object: { id: noteId, type: 'Tombstone' },
853
- };
854
- }
855
-
856
- export function undoActivity({ urls, activity, serial }) {
857
- return {
858
- '@context': AS_CTX,
859
- id: urls.actor + '#undo-' + serial,
860
- type: 'Undo',
861
- actor: urls.actor,
862
- object: activity,
863
- };
864
- }
865
-
866
- // A group's ban (FEP-1b12 moderation). Only ever announced BY a group —
867
- // a person's blocks are never put on the wire.
868
- export function blockActivity({ urls, targetActor, serial }) {
869
- return {
870
- '@context': AS_CTX,
871
- id: urls.actor + '#block-' + serial,
872
- type: 'Block',
873
- actor: urls.actor,
874
- object: targetActor,
875
- };
876
- }
877
-
878
- // Add/Remove on one of this actor's own collections — the moderator roster
879
- // is the one that changes hands (FEP-1b12).
880
- export function addRemoveActivity({ urls, type, object, target, serial }) {
881
- return {
882
- '@context': AS_CTX,
883
- id: urls.actor + '#' + type.toLowerCase() + '-' + serial,
884
- type,
885
- actor: urls.actor,
886
- object,
887
- target,
888
- };
889
- }