fedipod 0.18.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (320) hide show
  1. package/README.md +20 -0
  2. package/bin/fedipod.mjs +3 -5
  3. package/cli.md +13 -0
  4. package/groups.md +11 -0
  5. package/gui.md +15 -1
  6. package/lib/admin.mjs +3 -3
  7. package/lib/deliver.mjs +6 -0
  8. package/lib/export-collections.mjs +1 -1
  9. package/lib/front-core.mjs +272 -49
  10. package/lib/gateway-core.mjs +3 -7
  11. package/lib/httpsig.mjs +33 -2
  12. package/lib/import.mjs +1 -1
  13. package/lib/intake.mjs +335 -118
  14. package/lib/links.mjs +6 -32
  15. package/lib/mastoapi.mjs +372 -35
  16. package/lib/migrate.mjs +4 -18
  17. package/lib/pod/README.md +61 -0
  18. package/lib/pod/actor.mjs +96 -0
  19. package/lib/pod/collection.mjs +77 -0
  20. package/lib/pod/containers.mjs +136 -0
  21. package/lib/pod/discovery.mjs +46 -0
  22. package/lib/pod/featured.mjs +18 -0
  23. package/lib/pod/followers.mjs +24 -0
  24. package/lib/pod/following.mjs +10 -0
  25. package/lib/pod/http.mjs +74 -0
  26. package/lib/pod/inbox.mjs +144 -0
  27. package/lib/pod/links.mjs +35 -0
  28. package/lib/pod/media.mjs +27 -0
  29. package/lib/pod/notes.mjs +80 -0
  30. package/lib/pod/notifications.mjs +89 -0
  31. package/lib/pod/outbox.mjs +18 -0
  32. package/lib/pod/package.json +19 -0
  33. package/lib/pod/policy.mjs +58 -0
  34. package/lib/pod/private.mjs +19 -0
  35. package/lib/pod/root.mjs +63 -0
  36. package/lib/pod/state.mjs +39 -0
  37. package/lib/pod/transport.mjs +477 -0
  38. package/lib/pod/urls.mjs +71 -0
  39. package/lib/publisher.mjs +112 -183
  40. package/lib/remote.mjs +30 -395
  41. package/lib/social.mjs +18 -9
  42. package/lib/storage.mjs +2 -2
  43. package/lib/store.mjs +8 -1
  44. package/lib/tagfeed.mjs +19 -2
  45. package/lib/wire.mjs +48 -67
  46. package/package.json +9 -2
  47. package/run-agent.mjs +14 -39
  48. package/scripts/build-app.mjs +64 -0
  49. package/scripts/check-pod-calls.mjs +177 -0
  50. package/scripts/stage-site.mjs +144 -0
  51. package/web/admin/admin.js +141 -74
  52. package/web/admin/bar.css +9 -13
  53. package/web/admin/bar.js +2 -2
  54. package/web/admin/client/client.js +1 -1
  55. package/web/admin/client/index.html +5 -6
  56. package/web/admin/index.html +53 -65
  57. package/web/admin/setup/index.html +2 -1
  58. package/web/admin/setup/setup.js +1 -1
  59. package/web/admin/tokens.css +56 -0
  60. package/web/app/README.md +77 -0
  61. package/web/app/admin-facade.mjs +549 -0
  62. package/web/app/agent.mjs +490 -0
  63. package/web/app/atproto-browser.mjs +86 -0
  64. package/web/app/boot.mjs +340 -0
  65. package/web/app/deliver-relay.mjs +72 -0
  66. package/web/app/dist/boot.js +34067 -0
  67. package/web/app/dist/boot.js.map +7 -0
  68. package/web/app/dist/sw.js +53745 -0
  69. package/web/app/dist/sw.js.map +7 -0
  70. package/web/app/fediacct-browser.mjs +146 -0
  71. package/web/app/idb-kv.mjs +35 -0
  72. package/web/app/index.html +203 -0
  73. package/web/app/index.html~ +152 -0
  74. package/web/app/keys-browser.mjs +46 -0
  75. package/web/app/keystore.mjs +99 -0
  76. package/web/app/oidc-session.mjs +189 -0
  77. package/web/app/pod-auth.mjs +248 -0
  78. package/web/app/pod-remote.mjs +84 -0
  79. package/web/app/shims/_globals.mjs +2 -0
  80. package/web/app/shims/fedify-sig.mjs +77 -0
  81. package/web/app/shims/node-crypto.mjs +113 -0
  82. package/web/app/shims/node-fs.mjs +17 -0
  83. package/web/app/shims/node-path.mjs +19 -0
  84. package/web/app/shims/node-url.mjs +7 -0
  85. package/web/app/shims/prelude.js +58 -0
  86. package/web/app/shims/safefetch.mjs +58 -0
  87. package/web/app/shims/web-push.mjs +14 -0
  88. package/web/app/signup.mjs +248 -0
  89. package/web/app/site/_headers +9 -0
  90. package/web/app/site/_redirects +21 -0
  91. package/web/app/site/admin/admin.js +1181 -0
  92. package/web/app/site/admin/bar.css +60 -0
  93. package/web/app/site/admin/bar.js +61 -0
  94. package/web/app/site/admin/client/client.js +66 -0
  95. package/web/app/site/admin/client/index.html +43 -0
  96. package/web/app/site/admin/index.html +556 -0
  97. package/web/app/site/admin/setup/index.html +191 -0
  98. package/web/app/site/admin/setup/setup.js +308 -0
  99. package/web/app/site/admin/tokens.css +56 -0
  100. package/web/app/site/admin/window.css +55 -0
  101. package/web/app/site/admin/window.js +136 -0
  102. package/web/app/site/app/404.html +40 -0
  103. package/web/app/site/app/_headers +2 -0
  104. package/web/app/site/app/apple-touch-icon.png +0 -0
  105. package/web/app/site/app/assets/ICONS-DIPJeU0C.js +2 -0
  106. package/web/app/site/app/assets/ICONS-DIPJeU0C.js.map +1 -0
  107. package/web/app/site/app/assets/ScheduledAtField-D-Abfo4J.js +183 -0
  108. package/web/app/site/app/assets/ScheduledAtField-D-Abfo4J.js.map +1 -0
  109. package/web/app/site/app/assets/Temml-CCjoacWt.woff2 +0 -0
  110. package/web/app/site/app/assets/__vite-browser-external-C7iut881.js +2 -0
  111. package/web/app/site/app/assets/__vite-browser-external-C7iut881.js.map +1 -0
  112. package/web/app/site/app/assets/boosts-carousel-BiOaNdMT.jpg +0 -0
  113. package/web/app/site/app/assets/catch-up-O5q5C75Z.png +0 -0
  114. package/web/app/site/app/assets/chunk-aKtaBQYM.js +1 -0
  115. package/web/app/site/app/assets/compose-CFEx7wb4.js +2 -0
  116. package/web/app/site/app/assets/compose-CFEx7wb4.js.map +1 -0
  117. package/web/app/site/app/assets/compose-ZJN5xSb4.js +35 -0
  118. package/web/app/site/app/assets/compose-ZJN5xSb4.js.map +1 -0
  119. package/web/app/site/app/assets/debug.module-YFQgwnIr.js +52 -0
  120. package/web/app/site/app/assets/debug.module-YFQgwnIr.js.map +1 -0
  121. package/web/app/site/app/assets/dom-p1YcJ5Rw.js +2 -0
  122. package/web/app/site/app/assets/dom-p1YcJ5Rw.js.map +1 -0
  123. package/web/app/site/app/assets/home-mobile-dark@2x-Dc-J8-SM.png +0 -0
  124. package/web/app/site/app/assets/home-mobile-light@2x-Bd3fod8m.png +0 -0
  125. package/web/app/site/app/assets/home-tablet-dark@2x-BgcNI8le.png +0 -0
  126. package/web/app/site/app/assets/home-tablet-light@2x-BW2RV9T6.png +0 -0
  127. package/web/app/site/app/assets/icons/add-circle-line-WD0mwORD.js +1 -0
  128. package/web/app/site/app/assets/icons/alert-line-CpFUMf9s.js +1 -0
  129. package/web/app/site/app/assets/icons/android-2-line-Bzxnrd3S.js +1 -0
  130. package/web/app/site/app/assets/icons/announcement-line-5nwaJFJm.js +1 -0
  131. package/web/app/site/app/assets/icons/arrow-down-circle-line-a7Id9-Iv.js +1 -0
  132. package/web/app/site/app/assets/icons/arrow-down-line-B8uzXzq6.js +1 -0
  133. package/web/app/site/app/assets/icons/arrow-left-line-CGrGT9C-.js +1 -0
  134. package/web/app/site/app/assets/icons/arrow-right-line-CE-HERfX.js +1 -0
  135. package/web/app/site/app/assets/icons/arrow-to-up-line-CvdQjmcy.js +1 -0
  136. package/web/app/site/app/assets/icons/arrow-up-circle-line-DEparSNF.js +1 -0
  137. package/web/app/site/app/assets/icons/arrow-up-line-C7yBeugc.js +1 -0
  138. package/web/app/site/app/assets/icons/arrows-right-line-BtbXmJGP.js +1 -0
  139. package/web/app/site/app/assets/icons/at-line-CF66Bznk.js +1 -0
  140. package/web/app/site/app/assets/icons/attachment-line-BY8It-l9.js +1 -0
  141. package/web/app/site/app/assets/icons/blockquote-line-BS6pU_hm.js +1 -0
  142. package/web/app/site/app/assets/icons/board-line-C0UWXyA6.js +1 -0
  143. package/web/app/site/app/assets/icons/bookmark-line-1CtX5JLF.js +1 -0
  144. package/web/app/site/app/assets/icons/building-5-line-Dd59MzbO.js +1 -0
  145. package/web/app/site/app/assets/icons/bus-2-line-6_dvA0GG.js +1 -0
  146. package/web/app/site/app/assets/icons/calendar-day-line-Crpl_fmu.js +1 -0
  147. package/web/app/site/app/assets/icons/calendar-month-line-vommts2C.js +1 -0
  148. package/web/app/site/app/assets/icons/calendar-time-add-line-BU_DTlQk.js +1 -0
  149. package/web/app/site/app/assets/icons/camera-line-Cv69MEei.js +1 -0
  150. package/web/app/site/app/assets/icons/celebrate-line-B8DvIoi0.js +1 -0
  151. package/web/app/site/app/assets/icons/chart-bar-line-FK-QOmtF.js +1 -0
  152. package/web/app/site/app/assets/icons/chart-line-line-BGcJAFWw.js +1 -0
  153. package/web/app/site/app/assets/icons/chat-3-line-YFK2-hwz.js +1 -0
  154. package/web/app/site/app/assets/icons/check-circle-line-DsAxFr5-.js +1 -0
  155. package/web/app/site/app/assets/icons/clipboard-line-BBp364gJ.js +1 -0
  156. package/web/app/site/app/assets/icons/close-circle-line-CICdiuGz.js +1 -0
  157. package/web/app/site/app/assets/icons/close-line-DYrFWcjv.js +1 -0
  158. package/web/app/site/app/assets/icons/cloud-line-tiQAbah8.js +1 -0
  159. package/web/app/site/app/assets/icons/code-line-fAl8iiV3.js +1 -0
  160. package/web/app/site/app/assets/icons/comment-2-line-CR3g7WeB.js +1 -0
  161. package/web/app/site/app/assets/icons/copy-2-line-CLW69yF5.js +1 -0
  162. package/web/app/site/app/assets/icons/delete-2-line-B8xmbr4n.js +1 -0
  163. package/web/app/site/app/assets/icons/document-3-line-DBGNhAg2.js +1 -0
  164. package/web/app/site/app/assets/icons/document-line-C7mb9sb7.js +1 -0
  165. package/web/app/site/app/assets/icons/down-line-BGqSSThd.js +1 -0
  166. package/web/app/site/app/assets/icons/edit-4-line-DcgGaEhx.js +1 -0
  167. package/web/app/site/app/assets/icons/emoji-2-line-D8YgnM-y.js +1 -0
  168. package/web/app/site/app/assets/icons/exit-line-Dc07BS-x.js +1 -0
  169. package/web/app/site/app/assets/icons/external-link-line-BvpSkmjO.js +1 -0
  170. package/web/app/site/app/assets/icons/eye-2-line-CiWXx-f_.js +1 -0
  171. package/web/app/site/app/assets/icons/eye-close-line-BgTaSJ4P.js +1 -0
  172. package/web/app/site/app/assets/icons/filter-2-line-B_mR2usy.js +1 -0
  173. package/web/app/site/app/assets/icons/filter-line-C_nm-3cU.js +1 -0
  174. package/web/app/site/app/assets/icons/flag-1-line-C2HB05I0.js +1 -0
  175. package/web/app/site/app/assets/icons/forbid-circle-line-D3LTT9yd.js +1 -0
  176. package/web/app/site/app/assets/icons/formula-line-DLrt2d6j.js +1 -0
  177. package/web/app/site/app/assets/icons/grid-line-CgUmEikf.js +1 -0
  178. package/web/app/site/app/assets/icons/group-line-C5fnX0wU.js +1 -0
  179. package/web/app/site/app/assets/icons/hand-finger-2-line-BGgxbuL9.js +1 -0
  180. package/web/app/site/app/assets/icons/hashtag-line-CyTEEXBb.js +1 -0
  181. package/web/app/site/app/assets/icons/heart-crack-line-BZddbO2C.js +1 -0
  182. package/web/app/site/app/assets/icons/heart-line-CJ7K47J4.js +1 -0
  183. package/web/app/site/app/assets/icons/history-2-line-kWWvFzqv.js +1 -0
  184. package/web/app/site/app/assets/icons/history-line-bGQiwbTH.js +1 -0
  185. package/web/app/site/app/assets/icons/home-3-line-fRU1zeLG.js +1 -0
  186. package/web/app/site/app/assets/icons/information-line-iNk6-sOY.js +1 -0
  187. package/web/app/site/app/assets/icons/keyboard-line-DsCyuQNz.js +1 -0
  188. package/web/app/site/app/assets/icons/layout-4-line-CZkOX52Z.js +1 -0
  189. package/web/app/site/app/assets/icons/layout-5-line-B_znt5wO.js +1 -0
  190. package/web/app/site/app/assets/icons/left-line-eUw0tya4.js +1 -0
  191. package/web/app/site/app/assets/icons/lightning-line-DaKoDdix.js +1 -0
  192. package/web/app/site/app/assets/icons/link-2-line-DCqLmMA7.js +1 -0
  193. package/web/app/site/app/assets/icons/list-check-line-BAxosBh9.js +1 -0
  194. package/web/app/site/app/assets/icons/lock-line-CPoaXeUW.js +1 -0
  195. package/web/app/site/app/assets/icons/mail-line-B0P8aa1L.js +1 -0
  196. package/web/app/site/app/assets/icons/moon-line-CEqNAToP.js +1 -0
  197. package/web/app/site/app/assets/icons/more-1-fill-Dw2e5xab.js +1 -0
  198. package/web/app/site/app/assets/icons/more-3-line-BIiVjYTz.js +1 -0
  199. package/web/app/site/app/assets/icons/notification-line-BZZq2Gtu.js +1 -0
  200. package/web/app/site/app/assets/icons/pencil-line-CKh8-A1v.js +1 -0
  201. package/web/app/site/app/assets/icons/photo-album-line-Bnfg2u40.js +1 -0
  202. package/web/app/site/app/assets/icons/pin-line-DpTolyhs.js +1 -0
  203. package/web/app/site/app/assets/icons/play-fill-B0-G_8lX.js +1 -0
  204. package/web/app/site/app/assets/icons/qrcode-2-line-B6SPCaHn.js +1 -0
  205. package/web/app/site/app/assets/icons/quill-pen-line-B6LUrnCP.js +1 -0
  206. package/web/app/site/app/assets/icons/quote-left-fill-hQZ1cj_6.js +1 -0
  207. package/web/app/site/app/assets/icons/radar-line-CKyroTth.js +1 -0
  208. package/web/app/site/app/assets/icons/react-line-CoIKhhtK.js +1 -0
  209. package/web/app/site/app/assets/icons/refresh-2-line-D0mTF97S.js +1 -0
  210. package/web/app/site/app/assets/icons/right-line-B3KWJaqc.js +1 -0
  211. package/web/app/site/app/assets/icons/rocket-line-4KZl537D.js +1 -0
  212. package/web/app/site/app/assets/icons/round-fill-C69NTrZ0.js +1 -0
  213. package/web/app/site/app/assets/icons/round-line-B9k_1uo-.js +1 -0
  214. package/web/app/site/app/assets/icons/route-line-B28ReHcE.js +1 -0
  215. package/web/app/site/app/assets/icons/rows-4-line-CrBniagM.js +1 -0
  216. package/web/app/site/app/assets/icons/scan-line-DSf74YdP.js +1 -0
  217. package/web/app/site/app/assets/icons/search-2-line-B6geIU1z.js +1 -0
  218. package/web/app/site/app/assets/icons/settings-3-line-azJwK47w.js +1 -0
  219. package/web/app/site/app/assets/icons/settings-6-line-C_5zc5YR.js +1 -0
  220. package/web/app/site/app/assets/icons/share-2-line-D5MxWjrm.js +1 -0
  221. package/web/app/site/app/assets/icons/share-forward-line-Cp61fZzZ.js +1 -0
  222. package/web/app/site/app/assets/icons/sparkles-2-line-DYYyXTaW.js +1 -0
  223. package/web/app/site/app/assets/icons/sparkles-line-C2hY1s6I.js +1 -0
  224. package/web/app/site/app/assets/icons/time-line-BF3eet1v.js +1 -0
  225. package/web/app/site/app/assets/icons/transfer-4-line-oisfvTBw.js +1 -0
  226. package/web/app/site/app/assets/icons/translate-line-BFWol2Ce.js +1 -0
  227. package/web/app/site/app/assets/icons/unlock-line-X_x5vwv3.js +1 -0
  228. package/web/app/site/app/assets/icons/upload-3-line-DgzwUJeW.js +1 -0
  229. package/web/app/site/app/assets/icons/user-4-line-BVSynZCp.js +1 -0
  230. package/web/app/site/app/assets/icons/user-add-2-line-BlGS5Q8q.js +1 -0
  231. package/web/app/site/app/assets/icons/user-add-line-BbXdHG4z.js +1 -0
  232. package/web/app/site/app/assets/icons/user-edit-line-BFAHOV0X.js +1 -0
  233. package/web/app/site/app/assets/icons/user-follow-line-BXux0wSd.js +1 -0
  234. package/web/app/site/app/assets/icons/user-star-line-DdYlpHLU.js +1 -0
  235. package/web/app/site/app/assets/icons/user-warning-line-Cj43j0_E.js +1 -0
  236. package/web/app/site/app/assets/icons/user-x-line-DIDgNlPH.js +1 -0
  237. package/web/app/site/app/assets/icons/volume-line-Bs390KQt.js +1 -0
  238. package/web/app/site/app/assets/icons/volume-mute-line-DzlzIqFZ.js +1 -0
  239. package/web/app/site/app/assets/icons/walk-line-yNXfB-md.js +1 -0
  240. package/web/app/site/app/assets/icons/world-2-line-CenkGIwj.js +1 -0
  241. package/web/app/site/app/assets/icons/zoom-in-line-CaENUphf.js +1 -0
  242. package/web/app/site/app/assets/icons/zoom-out-line-BDNk0EWo.js +1 -0
  243. package/web/app/site/app/assets/instances-BNPptnn-.json +333 -0
  244. package/web/app/site/app/assets/locales/ar-SA-CfheRbCu.js +1 -0
  245. package/web/app/site/app/assets/locales/ca-ES-DHRW86Tz.js +1 -0
  246. package/web/app/site/app/assets/locales/cs-CZ-CKoqEuA1.js +1 -0
  247. package/web/app/site/app/assets/locales/de-DE-ByRPMRFy.js +1 -0
  248. package/web/app/site/app/assets/locales/eo-UY-CPC7wLCS.js +1 -0
  249. package/web/app/site/app/assets/locales/es-ES-dJa0Fd9t.js +1 -0
  250. package/web/app/site/app/assets/locales/eu-ES-C3djlPR2.js +1 -0
  251. package/web/app/site/app/assets/locales/fa-IR-DTT490Ck.js +1 -0
  252. package/web/app/site/app/assets/locales/fi-FI-B23MCPuz.js +1 -0
  253. package/web/app/site/app/assets/locales/fr-FR-BcaQsO5C.js +1 -0
  254. package/web/app/site/app/assets/locales/gl-ES-BR-CVKBN.js +1 -0
  255. package/web/app/site/app/assets/locales/he-IL-CfheRbCu.js +1 -0
  256. package/web/app/site/app/assets/locales/hu-HU-CfheRbCu.js +1 -0
  257. package/web/app/site/app/assets/locales/it-IT-C1136UFV.js +1 -0
  258. package/web/app/site/app/assets/locales/ja-JP-CRZmRAhD.js +1 -0
  259. package/web/app/site/app/assets/locales/kab-Cz8xH_TT.js +1 -0
  260. package/web/app/site/app/assets/locales/ko-KR-CG9UiY4o.js +1 -0
  261. package/web/app/site/app/assets/locales/lt-LT-CJcI6sHZ.js +1 -0
  262. package/web/app/site/app/assets/locales/nb-NO-CfheRbCu.js +1 -0
  263. package/web/app/site/app/assets/locales/nl-NL-DxN4Yc1t.js +1 -0
  264. package/web/app/site/app/assets/locales/oc-FR-CfheRbCu.js +1 -0
  265. package/web/app/site/app/assets/locales/pl-PL-DkwGXf6P.js +1 -0
  266. package/web/app/site/app/assets/locales/pseudo-LOCALE-CQ4XxkQ0.js +1 -0
  267. package/web/app/site/app/assets/locales/pt-BR-nGL0HdFS.js +1 -0
  268. package/web/app/site/app/assets/locales/pt-PT-Cp17aj2z.js +1 -0
  269. package/web/app/site/app/assets/locales/ru-RU-0Y8cIoaK.js +1 -0
  270. package/web/app/site/app/assets/locales/th-TH-CfheRbCu.js +1 -0
  271. package/web/app/site/app/assets/locales/tok-CfheRbCu.js +1 -0
  272. package/web/app/site/app/assets/locales/tr-TR-CfheRbCu.js +1 -0
  273. package/web/app/site/app/assets/locales/uk-UA-C5E3dFBf.js +1 -0
  274. package/web/app/site/app/assets/locales/zh-CN-BHJi7_et.js +1 -0
  275. package/web/app/site/app/assets/locales/zh-TW-CfheRbCu.js +1 -0
  276. package/web/app/site/app/assets/main-BdqNbG-a.js +26 -0
  277. package/web/app/site/app/assets/main-BdqNbG-a.js.map +1 -0
  278. package/web/app/site/app/assets/mock-home-DGhR__He.js +2 -0
  279. package/web/app/site/app/assets/mock-home-DGhR__He.js.map +1 -0
  280. package/web/app/site/app/assets/multi-column-ROck0NVt.jpg +0 -0
  281. package/web/app/site/app/assets/multi-hashtag-timeline-DH5INVHi.jpg +0 -0
  282. package/web/app/site/app/assets/nested-comments-thread-hv59kZLc.jpg +0 -0
  283. package/web/app/site/app/assets/polyfill-force-H8sUkLGp.js +2 -0
  284. package/web/app/site/app/assets/polyfill-force-H8sUkLGp.js.map +1 -0
  285. package/web/app/site/app/assets/pwa-viewport-ogKbF51K.js +20 -0
  286. package/web/app/site/app/assets/pwa-viewport-ogKbF51K.js.map +1 -0
  287. package/web/app/site/app/assets/style-B1iLPTPR.css +2 -0
  288. package/web/app/site/app/assets/temml-DuyIQMei.js +97 -0
  289. package/web/app/site/app/assets/temml-DuyIQMei.js.map +1 -0
  290. package/web/app/site/app/assets/tinyld.light.node-DtbBhLYC.js +2 -0
  291. package/web/app/site/app/assets/tinyld.light.node-DtbBhLYC.js.map +1 -0
  292. package/web/app/site/app/assets/year-in-posts-B2lywqdx.png +0 -0
  293. package/web/app/site/app/assets/year-in-posts-DJv_star.js +3 -0
  294. package/web/app/site/app/assets/year-in-posts-DJv_star.js.map +1 -0
  295. package/web/app/site/app/compose/index.html +20 -0
  296. package/web/app/site/app/favicon.ico +0 -0
  297. package/web/app/site/app/index.html +73 -0
  298. package/web/app/site/app/logo-192.png +0 -0
  299. package/web/app/site/app/logo-512.png +0 -0
  300. package/web/app/site/app/logo-badge-72.png +0 -0
  301. package/web/app/site/app/logo-maskable-512.png +0 -0
  302. package/web/app/site/app/logo-monochrome-512.png +0 -0
  303. package/web/app/site/app/logo-monochrome-maskable-512.png +0 -0
  304. package/web/app/site/app/manifest.webmanifest +1 -0
  305. package/web/app/site/app/og-image-2.jpg +0 -0
  306. package/web/app/site/app/og-image.png +0 -0
  307. package/web/app/site/app/robots.txt +2 -0
  308. package/web/app/site/app/version.json +1 -0
  309. package/web/app/site/boot.js +34067 -0
  310. package/web/app/site/index.html +203 -0
  311. package/web/app/site/sw.js +53745 -0
  312. package/web/app/sw-src.mjs +205 -0
  313. package/web/front/#new-account.html# +43 -0
  314. package/web/front/admin.html +1 -81
  315. package/web/front/admin.js +85 -0
  316. package/web/front/new-account.html +1 -9
  317. package/web/front/new-account.js +13 -0
  318. package/web/front/run.html +1 -92
  319. package/web/front/run.js +107 -0
  320. package/lib/podrdf.mjs +0 -132
@@ -0,0 +1,144 @@
1
+ // stage-site.mjs — assemble the deployable static site for the in-browser agent.
2
+ //
3
+ // Output (web/app/site/, gitignored): the sign-up and sign-in pages at the
4
+ // root, the boot glue and the agent service worker (sw.js must be at the root
5
+ // so it controls the whole origin), and Phanpy under /app/ with its own
6
+ // service-worker registration removed so only the agent worker runs.
7
+ //
8
+ // npm run build:app # both bundles, into web/app/dist/
9
+ // npm run stage # this script
10
+ //
11
+ // The bundles are tracked, so rebuild and commit them in the SAME commit as
12
+ // whatever source change caused them — a dist that does not match its own
13
+ // source is the hardest kind of thing to see in the browser build.
14
+ import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url';
15
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
16
+ const site = path.join(root, 'web/app/site');
17
+ fs.rmSync(site, { recursive: true, force: true }); fs.mkdirSync(path.join(site, 'app'), { recursive: true });
18
+ const cp = (from, to) => fs.copyFileSync(path.join(root, from), path.join(site, to));
19
+ cp('web/app/index.html', 'index.html');
20
+ cp('web/app/dist/boot.js', 'boot.js');
21
+ cp('web/app/dist/sw.js', 'sw.js');
22
+ const copyDir = (from, to) => { fs.mkdirSync(to, { recursive: true });
23
+ for (const e of fs.readdirSync(from, { withFileTypes: true })) {
24
+ const s = path.join(from, e.name), d = path.join(to, e.name);
25
+ if (e.isDirectory()) copyDir(s, d);
26
+ else if (e.name === 'sw.js' || e.name === 'sw.js.map') { /* drop Phanpy's own worker */ }
27
+ else if (e.name === 'index.html') fs.writeFileSync(d, fs.readFileSync(s, 'utf8').replace(/<script id="vite-plugin-pwa:inline-sw">[\s\S]*?<\/script>/i, ''));
28
+ else fs.copyFileSync(s, d);
29
+ } };
30
+ copyDir(path.join(root, 'phanpy/dist'), path.join(site, 'app'));
31
+ // The owner's record/manage surface: the SAME web/admin pages the Node agent
32
+ // serves, reused as static files (the agent answers their data endpoints from
33
+ // the worker — see admin-facade.mjs). Two browser adaptations: the client shell
34
+ // frames the client at /app/ (locally the client is the origin root, so the
35
+ // source says "../../"), and the manual "Drain the inbox" control is hidden —
36
+ // the browser drains automatically over notifications.
37
+ const copyAdmin = (from, to) => { fs.mkdirSync(to, { recursive: true });
38
+ for (const e of fs.readdirSync(from, { withFileTypes: true })) {
39
+ const s = path.join(from, e.name), d = path.join(to, e.name);
40
+ if (e.isDirectory()) { copyAdmin(s, d); continue; }
41
+ if (e.name !== 'index.html') { fs.copyFileSync(s, d); continue; }
42
+ // Every bar in the browser build: no "Local actors" dropdown (there is one
43
+ // identity per browser), and the FediPod brand is a link to the sign-in /
44
+ // add-a-pod page — /?add shows it without bouncing a signed-in user back
45
+ // into the client, so setup is always one click from the bar.
46
+ let text = fs.readFileSync(s, 'utf8')
47
+ .replace('<h1 class="name">FediPod</h1>', '<h1 class="name"><a href="/?add">FediPod</a></h1>')
48
+ // A sign-out beside "manage account", on every bar. It clears the FediPod
49
+ // session, the client's stored account, browser-stored connected keys, and
50
+ // the worker — the /?signout page (boot.js) does the teardown.
51
+ .replace('>manage account</a>', '>manage account</a>\n <a id="bar-signout" href="/?signout" title=" Sign out of FediPod and clear this browser">sign out</a>')
52
+ .replace('</head>', '<style>#actor-pick{display:none!important}</style>\n</head>');
53
+ if (/[/\\]client$/.test(from)) {
54
+ // The client shell frames the client at /app/ (locally the client is the
55
+ // origin root, so the source says "../../").
56
+ text = text.replace('src="../../"', 'src="/app/"');
57
+ } else if (/[/\\]admin$/.test(from)) {
58
+ // Hide controls the browser build does not carry: the manual drain (it
59
+ // drains automatically over notifications), the local logfile viewer
60
+ // (there is no local log in a browser), and the "create a handle at the
61
+ // gateway" choice — a fronted @you@front identity is not the browser
62
+ // model, and admin-facade.mjs refuses one outright. Leaving the radio
63
+ // visible meant the page checked the name's availability as you typed,
64
+ // told you it was free, and then refused the attach. With it hidden the
65
+ // pod-based choice stays selected, so that check never runs either.
66
+ // And rename "Recover posts" to the plainer "Refresh Feed" for this
67
+ // audience.
68
+ text = text
69
+ .replace('<style>#actor-pick', '<style>#gw-shape-front,#do-drain,#do-log,#actor-pick')
70
+ .replace('>Recover posts<', '>Refresh Feed<')
71
+ // Reveal the password row on the rotate-key warning: here the key on
72
+ // the pod is wrapped under the account password (keystore.mjs), so a
73
+ // replacement has to be wrapped too and there is nowhere to get one
74
+ // from but the owner. The Node agent keeps its key on disk and leaves
75
+ // this row hidden.
76
+ .replace('<div id="rotate-pw-row" hidden>', '<div id="rotate-pw-row">')
77
+ .replace('Put back posts this machine lost, from what the pod still holds',
78
+ 'Refresh your feed from what your pod still holds');
79
+ }
80
+ fs.writeFileSync(d, text);
81
+ } };
82
+ copyAdmin(path.join(root, 'web/admin'), path.join(site, 'admin'));
83
+ // Netlify routing. The project sends every path to the gateway function by
84
+ // default, which would shadow these static files, so file-based rules (which
85
+ // win over the project's UI redirect) serve the app directly and forward only
86
+ // the gateway's own paths to the function. First match wins.
87
+ fs.writeFileSync(path.join(site, '_redirects'), [
88
+ '# gateway + front API → the function',
89
+ '/api/handle /.netlify/functions/front 200',
90
+ '/api/attach /.netlify/functions/front 200',
91
+ '/api/relay /.netlify/functions/front 200',
92
+ '/api/roster /.netlify/functions/front 200',
93
+ '/api/revoke /.netlify/functions/front 200',
94
+ '/api/agent /.netlify/functions/front 200',
95
+ '/u/* /.netlify/functions/front 200',
96
+ '/.well-known/* /.netlify/functions/front 200',
97
+ '/install /.netlify/functions/front 200',
98
+ '/run /.netlify/functions/front 200',
99
+ '/roster /.netlify/functions/front 200',
100
+ '/solid-oidc-client.js /.netlify/functions/front 200',
101
+ '# the in-browser app, served static (these win over the project catch-all)',
102
+ '/ /index.html 200',
103
+ '/sw.js /sw.js 200',
104
+ '/boot.js /boot.js 200',
105
+
106
+ '/app /app/ 301',
107
+ '/app/* /app/:splat 200',
108
+ '/admin /admin/ 301',
109
+ '/admin/* /admin/:splat 200',
110
+ '', ].join('\n'));
111
+ // Response headers. Same-origin IS the trust boundary for this build — the
112
+ // worker answers the admin routes on this origin, and one page here renders
113
+ // fediverse HTML from strangers — so `script-src 'self'` is what stands
114
+ // between a remote post and the identity. Only `/app/*` and `/admin/*` are
115
+ // given a CSP header here: the root page carries its own `<meta>` CSP, tuned
116
+ // to its issuer, and a header would intersect with it and break the login.
117
+ //
118
+ // Netlify reads ONE `_headers`, at the publish root. (Phanpy ships its own
119
+ // under `app/`, which is why that copy has never done anything.)
120
+ //
121
+ // `Referrer-Policy: same-origin` and not `no-referrer`: the worker's own gate
122
+ // reads `request.referrer` to tell the bundled client's sign-in navigation
123
+ // from a cross-site one (sw-src.mjs), and stripping it everywhere would refuse
124
+ // the legitimate one. Same-origin sends it to us and to nobody else.
125
+ //
126
+ // `style-src` keeps `'unsafe-inline'`: the admin pages get a `<style>` block
127
+ // injected above, and `innerHTML` with a `style=` attribute is common in both.
128
+ // Inline STYLE cannot exfiltrate the way inline SCRIPT can, and tightening it
129
+ // would cost the boundary nothing. `/app/*` needs `https:` for images and
130
+ // media because the whole point of the client is rendering other servers'
131
+ // avatars and attachments.
132
+ fs.writeFileSync(path.join(site, '_headers'), [
133
+ '/app/*',
134
+ " Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' https: data: blob:; media-src 'self' https: blob:; connect-src 'self' https:; font-src 'self' data:; object-src 'none'; base-uri 'none'; frame-ancestors 'self'; form-action 'self'",
135
+ ' X-Content-Type-Options: nosniff',
136
+ ' Referrer-Policy: same-origin',
137
+ '',
138
+ '/admin/*',
139
+ " Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' https: data: blob:; connect-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'self'; form-action 'self'",
140
+ ' X-Content-Type-Options: nosniff',
141
+ ' Referrer-Policy: same-origin',
142
+ '', ].join('\n'));
143
+ let n = 0; (function count(d) { for (const e of fs.readdirSync(d, { withFileTypes: true })) e.isDirectory() ? count(path.join(d, e.name)) : n++; })(site);
144
+ console.log(`staged web/app/site — ${n} files`);
@@ -13,8 +13,15 @@ const $ = (id) => document.getElementById(id);
13
13
  // machine, `/app` when the identity is hosted by its own pod server. Read from
14
14
  // the page's own address, so one build serves both.
15
15
  const BASE = location.pathname.replace(/\/admin\/.*$/u, '');
16
+ // Every call carries `x-fedipod-page`. On the Node agent it is ignored — the
17
+ // Origin check there already refuses a page on somebody else's site. In the
18
+ // browser build the agent IS this origin's service worker, which cannot see
19
+ // Sec-Fetch-* and answers these routes with no password, so this header is the
20
+ // whole door: a cross-site form cannot set one, and a cross-origin fetch that
21
+ // sets one is stopped at a preflight the worker never grants. See sw-src.mjs.
22
+ const PAGE_HEADER = { 'x-fedipod-page': '1' };
16
23
  const api = async (path, init) => {
17
- const res = await fetch(BASE + path, init);
24
+ const res = await fetch(BASE + path, { ...init, headers: { ...(init?.headers || {}), ...PAGE_HEADER } });
18
25
  return { status: res.status, json: await res.json().catch(() => null) };
19
26
  };
20
27
  const postJson = (path, body) => api(path, {
@@ -27,14 +34,12 @@ let config = null;
27
34
  const MODERATION = $('moderation');
28
35
  const STATUS = $('status-ctl');
29
36
  const STATUS_PICK = $('status-pick'); // held: getElementById can't see it mid-render
30
- const BSKY_CTL = $('bsky-ctl');
31
37
  const IDENT_CTL = $('ident-ctl');
32
38
  const FOLLOWS_CTL = $('follows-ctl');
33
39
  const FOLLOWS_PICK = $('follows-pick');
34
40
  const UPDATE_CTL = $('update-ctl');
35
41
  const UPDATE_WORD = $('update-word');
36
42
  const UPDATE_GO = $('update-go');
37
- const BSKY_CROSSPOST = $('bsky-crosspost'); // held: it rides BSKY_CTL into a generated row
38
43
 
39
44
  // #say and #fatal live in the accessibility tree from load and hide by being
40
45
  // empty (see the stylesheet). Unhiding a live region and filling it in the same
@@ -95,10 +100,7 @@ function resetConfirm() {
95
100
  $('confirm-handle').value = '';
96
101
  $('confirm-handle-move').value = '';
97
102
  $('move-target').value = '';
98
- $('state-path').value = '';
99
- $('state-url').value = '';
100
- document.querySelector('input[name=stateWhere][value=path]').checked = true;
101
- stateRows();
103
+ $('rotate-password').value = '';
102
104
  }
103
105
  function closePanels(keep = null) {
104
106
  if (!keep) solWindow.close();
@@ -126,34 +128,129 @@ function identityRows() {
126
128
  dd.append(...nodes);
127
129
  out.push([dt, dd]);
128
130
  };
129
- if (config.atproto?.connected) {
130
- const a = document.createElement('a');
131
- a.href = `https://bsky.app/profile/${config.atproto.handle}`;
132
- a.textContent = `@${config.atproto.handle}`;
133
- a.target = '_blank';
134
- a.rel = 'noopener';
135
- a.title = ` Open this account's Bluesky page in a new tab`;
136
- BSKY_CTL.hidden = false;
137
- BSKY_CROSSPOST.value = config.atproto.crossPost ? 'on' : 'off';
138
- row('Bluesky', a, ' ', BSKY_CTL);
139
- } else {
140
- BSKY_CTL.hidden = true;
131
+ // One descriptor shape for every connected account — Bluesky and each
132
+ // fediverse account — so the row and its Options menu build the same way.
133
+ const accounts = [];
134
+ if (config.atproto?.connected) accounts.push({
135
+ label: 'Bluesky', handle: `@${config.atproto.handle}`,
136
+ href: `https://bsky.app/profile/${config.atproto.handle}`,
137
+ storage: config.atproto.storage || null,
138
+ feedPaused: 'feedPaused' in config.atproto ? config.atproto.feedPaused : null,
139
+ crossPost: 'crossPost' in config.atproto ? config.atproto.crossPost : null,
140
+ feed: (paused) => ['/atproto', { feedPaused: paused }],
141
+ cross: (on) => ['/atproto', { crossPost: on }],
142
+ store: (w) => ['/atproto', { storage: w }],
143
+ off: () => ['/atproto/disconnect', {}],
144
+ });
145
+ for (const acct of config.fediAccounts || []) accounts.push({
146
+ label: acct.host,
147
+ handle: acct.needsReconnect ? `${acct.handle} — sign in again` : acct.handle,
148
+ warn: acct.needsReconnect,
149
+ storage: acct.storage || null,
150
+ feedPaused: !acct.enabled,
151
+ crossPost: null,
152
+ feed: (paused) => ['/fediacct', { id: acct.id, enabled: !paused }],
153
+ store: (w) => ['/fediacct', { id: acct.id, storage: w }],
154
+ off: () => ['/fediacct/disconnect', { id: acct.id }],
155
+ });
156
+ for (const acct of accounts) {
157
+ const name = document.createElement(acct.href ? 'a' : 'span');
158
+ name.textContent = acct.handle;
159
+ if (acct.href) { name.href = acct.href; name.target = '_blank'; name.rel = 'noopener'; name.title = ' Open this account elsewhere in a new tab'; }
160
+ if (acct.warn) name.className = 'warn';
161
+ // Where storage is a real choice (every browser-build account today), two
162
+ // dropdowns sit in the row itself. Where it isn't (the Node agent's
163
+ // Bluesky, which shows a cross-post toggle instead), the Options
164
+ // disclosure it always has is unchanged.
165
+ row(acct.label, name, ' ', ...(acct.storage ? connectionControls(acct) : [optionsMenu(acct)]));
166
+ }
167
+ return out;
168
+ }
169
+
170
+ // Connected, Paused or Disconnected, and where the key lives — one dropdown
171
+ // each, in the row. Replaces the separate feed and connect dropdowns that used
172
+ // to sit behind an Options disclosure; only reached where acct.storage is set.
173
+ function connectionControls(acct) {
174
+ const send = async ([path, payload], msg) => { if (await write(path, payload, msg)) await load(); };
175
+ const pick = (opts, current, onChange, title) => {
176
+ const el = document.createElement('select');
177
+ if (title) el.title = title;
178
+ for (const [v, t] of opts) { const o = document.createElement('option'); o.value = v; o.textContent = t; o.selected = v === current; el.appendChild(o); }
179
+ el.addEventListener('change', () => onChange(el.value));
180
+ return el;
181
+ };
182
+ const conn = pick(
183
+ [['connected', 'Connected'], ['paused', 'Paused'], ['disconnected', 'Disconnected']],
184
+ acct.feedPaused ? 'paused' : 'connected',
185
+ (v) => (v === 'disconnected'
186
+ ? send(acct.off(), 'disconnected')
187
+ : send(acct.feed(v === 'paused'), v === 'paused' ? 'paused adding to your feed' : 'added to your home feed')),
188
+ ' Connected feeds this account into your home feed here; Paused keeps the account but stops adding its posts; Disconnected removes it.');
189
+ const storage = pick(
190
+ [['pod', 'Store key on Pod'], ['browser', 'Store key in Browser']],
191
+ acct.storage,
192
+ (v) => {
193
+ 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; }
194
+ send(acct.store(v), v === 'pod' ? 'stored on your pod' : 'stored in this browser');
195
+ },
196
+ ' 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.');
197
+ const wrap = document.createElement('span');
198
+ wrap.className = 'conn-controls';
199
+ wrap.append(conn, ' ', storage);
200
+ return [wrap];
201
+ }
202
+
203
+ // Every connected account without a storage choice carries an Options menu
204
+ // instead: pause its feed, its cross-post toggle (the Node agent's Bluesky),
205
+ // disconnect.
206
+ function optionsMenu(acct) {
207
+ const d = document.createElement('details');
208
+ d.className = 'opts';
209
+ const sum = document.createElement('summary');
210
+ sum.textContent = 'Options';
211
+ d.appendChild(sum);
212
+ const body = document.createElement('div');
213
+ d.appendChild(body);
214
+
215
+ // Each control is one dropdown whose options ARE the states, so it needs no
216
+ // separate label — the chosen option reads as the current state.
217
+ const pick = (opts, current, onChange) => {
218
+ const el = document.createElement('select');
219
+ for (const [v, t] of opts) { const o = document.createElement('option'); o.value = v; o.textContent = t; o.selected = v === current; el.appendChild(o); }
220
+ el.addEventListener('change', () => onChange(el.value));
221
+ body.appendChild(el);
222
+ };
223
+ const send = async ([path, payload], msg) => { if (await write(path, payload, msg)) await load(); };
224
+
225
+ // Feed: shown in / hidden from the feed you read here.
226
+ if (acct.feedPaused !== null) {
227
+ pick([['show', 'Add to home feed'], ['hide', 'Pause adding to home feed']], acct.feedPaused ? 'hide' : 'show',
228
+ (v) => send(acct.feed(v === 'hide'), v === 'hide' ? 'paused adding to your feed' : 'added to your home feed'));
141
229
  }
142
- for (const acct of config.fediAccounts || []) {
143
- const name = document.createElement('span');
144
- name.textContent = acct.needsReconnect ? `${acct.handle} — sign in again` : acct.handle;
145
- if (acct.needsReconnect) name.className = 'warn';
146
- const off = document.createElement('button');
147
- off.type = 'button';
148
- off.className = 'inline danger';
149
- off.textContent = 'Disconnect';
150
- off.title = ` Stop reading ${acct.handle} and remove its stored token`;
151
- off.addEventListener('click', async () => {
152
- if (await write('/fediacct/disconnect', { id: acct.id }, `${acct.handle} disconnected`)) await load();
230
+ // Storage where the browser offers the choice; the Node agent's Bluesky shows
231
+ // its cross-post toggle in the same slot instead.
232
+ if (acct.storage) {
233
+ pick([['browser', 'Store key in Browser'], ['pod', 'Store key on Pod']], acct.storage, (v) => {
234
+ 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; }
235
+ send(acct.store(v), v === 'pod' ? 'stored on your pod' : 'stored in this browser');
153
236
  });
154
- row(acct.host, name, ' ', off);
237
+ } else if (acct.crossPost !== null) {
238
+ pick([['on', 'Cross-post On'], ['off', 'Cross-post Off']], acct.crossPost ? 'on' : 'off',
239
+ (v) => send(acct.cross(v === 'on'), ''));
155
240
  }
156
- return out;
241
+ // Connection: staying connected, or disconnecting.
242
+ pick([['connected', 'Connected'], ['disconnect', 'Disconnect']], 'connected',
243
+ (v) => { if (v === 'disconnect') send(acct.off(), 'disconnected'); else load(); });
244
+
245
+ // The storage consequence, at the bottom of the box under every dropdown.
246
+ if (acct.storage) {
247
+ const hint = document.createElement('p'); hint.className = 'opt-hint';
248
+ hint.textContent = acct.storage === 'pod'
249
+ ? 'Follows you to every browser you sign in from; a full-access token lives on your pod.'
250
+ : 'Stays in this browser; the token never leaves this device.';
251
+ body.appendChild(hint);
252
+ }
253
+ return d;
157
254
  }
158
255
 
159
256
  function render() {
@@ -180,9 +277,10 @@ function render() {
180
277
  ['Other identities', 'ctl'],
181
278
  ['local store', config.home],
182
279
  // The address you actually open, not the bare number — the named origin when
183
- // there is one, since that is what the client and the OAuth redirect use.
184
- ['local host', (origins.named || origins.loopback || `http://localhost:${config.port}`)
185
- .replace(/\/$/, '')],
280
+ // there is one, since that is what the client and the OAuth redirect use. An
281
+ // agent with no local host of its own (embedded in a pod server, or in a
282
+ // browser) sends none, and then there is no row rather than a bare number.
283
+ ['local host', (origins.named || origins.loopback || '').replace(/\/$/, '') || null],
186
284
  ];
187
285
  if (config.version || config.update || config.pendingUpgrade?.length) rows.push(['software', 'ctl']);
188
286
  rows.push(['gateway', 'ctl']);
@@ -502,13 +600,8 @@ $('fediacct-form').addEventListener('submit', async (ev) => {
502
600
  } finally { fediBusy = false; }
503
601
  });
504
602
 
505
- $('bsky-disconnect').addEventListener('click', async () => {
506
- if (bskyBusy) return;
507
- bskyBusy = true;
508
- try {
509
- if (await write('/atproto/disconnect', {}, 'bluesky account disconnected')) await load();
510
- } finally { bskyBusy = false; }
511
- });
603
+ // Disconnecting Bluesky, pausing its feed and choosing its storage all live in
604
+ // the account's Options menu now (see optionsMenu). Connecting is still here.
512
605
  // Adding resolves the old account on the agent side, so what lands in
513
606
  // alsoKnownAs is its canonical id, not the string typed here.
514
607
  // The accounts elsewhere this one may receive a Move from — the chips in the
@@ -553,15 +646,6 @@ $('alias-add').addEventListener('click', async () => {
553
646
  } finally { aliasBusy = false; }
554
647
  });
555
648
 
556
- // Silent on success — the select already shows the new state.
557
- const setCrossPost = async (on) => {
558
- if (bskyBusy || on === !!config.atproto?.crossPost) return;
559
- bskyBusy = true;
560
- try {
561
- if (await write('/atproto', { crossPost: on }, '')) config.atproto.crossPost = on;
562
- BSKY_CROSSPOST.value = config.atproto?.crossPost ? 'on' : 'off';
563
- } finally { bskyBusy = false; }
564
- };
565
649
 
566
650
  // ---- group ----
567
651
 
@@ -655,8 +739,6 @@ function onPick(el, apply, repaint = renderGroupToggles) {
655
739
  onPick($('joins-mod'), (v) => setJoins(v === MOD.on));
656
740
  onPick($('review-mod'), (v) => setReview(v === MOD.on));
657
741
  onPick(STATUS_PICK, (v) => setStatus(v === 'parked'), renderStatus);
658
- onPick(BSKY_CROSSPOST, (v) => setCrossPost(v === 'on'),
659
- () => { BSKY_CROSSPOST.value = config?.atproto?.crossPost ? 'on' : 'off'; });
660
742
 
661
743
  // A person's follower gate. Automatic is what a migration wave needs: every
662
744
  // follower's server re-follows at once, and the waiting queue caps at 500.
@@ -897,21 +979,7 @@ const LIFECYCLE = {
897
979
  retire: { path: '/retire', title: 'Retire this identity', go: 'Retire it', danger: true, done: (r) => `retired ${r.deletedAt}: Delete delivered to ${r.inboxes} inbox(es)` },
898
980
  move: { path: '/move', title: 'Transfer this account away', go: 'Transfer it', focus: 'move-target',
899
981
  done: (r) => `transferred to ${r.target}: Move delivered to ${r.inboxes} inbox(es), unfollowed ${r.unfollowed}/${r.following}` },
900
- 'move-state': { path: '/state-move', title: 'Move private data', go: 'Move it', focus: 'state-path',
901
- done: (r) => (r.unchanged ? 'already there — nothing moved'
902
- : `moved ${r.docs} document(s) and ${r.notes} post(s) — private data is now ${r.now}`) },
903
982
  };
904
-
905
- // The destination choice reveals the field it needs: a directory for this
906
- // device, an address for another one, nothing for the pod.
907
- function stateRows() {
908
- const w = picked('stateWhere');
909
- $('state-row-path').hidden = w !== 'path';
910
- $('state-row-url').hidden = w !== 'url';
911
- }
912
- for (const el of document.querySelectorAll('input[name=stateWhere]')) {
913
- el.addEventListener('change', stateRows);
914
- }
915
983
  let pending = null;
916
984
 
917
985
  const closeConfirm = () => closePanels();
@@ -954,16 +1022,15 @@ $('confirm-form').addEventListener('submit', async (ev) => {
954
1022
  const what = pending;
955
1023
  const body = what === 'retire' ? { confirm: $('confirm-handle').value.trim() }
956
1024
  : what === 'move' ? { target: $('move-target').value.trim(), confirm: $('confirm-handle-move').value.trim() }
957
- : what === 'move-state' ? {
958
- to: picked('stateWhere') === 'pod' ? 'pod'
959
- : picked('stateWhere') === 'url' ? $('state-url').value.trim() : $('state-path').value.trim(),
960
- }
1025
+ // Only where the field is shown (the browser build — see index.html).
1026
+ // On the Node agent the row stays hidden and nothing is sent, which is
1027
+ // what that agent expects.
1028
+ : what === 'rotate-key' && !$('rotate-pw-row').hidden
1029
+ ? { password: $('rotate-password').value }
961
1030
  : {};
962
1031
  if (what === 'move' && !body.target) { say('name the account to transfer to', 'err'); return; }
963
- if (what === 'move-state' && !body.to) { say('name the destination', 'err'); return; }
964
1032
  $('confirm-go').disabled = true;
965
- say(what === 'move-state' ? 'moving your private data — copying, checking, then switching over'
966
- : `${what} — this talks to the pod and to other servers, so it takes a moment`);
1033
+ say(`${what} — this talks to the pod and to other servers, so it takes a moment`);
967
1034
  const r = await write(LIFECYCLE[what].path, body, what);
968
1035
  $('confirm-go').disabled = false;
969
1036
  if (!r) return; // write() already said why
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
@@ -4,18 +4,17 @@
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
6
  <title>FediPod — client</title>
7
+ <link rel="stylesheet" href="../tokens.css">
7
8
  <style>
8
- :root { color-scheme: light dark; font-size: 112.5%; --link: #1a4f8a; --heading: #5b3a8e; }
9
- /* The bar is fixed height, the client takes the rest — the page never scrolls,
10
- the frame does. */
9
+ /* Colours are the shared tokens (tokens.css); only layout is here. The bar is
10
+ a fixed height, the client takes the rest — the page never scrolls, the frame
11
+ does. */
12
+ :root { color-scheme: light dark; font-size: 125%; }
11
13
  html { height: 100%; }
12
14
  body { font: 1rem/1.5 system-ui, sans-serif; margin: 0; height: 100dvh;
13
15
  display: flex; flex-direction: column; overflow: hidden; }
14
16
  main { flex: 1 1 auto; min-height: 0; display: flex; }
15
17
  #client { flex: 1 1 auto; min-height: 0; width: 100%; border: 0; }
16
- @media (prefers-color-scheme: dark) {
17
- :root { --link: #7fb3e8; --heading: #b9a3e3; }
18
- }
19
18
  </style>
20
19
  <link rel="stylesheet" href="../bar.css">
21
20
  </head>