fedipod-server 0.10.0 → 0.12.1

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 (416) hide show
  1. package/README.md +15 -6
  2. package/dist/claims.js +1 -1
  3. package/dist/handler.d.ts +7 -1
  4. package/dist/handler.js +58 -12
  5. package/dist/handler.jsonld +5 -1
  6. package/dist/store-pod.js +18 -4
  7. package/lib/{c2s.mjs → client/c2s.mjs} +8 -3
  8. package/lib/{localapi.mjs → client/localapi.mjs} +2 -2
  9. package/lib/client/masto/accounts.mjs +264 -0
  10. package/lib/client/masto/body.mjs +69 -0
  11. package/lib/client/masto/index.mjs +183 -0
  12. package/lib/client/masto/instance.mjs +104 -0
  13. package/lib/client/masto/media.mjs +133 -0
  14. package/lib/client/masto/oauth.mjs +599 -0
  15. package/lib/client/masto/render.mjs +459 -0
  16. package/lib/client/masto/statuses.mjs +331 -0
  17. package/lib/client/masto/timelines.mjs +316 -0
  18. package/lib/{streaming.mjs → client/streaming.mjs} +1 -1
  19. package/lib/{acctfeed.mjs → connections/acctfeed.mjs} +1 -1
  20. package/lib/{atproto.mjs → connections/atproto.mjs} +15 -16
  21. package/lib/{bskygroup.mjs → connections/bskygroup.mjs} +1 -1
  22. package/lib/{fediacct.mjs → connections/fediacct.mjs} +31 -35
  23. package/lib/{import.mjs → connections/import.mjs} +2 -2
  24. package/lib/{tagfeed.mjs → connections/tagfeed.mjs} +21 -4
  25. package/lib/connections/vault.mjs +114 -0
  26. package/lib/core/as2.mjs +124 -0
  27. package/lib/core/contexts/activitystreams.json +379 -0
  28. package/lib/core/contexts/did-v1.json +57 -0
  29. package/lib/core/contexts/fep-5711.json +36 -0
  30. package/lib/core/contexts/gotosocial.json +86 -0
  31. package/lib/core/contexts/identity-v1.json +152 -0
  32. package/lib/core/contexts/index.mjs +45 -0
  33. package/lib/core/contexts/join-lemmy.json +33 -0
  34. package/lib/core/contexts/joinmastodon.json +28 -0
  35. package/lib/core/contexts/map.json +16 -0
  36. package/lib/core/contexts/miscellany.json +19 -0
  37. package/lib/core/contexts/schemaorg.json +8845 -0
  38. package/lib/core/contexts/security-data-integrity-v1.json +78 -0
  39. package/lib/core/contexts/security-data-integrity-v2.json +81 -0
  40. package/lib/core/contexts/security-multikey-v1.json +35 -0
  41. package/lib/core/contexts/security-v1.json +74 -0
  42. package/lib/core/contexts/webfinger.json +10 -0
  43. package/lib/{deliver.mjs → core/deliver.mjs} +8 -2
  44. package/lib/core/intake/activities.mjs +437 -0
  45. package/lib/core/intake/activity.mjs +240 -0
  46. package/lib/core/intake/channel.mjs +144 -0
  47. package/lib/core/intake/group.mjs +222 -0
  48. package/lib/core/intake/index.mjs +629 -0
  49. package/lib/core/intake/notes.mjs +288 -0
  50. package/lib/core/intake/verify.mjs +141 -0
  51. package/lib/{keys.mjs → core/keys.mjs} +1 -1
  52. package/lib/core/publisher/collections.mjs +229 -0
  53. package/lib/core/publisher/index.mjs +421 -0
  54. package/lib/core/publisher/notes.mjs +188 -0
  55. package/lib/core/publisher/questions.mjs +233 -0
  56. package/lib/core/publisher/restore.mjs +196 -0
  57. package/lib/core/shapes/activitystreams.ttl +129 -0
  58. package/lib/core/shapes/index.mjs +107 -0
  59. package/lib/core/shapes/shapes-text.mjs +13 -0
  60. package/lib/{social.mjs → core/social.mjs} +19 -10
  61. package/lib/{storage.mjs → core/storage.mjs} +2 -2
  62. package/lib/{store.mjs → core/store.mjs} +12 -1
  63. package/lib/{wire.mjs → core/wire.mjs} +48 -67
  64. package/lib/device/admin/index.mjs +13 -0
  65. package/lib/device/admin/origins.mjs +35 -0
  66. package/lib/device/admin/routes/connections.mjs +144 -0
  67. package/lib/device/admin/routes/gateway.mjs +199 -0
  68. package/lib/device/admin/routes/lifecycle.mjs +191 -0
  69. package/lib/device/admin/routes/owner.mjs +322 -0
  70. package/lib/device/admin/routes/setup.mjs +393 -0
  71. package/lib/device/admin/routes/social.mjs +188 -0
  72. package/lib/device/admin/server.mjs +95 -0
  73. package/lib/device/admin/static.mjs +244 -0
  74. package/lib/device/admin/surface.mjs +274 -0
  75. package/lib/device/cli/commands/account.mjs +586 -0
  76. package/lib/device/cli/commands/run.mjs +278 -0
  77. package/lib/device/cli/commands/service.mjs +221 -0
  78. package/lib/device/cli/commands/setup.mjs +410 -0
  79. package/lib/device/cli/commands/state.mjs +559 -0
  80. package/lib/device/cli/context.mjs +288 -0
  81. package/lib/{export-collections.mjs → device/export-collections.mjs} +1 -1
  82. package/lib/{migrate.mjs → device/migrate.mjs} +5 -19
  83. package/lib/device/remote.mjs +54 -0
  84. package/lib/{setup.mjs → device/setup.mjs} +3 -3
  85. package/lib/{update.mjs → device/update.mjs} +1 -1
  86. package/lib/{directory.mjs → gateway/directory.mjs} +1 -1
  87. package/lib/{front-core.mjs → gateway/front-core.mjs} +272 -49
  88. package/lib/{gateway-core.mjs → gateway/gateway-core.mjs} +3 -7
  89. package/lib/{httpsig.mjs → gateway/httpsig.mjs} +34 -3
  90. package/lib/pod/README.md +61 -0
  91. package/lib/pod/actor.mjs +96 -0
  92. package/lib/pod/collection.mjs +77 -0
  93. package/lib/pod/containers.mjs +136 -0
  94. package/lib/pod/discovery.mjs +46 -0
  95. package/lib/pod/featured.mjs +18 -0
  96. package/lib/pod/followers.mjs +24 -0
  97. package/lib/pod/following.mjs +10 -0
  98. package/lib/pod/http.mjs +74 -0
  99. package/lib/pod/inbox.mjs +144 -0
  100. package/lib/{links.mjs → pod/links.mjs} +1 -1
  101. package/lib/pod/media.mjs +27 -0
  102. package/lib/pod/notes.mjs +80 -0
  103. package/lib/pod/notifications.mjs +89 -0
  104. package/lib/pod/outbox.mjs +18 -0
  105. package/lib/pod/package.json +19 -0
  106. package/lib/pod/policy.mjs +58 -0
  107. package/lib/pod/private.mjs +19 -0
  108. package/lib/pod/root.mjs +63 -0
  109. package/lib/pod/state.mjs +39 -0
  110. package/lib/{remote.mjs → pod/transport.mjs} +217 -159
  111. package/lib/pod/urls.mjs +71 -0
  112. package/lib/{embed.mjs → server/embed.mjs} +149 -22
  113. package/lib/shared/links.mjs +9 -0
  114. package/lib/{ua.mjs → shared/ua.mjs} +1 -1
  115. package/package.json +1 -1
  116. package/run-agent.mjs +47 -64
  117. package/web/admin/actors.js +145 -0
  118. package/web/admin/bar.css +9 -13
  119. package/web/admin/bar.js +2 -2
  120. package/web/admin/client/client.js +1 -1
  121. package/web/admin/client/index.html +5 -6
  122. package/web/admin/common.js +23 -0
  123. package/web/admin/connections.js +112 -0
  124. package/web/admin/gateway.js +111 -0
  125. package/web/admin/group.js +258 -0
  126. package/web/admin/index.html +60 -66
  127. package/web/admin/record.js +378 -0
  128. package/web/admin/setup/index.html +3 -1
  129. package/web/admin/setup/setup.js +2 -13
  130. package/web/admin/tokens.css +56 -0
  131. package/web/admin/upkeep.js +170 -0
  132. package/web/app/README.md +77 -0
  133. package/web/app/admin-facade.mjs +549 -0
  134. package/web/app/agent.mjs +490 -0
  135. package/web/app/atproto-browser.mjs +86 -0
  136. package/web/app/boot.mjs +340 -0
  137. package/web/app/deliver-relay.mjs +72 -0
  138. package/web/app/dist/boot.js +34067 -0
  139. package/web/app/dist/boot.js.map +7 -0
  140. package/web/app/dist/sw.js +70014 -0
  141. package/web/app/dist/sw.js.map +7 -0
  142. package/web/app/fediacct-browser.mjs +146 -0
  143. package/web/app/idb-kv.mjs +35 -0
  144. package/web/app/index.html +203 -0
  145. package/web/app/index.html~ +152 -0
  146. package/web/app/keys-browser.mjs +46 -0
  147. package/web/app/keystore.mjs +99 -0
  148. package/web/app/oidc-session.mjs +189 -0
  149. package/web/app/pod-auth.mjs +248 -0
  150. package/web/app/pod-remote.mjs +84 -0
  151. package/web/app/shims/_globals.mjs +2 -0
  152. package/web/app/shims/fedify-sig.mjs +77 -0
  153. package/web/app/shims/node-crypto.mjs +113 -0
  154. package/web/app/shims/node-fs.mjs +17 -0
  155. package/web/app/shims/node-path.mjs +19 -0
  156. package/web/app/shims/node-url.mjs +7 -0
  157. package/web/app/shims/prelude.js +58 -0
  158. package/web/app/shims/safefetch.mjs +58 -0
  159. package/web/app/shims/shapes-text.mjs +8 -0
  160. package/web/app/shims/web-push.mjs +14 -0
  161. package/web/app/signup.mjs +248 -0
  162. package/web/app/site/_headers +9 -0
  163. package/web/app/site/_redirects +21 -0
  164. package/web/app/site/admin/actors.js +145 -0
  165. package/web/app/site/admin/bar.css +60 -0
  166. package/web/app/site/admin/bar.js +61 -0
  167. package/web/app/site/admin/client/client.js +66 -0
  168. package/web/app/site/admin/client/index.html +43 -0
  169. package/web/app/site/admin/common.js +23 -0
  170. package/web/app/site/admin/connections.js +112 -0
  171. package/web/app/site/admin/gateway.js +111 -0
  172. package/web/app/site/admin/group.js +258 -0
  173. package/web/app/site/admin/index.html +562 -0
  174. package/web/app/site/admin/record.js +378 -0
  175. package/web/app/site/admin/setup/index.html +192 -0
  176. package/web/app/site/admin/setup/setup.js +297 -0
  177. package/web/app/site/admin/tokens.css +56 -0
  178. package/web/app/site/admin/upkeep.js +170 -0
  179. package/web/app/site/admin/window.css +55 -0
  180. package/web/app/site/admin/window.js +136 -0
  181. package/web/app/site/app/404.html +40 -0
  182. package/web/app/site/app/_headers +2 -0
  183. package/web/app/site/app/apple-touch-icon.png +0 -0
  184. package/web/app/site/app/assets/ICONS-DIPJeU0C.js +2 -0
  185. package/web/app/site/app/assets/ICONS-DIPJeU0C.js.map +1 -0
  186. package/web/app/site/app/assets/ScheduledAtField-D-Abfo4J.js +183 -0
  187. package/web/app/site/app/assets/ScheduledAtField-D-Abfo4J.js.map +1 -0
  188. package/web/app/site/app/assets/Temml-CCjoacWt.woff2 +0 -0
  189. package/web/app/site/app/assets/__vite-browser-external-C7iut881.js +2 -0
  190. package/web/app/site/app/assets/__vite-browser-external-C7iut881.js.map +1 -0
  191. package/web/app/site/app/assets/boosts-carousel-BiOaNdMT.jpg +0 -0
  192. package/web/app/site/app/assets/catch-up-O5q5C75Z.png +0 -0
  193. package/web/app/site/app/assets/chunk-aKtaBQYM.js +1 -0
  194. package/web/app/site/app/assets/compose-CFEx7wb4.js +2 -0
  195. package/web/app/site/app/assets/compose-CFEx7wb4.js.map +1 -0
  196. package/web/app/site/app/assets/compose-ZJN5xSb4.js +35 -0
  197. package/web/app/site/app/assets/compose-ZJN5xSb4.js.map +1 -0
  198. package/web/app/site/app/assets/debug.module-YFQgwnIr.js +52 -0
  199. package/web/app/site/app/assets/debug.module-YFQgwnIr.js.map +1 -0
  200. package/web/app/site/app/assets/dom-p1YcJ5Rw.js +2 -0
  201. package/web/app/site/app/assets/dom-p1YcJ5Rw.js.map +1 -0
  202. package/web/app/site/app/assets/home-mobile-dark@2x-Dc-J8-SM.png +0 -0
  203. package/web/app/site/app/assets/home-mobile-light@2x-Bd3fod8m.png +0 -0
  204. package/web/app/site/app/assets/home-tablet-dark@2x-BgcNI8le.png +0 -0
  205. package/web/app/site/app/assets/home-tablet-light@2x-BW2RV9T6.png +0 -0
  206. package/web/app/site/app/assets/icons/add-circle-line-WD0mwORD.js +1 -0
  207. package/web/app/site/app/assets/icons/alert-line-CpFUMf9s.js +1 -0
  208. package/web/app/site/app/assets/icons/android-2-line-Bzxnrd3S.js +1 -0
  209. package/web/app/site/app/assets/icons/announcement-line-5nwaJFJm.js +1 -0
  210. package/web/app/site/app/assets/icons/arrow-down-circle-line-a7Id9-Iv.js +1 -0
  211. package/web/app/site/app/assets/icons/arrow-down-line-B8uzXzq6.js +1 -0
  212. package/web/app/site/app/assets/icons/arrow-left-line-CGrGT9C-.js +1 -0
  213. package/web/app/site/app/assets/icons/arrow-right-line-CE-HERfX.js +1 -0
  214. package/web/app/site/app/assets/icons/arrow-to-up-line-CvdQjmcy.js +1 -0
  215. package/web/app/site/app/assets/icons/arrow-up-circle-line-DEparSNF.js +1 -0
  216. package/web/app/site/app/assets/icons/arrow-up-line-C7yBeugc.js +1 -0
  217. package/web/app/site/app/assets/icons/arrows-right-line-BtbXmJGP.js +1 -0
  218. package/web/app/site/app/assets/icons/at-line-CF66Bznk.js +1 -0
  219. package/web/app/site/app/assets/icons/attachment-line-BY8It-l9.js +1 -0
  220. package/web/app/site/app/assets/icons/blockquote-line-BS6pU_hm.js +1 -0
  221. package/web/app/site/app/assets/icons/board-line-C0UWXyA6.js +1 -0
  222. package/web/app/site/app/assets/icons/bookmark-line-1CtX5JLF.js +1 -0
  223. package/web/app/site/app/assets/icons/building-5-line-Dd59MzbO.js +1 -0
  224. package/web/app/site/app/assets/icons/bus-2-line-6_dvA0GG.js +1 -0
  225. package/web/app/site/app/assets/icons/calendar-day-line-Crpl_fmu.js +1 -0
  226. package/web/app/site/app/assets/icons/calendar-month-line-vommts2C.js +1 -0
  227. package/web/app/site/app/assets/icons/calendar-time-add-line-BU_DTlQk.js +1 -0
  228. package/web/app/site/app/assets/icons/camera-line-Cv69MEei.js +1 -0
  229. package/web/app/site/app/assets/icons/celebrate-line-B8DvIoi0.js +1 -0
  230. package/web/app/site/app/assets/icons/chart-bar-line-FK-QOmtF.js +1 -0
  231. package/web/app/site/app/assets/icons/chart-line-line-BGcJAFWw.js +1 -0
  232. package/web/app/site/app/assets/icons/chat-3-line-YFK2-hwz.js +1 -0
  233. package/web/app/site/app/assets/icons/check-circle-line-DsAxFr5-.js +1 -0
  234. package/web/app/site/app/assets/icons/clipboard-line-BBp364gJ.js +1 -0
  235. package/web/app/site/app/assets/icons/close-circle-line-CICdiuGz.js +1 -0
  236. package/web/app/site/app/assets/icons/close-line-DYrFWcjv.js +1 -0
  237. package/web/app/site/app/assets/icons/cloud-line-tiQAbah8.js +1 -0
  238. package/web/app/site/app/assets/icons/code-line-fAl8iiV3.js +1 -0
  239. package/web/app/site/app/assets/icons/comment-2-line-CR3g7WeB.js +1 -0
  240. package/web/app/site/app/assets/icons/copy-2-line-CLW69yF5.js +1 -0
  241. package/web/app/site/app/assets/icons/delete-2-line-B8xmbr4n.js +1 -0
  242. package/web/app/site/app/assets/icons/document-3-line-DBGNhAg2.js +1 -0
  243. package/web/app/site/app/assets/icons/document-line-C7mb9sb7.js +1 -0
  244. package/web/app/site/app/assets/icons/down-line-BGqSSThd.js +1 -0
  245. package/web/app/site/app/assets/icons/edit-4-line-DcgGaEhx.js +1 -0
  246. package/web/app/site/app/assets/icons/emoji-2-line-D8YgnM-y.js +1 -0
  247. package/web/app/site/app/assets/icons/exit-line-Dc07BS-x.js +1 -0
  248. package/web/app/site/app/assets/icons/external-link-line-BvpSkmjO.js +1 -0
  249. package/web/app/site/app/assets/icons/eye-2-line-CiWXx-f_.js +1 -0
  250. package/web/app/site/app/assets/icons/eye-close-line-BgTaSJ4P.js +1 -0
  251. package/web/app/site/app/assets/icons/filter-2-line-B_mR2usy.js +1 -0
  252. package/web/app/site/app/assets/icons/filter-line-C_nm-3cU.js +1 -0
  253. package/web/app/site/app/assets/icons/flag-1-line-C2HB05I0.js +1 -0
  254. package/web/app/site/app/assets/icons/forbid-circle-line-D3LTT9yd.js +1 -0
  255. package/web/app/site/app/assets/icons/formula-line-DLrt2d6j.js +1 -0
  256. package/web/app/site/app/assets/icons/grid-line-CgUmEikf.js +1 -0
  257. package/web/app/site/app/assets/icons/group-line-C5fnX0wU.js +1 -0
  258. package/web/app/site/app/assets/icons/hand-finger-2-line-BGgxbuL9.js +1 -0
  259. package/web/app/site/app/assets/icons/hashtag-line-CyTEEXBb.js +1 -0
  260. package/web/app/site/app/assets/icons/heart-crack-line-BZddbO2C.js +1 -0
  261. package/web/app/site/app/assets/icons/heart-line-CJ7K47J4.js +1 -0
  262. package/web/app/site/app/assets/icons/history-2-line-kWWvFzqv.js +1 -0
  263. package/web/app/site/app/assets/icons/history-line-bGQiwbTH.js +1 -0
  264. package/web/app/site/app/assets/icons/home-3-line-fRU1zeLG.js +1 -0
  265. package/web/app/site/app/assets/icons/information-line-iNk6-sOY.js +1 -0
  266. package/web/app/site/app/assets/icons/keyboard-line-DsCyuQNz.js +1 -0
  267. package/web/app/site/app/assets/icons/layout-4-line-CZkOX52Z.js +1 -0
  268. package/web/app/site/app/assets/icons/layout-5-line-B_znt5wO.js +1 -0
  269. package/web/app/site/app/assets/icons/left-line-eUw0tya4.js +1 -0
  270. package/web/app/site/app/assets/icons/lightning-line-DaKoDdix.js +1 -0
  271. package/web/app/site/app/assets/icons/link-2-line-DCqLmMA7.js +1 -0
  272. package/web/app/site/app/assets/icons/list-check-line-BAxosBh9.js +1 -0
  273. package/web/app/site/app/assets/icons/lock-line-CPoaXeUW.js +1 -0
  274. package/web/app/site/app/assets/icons/mail-line-B0P8aa1L.js +1 -0
  275. package/web/app/site/app/assets/icons/moon-line-CEqNAToP.js +1 -0
  276. package/web/app/site/app/assets/icons/more-1-fill-Dw2e5xab.js +1 -0
  277. package/web/app/site/app/assets/icons/more-3-line-BIiVjYTz.js +1 -0
  278. package/web/app/site/app/assets/icons/notification-line-BZZq2Gtu.js +1 -0
  279. package/web/app/site/app/assets/icons/pencil-line-CKh8-A1v.js +1 -0
  280. package/web/app/site/app/assets/icons/photo-album-line-Bnfg2u40.js +1 -0
  281. package/web/app/site/app/assets/icons/pin-line-DpTolyhs.js +1 -0
  282. package/web/app/site/app/assets/icons/play-fill-B0-G_8lX.js +1 -0
  283. package/web/app/site/app/assets/icons/qrcode-2-line-B6SPCaHn.js +1 -0
  284. package/web/app/site/app/assets/icons/quill-pen-line-B6LUrnCP.js +1 -0
  285. package/web/app/site/app/assets/icons/quote-left-fill-hQZ1cj_6.js +1 -0
  286. package/web/app/site/app/assets/icons/radar-line-CKyroTth.js +1 -0
  287. package/web/app/site/app/assets/icons/react-line-CoIKhhtK.js +1 -0
  288. package/web/app/site/app/assets/icons/refresh-2-line-D0mTF97S.js +1 -0
  289. package/web/app/site/app/assets/icons/right-line-B3KWJaqc.js +1 -0
  290. package/web/app/site/app/assets/icons/rocket-line-4KZl537D.js +1 -0
  291. package/web/app/site/app/assets/icons/round-fill-C69NTrZ0.js +1 -0
  292. package/web/app/site/app/assets/icons/round-line-B9k_1uo-.js +1 -0
  293. package/web/app/site/app/assets/icons/route-line-B28ReHcE.js +1 -0
  294. package/web/app/site/app/assets/icons/rows-4-line-CrBniagM.js +1 -0
  295. package/web/app/site/app/assets/icons/scan-line-DSf74YdP.js +1 -0
  296. package/web/app/site/app/assets/icons/search-2-line-B6geIU1z.js +1 -0
  297. package/web/app/site/app/assets/icons/settings-3-line-azJwK47w.js +1 -0
  298. package/web/app/site/app/assets/icons/settings-6-line-C_5zc5YR.js +1 -0
  299. package/web/app/site/app/assets/icons/share-2-line-D5MxWjrm.js +1 -0
  300. package/web/app/site/app/assets/icons/share-forward-line-Cp61fZzZ.js +1 -0
  301. package/web/app/site/app/assets/icons/sparkles-2-line-DYYyXTaW.js +1 -0
  302. package/web/app/site/app/assets/icons/sparkles-line-C2hY1s6I.js +1 -0
  303. package/web/app/site/app/assets/icons/time-line-BF3eet1v.js +1 -0
  304. package/web/app/site/app/assets/icons/transfer-4-line-oisfvTBw.js +1 -0
  305. package/web/app/site/app/assets/icons/translate-line-BFWol2Ce.js +1 -0
  306. package/web/app/site/app/assets/icons/unlock-line-X_x5vwv3.js +1 -0
  307. package/web/app/site/app/assets/icons/upload-3-line-DgzwUJeW.js +1 -0
  308. package/web/app/site/app/assets/icons/user-4-line-BVSynZCp.js +1 -0
  309. package/web/app/site/app/assets/icons/user-add-2-line-BlGS5Q8q.js +1 -0
  310. package/web/app/site/app/assets/icons/user-add-line-BbXdHG4z.js +1 -0
  311. package/web/app/site/app/assets/icons/user-edit-line-BFAHOV0X.js +1 -0
  312. package/web/app/site/app/assets/icons/user-follow-line-BXux0wSd.js +1 -0
  313. package/web/app/site/app/assets/icons/user-star-line-DdYlpHLU.js +1 -0
  314. package/web/app/site/app/assets/icons/user-warning-line-Cj43j0_E.js +1 -0
  315. package/web/app/site/app/assets/icons/user-x-line-DIDgNlPH.js +1 -0
  316. package/web/app/site/app/assets/icons/volume-line-Bs390KQt.js +1 -0
  317. package/web/app/site/app/assets/icons/volume-mute-line-DzlzIqFZ.js +1 -0
  318. package/web/app/site/app/assets/icons/walk-line-yNXfB-md.js +1 -0
  319. package/web/app/site/app/assets/icons/world-2-line-CenkGIwj.js +1 -0
  320. package/web/app/site/app/assets/icons/zoom-in-line-CaENUphf.js +1 -0
  321. package/web/app/site/app/assets/icons/zoom-out-line-BDNk0EWo.js +1 -0
  322. package/web/app/site/app/assets/instances-BNPptnn-.json +333 -0
  323. package/web/app/site/app/assets/locales/ar-SA-CfheRbCu.js +1 -0
  324. package/web/app/site/app/assets/locales/ca-ES-DHRW86Tz.js +1 -0
  325. package/web/app/site/app/assets/locales/cs-CZ-CKoqEuA1.js +1 -0
  326. package/web/app/site/app/assets/locales/de-DE-ByRPMRFy.js +1 -0
  327. package/web/app/site/app/assets/locales/eo-UY-CPC7wLCS.js +1 -0
  328. package/web/app/site/app/assets/locales/es-ES-dJa0Fd9t.js +1 -0
  329. package/web/app/site/app/assets/locales/eu-ES-C3djlPR2.js +1 -0
  330. package/web/app/site/app/assets/locales/fa-IR-DTT490Ck.js +1 -0
  331. package/web/app/site/app/assets/locales/fi-FI-B23MCPuz.js +1 -0
  332. package/web/app/site/app/assets/locales/fr-FR-BcaQsO5C.js +1 -0
  333. package/web/app/site/app/assets/locales/gl-ES-BR-CVKBN.js +1 -0
  334. package/web/app/site/app/assets/locales/he-IL-CfheRbCu.js +1 -0
  335. package/web/app/site/app/assets/locales/hu-HU-CfheRbCu.js +1 -0
  336. package/web/app/site/app/assets/locales/it-IT-C1136UFV.js +1 -0
  337. package/web/app/site/app/assets/locales/ja-JP-CRZmRAhD.js +1 -0
  338. package/web/app/site/app/assets/locales/kab-Cz8xH_TT.js +1 -0
  339. package/web/app/site/app/assets/locales/ko-KR-CG9UiY4o.js +1 -0
  340. package/web/app/site/app/assets/locales/lt-LT-CJcI6sHZ.js +1 -0
  341. package/web/app/site/app/assets/locales/nb-NO-CfheRbCu.js +1 -0
  342. package/web/app/site/app/assets/locales/nl-NL-DxN4Yc1t.js +1 -0
  343. package/web/app/site/app/assets/locales/oc-FR-CfheRbCu.js +1 -0
  344. package/web/app/site/app/assets/locales/pl-PL-DkwGXf6P.js +1 -0
  345. package/web/app/site/app/assets/locales/pseudo-LOCALE-CQ4XxkQ0.js +1 -0
  346. package/web/app/site/app/assets/locales/pt-BR-nGL0HdFS.js +1 -0
  347. package/web/app/site/app/assets/locales/pt-PT-Cp17aj2z.js +1 -0
  348. package/web/app/site/app/assets/locales/ru-RU-0Y8cIoaK.js +1 -0
  349. package/web/app/site/app/assets/locales/th-TH-CfheRbCu.js +1 -0
  350. package/web/app/site/app/assets/locales/tok-CfheRbCu.js +1 -0
  351. package/web/app/site/app/assets/locales/tr-TR-CfheRbCu.js +1 -0
  352. package/web/app/site/app/assets/locales/uk-UA-C5E3dFBf.js +1 -0
  353. package/web/app/site/app/assets/locales/zh-CN-BHJi7_et.js +1 -0
  354. package/web/app/site/app/assets/locales/zh-TW-CfheRbCu.js +1 -0
  355. package/web/app/site/app/assets/main-BdqNbG-a.js +26 -0
  356. package/web/app/site/app/assets/main-BdqNbG-a.js.map +1 -0
  357. package/web/app/site/app/assets/mock-home-DGhR__He.js +2 -0
  358. package/web/app/site/app/assets/mock-home-DGhR__He.js.map +1 -0
  359. package/web/app/site/app/assets/multi-column-ROck0NVt.jpg +0 -0
  360. package/web/app/site/app/assets/multi-hashtag-timeline-DH5INVHi.jpg +0 -0
  361. package/web/app/site/app/assets/nested-comments-thread-hv59kZLc.jpg +0 -0
  362. package/web/app/site/app/assets/polyfill-force-H8sUkLGp.js +2 -0
  363. package/web/app/site/app/assets/polyfill-force-H8sUkLGp.js.map +1 -0
  364. package/web/app/site/app/assets/pwa-viewport-ogKbF51K.js +20 -0
  365. package/web/app/site/app/assets/pwa-viewport-ogKbF51K.js.map +1 -0
  366. package/web/app/site/app/assets/style-B1iLPTPR.css +2 -0
  367. package/web/app/site/app/assets/temml-DuyIQMei.js +97 -0
  368. package/web/app/site/app/assets/temml-DuyIQMei.js.map +1 -0
  369. package/web/app/site/app/assets/tinyld.light.node-DtbBhLYC.js +2 -0
  370. package/web/app/site/app/assets/tinyld.light.node-DtbBhLYC.js.map +1 -0
  371. package/web/app/site/app/assets/year-in-posts-B2lywqdx.png +0 -0
  372. package/web/app/site/app/assets/year-in-posts-DJv_star.js +3 -0
  373. package/web/app/site/app/assets/year-in-posts-DJv_star.js.map +1 -0
  374. package/web/app/site/app/compose/index.html +20 -0
  375. package/web/app/site/app/favicon.ico +0 -0
  376. package/web/app/site/app/index.html +73 -0
  377. package/web/app/site/app/logo-192.png +0 -0
  378. package/web/app/site/app/logo-512.png +0 -0
  379. package/web/app/site/app/logo-badge-72.png +0 -0
  380. package/web/app/site/app/logo-maskable-512.png +0 -0
  381. package/web/app/site/app/logo-monochrome-512.png +0 -0
  382. package/web/app/site/app/logo-monochrome-maskable-512.png +0 -0
  383. package/web/app/site/app/manifest.webmanifest +1 -0
  384. package/web/app/site/app/og-image-2.jpg +0 -0
  385. package/web/app/site/app/og-image.png +0 -0
  386. package/web/app/site/app/robots.txt +2 -0
  387. package/web/app/site/app/version.json +1 -0
  388. package/web/app/site/boot.js +34067 -0
  389. package/web/app/site/index.html +203 -0
  390. package/web/app/site/sw.js +70014 -0
  391. package/web/app/sw-src.mjs +220 -0
  392. package/web/front/#new-account.html# +43 -0
  393. package/web/front/admin.html +1 -81
  394. package/web/front/admin.js +85 -0
  395. package/web/front/new-account.html +1 -9
  396. package/web/front/new-account.js +13 -0
  397. package/web/front/run.html +1 -92
  398. package/web/front/run.js +107 -0
  399. package/lib/admin.mjs +0 -1913
  400. package/lib/intake.mjs +0 -1764
  401. package/lib/mastoapi.mjs +0 -1947
  402. package/lib/podrdf.mjs +0 -132
  403. package/lib/publisher.mjs +0 -1263
  404. package/web/admin/admin.js +0 -1114
  405. /package/lib/{oidc-auth.mjs → client/oidc-auth.mjs} +0 -0
  406. /package/lib/{webpush.mjs → client/webpush.mjs} +0 -0
  407. /package/lib/{bskyfeed.mjs → connections/bskyfeed.mjs} +0 -0
  408. /package/lib/{lease.mjs → core/lease.mjs} +0 -0
  409. /package/lib/{polls.mjs → core/polls.mjs} +0 -0
  410. /package/lib/{proof.mjs → core/proof.mjs} +0 -0
  411. /package/lib/{account.mjs → device/account.mjs} +0 -0
  412. /package/lib/{certs.mjs → device/certs.mjs} +0 -0
  413. /package/lib/{home.mjs → device/home.mjs} +0 -0
  414. /package/lib/{ports.mjs → device/ports.mjs} +0 -0
  415. /package/lib/{guard.mjs → shared/guard.mjs} +0 -0
  416. /package/lib/{safefetch.mjs → shared/safefetch.mjs} +0 -0
@@ -16,15 +16,16 @@ import path from 'node:path';
16
16
 
17
17
  import { createRequire } from 'node:module';
18
18
 
19
- import { Agent } from '../run-agent.mjs';
20
- import { RemotePod } from './remote.mjs';
21
- import { apUrls } from './wire.mjs';
22
- import { writeJsonAtomic } from './home.mjs';
23
- import { buildAdminSurface } from './admin.mjs';
24
- import { FixedAuthorities } from './guard.mjs';
19
+ import { Agent } from '../../run-agent.mjs';
20
+ import { RemotePod } from '../device/remote.mjs';
21
+ import { apUrls, DEFAULT_ROOT } from '../core/wire.mjs';
22
+ import { writeJsonAtomic } from '../device/home.mjs';
23
+ import { buildAdminSurface } from '../device/admin/index.mjs';
24
+ import { FixedAuthorities } from '../shared/guard.mjs';
25
+ import { CONNECTION_PREFIX, fileVault, moveVault, podVault } from '../connections/vault.mjs';
25
26
 
26
27
  const require = createRequire(import.meta.url);
27
- const { makeGate } = require('../vendor/gate.cjs');
28
+ const { makeGate } = require('../../vendor/gate.cjs');
28
29
 
29
30
  // How long to gather store events before draining, so a delivery of several
30
31
  // items costs one sweep rather than one each.
@@ -34,23 +35,75 @@ const DRAIN_COALESCE_MS = 250;
34
35
  const mintSecret = () => crypto.randomBytes(32).toString('base64');
35
36
 
36
37
  /**
37
- * The secret guarding one identity's owner door, kept beside its signing key.
38
- * Minted when absent; `rotate` re-mints over an existing one — which is how a
39
- * lost secret is recovered, by proving pod control again. Never logged.
38
+ * The secret guarding one identity's owner door, kept in its pod's state
39
+ * beside its signing key. Minted when absent; `rotate` re-mints over an
40
+ * existing one — which is how a lost secret is recovered, by proving pod
41
+ * control again. Never logged.
42
+ *
43
+ * `dataDir`, when given, is where an identity set up before this kept it: the
44
+ * secret is moved into the pod and the host's copy removed, so the owner's
45
+ * existing door link goes on working.
40
46
  */
41
- export function ensureDoorSecret(dataDir, handle, { rotate = false } = {}) {
42
- const home = path.join(dataDir, handle);
43
- fs.mkdirSync(home, { recursive: true, mode: 0o700 });
44
- const file = path.join(home, 'door-secret.json');
47
+ export async function ensureDoorSecret(session, podBase, { rotate = false, dataDir = null, handle = null, log = () => {} } = {}) {
48
+ const base = podBase.endsWith('/') ? podBase : podBase + '/';
49
+ const url = apUrls(base, DEFAULT_ROOT).state + 'door-secret.json';
50
+ const onHost = dataDir && handle ? path.join(dataDir, handle, 'door-secret.json') : null;
51
+
45
52
  if (!rotate) {
46
- try {
47
- const rec = JSON.parse(fs.readFileSync(file, 'utf8'));
48
- if (rec?.secret) return { secret: rec.secret, path: file, rotated: false };
49
- } catch { /* absent or unreadable: mint below */ }
53
+ const res = await session.fetch(url, { headers: { accept: 'application/json' } })
54
+ .catch(() => ({ ok: false }));
55
+ if (res.ok) {
56
+ try {
57
+ const rec = JSON.parse(await res.text());
58
+ if (rec?.secret) {
59
+ if (onHost) fs.rmSync(onHost, { force: true });
60
+ return { secret: rec.secret, url, rotated: false };
61
+ }
62
+ } catch { /* unreadable: mint below */ }
63
+ }
64
+ // Set up before the secret lived on the pod: keep the one the owner has.
65
+ if (onHost && fs.existsSync(onHost)) {
66
+ try {
67
+ const rec = JSON.parse(fs.readFileSync(onHost, 'utf8'));
68
+ if (rec?.secret && await putSecret(session, url, rec)) {
69
+ fs.rmSync(onHost, { force: true });
70
+ log('door secret moved into the pod');
71
+ return { secret: rec.secret, url, rotated: false };
72
+ }
73
+ } catch { /* unreadable: mint below */ }
74
+ }
50
75
  }
76
+
51
77
  const rec = { secret: mintSecret(), mintedAt: new Date().toISOString() };
52
- writeJsonAtomic(file, rec, { mode: 0o600 });
53
- return { secret: rec.secret, path: file, rotated: rotate };
78
+ if (await putSecret(session, url, rec)) {
79
+ if (onHost) fs.rmSync(onHost, { force: true });
80
+ return { secret: rec.secret, url, rotated: rotate };
81
+ }
82
+ // The pod would not take it. Opting in must not fail for that: a pod that
83
+ // cannot be written to right now is a pod whose owner still asked for an
84
+ // identity, and the secret has somewhere else to live — where it lived
85
+ // before. The next start moves it onto the pod.
86
+ if (onHost) {
87
+ writeJsonAtomic(onHost, rec, { mode: 0o600 });
88
+ log(`the pod would not take the door secret; it is on this host at ${onHost} for now`);
89
+ return { secret: rec.secret, url: onHost, rotated: rotate };
90
+ }
91
+ throw new Error(`could not write the door secret to ${url}, and there is no directory to keep it in`);
92
+ }
93
+
94
+ async function putSecret(session, url, rec) {
95
+ try {
96
+ const res = await session.fetch(url, {
97
+ method: 'PUT',
98
+ headers: { 'content-type': 'application/json' },
99
+ body: JSON.stringify(rec, null, 2) + '\n',
100
+ });
101
+ return res.ok;
102
+ } catch {
103
+ // A store that throws rather than answering is a store that did not take
104
+ // it, which is the same answer.
105
+ return false;
106
+ }
54
107
  }
55
108
 
56
109
  /** The identity's name: a subdomain pod is its label, a path pod its last segment. */
@@ -64,20 +117,90 @@ export function handleFor(podBase) {
64
117
  * The credential file an embedded identity runs on. It names the pod and
65
118
  * nothing else: there is no client id and no secret, because there is nobody
66
119
  * to authenticate to. Absent `privateRoot`, so the state tree stays on the pod.
120
+ *
121
+ * `keysMode: 'pod'` because here the pod's server and the agent are the same
122
+ * process: keeping the signing key on the host's disk puts it somewhere the
123
+ * pod does not travel to, so handing the pod over hands over an identity that
124
+ * cannot sign. An identity set up before this gets the mode added.
67
125
  */
68
126
  function ensureCredential(home, { podBase, webId }) {
69
127
  const file = path.join(home, 'credential.json');
70
- try { return JSON.parse(fs.readFileSync(file, 'utf8')); }
71
- catch { /* first run for this identity */ }
128
+ try {
129
+ const rec = JSON.parse(fs.readFileSync(file, 'utf8'));
130
+ if (rec.keysMode === 'pod') return rec;
131
+ const updated = { ...rec, keysMode: 'pod' };
132
+ writeJsonAtomic(file, updated, { mode: 0o600 });
133
+ return updated;
134
+ } catch { /* first run for this identity */ }
72
135
  const rec = {
73
136
  webId,
74
137
  remotePod: podBase.endsWith('/') ? podBase : podBase + '/',
75
138
  root: 'activitypods-js/',
139
+ keysMode: 'pod',
76
140
  };
77
141
  writeJsonAtomic(file, rec, { mode: 0o600 });
78
142
  return rec;
79
143
  }
80
144
 
145
+ /**
146
+ * Move an identity's signing key off the host's disk and into its pod.
147
+ *
148
+ * Only after the pod has it: the private key is the one thing here that
149
+ * nothing can rebuild, and remote servers have its public half cached, so a
150
+ * copy lost between the two places is an identity that can never sign again
151
+ * under the name it already published.
152
+ */
153
+ async function keyIntoPod(agent, home, log) {
154
+ const local = path.join(home, 'keys.json');
155
+ if (!fs.existsSync(local)) return;
156
+ if (agent.store.read('keys.json', null)) {
157
+ fs.rmSync(local, { force: true });
158
+ log('this identity signs with the key in its pod; removed the copy on this host');
159
+ return;
160
+ }
161
+ let rec;
162
+ try {
163
+ rec = JSON.parse(fs.readFileSync(local, 'utf8'));
164
+ } catch (e) {
165
+ log(`the local signing key could not be read (${e.message}) — leaving it where it is`);
166
+ return;
167
+ }
168
+ agent.store.write('keys.json', rec);
169
+ if (!await agent.store.commit()) {
170
+ log('could not write the signing key to the pod — it stays on this host for now');
171
+ return;
172
+ }
173
+ fs.rmSync(local, { force: true });
174
+ log('signing key moved into the pod');
175
+ }
176
+
177
+ /**
178
+ * Move an identity's credentials for accounts it holds on OTHER servers off
179
+ * the host's disk and into its pod. A token here is full access to somebody's
180
+ * account on a server this project does not run, so the host's copy goes only
181
+ * after the pod has taken it.
182
+ */
183
+ async function connectionsIntoPod(agent, home, log) {
184
+ const moves = [
185
+ [fileVault(path.join(home, 'fediaccts')), podVault(agent.store, `${CONNECTION_PREFIX}fedi-`)],
186
+ [fileVault(path.join(home, 'fediaccts', '_apps')), podVault(agent.store, `${CONNECTION_PREFIX}fediapp-`)],
187
+ [fileVault(home), podVault(agent.store, CONNECTION_PREFIX)],
188
+ ];
189
+ // The last pair is the whole home directory, where only the Bluesky
190
+ // credential is ours to move.
191
+ for (const [from, to] of moves.slice(0, 2)) await moveVault(from, to, log);
192
+ const bluesky = moves[2][0].read('atproto');
193
+ if (bluesky && !moves[2][1].read('bluesky')) {
194
+ moves[2][1].write('bluesky', bluesky);
195
+ if (await moves[2][1].commit()) {
196
+ await moves[2][0].remove('atproto');
197
+ log('the Bluesky connection moved into the pod');
198
+ } else {
199
+ log('could not write the Bluesky connection to the pod — it stays on this host for now');
200
+ }
201
+ }
202
+ }
203
+
81
204
  /**
82
205
  * Bring one identity up inside the server.
83
206
  *
@@ -151,6 +274,10 @@ export async function startEmbeddedAgent({
151
274
  agent.stateLoaded = true;
152
275
  }
153
276
 
277
+ // Before connect(), which is what looks the key and the connections up.
278
+ await keyIntoPod(agent, home, log);
279
+ await connectionsIntoPod(agent, home, log);
280
+
154
281
  await agent.connect();
155
282
 
156
283
  // The client surfaces, on the pod's own origin: the Mastodon API a phone app
@@ -0,0 +1,9 @@
1
+ // links.mjs — reading RFC 8288 Link headers.
2
+ //
3
+ // The implementation moved into the pod library (lib/pod/links.mjs), which has
4
+ // to carry it: that library runs unmodified in a service worker and may not
5
+ // import anything above its own directory. This path stays so the dozen
6
+ // importers around the project do not move, and so there is still one obvious
7
+ // place to look for "how does this read a Link header".
8
+
9
+ export { linkTargets, REL } from '../pod/links.mjs';
@@ -16,7 +16,7 @@ import { fileURLToPath } from 'node:url';
16
16
  let version = '0';
17
17
  try {
18
18
  ({ version } = JSON.parse(fs.readFileSync(
19
- path.join(path.dirname(fileURLToPath(import.meta.url)), '../package.json'), 'utf8')));
19
+ path.join(path.dirname(fileURLToPath(import.meta.url)), '../../package.json'), 'utf8')));
20
20
  } catch {
21
21
  try {
22
22
  ({ version } = JSON.parse(fs.readFileSync(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fedipod-server",
3
- "version": "0.10.0",
3
+ "version": "0.12.1",
4
4
  "description": "The FediPod Server: a full ActivityPub server as a Community Solid Server component.",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
package/run-agent.mjs CHANGED
@@ -28,29 +28,29 @@ import os from 'node:os';
28
28
  import path from 'node:path';
29
29
  import { pathToFileURL } from 'node:url';
30
30
 
31
- import { PodStore } from './lib/store.mjs';
32
- import { apRoot, rootOf, writeJsonAtomic } from './lib/home.mjs';
33
- import { ensureTrustedTls } from './lib/certs.mjs';
34
- import { storageFor } from './lib/storage.mjs';
35
- import { resolveKeys } from './lib/keys.mjs';
36
- import { RemotePod } from './lib/remote.mjs';
37
- import { PodRdf } from './lib/podrdf.mjs';
38
- import { Deliverer } from './lib/deliver.mjs';
39
- import { Publisher } from './lib/publisher.mjs';
40
- import { Intake } from './lib/intake.mjs';
41
- import { TagFeed } from './lib/tagfeed.mjs';
42
- import { ImportWorker } from './lib/import.mjs';
43
- import { Atproto } from './lib/atproto.mjs';
44
- import { FediAccounts } from './lib/fediacct.mjs';
45
- import { AcctFeed } from './lib/acctfeed.mjs';
46
- import { BskyFeed } from './lib/bskyfeed.mjs';
47
- import { BskyGroup } from './lib/bskygroup.mjs';
48
- import { Lease } from './lib/lease.mjs';
49
- import { startAdmin } from './lib/admin.mjs';
50
- import { exposureProblem, hostLabel } from './lib/guard.mjs';
51
- import { pendingSteps } from './lib/migrate.mjs';
52
- import { apUrls, assertionKeyId , publicHandle } from './lib/wire.mjs';
53
- import { followActor, unfollowActor, resolveHandle } from './lib/social.mjs';
31
+ import { PodStore } from './lib/core/store.mjs';
32
+ import { apRoot, rootOf, writeJsonAtomic } from './lib/device/home.mjs';
33
+ import { ensureTrustedTls } from './lib/device/certs.mjs';
34
+ import { storageFor } from './lib/core/storage.mjs';
35
+ import { resolveKeys } from './lib/core/keys.mjs';
36
+ import { RemotePod } from './lib/device/remote.mjs';
37
+ import { Deliverer } from './lib/core/deliver.mjs';
38
+ import { Publisher } from './lib/core/publisher/index.mjs';
39
+ import { Intake } from './lib/core/intake/index.mjs';
40
+ import { TagFeed } from './lib/connections/tagfeed.mjs';
41
+ import { ImportWorker } from './lib/connections/import.mjs';
42
+ import { Atproto } from './lib/connections/atproto.mjs';
43
+ import { FediAccounts } from './lib/connections/fediacct.mjs';
44
+ import { vaultsFor } from './lib/connections/vault.mjs';
45
+ import { AcctFeed } from './lib/connections/acctfeed.mjs';
46
+ import { BskyFeed } from './lib/connections/bskyfeed.mjs';
47
+ import { BskyGroup } from './lib/connections/bskygroup.mjs';
48
+ import { Lease } from './lib/core/lease.mjs';
49
+ import { startAdmin } from './lib/device/admin/index.mjs';
50
+ import { exposureProblem, hostLabel } from './lib/shared/guard.mjs';
51
+ import { pendingSteps } from './lib/device/migrate.mjs';
52
+ import { apUrls, assertionKeyId , publicHandle } from './lib/core/wire.mjs';
53
+ import { followActor, unfollowActor, resolveHandle } from './lib/core/social.mjs';
54
54
 
55
55
  export class Agent {
56
56
  constructor({ home, log }) {
@@ -69,15 +69,14 @@ export class Agent {
69
69
 
70
70
  // Where the private half lives. `privateRoot` in the credential file names a
71
71
  // container — by default a plain directory beside the credential, and a pod
72
- // on this machine if you move it there — under which the same two trees are
73
- // laid out as on the pod. Absent means on the pod, exactly as before, so an
72
+ // on this machine if you move it there. Absent means on the pod, so an
74
73
  // existing install is untouched.
75
74
  privateUrls(cred, urls = this.urls) {
76
75
  if (!cred.privateRoot) {
77
- return { state: urls.state, fediverse: urls.fediverse, archive: urls.home + 'inbox-archive/', elsewhere: false };
76
+ return { state: urls.state, archive: urls.home + 'inbox-archive/', elsewhere: false };
78
77
  }
79
78
  const base = cred.privateRoot.endsWith('/') ? cred.privateRoot : cred.privateRoot + '/';
80
- return { state: base + 'ap-state/', fediverse: base + 'fediverse/', archive: base + 'inbox-archive/', elsewhere: true };
79
+ return { state: base + 'ap-state/', archive: base + 'inbox-archive/', elsewhere: true };
81
80
  }
82
81
 
83
82
  // A container to keep the private half in. On the pod it is reached with the
@@ -146,14 +145,9 @@ export class Agent {
146
145
  await this.remote.setAcl(this.urls.home, []);
147
146
  await this.remote.setAcl(this.urls.state, []);
148
147
  if (priv.elsewhere) {
149
- for (const s of [stateStore, this.privateStorage(cred, 'fediverse')]) {
150
- const r = await s.write('.keep', '{"keep":true}\n', 'application/json');
151
- if (!r.ok) throw new Error(`private store ${s.base}.keep → ${r.why}`);
152
- }
148
+ const r = await stateStore.write('.keep', '{"keep":true}\n', 'application/json');
149
+ if (!r.ok) throw new Error(`private store ${stateStore.base}.keep → ${r.why}`);
153
150
  this.log(`private state lives at ${cred.privateRoot} (${stateStore.kind})`);
154
- } else {
155
- await this.remote.putJson(this.urls.fediverse + '.keep', { keep: true }, 'application/json');
156
- await this.remote.setAcl(this.urls.fediverse, []);
157
151
  }
158
152
  this.store.attach(stateStore);
159
153
  // Re-running setup must not destroy state set afterwards (the UI
@@ -314,7 +308,6 @@ export class Agent {
314
308
  const { rotateKeyOnce, ...rest } = cred;
315
309
  writeJsonAtomic(path.join(this.home, 'credential.json'), rest);
316
310
  }
317
- this.local = new PodRdf({ storage: this.privateStorage(cred, 'fediverse') });
318
311
  // connect() can run more than once — connectWithRetry retries after a throw,
319
312
  // and the CLI connects for real after its confirmation. Deliverer arms a
320
313
  // 60s queue timer in its constructor and Intake owns a poll timer and a
@@ -332,13 +325,10 @@ export class Agent {
332
325
  log: this.log, passive: this.viewer,
333
326
  });
334
327
  this.publisher = new Publisher({
335
- config, remote: this.remote, local: this.local, store: this.store,
328
+ config, remote: this.remote, store: this.store,
336
329
  deliverer: this.deliverer, publicKeyPem: keys.rsaPublicPem,
337
330
  assertionKey: keys.edPublicMultibase, log: this.log,
338
331
  resolveMention: (h) => resolveHandle(this, h),
339
- // Whether the fediverse tree is on the pod at all, so the ACL check does
340
- // not probe for something the default layout keeps on local disk.
341
- privateOnPod: !cred.privateRoot,
342
332
  // Inside a pod server the client surface answers on the pod's own
343
333
  // origin, so it can be advertised. Standalone it is on loopback, and
344
334
  // naming it in a world-readable actor would send clients nowhere.
@@ -347,7 +337,7 @@ export class Agent {
347
337
  // Intake is constructed even for viewers — its signed fetchAP powers
348
338
  // search/deref; start() (draining) is active-only.
349
339
  this.intake = new Intake({
350
- config, urls: this.urls, remote: this.remote, local: this.local, store: this.store,
340
+ config, urls: this.urls, remote: this.remote, store: this.store,
351
341
  deliverer: this.deliverer, publisher: this.publisher, log: this.log, lease: this.lease,
352
342
  archive: this.privateStorage(cred, 'archive'),
353
343
  push: !this.embedded, pollSeconds: this.pollSeconds || null,
@@ -355,13 +345,20 @@ export class Agent {
355
345
  // The CSV-import worker: paced, resumable, armed only while active.
356
346
  this.importer?.stop();
357
347
  this.importer = new ImportWorker({ agent: this, log: this.log });
348
+ // Where the credentials for accounts held on OTHER servers are kept.
349
+ const vaults = vaultsFor({ embedded: this.embedded, home: this.home, store: this.store });
358
350
  // The Bluesky connection, when one exists. Stamped to this actor; a
359
351
  // credential connected for another identity is treated as absent.
360
- this.atproto = new Atproto({ localDir: this.home, actorId: this.urls.actor, log: this.log });
352
+ this.atproto = new Atproto({
353
+ localDir: this.home, actorId: this.urls.actor, log: this.log, vault: vaults.bluesky,
354
+ });
361
355
  this.publisher.atproto = this.atproto;
362
356
  // Fediverse accounts the owner holds elsewhere. Same custody rules as the
363
357
  // Bluesky credential, and the same absence when stamped for someone else.
364
- this.fediaccts = new FediAccounts({ localDir: this.home, actorId: this.urls.actor, log: this.log });
358
+ this.fediaccts = new FediAccounts({
359
+ localDir: this.home, actorId: this.urls.actor, log: this.log,
360
+ vault: vaults.fedi, apps: vaults.apps,
361
+ });
365
362
  this.acctfeed?.stop();
366
363
  this.acctfeed = null;
367
364
  this.bskyfeed?.stop();
@@ -581,7 +578,7 @@ export class Agent {
581
578
  this.ensureActorPublished()
582
579
  .catch(e => this.log(`actor check failed: ${e.message}`));
583
580
  }
584
- this.backfillStatuses().catch(e => this.log(`statuses backfill failed: ${e.message}`));
581
+ this.seedFollowNotifications().catch(e => this.log(`notification seeding failed: ${e.message}`));
585
582
  }
586
583
 
587
584
  // Re-read state before acting on what we hold. A viewer's cache is kept
@@ -695,27 +692,13 @@ export class Agent {
695
692
  return true;
696
693
  }
697
694
 
698
- // The statuses index is an operational mirror of the pod's RDF — rebuild it
699
- // from /fediverse/ when absent (fresh state, or state loss).
700
- async backfillStatuses() {
701
- if (!this.store.has('notifications.json')) {
702
- for (const f of this.store.getContacts().followers) {
703
- this.store.addNotification({ type: 'follow', actor: f.actor });
704
- }
705
- }
706
- if (this.store.has('statuses.json')) return;
707
- const entries = [];
708
- for (const [container, kind] of [['timeline', 'timeline'], ['posts', 'post']]) {
709
- for (const url of await this.local.listNotes(container)) {
710
- try {
711
- const n = await this.local.readNote(url);
712
- if (n.noteId) entries.push({ ...n, kind, slug: url.split('/').pop() });
713
- } catch (e) { this.log(`backfill: skipped ${url}: ${e.message}`); }
714
- }
695
+ // A first run inherits followers without ever having seen them arrive, so
696
+ // the notifications list starts empty where it should start full.
697
+ async seedFollowNotifications() {
698
+ if (this.store.has('notifications.json')) return;
699
+ for (const f of this.store.getContacts().followers) {
700
+ this.store.addNotification({ type: 'follow', actor: f.actor });
715
701
  }
716
- entries.sort((a, b) => String(b.published || '').localeCompare(String(a.published || '')));
717
- this.store.write('statuses.json', entries.slice(0, 1000));
718
- this.log(`backfilled ${entries.length} statuses from pod RDF`);
719
702
  }
720
703
  }
721
704
 
@@ -798,7 +781,7 @@ export async function startAgent({
798
781
  // Is a newer FediPod published? Once at boot and daily after; the answer
799
782
  // rides /status and the record page offers the update.
800
783
  const updateTick = async () => {
801
- const { checkLatest } = await import('./lib/update.mjs');
784
+ const { checkLatest } = await import('./lib/device/update.mjs');
802
785
  const u = await checkLatest();
803
786
  if (u) {
804
787
  agent.updateInfo = u;
@@ -0,0 +1,145 @@
1
+ // actors.js — the other actors on this machine: the picker, going to one,
2
+ // starting a stopped one, and the form for a new one.
3
+
4
+ // Somewhere to go, and nothing else. A stopped actor is left out — the link
5
+ // would land on nothing. The one you are on keeps its place in the row but
6
+ // offers only `app`, because its admin page is the page you are reading.
7
+
8
+ let actors = [];
9
+
10
+ async function renderOthers() {
11
+ const { json } = await api('/profiles');
12
+ actors = json?.identities || [];
13
+ const sel = $('actor-pick');
14
+ sel.textContent = '';
15
+ // The fediverse address, which is what the actor IS to everyone else, and the
16
+ // only form that stays distinct: two identities can share a local handle, but
17
+ // never a handle AND a pod. A stopped one has no address to report — nothing
18
+ // answered — so it falls back to the name it is filed under.
19
+ const label = (r) => (r.address || r.handle || r.name)
20
+ + (r.mode && r.mode !== 'active' ? ` (${r.mode})` : '') + (r.mode ? '' : ' (stopped)');
21
+ const seen = {};
22
+ for (const r of actors) seen[label(r)] = (seen[label(r)] || 0) + 1;
23
+
24
+ for (const [i, r] of actors.entries()) {
25
+ const o = document.createElement('option');
26
+ o.value = String(i);
27
+ o.selected = !!r.current; // it opens showing where you already are
28
+ o.textContent = label(r) + (seen[label(r)] > 1 && r.port ? ` :${r.port}` : '');
29
+ sel.appendChild(o);
30
+ }
31
+ // The way to a NEW actor rides the same dropdown as the existing ones.
32
+ const add = document.createElement('option');
33
+ add.value = '__add';
34
+ add.textContent = '+ add a new account…';
35
+ sel.appendChild(add);
36
+ }
37
+
38
+ // Going to an actor commits a navigation and, for a stopped one, a POST that
39
+ // boots its pod — so it must run once per choice. Pressing Enter on the select
40
+ // fires BOTH a keydown (handled below) and a native `change`, so without a
41
+ // guard a keyboard commit would start the actor twice.
42
+ let goingToActor = false;
43
+ const goToActor = async () => {
44
+ if (goingToActor) return;
45
+ if ($('actor-pick').value === '__add') { renderOthers(); openNewActor(); return; }
46
+ const r = actors[Number($('actor-pick').value)];
47
+ if (!r || r.current) return;
48
+ goingToActor = true;
49
+ try {
50
+ if (r.mode) { location.href = r.admin; return; }
51
+ $('actor-pick').disabled = true;
52
+ say(`starting ${r.name}`);
53
+ const started = await write('/start-actor', { name: r.name }, `${r.name} is up`);
54
+ $('actor-pick').disabled = false;
55
+ if (started?.url) location.href = `${started.url}admin/`;
56
+ else renderOthers(); // put the picker back on the current actor
57
+ } finally {
58
+ goingToActor = false;
59
+ }
60
+ };
61
+ // Choosing one goes to its record, as it always has.
62
+ //
63
+ // Arrowing is the one case that must not: on a closed select every Arrow
64
+ // keypress fires `change`, so a keyboard user browsing the list would be
65
+ // carried off to the first actor they passed — and for a stopped one that
66
+ // boots its pod. An arrow key arms a flag and the change it causes is ignored;
67
+ // Enter commits. A mouse never sets the flag, so clicking behaves as before.
68
+ let arrowing = false;
69
+ $('actor-pick').addEventListener('keydown', (ev) => {
70
+ if (['ArrowUp', 'ArrowDown', 'Home', 'End', 'PageUp', 'PageDown'].includes(ev.key)) arrowing = true;
71
+ if (ev.key === 'Enter') { arrowing = false; goToActor(); }
72
+ });
73
+ $('actor-pick').addEventListener('pointerdown', () => { arrowing = false; });
74
+ $('actor-pick').addEventListener('change', () => {
75
+ if (!arrowing) goToActor();
76
+ arrowing = false;
77
+ });
78
+
79
+ // Setting up a new actor asks for exactly what this page cannot already tell
80
+ // it: the pod and the account behind it, and its permanent name. Display name,
81
+ // bio and pictures are the client's job now — Phanpy's profile editor writes
82
+ // them through /api/v1/accounts/update_credentials. Where the private data goes
83
+ // is not a question either: it lands beside the credential, and `state --to`
84
+ // moves it afterwards.
85
+
86
+ const picked = (name) => document.querySelector(`input[name=${name}]:checked`)?.value;
87
+
88
+ function newActorRows() {
89
+ const mode = picked('newMode');
90
+ $('new-row-podname').hidden = mode !== 'new';
91
+ $('new-row-pod').hidden = mode !== 'existing';
92
+ }
93
+ for (const el of document.querySelectorAll('input[name=newMode]')) {
94
+ el.addEventListener('change', newActorRows);
95
+ }
96
+
97
+ // While the form is up the page IS the new actor's setup — so the record of
98
+ // the actor you came from goes away, list included. Which panes were showing
99
+ // is remembered rather than recomputed: whether Group and Inbox belong is a
100
+ // decision render() already made.
101
+ const RECORD_PANES = ['pane-identity', 'pane-group', 'pane-inbox', 'rail'];
102
+ let putBack = [];
103
+
104
+ function showNewActor(on) {
105
+ if (on) closePanels(); // nothing of the old actor left open behind it
106
+ document.body.classList.toggle('adding', on); // see body.adding in the CSS
107
+ $('new-actor-form').hidden = !on;
108
+ if (on) {
109
+ putBack = RECORD_PANES.filter(id => !$(id).hidden);
110
+ for (const id of putBack) $(id).hidden = true;
111
+ } else {
112
+ for (const id of putBack) $(id).hidden = false;
113
+ putBack = [];
114
+ }
115
+ }
116
+
117
+ // `add a new account` is the last item of the Local Actors dropdown; choosing
118
+ // it opens the form in place. Arriving with ?new does the same.
119
+ function openNewActor() {
120
+ showNewActor(true);
121
+ newActorRows();
122
+ $('new-handle').focus();
123
+ }
124
+ $('new-actor-cancel').addEventListener('click', () => { showNewActor(false); say('nothing changed'); });
125
+
126
+ $('new-actor-form').addEventListener('submit', async (ev) => {
127
+ ev.preventDefault();
128
+ const handle = $('new-handle').value.trim();
129
+ const answers = {
130
+ handle,
131
+ kind: picked('newKind'),
132
+ mode: picked('newMode'),
133
+ issuer: $('new-issuer').value.trim(),
134
+ email: $('new-email').value.trim(),
135
+ password: $('new-password').value,
136
+ };
137
+ if (answers.mode === 'new') answers.podName = $('new-podname').value.trim() || handle;
138
+ else answers.pod = $('new-pod').value.trim();
139
+ $('new-actor-go').disabled = true;
140
+ say(`setting up ${handle || 'the new actor'} — this takes a while`);
141
+ const r = await write('/new-actor', answers, `setting up ${handle}`);
142
+ $('new-actor-go').disabled = false;
143
+ if (r?.url) location.href = r.url; // its own page, where the progress is
144
+ });
145
+
package/web/admin/bar.css CHANGED
@@ -4,18 +4,9 @@
4
4
  page's left edge and the buttons over its right rather than out at the window
5
5
  borders. `width: 100%` with `box-sizing: border-box` keeps the padding inside
6
6
  that 960 instead of adding to it, which is what makes the two margins equal. */
7
- /* Phanpy's own colours, read off the running client. --surface is the one you
8
- see behind Phanpy — its --bg-faded-color — and that is the PAGE. --raised is
9
- Phanpy's --bg-color, and that is the bar sitting on top of it. Declared here
10
- because bar.css is on all three pages, so they cannot drift. */
11
- :root { --surface: #f0f2f5; --raised: #fff;
12
- --ink: #1b1c1e; --field-bg: #ffffff; --field-bg-hover: #f7f4ee; --field-edge: #767676;
13
- --chevron: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='6'><path d='M1 1l4 4 4-4' fill='none' stroke='%23444444' stroke-width='1.6' stroke-linecap='round'/></svg>"); }
14
- @media (prefers-color-scheme: dark) {
15
- :root { --surface: #18191a; --raised: #242526;
16
- --ink: #e8eaed; --field-bg: #383d44; --field-bg-hover: #40464e; --field-edge: #9aa0a6;
17
- --chevron: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='6'><path d='M1 1l4 4 4-4' fill='none' stroke='%23dddddd' stroke-width='1.6' stroke-linecap='round'/></svg>"); }
18
- }
7
+ /* Colours come from tokens.css (the front's palette), linked by every page
8
+ before this file, so the bar matches the front and the record rather than
9
+ Phanpy: --surface is the page ground, --raised the bar band on top of it. */
19
10
  body { background: var(--surface); }
20
11
  /* The BAND spans the window; its CONTENTS stay in the 960 column. Constraining
21
12
  the element itself left the colour as a stripe with bare page either side.
@@ -51,8 +42,13 @@ body { background: var(--surface); }
51
42
  #bar select:hover { background-color: var(--field-bg-hover); }
52
43
  /* The app title, in px rather than rem: it is chrome, the same on every page,
53
44
  not something that should move when a page changes its base size. */
54
- #bar .name { flex: 1 1 auto; font-weight: 600; font-size: 24px; color: var(--heading);
45
+ #bar .name { flex: 1 1 auto; font-weight: 600; font-size: 40px; color: var(--heading);
55
46
  margin: 0; line-height: 1.1; }
47
+ /* The browser build wraps the brand text in a link (to /?add) — the general
48
+ #bar a rule below would otherwise pin it back down to nav-link size and
49
+ colour, since a rule on the anchor itself always wins over inheriting the
50
+ heading's size. */
51
+ #bar .name a { font: inherit; color: inherit; text-decoration: none; }
56
52
  /* Plain text, not chrome: three destinations in a row read better without a
57
53
  box round each. */
58
54
  #bar button, #bar a { font: inherit; font-size: .95rem; padding: .15rem .2rem;
package/web/admin/bar.js CHANGED
@@ -17,7 +17,7 @@
17
17
  // open windows apart without putting the handle in the page twice.
18
18
  // /status, not /config: an agent that is not set up yet answers this one and
19
19
  // 409s the other, which logged a failed request on every setup page.
20
- fetch(location.pathname.replace(/\/admin\/.*$/u, '') + '/status').then(r => (r.ok ? r.json() : null)).then((s) => {
20
+ fetch(location.pathname.replace(/\/admin\/.*$/u, '') + '/status', { headers: { 'x-fedipod-page': '1' } }).then(r => (r.ok ? r.json() : null)).then((s) => {
21
21
  if (s?.handle) document.title = `FediPod — ${s.handle}`;
22
22
  // The full fediverse handle, centred in the bar on every page.
23
23
  const el = document.getElementById('bar-handle');
@@ -32,7 +32,7 @@
32
32
  const pick = document.getElementById('actor-pick');
33
33
  if (pick && !document.getElementById('new-actor-form')) {
34
34
  const base = location.pathname.replace(/\/admin\/.*$/u, '');
35
- fetch(base + '/profiles').then(r => (r.ok ? r.json() : null)).then((j) => {
35
+ fetch(base + '/profiles', { headers: { 'x-fedipod-page': '1' } }).then(r => (r.ok ? r.json() : null)).then((j) => {
36
36
  const actors = j?.identities || [];
37
37
  pick.textContent = '';
38
38
  const label = (r) => (r.address || r.handle || r.name)
@@ -27,7 +27,7 @@
27
27
  // "which account logged in here" is a different question, and it is the one
28
28
  // that goes wrong when the two origins get mixed.
29
29
  const base = location.pathname.replace(/\/admin\/.*$/u, '');
30
- const status = await fetch(base + '/status').then(r => (r.ok ? r.json() : null)).catch(() => null);
30
+ const status = await fetch(base + '/status', { headers: { 'x-fedipod-page': '1' } }).then(r => (r.ok ? r.json() : null)).catch(() => null);
31
31
  if (!status?.actor) return; // not set up yet; setup owns that
32
32
 
33
33
  // A deep link into the client — the record links an actor's own page this way