fedipod-server 0.29.0 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (440) hide show
  1. package/README.md +11 -6
  2. package/dist/door.d.ts +4 -0
  3. package/dist/door.js +31 -0
  4. package/dist/fedipod.d.ts +16 -0
  5. package/dist/fedipod.js +13 -0
  6. package/dist/front.d.ts +3 -0
  7. package/dist/front.js +94 -0
  8. package/dist/handler.d.ts +36 -101
  9. package/dist/handler.js +85 -645
  10. package/dist/handler.jsonld +2 -30
  11. package/dist/identities.d.ts +62 -0
  12. package/dist/identities.js +236 -0
  13. package/dist/mounts.d.ts +43 -0
  14. package/dist/mounts.js +123 -0
  15. package/dist/opt-in.d.ts +46 -0
  16. package/dist/opt-in.js +176 -0
  17. package/package.json +10 -18
  18. package/lib/client/c2s.mjs +0 -636
  19. package/lib/client/localapi.mjs +0 -87
  20. package/lib/client/masto/accounts.mjs +0 -310
  21. package/lib/client/masto/body.mjs +0 -156
  22. package/lib/client/masto/index.mjs +0 -206
  23. package/lib/client/masto/instance.mjs +0 -112
  24. package/lib/client/masto/media.mjs +0 -79
  25. package/lib/client/masto/oauth.mjs +0 -656
  26. package/lib/client/masto/render.mjs +0 -518
  27. package/lib/client/masto/statuses.mjs +0 -372
  28. package/lib/client/masto/timelines.mjs +0 -438
  29. package/lib/client/oidc-auth.mjs +0 -62
  30. package/lib/client/streaming.mjs +0 -154
  31. package/lib/client/webpush.mjs +0 -98
  32. package/lib/connections/acctfeed.mjs +0 -257
  33. package/lib/connections/atproto.mjs +0 -309
  34. package/lib/connections/bskyfeed.mjs +0 -243
  35. package/lib/connections/bskygroup.mjs +0 -162
  36. package/lib/connections/fediacct.mjs +0 -253
  37. package/lib/connections/import.mjs +0 -357
  38. package/lib/connections/tagfeed.mjs +0 -212
  39. package/lib/connections/vault.mjs +0 -114
  40. package/lib/core/as2.mjs +0 -206
  41. package/lib/core/contexts/activitystreams.json +0 -379
  42. package/lib/core/contexts/anno.json +0 -126
  43. package/lib/core/contexts/did-v1.json +0 -57
  44. package/lib/core/contexts/fep-5711.json +0 -36
  45. package/lib/core/contexts/gotosocial.json +0 -86
  46. package/lib/core/contexts/identity-v1.json +0 -152
  47. package/lib/core/contexts/index.mjs +0 -63
  48. package/lib/core/contexts/join-lemmy.json +0 -33
  49. package/lib/core/contexts/joinmastodon.json +0 -28
  50. package/lib/core/contexts/litepub-0.1.json +0 -60
  51. package/lib/core/contexts/map.json +0 -19
  52. package/lib/core/contexts/miscellany.json +0 -19
  53. package/lib/core/contexts/quotes.json +0 -18
  54. package/lib/core/contexts/schemaorg.json +0 -8845
  55. package/lib/core/contexts/security-data-integrity-v1.json +0 -78
  56. package/lib/core/contexts/security-data-integrity-v2.json +0 -81
  57. package/lib/core/contexts/security-multikey-v1.json +0 -35
  58. package/lib/core/contexts/security-v1.json +0 -74
  59. package/lib/core/contexts/webfinger.json +0 -10
  60. package/lib/core/deliver.mjs +0 -387
  61. package/lib/core/graphview.mjs +0 -327
  62. package/lib/core/intake/activities.mjs +0 -649
  63. package/lib/core/intake/activity.mjs +0 -318
  64. package/lib/core/intake/channel.mjs +0 -144
  65. package/lib/core/intake/group.mjs +0 -258
  66. package/lib/core/intake/index.mjs +0 -732
  67. package/lib/core/intake/notes.mjs +0 -367
  68. package/lib/core/intake/verify.mjs +0 -142
  69. package/lib/core/keys.mjs +0 -153
  70. package/lib/core/lease.mjs +0 -206
  71. package/lib/core/place.mjs +0 -82
  72. package/lib/core/polls.mjs +0 -105
  73. package/lib/core/profile-page.mjs +0 -104
  74. package/lib/core/proof.mjs +0 -79
  75. package/lib/core/publisher/collections.mjs +0 -272
  76. package/lib/core/publisher/index.mjs +0 -522
  77. package/lib/core/publisher/notes.mjs +0 -519
  78. package/lib/core/publisher/own.mjs +0 -143
  79. package/lib/core/publisher/questions.mjs +0 -241
  80. package/lib/core/publisher/restore.mjs +0 -224
  81. package/lib/core/shapes/activitystreams.ttl +0 -157
  82. package/lib/core/shapes/index.mjs +0 -132
  83. package/lib/core/shapes/shapes-text.mjs +0 -13
  84. package/lib/core/social.mjs +0 -561
  85. package/lib/core/storage.mjs +0 -176
  86. package/lib/core/store.mjs +0 -578
  87. package/lib/core/wire-quotes.mjs +0 -71
  88. package/lib/core/wire.mjs +0 -925
  89. package/lib/device/account.mjs +0 -87
  90. package/lib/device/admin/index.mjs +0 -13
  91. package/lib/device/admin/origins.mjs +0 -35
  92. package/lib/device/admin/routes/connections.mjs +0 -144
  93. package/lib/device/admin/routes/gateway.mjs +0 -199
  94. package/lib/device/admin/routes/lifecycle.mjs +0 -200
  95. package/lib/device/admin/routes/owner.mjs +0 -322
  96. package/lib/device/admin/routes/setup.mjs +0 -428
  97. package/lib/device/admin/routes/social.mjs +0 -188
  98. package/lib/device/admin/server.mjs +0 -95
  99. package/lib/device/admin/static.mjs +0 -247
  100. package/lib/device/admin/surface.mjs +0 -315
  101. package/lib/device/certs.mjs +0 -281
  102. package/lib/device/cli/commands/account.mjs +0 -586
  103. package/lib/device/cli/commands/run.mjs +0 -278
  104. package/lib/device/cli/commands/service.mjs +0 -221
  105. package/lib/device/cli/commands/setup.mjs +0 -489
  106. package/lib/device/cli/commands/state.mjs +0 -561
  107. package/lib/device/cli/context.mjs +0 -288
  108. package/lib/device/export-collections.mjs +0 -161
  109. package/lib/device/gateway-move.mjs +0 -58
  110. package/lib/device/home.mjs +0 -161
  111. package/lib/device/migrate.mjs +0 -115
  112. package/lib/device/ports.mjs +0 -27
  113. package/lib/device/remote.mjs +0 -54
  114. package/lib/device/setup.mjs +0 -496
  115. package/lib/device/update.mjs +0 -96
  116. package/lib/gateway/caches.mjs +0 -36
  117. package/lib/gateway/directory.mjs +0 -90
  118. package/lib/gateway/front-core.mjs +0 -966
  119. package/lib/gateway/gateway-core.mjs +0 -244
  120. package/lib/gateway/headers.mjs +0 -49
  121. package/lib/gateway/httpsig.mjs +0 -159
  122. package/lib/gateway/notices.mjs +0 -84
  123. package/lib/gateway/quiet.mjs +0 -189
  124. package/lib/gateway/relay-extras.mjs +0 -89
  125. package/lib/gateway/token-claims.mjs +0 -16
  126. package/lib/pod/README.md +0 -61
  127. package/lib/pod/actor.mjs +0 -96
  128. package/lib/pod/collection.mjs +0 -77
  129. package/lib/pod/containers.mjs +0 -140
  130. package/lib/pod/discovery.mjs +0 -46
  131. package/lib/pod/featured.mjs +0 -18
  132. package/lib/pod/followers.mjs +0 -24
  133. package/lib/pod/following.mjs +0 -10
  134. package/lib/pod/http.mjs +0 -74
  135. package/lib/pod/inbox.mjs +0 -166
  136. package/lib/pod/links.mjs +0 -35
  137. package/lib/pod/location.mjs +0 -52
  138. package/lib/pod/media.mjs +0 -27
  139. package/lib/pod/notes.mjs +0 -78
  140. package/lib/pod/notifications.mjs +0 -89
  141. package/lib/pod/outbox.mjs +0 -18
  142. package/lib/pod/package.json +0 -19
  143. package/lib/pod/policy.mjs +0 -58
  144. package/lib/pod/private.mjs +0 -19
  145. package/lib/pod/root.mjs +0 -104
  146. package/lib/pod/state.mjs +0 -40
  147. package/lib/pod/transport.mjs +0 -674
  148. package/lib/pod/type-index.mjs +0 -101
  149. package/lib/pod/urls.mjs +0 -91
  150. package/lib/server/embed.mjs +0 -424
  151. package/lib/session/README.md +0 -137
  152. package/lib/session/demo.html +0 -94
  153. package/lib/session/fedi-account.mjs +0 -417
  154. package/lib/session/fedi-login.mjs +0 -123
  155. package/lib/session/oidc-session.mjs +0 -233
  156. package/lib/session/package.json +0 -49
  157. package/lib/shared/guard.mjs +0 -213
  158. package/lib/shared/links.mjs +0 -9
  159. package/lib/shared/safefetch.mjs +0 -235
  160. package/lib/shared/ua.mjs +0 -29
  161. package/phanpy/dist/404.html +0 -40
  162. package/phanpy/dist/_headers +0 -2
  163. package/phanpy/dist/apple-touch-icon.png +0 -0
  164. package/phanpy/dist/assets/ICONS-DIPJeU0C.js +0 -2
  165. package/phanpy/dist/assets/ICONS-DIPJeU0C.js.map +0 -1
  166. package/phanpy/dist/assets/ScheduledAtField-D-Abfo4J.js +0 -183
  167. package/phanpy/dist/assets/ScheduledAtField-D-Abfo4J.js.map +0 -1
  168. package/phanpy/dist/assets/Temml-CCjoacWt.woff2 +0 -0
  169. package/phanpy/dist/assets/__vite-browser-external-C7iut881.js +0 -2
  170. package/phanpy/dist/assets/__vite-browser-external-C7iut881.js.map +0 -1
  171. package/phanpy/dist/assets/boosts-carousel-BiOaNdMT.jpg +0 -0
  172. package/phanpy/dist/assets/catch-up-O5q5C75Z.png +0 -0
  173. package/phanpy/dist/assets/chunk-aKtaBQYM.js +0 -1
  174. package/phanpy/dist/assets/compose-CFEx7wb4.js +0 -2
  175. package/phanpy/dist/assets/compose-CFEx7wb4.js.map +0 -1
  176. package/phanpy/dist/assets/compose-ZJN5xSb4.js +0 -35
  177. package/phanpy/dist/assets/compose-ZJN5xSb4.js.map +0 -1
  178. package/phanpy/dist/assets/debug.module-YFQgwnIr.js +0 -52
  179. package/phanpy/dist/assets/debug.module-YFQgwnIr.js.map +0 -1
  180. package/phanpy/dist/assets/dom-p1YcJ5Rw.js +0 -2
  181. package/phanpy/dist/assets/dom-p1YcJ5Rw.js.map +0 -1
  182. package/phanpy/dist/assets/home-mobile-dark@2x-Dc-J8-SM.png +0 -0
  183. package/phanpy/dist/assets/home-mobile-light@2x-Bd3fod8m.png +0 -0
  184. package/phanpy/dist/assets/home-tablet-dark@2x-BgcNI8le.png +0 -0
  185. package/phanpy/dist/assets/home-tablet-light@2x-BW2RV9T6.png +0 -0
  186. package/phanpy/dist/assets/icons/add-circle-line-WD0mwORD.js +0 -1
  187. package/phanpy/dist/assets/icons/alert-line-CpFUMf9s.js +0 -1
  188. package/phanpy/dist/assets/icons/android-2-line-Bzxnrd3S.js +0 -1
  189. package/phanpy/dist/assets/icons/announcement-line-5nwaJFJm.js +0 -1
  190. package/phanpy/dist/assets/icons/arrow-down-circle-line-a7Id9-Iv.js +0 -1
  191. package/phanpy/dist/assets/icons/arrow-down-line-B8uzXzq6.js +0 -1
  192. package/phanpy/dist/assets/icons/arrow-left-line-CGrGT9C-.js +0 -1
  193. package/phanpy/dist/assets/icons/arrow-right-line-CE-HERfX.js +0 -1
  194. package/phanpy/dist/assets/icons/arrow-to-up-line-CvdQjmcy.js +0 -1
  195. package/phanpy/dist/assets/icons/arrow-up-circle-line-DEparSNF.js +0 -1
  196. package/phanpy/dist/assets/icons/arrow-up-line-C7yBeugc.js +0 -1
  197. package/phanpy/dist/assets/icons/arrows-right-line-BtbXmJGP.js +0 -1
  198. package/phanpy/dist/assets/icons/at-line-CF66Bznk.js +0 -1
  199. package/phanpy/dist/assets/icons/attachment-line-BY8It-l9.js +0 -1
  200. package/phanpy/dist/assets/icons/blockquote-line-BS6pU_hm.js +0 -1
  201. package/phanpy/dist/assets/icons/board-line-C0UWXyA6.js +0 -1
  202. package/phanpy/dist/assets/icons/bookmark-line-1CtX5JLF.js +0 -1
  203. package/phanpy/dist/assets/icons/building-5-line-Dd59MzbO.js +0 -1
  204. package/phanpy/dist/assets/icons/bus-2-line-6_dvA0GG.js +0 -1
  205. package/phanpy/dist/assets/icons/calendar-day-line-Crpl_fmu.js +0 -1
  206. package/phanpy/dist/assets/icons/calendar-month-line-vommts2C.js +0 -1
  207. package/phanpy/dist/assets/icons/calendar-time-add-line-BU_DTlQk.js +0 -1
  208. package/phanpy/dist/assets/icons/camera-line-Cv69MEei.js +0 -1
  209. package/phanpy/dist/assets/icons/celebrate-line-B8DvIoi0.js +0 -1
  210. package/phanpy/dist/assets/icons/chart-bar-line-FK-QOmtF.js +0 -1
  211. package/phanpy/dist/assets/icons/chart-line-line-BGcJAFWw.js +0 -1
  212. package/phanpy/dist/assets/icons/chat-3-line-YFK2-hwz.js +0 -1
  213. package/phanpy/dist/assets/icons/check-circle-line-DsAxFr5-.js +0 -1
  214. package/phanpy/dist/assets/icons/clipboard-line-BBp364gJ.js +0 -1
  215. package/phanpy/dist/assets/icons/close-circle-line-CICdiuGz.js +0 -1
  216. package/phanpy/dist/assets/icons/close-line-DYrFWcjv.js +0 -1
  217. package/phanpy/dist/assets/icons/cloud-line-tiQAbah8.js +0 -1
  218. package/phanpy/dist/assets/icons/code-line-fAl8iiV3.js +0 -1
  219. package/phanpy/dist/assets/icons/comment-2-line-CR3g7WeB.js +0 -1
  220. package/phanpy/dist/assets/icons/copy-2-line-CLW69yF5.js +0 -1
  221. package/phanpy/dist/assets/icons/delete-2-line-B8xmbr4n.js +0 -1
  222. package/phanpy/dist/assets/icons/document-3-line-DBGNhAg2.js +0 -1
  223. package/phanpy/dist/assets/icons/document-line-C7mb9sb7.js +0 -1
  224. package/phanpy/dist/assets/icons/down-line-BGqSSThd.js +0 -1
  225. package/phanpy/dist/assets/icons/edit-4-line-DcgGaEhx.js +0 -1
  226. package/phanpy/dist/assets/icons/emoji-2-line-D8YgnM-y.js +0 -1
  227. package/phanpy/dist/assets/icons/exit-line-Dc07BS-x.js +0 -1
  228. package/phanpy/dist/assets/icons/external-link-line-BvpSkmjO.js +0 -1
  229. package/phanpy/dist/assets/icons/eye-2-line-CiWXx-f_.js +0 -1
  230. package/phanpy/dist/assets/icons/eye-close-line-BgTaSJ4P.js +0 -1
  231. package/phanpy/dist/assets/icons/filter-2-line-B_mR2usy.js +0 -1
  232. package/phanpy/dist/assets/icons/filter-line-C_nm-3cU.js +0 -1
  233. package/phanpy/dist/assets/icons/flag-1-line-C2HB05I0.js +0 -1
  234. package/phanpy/dist/assets/icons/forbid-circle-line-D3LTT9yd.js +0 -1
  235. package/phanpy/dist/assets/icons/formula-line-DLrt2d6j.js +0 -1
  236. package/phanpy/dist/assets/icons/grid-line-CgUmEikf.js +0 -1
  237. package/phanpy/dist/assets/icons/group-line-C5fnX0wU.js +0 -1
  238. package/phanpy/dist/assets/icons/hand-finger-2-line-BGgxbuL9.js +0 -1
  239. package/phanpy/dist/assets/icons/hashtag-line-CyTEEXBb.js +0 -1
  240. package/phanpy/dist/assets/icons/heart-crack-line-BZddbO2C.js +0 -1
  241. package/phanpy/dist/assets/icons/heart-line-CJ7K47J4.js +0 -1
  242. package/phanpy/dist/assets/icons/history-2-line-kWWvFzqv.js +0 -1
  243. package/phanpy/dist/assets/icons/history-line-bGQiwbTH.js +0 -1
  244. package/phanpy/dist/assets/icons/home-3-line-fRU1zeLG.js +0 -1
  245. package/phanpy/dist/assets/icons/information-line-iNk6-sOY.js +0 -1
  246. package/phanpy/dist/assets/icons/keyboard-line-DsCyuQNz.js +0 -1
  247. package/phanpy/dist/assets/icons/layout-4-line-CZkOX52Z.js +0 -1
  248. package/phanpy/dist/assets/icons/layout-5-line-B_znt5wO.js +0 -1
  249. package/phanpy/dist/assets/icons/left-line-eUw0tya4.js +0 -1
  250. package/phanpy/dist/assets/icons/lightning-line-DaKoDdix.js +0 -1
  251. package/phanpy/dist/assets/icons/link-2-line-DCqLmMA7.js +0 -1
  252. package/phanpy/dist/assets/icons/list-check-line-BAxosBh9.js +0 -1
  253. package/phanpy/dist/assets/icons/lock-line-CPoaXeUW.js +0 -1
  254. package/phanpy/dist/assets/icons/mail-line-B0P8aa1L.js +0 -1
  255. package/phanpy/dist/assets/icons/moon-line-CEqNAToP.js +0 -1
  256. package/phanpy/dist/assets/icons/more-1-fill-Dw2e5xab.js +0 -1
  257. package/phanpy/dist/assets/icons/more-3-line-BIiVjYTz.js +0 -1
  258. package/phanpy/dist/assets/icons/notification-line-BZZq2Gtu.js +0 -1
  259. package/phanpy/dist/assets/icons/pencil-line-CKh8-A1v.js +0 -1
  260. package/phanpy/dist/assets/icons/photo-album-line-Bnfg2u40.js +0 -1
  261. package/phanpy/dist/assets/icons/pin-line-DpTolyhs.js +0 -1
  262. package/phanpy/dist/assets/icons/play-fill-B0-G_8lX.js +0 -1
  263. package/phanpy/dist/assets/icons/qrcode-2-line-B6SPCaHn.js +0 -1
  264. package/phanpy/dist/assets/icons/quill-pen-line-B6LUrnCP.js +0 -1
  265. package/phanpy/dist/assets/icons/quote-left-fill-hQZ1cj_6.js +0 -1
  266. package/phanpy/dist/assets/icons/radar-line-CKyroTth.js +0 -1
  267. package/phanpy/dist/assets/icons/react-line-CoIKhhtK.js +0 -1
  268. package/phanpy/dist/assets/icons/refresh-2-line-D0mTF97S.js +0 -1
  269. package/phanpy/dist/assets/icons/right-line-B3KWJaqc.js +0 -1
  270. package/phanpy/dist/assets/icons/rocket-line-4KZl537D.js +0 -1
  271. package/phanpy/dist/assets/icons/round-fill-C69NTrZ0.js +0 -1
  272. package/phanpy/dist/assets/icons/round-line-B9k_1uo-.js +0 -1
  273. package/phanpy/dist/assets/icons/route-line-B28ReHcE.js +0 -1
  274. package/phanpy/dist/assets/icons/rows-4-line-CrBniagM.js +0 -1
  275. package/phanpy/dist/assets/icons/scan-line-DSf74YdP.js +0 -1
  276. package/phanpy/dist/assets/icons/search-2-line-B6geIU1z.js +0 -1
  277. package/phanpy/dist/assets/icons/settings-3-line-azJwK47w.js +0 -1
  278. package/phanpy/dist/assets/icons/settings-6-line-C_5zc5YR.js +0 -1
  279. package/phanpy/dist/assets/icons/share-2-line-D5MxWjrm.js +0 -1
  280. package/phanpy/dist/assets/icons/share-forward-line-Cp61fZzZ.js +0 -1
  281. package/phanpy/dist/assets/icons/sparkles-2-line-DYYyXTaW.js +0 -1
  282. package/phanpy/dist/assets/icons/sparkles-line-C2hY1s6I.js +0 -1
  283. package/phanpy/dist/assets/icons/time-line-BF3eet1v.js +0 -1
  284. package/phanpy/dist/assets/icons/transfer-4-line-oisfvTBw.js +0 -1
  285. package/phanpy/dist/assets/icons/translate-line-BFWol2Ce.js +0 -1
  286. package/phanpy/dist/assets/icons/unlock-line-X_x5vwv3.js +0 -1
  287. package/phanpy/dist/assets/icons/upload-3-line-DgzwUJeW.js +0 -1
  288. package/phanpy/dist/assets/icons/user-4-line-BVSynZCp.js +0 -1
  289. package/phanpy/dist/assets/icons/user-add-2-line-BlGS5Q8q.js +0 -1
  290. package/phanpy/dist/assets/icons/user-add-line-BbXdHG4z.js +0 -1
  291. package/phanpy/dist/assets/icons/user-edit-line-BFAHOV0X.js +0 -1
  292. package/phanpy/dist/assets/icons/user-follow-line-BXux0wSd.js +0 -1
  293. package/phanpy/dist/assets/icons/user-star-line-DdYlpHLU.js +0 -1
  294. package/phanpy/dist/assets/icons/user-warning-line-Cj43j0_E.js +0 -1
  295. package/phanpy/dist/assets/icons/user-x-line-DIDgNlPH.js +0 -1
  296. package/phanpy/dist/assets/icons/volume-line-Bs390KQt.js +0 -1
  297. package/phanpy/dist/assets/icons/volume-mute-line-DzlzIqFZ.js +0 -1
  298. package/phanpy/dist/assets/icons/walk-line-yNXfB-md.js +0 -1
  299. package/phanpy/dist/assets/icons/world-2-line-CenkGIwj.js +0 -1
  300. package/phanpy/dist/assets/icons/zoom-in-line-CaENUphf.js +0 -1
  301. package/phanpy/dist/assets/icons/zoom-out-line-BDNk0EWo.js +0 -1
  302. package/phanpy/dist/assets/instances-BNPptnn-.json +0 -333
  303. package/phanpy/dist/assets/locales/ar-SA-CfheRbCu.js +0 -1
  304. package/phanpy/dist/assets/locales/ca-ES-DHRW86Tz.js +0 -1
  305. package/phanpy/dist/assets/locales/cs-CZ-CKoqEuA1.js +0 -1
  306. package/phanpy/dist/assets/locales/de-DE-ByRPMRFy.js +0 -1
  307. package/phanpy/dist/assets/locales/eo-UY-CPC7wLCS.js +0 -1
  308. package/phanpy/dist/assets/locales/es-ES-dJa0Fd9t.js +0 -1
  309. package/phanpy/dist/assets/locales/eu-ES-C3djlPR2.js +0 -1
  310. package/phanpy/dist/assets/locales/fa-IR-DTT490Ck.js +0 -1
  311. package/phanpy/dist/assets/locales/fi-FI-B23MCPuz.js +0 -1
  312. package/phanpy/dist/assets/locales/fr-FR-BcaQsO5C.js +0 -1
  313. package/phanpy/dist/assets/locales/gl-ES-BR-CVKBN.js +0 -1
  314. package/phanpy/dist/assets/locales/he-IL-CfheRbCu.js +0 -1
  315. package/phanpy/dist/assets/locales/hu-HU-CfheRbCu.js +0 -1
  316. package/phanpy/dist/assets/locales/it-IT-C1136UFV.js +0 -1
  317. package/phanpy/dist/assets/locales/ja-JP-CRZmRAhD.js +0 -1
  318. package/phanpy/dist/assets/locales/kab-Cz8xH_TT.js +0 -1
  319. package/phanpy/dist/assets/locales/ko-KR-CG9UiY4o.js +0 -1
  320. package/phanpy/dist/assets/locales/lt-LT-CJcI6sHZ.js +0 -1
  321. package/phanpy/dist/assets/locales/nb-NO-CfheRbCu.js +0 -1
  322. package/phanpy/dist/assets/locales/nl-NL-DxN4Yc1t.js +0 -1
  323. package/phanpy/dist/assets/locales/oc-FR-CfheRbCu.js +0 -1
  324. package/phanpy/dist/assets/locales/pl-PL-DkwGXf6P.js +0 -1
  325. package/phanpy/dist/assets/locales/pseudo-LOCALE-CQ4XxkQ0.js +0 -1
  326. package/phanpy/dist/assets/locales/pt-BR-nGL0HdFS.js +0 -1
  327. package/phanpy/dist/assets/locales/pt-PT-Cp17aj2z.js +0 -1
  328. package/phanpy/dist/assets/locales/ru-RU-0Y8cIoaK.js +0 -1
  329. package/phanpy/dist/assets/locales/th-TH-CfheRbCu.js +0 -1
  330. package/phanpy/dist/assets/locales/tok-CfheRbCu.js +0 -1
  331. package/phanpy/dist/assets/locales/tr-TR-CfheRbCu.js +0 -1
  332. package/phanpy/dist/assets/locales/uk-UA-C5E3dFBf.js +0 -1
  333. package/phanpy/dist/assets/locales/zh-CN-BHJi7_et.js +0 -1
  334. package/phanpy/dist/assets/locales/zh-TW-CfheRbCu.js +0 -1
  335. package/phanpy/dist/assets/main-BdqNbG-a.js +0 -26
  336. package/phanpy/dist/assets/main-BdqNbG-a.js.map +0 -1
  337. package/phanpy/dist/assets/mock-home-DGhR__He.js +0 -2
  338. package/phanpy/dist/assets/mock-home-DGhR__He.js.map +0 -1
  339. package/phanpy/dist/assets/multi-column-ROck0NVt.jpg +0 -0
  340. package/phanpy/dist/assets/multi-hashtag-timeline-DH5INVHi.jpg +0 -0
  341. package/phanpy/dist/assets/nested-comments-thread-hv59kZLc.jpg +0 -0
  342. package/phanpy/dist/assets/polyfill-force-H8sUkLGp.js +0 -2
  343. package/phanpy/dist/assets/polyfill-force-H8sUkLGp.js.map +0 -1
  344. package/phanpy/dist/assets/pwa-viewport-ogKbF51K.js +0 -20
  345. package/phanpy/dist/assets/pwa-viewport-ogKbF51K.js.map +0 -1
  346. package/phanpy/dist/assets/style-B1iLPTPR.css +0 -2
  347. package/phanpy/dist/assets/temml-DuyIQMei.js +0 -97
  348. package/phanpy/dist/assets/temml-DuyIQMei.js.map +0 -1
  349. package/phanpy/dist/assets/tinyld.light.node-DtbBhLYC.js +0 -2
  350. package/phanpy/dist/assets/tinyld.light.node-DtbBhLYC.js.map +0 -1
  351. package/phanpy/dist/assets/year-in-posts-B2lywqdx.png +0 -0
  352. package/phanpy/dist/assets/year-in-posts-DJv_star.js +0 -3
  353. package/phanpy/dist/assets/year-in-posts-DJv_star.js.map +0 -1
  354. package/phanpy/dist/compose/index.html +0 -20
  355. package/phanpy/dist/favicon.ico +0 -0
  356. package/phanpy/dist/index.html +0 -73
  357. package/phanpy/dist/logo-192.png +0 -0
  358. package/phanpy/dist/logo-512.png +0 -0
  359. package/phanpy/dist/logo-badge-72.png +0 -0
  360. package/phanpy/dist/logo-maskable-512.png +0 -0
  361. package/phanpy/dist/logo-monochrome-512.png +0 -0
  362. package/phanpy/dist/logo-monochrome-maskable-512.png +0 -0
  363. package/phanpy/dist/manifest.webmanifest +0 -1
  364. package/phanpy/dist/og-image-2.jpg +0 -0
  365. package/phanpy/dist/og-image.png +0 -0
  366. package/phanpy/dist/robots.txt +0 -2
  367. package/phanpy/dist/sw.js +0 -2
  368. package/phanpy/dist/sw.js.map +0 -1
  369. package/phanpy/dist/version.json +0 -1
  370. package/run-agent.mjs +0 -899
  371. package/vendor/gate.cjs +0 -138
  372. package/vendor/idp-grant.cjs +0 -394
  373. package/vendor/jfetch.cjs +0 -16
  374. package/web/admin/actors.js +0 -147
  375. package/web/admin/bar.css +0 -126
  376. package/web/admin/bar.js +0 -99
  377. package/web/admin/client/client.js +0 -66
  378. package/web/admin/client/index.html +0 -69
  379. package/web/admin/client-pick.js +0 -79
  380. package/web/admin/common.js +0 -23
  381. package/web/admin/connections.js +0 -112
  382. package/web/admin/gateway.js +0 -133
  383. package/web/admin/group.js +0 -258
  384. package/web/admin/index.html +0 -612
  385. package/web/admin/notices-bar.js +0 -90
  386. package/web/admin/oauth-signin.mjs +0 -37
  387. package/web/admin/record.js +0 -393
  388. package/web/admin/setup/index.html +0 -257
  389. package/web/admin/setup/setup.js +0 -339
  390. package/web/admin/tokens.css +0 -68
  391. package/web/admin/upkeep.js +0 -177
  392. package/web/admin/window.css +0 -55
  393. package/web/admin/window.js +0 -136
  394. package/web/app/README.md +0 -81
  395. package/web/app/admin-facade.mjs +0 -588
  396. package/web/app/agent.mjs +0 -672
  397. package/web/app/atproto-browser.mjs +0 -86
  398. package/web/app/authorize_interaction.html +0 -56
  399. package/web/app/authorize_interaction.js +0 -69
  400. package/web/app/boot.mjs +0 -616
  401. package/web/app/deliver-relay.mjs +0 -129
  402. package/web/app/dist/boot.js +0 -34562
  403. package/web/app/dist/boot.js.map +0 -7
  404. package/web/app/dist/sw.js +0 -73456
  405. package/web/app/dist/sw.js.map +0 -7
  406. package/web/app/fediacct-browser.mjs +0 -146
  407. package/web/app/gateway-move.mjs +0 -60
  408. package/web/app/idb-kv.mjs +0 -35
  409. package/web/app/index.html +0 -258
  410. package/web/app/keys-browser.mjs +0 -71
  411. package/web/app/keystore.mjs +0 -82
  412. package/web/app/oidc-session.mjs +0 -6
  413. package/web/app/pod-auth.mjs +0 -248
  414. package/web/app/pod-remote.mjs +0 -84
  415. package/web/app/shims/_globals.mjs +0 -2
  416. package/web/app/shims/fedify-sig.mjs +0 -77
  417. package/web/app/shims/node-crypto.mjs +0 -113
  418. package/web/app/shims/node-fs.mjs +0 -17
  419. package/web/app/shims/node-path.mjs +0 -19
  420. package/web/app/shims/node-url.mjs +0 -7
  421. package/web/app/shims/prelude.js +0 -58
  422. package/web/app/shims/safefetch.mjs +0 -58
  423. package/web/app/shims/shapes-text.mjs +0 -8
  424. package/web/app/shims/web-push.mjs +0 -14
  425. package/web/app/signup.mjs +0 -357
  426. package/web/app/sw-src.mjs +0 -220
  427. package/web/app/update.js +0 -70
  428. package/web/front/admin.html +0 -55
  429. package/web/front/admin.js +0 -89
  430. package/web/front/install.sh +0 -69
  431. package/web/front/new-account.html +0 -35
  432. package/web/front/new-account.js +0 -13
  433. package/web/front/notices.html +0 -69
  434. package/web/front/notices.js +0 -107
  435. package/web/front/run.html +0 -64
  436. package/web/front/run.js +0 -221
  437. package/web/front/solid-oidc-client.js +0 -6
  438. package/web/icons/forum.svg +0 -11
  439. package/web/icons/gateway.svg +0 -14
  440. package/web/icons/local.svg +0 -11
@@ -1,357 +0,0 @@
1
- // import.mjs — bring a non-FediPod account's exports in: the CSV files a
2
- // Mastodon-family server hands its leaver (follows, blocks, mutes, lists,
3
- // domain blocks; Misskey's exports are the same shapes). Parsing is here;
4
- // application runs in a paced worker owned by the agent, because most rows
5
- // cost network requests — a handle has to be resolved to its actor before
6
- // anything can be followed, blocked or muted — and a migration-sized list
7
- // applied at full speed is a flood aimed at every server it names.
8
- //
9
- // The state document is shared with stage() and clear(), which run from the
10
- // admin route while a row is in flight — so nothing here holds a copy of it
11
- // across a network await. Every mutation is read-modify-write against a fresh
12
- // read, and a row that finished travelling is committed only if its record is
13
- // still there and still pending.
14
-
15
- import crypto from 'node:crypto';
16
- import { lookupWebFinger, selfLink, confirmDelegation, followActor, unfollowActor } from '../core/social.mjs';
17
-
18
- export const IMPORT_STATE_DOC = 'import-state.json';
19
- export const IMPORT_KINDS = ['follow', 'block', 'mute', 'list', 'domain'];
20
-
21
- const TICK_MS = 400; // one network row per tick at most
22
- const FOLLOW_GAP_MS = 1000; // follows also deliver, so they go slower
23
- const PUBLISH_EVERY = 25; // follows between collection republishes
24
- const MAX_ROWS = 25_000; // staged-row cap: the doc is rewritten per row
25
-
26
- // A real CSV parse — quoted fields, doubled quotes, CRLF — because a list
27
- // title may contain a comma and a display name may contain anything.
28
- export function parseCsv(text) {
29
- const rows = [];
30
- let row = [], field = '', q = false;
31
- const endField = () => { row.push(field); field = ''; };
32
- const endRow = () => { endField(); if (row.some(f => f !== '')) rows.push(row); row = []; };
33
- for (let i = 0; i < text.length; i++) {
34
- const c = text[i];
35
- if (q) {
36
- if (c === '"') { if (text[i + 1] === '"') { field += '"'; i++; } else q = false; }
37
- else field += c;
38
- } else if (c === '"') q = true;
39
- else if (c === ',') endField();
40
- else if (c === '\n' || c === '\r') { if (c === '\r' && text[i + 1] === '\n') i++; endRow(); }
41
- else field += c;
42
- }
43
- endRow();
44
- return rows;
45
- }
46
-
47
- const HANDLE = /^[^@\s]+@[^@\s]+$/;
48
- const DOMAIN = /^[a-z0-9-]+(\.[a-z0-9-]+)+$/i;
49
- const cleanHandle = (s) => String(s || '').trim().replace(/^@/, '');
50
-
51
- // CSV text → normalized row values for one kind. Header and comment rows are
52
- // recognized only where they can exist — a LIST title is free text and may
53
- // legitimately start with '#' or say anything. Invalid rows come back counted,
54
- // with the first few quoted so a mis-picked file is obvious.
55
- export function normalizeImport(kind, text) {
56
- if (!IMPORT_KINDS.includes(kind)) throw new Error(`unknown import kind: ${kind}`);
57
- const rows = parseCsv(String(text || ''));
58
- const values = [];
59
- const invalid = [];
60
- for (const cols of rows) {
61
- const first = String(cols[0] || '').trim();
62
- if (!first) continue;
63
- if (kind !== 'list' && /^#/.test(first)) continue; // comment
64
- if (kind !== 'list' && kind !== 'domain' && /account address/i.test(first)) continue; // header
65
- if (kind === 'domain') {
66
- const d = first.toLowerCase();
67
- if (DOMAIN.test(d)) values.push(d); else invalid.push(first);
68
- } else if (kind === 'list') {
69
- const handle = cleanHandle(cols[1]);
70
- if (first && HANDLE.test(handle)) values.push({ value: handle, list: first });
71
- else invalid.push(cols.join(','));
72
- } else {
73
- const handle = cleanHandle(first);
74
- if (HANDLE.test(handle)) values.push(handle); else invalid.push(first);
75
- }
76
- }
77
- return { values, invalid };
78
- }
79
-
80
- // The paced applier. One instance per agent, armed only while the agent is
81
- // active — startActive/requestTakeover resume it, demote stops it — and each
82
- // tick re-checks the lease and the pod's backoff before touching anything.
83
- export class ImportWorker {
84
- constructor({ agent, log }) {
85
- this.agent = agent;
86
- this.log = log || (() => {});
87
- this.timer = null;
88
- this.busy = false;
89
- this.lastFollowAt = 0;
90
- }
91
-
92
- state() { return this.agent.store.read(IMPORT_STATE_DOC, null); }
93
-
94
- // Add parsed rows to the run. Rows already staged, already applied, or
95
- // already following are counted out rather than re-queued, so re-running
96
- // the same file is a no-op. The cap keeps the per-row rewrite bounded.
97
- stage(kind, values) {
98
- const agent = this.agent;
99
- const st = this.state() || { startedAt: new Date().toISOString(), rows: [] };
100
- delete st.completedAt; // a new batch reopens the run
101
- const key = (r) => [r.kind, r.list || '', r.value].join(' ');
102
- const have = new Set(st.rows.map(key));
103
- const following = new Set(agent.store.getContacts().following.map(f => f.handle).filter(Boolean));
104
- const domains = new Set(agent.store.getBlocklist().domains);
105
- let staged = 0, duplicate = 0, already = 0, refused = 0;
106
- for (const v of values) {
107
- const row = typeof v === 'string' ? { kind, value: v } : { kind, ...v };
108
- row.status = 'pending';
109
- if (have.has(key(row))) { duplicate++; continue; }
110
- if (kind === 'follow' && following.has(row.value)) { already++; continue; }
111
- if (kind === 'domain' && domains.has(row.value)) { already++; continue; }
112
- if (st.rows.length >= MAX_ROWS) { refused++; continue; }
113
- have.add(key(row));
114
- st.rows.push(row);
115
- staged++;
116
- }
117
- agent.store.write(IMPORT_STATE_DOC, st);
118
- if (staged) this.start();
119
- return { staged, duplicate, already, refused, total: st.rows.length };
120
- }
121
-
122
- progress() {
123
- const st = this.state();
124
- const prior = st?.doneCount || 0;
125
- if (!st?.rows?.length && !prior) return { rows: 0, pending: 0, done: 0, failed: 0, running: !!this.timer };
126
- const byKind = {};
127
- let pending = 0, done = prior, failed = 0;
128
- for (const r of st.rows) {
129
- const k = (byKind[r.kind] ||= { pending: 0, done: 0, failed: 0 });
130
- k[r.status]++;
131
- if (r.status === 'pending') pending++;
132
- else if (r.status === 'done') done++;
133
- else failed++;
134
- }
135
- return {
136
- rows: st.rows.length + prior, pending, done, failed, byKind,
137
- running: !!this.timer, startedAt: st.startedAt, completedAt: st.completedAt || null,
138
- failures: st.rows.filter(r => r.status === 'failed').slice(0, 100)
139
- .map(r => ({ kind: r.kind, value: r.value, ...(r.list ? { list: r.list } : {}), reason: r.reason })),
140
- };
141
- }
142
-
143
- clear() {
144
- this.stop();
145
- this.agent.store.write(IMPORT_STATE_DOC, { rows: [] });
146
- }
147
-
148
- // Called from startActive and after a takeover: pick a stranded run back
149
- // up. Any unclosed run arms the timer — rows still pending, or a final
150
- // publish that never landed.
151
- resume() {
152
- const st = this.state();
153
- if (!st?.rows?.length || st.completedAt) return;
154
- this.start();
155
- }
156
-
157
- start() {
158
- if (this.timer) return;
159
- this.timer = setInterval(() => this.tick().catch(e => this.log(`import tick: ${e.message}`)), TICK_MS);
160
- this.timer.unref?.(); // a browser's setInterval returns a number
161
- }
162
-
163
- stop() {
164
- clearInterval(this.timer);
165
- this.timer = null;
166
- }
167
-
168
- async tick() {
169
- if (this.busy) return;
170
- this.busy = true;
171
- try {
172
- const agent = this.agent;
173
- const st = this.state();
174
- if (!st?.rows) { this.stop(); return; }
175
- // A parked or moved actor runs no import: parking mass-unfollows and a
176
- // move hands the account on — the worker must not pull against either.
177
- const cfg = agent.store.getConfig?.() || {};
178
- if (cfg?.quiescedAt || cfg?.movedTo) { this.stop(); return; }
179
- const next = st.rows.find(r => r.status === 'pending');
180
- if (!next) {
181
- if (!st.completedAt) await this.finish();
182
- else this.stop();
183
- return;
184
- }
185
- if (agent.viewer) return; // demoted mid-run
186
- if (agent.lease && !agent.lease.stillHeld()) return; // another device is acting
187
- if (agent.remote?.pausedUntil > Date.now()) return; // the pod asked for quiet
188
- if (next.kind === 'follow' && Date.now() - this.lastFollowAt < FOLLOW_GAP_MS) return;
189
- if (next.kind === 'domain') {
190
- // Free rows — no resolution, no delivery — the whole batch lands in
191
- // one tick. No awaits between the read and the write, so no race.
192
- const b = agent.store.getBlocklist();
193
- const fresh = this.state();
194
- if (!fresh?.rows) return;
195
- for (const r of fresh.rows) {
196
- if (r.status !== 'pending' || r.kind !== 'domain') continue;
197
- if (!b.domains.includes(r.value)) b.domains.push(r.value);
198
- r.status = 'done';
199
- }
200
- agent.store.setBlocklist(b);
201
- agent.store.write(IMPORT_STATE_DOC, fresh);
202
- return;
203
- }
204
- // The network happens here, touching no state document; the verdict is
205
- // committed against a FRESH read afterwards, so rows staged or cleared
206
- // while this one travelled are never overwritten.
207
- const outcome = await this.applyRow(next);
208
- if (outcome) this.commitRow(next, outcome);
209
- if (outcome?.followed && (this.state()?.followsSincePublish || 0) >= PUBLISH_EVERY) {
210
- await agent.publisher.publishCollections({ following: true, pending: true });
211
- const after = this.state();
212
- if (after) { after.followsSincePublish = 0; agent.store.write(IMPORT_STATE_DOC, after); }
213
- }
214
- } finally { this.busy = false; }
215
- }
216
-
217
- // Record one travelled row's verdict. Fresh read; the row is matched by
218
- // content — if clear() dropped it or a duplicate check superseded it while
219
- // it was in flight, there is nothing to record and nothing is written.
220
- commitRow(row, outcome) {
221
- const st = this.state();
222
- const match = st?.rows?.find(r => r.status === 'pending' && r.kind === row.kind
223
- && r.value === row.value && (r.list || '') === (row.list || ''));
224
- if (!match) return;
225
- match.status = outcome.status;
226
- if (outcome.reason) match.reason = outcome.reason;
227
- if (outcome.followed) st.followsSincePublish = (st.followsSincePublish || 0) + 1;
228
- if (outcome.blocked) st.blocksApplied = true;
229
- this.agent.store.write(IMPORT_STATE_DOC, st);
230
- }
231
-
232
- // handle → { id, inbox } — the lean resolve: WebFinger, one actor fetch,
233
- // the delegation round-trip, and nothing else. A handle we already follow
234
- // answers from contacts without touching the network, and the blocklist is
235
- // honored the same way the interactive resolve honors it.
236
- async resolve(clean, { forBlock = false } = {}) {
237
- const agent = this.agent;
238
- const host = clean.slice(clean.lastIndexOf('@') + 1);
239
- if (agent.store.isBlocked(`https://${host}/`)) throw new Error('domain is blocked');
240
- const known = agent.store.getContacts().following.find(f => f.handle === clean && f.actor);
241
- if (known) return { id: known.actor, inbox: known.inbox };
242
- const jrd = await lookupWebFinger('acct:' + clean);
243
- const href = selfLink(jrd)?.href;
244
- if (!href) throw new Error('webfinger found no actor');
245
- const doc = await agent.intake.fetchAP(href);
246
- if (!doc?.id) throw new Error('actor document unusable');
247
- if (!forBlock && agent.store.isBlocked(doc.id)) throw new Error('actor is blocked');
248
- await confirmDelegation(clean.slice(clean.lastIndexOf('@') + 1), doc);
249
- return doc;
250
- }
251
-
252
- // One row's whole journey, returned as a verdict — never written here.
253
- // null means "no verdict": the pod's backoff interrupted it, and the row
254
- // stays pending for after the pause.
255
- async applyRow(row) {
256
- const agent = this.agent;
257
- const done = (extra = {}) => ({ status: 'done', ...extra });
258
- try {
259
- if (row.kind === 'follow') {
260
- this.lastFollowAt = Date.now();
261
- if (agent.store.getContacts().following.some(f => f.handle === row.value)) {
262
- return done({ reason: 'already following' });
263
- }
264
- const doc = await this.resolve(row.value);
265
- if (agent.store.getContacts().following.some(f => f.actor === doc.id)) {
266
- return done({ reason: 'already following' });
267
- }
268
- // followActor records the contact BEFORE delivering, which is what
269
- // lets the Accept that comes back seconds later find its record.
270
- await followActor(agent, doc.id, { publish: false, doc });
271
- const contacts = agent.store.getContacts();
272
- const rec = contacts.following.find(f => f.actor === doc.id);
273
- if (rec && !rec.handle) { rec.handle = row.value; agent.store.setContacts(contacts); }
274
- return done({ followed: true });
275
- }
276
- if (row.kind === 'block') {
277
- const host = row.value.slice(row.value.lastIndexOf('@') + 1);
278
- const b = agent.store.getBlocklist();
279
- if (b.domains.some(d => host === d || host.endsWith('.' + d))) {
280
- return done({ reason: 'domain already blocked' });
281
- }
282
- const doc = await this.resolve(row.value, { forBlock: true });
283
- const b2 = agent.store.getBlocklist();
284
- if (b2.actors.includes(doc.id)) return done({ reason: 'already blocked' });
285
- b2.actors.push(doc.id);
286
- agent.store.setBlocklist(b2);
287
- if (agent.store.getContacts().following.some(f => f.actor === doc.id)) {
288
- await unfollowActor(agent, doc.id).catch(() => {});
289
- }
290
- return done({ blocked: true });
291
- }
292
- if (row.kind === 'mute') {
293
- const doc = await this.resolve(row.value);
294
- const m = agent.store.getMuted();
295
- if (m.actors.includes(doc.id)) return done({ reason: 'already muted' });
296
- m.actors.push(doc.id);
297
- agent.store.setMuted(m);
298
- return done();
299
- }
300
- if (row.kind === 'list') {
301
- const doc = await this.resolve(row.value);
302
- const lists = agent.store.getLists();
303
- let l = lists.find(x => x.title === row.list);
304
- if (!l) {
305
- l = { id: crypto.randomBytes(8).toString('hex'), title: row.list, repliesPolicy: 'list', members: [] };
306
- lists.push(l);
307
- }
308
- if ((l.members || []).includes(doc.id)) return done({ reason: 'already on the list' });
309
- l.members = [...(l.members || []), doc.id];
310
- agent.store.setLists(lists);
311
- return done();
312
- }
313
- return { status: 'failed', reason: `unknown kind ${row.kind}` };
314
- } catch (e) {
315
- if (agent.remote?.pausedUntil > Date.now()) return null;
316
- return { status: 'failed', reason: e.message };
317
- }
318
- }
319
-
320
- // Everything applied: the batched republishes, once, then the record closes
321
- // — done rows pruned to a count so a long run's record stays small, failed
322
- // rows kept until --clear so they can be read. A publish the pod refuses
323
- // leaves the run open; a restart or the next batch retries it. A batch that
324
- // landed while the publishes were in flight keeps the run open too.
325
- async finish() {
326
- const agent = this.agent;
327
- let st = this.state();
328
- if (!st?.rows) { this.stop(); return; }
329
- if (st.rows.some(r => r.status === 'pending')) return;
330
- try {
331
- if (st.followsSincePublish) {
332
- await agent.publisher.publishCollections({ following: true, pending: true });
333
- const s = this.state();
334
- if (s) { s.followsSincePublish = 0; agent.store.write(IMPORT_STATE_DOC, s); }
335
- }
336
- if (this.state()?.blocksApplied) {
337
- await agent.publisher.publishCollections({ blocked: true });
338
- const s = this.state();
339
- if (s) { s.blocksApplied = false; agent.store.write(IMPORT_STATE_DOC, s); }
340
- }
341
- } catch (e) {
342
- this.log(`import: final publish failed: ${e.message} — retried on restart or the next batch`);
343
- this.stop();
344
- return;
345
- }
346
- st = this.state();
347
- if (!st?.rows || st.rows.some(r => r.status === 'pending')) return;
348
- const doneNow = st.rows.filter(r => r.status === 'done').length;
349
- const failed = st.rows.filter(r => r.status === 'failed').length;
350
- st.doneCount = (st.doneCount || 0) + doneNow;
351
- st.rows = st.rows.filter(r => r.status !== 'done');
352
- st.completedAt = new Date().toISOString();
353
- agent.store.write(IMPORT_STATE_DOC, st);
354
- this.log(`import finished: ${st.doneCount} applied, ${failed} failed`);
355
- this.stop();
356
- }
357
- }
@@ -1,212 +0,0 @@
1
- // tagfeed.mjs — topical firehose for a single-actor instance: polls public
2
- // no-auth hashtag timelines on a configured instance and mirrors NEW notes
3
- // into the statuses index as kind 'tag'. View cache only — tag content is
4
- // NOT written to the pod; the pod holds followed/own content.
5
- //
6
- // Config in tagfeed.json: { instance, tags: [...], intervalMin }. Every note
7
- // is still verified by dereference at its origin before it is mirrored.
8
-
9
- import { isContentType, authorOf } from '../core/intake/index.mjs';
10
-
11
- // A public timeline page from somebody else's instance. Generous for 20 posts,
12
- // and a bound where there was none.
13
- const MAX_TIMELINE_BYTES = 2 * 1024 * 1024;
14
-
15
- // Two tags from each of six subjects, so a new account reads something on the
16
- // first morning without having followed anybody. Deliberately even: a busy
17
- // subject and a quiet one get the same number of places, and the sweep below
18
- // makes sure the busy ones cannot take the quiet ones' turns as well.
19
- const DEFAULTS = {
20
- instance: 'https://mastodon.social',
21
- tags: [
22
- 'fediverse', 'activitypub', // the network this account is on
23
- 'opensource', 'foss', // how the software gets made
24
- 'solidproject', 'linkeddata', // where the data lives
25
- 'privacy', 'digitalrights', // what is at stake
26
- 'indieweb', 'smallweb', // publishing for yourself
27
- 'commons', 'platformcooperativism', // owning it together
28
- ],
29
- intervalMin: 15,
30
- };
31
- const PER_TAG = 20; // statuses requested per tag per sweep
32
- const MAX_NEW_PER_SWEEP = 8; // dereference budget per sweep — stay light
33
- // …and no one tag may take more than this much of it. Without a per-tag share
34
- // the budget was spent in list order: the first two or three busy tags used it
35
- // all and everything below them never appeared once, however long you waited.
36
- const MAX_NEW_PER_TAG = 2;
37
- // Only this many tags are polled in any one sweep, taken in turn. A sweep holds
38
- // its writes until it finishes — so on the browser build, where the agent lives
39
- // in a service worker the browser is free to kill whenever it likes, a sweep
40
- // that runs long enough to be killed contributes NOTHING. Twelve tags was long
41
- // enough: a new account could open the app repeatedly and still see an empty
42
- // timeline. Four finishes. The list is still covered, three sweeps at a time,
43
- // and a sweep now costs the gateway less than the three-tag one it replaced.
44
- const TAGS_PER_SWEEP = 4;
45
- const MAX_TAG_ENTRIES = 200; // oldest tag entries pruned beyond this
46
- // The instance here is a stranger's server we poll on our own schedule, and it
47
- // has no other way to tell us to stop.
48
- const BACKOFF_MIN_MS = 15 * 60_000;
49
- const BACKOFF_MAX_MS = 6 * 60 * 60_000;
50
- const instanceRefusal = (status) => (status ? `the instance answered ${status}` : 'the instance did not answer');
51
-
52
- export class TagFeed {
53
- constructor({ store, intake, log = console.log, fetcher = globalThis.fetch }) {
54
- Object.assign(this, { store, intake, log, fetcher });
55
- this.lastSweep = null;
56
- this.lastAdded = 0;
57
- // Where this sweep starts in the tag list. It moves on by one each time, so
58
- // when the budget does run out it is a different tag that goes without.
59
- this.cursor = 0;
60
- }
61
-
62
- config() { return { ...DEFAULTS, ...this.store.read('tagfeed.json', {}) }; }
63
-
64
- setConfig(patch) {
65
- const clean = {};
66
- if (patch.instance) clean.instance = String(patch.instance).replace(/\/+$/, '');
67
- if (Array.isArray(patch.tags)) clean.tags = patch.tags.map(t => String(t).replace(/^#/, '').trim()).filter(Boolean);
68
- if (patch.intervalMin) clean.intervalMin = Math.max(5, Number(patch.intervalMin) || DEFAULTS.intervalMin);
69
- this.store.write('tagfeed.json', { ...this.config(), ...clean });
70
- this.stop();
71
- this.start();
72
- return this.config();
73
- }
74
-
75
- start() {
76
- this.stopped = false; // restartable, the same way Intake.start is
77
- this.sweep().catch(e => this.log(`tagfeed: ${e.message}`));
78
- // Jittered and self-scheduling: every agent polling the same instance on
79
- // the same 15-minute boundary is a beat nobody asked for.
80
- const tick = () => {
81
- this.timer = setTimeout(() => {
82
- this.sweep()
83
- .catch(e => this.log(`tagfeed: ${e.message}`))
84
- .finally(() => { if (!this.stopped) tick(); });
85
- }, Math.round(this.config().intervalMin * 60_000 * (0.85 + Math.random() * 0.3)));
86
- this.timer.unref?.();
87
- };
88
- tick();
89
- }
90
-
91
- // The flag is what makes this stick. Clearing the timer only cancels a sweep
92
- // that has not started: one already in flight re-arms itself in `finally`,
93
- // and `this.stopped` was read there but never written — so a stop landing
94
- // mid-sweep leaked that chain for the life of the process. Intake has had the
95
- // flag all along; this is the same shape.
96
- stop() { this.stopped = true; clearTimeout(this.timer); }
97
-
98
- // Jittered exponential, capped, and cleared by an instance that answers.
99
- // The next sweep is skipped rather than the timer stretched, so the tag
100
- // config keeps meaning what it says once the far end is well again.
101
- _backOff(status, retryAfter) {
102
- this.failures = (this.failures || 0) + 1;
103
- const ladder = Math.min(BACKOFF_MIN_MS * 2 ** (this.failures - 1), BACKOFF_MAX_MS);
104
- const wait = retryAfter || Math.round(ladder * (0.85 + Math.random() * 0.3));
105
- this.quietUntil = Date.now() + wait;
106
- this.log(`tagfeed: ${instanceRefusal(status)} — not asking again for ${Math.round(wait / 60_000)} min`);
107
- }
108
-
109
- async sweep() {
110
- const { instance, tags } = this.config();
111
- if (!tags.length) return;
112
- if (this.quietUntil && Date.now() < this.quietUntil) {
113
- this.log(`tagfeed: still backing off for ${Math.round((this.quietUntil - Date.now()) / 60_000)} min`);
114
- return;
115
- }
116
- this.lastSweep = new Date().toISOString();
117
- const known = new Set(this.store.getStatuses().map(s => s.noteId));
118
- let budget = MAX_NEW_PER_SWEEP;
119
- let added = 0;
120
- // A different handful every time, taken in turn, so no tag is permanently
121
- // first and none is permanently last.
122
- const start = this.cursor % tags.length;
123
- this.cursor = (start + TAGS_PER_SWEEP) % tags.length;
124
- const order = [...tags, ...tags].slice(start, start + Math.min(TAGS_PER_SWEEP, tags.length));
125
- // The store's 300ms debounce cannot coalesce a sweep that awaits between
126
- // items — every note costs a fetch to somebody else's server, which outlives
127
- // the timer — so statuses.json and actors.json, the two largest documents,
128
- // were serialized and PUT whole once per new note. On by default, every 15
129
- // minutes, and on the browser build every one of those is a pod write.
130
- // acctfeed has done it this way since 2026-08-03; this is the same fix.
131
- this.store.hold?.();
132
- try {
133
- for (const tag of order) {
134
- let perTag = MAX_NEW_PER_TAG;
135
- let list;
136
- try {
137
- const { safeFetch, retryAfterMs, readCapped } = await import('../shared/safefetch.mjs');
138
- const url = `${instance}/api/v1/timelines/tag/${encodeURIComponent(tag)}?limit=${PER_TAG}`;
139
- const res = this.fetcher === globalThis.fetch
140
- ? await safeFetch(url, { headers: { accept: 'application/json' } })
141
- : await this.fetcher(url, { headers: { accept: 'application/json' } });
142
- if (res.status >= 400) {
143
- // This is somebody else's server, polled on OUR schedule, and it had
144
- // no way to ask us to stop: a 429 or a 503 logged a line and the next
145
- // sweep arrived on the same cadence regardless. One refusal now ends
146
- // the sweep — the remaining tags are on the same instance, so there
147
- // is nothing to be gained by asking them too.
148
- this._backOff(res.status, retryAfterMs(res));
149
- return;
150
- }
151
- // Somebody else's server, polled on our schedule: bounded, because
152
- // res.json() would buffer a timeline of whatever size it sent.
153
- list = JSON.parse(await readCapped(res, MAX_TIMELINE_BYTES));
154
- } catch (e) {
155
- // A network failure is the same signal, minus the courtesy.
156
- this.log(`tagfeed #${tag}: ${e.message}`);
157
- this._backOff(0, null);
158
- return;
159
- }
160
- this.failures = 0; // it answered; the ladder resets
161
- for (const st of Array.isArray(list) ? list : []) {
162
- const noteId = st?.uri;
163
- if (!noteId || known.has(noteId) || this.store.isBlocked(noteId)) continue;
164
- if (perTag <= 0 || budget <= 0) break;
165
- perTag--; budget--;
166
- const note = await this.intake.fetchAP(noteId).catch(() => null);
167
- if (!note || note.id !== noteId || !isContentType(note.type)) continue;
168
- // The author is only known once the note is dereferenced, and this
169
- // path never went through ingestNote, where that check lives. So a
170
- // blocked account still reached the timeline by posting under a tag —
171
- // the one route round a block the owner had explicitly set.
172
- // The author is only known once the note is dereferenced, and this path
173
- // never went through ingestNote, where that check lives. Two things
174
- // followed: a blocked account reached the timeline by posting under a
175
- // tag — the one route round a block the owner had explicitly set — and
176
- // a note could name an author its own origin does not vouch for, which
177
- // is the same forgery ingestNote refuses. No `delivered` fallback here:
178
- // a tag-feed note has no envelope, so it names its author or it is not
179
- // one we can attribute.
180
- const author = authorOf(note);
181
- if (!author || this.store.isBlocked(author)) continue;
182
- if (!this.store.getActors()[author]) {
183
- await this.intake.fetchAP(author).catch(() => {}); // warm name+avatar
184
- }
185
- const { attachmentsOf, titledContent } = await import('../core/wire.mjs');
186
- const attachments = attachmentsOf(note);
187
- this.store.addStatus({
188
- noteId, actor: author, content: titledContent(note),
189
- published: note.published, inReplyTo: note.inReplyTo, kind: 'tag', tag,
190
- ...(attachments.length ? { attachments } : {}),
191
- });
192
- known.add(noteId);
193
- added++;
194
- }
195
- // Nothing left to take, so stop asking. Every remaining tag would be a
196
- // request whose answer is thrown away — and in the browser build every
197
- // one of those goes out through the gateway.
198
- if (budget <= 0) break;
199
- }
200
- const all = this.store.getStatuses();
201
- const tagged = all.filter(s => s.kind === 'tag');
202
- if (tagged.length > MAX_TAG_ENTRIES) {
203
- const drop = new Set(tagged.slice(MAX_TAG_ENTRIES).map(s => s.noteId)); // arrival order: tail = oldest
204
- this.store.write('statuses.json', all.filter(s => !drop.has(s.noteId)));
205
- }
206
- } finally {
207
- this.store.release?.();
208
- }
209
- this.lastAdded = added;
210
- if (added) this.log(`tagfeed: +${added} from #${order.join(' #')}`);
211
- }
212
- }
@@ -1,114 +0,0 @@
1
- // vault.mjs — where an identity keeps the credentials for accounts it holds on
2
- // OTHER servers: a full-access token on a Mastodon-API server, a Bluesky app
3
- // password. Not its own key, and nothing published.
4
- //
5
- // Two places, and which one is right depends on where the identity runs. On a
6
- // laptop the pod is somebody else's server, so a token goes on this machine
7
- // and nowhere near the pod. Inside a pod server the two are the same computer,
8
- // and the pod is the thing that travels — a token left on the host is a
9
- // connection the owner loses the day they take their pod elsewhere.
10
- //
11
- // The question is where the agent runs, not where its signing key is: sharing
12
- // a key through the pod is a laptop's own choice and says nothing about a
13
- // token for somebody else's server.
14
- //
15
- // `read`, `write` and `names` are synchronous because every caller here is.
16
- // The pod's writes land through the store's own retrying queue; `commit`
17
- // waits for them, and callers that have just taken a credential from a remote
18
- // server use it before they answer.
19
- import fs from 'node:fs';
20
- import path from 'node:path';
21
- import { writeJsonAtomic } from '../device/home.mjs';
22
-
23
- /** What every connection document in pod state is named from. */
24
- export const CONNECTION_PREFIX = 'conn-';
25
-
26
- /** One directory, one document per name, 0600 and atomic — the laptop. */
27
- export function fileVault(dir) {
28
- const file = (name) => path.join(dir, `${name}.json`);
29
- return {
30
- kind: 'file',
31
- where: dir,
32
- names() {
33
- try { return fs.readdirSync(dir).filter(n => n.endsWith('.json')).map(n => n.slice(0, -5)); }
34
- catch { return []; }
35
- },
36
- read(name) {
37
- try { return JSON.parse(fs.readFileSync(file(name), 'utf8')); } catch { return null; }
38
- },
39
- write(name, rec) {
40
- fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
41
- writeJsonAtomic(file(name), rec);
42
- },
43
- async remove(name) {
44
- try { fs.rmSync(file(name)); return true; } catch { return false; }
45
- },
46
- async commit() { return true; },
47
- };
48
- }
49
-
50
- /** Documents in the identity's own pod state, one per name under `prefix`. */
51
- export function podVault(store, prefix) {
52
- const doc = (name) => `${prefix}${name}.json`;
53
- return {
54
- kind: 'pod',
55
- where: `pod state (${prefix}*)`,
56
- names() {
57
- return store.names()
58
- .filter(n => n.startsWith(prefix) && n.endsWith('.json'))
59
- .map(n => n.slice(prefix.length, -5));
60
- },
61
- read(name) { return store.read(doc(name), null); },
62
- write(name, rec) { store.write(doc(name), rec); },
63
- async remove(name) { return store.remove(doc(name)); },
64
- async commit() { return store.commit(); },
65
- };
66
- }
67
-
68
- /**
69
- * The vaults one agent's connections use.
70
- *
71
- * `embedded` is the whole question: an agent running inside the server that
72
- * holds its pod is on the pod's own host, so a credential left on the machine
73
- * is one its owner loses the day they take the pod elsewhere. An agent on a
74
- * laptop is not, and its tokens stay where they are — including when the
75
- * owner has chosen to share their SIGNING key through the pod, which says
76
- * nothing about a token for somebody else's server.
77
- */
78
- export function vaultsFor({ embedded, home, store }) {
79
- if (!embedded) {
80
- return { bluesky: null, fedi: null, apps: null }; // the classes' own defaults, beside the credential
81
- }
82
- return {
83
- bluesky: podVault(store, CONNECTION_PREFIX),
84
- fedi: podVault(store, `${CONNECTION_PREFIX}fedi-`),
85
- apps: podVault(store, `${CONNECTION_PREFIX}fediapp-`),
86
- home,
87
- };
88
- }
89
-
90
- /**
91
- * Move whatever a file vault holds into a pod vault, and only then take the
92
- * host's copies away. A token here is full access to an account on a server
93
- * this project does not run: losing one in the move is a connection its owner
94
- * has to make again, and cannot be told about.
95
- */
96
- export async function moveVault(from, to, log = () => {}) {
97
- const names = from.names();
98
- if (!names.length) return 0;
99
- let moved = 0;
100
- for (const name of names) {
101
- const rec = from.read(name);
102
- if (!rec) continue;
103
- if (to.read(name)) { await from.remove(name); continue; }
104
- to.write(name, rec);
105
- moved += 1;
106
- }
107
- if (!await to.commit()) {
108
- log('could not write the connected-account credentials to the pod — they stay on this host for now');
109
- return 0;
110
- }
111
- for (const name of names) await from.remove(name);
112
- if (moved) log(`${moved} connected-account credential(s) moved into the pod`);
113
- return moved;
114
- }