fedipod-server 0.28.1 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (438) hide show
  1. package/README.md +38 -34
  2. package/config/server.json +22 -0
  3. package/dist/claims.js +3 -2
  4. package/dist/directory.d.ts +2 -0
  5. package/dist/door.d.ts +4 -0
  6. package/dist/door.js +31 -0
  7. package/dist/fedipod.d.ts +16 -0
  8. package/dist/fedipod.js +13 -0
  9. package/dist/front.d.ts +3 -0
  10. package/dist/front.js +94 -0
  11. package/dist/handler.d.ts +39 -101
  12. package/dist/handler.js +86 -617
  13. package/dist/handler.jsonld +6 -30
  14. package/dist/identities.d.ts +62 -0
  15. package/dist/identities.js +236 -0
  16. package/dist/mounts.d.ts +43 -0
  17. package/dist/mounts.js +123 -0
  18. package/dist/opt-in.d.ts +46 -0
  19. package/dist/opt-in.js +176 -0
  20. package/dist/store-pod.js +37 -0
  21. package/package.json +10 -18
  22. package/lib/client/c2s.mjs +0 -582
  23. package/lib/client/localapi.mjs +0 -87
  24. package/lib/client/masto/accounts.mjs +0 -309
  25. package/lib/client/masto/body.mjs +0 -156
  26. package/lib/client/masto/index.mjs +0 -206
  27. package/lib/client/masto/instance.mjs +0 -112
  28. package/lib/client/masto/media.mjs +0 -79
  29. package/lib/client/masto/oauth.mjs +0 -656
  30. package/lib/client/masto/render.mjs +0 -518
  31. package/lib/client/masto/statuses.mjs +0 -372
  32. package/lib/client/masto/timelines.mjs +0 -438
  33. package/lib/client/oidc-auth.mjs +0 -62
  34. package/lib/client/streaming.mjs +0 -154
  35. package/lib/client/webpush.mjs +0 -98
  36. package/lib/connections/acctfeed.mjs +0 -257
  37. package/lib/connections/atproto.mjs +0 -309
  38. package/lib/connections/bskyfeed.mjs +0 -243
  39. package/lib/connections/bskygroup.mjs +0 -162
  40. package/lib/connections/fediacct.mjs +0 -253
  41. package/lib/connections/import.mjs +0 -357
  42. package/lib/connections/tagfeed.mjs +0 -212
  43. package/lib/connections/vault.mjs +0 -114
  44. package/lib/core/as2.mjs +0 -206
  45. package/lib/core/contexts/activitystreams.json +0 -379
  46. package/lib/core/contexts/anno.json +0 -126
  47. package/lib/core/contexts/did-v1.json +0 -57
  48. package/lib/core/contexts/fep-5711.json +0 -36
  49. package/lib/core/contexts/gotosocial.json +0 -86
  50. package/lib/core/contexts/identity-v1.json +0 -152
  51. package/lib/core/contexts/index.mjs +0 -63
  52. package/lib/core/contexts/join-lemmy.json +0 -33
  53. package/lib/core/contexts/joinmastodon.json +0 -28
  54. package/lib/core/contexts/litepub-0.1.json +0 -60
  55. package/lib/core/contexts/map.json +0 -19
  56. package/lib/core/contexts/miscellany.json +0 -19
  57. package/lib/core/contexts/quotes.json +0 -18
  58. package/lib/core/contexts/schemaorg.json +0 -8845
  59. package/lib/core/contexts/security-data-integrity-v1.json +0 -78
  60. package/lib/core/contexts/security-data-integrity-v2.json +0 -81
  61. package/lib/core/contexts/security-multikey-v1.json +0 -35
  62. package/lib/core/contexts/security-v1.json +0 -74
  63. package/lib/core/contexts/webfinger.json +0 -10
  64. package/lib/core/deliver.mjs +0 -371
  65. package/lib/core/graphview.mjs +0 -327
  66. package/lib/core/intake/activities.mjs +0 -633
  67. package/lib/core/intake/activity.mjs +0 -318
  68. package/lib/core/intake/channel.mjs +0 -144
  69. package/lib/core/intake/group.mjs +0 -256
  70. package/lib/core/intake/index.mjs +0 -701
  71. package/lib/core/intake/notes.mjs +0 -360
  72. package/lib/core/intake/verify.mjs +0 -142
  73. package/lib/core/keys.mjs +0 -153
  74. package/lib/core/lease.mjs +0 -206
  75. package/lib/core/polls.mjs +0 -105
  76. package/lib/core/profile-page.mjs +0 -104
  77. package/lib/core/proof.mjs +0 -79
  78. package/lib/core/publisher/collections.mjs +0 -237
  79. package/lib/core/publisher/index.mjs +0 -500
  80. package/lib/core/publisher/notes.mjs +0 -453
  81. package/lib/core/publisher/questions.mjs +0 -239
  82. package/lib/core/publisher/restore.mjs +0 -224
  83. package/lib/core/shapes/activitystreams.ttl +0 -157
  84. package/lib/core/shapes/index.mjs +0 -132
  85. package/lib/core/shapes/shapes-text.mjs +0 -13
  86. package/lib/core/social.mjs +0 -531
  87. package/lib/core/storage.mjs +0 -176
  88. package/lib/core/store.mjs +0 -568
  89. package/lib/core/wire-quotes.mjs +0 -71
  90. package/lib/core/wire.mjs +0 -889
  91. package/lib/device/account.mjs +0 -87
  92. package/lib/device/admin/index.mjs +0 -13
  93. package/lib/device/admin/origins.mjs +0 -35
  94. package/lib/device/admin/routes/connections.mjs +0 -144
  95. package/lib/device/admin/routes/gateway.mjs +0 -199
  96. package/lib/device/admin/routes/lifecycle.mjs +0 -200
  97. package/lib/device/admin/routes/owner.mjs +0 -322
  98. package/lib/device/admin/routes/setup.mjs +0 -412
  99. package/lib/device/admin/routes/social.mjs +0 -188
  100. package/lib/device/admin/server.mjs +0 -95
  101. package/lib/device/admin/static.mjs +0 -247
  102. package/lib/device/admin/surface.mjs +0 -315
  103. package/lib/device/certs.mjs +0 -281
  104. package/lib/device/cli/commands/account.mjs +0 -586
  105. package/lib/device/cli/commands/run.mjs +0 -278
  106. package/lib/device/cli/commands/service.mjs +0 -221
  107. package/lib/device/cli/commands/setup.mjs +0 -447
  108. package/lib/device/cli/commands/state.mjs +0 -561
  109. package/lib/device/cli/context.mjs +0 -288
  110. package/lib/device/export-collections.mjs +0 -161
  111. package/lib/device/gateway-move.mjs +0 -58
  112. package/lib/device/home.mjs +0 -161
  113. package/lib/device/migrate.mjs +0 -115
  114. package/lib/device/ports.mjs +0 -27
  115. package/lib/device/remote.mjs +0 -54
  116. package/lib/device/setup.mjs +0 -459
  117. package/lib/device/update.mjs +0 -96
  118. package/lib/gateway/caches.mjs +0 -36
  119. package/lib/gateway/directory.mjs +0 -90
  120. package/lib/gateway/front-core.mjs +0 -970
  121. package/lib/gateway/gateway-core.mjs +0 -165
  122. package/lib/gateway/headers.mjs +0 -49
  123. package/lib/gateway/httpsig.mjs +0 -159
  124. package/lib/gateway/notices.mjs +0 -84
  125. package/lib/gateway/quiet.mjs +0 -189
  126. package/lib/pod/README.md +0 -61
  127. package/lib/pod/actor.mjs +0 -96
  128. package/lib/pod/collection.mjs +0 -77
  129. package/lib/pod/containers.mjs +0 -140
  130. package/lib/pod/discovery.mjs +0 -46
  131. package/lib/pod/featured.mjs +0 -18
  132. package/lib/pod/followers.mjs +0 -24
  133. package/lib/pod/following.mjs +0 -10
  134. package/lib/pod/http.mjs +0 -74
  135. package/lib/pod/inbox.mjs +0 -166
  136. package/lib/pod/links.mjs +0 -35
  137. package/lib/pod/media.mjs +0 -27
  138. package/lib/pod/notes.mjs +0 -80
  139. package/lib/pod/notifications.mjs +0 -89
  140. package/lib/pod/outbox.mjs +0 -18
  141. package/lib/pod/package.json +0 -19
  142. package/lib/pod/policy.mjs +0 -58
  143. package/lib/pod/private.mjs +0 -19
  144. package/lib/pod/root.mjs +0 -104
  145. package/lib/pod/state.mjs +0 -40
  146. package/lib/pod/transport.mjs +0 -525
  147. package/lib/pod/urls.mjs +0 -85
  148. package/lib/server/embed.mjs +0 -423
  149. package/lib/session/README.md +0 -137
  150. package/lib/session/demo.html +0 -94
  151. package/lib/session/fedi-account.mjs +0 -408
  152. package/lib/session/fedi-login.mjs +0 -123
  153. package/lib/session/oidc-session.mjs +0 -233
  154. package/lib/session/package.json +0 -49
  155. package/lib/shared/guard.mjs +0 -213
  156. package/lib/shared/links.mjs +0 -9
  157. package/lib/shared/safefetch.mjs +0 -235
  158. package/lib/shared/ua.mjs +0 -29
  159. package/phanpy/dist/404.html +0 -40
  160. package/phanpy/dist/_headers +0 -2
  161. package/phanpy/dist/apple-touch-icon.png +0 -0
  162. package/phanpy/dist/assets/ICONS-DIPJeU0C.js +0 -2
  163. package/phanpy/dist/assets/ICONS-DIPJeU0C.js.map +0 -1
  164. package/phanpy/dist/assets/ScheduledAtField-D-Abfo4J.js +0 -183
  165. package/phanpy/dist/assets/ScheduledAtField-D-Abfo4J.js.map +0 -1
  166. package/phanpy/dist/assets/Temml-CCjoacWt.woff2 +0 -0
  167. package/phanpy/dist/assets/__vite-browser-external-C7iut881.js +0 -2
  168. package/phanpy/dist/assets/__vite-browser-external-C7iut881.js.map +0 -1
  169. package/phanpy/dist/assets/boosts-carousel-BiOaNdMT.jpg +0 -0
  170. package/phanpy/dist/assets/catch-up-O5q5C75Z.png +0 -0
  171. package/phanpy/dist/assets/chunk-aKtaBQYM.js +0 -1
  172. package/phanpy/dist/assets/compose-CFEx7wb4.js +0 -2
  173. package/phanpy/dist/assets/compose-CFEx7wb4.js.map +0 -1
  174. package/phanpy/dist/assets/compose-ZJN5xSb4.js +0 -35
  175. package/phanpy/dist/assets/compose-ZJN5xSb4.js.map +0 -1
  176. package/phanpy/dist/assets/debug.module-YFQgwnIr.js +0 -52
  177. package/phanpy/dist/assets/debug.module-YFQgwnIr.js.map +0 -1
  178. package/phanpy/dist/assets/dom-p1YcJ5Rw.js +0 -2
  179. package/phanpy/dist/assets/dom-p1YcJ5Rw.js.map +0 -1
  180. package/phanpy/dist/assets/home-mobile-dark@2x-Dc-J8-SM.png +0 -0
  181. package/phanpy/dist/assets/home-mobile-light@2x-Bd3fod8m.png +0 -0
  182. package/phanpy/dist/assets/home-tablet-dark@2x-BgcNI8le.png +0 -0
  183. package/phanpy/dist/assets/home-tablet-light@2x-BW2RV9T6.png +0 -0
  184. package/phanpy/dist/assets/icons/add-circle-line-WD0mwORD.js +0 -1
  185. package/phanpy/dist/assets/icons/alert-line-CpFUMf9s.js +0 -1
  186. package/phanpy/dist/assets/icons/android-2-line-Bzxnrd3S.js +0 -1
  187. package/phanpy/dist/assets/icons/announcement-line-5nwaJFJm.js +0 -1
  188. package/phanpy/dist/assets/icons/arrow-down-circle-line-a7Id9-Iv.js +0 -1
  189. package/phanpy/dist/assets/icons/arrow-down-line-B8uzXzq6.js +0 -1
  190. package/phanpy/dist/assets/icons/arrow-left-line-CGrGT9C-.js +0 -1
  191. package/phanpy/dist/assets/icons/arrow-right-line-CE-HERfX.js +0 -1
  192. package/phanpy/dist/assets/icons/arrow-to-up-line-CvdQjmcy.js +0 -1
  193. package/phanpy/dist/assets/icons/arrow-up-circle-line-DEparSNF.js +0 -1
  194. package/phanpy/dist/assets/icons/arrow-up-line-C7yBeugc.js +0 -1
  195. package/phanpy/dist/assets/icons/arrows-right-line-BtbXmJGP.js +0 -1
  196. package/phanpy/dist/assets/icons/at-line-CF66Bznk.js +0 -1
  197. package/phanpy/dist/assets/icons/attachment-line-BY8It-l9.js +0 -1
  198. package/phanpy/dist/assets/icons/blockquote-line-BS6pU_hm.js +0 -1
  199. package/phanpy/dist/assets/icons/board-line-C0UWXyA6.js +0 -1
  200. package/phanpy/dist/assets/icons/bookmark-line-1CtX5JLF.js +0 -1
  201. package/phanpy/dist/assets/icons/building-5-line-Dd59MzbO.js +0 -1
  202. package/phanpy/dist/assets/icons/bus-2-line-6_dvA0GG.js +0 -1
  203. package/phanpy/dist/assets/icons/calendar-day-line-Crpl_fmu.js +0 -1
  204. package/phanpy/dist/assets/icons/calendar-month-line-vommts2C.js +0 -1
  205. package/phanpy/dist/assets/icons/calendar-time-add-line-BU_DTlQk.js +0 -1
  206. package/phanpy/dist/assets/icons/camera-line-Cv69MEei.js +0 -1
  207. package/phanpy/dist/assets/icons/celebrate-line-B8DvIoi0.js +0 -1
  208. package/phanpy/dist/assets/icons/chart-bar-line-FK-QOmtF.js +0 -1
  209. package/phanpy/dist/assets/icons/chart-line-line-BGcJAFWw.js +0 -1
  210. package/phanpy/dist/assets/icons/chat-3-line-YFK2-hwz.js +0 -1
  211. package/phanpy/dist/assets/icons/check-circle-line-DsAxFr5-.js +0 -1
  212. package/phanpy/dist/assets/icons/clipboard-line-BBp364gJ.js +0 -1
  213. package/phanpy/dist/assets/icons/close-circle-line-CICdiuGz.js +0 -1
  214. package/phanpy/dist/assets/icons/close-line-DYrFWcjv.js +0 -1
  215. package/phanpy/dist/assets/icons/cloud-line-tiQAbah8.js +0 -1
  216. package/phanpy/dist/assets/icons/code-line-fAl8iiV3.js +0 -1
  217. package/phanpy/dist/assets/icons/comment-2-line-CR3g7WeB.js +0 -1
  218. package/phanpy/dist/assets/icons/copy-2-line-CLW69yF5.js +0 -1
  219. package/phanpy/dist/assets/icons/delete-2-line-B8xmbr4n.js +0 -1
  220. package/phanpy/dist/assets/icons/document-3-line-DBGNhAg2.js +0 -1
  221. package/phanpy/dist/assets/icons/document-line-C7mb9sb7.js +0 -1
  222. package/phanpy/dist/assets/icons/down-line-BGqSSThd.js +0 -1
  223. package/phanpy/dist/assets/icons/edit-4-line-DcgGaEhx.js +0 -1
  224. package/phanpy/dist/assets/icons/emoji-2-line-D8YgnM-y.js +0 -1
  225. package/phanpy/dist/assets/icons/exit-line-Dc07BS-x.js +0 -1
  226. package/phanpy/dist/assets/icons/external-link-line-BvpSkmjO.js +0 -1
  227. package/phanpy/dist/assets/icons/eye-2-line-CiWXx-f_.js +0 -1
  228. package/phanpy/dist/assets/icons/eye-close-line-BgTaSJ4P.js +0 -1
  229. package/phanpy/dist/assets/icons/filter-2-line-B_mR2usy.js +0 -1
  230. package/phanpy/dist/assets/icons/filter-line-C_nm-3cU.js +0 -1
  231. package/phanpy/dist/assets/icons/flag-1-line-C2HB05I0.js +0 -1
  232. package/phanpy/dist/assets/icons/forbid-circle-line-D3LTT9yd.js +0 -1
  233. package/phanpy/dist/assets/icons/formula-line-DLrt2d6j.js +0 -1
  234. package/phanpy/dist/assets/icons/grid-line-CgUmEikf.js +0 -1
  235. package/phanpy/dist/assets/icons/group-line-C5fnX0wU.js +0 -1
  236. package/phanpy/dist/assets/icons/hand-finger-2-line-BGgxbuL9.js +0 -1
  237. package/phanpy/dist/assets/icons/hashtag-line-CyTEEXBb.js +0 -1
  238. package/phanpy/dist/assets/icons/heart-crack-line-BZddbO2C.js +0 -1
  239. package/phanpy/dist/assets/icons/heart-line-CJ7K47J4.js +0 -1
  240. package/phanpy/dist/assets/icons/history-2-line-kWWvFzqv.js +0 -1
  241. package/phanpy/dist/assets/icons/history-line-bGQiwbTH.js +0 -1
  242. package/phanpy/dist/assets/icons/home-3-line-fRU1zeLG.js +0 -1
  243. package/phanpy/dist/assets/icons/information-line-iNk6-sOY.js +0 -1
  244. package/phanpy/dist/assets/icons/keyboard-line-DsCyuQNz.js +0 -1
  245. package/phanpy/dist/assets/icons/layout-4-line-CZkOX52Z.js +0 -1
  246. package/phanpy/dist/assets/icons/layout-5-line-B_znt5wO.js +0 -1
  247. package/phanpy/dist/assets/icons/left-line-eUw0tya4.js +0 -1
  248. package/phanpy/dist/assets/icons/lightning-line-DaKoDdix.js +0 -1
  249. package/phanpy/dist/assets/icons/link-2-line-DCqLmMA7.js +0 -1
  250. package/phanpy/dist/assets/icons/list-check-line-BAxosBh9.js +0 -1
  251. package/phanpy/dist/assets/icons/lock-line-CPoaXeUW.js +0 -1
  252. package/phanpy/dist/assets/icons/mail-line-B0P8aa1L.js +0 -1
  253. package/phanpy/dist/assets/icons/moon-line-CEqNAToP.js +0 -1
  254. package/phanpy/dist/assets/icons/more-1-fill-Dw2e5xab.js +0 -1
  255. package/phanpy/dist/assets/icons/more-3-line-BIiVjYTz.js +0 -1
  256. package/phanpy/dist/assets/icons/notification-line-BZZq2Gtu.js +0 -1
  257. package/phanpy/dist/assets/icons/pencil-line-CKh8-A1v.js +0 -1
  258. package/phanpy/dist/assets/icons/photo-album-line-Bnfg2u40.js +0 -1
  259. package/phanpy/dist/assets/icons/pin-line-DpTolyhs.js +0 -1
  260. package/phanpy/dist/assets/icons/play-fill-B0-G_8lX.js +0 -1
  261. package/phanpy/dist/assets/icons/qrcode-2-line-B6SPCaHn.js +0 -1
  262. package/phanpy/dist/assets/icons/quill-pen-line-B6LUrnCP.js +0 -1
  263. package/phanpy/dist/assets/icons/quote-left-fill-hQZ1cj_6.js +0 -1
  264. package/phanpy/dist/assets/icons/radar-line-CKyroTth.js +0 -1
  265. package/phanpy/dist/assets/icons/react-line-CoIKhhtK.js +0 -1
  266. package/phanpy/dist/assets/icons/refresh-2-line-D0mTF97S.js +0 -1
  267. package/phanpy/dist/assets/icons/right-line-B3KWJaqc.js +0 -1
  268. package/phanpy/dist/assets/icons/rocket-line-4KZl537D.js +0 -1
  269. package/phanpy/dist/assets/icons/round-fill-C69NTrZ0.js +0 -1
  270. package/phanpy/dist/assets/icons/round-line-B9k_1uo-.js +0 -1
  271. package/phanpy/dist/assets/icons/route-line-B28ReHcE.js +0 -1
  272. package/phanpy/dist/assets/icons/rows-4-line-CrBniagM.js +0 -1
  273. package/phanpy/dist/assets/icons/scan-line-DSf74YdP.js +0 -1
  274. package/phanpy/dist/assets/icons/search-2-line-B6geIU1z.js +0 -1
  275. package/phanpy/dist/assets/icons/settings-3-line-azJwK47w.js +0 -1
  276. package/phanpy/dist/assets/icons/settings-6-line-C_5zc5YR.js +0 -1
  277. package/phanpy/dist/assets/icons/share-2-line-D5MxWjrm.js +0 -1
  278. package/phanpy/dist/assets/icons/share-forward-line-Cp61fZzZ.js +0 -1
  279. package/phanpy/dist/assets/icons/sparkles-2-line-DYYyXTaW.js +0 -1
  280. package/phanpy/dist/assets/icons/sparkles-line-C2hY1s6I.js +0 -1
  281. package/phanpy/dist/assets/icons/time-line-BF3eet1v.js +0 -1
  282. package/phanpy/dist/assets/icons/transfer-4-line-oisfvTBw.js +0 -1
  283. package/phanpy/dist/assets/icons/translate-line-BFWol2Ce.js +0 -1
  284. package/phanpy/dist/assets/icons/unlock-line-X_x5vwv3.js +0 -1
  285. package/phanpy/dist/assets/icons/upload-3-line-DgzwUJeW.js +0 -1
  286. package/phanpy/dist/assets/icons/user-4-line-BVSynZCp.js +0 -1
  287. package/phanpy/dist/assets/icons/user-add-2-line-BlGS5Q8q.js +0 -1
  288. package/phanpy/dist/assets/icons/user-add-line-BbXdHG4z.js +0 -1
  289. package/phanpy/dist/assets/icons/user-edit-line-BFAHOV0X.js +0 -1
  290. package/phanpy/dist/assets/icons/user-follow-line-BXux0wSd.js +0 -1
  291. package/phanpy/dist/assets/icons/user-star-line-DdYlpHLU.js +0 -1
  292. package/phanpy/dist/assets/icons/user-warning-line-Cj43j0_E.js +0 -1
  293. package/phanpy/dist/assets/icons/user-x-line-DIDgNlPH.js +0 -1
  294. package/phanpy/dist/assets/icons/volume-line-Bs390KQt.js +0 -1
  295. package/phanpy/dist/assets/icons/volume-mute-line-DzlzIqFZ.js +0 -1
  296. package/phanpy/dist/assets/icons/walk-line-yNXfB-md.js +0 -1
  297. package/phanpy/dist/assets/icons/world-2-line-CenkGIwj.js +0 -1
  298. package/phanpy/dist/assets/icons/zoom-in-line-CaENUphf.js +0 -1
  299. package/phanpy/dist/assets/icons/zoom-out-line-BDNk0EWo.js +0 -1
  300. package/phanpy/dist/assets/instances-BNPptnn-.json +0 -333
  301. package/phanpy/dist/assets/locales/ar-SA-CfheRbCu.js +0 -1
  302. package/phanpy/dist/assets/locales/ca-ES-DHRW86Tz.js +0 -1
  303. package/phanpy/dist/assets/locales/cs-CZ-CKoqEuA1.js +0 -1
  304. package/phanpy/dist/assets/locales/de-DE-ByRPMRFy.js +0 -1
  305. package/phanpy/dist/assets/locales/eo-UY-CPC7wLCS.js +0 -1
  306. package/phanpy/dist/assets/locales/es-ES-dJa0Fd9t.js +0 -1
  307. package/phanpy/dist/assets/locales/eu-ES-C3djlPR2.js +0 -1
  308. package/phanpy/dist/assets/locales/fa-IR-DTT490Ck.js +0 -1
  309. package/phanpy/dist/assets/locales/fi-FI-B23MCPuz.js +0 -1
  310. package/phanpy/dist/assets/locales/fr-FR-BcaQsO5C.js +0 -1
  311. package/phanpy/dist/assets/locales/gl-ES-BR-CVKBN.js +0 -1
  312. package/phanpy/dist/assets/locales/he-IL-CfheRbCu.js +0 -1
  313. package/phanpy/dist/assets/locales/hu-HU-CfheRbCu.js +0 -1
  314. package/phanpy/dist/assets/locales/it-IT-C1136UFV.js +0 -1
  315. package/phanpy/dist/assets/locales/ja-JP-CRZmRAhD.js +0 -1
  316. package/phanpy/dist/assets/locales/kab-Cz8xH_TT.js +0 -1
  317. package/phanpy/dist/assets/locales/ko-KR-CG9UiY4o.js +0 -1
  318. package/phanpy/dist/assets/locales/lt-LT-CJcI6sHZ.js +0 -1
  319. package/phanpy/dist/assets/locales/nb-NO-CfheRbCu.js +0 -1
  320. package/phanpy/dist/assets/locales/nl-NL-DxN4Yc1t.js +0 -1
  321. package/phanpy/dist/assets/locales/oc-FR-CfheRbCu.js +0 -1
  322. package/phanpy/dist/assets/locales/pl-PL-DkwGXf6P.js +0 -1
  323. package/phanpy/dist/assets/locales/pseudo-LOCALE-CQ4XxkQ0.js +0 -1
  324. package/phanpy/dist/assets/locales/pt-BR-nGL0HdFS.js +0 -1
  325. package/phanpy/dist/assets/locales/pt-PT-Cp17aj2z.js +0 -1
  326. package/phanpy/dist/assets/locales/ru-RU-0Y8cIoaK.js +0 -1
  327. package/phanpy/dist/assets/locales/th-TH-CfheRbCu.js +0 -1
  328. package/phanpy/dist/assets/locales/tok-CfheRbCu.js +0 -1
  329. package/phanpy/dist/assets/locales/tr-TR-CfheRbCu.js +0 -1
  330. package/phanpy/dist/assets/locales/uk-UA-C5E3dFBf.js +0 -1
  331. package/phanpy/dist/assets/locales/zh-CN-BHJi7_et.js +0 -1
  332. package/phanpy/dist/assets/locales/zh-TW-CfheRbCu.js +0 -1
  333. package/phanpy/dist/assets/main-BdqNbG-a.js +0 -26
  334. package/phanpy/dist/assets/main-BdqNbG-a.js.map +0 -1
  335. package/phanpy/dist/assets/mock-home-DGhR__He.js +0 -2
  336. package/phanpy/dist/assets/mock-home-DGhR__He.js.map +0 -1
  337. package/phanpy/dist/assets/multi-column-ROck0NVt.jpg +0 -0
  338. package/phanpy/dist/assets/multi-hashtag-timeline-DH5INVHi.jpg +0 -0
  339. package/phanpy/dist/assets/nested-comments-thread-hv59kZLc.jpg +0 -0
  340. package/phanpy/dist/assets/polyfill-force-H8sUkLGp.js +0 -2
  341. package/phanpy/dist/assets/polyfill-force-H8sUkLGp.js.map +0 -1
  342. package/phanpy/dist/assets/pwa-viewport-ogKbF51K.js +0 -20
  343. package/phanpy/dist/assets/pwa-viewport-ogKbF51K.js.map +0 -1
  344. package/phanpy/dist/assets/style-B1iLPTPR.css +0 -2
  345. package/phanpy/dist/assets/temml-DuyIQMei.js +0 -97
  346. package/phanpy/dist/assets/temml-DuyIQMei.js.map +0 -1
  347. package/phanpy/dist/assets/tinyld.light.node-DtbBhLYC.js +0 -2
  348. package/phanpy/dist/assets/tinyld.light.node-DtbBhLYC.js.map +0 -1
  349. package/phanpy/dist/assets/year-in-posts-B2lywqdx.png +0 -0
  350. package/phanpy/dist/assets/year-in-posts-DJv_star.js +0 -3
  351. package/phanpy/dist/assets/year-in-posts-DJv_star.js.map +0 -1
  352. package/phanpy/dist/compose/index.html +0 -20
  353. package/phanpy/dist/favicon.ico +0 -0
  354. package/phanpy/dist/index.html +0 -73
  355. package/phanpy/dist/logo-192.png +0 -0
  356. package/phanpy/dist/logo-512.png +0 -0
  357. package/phanpy/dist/logo-badge-72.png +0 -0
  358. package/phanpy/dist/logo-maskable-512.png +0 -0
  359. package/phanpy/dist/logo-monochrome-512.png +0 -0
  360. package/phanpy/dist/logo-monochrome-maskable-512.png +0 -0
  361. package/phanpy/dist/manifest.webmanifest +0 -1
  362. package/phanpy/dist/og-image-2.jpg +0 -0
  363. package/phanpy/dist/og-image.png +0 -0
  364. package/phanpy/dist/robots.txt +0 -2
  365. package/phanpy/dist/sw.js +0 -2
  366. package/phanpy/dist/sw.js.map +0 -1
  367. package/phanpy/dist/version.json +0 -1
  368. package/run-agent.mjs +0 -897
  369. package/vendor/gate.cjs +0 -138
  370. package/vendor/idp-grant.cjs +0 -394
  371. package/vendor/jfetch.cjs +0 -16
  372. package/web/admin/actors.js +0 -145
  373. package/web/admin/bar.css +0 -126
  374. package/web/admin/bar.js +0 -99
  375. package/web/admin/client/client.js +0 -66
  376. package/web/admin/client/index.html +0 -69
  377. package/web/admin/client-pick.js +0 -79
  378. package/web/admin/common.js +0 -23
  379. package/web/admin/connections.js +0 -112
  380. package/web/admin/gateway.js +0 -133
  381. package/web/admin/group.js +0 -258
  382. package/web/admin/index.html +0 -603
  383. package/web/admin/notices-bar.js +0 -90
  384. package/web/admin/oauth-signin.mjs +0 -37
  385. package/web/admin/record.js +0 -390
  386. package/web/admin/setup/index.html +0 -241
  387. package/web/admin/setup/setup.js +0 -326
  388. package/web/admin/tokens.css +0 -68
  389. package/web/admin/upkeep.js +0 -177
  390. package/web/admin/window.css +0 -55
  391. package/web/admin/window.js +0 -136
  392. package/web/app/README.md +0 -81
  393. package/web/app/admin-facade.mjs +0 -588
  394. package/web/app/agent.mjs +0 -663
  395. package/web/app/atproto-browser.mjs +0 -86
  396. package/web/app/authorize_interaction.html +0 -56
  397. package/web/app/authorize_interaction.js +0 -69
  398. package/web/app/boot.mjs +0 -576
  399. package/web/app/deliver-relay.mjs +0 -129
  400. package/web/app/dist/boot.js +0 -34218
  401. package/web/app/dist/boot.js.map +0 -7
  402. package/web/app/dist/sw.js +0 -72810
  403. package/web/app/dist/sw.js.map +0 -7
  404. package/web/app/fediacct-browser.mjs +0 -146
  405. package/web/app/gateway-move.mjs +0 -60
  406. package/web/app/idb-kv.mjs +0 -35
  407. package/web/app/index.html +0 -242
  408. package/web/app/keys-browser.mjs +0 -71
  409. package/web/app/keystore.mjs +0 -82
  410. package/web/app/oidc-session.mjs +0 -6
  411. package/web/app/pod-auth.mjs +0 -248
  412. package/web/app/pod-remote.mjs +0 -84
  413. package/web/app/shims/_globals.mjs +0 -2
  414. package/web/app/shims/fedify-sig.mjs +0 -77
  415. package/web/app/shims/node-crypto.mjs +0 -113
  416. package/web/app/shims/node-fs.mjs +0 -17
  417. package/web/app/shims/node-path.mjs +0 -19
  418. package/web/app/shims/node-url.mjs +0 -7
  419. package/web/app/shims/prelude.js +0 -58
  420. package/web/app/shims/safefetch.mjs +0 -58
  421. package/web/app/shims/shapes-text.mjs +0 -8
  422. package/web/app/shims/web-push.mjs +0 -14
  423. package/web/app/signup.mjs +0 -318
  424. package/web/app/sw-src.mjs +0 -220
  425. package/web/app/update.js +0 -70
  426. package/web/front/admin.html +0 -55
  427. package/web/front/admin.js +0 -89
  428. package/web/front/install.sh +0 -69
  429. package/web/front/new-account.html +0 -35
  430. package/web/front/new-account.js +0 -13
  431. package/web/front/notices.html +0 -69
  432. package/web/front/notices.js +0 -107
  433. package/web/front/run.html +0 -58
  434. package/web/front/run.js +0 -195
  435. package/web/front/solid-oidc-client.js +0 -6
  436. package/web/icons/forum.svg +0 -11
  437. package/web/icons/gateway.svg +0 -14
  438. package/web/icons/local.svg +0 -11
@@ -1,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
- }