fedipod-server 0.29.0 → 0.31.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 (444) hide show
  1. package/README.md +16 -6
  2. package/dist/claims.d.ts +2 -0
  3. package/dist/claims.js +13 -4
  4. package/dist/door.d.ts +4 -0
  5. package/dist/door.js +31 -0
  6. package/dist/fedipod.d.ts +15 -0
  7. package/dist/fedipod.js +13 -0
  8. package/dist/front.d.ts +3 -0
  9. package/dist/front.js +92 -0
  10. package/dist/handler.d.ts +36 -101
  11. package/dist/handler.js +93 -644
  12. package/dist/handler.jsonld +2 -30
  13. package/dist/identities.d.ts +71 -0
  14. package/dist/identities.js +236 -0
  15. package/dist/mounts.d.ts +43 -0
  16. package/dist/mounts.js +123 -0
  17. package/dist/opt-in.d.ts +46 -0
  18. package/dist/opt-in.js +176 -0
  19. package/dist/private-read.d.ts +4 -0
  20. package/dist/private-read.js +34 -0
  21. package/package.json +12 -20
  22. package/lib/client/c2s.mjs +0 -636
  23. package/lib/client/localapi.mjs +0 -87
  24. package/lib/client/masto/accounts.mjs +0 -310
  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 -387
  65. package/lib/core/graphview.mjs +0 -327
  66. package/lib/core/intake/activities.mjs +0 -649
  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 -258
  70. package/lib/core/intake/index.mjs +0 -732
  71. package/lib/core/intake/notes.mjs +0 -367
  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/place.mjs +0 -82
  76. package/lib/core/polls.mjs +0 -105
  77. package/lib/core/profile-page.mjs +0 -104
  78. package/lib/core/proof.mjs +0 -79
  79. package/lib/core/publisher/collections.mjs +0 -272
  80. package/lib/core/publisher/index.mjs +0 -522
  81. package/lib/core/publisher/notes.mjs +0 -519
  82. package/lib/core/publisher/own.mjs +0 -143
  83. package/lib/core/publisher/questions.mjs +0 -241
  84. package/lib/core/publisher/restore.mjs +0 -224
  85. package/lib/core/shapes/activitystreams.ttl +0 -157
  86. package/lib/core/shapes/index.mjs +0 -132
  87. package/lib/core/shapes/shapes-text.mjs +0 -13
  88. package/lib/core/social.mjs +0 -561
  89. package/lib/core/storage.mjs +0 -176
  90. package/lib/core/store.mjs +0 -578
  91. package/lib/core/wire-quotes.mjs +0 -71
  92. package/lib/core/wire.mjs +0 -925
  93. package/lib/device/account.mjs +0 -87
  94. package/lib/device/admin/index.mjs +0 -13
  95. package/lib/device/admin/origins.mjs +0 -35
  96. package/lib/device/admin/routes/connections.mjs +0 -144
  97. package/lib/device/admin/routes/gateway.mjs +0 -199
  98. package/lib/device/admin/routes/lifecycle.mjs +0 -200
  99. package/lib/device/admin/routes/owner.mjs +0 -322
  100. package/lib/device/admin/routes/setup.mjs +0 -428
  101. package/lib/device/admin/routes/social.mjs +0 -188
  102. package/lib/device/admin/server.mjs +0 -95
  103. package/lib/device/admin/static.mjs +0 -247
  104. package/lib/device/admin/surface.mjs +0 -315
  105. package/lib/device/certs.mjs +0 -281
  106. package/lib/device/cli/commands/account.mjs +0 -586
  107. package/lib/device/cli/commands/run.mjs +0 -278
  108. package/lib/device/cli/commands/service.mjs +0 -221
  109. package/lib/device/cli/commands/setup.mjs +0 -489
  110. package/lib/device/cli/commands/state.mjs +0 -561
  111. package/lib/device/cli/context.mjs +0 -288
  112. package/lib/device/export-collections.mjs +0 -161
  113. package/lib/device/gateway-move.mjs +0 -58
  114. package/lib/device/home.mjs +0 -161
  115. package/lib/device/migrate.mjs +0 -115
  116. package/lib/device/ports.mjs +0 -27
  117. package/lib/device/remote.mjs +0 -54
  118. package/lib/device/setup.mjs +0 -496
  119. package/lib/device/update.mjs +0 -96
  120. package/lib/gateway/caches.mjs +0 -36
  121. package/lib/gateway/directory.mjs +0 -90
  122. package/lib/gateway/front-core.mjs +0 -966
  123. package/lib/gateway/gateway-core.mjs +0 -244
  124. package/lib/gateway/headers.mjs +0 -49
  125. package/lib/gateway/httpsig.mjs +0 -159
  126. package/lib/gateway/notices.mjs +0 -84
  127. package/lib/gateway/quiet.mjs +0 -189
  128. package/lib/gateway/relay-extras.mjs +0 -89
  129. package/lib/gateway/token-claims.mjs +0 -16
  130. package/lib/pod/README.md +0 -61
  131. package/lib/pod/actor.mjs +0 -96
  132. package/lib/pod/collection.mjs +0 -77
  133. package/lib/pod/containers.mjs +0 -140
  134. package/lib/pod/discovery.mjs +0 -46
  135. package/lib/pod/featured.mjs +0 -18
  136. package/lib/pod/followers.mjs +0 -24
  137. package/lib/pod/following.mjs +0 -10
  138. package/lib/pod/http.mjs +0 -74
  139. package/lib/pod/inbox.mjs +0 -166
  140. package/lib/pod/links.mjs +0 -35
  141. package/lib/pod/location.mjs +0 -52
  142. package/lib/pod/media.mjs +0 -27
  143. package/lib/pod/notes.mjs +0 -78
  144. package/lib/pod/notifications.mjs +0 -89
  145. package/lib/pod/outbox.mjs +0 -18
  146. package/lib/pod/package.json +0 -19
  147. package/lib/pod/policy.mjs +0 -58
  148. package/lib/pod/private.mjs +0 -19
  149. package/lib/pod/root.mjs +0 -104
  150. package/lib/pod/state.mjs +0 -40
  151. package/lib/pod/transport.mjs +0 -674
  152. package/lib/pod/type-index.mjs +0 -101
  153. package/lib/pod/urls.mjs +0 -91
  154. package/lib/server/embed.mjs +0 -424
  155. package/lib/session/README.md +0 -137
  156. package/lib/session/demo.html +0 -94
  157. package/lib/session/fedi-account.mjs +0 -417
  158. package/lib/session/fedi-login.mjs +0 -123
  159. package/lib/session/oidc-session.mjs +0 -233
  160. package/lib/session/package.json +0 -49
  161. package/lib/shared/guard.mjs +0 -213
  162. package/lib/shared/links.mjs +0 -9
  163. package/lib/shared/safefetch.mjs +0 -235
  164. package/lib/shared/ua.mjs +0 -29
  165. package/phanpy/dist/404.html +0 -40
  166. package/phanpy/dist/_headers +0 -2
  167. package/phanpy/dist/apple-touch-icon.png +0 -0
  168. package/phanpy/dist/assets/ICONS-DIPJeU0C.js +0 -2
  169. package/phanpy/dist/assets/ICONS-DIPJeU0C.js.map +0 -1
  170. package/phanpy/dist/assets/ScheduledAtField-D-Abfo4J.js +0 -183
  171. package/phanpy/dist/assets/ScheduledAtField-D-Abfo4J.js.map +0 -1
  172. package/phanpy/dist/assets/Temml-CCjoacWt.woff2 +0 -0
  173. package/phanpy/dist/assets/__vite-browser-external-C7iut881.js +0 -2
  174. package/phanpy/dist/assets/__vite-browser-external-C7iut881.js.map +0 -1
  175. package/phanpy/dist/assets/boosts-carousel-BiOaNdMT.jpg +0 -0
  176. package/phanpy/dist/assets/catch-up-O5q5C75Z.png +0 -0
  177. package/phanpy/dist/assets/chunk-aKtaBQYM.js +0 -1
  178. package/phanpy/dist/assets/compose-CFEx7wb4.js +0 -2
  179. package/phanpy/dist/assets/compose-CFEx7wb4.js.map +0 -1
  180. package/phanpy/dist/assets/compose-ZJN5xSb4.js +0 -35
  181. package/phanpy/dist/assets/compose-ZJN5xSb4.js.map +0 -1
  182. package/phanpy/dist/assets/debug.module-YFQgwnIr.js +0 -52
  183. package/phanpy/dist/assets/debug.module-YFQgwnIr.js.map +0 -1
  184. package/phanpy/dist/assets/dom-p1YcJ5Rw.js +0 -2
  185. package/phanpy/dist/assets/dom-p1YcJ5Rw.js.map +0 -1
  186. package/phanpy/dist/assets/home-mobile-dark@2x-Dc-J8-SM.png +0 -0
  187. package/phanpy/dist/assets/home-mobile-light@2x-Bd3fod8m.png +0 -0
  188. package/phanpy/dist/assets/home-tablet-dark@2x-BgcNI8le.png +0 -0
  189. package/phanpy/dist/assets/home-tablet-light@2x-BW2RV9T6.png +0 -0
  190. package/phanpy/dist/assets/icons/add-circle-line-WD0mwORD.js +0 -1
  191. package/phanpy/dist/assets/icons/alert-line-CpFUMf9s.js +0 -1
  192. package/phanpy/dist/assets/icons/android-2-line-Bzxnrd3S.js +0 -1
  193. package/phanpy/dist/assets/icons/announcement-line-5nwaJFJm.js +0 -1
  194. package/phanpy/dist/assets/icons/arrow-down-circle-line-a7Id9-Iv.js +0 -1
  195. package/phanpy/dist/assets/icons/arrow-down-line-B8uzXzq6.js +0 -1
  196. package/phanpy/dist/assets/icons/arrow-left-line-CGrGT9C-.js +0 -1
  197. package/phanpy/dist/assets/icons/arrow-right-line-CE-HERfX.js +0 -1
  198. package/phanpy/dist/assets/icons/arrow-to-up-line-CvdQjmcy.js +0 -1
  199. package/phanpy/dist/assets/icons/arrow-up-circle-line-DEparSNF.js +0 -1
  200. package/phanpy/dist/assets/icons/arrow-up-line-C7yBeugc.js +0 -1
  201. package/phanpy/dist/assets/icons/arrows-right-line-BtbXmJGP.js +0 -1
  202. package/phanpy/dist/assets/icons/at-line-CF66Bznk.js +0 -1
  203. package/phanpy/dist/assets/icons/attachment-line-BY8It-l9.js +0 -1
  204. package/phanpy/dist/assets/icons/blockquote-line-BS6pU_hm.js +0 -1
  205. package/phanpy/dist/assets/icons/board-line-C0UWXyA6.js +0 -1
  206. package/phanpy/dist/assets/icons/bookmark-line-1CtX5JLF.js +0 -1
  207. package/phanpy/dist/assets/icons/building-5-line-Dd59MzbO.js +0 -1
  208. package/phanpy/dist/assets/icons/bus-2-line-6_dvA0GG.js +0 -1
  209. package/phanpy/dist/assets/icons/calendar-day-line-Crpl_fmu.js +0 -1
  210. package/phanpy/dist/assets/icons/calendar-month-line-vommts2C.js +0 -1
  211. package/phanpy/dist/assets/icons/calendar-time-add-line-BU_DTlQk.js +0 -1
  212. package/phanpy/dist/assets/icons/camera-line-Cv69MEei.js +0 -1
  213. package/phanpy/dist/assets/icons/celebrate-line-B8DvIoi0.js +0 -1
  214. package/phanpy/dist/assets/icons/chart-bar-line-FK-QOmtF.js +0 -1
  215. package/phanpy/dist/assets/icons/chart-line-line-BGcJAFWw.js +0 -1
  216. package/phanpy/dist/assets/icons/chat-3-line-YFK2-hwz.js +0 -1
  217. package/phanpy/dist/assets/icons/check-circle-line-DsAxFr5-.js +0 -1
  218. package/phanpy/dist/assets/icons/clipboard-line-BBp364gJ.js +0 -1
  219. package/phanpy/dist/assets/icons/close-circle-line-CICdiuGz.js +0 -1
  220. package/phanpy/dist/assets/icons/close-line-DYrFWcjv.js +0 -1
  221. package/phanpy/dist/assets/icons/cloud-line-tiQAbah8.js +0 -1
  222. package/phanpy/dist/assets/icons/code-line-fAl8iiV3.js +0 -1
  223. package/phanpy/dist/assets/icons/comment-2-line-CR3g7WeB.js +0 -1
  224. package/phanpy/dist/assets/icons/copy-2-line-CLW69yF5.js +0 -1
  225. package/phanpy/dist/assets/icons/delete-2-line-B8xmbr4n.js +0 -1
  226. package/phanpy/dist/assets/icons/document-3-line-DBGNhAg2.js +0 -1
  227. package/phanpy/dist/assets/icons/document-line-C7mb9sb7.js +0 -1
  228. package/phanpy/dist/assets/icons/down-line-BGqSSThd.js +0 -1
  229. package/phanpy/dist/assets/icons/edit-4-line-DcgGaEhx.js +0 -1
  230. package/phanpy/dist/assets/icons/emoji-2-line-D8YgnM-y.js +0 -1
  231. package/phanpy/dist/assets/icons/exit-line-Dc07BS-x.js +0 -1
  232. package/phanpy/dist/assets/icons/external-link-line-BvpSkmjO.js +0 -1
  233. package/phanpy/dist/assets/icons/eye-2-line-CiWXx-f_.js +0 -1
  234. package/phanpy/dist/assets/icons/eye-close-line-BgTaSJ4P.js +0 -1
  235. package/phanpy/dist/assets/icons/filter-2-line-B_mR2usy.js +0 -1
  236. package/phanpy/dist/assets/icons/filter-line-C_nm-3cU.js +0 -1
  237. package/phanpy/dist/assets/icons/flag-1-line-C2HB05I0.js +0 -1
  238. package/phanpy/dist/assets/icons/forbid-circle-line-D3LTT9yd.js +0 -1
  239. package/phanpy/dist/assets/icons/formula-line-DLrt2d6j.js +0 -1
  240. package/phanpy/dist/assets/icons/grid-line-CgUmEikf.js +0 -1
  241. package/phanpy/dist/assets/icons/group-line-C5fnX0wU.js +0 -1
  242. package/phanpy/dist/assets/icons/hand-finger-2-line-BGgxbuL9.js +0 -1
  243. package/phanpy/dist/assets/icons/hashtag-line-CyTEEXBb.js +0 -1
  244. package/phanpy/dist/assets/icons/heart-crack-line-BZddbO2C.js +0 -1
  245. package/phanpy/dist/assets/icons/heart-line-CJ7K47J4.js +0 -1
  246. package/phanpy/dist/assets/icons/history-2-line-kWWvFzqv.js +0 -1
  247. package/phanpy/dist/assets/icons/history-line-bGQiwbTH.js +0 -1
  248. package/phanpy/dist/assets/icons/home-3-line-fRU1zeLG.js +0 -1
  249. package/phanpy/dist/assets/icons/information-line-iNk6-sOY.js +0 -1
  250. package/phanpy/dist/assets/icons/keyboard-line-DsCyuQNz.js +0 -1
  251. package/phanpy/dist/assets/icons/layout-4-line-CZkOX52Z.js +0 -1
  252. package/phanpy/dist/assets/icons/layout-5-line-B_znt5wO.js +0 -1
  253. package/phanpy/dist/assets/icons/left-line-eUw0tya4.js +0 -1
  254. package/phanpy/dist/assets/icons/lightning-line-DaKoDdix.js +0 -1
  255. package/phanpy/dist/assets/icons/link-2-line-DCqLmMA7.js +0 -1
  256. package/phanpy/dist/assets/icons/list-check-line-BAxosBh9.js +0 -1
  257. package/phanpy/dist/assets/icons/lock-line-CPoaXeUW.js +0 -1
  258. package/phanpy/dist/assets/icons/mail-line-B0P8aa1L.js +0 -1
  259. package/phanpy/dist/assets/icons/moon-line-CEqNAToP.js +0 -1
  260. package/phanpy/dist/assets/icons/more-1-fill-Dw2e5xab.js +0 -1
  261. package/phanpy/dist/assets/icons/more-3-line-BIiVjYTz.js +0 -1
  262. package/phanpy/dist/assets/icons/notification-line-BZZq2Gtu.js +0 -1
  263. package/phanpy/dist/assets/icons/pencil-line-CKh8-A1v.js +0 -1
  264. package/phanpy/dist/assets/icons/photo-album-line-Bnfg2u40.js +0 -1
  265. package/phanpy/dist/assets/icons/pin-line-DpTolyhs.js +0 -1
  266. package/phanpy/dist/assets/icons/play-fill-B0-G_8lX.js +0 -1
  267. package/phanpy/dist/assets/icons/qrcode-2-line-B6SPCaHn.js +0 -1
  268. package/phanpy/dist/assets/icons/quill-pen-line-B6LUrnCP.js +0 -1
  269. package/phanpy/dist/assets/icons/quote-left-fill-hQZ1cj_6.js +0 -1
  270. package/phanpy/dist/assets/icons/radar-line-CKyroTth.js +0 -1
  271. package/phanpy/dist/assets/icons/react-line-CoIKhhtK.js +0 -1
  272. package/phanpy/dist/assets/icons/refresh-2-line-D0mTF97S.js +0 -1
  273. package/phanpy/dist/assets/icons/right-line-B3KWJaqc.js +0 -1
  274. package/phanpy/dist/assets/icons/rocket-line-4KZl537D.js +0 -1
  275. package/phanpy/dist/assets/icons/round-fill-C69NTrZ0.js +0 -1
  276. package/phanpy/dist/assets/icons/round-line-B9k_1uo-.js +0 -1
  277. package/phanpy/dist/assets/icons/route-line-B28ReHcE.js +0 -1
  278. package/phanpy/dist/assets/icons/rows-4-line-CrBniagM.js +0 -1
  279. package/phanpy/dist/assets/icons/scan-line-DSf74YdP.js +0 -1
  280. package/phanpy/dist/assets/icons/search-2-line-B6geIU1z.js +0 -1
  281. package/phanpy/dist/assets/icons/settings-3-line-azJwK47w.js +0 -1
  282. package/phanpy/dist/assets/icons/settings-6-line-C_5zc5YR.js +0 -1
  283. package/phanpy/dist/assets/icons/share-2-line-D5MxWjrm.js +0 -1
  284. package/phanpy/dist/assets/icons/share-forward-line-Cp61fZzZ.js +0 -1
  285. package/phanpy/dist/assets/icons/sparkles-2-line-DYYyXTaW.js +0 -1
  286. package/phanpy/dist/assets/icons/sparkles-line-C2hY1s6I.js +0 -1
  287. package/phanpy/dist/assets/icons/time-line-BF3eet1v.js +0 -1
  288. package/phanpy/dist/assets/icons/transfer-4-line-oisfvTBw.js +0 -1
  289. package/phanpy/dist/assets/icons/translate-line-BFWol2Ce.js +0 -1
  290. package/phanpy/dist/assets/icons/unlock-line-X_x5vwv3.js +0 -1
  291. package/phanpy/dist/assets/icons/upload-3-line-DgzwUJeW.js +0 -1
  292. package/phanpy/dist/assets/icons/user-4-line-BVSynZCp.js +0 -1
  293. package/phanpy/dist/assets/icons/user-add-2-line-BlGS5Q8q.js +0 -1
  294. package/phanpy/dist/assets/icons/user-add-line-BbXdHG4z.js +0 -1
  295. package/phanpy/dist/assets/icons/user-edit-line-BFAHOV0X.js +0 -1
  296. package/phanpy/dist/assets/icons/user-follow-line-BXux0wSd.js +0 -1
  297. package/phanpy/dist/assets/icons/user-star-line-DdYlpHLU.js +0 -1
  298. package/phanpy/dist/assets/icons/user-warning-line-Cj43j0_E.js +0 -1
  299. package/phanpy/dist/assets/icons/user-x-line-DIDgNlPH.js +0 -1
  300. package/phanpy/dist/assets/icons/volume-line-Bs390KQt.js +0 -1
  301. package/phanpy/dist/assets/icons/volume-mute-line-DzlzIqFZ.js +0 -1
  302. package/phanpy/dist/assets/icons/walk-line-yNXfB-md.js +0 -1
  303. package/phanpy/dist/assets/icons/world-2-line-CenkGIwj.js +0 -1
  304. package/phanpy/dist/assets/icons/zoom-in-line-CaENUphf.js +0 -1
  305. package/phanpy/dist/assets/icons/zoom-out-line-BDNk0EWo.js +0 -1
  306. package/phanpy/dist/assets/instances-BNPptnn-.json +0 -333
  307. package/phanpy/dist/assets/locales/ar-SA-CfheRbCu.js +0 -1
  308. package/phanpy/dist/assets/locales/ca-ES-DHRW86Tz.js +0 -1
  309. package/phanpy/dist/assets/locales/cs-CZ-CKoqEuA1.js +0 -1
  310. package/phanpy/dist/assets/locales/de-DE-ByRPMRFy.js +0 -1
  311. package/phanpy/dist/assets/locales/eo-UY-CPC7wLCS.js +0 -1
  312. package/phanpy/dist/assets/locales/es-ES-dJa0Fd9t.js +0 -1
  313. package/phanpy/dist/assets/locales/eu-ES-C3djlPR2.js +0 -1
  314. package/phanpy/dist/assets/locales/fa-IR-DTT490Ck.js +0 -1
  315. package/phanpy/dist/assets/locales/fi-FI-B23MCPuz.js +0 -1
  316. package/phanpy/dist/assets/locales/fr-FR-BcaQsO5C.js +0 -1
  317. package/phanpy/dist/assets/locales/gl-ES-BR-CVKBN.js +0 -1
  318. package/phanpy/dist/assets/locales/he-IL-CfheRbCu.js +0 -1
  319. package/phanpy/dist/assets/locales/hu-HU-CfheRbCu.js +0 -1
  320. package/phanpy/dist/assets/locales/it-IT-C1136UFV.js +0 -1
  321. package/phanpy/dist/assets/locales/ja-JP-CRZmRAhD.js +0 -1
  322. package/phanpy/dist/assets/locales/kab-Cz8xH_TT.js +0 -1
  323. package/phanpy/dist/assets/locales/ko-KR-CG9UiY4o.js +0 -1
  324. package/phanpy/dist/assets/locales/lt-LT-CJcI6sHZ.js +0 -1
  325. package/phanpy/dist/assets/locales/nb-NO-CfheRbCu.js +0 -1
  326. package/phanpy/dist/assets/locales/nl-NL-DxN4Yc1t.js +0 -1
  327. package/phanpy/dist/assets/locales/oc-FR-CfheRbCu.js +0 -1
  328. package/phanpy/dist/assets/locales/pl-PL-DkwGXf6P.js +0 -1
  329. package/phanpy/dist/assets/locales/pseudo-LOCALE-CQ4XxkQ0.js +0 -1
  330. package/phanpy/dist/assets/locales/pt-BR-nGL0HdFS.js +0 -1
  331. package/phanpy/dist/assets/locales/pt-PT-Cp17aj2z.js +0 -1
  332. package/phanpy/dist/assets/locales/ru-RU-0Y8cIoaK.js +0 -1
  333. package/phanpy/dist/assets/locales/th-TH-CfheRbCu.js +0 -1
  334. package/phanpy/dist/assets/locales/tok-CfheRbCu.js +0 -1
  335. package/phanpy/dist/assets/locales/tr-TR-CfheRbCu.js +0 -1
  336. package/phanpy/dist/assets/locales/uk-UA-C5E3dFBf.js +0 -1
  337. package/phanpy/dist/assets/locales/zh-CN-BHJi7_et.js +0 -1
  338. package/phanpy/dist/assets/locales/zh-TW-CfheRbCu.js +0 -1
  339. package/phanpy/dist/assets/main-BdqNbG-a.js +0 -26
  340. package/phanpy/dist/assets/main-BdqNbG-a.js.map +0 -1
  341. package/phanpy/dist/assets/mock-home-DGhR__He.js +0 -2
  342. package/phanpy/dist/assets/mock-home-DGhR__He.js.map +0 -1
  343. package/phanpy/dist/assets/multi-column-ROck0NVt.jpg +0 -0
  344. package/phanpy/dist/assets/multi-hashtag-timeline-DH5INVHi.jpg +0 -0
  345. package/phanpy/dist/assets/nested-comments-thread-hv59kZLc.jpg +0 -0
  346. package/phanpy/dist/assets/polyfill-force-H8sUkLGp.js +0 -2
  347. package/phanpy/dist/assets/polyfill-force-H8sUkLGp.js.map +0 -1
  348. package/phanpy/dist/assets/pwa-viewport-ogKbF51K.js +0 -20
  349. package/phanpy/dist/assets/pwa-viewport-ogKbF51K.js.map +0 -1
  350. package/phanpy/dist/assets/style-B1iLPTPR.css +0 -2
  351. package/phanpy/dist/assets/temml-DuyIQMei.js +0 -97
  352. package/phanpy/dist/assets/temml-DuyIQMei.js.map +0 -1
  353. package/phanpy/dist/assets/tinyld.light.node-DtbBhLYC.js +0 -2
  354. package/phanpy/dist/assets/tinyld.light.node-DtbBhLYC.js.map +0 -1
  355. package/phanpy/dist/assets/year-in-posts-B2lywqdx.png +0 -0
  356. package/phanpy/dist/assets/year-in-posts-DJv_star.js +0 -3
  357. package/phanpy/dist/assets/year-in-posts-DJv_star.js.map +0 -1
  358. package/phanpy/dist/compose/index.html +0 -20
  359. package/phanpy/dist/favicon.ico +0 -0
  360. package/phanpy/dist/index.html +0 -73
  361. package/phanpy/dist/logo-192.png +0 -0
  362. package/phanpy/dist/logo-512.png +0 -0
  363. package/phanpy/dist/logo-badge-72.png +0 -0
  364. package/phanpy/dist/logo-maskable-512.png +0 -0
  365. package/phanpy/dist/logo-monochrome-512.png +0 -0
  366. package/phanpy/dist/logo-monochrome-maskable-512.png +0 -0
  367. package/phanpy/dist/manifest.webmanifest +0 -1
  368. package/phanpy/dist/og-image-2.jpg +0 -0
  369. package/phanpy/dist/og-image.png +0 -0
  370. package/phanpy/dist/robots.txt +0 -2
  371. package/phanpy/dist/sw.js +0 -2
  372. package/phanpy/dist/sw.js.map +0 -1
  373. package/phanpy/dist/version.json +0 -1
  374. package/run-agent.mjs +0 -899
  375. package/vendor/gate.cjs +0 -138
  376. package/vendor/idp-grant.cjs +0 -394
  377. package/vendor/jfetch.cjs +0 -16
  378. package/web/admin/actors.js +0 -147
  379. package/web/admin/bar.css +0 -126
  380. package/web/admin/bar.js +0 -99
  381. package/web/admin/client/client.js +0 -66
  382. package/web/admin/client/index.html +0 -69
  383. package/web/admin/client-pick.js +0 -79
  384. package/web/admin/common.js +0 -23
  385. package/web/admin/connections.js +0 -112
  386. package/web/admin/gateway.js +0 -133
  387. package/web/admin/group.js +0 -258
  388. package/web/admin/index.html +0 -612
  389. package/web/admin/notices-bar.js +0 -90
  390. package/web/admin/oauth-signin.mjs +0 -37
  391. package/web/admin/record.js +0 -393
  392. package/web/admin/setup/index.html +0 -257
  393. package/web/admin/setup/setup.js +0 -339
  394. package/web/admin/tokens.css +0 -68
  395. package/web/admin/upkeep.js +0 -177
  396. package/web/admin/window.css +0 -55
  397. package/web/admin/window.js +0 -136
  398. package/web/app/README.md +0 -81
  399. package/web/app/admin-facade.mjs +0 -588
  400. package/web/app/agent.mjs +0 -672
  401. package/web/app/atproto-browser.mjs +0 -86
  402. package/web/app/authorize_interaction.html +0 -56
  403. package/web/app/authorize_interaction.js +0 -69
  404. package/web/app/boot.mjs +0 -616
  405. package/web/app/deliver-relay.mjs +0 -129
  406. package/web/app/dist/boot.js +0 -34562
  407. package/web/app/dist/boot.js.map +0 -7
  408. package/web/app/dist/sw.js +0 -73456
  409. package/web/app/dist/sw.js.map +0 -7
  410. package/web/app/fediacct-browser.mjs +0 -146
  411. package/web/app/gateway-move.mjs +0 -60
  412. package/web/app/idb-kv.mjs +0 -35
  413. package/web/app/index.html +0 -258
  414. package/web/app/keys-browser.mjs +0 -71
  415. package/web/app/keystore.mjs +0 -82
  416. package/web/app/oidc-session.mjs +0 -6
  417. package/web/app/pod-auth.mjs +0 -248
  418. package/web/app/pod-remote.mjs +0 -84
  419. package/web/app/shims/_globals.mjs +0 -2
  420. package/web/app/shims/fedify-sig.mjs +0 -77
  421. package/web/app/shims/node-crypto.mjs +0 -113
  422. package/web/app/shims/node-fs.mjs +0 -17
  423. package/web/app/shims/node-path.mjs +0 -19
  424. package/web/app/shims/node-url.mjs +0 -7
  425. package/web/app/shims/prelude.js +0 -58
  426. package/web/app/shims/safefetch.mjs +0 -58
  427. package/web/app/shims/shapes-text.mjs +0 -8
  428. package/web/app/shims/web-push.mjs +0 -14
  429. package/web/app/signup.mjs +0 -357
  430. package/web/app/sw-src.mjs +0 -220
  431. package/web/app/update.js +0 -70
  432. package/web/front/admin.html +0 -55
  433. package/web/front/admin.js +0 -89
  434. package/web/front/install.sh +0 -69
  435. package/web/front/new-account.html +0 -35
  436. package/web/front/new-account.js +0 -13
  437. package/web/front/notices.html +0 -69
  438. package/web/front/notices.js +0 -107
  439. package/web/front/run.html +0 -64
  440. package/web/front/run.js +0 -221
  441. package/web/front/solid-oidc-client.js +0 -6
  442. package/web/icons/forum.svg +0 -11
  443. package/web/icons/gateway.svg +0 -14
  444. package/web/icons/local.svg +0 -11
@@ -1,519 +0,0 @@
1
- // notes.mjs — a note of the actor's own going up: the containers it needs
2
- // (media, and the owner-only one private posts live in), who it mentions,
3
- // publishing it, and editing it afterwards.
4
-
5
- import crypto from 'node:crypto';
6
- import * as wire from '../wire.mjs';
7
- import * as wireQuotes from '../wire-quotes.mjs';
8
- import * as containers from '../../pod/containers.mjs';
9
- import * as podNotes from '../../pod/notes.mjs';
10
-
11
- // Media container on the remote pod — public-Read like notes, created lazily
12
- // at first upload (idempotent; the flag only saves round-trips).
13
- // Ask before writing. `_mediaReady` is per PROCESS, and a service worker is
14
- // restarted whenever the browser feels like it — so on the browser build this
15
- // re-created a container that has existed since sign-up, and rewrote its ACL,
16
- // on every restart. A HEAD is one request and usually the only one.
17
- export async function ensureMediaContainer(publisher) {
18
- if (publisher._mediaReady) return;
19
- if (await containers.exists(publisher.remote, publisher.urls.media)) { publisher._mediaReady = true; return; }
20
- await containers.provisionPublic(publisher.remote, publisher.urls.media);
21
- publisher._mediaReady = true;
22
- }
23
-
24
- // Whether the canary this class writes is already there. Only a definite
25
- // "yes" counts: anything else falls through to the write, which is
26
- // idempotent anyway, so a failed probe costs a request and never correctness.
27
- export function containerExists(publisher, base) { return containers.exists(publisher.remote, base); }
28
-
29
- // The owner-only container that followers-only and direct posts live in.
30
- // Its ACL is set once; every note under it inherits.
31
- export async function ensurePrivateContainer(publisher) {
32
- if (publisher._privateContainer) return;
33
- // Same reasoning as ensureMediaContainer: probe before provisioning, so a
34
- // worker restart is not a re-provision. The ACL is NOT skipped on the
35
- // strength of the container existing alone — ensurePrivateAcls re-checks
36
- // that separately on every connect, which is the control that matters here.
37
- if (await containers.exists(publisher.remote, publisher.urls.privateNotes)) { publisher._privateContainer = true; return; }
38
- await containers.provisionOwnerOnly(publisher.remote, publisher.urls.privateNotes);
39
- publisher._privateContainer = true;
40
- }
41
-
42
- // Whether the pod actually enforces that ACL: a bare, unauthenticated read
43
- // of the private container's canary must be refused. Returns true, or the
44
- // reason private posts stay off. A definite answer is cached for the run; a
45
- // network failure is not, so a pod that was briefly unreachable is asked
46
- // again rather than refused forever.
47
- export async function privateReady(publisher) {
48
- if (publisher._privateVerdict !== undefined) return publisher._privateVerdict;
49
- try {
50
- await publisher.ensurePrivateContainer();
51
- // The probe is credential-free and bypasses RemotePod's url map, so a
52
- // fronted identity must map the advertised private container back to the
53
- // pod itself — the ACL we are testing is the pod's.
54
- const pn = publisher.urls.toPod ? publisher.urls.toPod(publisher.urls.privateNotes) : publisher.urls.privateNotes;
55
- const verdict = await containers.probePrivateEnforcement(publisher.probeFetch || fetch, pn + '.keep');
56
- publisher._privateVerdict = verdict === true ? true : `${verdict} — private posts stay off it`;
57
- return publisher._privateVerdict;
58
- } catch (e) {
59
- return `could not verify that the pod protects private posts (${e.message})`;
60
- }
61
- }
62
-
63
- // Compose → wire note on remote pod + RDF truth locally + deliver Create.
64
- // Who this text mentions, resolved. A mention nobody can resolve stays plain
65
- // text rather than failing the post. The text itself decides: trim a handle
66
- // out and that person is not notified, which is what every fediverse client
67
- // leads people to expect. A Group named in the parent is the one thing
68
- // carried forward regardless — drop it and the group stops carrying the
69
- // thread.
70
- export async function mentionsFor(publisher, content, inReplyTo) {
71
- const inText = new Set(wire.mentionsIn(content));
72
- const carried = inReplyTo
73
- ? (publisher.store.getStatuses().find(s => s.noteId === inReplyTo)?.mentions || [])
74
- .map(m => String(m.name || '').replace(/^@/, '')).filter(Boolean)
75
- : [];
76
- const mentions = [];
77
- for (const handle of [...new Set([...inText, ...carried])]) {
78
- if (!publisher.resolveMention) break;
79
- const doc = await publisher.resolveMention(handle).catch(() => null);
80
- if (!doc?.id) { publisher.log(`mention @${handle} did not resolve — left as text`); continue; }
81
- if (!inText.has(handle) && doc.type !== 'Group') continue; // author trimmed them out
82
- // Naming yourself still tags you; it never delivers to your own inbox (§7.1).
83
- const self = doc.id === publisher.urls.actor;
84
- mentions.push({ handle, actor: doc.id, page: doc.url || null, inbox: self ? null : doc.endpoints?.sharedInbox || doc.inbox });
85
- }
86
- return mentions;
87
- }
88
-
89
- // Who a reply is answering. Their server gets the post whether or not the
90
- // text names them — that is how the thread stays whole where they are — but
91
- // no Mention tag is added, so a handle the author trimmed out still means no
92
- // notification. Nothing for a direct post: it goes to whom it names. The
93
- // parent is read from the timeline row; a parent the agent never held (a
94
- // client-to-server reply to any address) is fetched to find its author.
95
- export async function replyTarget(publisher, inReplyTo, mentions = [], visibility = 'public') {
96
- if (!inReplyTo || visibility === 'direct' || !publisher.resolveActor) return null;
97
- const parent = publisher.store.getStatuses().find(s => s.noteId === inReplyTo);
98
- let actor = parent?.actor || null;
99
- if (!parent) {
100
- const doc = await publisher.resolveActor(inReplyTo).catch(() => null);
101
- const by = doc?.attributedTo;
102
- actor = typeof by === 'string' ? by : (Array.isArray(by) ? by[0] : by)?.id || null;
103
- if (typeof actor === 'object') actor = actor?.id || null;
104
- }
105
- if (!actor || actor === publisher.urls.actor) return null;
106
- if (mentions.some(m => m.actor === actor)) return null;
107
- if (publisher.store.isBlocked?.(actor)) return null;
108
- const doc = await publisher.resolveActor(actor).catch(() => null);
109
- const inbox = doc?.endpoints?.sharedInbox || doc?.inbox || null;
110
- if (!inbox) publisher.log(`reply: no inbox found for ${actor} — they get it only through followers`);
111
- return { actor, inbox };
112
- }
113
-
114
- // Inboxes for actors a client addressed by id — the people in a post's
115
- // to/cc and the ones in bto/bcc, who are delivered to and never listed.
116
- export async function inboxesFor(publisher, actorIds = []) {
117
- const out = [];
118
- for (const id of [...new Set(actorIds.filter(a => typeof a === 'string' && a && a !== publisher.urls.actor))]) {
119
- if (!publisher.resolveActor) break;
120
- const doc = await publisher.resolveActor(id).catch(() => null);
121
- const inbox = doc?.endpoints?.sharedInbox || doc?.inbox;
122
- if (inbox) out.push(inbox);
123
- else publisher.log(`addressed actor ${id} has no inbox the agent can find — not delivered to`);
124
- }
125
- return out;
126
- }
127
-
128
- // A direct post goes only to the people it names, so one that names nobody
129
- // the agent can find would be kept in the private container and delivered
130
- // to no one, with nothing said. Refused instead, before anything is written,
131
- // with the handles that did not resolve — that is what the sender can act on.
132
- export function assertDirectAddressed(content, mentions) {
133
- const wanted = [...new Set(wire.mentionsIn(content))];
134
- const missing = wanted.filter((h) => !mentions.some((m) => m.handle === h));
135
- let why = null;
136
- if (!wanted.length) why = 'a direct message names who it is for — mention them as @name@host';
137
- else if (missing.length) why = `could not find ${missing.map((h) => '@' + h).join(', ')} — the direct message was not sent`;
138
- if (!why) return;
139
- const e = new Error(why);
140
- e.code = 'unaddressed';
141
- throw e;
142
- }
143
-
144
- // A client may name the document it is creating (the Slug of a client-to-server
145
- // POST). Only a plain name is taken, and only when nothing is there already;
146
- // otherwise the agent mints one as it always has.
147
- const SLUG_OK = /^[A-Za-z0-9._-]{1,64}$/u;
148
- export const safeSlug = (s) => (typeof s === 'string' && SLUG_OK.test(s) && !/^\.+$/u.test(s) ? s : null);
149
- export async function slugFor(publisher, container, wanted, published) {
150
- const name = safeSlug(wanted);
151
- if (name && !(await podNotes.read(publisher.remote, container + name).catch(() => null))) return name;
152
- return published.slice(0, 10) + '-' + crypto.randomBytes(4).toString('hex');
153
- }
154
-
155
- // A message from this account to itself, for something its owner must hear
156
- // and has no other way to: a post an app sent through the outbox door that the
157
- // account then refused, after the door had already told the app "created".
158
- // Addressed to the owner alone and kept in the private container, so it shows
159
- // in the owner's apps as a direct message and a mention, and nowhere else.
160
- export async function noteToSelf(publisher, text) {
161
- const { urls } = publisher;
162
- const published = new Date().toISOString();
163
- const slug = published.slice(0, 10) + '-' + crypto.randomBytes(4).toString('hex');
164
- const id = urls.privateNotes + slug;
165
- const content = wire.contentHtml(text);
166
- await podNotes.write(publisher.remote, id, { '@context': wire.AS_CTX, id, type: 'Note',
167
- attributedTo: urls.actor, to: [urls.actor], published, content })
168
- .catch(e => publisher.log(`note to self kept here only — the pod would not take it: ${e.message}`));
169
- publisher.store.addStatus({ noteId: id, actor: urls.actor, content, text, published,
170
- kind: 'post', visibility: 'direct', slug });
171
- publisher.store.addNotification({ type: 'mention', actor: urls.actor, noteId: id });
172
- return id;
173
- }
174
-
175
- // An app's edit of an object that is not a Note — an annotation, a poll —
176
- // changes only the fields it sends (§6.3); a field sent as null is removed.
177
- // What the object is, who made it and when, where it lives and who it is
178
- // addressed to stay as they are, and so do a poll's choices and its count.
179
- const FIXED = new Set(['@context', 'id', 'type', 'attributedTo', 'published', 'to', 'cc', 'bto', 'bcc',
180
- 'audience', 'oneOf', 'anyOf', 'endTime', 'closed', 'votersCount', 'replies']);
181
- export async function updateObject(publisher, s, patch, { updated = new Date().toISOString() } = {}) {
182
- const { urls } = publisher;
183
- const doc = await podNotes.read(publisher.remote, s.noteId);
184
- if (!doc?.id) throw new Error('the object is not on the pod to change');
185
- const next = { ...doc };
186
- for (const [k, v] of Object.entries(patch || {})) {
187
- if (FIXED.has(k)) continue;
188
- if (v === null) delete next[k]; else next[k] = v;
189
- }
190
- if (typeof next.content === 'string') next.content = wire.sanitizeHtml(next.content);
191
- next.updated = updated;
192
- await podNotes.write(publisher.remote, doc.id, next);
193
- await podNotes.writeCreate(publisher.remote, wire.createActivityId(doc.id), wire.createActivity(next, urls));
194
- publisher.store.updateStatus(s.noteId, { content: rowContent(next), editedAt: updated });
195
- const update = wire.updateActivity(next, urls);
196
- const inboxes = [...new Set([
197
- ...(s.visibility === 'direct' ? [] : publisher.store.getContacts().followers.map(f => f.sharedInbox || f.inbox)),
198
- ...(await inboxesFor(publisher, [...(s.addressed || []), ...(s.replyActor ? [s.replyActor] : [])])),
199
- ].filter(Boolean))];
200
- await publisher.deliverer.deliverToAll(inboxes, update);
201
- if (publisher.store.read('outbox.json', []).includes(s.noteId)) await publisher.recordOutbox(update);
202
- return update;
203
- }
204
-
205
- // What the author's own timeline shows for an object: its content, else its
206
- // text under one of the names other vocabularies use, else a link to it.
207
- export function rowContent(obj) {
208
- if (typeof obj?.content === 'string' && obj.content.trim()) return wire.sanitizeHtml(obj.content);
209
- const plain = [obj?.bodyValue, obj?.name, obj?.summary].find((v) => typeof v === 'string' && v.trim());
210
- if (plain) return wire.contentHtml(plain);
211
- const esc = (v) => String(v).replace(/[&<>"]/gu, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c]));
212
- return `<p><a href="${esc(obj?.id || '')}">${esc(obj?.type || 'object')}</a></p>`;
213
- }
214
-
215
- // `quote`: the post this one quotes, `{ id, actor }` — the quoted author is
216
- // asked to allow it (FEP-044f). A quote of one of this actor's own posts is
217
- // allowed on the spot.
218
- export async function publishNote(publisher, content, { inReplyTo, attachments, visibility = 'public', spoilerText = null, sensitive = false, slug: wanted = null, also = [], deliverTo = [], quote = null } = {}) {
219
- const { urls } = publisher;
220
- const priv = visibility === 'private' || visibility === 'direct';
221
- if (priv) {
222
- const ready = await publisher.privateReady();
223
- if (ready !== true) throw new Error(ready);
224
- }
225
- const published = new Date().toISOString();
226
- const slug = await slugFor(publisher, priv ? urls.privateNotes : urls.notes, wanted, published);
227
- const mentions = await publisher._mentionsFor(content, inReplyTo);
228
- // Addressed by id (a client-to-server post) it is addressed already; only a
229
- // post that names nobody but in its text has to name them there.
230
- if (visibility === 'direct' && !also.length && !deliverTo.length) assertDirectAddressed(content, mentions);
231
- const reply = await replyTarget(publisher, inReplyTo, mentions, visibility);
232
- const selfQuote = quote?.id && quote.actor === urls.actor;
233
- const quoted = quote?.id ? {
234
- id: quote.id,
235
- // Our own post: the authorization is minted here, before the note goes out.
236
- authorization: selfQuote ? await grantQuote(publisher, quote.id, (priv ? urls.privateNotes : urls.notes) + slug) : null,
237
- } : null;
238
- const note = wire.noteDoc({ urls, slug, content, published, inReplyTo, attachments, mentions,
239
- visibility, summary: spoilerText, sensitive, container: priv ? urls.privateNotes : urls.notes,
240
- also: [...also, ...(reply ? [reply.actor] : []), ...(quote?.actor && !selfQuote ? [quote.actor] : [])], quote: quoted });
241
- const addressedInboxes = await inboxesFor(publisher, [...also, ...deliverTo, ...(quote?.actor && !selfQuote ? [quote.actor] : [])]);
242
-
243
- await podNotes.write(publisher.remote, note.id, note);
244
- // Empty, but present: a dangling `replies` that 404s is worse than none.
245
- await podNotes.writeEmptyReplies(publisher.remote, wire.repliesId(note.id),
246
- wire.collection(wire.repliesId(note.id), []));
247
- // The outbox is the PUBLIC index; a private or direct post is not in it.
248
- if (!priv) await publisher.recordOutbox(note.id);
249
-
250
- const request = quote?.id && !selfQuote
251
- ? wireQuotes.quoteRequestActivity({ urls, note, quoted: quote.id, quotedActor: quote.actor, serial: Date.now() })
252
- : null;
253
- publisher.store.addStatus({
254
- noteId: note.id, actor: urls.actor, content: note.content, published, inReplyTo,
255
- kind: 'post', slug, text: content, visibility,
256
- // Everyone a client named, blind copies included: an edit or a deletion
257
- // must reach them too. Kept here only, never published. `named` is the
258
- // visible part, which an edit addresses again.
259
- ...(also.length || deliverTo.length ? { addressed: [...new Set([...also, ...deliverTo])] } : {}),
260
- ...(also.length ? { named: [...also] } : {}),
261
- ...(spoilerText ? { spoiler: spoilerText } : {}),
262
- ...(note.sensitive ? { sensitive: true } : {}),
263
- ...(attachments?.length ? { attachments } : {}),
264
- ...rowTags(note),
265
- ...(quote?.id ? {
266
- quote: quote.id,
267
- quoteState: selfQuote ? 'accepted' : 'pending',
268
- ...(quoted.authorization ? { quoteAuthorization: quoted.authorization } : {}),
269
- ...(request ? { quoteRequest: { id: request.id, actor: quote.actor } } : {}),
270
- } : {}),
271
- });
272
- // The row is what the author's own timeline reads. Land it before answering
273
- // — the same PUT the debounce would send in 300 ms — so a worker killed or a
274
- // write refused after the answer cannot leave a post that stands on the pod
275
- // and reached followers but never shows to its author.
276
- if (await publisher.store.commit?.() === false) publisher.log(`post published but its timeline row was refused: ${note.id}`);
277
-
278
- const create = wire.createActivity(note, urls);
279
- // Published as its own document: a group that carries this post wraps the
280
- // whole activity, and the receiving server resolves it by fetching this id.
281
- // It inherits the notes container's public-Read acl:default.
282
- await podNotes.writeCreate(publisher.remote, create.id, create);
283
- const contacts = publisher.store.getContacts();
284
- // A direct post goes to the people it names and to nobody else.
285
- const inboxes = [...new Set([
286
- ...(visibility === 'direct' ? [] : contacts.followers.map(f => f.sharedInbox || f.inbox)),
287
- ...mentions.map(m => m.inbox),
288
- reply?.inbox,
289
- ...addressedInboxes,
290
- ].filter(Boolean))];
291
- await publisher.deliverer.deliverToAll(inboxes, create);
292
- publisher.log(`note published: ${note.id} → ${inboxes.length} inbox(es)`);
293
- // The quoted author is asked after the note is out: their Accept names the
294
- // note, so it has to exist where they will look for it.
295
- if (request) {
296
- const [inbox] = await inboxesFor(publisher, [quote.actor]);
297
- if (inbox) await publisher.deliverer.deliver(inbox, request);
298
- else publisher.log(`quote of ${quote.id}: no inbox found for ${quote.actor} — the quote stays pending`);
299
- }
300
-
301
- // Bluesky mirror: PUBLIC posts only — unlisted, followers-only and direct
302
- // are never carried off the fediverse. A mirror failure never fails the
303
- // post; it is logged and recorded on the status for the admin page.
304
- if (visibility === 'public' && publisher.atproto?.connected()
305
- && publisher.store.getConfig()?.atproto?.crossPost) {
306
- try {
307
- const mirror = await publisher.atproto.crossPost(
308
- { text: content, published, attachments }, { noteUrl: note.id });
309
- publisher.store.updateStatus(note.id, { atproto: mirror });
310
- publisher.log(`cross-posted to bluesky: ${mirror.uri}${mirror.truncated ? ' (truncated, links back)' : ''}`);
311
- } catch (e) {
312
- publisher.store.updateStatus(note.id, { atproto: { error: e.message } });
313
- publisher.log(`bluesky cross-post failed: ${e.message}`);
314
- }
315
- }
316
- return note;
317
- }
318
-
319
- // An edit keeps the note's id, slug and published time; `updated` is the
320
- // edit's own stamp. The pod documents are overwritten in place — the Create
321
- // too, so a group's Announce resolves to the edited text — and an Update
322
- // goes everywhere the Create went.
323
- // Any object as a post: what a client-to-server Create carries when it is not
324
- // a Note or a Question — a Web Annotation, say. Stored as sent, with its own
325
- // context, under this actor; the Create around it is what followers receive.
326
- // Servers that know the type show it; the rest ignore it, which is the
327
- // expected outcome.
328
- //
329
- // An object already living on this pod (an id under it) is not copied: the
330
- // Create names it where it is. Anything else is written into the notes
331
- // container under the client's slug when it is free, else a minted one.
332
- export async function publishObject(publisher, object, { visibility = 'public', slug: wanted = null, also = [], deliverTo = [] } = {}) {
333
- const { urls } = publisher;
334
- const priv = visibility === 'private' || visibility === 'direct';
335
- if (priv) {
336
- const ready = await publisher.privateReady();
337
- if (ready !== true) throw new Error(ready);
338
- }
339
- const published = new Date().toISOString();
340
- const container = priv ? urls.privateNotes : urls.notes;
341
- const pod = publisher.config?.remotePod || urls.base;
342
- const ownId = typeof object.id === 'string' && /^https?:\/\//u.test(object.id) && object.id.startsWith(pod)
343
- ? object.id : null;
344
- const name = await slugFor(publisher, container, wanted, published);
345
- const addressed = wire.addressing(urls, visibility, visibility === 'direct' ? also : []);
346
- if (visibility !== 'direct') addressed.cc = [...new Set([...addressed.cc, ...also])];
347
- let doc = null;
348
- const id = ownId || container + name;
349
- if (!ownId) {
350
- // bto and bcc are for delivery alone: they never appear in what is
351
- // stored, served or sent (ActivityPub §6).
352
- const { bto, bcc, ...sent } = object; // eslint-disable-line no-unused-vars
353
- doc = { ...sent, id, attributedTo: urls.actor, published: object.published || published, to: addressed.to, cc: addressed.cc };
354
- if (!doc['@context']) doc['@context'] = wire.AS_CTX;
355
- if (typeof doc.content === 'string') doc.content = wire.sanitizeHtml(doc.content);
356
- await podNotes.write(publisher.remote, id, doc);
357
- }
358
- if (!priv) await publisher.recordOutbox(id);
359
- const row = doc || object;
360
- publisher.store.addStatus({
361
- noteId: id, actor: urls.actor, content: rowContent(row), published: row.published || published,
362
- kind: 'post', slug: name, visibility,
363
- ...(also.length || deliverTo.length ? { addressed: [...new Set([...also, ...deliverTo])] } : {}),
364
- ...(also.length ? { named: [...also] } : {}),
365
- });
366
- if (await publisher.store.commit?.() === false) publisher.log(`object published but its timeline row was refused: ${id}`);
367
-
368
- // The Create sits beside the object; for an object living elsewhere on the
369
- // pod it sits in the notes container, whose ACL is public Read.
370
- const createId = doc ? wire.createActivityId(id) : wire.createActivityId(container + name);
371
- const create = {
372
- '@context': wire.AS_CTX, id: createId, type: 'Create', actor: urls.actor,
373
- published: row.published || published, to: addressed.to, cc: addressed.cc,
374
- object: doc || id,
375
- };
376
- await podNotes.writeCreate(publisher.remote, create.id, create);
377
- const contacts = publisher.store.getContacts();
378
- const inboxes = [...new Set([
379
- ...(visibility === 'direct' ? [] : contacts.followers.map((f) => f.sharedInbox || f.inbox)),
380
- ...(await inboxesFor(publisher, [...also, ...deliverTo])),
381
- ].filter(Boolean))];
382
- await publisher.deliverer.deliverToAll(inboxes, create);
383
- publisher.log(`${row.type || 'object'} published: ${id} → ${inboxes.length} inbox(es)`);
384
- return { id, createId, copied: !ownId };
385
- }
386
-
387
- // What the author's timeline row keeps of a note's tags: the people it
388
- // mentions and the hashtags it carries, each as the client-facing shape.
389
- export function rowTags(note) {
390
- const tags = [].concat(note.tag || []);
391
- const mentions = tags.filter(t => t.type === 'Mention').map(t => ({ href: t.href, name: t.name }));
392
- const hashtags = tags.filter(t => t.type === 'Hashtag').map(t => ({ name: String(t.name).replace(/^#/, ''), url: t.href }));
393
- return { ...(mentions.length ? { mentions } : {}), ...(hashtags.length ? { tags: hashtags } : {}) };
394
- }
395
-
396
- export async function updateNote(publisher, s, { content, spoilerText = null, sensitive = null, attachments = null, updated = new Date().toISOString() } = {}) {
397
- const { urls } = publisher;
398
- const inText = new Set(wire.mentionsIn(content));
399
- const mentions = [];
400
- for (const handle of inText) {
401
- if (!publisher.resolveMention) break;
402
- const doc = await publisher.resolveMention(handle).catch(() => null);
403
- if (!doc?.id) { publisher.log(`mention @${handle} did not resolve — left as text`); continue; }
404
- mentions.push({ handle, actor: doc.id, page: doc.url || null,
405
- inbox: doc.id === urls.actor ? null : doc.endpoints?.sharedInbox || doc.inbox });
406
- }
407
- const atts = attachments ?? s.attachments ?? [];
408
- // The note stays in the container its visibility put it in; a recovered
409
- // post may carry no slug, but the note id already contains it.
410
- const container = String(s.noteId).startsWith(urls.privateNotes) ? urls.privateNotes : urls.notes;
411
- const slug = s.slug || String(s.noteId).slice(container.length);
412
- const reply = await replyTarget(publisher, s.inReplyTo, mentions, s.visibility || 'public');
413
- const note = wire.noteDoc({
414
- urls, slug, content, published: s.published, inReplyTo: s.inReplyTo,
415
- attachments: atts, mentions, visibility: s.visibility || 'public',
416
- summary: spoilerText, sensitive: sensitive ?? !!s.sensitive, updated, container,
417
- also: [...new Set([...(s.named || []), ...(reply ? [reply.actor] : [])])],
418
- quote: rowQuote(s),
419
- });
420
- await podNotes.write(publisher.remote, note.id, note);
421
- await podNotes.writeCreate(publisher.remote, wire.createActivityId(note.id), wire.createActivity(note, urls));
422
- const patched = publisher.store.updateStatus(s.noteId, {
423
- content: note.content, text: content, editedAt: updated,
424
- spoiler: spoilerText || undefined,
425
- sensitive: note.sensitive || undefined,
426
- attachments: atts.length ? atts : undefined,
427
- mentions: rowTags(note).mentions,
428
- tags: rowTags(note).tags,
429
- });
430
- const update = wire.updateActivity(note, urls);
431
- const contacts = publisher.store.getContacts();
432
- const inboxes = [...new Set([
433
- ...(s.visibility === 'direct' ? [] : contacts.followers.map(f => f.sharedInbox || f.inbox)),
434
- ...mentions.map(m => m.inbox),
435
- reply?.inbox,
436
- // everyone a client named or blind-copied, and a quoted author
437
- ...(await inboxesFor(publisher, [...(s.addressed || []), ...(s.quoteRequest?.actor ? [s.quoteRequest.actor] : [])])),
438
- ].filter(Boolean))];
439
- await publisher.deliverer.deliverToAll(inboxes, update);
440
- // The edit goes on the record beside the Create, for a post the outbox lists:
441
- // a reader catching up from the outbox otherwise keeps the words it first saw.
442
- if (publisher.store.read('outbox.json', []).includes(s.noteId)) await publisher.recordOutbox(update);
443
- publisher.log(`note edited: ${note.id} → ${inboxes.length} inbox(es)`);
444
- return patched;
445
- }
446
-
447
- // What a note's document says about its quote, read off the timeline row: the
448
- // quoted post and, once granted, the authorization. A quote the author
449
- // refused is no longer said.
450
- export function rowQuote(s) {
451
- if (!s?.quote || s.quoteState === 'rejected') return null;
452
- return { id: s.quote, ...(s.quoteAuthorization ? { authorization: s.quoteAuthorization } : {}) };
453
- }
454
-
455
- // The note's document again, unchanged in what it says, changed in what it
456
- // says about its quote. Not an edit — no `updated` stamp — so nobody shows it
457
- // as edited; the Update goes where the Create went, plus to the quoted author.
458
- async function restateNote(publisher, s, { quote }) {
459
- const { urls } = publisher;
460
- const content = s.text ?? '';
461
- const mentions = await publisher._mentionsFor(content, s.inReplyTo);
462
- const container = String(s.noteId).startsWith(urls.privateNotes) ? urls.privateNotes : urls.notes;
463
- const slug = s.slug || String(s.noteId).slice(container.length);
464
- const reply = await replyTarget(publisher, s.inReplyTo, mentions, s.visibility || 'public');
465
- const note = wire.noteDoc({
466
- urls, slug, content, published: s.published, inReplyTo: s.inReplyTo,
467
- attachments: s.attachments || [], mentions, visibility: s.visibility || 'public',
468
- summary: s.spoiler || null, sensitive: !!s.sensitive, updated: s.editedAt || null, container,
469
- also: [...(reply ? [reply.actor] : []), ...(s.quoteRequest?.actor ? [s.quoteRequest.actor] : [])],
470
- quote,
471
- });
472
- await podNotes.write(publisher.remote, note.id, note);
473
- await podNotes.writeCreate(publisher.remote, wire.createActivityId(note.id), wire.createActivity(note, urls));
474
- const update = wire.updateActivity(note, urls, { serial: Date.now() });
475
- const contacts = publisher.store.getContacts();
476
- const inboxes = [...new Set([
477
- ...(s.visibility === 'direct' ? [] : contacts.followers.map(f => f.sharedInbox || f.inbox)),
478
- ...mentions.map(m => m.inbox),
479
- reply?.inbox,
480
- ...(await inboxesFor(publisher, s.quoteRequest?.actor ? [s.quoteRequest.actor] : [])),
481
- ].filter(Boolean))];
482
- await publisher.deliverer.deliverToAll(inboxes, update);
483
- return inboxes.length;
484
- }
485
-
486
- // The quoted author said yes: their authorization goes onto the note, and
487
- // everyone who has the note is told.
488
- export async function authorizeQuote(publisher, s, authorizationId) {
489
- const patched = publisher.store.updateStatus(s.noteId, { quoteState: 'accepted', quoteAuthorization: authorizationId });
490
- const n = await restateNote(publisher, patched || s, { quote: { id: s.quote, authorization: authorizationId } });
491
- publisher.log(`quote allowed by ${s.quoteRequest?.actor || 'the author'}: ${s.noteId} → ${n} inbox(es)`);
492
- return patched;
493
- }
494
-
495
- // The quoted author said no: the note stops naming their post.
496
- export async function withdrawQuote(publisher, s) {
497
- const patched = publisher.store.updateStatus(s.noteId, { quoteState: 'rejected', quoteAuthorization: undefined });
498
- const n = await restateNote(publisher, patched || s, { quote: null });
499
- publisher.log(`quote refused by ${s.quoteRequest?.actor || 'the author'}: ${s.noteId} → ${n} inbox(es)`);
500
- return patched;
501
- }
502
-
503
- // Somebody may quote one of this actor's posts: the authorization is written
504
- // beside the post — public-Read, like the post — and remembered on its row,
505
- // so a receiver that checks the quote finds it and a re-asked question gets
506
- // the same answer. Returns the authorization's id.
507
- const MAX_QUOTED_BY = 200;
508
- export async function grantQuote(publisher, noteId, instrumentId) {
509
- const { urls } = publisher;
510
- const id = wireQuotes.quoteAuthorizationId(noteId, instrumentId);
511
- await podNotes.write(publisher.remote, id, wireQuotes.quoteAuthorizationDoc({ urls, id, instrument: instrumentId, target: noteId }));
512
- const s = publisher.store.getStatuses().find(x => x.noteId === noteId);
513
- if (s) {
514
- const quotedBy = (s.quotedBy || []).filter(q => q.note !== instrumentId);
515
- quotedBy.unshift({ note: instrumentId, authorization: id });
516
- publisher.store.updateStatus(noteId, { quotedBy: quotedBy.slice(0, MAX_QUOTED_BY) });
517
- }
518
- return id;
519
- }
@@ -1,143 +0,0 @@
1
- // own.mjs — the outbox as its owner reads it, and the owner's liked list.
2
- //
3
- // The public outbox is what a stranger may see: posts and boosts, and what
4
- // withdrew them. ActivityPub's outbox is every message the actor produced,
5
- // filtered by who is asking (§5.1), so its owner is owed the rest as well —
6
- // likes, follows, undos, follower decisions, followers-only and direct posts.
7
- // That view lives in the private container, whose owner-only rule it inherits,
8
- // and is what the outbox address answers its signed-in owner.
9
- //
10
- // Everything this actor sends passes through the deliverer, which hands each
11
- // fresh activity here (Deliverer.onSent), so nothing has to remember to record
12
- // itself. What is never sent — a block or a pin posted by a client — is
13
- // recorded by the client-to-server dispatcher.
14
- //
15
- // `liked` (§5.5) is the objects this actor has liked and not taken back,
16
- // kept beside it under the same rule.
17
-
18
- import crypto from 'node:crypto';
19
- import * as wire from '../wire.mjs';
20
- import * as collection from '../../pod/collection.mjs';
21
-
22
- const OWN = 'outbox-own.json';
23
- const LIKED = 'liked.json';
24
- // The owner's view keeps its newest entries; older ones leave the view only —
25
- // the public outbox, and everything the account did, are untouched.
26
- export const OWN_MAX = 5000;
27
-
28
- // A Create is kept by its object's id, as the public outbox keeps it, so the
29
- // two can be matched when a post is withdrawn; anything else is kept whole.
30
- const localForm = (a) => (a?.type === 'Create' && (typeof a.object === 'string' ? a.object : a.object?.id)) || a;
31
-
32
- export function recordOwn(publisher, activity) {
33
- const { urls, store } = publisher;
34
- if (!activity?.type || activity.actor !== urls.actor) return false;
35
- const item = localForm(activity);
36
- const id = wire.outboxItemId(item);
37
- const own = store.read(OWN, []);
38
- if (id && own.some(i => wire.outboxItemId(i) === id)) return false; // a retry, or said twice
39
- own.unshift(item);
40
- store.write(OWN, own.slice(0, OWN_MAX));
41
- if (activity.type === 'Like' || (activity.type === 'Undo' && activity.object?.type === 'Like')) {
42
- backfillLiked(publisher);
43
- const object = wire.outboxItemId(activity.type === 'Like' ? activity.object : activity.object.object);
44
- if (object) {
45
- const liked = store.read(LIKED, []).filter(o => o !== object);
46
- if (activity.type === 'Like') liked.unshift(object);
47
- store.write(LIKED, liked);
48
- }
49
- }
50
- schedule(publisher);
51
- return true;
52
- }
53
-
54
- // An account that liked things before the list existed: the likes it still
55
- // holds (a timeline row marked favourited, with the Like that was sent) are
56
- // its liked list to begin with. Once only — a list that exists, even empty,
57
- // is the record from then on.
58
- export function backfillLiked(publisher) {
59
- const { store } = publisher;
60
- if (store.read(LIKED, null) !== null) return 0;
61
- const liked = (store.getStatuses?.() || [])
62
- .filter(s => s.favourited && s.noteId)
63
- .sort((x, y) => String(y.published || '').localeCompare(String(x.published || '')))
64
- .map(s => s.noteId);
65
- store.write(LIKED, [...new Set(liked)]);
66
- schedule(publisher);
67
- return liked.length;
68
- }
69
-
70
- // A post deleted or a boost withdrawn leaves the owner's view as it leaves the
71
- // public one; the Delete or Undo that did it is already recorded, having been
72
- // sent.
73
- export function unrecordOwn(publisher, matches) {
74
- const own = publisher.store.read(OWN, []);
75
- const kept = own.filter(i => !matches(i));
76
- if (kept.length === own.length) return;
77
- publisher.store.write(OWN, kept);
78
- schedule(publisher);
79
- }
80
-
81
- // Recording never waits on the pod: a like is answered when it is sent, and
82
- // the pages follow. One publish at a time, the last one covering every record
83
- // made while it waited.
84
- function schedule(publisher) {
85
- if (publisher._ownQueued) return;
86
- publisher._ownQueued = true;
87
- publisher._ownChain = (publisher._ownChain || Promise.resolve()).then(async () => {
88
- publisher._ownQueued = false;
89
- try { await publishOwn(publisher); }
90
- catch (e) { publisher.log?.(`owner's outbox view not published: ${e.message}`); }
91
- });
92
- }
93
-
94
- export function ownSettled(publisher) { return publisher._ownChain || Promise.resolve(); }
95
-
96
- export async function publishOwn(publisher) {
97
- const { urls, store, remote } = publisher;
98
- // Written only where the private folder is proved to keep it private — the
99
- // same bar the pending and blocked lists clear. Recorded meanwhile, and
100
- // published the first time the bar is met.
101
- if (publisher.privateReady && await publisher.privateReady() !== true) return;
102
- const seen = store.read('published.json', {});
103
- const own = store.read(OWN, []);
104
- const out = wire.outboxPaging(own, seen.ownIndex || []);
105
- const ownPages = await writePages(remote, seen.ownPages, out.pages.map((items, i) => ({
106
- id: wire.outboxPageId(urls.ownOutbox, i + 1), doc: wire.outboxPage(urls.ownOutbox, i + 1, items),
107
- })));
108
- const ownHead = await writeHead(remote, seen.ownHead, urls.ownOutbox, wire.outboxHead(urls.ownOutbox, own.length, out.pages.length));
109
-
110
- const liked = store.read(LIKED, []);
111
- // liked.json is newest first; pages are filled from the oldest end.
112
- const lk = wire.followersPaging([...liked].reverse(), seen.likedIndex || []);
113
- const likedPages = await writePages(remote, seen.likedPages, lk.pages.map((items, i) => ({
114
- id: wire.followersPageId(urls.liked, i + 1), doc: wire.followersPage(urls.liked, i + 1, items, lk.pages.length),
115
- })));
116
- const likedHead = await writeHead(remote, seen.likedHead, urls.liked, wire.followersHead(urls.liked, liked.length, lk.pages.length));
117
-
118
- store.write('published.json', { ...store.read('published.json', {}),
119
- ownPages, ownIndex: out.index, ownHead, likedPages, likedIndex: lk.index, likedHead });
120
- }
121
-
122
- // A head is written only when it changed: most records change a page, not the
123
- // count or the bounds.
124
- async function writeHead(remote, before, url, doc) {
125
- const digest = crypto.createHash('sha256').update(JSON.stringify(doc)).digest('hex').slice(0, 16);
126
- if (digest !== before) await collection.writeHead(remote, url, doc);
127
- return digest;
128
- }
129
-
130
- // Only the pages that changed, and none past the new end. No rule is written:
131
- // the private container's owner-only rule is inherited.
132
- async function writePages(remote, before = {}, pages) {
133
- const after = {};
134
- for (const [i, { id, doc }] of pages.entries()) {
135
- const n = i + 1;
136
- after[n] = crypto.createHash('sha256').update(JSON.stringify(doc)).digest('hex').slice(0, 16);
137
- if (before[n] !== after[n]) await collection.writePage(remote, id, doc);
138
- }
139
- for (const n of Object.keys(before).map(Number).filter(n => Number.isFinite(n) && n > pages.length)) {
140
- await collection.dropPage(remote, pages[0].id.replace(/-\d+$/u, `-${n}`));
141
- }
142
- return after;
143
- }