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