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,98 +0,0 @@
1
- // webpush.mjs — Mastodon-style Web Push from the agent itself. The VAPID
2
- // keypair is minted once into state; each client login keeps one subscription;
3
- // payloads go straight from this process to the browser's push service, so a
4
- // closed client still hears about mentions. No relay, no third party account,
5
- // and nothing here touches the pod.
6
- import crypto from 'node:crypto';
7
- import webpush from 'web-push';
8
-
9
- export class Push {
10
- constructor({ store, subject, log = () => {} }) {
11
- this.store = store;
12
- this.subject = subject; // () => the actor URL, VAPID's contact
13
- this.log = log;
14
- }
15
-
16
- state() { return this.store.read('webpush.json', { subs: {} }); }
17
- save(s) { this.store.write('webpush.json', s); }
18
-
19
- vapid() {
20
- const st = this.state();
21
- if (!st.vapid) {
22
- st.vapid = webpush.generateVAPIDKeys();
23
- this.save(st);
24
- this.log('webpush: VAPID keypair minted');
25
- }
26
- return st.vapid;
27
- }
28
-
29
- publicKey() { return this.vapid().publicKey; }
30
-
31
- // One subscription per client login, keyed by a hash of its bearer token —
32
- // the token itself is never written down twice.
33
- keyOf(token) { return crypto.createHash('sha256').update(String(token)).digest('hex').slice(0, 16); }
34
-
35
- get(token) { return this.state().subs[this.keyOf(token)] || null; }
36
-
37
- set(token, { endpoint, keys, alerts }) {
38
- if (!/^https:\/\//.test(String(endpoint || ''))) return null;
39
- if (!keys?.p256dh || !keys?.auth) return null;
40
- const st = this.state();
41
- st.subs[this.keyOf(token)] = { endpoint, keys: { p256dh: keys.p256dh, auth: keys.auth }, alerts: alerts || {} };
42
- this.save(st);
43
- return st.subs[this.keyOf(token)];
44
- }
45
-
46
- setAlerts(token, alerts) {
47
- const st = this.state();
48
- const sub = st.subs[this.keyOf(token)];
49
- if (!sub) return null;
50
- sub.alerts = { ...sub.alerts, ...(alerts || {}) };
51
- this.save(st);
52
- return sub;
53
- }
54
-
55
- drop(token) {
56
- const st = this.state();
57
- delete st.subs[this.keyOf(token)];
58
- this.save(st);
59
- }
60
-
61
- json(token, sub) {
62
- return {
63
- id: this.keyOf(token), endpoint: sub.endpoint,
64
- alerts: sub.alerts || {}, policy: 'all',
65
- server_key: this.publicKey(),
66
- };
67
- }
68
-
69
- // Push one notification to every subscription whose alerts allow the type.
70
- // A push service answering 404/410 means the browser dropped the
71
- // subscription — it is removed rather than retried forever.
72
- async notify(n, payload) {
73
- const st = this.state();
74
- const entries = Object.entries(st.subs);
75
- if (!entries.length) return;
76
- const { publicKey, privateKey } = this.vapid();
77
- const details = { subject: this.subject(), publicKey, privateKey };
78
- let dropped = false;
79
- for (const [key, sub] of entries) {
80
- if (sub.alerts && sub.alerts[n.type] === false) continue;
81
- try {
82
- await webpush.sendNotification(
83
- { endpoint: sub.endpoint, keys: sub.keys },
84
- JSON.stringify(payload),
85
- { vapidDetails: details, contentEncoding: 'aes128gcm', TTL: 3600 },
86
- );
87
- } catch (e) {
88
- if (e?.statusCode === 404 || e?.statusCode === 410) {
89
- delete st.subs[key];
90
- dropped = true;
91
- } else {
92
- this.log(`webpush: ${e?.statusCode || ''} ${e?.message || e}`);
93
- }
94
- }
95
- }
96
- if (dropped) this.save(st);
97
- }
98
- }
@@ -1,257 +0,0 @@
1
- // acctfeed.mjs — polls each connected fediverse account's home timeline and
2
- // notifications and merges them into the statuses index as kind 'acct'. View
3
- // cache only: none of it is written to the pod, which holds followed and own
4
- // content. Modeled on bskyfeed.mjs, which is modeled on tagfeed.mjs.
5
- //
6
- // A note is NOT re-fetched at its origin before being stored. The dereference
7
- // rule guards material an unauthenticated stranger chose for us; this is the
8
- // owner's own authenticated account answering with the result of the owner's
9
- // own follow decisions there — the same trust anchor that lets bskyfeed skip
10
- // it. A local block still applies, because that is the owner's decision.
11
- //
12
- // Config in fediacctfeed.json: { intervalMin, accounts: { <id>: marks } }.
13
-
14
- import { sanitizeHtml } from '../core/wire.mjs';
15
-
16
- const DEFAULTS = { intervalMin: 5 };
17
- const PER_SWEEP = 40;
18
- const MAX_PER_ACCT = 150;
19
- const MAX_ACCT_ENTRIES = 400;
20
- const BACKOFF_MIN_MS = 5 * 60_000;
21
- const BACKOFF_MAX_MS = 2 * 60 * 60_000;
22
- // Mastodon allows 300 requests per five minutes per token. Four go per sweep,
23
- // so the floor is only ever reached by something else using the same token.
24
- const RATE_FLOOR = 20;
25
-
26
- const MEDIA_TYPES = {
27
- image: 'image/jpeg', gifv: 'image/gif', video: 'video/mp4', audio: 'audio/mpeg',
28
- };
29
-
30
- export class AcctFeed {
31
- constructor({ store, accounts, log = console.log, onNotification = null }) {
32
- Object.assign(this, { store, accounts, log, onNotification });
33
- this.state = new Map(); // account id → { failures, quietUntil }
34
- this.lastSweep = null;
35
- this.lastAdded = 0;
36
- }
37
-
38
- config() { return { ...DEFAULTS, ...this.store.read('fediacctfeed.json', {}) }; }
39
-
40
- setConfig(patch) {
41
- const clean = {};
42
- if (patch.intervalMin) clean.intervalMin = Math.max(1, Number(patch.intervalMin) || DEFAULTS.intervalMin);
43
- this.store.write('fediacctfeed.json', { ...this.config(), ...clean });
44
- this.stop();
45
- this.start();
46
- return this.config();
47
- }
48
-
49
- marks(id) { return this.config().accounts?.[id] || {}; }
50
-
51
- setMarks(id, patch) {
52
- const cfg = this.config();
53
- this.store.write('fediacctfeed.json', {
54
- ...cfg, accounts: { ...(cfg.accounts || {}), [id]: { ...(cfg.accounts?.[id] || {}), ...patch } },
55
- });
56
- }
57
-
58
- start() {
59
- this.stopped = false;
60
- this.sweep().catch(e => this.log(`acctfeed: ${e.message}`));
61
- const tick = () => {
62
- this.timer = setTimeout(() => {
63
- this.sweep()
64
- .catch(e => this.log(`acctfeed: ${e.message}`))
65
- .finally(() => { if (!this.stopped) tick(); });
66
- }, Math.round(this.config().intervalMin * 60_000 * (0.85 + Math.random() * 0.3)));
67
- this.timer.unref?.();
68
- };
69
- tick();
70
- }
71
-
72
- stop() { this.stopped = true; clearTimeout(this.timer); }
73
-
74
- // Per account, not per feed: one server being down must not silence the rest.
75
- _stateOf(id) {
76
- if (!this.state.has(id)) this.state.set(id, { failures: 0, quietUntil: 0 });
77
- return this.state.get(id);
78
- }
79
-
80
- _backOff(id, handle, status, retryAfter) {
81
- const st = this._stateOf(id);
82
- st.failures += 1;
83
- const ladder = Math.min(BACKOFF_MIN_MS * 2 ** (st.failures - 1), BACKOFF_MAX_MS);
84
- const wait = retryAfter || Math.round(ladder * (0.85 + Math.random() * 0.3));
85
- st.quietUntil = Date.now() + wait;
86
- this.log(`acctfeed: ${handle} ${status ? `answered ${status}` : 'did not answer'} — not asking again for ${Math.round(wait / 60_000)} min`);
87
- }
88
-
89
- // One author into the shared actor cache. `uri` is the ActivityPub id, so a
90
- // person the pod already knows from its own inbox stays one actor here.
91
- _rememberAuthor(a) {
92
- const url = a?.uri || a?.url;
93
- if (!url) return null;
94
- if (!this.store.getActors()[url]) {
95
- this.store.cacheActor(url, {
96
- name: a.display_name || a.username, preferredUsername: a.username,
97
- icon: a.avatar || null, type: a.bot ? 'Service' : 'Person',
98
- });
99
- }
100
- return url;
101
- }
102
-
103
- // One Mastodon status into the statuses index. Never pre-checks for a
104
- // duplicate: a repeat sighting is what carries the second account's
105
- // provenance, and addStatus is the only thing that knows how to merge it.
106
- _mirror(st, { acct, via = null, parents = null }) {
107
- const noteId = st.uri;
108
- if (!noteId) return false;
109
- const actor = this._rememberAuthor(st.account);
110
- if (!actor || this.store.isBlocked(actor)) return false;
111
- const inReplyTo = st.in_reply_to_id ? parents?.get(String(st.in_reply_to_id)) : null;
112
- const out = this.store.addStatus({
113
- noteId, actor,
114
- content: sanitizeHtml(st.content || ''),
115
- published: st.created_at,
116
- kind: 'acct',
117
- sourceAccts: [{ acct, remoteId: String(st.id) }],
118
- ...(st.url && st.url !== noteId ? { link: st.url } : {}),
119
- ...(inReplyTo ? { inReplyTo } : {}),
120
- ...(via ? { via } : {}),
121
- ...(st.spoiler_text ? { spoiler: st.spoiler_text } : {}),
122
- ...(st.media_attachments?.length ? {
123
- attachments: st.media_attachments.map(m => ({
124
- url: m.url, mediaType: MEDIA_TYPES[m.type] || 'image/jpeg', description: m.description || '',
125
- })),
126
- } : {}),
127
- });
128
- return !!out?.added;
129
- }
130
-
131
- // Mastodon threads by its own local ids, which mean nothing anywhere else.
132
- // A parent we already hold from this same account can be resolved locally,
133
- // and one we do not hold simply has no parent here rather than a fetch.
134
- _parentMap(acct) {
135
- const map = new Map();
136
- for (const s of this.store.getStatuses()) {
137
- for (const v of s.sourceAccts || []) {
138
- if (v.acct === acct && v.remoteId) map.set(String(v.remoteId), s.noteId);
139
- }
140
- }
141
- return map;
142
- }
143
-
144
- _rateGuard(id, handle, res) {
145
- const left = Number(res.headers.get('x-ratelimit-remaining'));
146
- if (!Number.isFinite(left) || left > RATE_FLOOR) return false;
147
- const reset = Date.parse(res.headers.get('x-ratelimit-reset') || '');
148
- const wait = Number.isFinite(reset) ? Math.max(0, reset - Date.now()) : BACKOFF_MIN_MS;
149
- this._stateOf(id).quietUntil = Date.now() + wait;
150
- this.log(`acctfeed: ${handle} has ${left} requests left — waiting ${Math.round(wait / 1000)}s`);
151
- return true;
152
- }
153
-
154
- async sweep() {
155
- const rows = (this.accounts?.list() || []).filter(r => r.token && r.enabled !== false);
156
- if (!rows.length) return;
157
- this.lastSweep = new Date().toISOString();
158
- let added = 0;
159
- // The 300ms debounce cannot coalesce a sweep that awaits between items —
160
- // every fetch outlives it — so the whole sweep is one commit boundary.
161
- // Without this, statuses.json is serialized whole once per post.
162
- this.store.hold();
163
- try {
164
- for (const rec of rows) {
165
- if (this.stopped) break;
166
- added += await this._sweepOne(rec).catch((e) => {
167
- this._backOff(rec.id, rec.handle, e.status || 0, null);
168
- return 0;
169
- });
170
- }
171
- this._prune();
172
- } finally {
173
- this.store.release();
174
- }
175
- this.lastAdded = added;
176
- if (added) this.log(`acctfeed: +${added} from ${rows.length} connected account(s)`);
177
- }
178
-
179
- async _sweepOne(rec) {
180
- const st = this._stateOf(rec.id);
181
- if (st.quietUntil && Date.now() < st.quietUntil) return 0;
182
- const marks = this.marks(rec.id);
183
- const parents = this._parentMap(rec.id);
184
- let added = 0;
185
-
186
- const homeQ = marks.homeSinceId ? `&since_id=${encodeURIComponent(marks.homeSinceId)}` : '';
187
- const homeRes = await this.accounts.api(rec.id, `/api/v1/timelines/home?limit=${PER_SWEEP}${homeQ}`);
188
- if (this._rateGuard(rec.id, rec.handle, homeRes)) return 0;
189
- const home = await homeRes.json().catch(() => []);
190
- if (!Array.isArray(home)) throw new Error(`${rec.handle} answered with no timeline`);
191
- for (const item of home) {
192
- if (item?.reblog) {
193
- // A boost: the inner post is the content, the booster is the carrier —
194
- // the same envelope statusOrBoost already renders for our own timeline.
195
- const via = this._rememberAuthor(item.account);
196
- if (this._mirror(item.reblog, { acct: rec.id, via, parents })) added++;
197
- } else if (item?.uri) {
198
- if (this._mirror(item, { acct: rec.id, parents })) added++;
199
- }
200
- }
201
- // Mastodon returns newest first, so the first row is the new mark.
202
- // Ids are 19-digit snowflakes: opaque strings, never numbers.
203
- if (home.length && home[0]?.id) this.setMarks(rec.id, { homeSinceId: String(home[0].id) });
204
-
205
- const notifQ = marks.notifSinceId ? `&since_id=${encodeURIComponent(marks.notifSinceId)}` : '';
206
- const notifRes = await this.accounts.api(rec.id, `/api/v1/notifications?limit=${PER_SWEEP}${notifQ}`);
207
- if (this._rateGuard(rec.id, rec.handle, notifRes)) return added;
208
- const notes = await notifRes.json().catch(() => []);
209
- if (Array.isArray(notes)) {
210
- for (const n of notes) {
211
- if (!n?.account || String(n.account.id) === String(rec.accountId)) continue;
212
- const actor = this._rememberAuthor(n.account);
213
- if (!actor || this.store.isBlocked(actor)) continue;
214
- // The post a favourite or boost is about is one of this account's own,
215
- // so it is mirrored first — a notification pointing at nothing is a
216
- // row the client drops.
217
- if (n.status?.uri) this._mirror(n.status, { acct: rec.id, parents });
218
- if (n.type === 'favourite' || n.type === 'reblog') {
219
- this.store.addNotification({
220
- type: n.type === 'favourite' ? 'favourite' : 'reblog',
221
- actor, noteId: n.status?.uri, via: rec.id,
222
- });
223
- } else if (n.type === 'follow' || n.type === 'follow_request') {
224
- this.store.addNotification({ type: 'follow', actor, via: rec.id });
225
- await this.onNotification?.(n, { actor, acct: rec.id });
226
- } else if (n.type === 'mention') {
227
- this.store.addNotification({ type: 'mention', actor, noteId: n.status?.uri, via: rec.id });
228
- await this.onNotification?.(n, { actor, acct: rec.id });
229
- }
230
- }
231
- if (notes.length && notes[0]?.id) this.setMarks(rec.id, { notifSinceId: String(notes[0].id) });
232
- }
233
-
234
- st.failures = 0;
235
- return added;
236
- }
237
-
238
- // Two caps. The per-account one keeps a busy account from crowding out a
239
- // quiet one; the total keeps every connected account together from evicting
240
- // the pod's own posts and the timeline it already had.
241
- _prune() {
242
- const all = this.store.getStatuses();
243
- const mine = all.filter(s => s.kind === 'acct');
244
- if (!mine.length) return;
245
- const drop = new Set();
246
- const seen = new Map();
247
- for (const s of mine) {
248
- const acct = s.sourceAccts?.[0]?.acct || '?';
249
- const n = (seen.get(acct) || 0) + 1;
250
- seen.set(acct, n);
251
- if (n > MAX_PER_ACCT) drop.add(s.noteId);
252
- }
253
- for (const s of mine.slice(MAX_ACCT_ENTRIES)) drop.add(s.noteId);
254
- if (!drop.size) return;
255
- this.store.write('statuses.json', all.filter(s => !drop.has(s.noteId)));
256
- }
257
- }
@@ -1,309 +0,0 @@
1
- // atproto.mjs — the agent's connection to an ATProto (Bluesky) account. The
2
- // agent is an API CLIENT of an existing account on bsky.social or any PDS —
3
- // the same relationship a Mastodon app has to this agent. Nothing here hosts
4
- // ATProto; the pod stays the only public surface.
5
- //
6
- // Credential at AP_HOME/atproto.json (0600, atomic, stamped with the actor it
7
- // was connected for — the keys.json rules). The app password is kept so an
8
- // expired refresh token re-logins instead of demanding a reconnect.
9
-
10
- import { fileVault } from './vault.mjs';
11
- import { safeFetch, retryAfterMs } from '../shared/safefetch.mjs';
12
-
13
- const FILE = 'atproto.json';
14
- const POST_NSID = 'app.bsky.feed.post';
15
- const MAX_GRAPHEMES = 300;
16
- const MAX_IMAGES = 4;
17
- const MAX_BLOB_BYTES = 950_000;
18
-
19
- const enc = new TextEncoder();
20
- const seg = new Intl.Segmenter();
21
- const graphemes = (s) => { let n = 0; for (const _ of seg.segment(s)) n++; return n; };
22
- const byteLen = (s) => enc.encode(s).length;
23
-
24
- // Facet byte offsets are UTF-8 BYTE indexes into the text, not code units.
25
- function detectFacets(text) {
26
- const facets = [];
27
- for (const m of text.matchAll(/https?:\/\/[^\s<>"'\])]+/g)) {
28
- facets.push({
29
- index: { byteStart: byteLen(text.slice(0, m.index)), byteEnd: byteLen(text.slice(0, m.index + m[0].length)) },
30
- features: [{ $type: 'app.bsky.richtext.facet#link', uri: m[0] }],
31
- });
32
- }
33
- for (const m of text.matchAll(/(^|\s)#([\p{L}\p{N}_]+)/gu)) {
34
- const start = m.index + m[1].length;
35
- const tagText = '#' + m[2];
36
- facets.push({
37
- index: { byteStart: byteLen(text.slice(0, start)), byteEnd: byteLen(text.slice(0, start + tagText.length)) },
38
- features: [{ $type: 'app.bsky.richtext.facet#tag', tag: m[2] }],
39
- });
40
- }
41
- return facets;
42
- }
43
-
44
- // The post text Bluesky gets: the note's own text when it fits, otherwise a
45
- // grapheme-safe cut with a link to the pod note, which holds the whole thing.
46
- export function bskyText(text, noteUrl) {
47
- const clean = String(text || '').trim();
48
- if (graphemes(clean) <= MAX_GRAPHEMES) {
49
- return { text: clean, facets: detectFacets(clean), truncated: false };
50
- }
51
- const tail = '… ' + noteUrl;
52
- const budget = MAX_GRAPHEMES - graphemes(tail);
53
- let cut = '';
54
- let used = 0;
55
- for (const s of seg.segment(clean)) {
56
- if (used >= budget) break;
57
- cut += s.segment;
58
- used++;
59
- }
60
- const out = cut.trimEnd() + tail;
61
- const facets = detectFacets(out);
62
- return { text: out, facets, truncated: true };
63
- }
64
-
65
- export class Atproto {
66
- /**
67
- * `vault` is where the app password is kept: this machine when the identity
68
- * runs on a laptop, its own pod when it runs inside the pod server. Absent,
69
- * it is the file beside the credential, which is what a laptop wants.
70
- */
71
- constructor({ localDir, vault = null, actorId = null, log = console.log, fetcher = null }) {
72
- this.vault = vault || fileVault(localDir);
73
- this.name = vault ? 'bluesky' : FILE.replace(/\.json$/u, '');
74
- this.actorId = actorId;
75
- this.log = log;
76
- // Injectable for tests; the default is the politeness stack.
77
- this.fetcher = fetcher || ((url, init) => safeFetch(url, init));
78
- this.pausedUntil = 0;
79
- this.lastError = null;
80
- }
81
-
82
- // The stamped-record guard: a credential minted for another actor is treated
83
- // as absent, never silently adopted across identities.
84
- read() {
85
- const rec = this.vault.read(this.name);
86
- if (!rec) return null;
87
- if (rec?.mintedFor && this.actorId && rec.mintedFor !== this.actorId) {
88
- this.log(`the Bluesky connection belongs to ${rec.mintedFor} — not reusing it for ${this.actorId}`);
89
- return null;
90
- }
91
- return rec;
92
- }
93
-
94
- write(rec) { this.vault.write(this.name, rec); }
95
-
96
- connected() { return !!this.read()?.did; }
97
-
98
- status() {
99
- const rec = this.read();
100
- return {
101
- connected: !!rec?.did,
102
- service: rec?.service || null,
103
- handle: rec?.handle || null,
104
- did: rec?.did || null,
105
- lastError: this.lastError,
106
- cooldownFor: Math.max(0, Math.round((this.pausedUntil - Date.now()) / 1000)),
107
- };
108
- }
109
-
110
- async _fetch(url, init) {
111
- const left = this.pausedUntil - Date.now();
112
- if (left > 0) throw new Error(`bluesky asked us to back off — ${Math.ceil(left / 1000)}s left`);
113
- const res = await this.fetcher(url, init);
114
- if (res.status === 429 || res.status === 503) {
115
- const ms = retryAfterMs(res) ?? 60_000;
116
- this.pausedUntil = Date.now() + ms;
117
- this.log(`bluesky answered ${res.status} — pausing for ${Math.round(ms / 1000)}s`);
118
- }
119
- return res;
120
- }
121
-
122
- async _json(res) {
123
- const body = await res.json().catch(() => ({}));
124
- if (res.status >= 400) {
125
- const err = new Error(body.message || body.error || `bluesky answered ${res.status}`);
126
- err.status = res.status;
127
- err.code = body.error || null;
128
- throw err;
129
- }
130
- return body;
131
- }
132
-
133
- // One session mint, straight from service + identifier + app password.
134
- // The identifier reaches the server bare: a fediverse-habit leading @ makes
135
- // Bluesky parse the handle as an email and refuse it confusingly.
136
- async connect({ service, identifier, appPassword }) {
137
- const base = String(service || 'https://bsky.social').replace(/\/+$/, '');
138
- const id = String(identifier || '').trim().replace(/^@/, '');
139
- const res = await this._fetch(`${base}/xrpc/com.atproto.server.createSession`, {
140
- method: 'POST',
141
- headers: { 'content-type': 'application/json' },
142
- body: JSON.stringify({ identifier: id, password: appPassword }),
143
- });
144
- const s = await this._json(res);
145
- this.write({
146
- service: base, identifier: id, appPassword,
147
- did: s.did, handle: s.handle,
148
- accessJwt: s.accessJwt, refreshJwt: s.refreshJwt,
149
- ...(this.actorId ? { mintedFor: this.actorId } : {}),
150
- });
151
- this.lastError = null;
152
- this.log(`bluesky connected: @${s.handle} (${s.did})`);
153
- return { did: s.did, handle: s.handle, service: base };
154
- }
155
-
156
- // Best-effort session teardown, then the local record is gone regardless.
157
- async disconnect() {
158
- const rec = this.read();
159
- if (rec?.refreshJwt) {
160
- await this._fetch(`${rec.service}/xrpc/com.atproto.server.deleteSession`, {
161
- method: 'POST', headers: { authorization: `Bearer ${rec.refreshJwt}` },
162
- }).catch(() => {});
163
- }
164
- await this.vault.remove(this.name);
165
- this.log('bluesky disconnected');
166
- }
167
-
168
- async _refresh(rec) {
169
- const res = await this._fetch(`${rec.service}/xrpc/com.atproto.server.refreshSession`, {
170
- method: 'POST', headers: { authorization: `Bearer ${rec.refreshJwt}` },
171
- });
172
- if (res.status >= 400) {
173
- // The refresh token died; the stored app password re-mints the session.
174
- await this._json(res).catch(() => {});
175
- return this.connect({ service: rec.service, identifier: rec.identifier, appPassword: rec.appPassword })
176
- .then(() => this.read());
177
- }
178
- const s = await this._json(res);
179
- const next = { ...rec, accessJwt: s.accessJwt, refreshJwt: s.refreshJwt };
180
- this.write(next);
181
- return next;
182
- }
183
-
184
- // Mirror one public note. Returns { uri, cid } — the caller records it on
185
- // the status so a later delete can find the mirror.
186
- async crossPost({ text, published, attachments = [] }, { noteUrl }) {
187
- const rec = this.read();
188
- if (!rec?.did) throw new Error('no bluesky account connected');
189
- const body = bskyText(text, noteUrl);
190
- const images = [];
191
- for (const a of attachments.filter(x => /^image\//.test(x.mediaType || '')).slice(0, MAX_IMAGES)) {
192
- const res = await this._fetch(a.url, {});
193
- if (res.status >= 400) throw new Error(`attachment fetch failed (${res.status}): ${a.url}`);
194
- const bytes = Buffer.from(await res.arrayBuffer());
195
- if (bytes.length > MAX_BLOB_BYTES) { this.log(`bluesky: image too large, skipped (${a.url})`); continue; }
196
- const up = await this.xrpc('com.atproto.repo.uploadBlob', {
197
- body: bytes, contentType: a.mediaType,
198
- });
199
- images.push({ image: up.blob, alt: a.description || '' });
200
- }
201
- const record = {
202
- $type: POST_NSID,
203
- text: body.text,
204
- ...(body.facets.length ? { facets: body.facets } : {}),
205
- ...(images.length ? { embed: { $type: 'app.bsky.embed.images', images } } : {}),
206
- createdAt: published || new Date().toISOString(),
207
- };
208
- const out = await this.xrpc('com.atproto.repo.createRecord', {
209
- body: { repo: rec.did, collection: POST_NSID, record },
210
- });
211
- return { uri: out.uri, cid: out.cid, ...(body.truncated ? { truncated: true } : {}) };
212
- }
213
-
214
- // at://did/collection/rkey → deleteRecord. Absence on the far side is fine.
215
- async deleteCrossPost(uri) {
216
- const m = String(uri).match(/^at:\/\/([^/]+)\/([^/]+)\/(.+)$/);
217
- if (!m) throw new Error(`not an at:// uri: ${uri}`);
218
- await this.xrpc('com.atproto.repo.deleteRecord', {
219
- body: { repo: m[1], collection: m[2], rkey: m[3] },
220
- });
221
- }
222
-
223
- // The post as Bluesky holds it — for a cid an old mirror never stored, and
224
- // for the thread root a reply must carry.
225
- async _post(uri) {
226
- const out = await this.xrpc('app.bsky.feed.getPosts', { params: { uris: uri } });
227
- const p = (out.posts || [])[0];
228
- if (!p) throw new Error('that post is gone on Bluesky');
229
- return p;
230
- }
231
-
232
- // A native like or repost of someone's post — the record every Bluesky app
233
- // writes. Returns the minted record's uri, the only handle an undo has.
234
- async like(uri, cid = null) { return this._subjectRecord('app.bsky.feed.like', uri, cid); }
235
- async repost(uri, cid = null) { return this._subjectRecord('app.bsky.feed.repost', uri, cid); }
236
-
237
- async _subjectRecord(collection, uri, cid) {
238
- const rec = this.read();
239
- if (!rec?.did) throw new Error('no bluesky account connected');
240
- const subject = cid ? { uri, cid } : await this._post(uri).then(p => ({ uri: p.uri, cid: p.cid }));
241
- const out = await this.xrpc('com.atproto.repo.createRecord', {
242
- body: {
243
- repo: rec.did, collection,
244
- record: { $type: collection, subject, createdAt: new Date().toISOString() },
245
- },
246
- });
247
- return { uri: out.uri };
248
- }
249
-
250
- // A native reply under someone's post, threaded at the root their post
251
- // hangs from. A reply exists only on Bluesky — the fediverse cannot see the
252
- // parent — so there is no AP object and nothing to cross-post.
253
- async reply(text, parentUri) {
254
- const rec = this.read();
255
- if (!rec?.did) throw new Error('no bluesky account connected');
256
- const clean = String(text || '').trim();
257
- const n = graphemes(clean);
258
- if (n > MAX_GRAPHEMES) throw new Error(`a Bluesky reply fits ${MAX_GRAPHEMES} characters — this one is ${n}`);
259
- const p = await this._post(parentUri);
260
- const parent = { uri: p.uri, cid: p.cid };
261
- const facets = detectFacets(clean);
262
- const out = await this.xrpc('com.atproto.repo.createRecord', {
263
- body: {
264
- repo: rec.did, collection: POST_NSID,
265
- record: {
266
- $type: POST_NSID, text: clean,
267
- ...(facets.length ? { facets } : {}),
268
- reply: { root: p.record?.reply?.root || parent, parent },
269
- createdAt: new Date().toISOString(),
270
- },
271
- },
272
- });
273
- return { uri: out.uri, cid: out.cid };
274
- }
275
-
276
- // The one door every call goes through. GET when there is no body.
277
- // Retries exactly once after an expired-token refresh.
278
- async xrpc(nsid, { params = null, body = null, contentType = 'application/json', method = null } = {}) {
279
- let rec = this.read();
280
- if (!rec?.did) throw new Error('no bluesky account connected');
281
- const qs = params
282
- ? '?' + new URLSearchParams(Object.entries(params).filter(([, v]) => v != null)).toString()
283
- : '';
284
- const doCall = (r) => this._fetch(`${r.service}/xrpc/${nsid}${qs}`, {
285
- method: method || (body != null ? 'POST' : 'GET'),
286
- headers: {
287
- authorization: `Bearer ${r.accessJwt}`,
288
- ...(body != null ? { 'content-type': contentType } : {}),
289
- },
290
- ...(body != null ? { body: contentType === 'application/json' ? JSON.stringify(body) : body } : {}),
291
- });
292
- let res = await doCall(rec);
293
- if (res.status === 400 || res.status === 401) {
294
- const peek = await res.clone().json().catch(() => ({}));
295
- if (peek.error === 'ExpiredToken' || peek.error === 'InvalidToken') {
296
- rec = await this._refresh(rec);
297
- res = await doCall(rec);
298
- }
299
- }
300
- try {
301
- const out = await this._json(res);
302
- this.lastError = null;
303
- return out;
304
- } catch (e) {
305
- this.lastError = e.message;
306
- throw e;
307
- }
308
- }
309
- }