fedipod-server 0.28.1 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (438) hide show
  1. package/README.md +38 -34
  2. package/config/server.json +22 -0
  3. package/dist/claims.js +3 -2
  4. package/dist/directory.d.ts +2 -0
  5. package/dist/door.d.ts +4 -0
  6. package/dist/door.js +31 -0
  7. package/dist/fedipod.d.ts +16 -0
  8. package/dist/fedipod.js +13 -0
  9. package/dist/front.d.ts +3 -0
  10. package/dist/front.js +94 -0
  11. package/dist/handler.d.ts +39 -101
  12. package/dist/handler.js +86 -617
  13. package/dist/handler.jsonld +6 -30
  14. package/dist/identities.d.ts +62 -0
  15. package/dist/identities.js +236 -0
  16. package/dist/mounts.d.ts +43 -0
  17. package/dist/mounts.js +123 -0
  18. package/dist/opt-in.d.ts +46 -0
  19. package/dist/opt-in.js +176 -0
  20. package/dist/store-pod.js +37 -0
  21. package/package.json +10 -18
  22. package/lib/client/c2s.mjs +0 -582
  23. package/lib/client/localapi.mjs +0 -87
  24. package/lib/client/masto/accounts.mjs +0 -309
  25. package/lib/client/masto/body.mjs +0 -156
  26. package/lib/client/masto/index.mjs +0 -206
  27. package/lib/client/masto/instance.mjs +0 -112
  28. package/lib/client/masto/media.mjs +0 -79
  29. package/lib/client/masto/oauth.mjs +0 -656
  30. package/lib/client/masto/render.mjs +0 -518
  31. package/lib/client/masto/statuses.mjs +0 -372
  32. package/lib/client/masto/timelines.mjs +0 -438
  33. package/lib/client/oidc-auth.mjs +0 -62
  34. package/lib/client/streaming.mjs +0 -154
  35. package/lib/client/webpush.mjs +0 -98
  36. package/lib/connections/acctfeed.mjs +0 -257
  37. package/lib/connections/atproto.mjs +0 -309
  38. package/lib/connections/bskyfeed.mjs +0 -243
  39. package/lib/connections/bskygroup.mjs +0 -162
  40. package/lib/connections/fediacct.mjs +0 -253
  41. package/lib/connections/import.mjs +0 -357
  42. package/lib/connections/tagfeed.mjs +0 -212
  43. package/lib/connections/vault.mjs +0 -114
  44. package/lib/core/as2.mjs +0 -206
  45. package/lib/core/contexts/activitystreams.json +0 -379
  46. package/lib/core/contexts/anno.json +0 -126
  47. package/lib/core/contexts/did-v1.json +0 -57
  48. package/lib/core/contexts/fep-5711.json +0 -36
  49. package/lib/core/contexts/gotosocial.json +0 -86
  50. package/lib/core/contexts/identity-v1.json +0 -152
  51. package/lib/core/contexts/index.mjs +0 -63
  52. package/lib/core/contexts/join-lemmy.json +0 -33
  53. package/lib/core/contexts/joinmastodon.json +0 -28
  54. package/lib/core/contexts/litepub-0.1.json +0 -60
  55. package/lib/core/contexts/map.json +0 -19
  56. package/lib/core/contexts/miscellany.json +0 -19
  57. package/lib/core/contexts/quotes.json +0 -18
  58. package/lib/core/contexts/schemaorg.json +0 -8845
  59. package/lib/core/contexts/security-data-integrity-v1.json +0 -78
  60. package/lib/core/contexts/security-data-integrity-v2.json +0 -81
  61. package/lib/core/contexts/security-multikey-v1.json +0 -35
  62. package/lib/core/contexts/security-v1.json +0 -74
  63. package/lib/core/contexts/webfinger.json +0 -10
  64. package/lib/core/deliver.mjs +0 -371
  65. package/lib/core/graphview.mjs +0 -327
  66. package/lib/core/intake/activities.mjs +0 -633
  67. package/lib/core/intake/activity.mjs +0 -318
  68. package/lib/core/intake/channel.mjs +0 -144
  69. package/lib/core/intake/group.mjs +0 -256
  70. package/lib/core/intake/index.mjs +0 -701
  71. package/lib/core/intake/notes.mjs +0 -360
  72. package/lib/core/intake/verify.mjs +0 -142
  73. package/lib/core/keys.mjs +0 -153
  74. package/lib/core/lease.mjs +0 -206
  75. package/lib/core/polls.mjs +0 -105
  76. package/lib/core/profile-page.mjs +0 -104
  77. package/lib/core/proof.mjs +0 -79
  78. package/lib/core/publisher/collections.mjs +0 -237
  79. package/lib/core/publisher/index.mjs +0 -500
  80. package/lib/core/publisher/notes.mjs +0 -453
  81. package/lib/core/publisher/questions.mjs +0 -239
  82. package/lib/core/publisher/restore.mjs +0 -224
  83. package/lib/core/shapes/activitystreams.ttl +0 -157
  84. package/lib/core/shapes/index.mjs +0 -132
  85. package/lib/core/shapes/shapes-text.mjs +0 -13
  86. package/lib/core/social.mjs +0 -531
  87. package/lib/core/storage.mjs +0 -176
  88. package/lib/core/store.mjs +0 -568
  89. package/lib/core/wire-quotes.mjs +0 -71
  90. package/lib/core/wire.mjs +0 -889
  91. package/lib/device/account.mjs +0 -87
  92. package/lib/device/admin/index.mjs +0 -13
  93. package/lib/device/admin/origins.mjs +0 -35
  94. package/lib/device/admin/routes/connections.mjs +0 -144
  95. package/lib/device/admin/routes/gateway.mjs +0 -199
  96. package/lib/device/admin/routes/lifecycle.mjs +0 -200
  97. package/lib/device/admin/routes/owner.mjs +0 -322
  98. package/lib/device/admin/routes/setup.mjs +0 -412
  99. package/lib/device/admin/routes/social.mjs +0 -188
  100. package/lib/device/admin/server.mjs +0 -95
  101. package/lib/device/admin/static.mjs +0 -247
  102. package/lib/device/admin/surface.mjs +0 -315
  103. package/lib/device/certs.mjs +0 -281
  104. package/lib/device/cli/commands/account.mjs +0 -586
  105. package/lib/device/cli/commands/run.mjs +0 -278
  106. package/lib/device/cli/commands/service.mjs +0 -221
  107. package/lib/device/cli/commands/setup.mjs +0 -447
  108. package/lib/device/cli/commands/state.mjs +0 -561
  109. package/lib/device/cli/context.mjs +0 -288
  110. package/lib/device/export-collections.mjs +0 -161
  111. package/lib/device/gateway-move.mjs +0 -58
  112. package/lib/device/home.mjs +0 -161
  113. package/lib/device/migrate.mjs +0 -115
  114. package/lib/device/ports.mjs +0 -27
  115. package/lib/device/remote.mjs +0 -54
  116. package/lib/device/setup.mjs +0 -459
  117. package/lib/device/update.mjs +0 -96
  118. package/lib/gateway/caches.mjs +0 -36
  119. package/lib/gateway/directory.mjs +0 -90
  120. package/lib/gateway/front-core.mjs +0 -970
  121. package/lib/gateway/gateway-core.mjs +0 -165
  122. package/lib/gateway/headers.mjs +0 -49
  123. package/lib/gateway/httpsig.mjs +0 -159
  124. package/lib/gateway/notices.mjs +0 -84
  125. package/lib/gateway/quiet.mjs +0 -189
  126. package/lib/pod/README.md +0 -61
  127. package/lib/pod/actor.mjs +0 -96
  128. package/lib/pod/collection.mjs +0 -77
  129. package/lib/pod/containers.mjs +0 -140
  130. package/lib/pod/discovery.mjs +0 -46
  131. package/lib/pod/featured.mjs +0 -18
  132. package/lib/pod/followers.mjs +0 -24
  133. package/lib/pod/following.mjs +0 -10
  134. package/lib/pod/http.mjs +0 -74
  135. package/lib/pod/inbox.mjs +0 -166
  136. package/lib/pod/links.mjs +0 -35
  137. package/lib/pod/media.mjs +0 -27
  138. package/lib/pod/notes.mjs +0 -80
  139. package/lib/pod/notifications.mjs +0 -89
  140. package/lib/pod/outbox.mjs +0 -18
  141. package/lib/pod/package.json +0 -19
  142. package/lib/pod/policy.mjs +0 -58
  143. package/lib/pod/private.mjs +0 -19
  144. package/lib/pod/root.mjs +0 -104
  145. package/lib/pod/state.mjs +0 -40
  146. package/lib/pod/transport.mjs +0 -525
  147. package/lib/pod/urls.mjs +0 -85
  148. package/lib/server/embed.mjs +0 -423
  149. package/lib/session/README.md +0 -137
  150. package/lib/session/demo.html +0 -94
  151. package/lib/session/fedi-account.mjs +0 -408
  152. package/lib/session/fedi-login.mjs +0 -123
  153. package/lib/session/oidc-session.mjs +0 -233
  154. package/lib/session/package.json +0 -49
  155. package/lib/shared/guard.mjs +0 -213
  156. package/lib/shared/links.mjs +0 -9
  157. package/lib/shared/safefetch.mjs +0 -235
  158. package/lib/shared/ua.mjs +0 -29
  159. package/phanpy/dist/404.html +0 -40
  160. package/phanpy/dist/_headers +0 -2
  161. package/phanpy/dist/apple-touch-icon.png +0 -0
  162. package/phanpy/dist/assets/ICONS-DIPJeU0C.js +0 -2
  163. package/phanpy/dist/assets/ICONS-DIPJeU0C.js.map +0 -1
  164. package/phanpy/dist/assets/ScheduledAtField-D-Abfo4J.js +0 -183
  165. package/phanpy/dist/assets/ScheduledAtField-D-Abfo4J.js.map +0 -1
  166. package/phanpy/dist/assets/Temml-CCjoacWt.woff2 +0 -0
  167. package/phanpy/dist/assets/__vite-browser-external-C7iut881.js +0 -2
  168. package/phanpy/dist/assets/__vite-browser-external-C7iut881.js.map +0 -1
  169. package/phanpy/dist/assets/boosts-carousel-BiOaNdMT.jpg +0 -0
  170. package/phanpy/dist/assets/catch-up-O5q5C75Z.png +0 -0
  171. package/phanpy/dist/assets/chunk-aKtaBQYM.js +0 -1
  172. package/phanpy/dist/assets/compose-CFEx7wb4.js +0 -2
  173. package/phanpy/dist/assets/compose-CFEx7wb4.js.map +0 -1
  174. package/phanpy/dist/assets/compose-ZJN5xSb4.js +0 -35
  175. package/phanpy/dist/assets/compose-ZJN5xSb4.js.map +0 -1
  176. package/phanpy/dist/assets/debug.module-YFQgwnIr.js +0 -52
  177. package/phanpy/dist/assets/debug.module-YFQgwnIr.js.map +0 -1
  178. package/phanpy/dist/assets/dom-p1YcJ5Rw.js +0 -2
  179. package/phanpy/dist/assets/dom-p1YcJ5Rw.js.map +0 -1
  180. package/phanpy/dist/assets/home-mobile-dark@2x-Dc-J8-SM.png +0 -0
  181. package/phanpy/dist/assets/home-mobile-light@2x-Bd3fod8m.png +0 -0
  182. package/phanpy/dist/assets/home-tablet-dark@2x-BgcNI8le.png +0 -0
  183. package/phanpy/dist/assets/home-tablet-light@2x-BW2RV9T6.png +0 -0
  184. package/phanpy/dist/assets/icons/add-circle-line-WD0mwORD.js +0 -1
  185. package/phanpy/dist/assets/icons/alert-line-CpFUMf9s.js +0 -1
  186. package/phanpy/dist/assets/icons/android-2-line-Bzxnrd3S.js +0 -1
  187. package/phanpy/dist/assets/icons/announcement-line-5nwaJFJm.js +0 -1
  188. package/phanpy/dist/assets/icons/arrow-down-circle-line-a7Id9-Iv.js +0 -1
  189. package/phanpy/dist/assets/icons/arrow-down-line-B8uzXzq6.js +0 -1
  190. package/phanpy/dist/assets/icons/arrow-left-line-CGrGT9C-.js +0 -1
  191. package/phanpy/dist/assets/icons/arrow-right-line-CE-HERfX.js +0 -1
  192. package/phanpy/dist/assets/icons/arrow-to-up-line-CvdQjmcy.js +0 -1
  193. package/phanpy/dist/assets/icons/arrow-up-circle-line-DEparSNF.js +0 -1
  194. package/phanpy/dist/assets/icons/arrow-up-line-C7yBeugc.js +0 -1
  195. package/phanpy/dist/assets/icons/arrows-right-line-BtbXmJGP.js +0 -1
  196. package/phanpy/dist/assets/icons/at-line-CF66Bznk.js +0 -1
  197. package/phanpy/dist/assets/icons/attachment-line-BY8It-l9.js +0 -1
  198. package/phanpy/dist/assets/icons/blockquote-line-BS6pU_hm.js +0 -1
  199. package/phanpy/dist/assets/icons/board-line-C0UWXyA6.js +0 -1
  200. package/phanpy/dist/assets/icons/bookmark-line-1CtX5JLF.js +0 -1
  201. package/phanpy/dist/assets/icons/building-5-line-Dd59MzbO.js +0 -1
  202. package/phanpy/dist/assets/icons/bus-2-line-6_dvA0GG.js +0 -1
  203. package/phanpy/dist/assets/icons/calendar-day-line-Crpl_fmu.js +0 -1
  204. package/phanpy/dist/assets/icons/calendar-month-line-vommts2C.js +0 -1
  205. package/phanpy/dist/assets/icons/calendar-time-add-line-BU_DTlQk.js +0 -1
  206. package/phanpy/dist/assets/icons/camera-line-Cv69MEei.js +0 -1
  207. package/phanpy/dist/assets/icons/celebrate-line-B8DvIoi0.js +0 -1
  208. package/phanpy/dist/assets/icons/chart-bar-line-FK-QOmtF.js +0 -1
  209. package/phanpy/dist/assets/icons/chart-line-line-BGcJAFWw.js +0 -1
  210. package/phanpy/dist/assets/icons/chat-3-line-YFK2-hwz.js +0 -1
  211. package/phanpy/dist/assets/icons/check-circle-line-DsAxFr5-.js +0 -1
  212. package/phanpy/dist/assets/icons/clipboard-line-BBp364gJ.js +0 -1
  213. package/phanpy/dist/assets/icons/close-circle-line-CICdiuGz.js +0 -1
  214. package/phanpy/dist/assets/icons/close-line-DYrFWcjv.js +0 -1
  215. package/phanpy/dist/assets/icons/cloud-line-tiQAbah8.js +0 -1
  216. package/phanpy/dist/assets/icons/code-line-fAl8iiV3.js +0 -1
  217. package/phanpy/dist/assets/icons/comment-2-line-CR3g7WeB.js +0 -1
  218. package/phanpy/dist/assets/icons/copy-2-line-CLW69yF5.js +0 -1
  219. package/phanpy/dist/assets/icons/delete-2-line-B8xmbr4n.js +0 -1
  220. package/phanpy/dist/assets/icons/document-3-line-DBGNhAg2.js +0 -1
  221. package/phanpy/dist/assets/icons/document-line-C7mb9sb7.js +0 -1
  222. package/phanpy/dist/assets/icons/down-line-BGqSSThd.js +0 -1
  223. package/phanpy/dist/assets/icons/edit-4-line-DcgGaEhx.js +0 -1
  224. package/phanpy/dist/assets/icons/emoji-2-line-D8YgnM-y.js +0 -1
  225. package/phanpy/dist/assets/icons/exit-line-Dc07BS-x.js +0 -1
  226. package/phanpy/dist/assets/icons/external-link-line-BvpSkmjO.js +0 -1
  227. package/phanpy/dist/assets/icons/eye-2-line-CiWXx-f_.js +0 -1
  228. package/phanpy/dist/assets/icons/eye-close-line-BgTaSJ4P.js +0 -1
  229. package/phanpy/dist/assets/icons/filter-2-line-B_mR2usy.js +0 -1
  230. package/phanpy/dist/assets/icons/filter-line-C_nm-3cU.js +0 -1
  231. package/phanpy/dist/assets/icons/flag-1-line-C2HB05I0.js +0 -1
  232. package/phanpy/dist/assets/icons/forbid-circle-line-D3LTT9yd.js +0 -1
  233. package/phanpy/dist/assets/icons/formula-line-DLrt2d6j.js +0 -1
  234. package/phanpy/dist/assets/icons/grid-line-CgUmEikf.js +0 -1
  235. package/phanpy/dist/assets/icons/group-line-C5fnX0wU.js +0 -1
  236. package/phanpy/dist/assets/icons/hand-finger-2-line-BGgxbuL9.js +0 -1
  237. package/phanpy/dist/assets/icons/hashtag-line-CyTEEXBb.js +0 -1
  238. package/phanpy/dist/assets/icons/heart-crack-line-BZddbO2C.js +0 -1
  239. package/phanpy/dist/assets/icons/heart-line-CJ7K47J4.js +0 -1
  240. package/phanpy/dist/assets/icons/history-2-line-kWWvFzqv.js +0 -1
  241. package/phanpy/dist/assets/icons/history-line-bGQiwbTH.js +0 -1
  242. package/phanpy/dist/assets/icons/home-3-line-fRU1zeLG.js +0 -1
  243. package/phanpy/dist/assets/icons/information-line-iNk6-sOY.js +0 -1
  244. package/phanpy/dist/assets/icons/keyboard-line-DsCyuQNz.js +0 -1
  245. package/phanpy/dist/assets/icons/layout-4-line-CZkOX52Z.js +0 -1
  246. package/phanpy/dist/assets/icons/layout-5-line-B_znt5wO.js +0 -1
  247. package/phanpy/dist/assets/icons/left-line-eUw0tya4.js +0 -1
  248. package/phanpy/dist/assets/icons/lightning-line-DaKoDdix.js +0 -1
  249. package/phanpy/dist/assets/icons/link-2-line-DCqLmMA7.js +0 -1
  250. package/phanpy/dist/assets/icons/list-check-line-BAxosBh9.js +0 -1
  251. package/phanpy/dist/assets/icons/lock-line-CPoaXeUW.js +0 -1
  252. package/phanpy/dist/assets/icons/mail-line-B0P8aa1L.js +0 -1
  253. package/phanpy/dist/assets/icons/moon-line-CEqNAToP.js +0 -1
  254. package/phanpy/dist/assets/icons/more-1-fill-Dw2e5xab.js +0 -1
  255. package/phanpy/dist/assets/icons/more-3-line-BIiVjYTz.js +0 -1
  256. package/phanpy/dist/assets/icons/notification-line-BZZq2Gtu.js +0 -1
  257. package/phanpy/dist/assets/icons/pencil-line-CKh8-A1v.js +0 -1
  258. package/phanpy/dist/assets/icons/photo-album-line-Bnfg2u40.js +0 -1
  259. package/phanpy/dist/assets/icons/pin-line-DpTolyhs.js +0 -1
  260. package/phanpy/dist/assets/icons/play-fill-B0-G_8lX.js +0 -1
  261. package/phanpy/dist/assets/icons/qrcode-2-line-B6SPCaHn.js +0 -1
  262. package/phanpy/dist/assets/icons/quill-pen-line-B6LUrnCP.js +0 -1
  263. package/phanpy/dist/assets/icons/quote-left-fill-hQZ1cj_6.js +0 -1
  264. package/phanpy/dist/assets/icons/radar-line-CKyroTth.js +0 -1
  265. package/phanpy/dist/assets/icons/react-line-CoIKhhtK.js +0 -1
  266. package/phanpy/dist/assets/icons/refresh-2-line-D0mTF97S.js +0 -1
  267. package/phanpy/dist/assets/icons/right-line-B3KWJaqc.js +0 -1
  268. package/phanpy/dist/assets/icons/rocket-line-4KZl537D.js +0 -1
  269. package/phanpy/dist/assets/icons/round-fill-C69NTrZ0.js +0 -1
  270. package/phanpy/dist/assets/icons/round-line-B9k_1uo-.js +0 -1
  271. package/phanpy/dist/assets/icons/route-line-B28ReHcE.js +0 -1
  272. package/phanpy/dist/assets/icons/rows-4-line-CrBniagM.js +0 -1
  273. package/phanpy/dist/assets/icons/scan-line-DSf74YdP.js +0 -1
  274. package/phanpy/dist/assets/icons/search-2-line-B6geIU1z.js +0 -1
  275. package/phanpy/dist/assets/icons/settings-3-line-azJwK47w.js +0 -1
  276. package/phanpy/dist/assets/icons/settings-6-line-C_5zc5YR.js +0 -1
  277. package/phanpy/dist/assets/icons/share-2-line-D5MxWjrm.js +0 -1
  278. package/phanpy/dist/assets/icons/share-forward-line-Cp61fZzZ.js +0 -1
  279. package/phanpy/dist/assets/icons/sparkles-2-line-DYYyXTaW.js +0 -1
  280. package/phanpy/dist/assets/icons/sparkles-line-C2hY1s6I.js +0 -1
  281. package/phanpy/dist/assets/icons/time-line-BF3eet1v.js +0 -1
  282. package/phanpy/dist/assets/icons/transfer-4-line-oisfvTBw.js +0 -1
  283. package/phanpy/dist/assets/icons/translate-line-BFWol2Ce.js +0 -1
  284. package/phanpy/dist/assets/icons/unlock-line-X_x5vwv3.js +0 -1
  285. package/phanpy/dist/assets/icons/upload-3-line-DgzwUJeW.js +0 -1
  286. package/phanpy/dist/assets/icons/user-4-line-BVSynZCp.js +0 -1
  287. package/phanpy/dist/assets/icons/user-add-2-line-BlGS5Q8q.js +0 -1
  288. package/phanpy/dist/assets/icons/user-add-line-BbXdHG4z.js +0 -1
  289. package/phanpy/dist/assets/icons/user-edit-line-BFAHOV0X.js +0 -1
  290. package/phanpy/dist/assets/icons/user-follow-line-BXux0wSd.js +0 -1
  291. package/phanpy/dist/assets/icons/user-star-line-DdYlpHLU.js +0 -1
  292. package/phanpy/dist/assets/icons/user-warning-line-Cj43j0_E.js +0 -1
  293. package/phanpy/dist/assets/icons/user-x-line-DIDgNlPH.js +0 -1
  294. package/phanpy/dist/assets/icons/volume-line-Bs390KQt.js +0 -1
  295. package/phanpy/dist/assets/icons/volume-mute-line-DzlzIqFZ.js +0 -1
  296. package/phanpy/dist/assets/icons/walk-line-yNXfB-md.js +0 -1
  297. package/phanpy/dist/assets/icons/world-2-line-CenkGIwj.js +0 -1
  298. package/phanpy/dist/assets/icons/zoom-in-line-CaENUphf.js +0 -1
  299. package/phanpy/dist/assets/icons/zoom-out-line-BDNk0EWo.js +0 -1
  300. package/phanpy/dist/assets/instances-BNPptnn-.json +0 -333
  301. package/phanpy/dist/assets/locales/ar-SA-CfheRbCu.js +0 -1
  302. package/phanpy/dist/assets/locales/ca-ES-DHRW86Tz.js +0 -1
  303. package/phanpy/dist/assets/locales/cs-CZ-CKoqEuA1.js +0 -1
  304. package/phanpy/dist/assets/locales/de-DE-ByRPMRFy.js +0 -1
  305. package/phanpy/dist/assets/locales/eo-UY-CPC7wLCS.js +0 -1
  306. package/phanpy/dist/assets/locales/es-ES-dJa0Fd9t.js +0 -1
  307. package/phanpy/dist/assets/locales/eu-ES-C3djlPR2.js +0 -1
  308. package/phanpy/dist/assets/locales/fa-IR-DTT490Ck.js +0 -1
  309. package/phanpy/dist/assets/locales/fi-FI-B23MCPuz.js +0 -1
  310. package/phanpy/dist/assets/locales/fr-FR-BcaQsO5C.js +0 -1
  311. package/phanpy/dist/assets/locales/gl-ES-BR-CVKBN.js +0 -1
  312. package/phanpy/dist/assets/locales/he-IL-CfheRbCu.js +0 -1
  313. package/phanpy/dist/assets/locales/hu-HU-CfheRbCu.js +0 -1
  314. package/phanpy/dist/assets/locales/it-IT-C1136UFV.js +0 -1
  315. package/phanpy/dist/assets/locales/ja-JP-CRZmRAhD.js +0 -1
  316. package/phanpy/dist/assets/locales/kab-Cz8xH_TT.js +0 -1
  317. package/phanpy/dist/assets/locales/ko-KR-CG9UiY4o.js +0 -1
  318. package/phanpy/dist/assets/locales/lt-LT-CJcI6sHZ.js +0 -1
  319. package/phanpy/dist/assets/locales/nb-NO-CfheRbCu.js +0 -1
  320. package/phanpy/dist/assets/locales/nl-NL-DxN4Yc1t.js +0 -1
  321. package/phanpy/dist/assets/locales/oc-FR-CfheRbCu.js +0 -1
  322. package/phanpy/dist/assets/locales/pl-PL-DkwGXf6P.js +0 -1
  323. package/phanpy/dist/assets/locales/pseudo-LOCALE-CQ4XxkQ0.js +0 -1
  324. package/phanpy/dist/assets/locales/pt-BR-nGL0HdFS.js +0 -1
  325. package/phanpy/dist/assets/locales/pt-PT-Cp17aj2z.js +0 -1
  326. package/phanpy/dist/assets/locales/ru-RU-0Y8cIoaK.js +0 -1
  327. package/phanpy/dist/assets/locales/th-TH-CfheRbCu.js +0 -1
  328. package/phanpy/dist/assets/locales/tok-CfheRbCu.js +0 -1
  329. package/phanpy/dist/assets/locales/tr-TR-CfheRbCu.js +0 -1
  330. package/phanpy/dist/assets/locales/uk-UA-C5E3dFBf.js +0 -1
  331. package/phanpy/dist/assets/locales/zh-CN-BHJi7_et.js +0 -1
  332. package/phanpy/dist/assets/locales/zh-TW-CfheRbCu.js +0 -1
  333. package/phanpy/dist/assets/main-BdqNbG-a.js +0 -26
  334. package/phanpy/dist/assets/main-BdqNbG-a.js.map +0 -1
  335. package/phanpy/dist/assets/mock-home-DGhR__He.js +0 -2
  336. package/phanpy/dist/assets/mock-home-DGhR__He.js.map +0 -1
  337. package/phanpy/dist/assets/multi-column-ROck0NVt.jpg +0 -0
  338. package/phanpy/dist/assets/multi-hashtag-timeline-DH5INVHi.jpg +0 -0
  339. package/phanpy/dist/assets/nested-comments-thread-hv59kZLc.jpg +0 -0
  340. package/phanpy/dist/assets/polyfill-force-H8sUkLGp.js +0 -2
  341. package/phanpy/dist/assets/polyfill-force-H8sUkLGp.js.map +0 -1
  342. package/phanpy/dist/assets/pwa-viewport-ogKbF51K.js +0 -20
  343. package/phanpy/dist/assets/pwa-viewport-ogKbF51K.js.map +0 -1
  344. package/phanpy/dist/assets/style-B1iLPTPR.css +0 -2
  345. package/phanpy/dist/assets/temml-DuyIQMei.js +0 -97
  346. package/phanpy/dist/assets/temml-DuyIQMei.js.map +0 -1
  347. package/phanpy/dist/assets/tinyld.light.node-DtbBhLYC.js +0 -2
  348. package/phanpy/dist/assets/tinyld.light.node-DtbBhLYC.js.map +0 -1
  349. package/phanpy/dist/assets/year-in-posts-B2lywqdx.png +0 -0
  350. package/phanpy/dist/assets/year-in-posts-DJv_star.js +0 -3
  351. package/phanpy/dist/assets/year-in-posts-DJv_star.js.map +0 -1
  352. package/phanpy/dist/compose/index.html +0 -20
  353. package/phanpy/dist/favicon.ico +0 -0
  354. package/phanpy/dist/index.html +0 -73
  355. package/phanpy/dist/logo-192.png +0 -0
  356. package/phanpy/dist/logo-512.png +0 -0
  357. package/phanpy/dist/logo-badge-72.png +0 -0
  358. package/phanpy/dist/logo-maskable-512.png +0 -0
  359. package/phanpy/dist/logo-monochrome-512.png +0 -0
  360. package/phanpy/dist/logo-monochrome-maskable-512.png +0 -0
  361. package/phanpy/dist/manifest.webmanifest +0 -1
  362. package/phanpy/dist/og-image-2.jpg +0 -0
  363. package/phanpy/dist/og-image.png +0 -0
  364. package/phanpy/dist/robots.txt +0 -2
  365. package/phanpy/dist/sw.js +0 -2
  366. package/phanpy/dist/sw.js.map +0 -1
  367. package/phanpy/dist/version.json +0 -1
  368. package/run-agent.mjs +0 -897
  369. package/vendor/gate.cjs +0 -138
  370. package/vendor/idp-grant.cjs +0 -394
  371. package/vendor/jfetch.cjs +0 -16
  372. package/web/admin/actors.js +0 -145
  373. package/web/admin/bar.css +0 -126
  374. package/web/admin/bar.js +0 -99
  375. package/web/admin/client/client.js +0 -66
  376. package/web/admin/client/index.html +0 -69
  377. package/web/admin/client-pick.js +0 -79
  378. package/web/admin/common.js +0 -23
  379. package/web/admin/connections.js +0 -112
  380. package/web/admin/gateway.js +0 -133
  381. package/web/admin/group.js +0 -258
  382. package/web/admin/index.html +0 -603
  383. package/web/admin/notices-bar.js +0 -90
  384. package/web/admin/oauth-signin.mjs +0 -37
  385. package/web/admin/record.js +0 -390
  386. package/web/admin/setup/index.html +0 -241
  387. package/web/admin/setup/setup.js +0 -326
  388. package/web/admin/tokens.css +0 -68
  389. package/web/admin/upkeep.js +0 -177
  390. package/web/admin/window.css +0 -55
  391. package/web/admin/window.js +0 -136
  392. package/web/app/README.md +0 -81
  393. package/web/app/admin-facade.mjs +0 -588
  394. package/web/app/agent.mjs +0 -663
  395. package/web/app/atproto-browser.mjs +0 -86
  396. package/web/app/authorize_interaction.html +0 -56
  397. package/web/app/authorize_interaction.js +0 -69
  398. package/web/app/boot.mjs +0 -576
  399. package/web/app/deliver-relay.mjs +0 -129
  400. package/web/app/dist/boot.js +0 -34218
  401. package/web/app/dist/boot.js.map +0 -7
  402. package/web/app/dist/sw.js +0 -72810
  403. package/web/app/dist/sw.js.map +0 -7
  404. package/web/app/fediacct-browser.mjs +0 -146
  405. package/web/app/gateway-move.mjs +0 -60
  406. package/web/app/idb-kv.mjs +0 -35
  407. package/web/app/index.html +0 -242
  408. package/web/app/keys-browser.mjs +0 -71
  409. package/web/app/keystore.mjs +0 -82
  410. package/web/app/oidc-session.mjs +0 -6
  411. package/web/app/pod-auth.mjs +0 -248
  412. package/web/app/pod-remote.mjs +0 -84
  413. package/web/app/shims/_globals.mjs +0 -2
  414. package/web/app/shims/fedify-sig.mjs +0 -77
  415. package/web/app/shims/node-crypto.mjs +0 -113
  416. package/web/app/shims/node-fs.mjs +0 -17
  417. package/web/app/shims/node-path.mjs +0 -19
  418. package/web/app/shims/node-url.mjs +0 -7
  419. package/web/app/shims/prelude.js +0 -58
  420. package/web/app/shims/safefetch.mjs +0 -58
  421. package/web/app/shims/shapes-text.mjs +0 -8
  422. package/web/app/shims/web-push.mjs +0 -14
  423. package/web/app/signup.mjs +0 -318
  424. package/web/app/sw-src.mjs +0 -220
  425. package/web/app/update.js +0 -70
  426. package/web/front/admin.html +0 -55
  427. package/web/front/admin.js +0 -89
  428. package/web/front/install.sh +0 -69
  429. package/web/front/new-account.html +0 -35
  430. package/web/front/new-account.js +0 -13
  431. package/web/front/notices.html +0 -69
  432. package/web/front/notices.js +0 -107
  433. package/web/front/run.html +0 -58
  434. package/web/front/run.js +0 -195
  435. package/web/front/solid-oidc-client.js +0 -6
  436. package/web/icons/forum.svg +0 -11
  437. package/web/icons/gateway.svg +0 -14
  438. package/web/icons/local.svg +0 -11
@@ -1,243 +0,0 @@
1
- // bskyfeed.mjs — mirrors the connected Bluesky account's home timeline and
2
- // notifications into the statuses index as kind 'bsky'. View cache only — none
3
- // of it is written to the pod; the pod holds fediverse content.
4
- //
5
- // Config in bskyfeed.json: { intervalMin }. The poll is authed XRPC against
6
- // the account's own PDS, so the politeness dial is the account's own rate
7
- // budget — backed off the same way tagfeed backs off a stranger's instance.
8
-
9
- const DEFAULTS = { intervalMin: 5 };
10
- const PER_SWEEP = 30;
11
- const MAX_BSKY_ENTRIES = 200;
12
- const BACKOFF_MIN_MS = 5 * 60_000;
13
- const BACKOFF_MAX_MS = 2 * 60 * 60_000;
14
-
15
- export const profileUrl = (did) => `https://bsky.app/profile/${did}`;
16
- export const postUrl = (uri) => {
17
- const m = String(uri).match(/^at:\/\/([^/]+)\/[^/]+\/(.+)$/);
18
- return m ? `https://bsky.app/profile/${m[1]}/post/${m[2]}` : null;
19
- };
20
-
21
- // Every place a post view keeps pictures: a plain image post, the media half
22
- // of a quote-with-media, and a link card's thumbnail. Bluesky's view types
23
- // differ by shape, not by a flag, so each is read where it lives.
24
- export function attachmentsOf(post) {
25
- const e = post?.embed || {};
26
- const images = [...(e.images || []), ...(e.media?.images || [])]
27
- .filter(i => i?.fullsize)
28
- .map(i => ({ url: i.fullsize, mediaType: 'image/jpeg', description: i.alt || '' }));
29
- const card = e.external?.thumb || e.media?.external?.thumb;
30
- if (card) {
31
- const ext = e.external || e.media.external;
32
- images.push({ url: card, mediaType: 'image/jpeg', description: ext.title || ext.uri || '' });
33
- }
34
- return images;
35
- }
36
-
37
- export class BskyFeed {
38
- constructor({ store, atproto, log = console.log, onNotification = null }) {
39
- Object.assign(this, { store, atproto, log, onNotification });
40
- this.lastSweep = null;
41
- this.lastAdded = 0;
42
- }
43
-
44
- config() { return { ...DEFAULTS, ...this.store.read('bskyfeed.json', {}) }; }
45
-
46
- setConfig(patch) {
47
- const clean = {};
48
- if (patch.intervalMin) clean.intervalMin = Math.max(1, Number(patch.intervalMin) || DEFAULTS.intervalMin);
49
- this.store.write('bskyfeed.json', { ...this.config(), ...clean });
50
- this.stop();
51
- this.start();
52
- return this.config();
53
- }
54
-
55
- start() {
56
- this.stopped = false;
57
- this.sweep().catch(e => this.log(`bskyfeed: ${e.message}`));
58
- const tick = () => {
59
- this.timer = setTimeout(() => {
60
- this.sweep()
61
- .catch(e => this.log(`bskyfeed: ${e.message}`))
62
- .finally(() => { if (!this.stopped) tick(); });
63
- }, Math.round(this.config().intervalMin * 60_000 * (0.85 + Math.random() * 0.3)));
64
- this.timer.unref?.();
65
- };
66
- tick();
67
- }
68
-
69
- stop() { this.stopped = true; clearTimeout(this.timer); }
70
-
71
- _backOff(status, retryAfter) {
72
- this.failures = (this.failures || 0) + 1;
73
- const ladder = Math.min(BACKOFF_MIN_MS * 2 ** (this.failures - 1), BACKOFF_MAX_MS);
74
- const wait = retryAfter || Math.round(ladder * (0.85 + Math.random() * 0.3));
75
- this.quietUntil = Date.now() + wait;
76
- this.log(`bskyfeed: ${status ? `bluesky answered ${status}` : 'bluesky did not answer'} — not asking again for ${Math.round(wait / 60_000)} min`);
77
- }
78
-
79
- // One author into the shared actor cache, under a page anyone can open.
80
- _rememberAuthor(a) {
81
- const url = profileUrl(a.did);
82
- if (!this.store.getActors()[url]) {
83
- this.store.cacheActor(url, {
84
- name: a.displayName || a.handle, preferredUsername: a.handle,
85
- icon: a.avatar || null, type: 'Person',
86
- });
87
- }
88
- return url;
89
- }
90
-
91
- // One Bluesky post into the statuses index. Returns its noteId, new or not.
92
- // A post seen before is completed, not duplicated: a reply first met as a
93
- // bare notification record learns its parent and its pictures from the
94
- // full view when that arrives.
95
- _mirrorPost(post, { via = null } = {}) {
96
- const noteId = post.uri;
97
- const inReplyTo = post.record?.reply?.parent?.uri || null;
98
- const attachments = attachmentsOf(post);
99
- const existing = this.store.getStatuses().find(s => s.noteId === noteId);
100
- if (existing) {
101
- const patch = {
102
- ...(inReplyTo && !existing.inReplyTo ? { inReplyTo } : {}),
103
- ...(attachments.length && !existing.attachments?.length ? { attachments } : {}),
104
- };
105
- if (Object.keys(patch).length && typeof this.store.updateStatus === 'function') {
106
- this.store.updateStatus(noteId, patch);
107
- }
108
- return { noteId, added: false };
109
- }
110
- const actor = this._rememberAuthor(post.author);
111
- if (this.store.isBlocked(actor)) return { noteId, added: false };
112
- const text = post.record?.text || '';
113
- this.store.addStatus({
114
- noteId, actor,
115
- content: `<p>${text.replace(/[&<>]/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;' }[c]))}</p>`,
116
- published: post.record?.createdAt || post.indexedAt,
117
- kind: 'bsky',
118
- ...(post.cid ? { cid: post.cid } : {}),
119
- link: postUrl(noteId),
120
- ...(via ? { via } : {}),
121
- ...(inReplyTo ? { inReplyTo } : {}),
122
- ...(attachments.length ? { attachments } : {}),
123
- });
124
- return { noteId, added: true };
125
- }
126
-
127
- // The conversation under one post, asked for when a post is opened: the
128
- // ancestors it hangs from and the replies beneath it, each mirrored with
129
- // the reply link the thread view is built from. View cache only, like the
130
- // timeline. Returns how many posts were new; a thread Bluesky will not
131
- // give (gone, blocked, rate-limited) adds none and is logged, not thrown.
132
- async mirrorThread(uri, { depth = 6 } = {}) {
133
- if (!this.atproto?.connected()) return 0;
134
- if (this.quietUntil && Date.now() < this.quietUntil) return 0;
135
- let out;
136
- try {
137
- out = await this.atproto.xrpc('app.bsky.feed.getPostThread', { params: { uri, depth, parentHeight: 40 } });
138
- } catch (e) {
139
- if (e.status === 429 || e.status >= 500) this._backOff(e.status, null);
140
- else this.log(`bskyfeed: thread ${uri}: ${e.message}`);
141
- return 0;
142
- }
143
- const self = this.atproto.read()?.did;
144
- const ownMirrors = new Set(this.store.getStatuses().map(s => s.atproto?.uri).filter(Boolean));
145
- let added = 0;
146
- const take = (node) => {
147
- const post = node?.post;
148
- if (!post?.uri) return; // notFoundPost, blockedPost
149
- if (post.author?.did === self && ownMirrors.has(post.uri)) return;
150
- if (this._mirrorPost(post).added) added++;
151
- };
152
- for (let up = out?.thread?.parent; up; up = up.parent) take(up);
153
- const walk = (node, left) => {
154
- take(node);
155
- if (left <= 0) return;
156
- for (const r of node?.replies || []) walk(r, left - 1);
157
- };
158
- walk(out?.thread, depth);
159
- if (added) this.log(`bskyfeed: +${added} from the thread under ${uri}`);
160
- return added;
161
- }
162
-
163
- async sweep() {
164
- if (!this.atproto?.connected()) return;
165
- if (this.quietUntil && Date.now() < this.quietUntil) return;
166
- this.lastSweep = new Date().toISOString();
167
- const self = this.atproto.read()?.did;
168
- let added = 0;
169
- // One commit boundary for the sweep, as acctfeed and tagfeed: every item
170
- // awaits Bluesky before the next, so the debounce never coalesced them and
171
- // the timeline index went to the pod once per notification.
172
- this.store.hold?.();
173
- try {
174
- // Our own CROSS-POSTS must not echo back into the feed — but a post or
175
- // reply written natively on Bluesky is ours to see here too. The mirrors
176
- // are known by uri, so only they are skipped.
177
- const ownMirrors = new Set(this.store.getStatuses().map(s => s.atproto?.uri).filter(Boolean));
178
- const tl = await this.atproto.xrpc('app.bsky.feed.getTimeline', { params: { limit: PER_SWEEP } });
179
- for (const item of tl.feed || []) {
180
- if (!item?.post?.uri) continue;
181
- if (item.post.author?.did === self && ownMirrors.has(item.post.uri)) continue;
182
- const via = item.reason?.$type === 'app.bsky.feed.defs#reasonRepost' && item.reason.by
183
- ? this._rememberAuthor(item.reason.by) : null;
184
- if (this._mirrorPost(item.post, { via }).added) added++;
185
- }
186
-
187
- // Only notifications newer than the last sweep's high-water mark are
188
- // acted on — the list is a window, not a queue, and re-reading it must
189
- // not re-fire the group hooks.
190
- const seen = this.store.read('bskyfeed.json', {}).lastNotifiedAt || '';
191
- let high = seen;
192
- const nots = await this.atproto.xrpc('app.bsky.notification.listNotifications', { params: { limit: PER_SWEEP } });
193
- for (const n of nots.notifications || []) {
194
- if (!n?.author?.did || n.author.did === self) continue;
195
- if (n.indexedAt) {
196
- if (seen && n.indexedAt <= seen) continue;
197
- if (n.indexedAt > high) high = n.indexedAt;
198
- }
199
- const actor = this._rememberAuthor(n.author);
200
- // A like or repost points at OUR mirror; the notification should point
201
- // at the pod post it mirrors, which is the one the client can open.
202
- if (n.reason === 'like' || n.reason === 'repost') {
203
- const mine = this.store.getStatuses().find(s => s.atproto?.uri === n.reasonSubject);
204
- if (!mine) continue;
205
- this.store.addNotification({
206
- type: n.reason === 'like' ? 'favourite' : 'reblog',
207
- actor, noteId: mine.noteId, bsky: true,
208
- });
209
- } else if (n.reason === 'follow') {
210
- this.store.addNotification({ type: 'follow', actor, bsky: true });
211
- await this.onNotification?.(n, { actor });
212
- } else if (n.reason === 'mention' || n.reason === 'reply') {
213
- // The notification carries the bare record — no picture URLs and
214
- // no view. The post view has both; the record is the fallback when
215
- // Bluesky will not give the view.
216
- let view = null;
217
- try {
218
- const got = await this.atproto.xrpc('app.bsky.feed.getPosts', { params: { uris: n.uri } });
219
- view = (got?.posts || [])[0] || null;
220
- } catch { /* the bare record still stands */ }
221
- this._mirrorPost(view || { uri: n.uri, cid: n.cid, author: n.author, record: n.record, indexedAt: n.indexedAt });
222
- this.store.addNotification({ type: 'mention', actor, noteId: n.uri, bsky: true });
223
- await this.onNotification?.(n, { actor });
224
- }
225
- }
226
- if (high && high !== seen) this.store.write('bskyfeed.json', { ...this.config(), lastNotifiedAt: high });
227
- this.failures = 0;
228
- } catch (e) {
229
- this._backOff(e.status || 0, null);
230
- return;
231
- } finally {
232
- this.store.release?.();
233
- }
234
- const all = this.store.getStatuses();
235
- const mirrored = all.filter(s => s.kind === 'bsky');
236
- if (mirrored.length > MAX_BSKY_ENTRIES) {
237
- const drop = new Set(mirrored.slice(MAX_BSKY_ENTRIES).map(s => s.noteId));
238
- this.store.write('statuses.json', all.filter(s => !drop.has(s.noteId)));
239
- }
240
- this.lastAdded = added;
241
- if (added) this.log(`bskyfeed: +${added} from the bluesky timeline`);
242
- }
243
- }
@@ -1,162 +0,0 @@
1
- // bskygroup.mjs — Bluesky members of a group, bridged-first. A bridged Bluesky
2
- // account (one that follows @ap.brid.gy) is a plain AP actor and takes the
3
- // existing FEP-1b12 paths untouched. This module covers the rest: knowing who
4
- // is bridged, nudging who isn't, and the degraded Bluesky-only membership —
5
- // follow-as-join, mention-as-submission, repost as the group's carry.
6
-
7
- import { profileUrl } from './bskyfeed.mjs';
8
- import { bskyText } from './atproto.mjs';
9
-
10
- const BRIDGE_ACTOR = (did) => `https://bsky.brid.gy/ap/${did}`;
11
- const AP_ACCEPT = 'application/activity+json, application/ld+json; profile="https://www.w3.org/ns/activitystreams"';
12
-
13
- export class BskyGroup {
14
- constructor({ store, atproto, intake, publisher, log = console.log, fetcher = null }) {
15
- Object.assign(this, { store, atproto, intake, publisher, log });
16
- this.fetcher = fetcher; // tests inject; default rides safefetch lazily
17
- }
18
-
19
- // A DID is bridged when Bridgy Fed serves an AP actor for it. Cached on
20
- // whatever record the caller keeps, so one join costs one probe.
21
- async isBridged(did) {
22
- try {
23
- const { safeFetch } = await import('../shared/safefetch.mjs');
24
- const doFetch = this.fetcher || ((u, i) => safeFetch(u, i));
25
- const res = await doFetch(BRIDGE_ACTOR(did), { headers: { accept: AP_ACCEPT } });
26
- return res.status < 400;
27
- } catch { return false; }
28
- }
29
-
30
- // ONE reply ever: tell an unbridged joiner how to reach the fediverse side.
31
- async nudge({ did, handle }) {
32
- const text = `@${handle} welcome! Follow @ap.brid.gy and your posts will reach `
33
- + 'the Fediverse side of this group too.';
34
- const start = 0;
35
- const rec = this.atproto.read();
36
- await this.atproto.xrpc('com.atproto.repo.createRecord', {
37
- body: {
38
- repo: rec.did, collection: 'app.bsky.feed.post',
39
- record: {
40
- $type: 'app.bsky.feed.post', text,
41
- facets: [{
42
- index: { byteStart: start, byteEnd: start + 1 + handle.length },
43
- features: [{ $type: 'app.bsky.richtext.facet#mention', did }],
44
- }],
45
- createdAt: new Date().toISOString(),
46
- },
47
- },
48
- });
49
- }
50
-
51
- // A native follow of the group's Bluesky account. Bridged accounts are left
52
- // to join through the bridge; unbridged ones become Bluesky-only members,
53
- // through the same approval queue as everyone else.
54
- async onFollow(author) {
55
- const actor = profileUrl(author.did);
56
- const contacts = this.store.getContacts();
57
- if (contacts.followers.some(f => f.actor === actor)) return;
58
- if (this.store.getRequests().some(r => r.actor === actor)) return;
59
- if (this.store.isBlocked(actor)) return;
60
- if (await this.isBridged(author.did)) {
61
- this.log(`bluesky follow from bridged @${author.handle} — their join arrives over AP`);
62
- return;
63
- }
64
- const member = { actor, bsky: { did: author.did, handle: author.handle } };
65
- if (this.store.getConfig()?.approveJoins) {
66
- const reqs = this.store.getRequests();
67
- reqs.unshift({ ...member, at: new Date().toISOString() });
68
- this.store.setRequests(reqs);
69
- this.log(`bluesky join request held: @${author.handle}`);
70
- return;
71
- }
72
- contacts.followers.push(member);
73
- this.store.setContacts(contacts);
74
- this.store.addNotification({ type: 'follow', actor, bsky: true });
75
- await this.nudgeOnce(member);
76
- this.log(`bluesky member joined: @${author.handle}`);
77
- }
78
-
79
- async nudgeOnce(member) {
80
- if (member.nudgedAt) return;
81
- try {
82
- await this.nudge(member.bsky);
83
- member.nudgedAt = new Date().toISOString();
84
- const contacts = this.store.getContacts();
85
- const rec = contacts.followers.find(f => f.actor === member.actor);
86
- if (rec) { rec.nudgedAt = member.nudgedAt; this.store.setContacts(contacts); }
87
- } catch (e) { this.log(`bluesky nudge failed for @${member.bsky?.handle}: ${e.message}`); }
88
- }
89
-
90
- // A mention of the group's handle is a submission — same rules as an AP
91
- // member addressing the group. amplify() applies the member, mute, and
92
- // review checks; its bsky branch calls carry() below.
93
- async onMention(n) {
94
- const actor = profileUrl(n.author.did);
95
- const isMember = this.store.getContacts().followers.some(f => f.actor === actor);
96
- if (!isMember) { this.log(`bluesky mention from non-member @${n.author.handle} — ignored`); return; }
97
- await this.intake.amplify(n.uri, {});
98
- }
99
-
100
- // The group's carry of a member's Bluesky post: a native repost. AP
101
- // followers get it only when the author is bridged — the group never
102
- // fabricates an AP object for someone else's words.
103
- async carry(s) {
104
- const rec = this.atproto.read();
105
- const out = await this.atproto.xrpc('com.atproto.repo.createRecord', {
106
- body: {
107
- repo: rec.did, collection: 'app.bsky.feed.repost',
108
- record: {
109
- $type: 'app.bsky.feed.repost',
110
- subject: { uri: s.noteId, cid: s.cid },
111
- createdAt: new Date().toISOString(),
112
- },
113
- },
114
- });
115
- this.store.updateStatus(s.noteId, { announcedAt: new Date().toISOString(), repostUri: out.uri });
116
- this.store.setPending(this.store.getPending().filter(p => p.noteId !== s.noteId));
117
- this.log(`bluesky repost: ${s.noteId}`);
118
- return { ok: true, noteId: s.noteId, repost: out.uri };
119
- }
120
-
121
- // Unsay a repost.
122
- async retract(s) {
123
- if (!s.repostUri) throw new Error('that post was never carried');
124
- await this.atproto.deleteCrossPost(s.repostUri);
125
- this.store.updateStatus(s.noteId, {
126
- announcedAt: undefined, repostUri: undefined, retractedAt: new Date().toISOString(),
127
- });
128
- return { ok: true, noteId: s.noteId, inboxes: 0 };
129
- }
130
-
131
- // An AP member's post the group just Announced, shown natively to the
132
- // group's Bluesky followers: the group account posts the conversion with
133
- // attribution and the link back.
134
- async mirrorCarry(s) {
135
- if (!this.store.getConfig()?.atproto?.crossPost) return;
136
- const handle = this.store.handleOf?.(s.actor) || s.actor;
137
- const plain = String(s.content || '').replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim();
138
- const body = bskyText(`via ${handle}: ${plain}`, s.noteId);
139
- const rec = this.atproto.read();
140
- await this.atproto.xrpc('com.atproto.repo.createRecord', {
141
- body: {
142
- repo: rec.did, collection: 'app.bsky.feed.post',
143
- record: {
144
- $type: 'app.bsky.feed.post', text: body.text,
145
- ...(body.facets.length ? { facets: body.facets } : {}),
146
- createdAt: new Date().toISOString(),
147
- },
148
- },
149
- });
150
- }
151
-
152
- // Ejection's Bluesky half: the DID is blocked so the departure sticks.
153
- async blockDid(did) {
154
- const rec = this.atproto.read();
155
- await this.atproto.xrpc('com.atproto.repo.createRecord', {
156
- body: {
157
- repo: rec.did, collection: 'app.bsky.graph.block',
158
- record: { $type: 'app.bsky.graph.block', subject: did, createdAt: new Date().toISOString() },
159
- },
160
- });
161
- }
162
- }
@@ -1,253 +0,0 @@
1
- // fediacct.mjs — the agent's connections to fediverse accounts the owner holds
2
- // on OTHER servers. The agent is an API client of an existing Mastodon-API
3
- // account, the same relationship a Mastodon app has to this agent, and the same
4
- // one atproto.mjs has to a Bluesky account. Nothing here is a second identity:
5
- // no key, no pod, nothing published.
6
- //
7
- // One credential per account, stamped with the actor it was connected for
8
- // (the keys.json rules). A record stamped for someone else is skipped rather
9
- // than fatal, so one stray one cannot cost the owner the whole roster.
10
- //
11
- // Where they are kept is vault.mjs's decision: this machine when the identity
12
- // runs on a laptop, the identity's own pod when it runs inside the pod server.
13
- // Either way config.json gets the handle and the host and nothing more — the
14
- // roster is public to anyone who can read the identity's config, the token
15
- // never is.
16
-
17
- import path from 'node:path';
18
- import crypto from 'node:crypto';
19
- import { fileVault } from './vault.mjs';
20
- import { safeFetch, retryAfterMs } from '../shared/safefetch.mjs';
21
-
22
- const DIR = 'fediaccts';
23
- const APPS = '_apps';
24
- const SCOPES = 'read write';
25
- const STATE_TTL_MS = 10 * 60_000;
26
- const CLIENT_NAME = 'FediPod';
27
-
28
- // The id becomes a file name, and both halves come from a remote server's own
29
- // answer. `--handle ../escape` is the shape of bug this project has already
30
- // paid for once, so the result is checked rather than trusted.
31
- export function safeId(user, host) {
32
- const id = `${String(user || '').trim()}@${String(host || '').trim()}`
33
- .toLowerCase().replace(/[^a-z0-9.@_-]/g, '-');
34
- if (!/^[a-z0-9][a-z0-9.@_-]{0,80}$/.test(id) || id.includes('..')) return null;
35
- return id;
36
- }
37
-
38
- // A host as we will address it: bare authority, no scheme, no path, no case.
39
- export function cleanHost(host) {
40
- const raw = String(host || '').trim().replace(/^https?:\/\//i, '').replace(/\/.*$/, '');
41
- return /^[a-z0-9.-]+(:\d+)?$/i.test(raw) ? raw.toLowerCase() : null;
42
- }
43
-
44
- export class FediAccounts {
45
- /**
46
- * `vault` and `apps` are where the credentials and the per-host app
47
- * registrations live. Absent, they are the directories beside the
48
- * credential file, which is what a laptop wants.
49
- */
50
- constructor({ localDir, vault = null, apps = null, actorId = null, log = console.log, fetcher = null }) {
51
- this.vault = vault || fileVault(path.join(localDir, DIR));
52
- this.apps = apps || fileVault(path.join(localDir, DIR, APPS));
53
- this.actorId = actorId;
54
- this.log = log;
55
- // Injectable for tests; the default is the politeness stack, which also
56
- // refuses private addresses — a connected account is on the public web.
57
- this.fetcher = fetcher || ((url, init) => safeFetch(url, init));
58
- this.pausedUntil = new Map(); // host → epoch ms
59
- this.pending = new Map(); // state nonce → { host, redirectUri, at }
60
- }
61
-
62
- // ---- records ----
63
-
64
- ids() { return this.vault.names().filter(n => !n.startsWith('_')); }
65
-
66
- read(id) {
67
- const rec = this.vault.read(id);
68
- if (!rec) return null;
69
- if (rec?.mintedFor && this.actorId && rec.mintedFor !== this.actorId) {
70
- this.log(`the connection record for ${id} belongs to ${rec.mintedFor} — not reusing it for ${this.actorId}`);
71
- return null;
72
- }
73
- return rec;
74
- }
75
-
76
- write(rec) { this.vault.write(rec.id, rec); }
77
-
78
- list() { return this.ids().map(id => this.read(id)).filter(Boolean); }
79
-
80
- connected() { return this.list().some(r => r.token && r.enabled !== false); }
81
-
82
- // What may be written to pod state: who, where, and whether it is polled.
83
- roster() {
84
- return this.list().map(r => ({
85
- id: r.id, handle: r.handle, host: r.host,
86
- addedAt: r.addedAt || null, enabled: r.enabled !== false,
87
- }));
88
- }
89
-
90
- status() {
91
- return this.list().map(r => ({
92
- id: r.id, handle: r.handle, host: r.host,
93
- enabled: r.enabled !== false,
94
- needsReconnect: !!r.needsReconnect,
95
- cooldownFor: Math.max(0, Math.round(((this.pausedUntil.get(r.host) || 0) - Date.now()) / 1000)),
96
- }));
97
- }
98
-
99
- setEnabled(id, on) {
100
- const rec = this.read(id);
101
- if (!rec) return null;
102
- this.write({ ...rec, enabled: !!on });
103
- return this.roster().find(r => r.id === id) || null;
104
- }
105
-
106
- async remove(id) {
107
- if (!this.read(id)) return false;
108
- if (!await this.vault.remove(id)) return false;
109
- this.log(`Fediverse account disconnected: ${id}`);
110
- return true;
111
- }
112
-
113
- // ---- HTTP ----
114
-
115
- async _fetch(host, url, init) {
116
- const left = (this.pausedUntil.get(host) || 0) - Date.now();
117
- if (left > 0) throw new Error(`${host} asked us to back off — ${Math.ceil(left / 1000)}s left`);
118
- const res = await this.fetcher(url, init);
119
- if (res.status === 429 || res.status === 503) {
120
- const ms = retryAfterMs(res) ?? 60_000;
121
- this.pausedUntil.set(host, Date.now() + ms);
122
- this.log(`${host} answered ${res.status} — pausing for ${Math.round(ms / 1000)}s`);
123
- }
124
- return res;
125
- }
126
-
127
- async _json(res, host) {
128
- const body = await res.json().catch(() => ({}));
129
- if (res.status >= 400) {
130
- const err = new Error(body.error_description || body.error || `${host} answered ${res.status}`);
131
- err.status = res.status;
132
- throw err;
133
- }
134
- return body;
135
- }
136
-
137
- // ---- the OAuth dance, as the client ----
138
-
139
- // One app registration per host, remembered. The redirect_uri is part of what
140
- // was registered, so an agent that moved to another port re-registers rather
141
- // than sending the server a redirect it will refuse.
142
- async appFor(host, redirectUri) {
143
- const known = this.apps.read(host);
144
- if (known?.redirectUri === redirectUri && known.clientId && known.clientSecret) return known;
145
- const res = await this._fetch(host, `https://${host}/api/v1/apps`, {
146
- method: 'POST',
147
- headers: { 'content-type': 'application/json' },
148
- body: JSON.stringify({
149
- client_name: CLIENT_NAME, redirect_uris: redirectUri,
150
- scopes: SCOPES, website: 'https://github.com/jeff-zucker/FediPod',
151
- }),
152
- });
153
- const body = await this._json(res, host);
154
- if (!body.client_id || !body.client_secret) throw new Error(`${host} registered no client`);
155
- const app = { host, redirectUri, clientId: body.client_id, clientSecret: body.client_secret };
156
- this.apps.write(host, app);
157
- return app;
158
- }
159
-
160
- // Step one: where to send the browser. The state nonce is single-use and
161
- // short-lived — without one, any page the owner visits could hand our
162
- // callback somebody else's authorization code and bind THEIR account here.
163
- async begin({ host, redirectUri }) {
164
- const h = cleanHost(host);
165
- if (!h) throw new Error('that is not a server address');
166
- const app = await this.appFor(h, redirectUri);
167
- const state = crypto.randomBytes(24).toString('hex');
168
- const now = Date.now();
169
- for (const [k, v] of this.pending) if (now - v.at > STATE_TTL_MS) this.pending.delete(k);
170
- this.pending.set(state, { host: h, redirectUri, at: now });
171
- const u = new URL(`https://${h}/oauth/authorize`);
172
- u.searchParams.set('client_id', app.clientId);
173
- u.searchParams.set('redirect_uri', redirectUri);
174
- u.searchParams.set('response_type', 'code');
175
- u.searchParams.set('scope', SCOPES);
176
- u.searchParams.set('state', state);
177
- return { url: u.href, state, host: h };
178
- }
179
-
180
- // Step two: the code comes back. Exchange it, ask the server who we now are,
181
- // and only then write anything.
182
- async complete({ state, code }) {
183
- const pend = this.pending.get(state);
184
- if (!pend || Date.now() - pend.at > STATE_TTL_MS) throw new Error('that sign-in has expired — start again');
185
- this.pending.delete(state);
186
- const { host, redirectUri } = pend;
187
- const app = await this.appFor(host, redirectUri);
188
- const res = await this._fetch(host, `https://${host}/oauth/token`, {
189
- method: 'POST',
190
- headers: { 'content-type': 'application/json' },
191
- body: JSON.stringify({
192
- grant_type: 'authorization_code', code, redirect_uri: redirectUri,
193
- client_id: app.clientId, client_secret: app.clientSecret, scope: SCOPES,
194
- }),
195
- });
196
- const tok = await this._json(res, host);
197
- if (!tok.access_token) throw new Error(`${host} returned no token`);
198
- const me = await this.whoami(host, tok.access_token);
199
- const id = safeId(me.username, host);
200
- if (!id) throw new Error(`${host} answered with a name we cannot file safely`);
201
- this.write({
202
- id, host, token: tok.access_token, scope: tok.scope || SCOPES,
203
- handle: `@${me.username}@${host}`, acct: me.acct || me.username,
204
- accountId: String(me.id), actorUrl: me.url || null,
205
- name: me.display_name || me.username, icon: me.avatar || null,
206
- addedAt: new Date().toISOString(), enabled: true,
207
- ...(this.actorId ? { mintedFor: this.actorId } : {}),
208
- });
209
- // The owner has just been through a browser to get this; it is written
210
- // down before they are told it worked.
211
- if (!await this.vault.commit()) throw new Error('the connection could not be written down');
212
- this.log(`Fediverse account connected: @${me.username}@${host}`);
213
- return this.roster().find(r => r.id === id);
214
- }
215
-
216
- async whoami(host, token) {
217
- const res = await this._fetch(host, `https://${host}/api/v1/accounts/verify_credentials`, {
218
- headers: { authorization: `Bearer ${token}` },
219
- });
220
- const me = await this._json(res, host);
221
- if (!me.username) throw new Error(`${host} did not say who we are`);
222
- return me;
223
- }
224
-
225
- // ---- authenticated calls on behalf of one connected account ----
226
-
227
- // Returns the Response, so a caller can read rate-limit headers. A 401 means
228
- // the token was revoked on the far side: the account is marked rather than
229
- // deleted, because the owner should be told rather than quietly dropped.
230
- async api(id, pathname, init = {}) {
231
- const rec = this.read(id);
232
- if (!rec?.token) throw new Error(`no connected account ${id}`);
233
- const url = pathname.startsWith('https://') ? pathname : `https://${rec.host}${pathname}`;
234
- const res = await this._fetch(rec.host, url, {
235
- ...init,
236
- headers: { ...(init.headers || {}), authorization: `Bearer ${rec.token}` },
237
- });
238
- if (res.status === 401) {
239
- if (!rec.needsReconnect) this.write({ ...rec, needsReconnect: true });
240
- const err = new Error(`${rec.handle} no longer accepts our token — reconnect it`);
241
- err.status = 401;
242
- throw err;
243
- }
244
- if (rec.needsReconnect && res.status < 400) this.write({ ...rec, needsReconnect: false });
245
- return res;
246
- }
247
-
248
- async apiJson(id, pathname, init = {}) {
249
- const rec = this.read(id);
250
- const res = await this.api(id, pathname, init);
251
- return this._json(res, rec?.host || id);
252
- }
253
- }