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,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
- }