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
@@ -4,30 +4,14 @@
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
6
  <title>FediPod — record</title>
7
+ <link rel="stylesheet" href="tokens.css">
7
8
  <style>
8
- /* 20px on the root, not on body, so every rem below scales with it.
9
- Two accents, kept apart on purpose: --link is anything you can act on,
10
- --heading is structure. Using one colour for both made the headings
11
- look clickable. */
12
- /* One control chassis, one selector per element kind so nothing can fall
13
- through unstyled: every control sits in the same light well; a select
14
- carries a drawn chevron, a button carries the action-blue text. */
15
- :root { color-scheme: light dark; font-size: 112.5%;
16
- --bar-col: 1280px; /* the bar centres on the same column */
17
- --link: #1a4f8a; --heading: #5b3a8e;
18
- --ink: #1b1c1e; --muted: #4a4e53;
19
- --btn-bg: #f4efe6; --btn-bg-hover: #ece3d2; --btn-bg-active: #e2d5bd; --btn-edge: #8a7a61;
20
- --field-bg: #ffffff; --field-bg-hover: #f7f4ee; --field-edge: #767676;
21
- --danger: #b00020; --ring: var(--link);
22
- --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>"); }
23
- @media (prefers-color-scheme: dark) {
24
- :root { --link: #7fb3e8; --heading: #b9a3e3;
25
- --ink: #e8eaed; --muted: #b9bdc2;
26
- --btn-bg: #35302a; --btn-bg-hover: #423b31; --btn-bg-active: #4d4436; --btn-edge: #9a8d7a;
27
- --field-bg: #383d44; --field-bg-hover: #40464e; --field-edge: #9aa0a6;
28
- --danger: #ff8a8a;
29
- --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>"); }
30
- }
9
+ /* Colours are the shared tokens (tokens.css, the front's palette), so the
10
+ record, the front and the bar are one visual system; only the layout root —
11
+ the base scale and the bar's column stays here. Controls share one look: a
12
+ field is a light well with a drawn chevron, a button is the front's filled
13
+ green. */
14
+ :root { color-scheme: light dark; font-size: 125%; --bar-col: 1280px; }
31
15
  /* The page itself never scrolls: the body is exactly the window; the content
32
16
  column beside the rail is the scroll container. */
33
17
  html { height: 100%; }
@@ -95,7 +79,7 @@ h3 { margin: 1.25rem 0 .35rem; }
95
79
  #moderation label { display: inline; margin: 0 .3rem 0 .5rem; font-weight: normal; color: var(--muted); }
96
80
  p { margin: .5rem 0; }
97
81
  .sub { color: var(--muted); margin-top: 0; }
98
- fieldset { border: 1px solid #bbb; border-radius: .4rem; margin: 1rem 0; padding: .75rem 1rem 1rem; }
82
+ fieldset { border: 1px solid var(--line); border-radius: .4rem; margin: 1rem 0; padding: .75rem 1rem 1rem; }
99
83
  legend { padding: 0 .3rem; font-weight: 600; color: var(--heading); }
100
84
  label { display: block; margin: .75rem 0 .2rem; }
101
85
  .hint { color: var(--muted); font-size: 1rem; margin: .15rem 0 0; }
@@ -114,12 +98,12 @@ select { font: inherit; appearance: none; max-width: 100%; min-width: 0;
114
98
  background-repeat: no-repeat; background-position: right .55rem center;
115
99
  background-size: .6rem auto; }
116
100
  select:hover { background-color: var(--field-bg-hover); }
117
- /* ACTIONS: identical to the settings same well, same border, same ink,
118
- Retire included. One control look for the whole page. */
119
- button { font: inherit; padding: .45rem 1rem; border-radius: .3rem; border: 1px solid var(--field-edge);
120
- background: var(--field-bg); color: var(--ink); cursor: pointer; }
121
- button:hover { background: var(--field-bg-hover); }
122
- button:active { background: var(--field-bg-hover); }
101
+ /* ACTIONS: the front's filled-green button, one look for the whole page,
102
+ Retire included the typed-handle confirm, not a colour, is what guards the
103
+ destructive ones. */
104
+ button { font: inherit; padding: .45rem 1rem; border-radius: .3rem; border: 1px solid var(--btn);
105
+ background: var(--btn); color: var(--btn-text); cursor: pointer; }
106
+ button:hover, button:active { background: var(--btn-hover); border-color: var(--btn-hover); }
123
107
  button.primary { font-weight: 600; }
124
108
  button:disabled { opacity: .55; cursor: default; }
125
109
  /* Small buttons that sit in a line of text. */
@@ -139,12 +123,29 @@ dt.under { padding-left: 1.25rem; }
139
123
  dd { margin: 0; min-width: 0; overflow-wrap: anywhere; }
140
124
  .rows { list-style: none; padding: 0; margin: .3rem 0; }
141
125
  .rows li { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center;
142
- padding: .4rem 0; border-top: 1px solid #ddd; }
126
+ padding: .4rem 0; border-top: 1px solid var(--line); }
143
127
  .rows li span { flex: 1 1 18rem; overflow-wrap: anywhere; }
144
128
  .muted { color: var(--muted); }
145
129
  /* The actors dropdown rides the bar, at the bar's own text scale. */
146
130
  #bar select { font-size: .95rem; }
147
- #bsky-ctl { display: inline-flex; gap: .6rem; align-items: baseline; margin-left: .6rem; }
131
+ /* The per-account Options disclosure: only where storage is not a real choice
132
+ (the Node agent's Bluesky, cross-post instead) — a summary opening
133
+ Show/Hide in Feed, Cross-post On/Off, Connected/Disconnect. Where storage
134
+ IS a choice, two dropdowns sit in the row directly instead (admin.js
135
+ connectionControls). */
136
+ details.opts { display: inline-block; }
137
+ details.opts > summary { cursor: pointer; color: var(--link); list-style: none; }
138
+ details.opts > summary::-webkit-details-marker { display: none; }
139
+ details.opts > summary::after { content: ' \25be'; }
140
+ details.opts[open] > summary::after { content: ' \25b4'; }
141
+ details.opts > div { margin: .4rem 0 .2rem; padding: .5rem .6rem; display: flex; flex-direction: column;
142
+ gap: .4rem; align-items: stretch; border: 1px solid var(--line); border-radius: .3rem;
143
+ background: var(--raised); min-width: 11rem; }
144
+ details.opts > div select { width: 100%; }
145
+ .opt-hint { color: var(--muted); font-size: .9rem; margin: -.1rem 0 .1rem; }
146
+ /* The connection and storage dropdowns that sit in the row itself (admin.js
147
+ connectionControls) — extra room from the account name before them. */
148
+ .conn-controls { display: inline-flex; align-items: center; gap: .4rem; margin-left: 1rem; }
148
149
  /* The alias row inside the Transfer-an-account-here panel. */
149
150
  #alias-ctl { display: flex; gap: .5rem; align-items: center; margin-top: .3rem; }
150
151
  #alias-ctl input { flex: 1 1 12rem; width: auto; }
@@ -155,15 +156,15 @@ dd { margin: 0; min-width: 0; overflow-wrap: anywhere; }
155
156
  below the control. */
156
157
  .choice { display: block; margin: .35rem 0; font-weight: normal; }
157
158
  .choice input { width: auto; margin-right: .4rem; }
158
- .warn { border-left: .25rem solid #c60; padding-left: .75rem; }
159
+ .warn { border-left: .25rem solid var(--warn); padding-left: .75rem; }
159
160
  .err { border-left: .25rem solid var(--danger); padding-left: .75rem; color: var(--danger); }
160
- .ok { border-left: .25rem solid #2a7; padding-left: .75rem; }
161
+ .ok { border-left: .25rem solid var(--ok); padding-left: .75rem; }
161
162
  pre { overflow-x: auto; background: #0001; padding: .6rem; border-radius: .3rem; font-size: .9rem; }
162
163
  @media (prefers-color-scheme: dark) {
163
164
  fieldset { border-color: #555; } .rows li { border-color: #444; }
164
165
  }
165
166
  </style>
166
- <link rel="stylesheet" href="bar.css">
167
+ <link rel="stylesheet" href="bar.css?v=3">
167
168
  <link rel="stylesheet" href="window.css">
168
169
  </head>
169
170
  <body>
@@ -309,13 +310,6 @@ pre { overflow-x: auto; background: #0001; padding: .6rem; border-radius: .3rem;
309
310
  <span id="ident-ctl" hidden>
310
311
  <button type="button" id="ident-open" class="inline" title=" Connect an account you hold on another network, so its timeline joins the one you read here">Connect an identity</button>
311
312
  </span>
312
- <span id="bsky-ctl" hidden>
313
- <select id="bsky-crosspost" aria-label="Bluesky crossposting" title=" Whether your public posts also go to the Bluesky account">
314
- <option value="on">crosspost on</option>
315
- <option value="off">crosspost off</option>
316
- </select>
317
- <button type="button" id="bsky-disconnect" class="inline danger" title=" Forget the Bluesky session and remove the stored credential">Disconnect</button>
318
- </span>
319
313
  </section>
320
314
 
321
315
  <span id="gateway-ctl" hidden>
@@ -470,7 +464,7 @@ pre { overflow-x: auto; background: #0001; padding: .6rem; border-radius: .3rem;
470
464
  Keep my pod-based handle: <strong id="gw-pod-preview"></strong><br>
471
465
  <span class="hint">Your address stays tied to your own pod — you can drop the
472
466
  gateway any time and keep everything.</span></label>
473
- <label class="choice"><input type="radio" name="gwShape" value="front">
467
+ <label class="choice" id="gw-shape-front"><input type="radio" name="gwShape" value="front">
474
468
  Create a handle at the gateway:
475
469
  @<input type="text" id="gw-name" placeholder="name" autocomplete="off"
476
470
  autocapitalize="off" spellcheck="false">@<span id="gw-front-host">fedipod.net</span><br>
@@ -498,6 +492,15 @@ pre { overflow-x: auto; background: #0001; padding: .6rem; border-radius: .3rem;
498
492
  <div id="warn-rotate-key" class="warn" hidden>
499
493
  <p>A new keypair replaces the one in this home and the actor is republished so other
500
494
  servers learn it. Any other device still holding the old key stops being able to sign.</p>
495
+ <!-- Browser build only: there the key on the pod is locked under the account
496
+ password, so a new one has to be locked too. The Node agent keeps its key
497
+ on disk and needs nothing here — stage-site.mjs reveals this row. -->
498
+ <div id="rotate-pw-row" hidden>
499
+ <label for="rotate-password">Your account password</label>
500
+ <input type="password" id="rotate-password" autocomplete="current-password">
501
+ <p class="hint">Your signing key is kept on your pod locked under this, so the
502
+ replacement has to be locked under it too. It is not sent anywhere else.</p>
503
+ </div>
501
504
  </div>
502
505
  <div id="warn-retire" class="warn" hidden>
503
506
  <p><strong>This cannot be undone.</strong> A Delete goes to every follower's inbox telling
@@ -513,28 +516,13 @@ pre { overflow-x: auto; background: #0001; padding: .6rem; border-radius: .3rem;
513
516
  <label for="confirm-handle">Type the handle to confirm</label>
514
517
  <input type="text" id="confirm-handle" autocomplete="off">
515
518
  </div>
516
- <div id="warn-move-state" class="warn" hidden>
517
- <p>Your timeline, contacts, blocklist and notifications move to the place you choose.
518
- Everything is copied and checked before the agent switches over, and the old copy is
519
- left where it was for you to delete. The agent keeps running.</p>
520
- <fieldset>
521
- <legend>Where to</legend>
522
- <label class="choice"><input type="radio" name="stateWhere" value="path" checked>
523
- another place on this device</label>
524
- <label class="choice"><input type="radio" name="stateWhere" value="url">
525
- another device</label>
526
- <label class="choice"><input type="radio" name="stateWhere" value="pod">
527
- onto the pod</label>
528
- <div id="state-row-path">
529
- <label for="state-path">Directory</label>
530
- <input type="text" id="state-path" autocomplete="off" placeholder="~/somewhere/private/">
531
- </div>
532
- <div id="state-row-url" hidden>
533
- <label for="state-url">Address the other device serves</label>
534
- <input type="url" id="state-url" autocomplete="off" placeholder="https://other-device.example/private/">
535
- </div>
536
- </fieldset>
537
- </div>
519
+ <!-- "Move private data" (POST /state-move) had a panel here and no way to
520
+ open it: nothing in this page has ever carried data-confirm="move-state",
521
+ so the panel was only ever hidden. Removed 2026-09-09 rather than left as
522
+ markup that looks like a feature. The route is still there for the CLI,
523
+ which is where moving the private half between a directory and the pod
524
+ belongs — it is about filesystem paths and credential.json, neither of
525
+ which a browser has. -->
538
526
  <div id="warn-move" class="warn" hidden>
539
527
  <p>A <code>Move</code> goes to every follower's inbox and their servers migrate them to the
540
528
  account you name, so they end up following that one instead. The actor left behind
@@ -561,6 +549,12 @@ pre { overflow-x: auto; background: #0001; padding: .6rem; border-radius: .3rem;
561
549
 
562
550
  <script src="bar.js"></script>
563
551
  <script src="window.js"></script>
564
- <script src="admin.js"></script>
552
+ <script src="common.js"></script>
553
+ <script src="record.js"></script>
554
+ <script src="actors.js"></script>
555
+ <script src="connections.js"></script>
556
+ <script src="group.js"></script>
557
+ <script src="upkeep.js"></script>
558
+ <script src="gateway.js"></script>
565
559
  </body>
566
560
  </html>
@@ -0,0 +1,378 @@
1
+ // record.js — the record itself: what the page holds of the config, the
2
+ // messages it says, every write, the load, the panels, and the rows of the
3
+ // facts list with the identity rows and their controls.
4
+ // admin.js — the record, as a page. Everything here is an existing admin
5
+ // route; the CLI's own group commands are thin clients over the same ones.
6
+ //
7
+ // Two rules the routes enforce and this page respects: a write is a MERGE, so a
8
+ // form that never mentions a field cannot delete it; and anything on the wire is
9
+ // not real until the actor is republished, which POST /config does for itself.
10
+ //
11
+ // The UI password is deliberately not here — `fedipod passwd` sets it. It
12
+ // only gates /oauth/authorize, so it does nothing for a loopback-only agent.
13
+
14
+ let config = null;
15
+ // Held, because render() moves it into a generated row and empties that row's
16
+ // list on the next pass — after which getElementById would not find it again.
17
+ const MODERATION = $('moderation');
18
+ const STATUS = $('status-ctl');
19
+ const STATUS_PICK = $('status-pick'); // held: getElementById can't see it mid-render
20
+ const IDENT_CTL = $('ident-ctl');
21
+ const FOLLOWS_CTL = $('follows-ctl');
22
+ const FOLLOWS_PICK = $('follows-pick');
23
+ const UPDATE_CTL = $('update-ctl');
24
+ const UPDATE_WORD = $('update-word');
25
+ const UPDATE_GO = $('update-go');
26
+
27
+ // #say and #fatal live in the accessibility tree from load and hide by being
28
+ // empty (see the stylesheet). Unhiding a live region and filling it in the same
29
+ // task is the classic way to get no announcement at all.
30
+ function say(text, cls = 'ok') {
31
+ const el = $('say');
32
+ el.className = cls;
33
+ // Clear first, then set on the next task: a live region that is handed the
34
+ // same text it already holds announces nothing, so two identical messages in
35
+ // a row ("nothing changed", "nothing changed") would be silent the second time.
36
+ el.textContent = '';
37
+ setTimeout(() => { el.textContent = text; }, 30);
38
+ }
39
+
40
+ // Every write goes through here, so nothing silently half-succeeds.
41
+ async function write(path, body, done) {
42
+ const { status, json } = await postJson(path, body);
43
+ if (status >= 400) { say(json?.error || `refused (HTTP ${status})`, 'err'); return null; }
44
+ say(done);
45
+ return json;
46
+ }
47
+
48
+ UPDATE_GO.onclick = async () => {
49
+ UPDATE_GO.disabled = true;
50
+ const r = await write('/update', {}, 'updating — the agents restart when it finishes; reload in a moment');
51
+ if (!r) UPDATE_GO.disabled = false;
52
+ };
53
+
54
+ async function load() {
55
+ const { status, json } = await api('/config');
56
+ if (status === 409) {
57
+ $('fatal').textContent = 'This agent has no identity yet.';
58
+ const a = document.createElement('a');
59
+ a.href = '/admin/setup/';
60
+ a.textContent = ' Set it up.';
61
+ $('fatal').appendChild(a);
62
+ return;
63
+ }
64
+ if (status !== 200 || !json) {
65
+ $('fatal').textContent = json?.error || `could not read the record (HTTP ${status})`;
66
+ return;
67
+ }
68
+ config = json;
69
+ render();
70
+ if (new URLSearchParams(location.search).has('new')) openNewActor();
71
+ }
72
+
73
+ // Every disclosure on the page — a form, the log, a lifecycle confirmation —
74
+ // is a panel declared inside the floating window and shown by window.js. It
75
+ // used to be an accordion at the foot of the page, which pushed whatever you
76
+ // were reading out from under you and could only ever show one thing.
77
+ //
78
+ // The reset lives here rather than in window.js: the window knows how to show a
79
+ // panel, not what any of them mean.
80
+ function resetConfirm() {
81
+ pending = null;
82
+ for (const k of Object.keys(LIFECYCLE)) $(`warn-${k}`)?.hidden !== undefined && ($(`warn-${k}`).hidden = true);
83
+ $('confirm-handle').value = '';
84
+ $('confirm-handle-move').value = '';
85
+ $('move-target').value = '';
86
+ $('rotate-password').value = '';
87
+ }
88
+ function closePanels(keep = null) {
89
+ if (!keep) solWindow.close();
90
+ if (keep !== 'output') outputSource = null;
91
+ if (keep !== 'confirm-form') resetConfirm();
92
+ }
93
+ // Closing it by the ✕ or by Escape has to clear the same state a Cancel does.
94
+ document.getElementById('win').addEventListener('win:closed', () => {
95
+ outputSource = null;
96
+ resetConfirm();
97
+ $('bsky-password').value = ''; // never leave a secret in a closed panel
98
+ });
99
+
100
+
101
+ // One row per identity connected elsewhere, indented under the row that adds
102
+ // them. Each carries its own way out: disconnecting one has nothing to do with
103
+ // the others.
104
+ function identityRows() {
105
+ const out = [];
106
+ const row = (label, ...nodes) => {
107
+ const dt = document.createElement('dt');
108
+ dt.className = 'under';
109
+ dt.textContent = label;
110
+ const dd = document.createElement('dd');
111
+ dd.append(...nodes);
112
+ out.push([dt, dd]);
113
+ };
114
+ // One descriptor shape for every connected account — Bluesky and each
115
+ // fediverse account — so the row and its Options menu build the same way.
116
+ const accounts = [];
117
+ if (config.atproto?.connected) accounts.push({
118
+ label: 'Bluesky', handle: `@${config.atproto.handle}`,
119
+ href: `https://bsky.app/profile/${config.atproto.handle}`,
120
+ storage: config.atproto.storage || null,
121
+ feedPaused: 'feedPaused' in config.atproto ? config.atproto.feedPaused : null,
122
+ crossPost: 'crossPost' in config.atproto ? config.atproto.crossPost : null,
123
+ feed: (paused) => ['/atproto', { feedPaused: paused }],
124
+ cross: (on) => ['/atproto', { crossPost: on }],
125
+ store: (w) => ['/atproto', { storage: w }],
126
+ off: () => ['/atproto/disconnect', {}],
127
+ });
128
+ for (const acct of config.fediAccounts || []) accounts.push({
129
+ label: acct.host,
130
+ handle: acct.needsReconnect ? `${acct.handle} — sign in again` : acct.handle,
131
+ warn: acct.needsReconnect,
132
+ storage: acct.storage || null,
133
+ feedPaused: !acct.enabled,
134
+ crossPost: null,
135
+ feed: (paused) => ['/fediacct', { id: acct.id, enabled: !paused }],
136
+ store: (w) => ['/fediacct', { id: acct.id, storage: w }],
137
+ off: () => ['/fediacct/disconnect', { id: acct.id }],
138
+ });
139
+ for (const acct of accounts) {
140
+ const name = document.createElement(acct.href ? 'a' : 'span');
141
+ name.textContent = acct.handle;
142
+ if (acct.href) { name.href = acct.href; name.target = '_blank'; name.rel = 'noopener'; name.title = ' Open this account elsewhere in a new tab'; }
143
+ if (acct.warn) name.className = 'warn';
144
+ // Where storage is a real choice (every browser-build account today), two
145
+ // dropdowns sit in the row itself. Where it isn't (the Node agent's
146
+ // Bluesky, which shows a cross-post toggle instead), the Options
147
+ // disclosure it always has is unchanged.
148
+ row(acct.label, name, ' ', ...(acct.storage ? connectionControls(acct) : [optionsMenu(acct)]));
149
+ }
150
+ return out;
151
+ }
152
+
153
+ // Connected, Paused or Disconnected, and where the key lives — one dropdown
154
+ // each, in the row. Replaces the separate feed and connect dropdowns that used
155
+ // to sit behind an Options disclosure; only reached where acct.storage is set.
156
+ function connectionControls(acct) {
157
+ const send = async ([path, payload], msg) => { if (await write(path, payload, msg)) await load(); };
158
+ const pick = (opts, current, onChange, title) => {
159
+ const el = document.createElement('select');
160
+ if (title) el.title = title;
161
+ for (const [v, t] of opts) { const o = document.createElement('option'); o.value = v; o.textContent = t; o.selected = v === current; el.appendChild(o); }
162
+ el.addEventListener('change', () => onChange(el.value));
163
+ return el;
164
+ };
165
+ const conn = pick(
166
+ [['connected', 'Connected'], ['paused', 'Paused'], ['disconnected', 'Disconnected']],
167
+ acct.feedPaused ? 'paused' : 'connected',
168
+ (v) => (v === 'disconnected'
169
+ ? send(acct.off(), 'disconnected')
170
+ : send(acct.feed(v === 'paused'), v === 'paused' ? 'paused adding to your feed' : 'added to your home feed')),
171
+ ' Connected feeds this account into your home feed here; Paused keeps the account but stops adding its posts; Disconnected removes it.');
172
+ const storage = pick(
173
+ [['pod', 'Store key on Pod'], ['browser', 'Store key in Browser']],
174
+ acct.storage,
175
+ (v) => {
176
+ if (v === 'pod' && !confirm('Store this account on your pod?\n\nIt will follow you to any browser you sign in from — but a full-access token to that account then lives on your pod.')) { load(); return; }
177
+ send(acct.store(v), v === 'pod' ? 'stored on your pod' : 'stored in this browser');
178
+ },
179
+ ' On your pod the key follows you to any browser you sign in from, and a full-access token then lives there; in this browser the token never leaves this device.');
180
+ const wrap = document.createElement('span');
181
+ wrap.className = 'conn-controls';
182
+ wrap.append(conn, ' ', storage);
183
+ return [wrap];
184
+ }
185
+
186
+ // Every connected account without a storage choice carries an Options menu
187
+ // instead: pause its feed, its cross-post toggle (the Node agent's Bluesky),
188
+ // disconnect.
189
+ function optionsMenu(acct) {
190
+ const d = document.createElement('details');
191
+ d.className = 'opts';
192
+ const sum = document.createElement('summary');
193
+ sum.textContent = 'Options';
194
+ d.appendChild(sum);
195
+ const body = document.createElement('div');
196
+ d.appendChild(body);
197
+
198
+ // Each control is one dropdown whose options ARE the states, so it needs no
199
+ // separate label — the chosen option reads as the current state.
200
+ const pick = (opts, current, onChange) => {
201
+ const el = document.createElement('select');
202
+ for (const [v, t] of opts) { const o = document.createElement('option'); o.value = v; o.textContent = t; o.selected = v === current; el.appendChild(o); }
203
+ el.addEventListener('change', () => onChange(el.value));
204
+ body.appendChild(el);
205
+ };
206
+ const send = async ([path, payload], msg) => { if (await write(path, payload, msg)) await load(); };
207
+
208
+ // Feed: shown in / hidden from the feed you read here.
209
+ if (acct.feedPaused !== null) {
210
+ pick([['show', 'Add to home feed'], ['hide', 'Pause adding to home feed']], acct.feedPaused ? 'hide' : 'show',
211
+ (v) => send(acct.feed(v === 'hide'), v === 'hide' ? 'paused adding to your feed' : 'added to your home feed'));
212
+ }
213
+ // Storage where the browser offers the choice; the Node agent's Bluesky shows
214
+ // its cross-post toggle in the same slot instead.
215
+ if (acct.storage) {
216
+ pick([['browser', 'Store key in Browser'], ['pod', 'Store key on Pod']], acct.storage, (v) => {
217
+ if (v === 'pod' && !confirm('Store this account on your pod?\n\nIt will follow you to any browser you sign in from — but a full-access token to that account then lives on your pod.')) { load(); return; }
218
+ send(acct.store(v), v === 'pod' ? 'stored on your pod' : 'stored in this browser');
219
+ });
220
+ } else if (acct.crossPost !== null) {
221
+ pick([['on', 'Cross-post On'], ['off', 'Cross-post Off']], acct.crossPost ? 'on' : 'off',
222
+ (v) => send(acct.cross(v === 'on'), ''));
223
+ }
224
+ // Connection: staying connected, or disconnecting.
225
+ pick([['connected', 'Connected'], ['disconnect', 'Disconnect']], 'connected',
226
+ (v) => { if (v === 'disconnect') send(acct.off(), 'disconnected'); else load(); });
227
+
228
+ // The storage consequence, at the bottom of the box under every dropdown.
229
+ if (acct.storage) {
230
+ const hint = document.createElement('p'); hint.className = 'opt-hint';
231
+ hint.textContent = acct.storage === 'pod'
232
+ ? 'Follows you to every browser you sign in from; a full-access token lives on your pod.'
233
+ : 'Stays in this browser; the token never leaves this device.';
234
+ body.appendChild(hint);
235
+ }
236
+ return d;
237
+ }
238
+
239
+ function render() {
240
+ // The bar names the actor, the same way on every page. Only the tab title is
241
+ // this page's own business.
242
+ document.title = `FediPod — ${config.handle}`;
243
+ const facts = $('facts');
244
+ facts.textContent = '';
245
+ // What this actor IS comes first. The handle is already in the address beside
246
+ // the heading; the pod, the issuer and the actor URL all read off the WebID;
247
+ // and where the private half sits is not something you act on from here.
248
+ const origins = config.origins || {};
249
+ // Two of these are addresses of things you can open, so they are links. The
250
+ // fediverse one goes to this actor's own page in the client, same origin, so
251
+ // it stays in the tab. The Solid one leaves for the pod, so it does not.
252
+ const rows = [
253
+ ['kind', config.kind ? config.kind[0].toUpperCase() + config.kind.slice(1) : config.kind],
254
+ // The value is the select itself — what it shows IS the state, and
255
+ // changing the word is the whole action, like the moderation controls.
256
+ ['status', 'ctl'],
257
+ ['Fediverse identity', config.address || `@${config.handle} — no resolvable address`,
258
+ config.accountId && config.address ? { href: `/admin/client/#/a/${config.accountId}` } : null],
259
+ ['Solid identity', config.webId, config.remotePod ? { href: config.remotePod, blank: true } : null],
260
+ ['Other identities', 'ctl'],
261
+ ['local store', config.home],
262
+ // The address you actually open, not the bare number — the named origin when
263
+ // there is one, since that is what the client and the OAuth redirect use. An
264
+ // agent with no local host of its own (embedded in a pod server, or in a
265
+ // browser) sends none, and then there is no row rather than a bare number.
266
+ ['local host', (origins.named || origins.loopback || '').replace(/\/$/, '') || null],
267
+ ];
268
+ if (config.version || config.update || config.pendingUpgrade?.length) rows.push(['software', 'ctl']);
269
+ rows.push(['gateway', 'ctl']);
270
+ if (config.quiescedAt) rows.push(['parked since', config.quiescedAt]);
271
+ if (config.movedTo) rows.push(['moved to', config.movedTo]);
272
+ // A person gates followers here; a group's gate is the joins control on its
273
+ // kind row, so the row would be a second switch for the same thing.
274
+ if (config.kind !== 'group') rows.splice(2, 0, ['new followers', 'ctl']);
275
+ // Rows that belong under the one being written, appended after it.
276
+ let kids = null;
277
+ for (const [k, v, link] of rows) {
278
+ if (!v) continue;
279
+ const dt = document.createElement('dt');
280
+ dt.textContent = k;
281
+ const dd = document.createElement('dd');
282
+ if (link) {
283
+ const a = document.createElement('a');
284
+ a.href = link.href;
285
+ a.textContent = v;
286
+ if (link.blank) {
287
+ a.target = '_blank';
288
+ // noopener because the pod is another origin: without it the page we
289
+ // open gets a handle on this one through window.opener.
290
+ a.rel = 'noopener';
291
+ a.title = ` Open ${link.href} in a new tab`;
292
+ } else {
293
+ a.title = ` This actor's page in our client`;
294
+ }
295
+ dd.appendChild(a);
296
+ } else {
297
+ dd.textContent = v;
298
+ }
299
+ // What a group can be moderated into is a property of BEING a group, so the
300
+ // controls sit on the row that says so. Moved rather than built here: the
301
+ // page still declares them, and `facts` is emptied on every render.
302
+ if (k === 'kind' && config.kind === 'group') dd.append(MODERATION);
303
+ if (k === 'status') {
304
+ dd.textContent = '';
305
+ dd.append(STATUS);
306
+ STATUS.hidden = false;
307
+ renderStatus();
308
+ }
309
+ if (k === 'new followers') {
310
+ dd.textContent = '';
311
+ dd.append(FOLLOWS_CTL);
312
+ FOLLOWS_CTL.hidden = false;
313
+ FOLLOWS_PICK.value = config.autoAcceptFollows ? 'auto' : 'approve';
314
+ }
315
+ if (k === 'gateway') {
316
+ dd.textContent = '';
317
+ dd.append(GATEWAY_CTL);
318
+ refreshGateway();
319
+ }
320
+ if (k === 'software') {
321
+ dd.textContent = '';
322
+ dd.append(UPDATE_CTL);
323
+ UPDATE_CTL.hidden = false;
324
+ const u = config.update;
325
+ // The version this agent is running, never the one sitting in the
326
+ // checkout — saying otherwise would name a version nobody is serving.
327
+ const running = config.version || u?.current || null;
328
+ const words = [];
329
+ if (running) words.push(u?.available ? `FediPod ${running} — ${u.latest} available` : `FediPod ${running}`);
330
+ if (config.versionOnDisk && running && config.versionOnDisk !== running)
331
+ words.push(`${config.versionOnDisk} is on disk — restart to run it`);
332
+ if (config.pendingUpgrade?.length) words.push('older data layout — run `fedipod upgrade` in a terminal');
333
+ UPDATE_WORD.textContent = words.join('; ');
334
+ UPDATE_GO.hidden = !u?.available;
335
+ }
336
+ // The way to add one. Each account already connected is a row of its own
337
+ // underneath, so this row stays the action and never becomes a list.
338
+ if (k === 'Other identities') {
339
+ dd.textContent = '';
340
+ dd.append(IDENT_CTL);
341
+ IDENT_CTL.hidden = false;
342
+ kids = identityRows();
343
+ }
344
+ facts.append(dt, dd);
345
+ if (kids) {
346
+ for (const [ckt, ckd] of kids) facts.append(ckt, ckd);
347
+ kids = null;
348
+ }
349
+ }
350
+ if (!config.address) {
351
+ const p = document.createElement('p');
352
+ p.className = 'warn';
353
+ p.textContent = `${config.remotePod} is not the root of its own host, so this actor cannot be `
354
+ + 'discovered as a handle by other servers. Posting and reading still work.';
355
+ $('pane-identity').appendChild(p);
356
+ }
357
+
358
+ // pane-others carries the create control too, so it appears even when this is
359
+ // the only actor and even if /profiles cannot be read.
360
+ for (const id of ['pane-others', 'pane-identity', 'rail']) $(id).hidden = false;
361
+ renderAliases();
362
+ renderOthers();
363
+ renderInbox();
364
+ if (config.kind === 'group') {
365
+ // Its lists have no bound, so this page scrolls — see body.group in the CSS.
366
+ document.body.classList.add('group');
367
+ $('pane-group').hidden = false;
368
+ MODERATION.hidden = false; // only a group has any
369
+ renderGroupToggles();
370
+ refreshGroup();
371
+ } else {
372
+ // A person has a follow-request queue too now: nothing binds an inbound
373
+ // Follow to the actor it names, so one that cannot be verified waits here
374
+ // rather than being accepted on the strength of who it claims to be.
375
+ refreshRequests();
376
+ }
377
+ }
378
+
@@ -4,9 +4,10 @@
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
6
  <title>FediPod — setup</title>
7
+ <link rel="stylesheet" href="../tokens.css">
7
8
  <style>
8
9
  /* 20px on the root, not on body, so every rem below scales with it. */
9
- :root { color-scheme: light dark; font-size: 112.5%; --heading: #1a4f8a; }
10
+ :root { color-scheme: light dark; font-size: 125%; --heading: #1a4f8a; }
10
11
  body { font: 1rem/1.5 system-ui, sans-serif; margin: 0; }
11
12
  /* The bar spans the window; the form stays in a centred column under it. */
12
13
  /* The same 960 column the bar and the record use, so the three pages line up. */
@@ -183,6 +184,7 @@ button:disabled { opacity: .5; cursor: default; }
183
184
  </main>
184
185
 
185
186
  <script src="../bar.js"></script>
187
+ <script src="../common.js"></script>
186
188
  <script src="setup.js"></script>
187
189
  </body>
188
190
  </html>
@@ -3,26 +3,15 @@
3
3
  // at localhost:<port> or at <handle>.localhost:<port>.
4
4
  //
5
5
  // External file, not an inline script: the CSP allows 'self' plus the hashes
6
- // of Phanpy's own inline bootstrap, and nothing else.
6
+ // of Phanpy's own inline bootstrap, and nothing else. `$`, `BASE`, `api` and
7
+ // `postJson` come from ../common.js, loaded first.
7
8
 
8
- const $ = (id) => document.getElementById(id);
9
9
  // Empty means gone, not a blank line where a sentence used to be.
10
10
  const strap = (t) => { const el = $('strap'); el.textContent = t || ''; el.hidden = !t; };
11
11
  // By id, not by position: these were `body > section` until a wrapper went
12
12
  // round them for layout, and the selector then matched nothing — every pane
13
13
  // kept whatever it started as and the page sat on "Reading this agent's state".
14
14
  const show = (id) => { for (const s of document.querySelectorAll('section[id^="pane-"]')) s.hidden = s.id !== id; };
15
- // The door this page is served behind: nothing when the agent runs on this
16
- // machine, `/app` when the identity is hosted by its own pod server. Read from
17
- // the page's own address, so one build serves both.
18
- const BASE = location.pathname.replace(/\/admin\/.*$/u, '');
19
- const api = async (path, init) => {
20
- const res = await fetch(BASE + path, init);
21
- return { status: res.status, json: await res.json().catch(() => null) };
22
- };
23
- const postJson = (path, body) => api(path, {
24
- method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body),
25
- });
26
15
 
27
16
  let state = null;
28
17