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
@@ -0,0 +1,297 @@
1
+ // setup.js — the questions the CLI used to ask, asked here instead. Every
2
+ // request is relative, so the page behaves identically whether it was reached
3
+ // at localhost:<port> or at <handle>.localhost:<port>.
4
+ //
5
+ // External file, not an inline script: the CSP allows 'self' plus the hashes
6
+ // of Phanpy's own inline bootstrap, and nothing else. `$`, `BASE`, `api` and
7
+ // `postJson` come from ../common.js, loaded first.
8
+
9
+ // Empty means gone, not a blank line where a sentence used to be.
10
+ const strap = (t) => { const el = $('strap'); el.textContent = t || ''; el.hidden = !t; };
11
+ // By id, not by position: these were `body > section` until a wrapper went
12
+ // round them for layout, and the selector then matched nothing — every pane
13
+ // kept whatever it started as and the page sat on "Reading this agent's state".
14
+ const show = (id) => { for (const s of document.querySelectorAll('section[id^="pane-"]')) s.hidden = s.id !== id; };
15
+
16
+ let state = null;
17
+
18
+ // ---- what the agent needs from us ----
19
+
20
+ async function start() {
21
+ const { json } = await api('/setup/state');
22
+ state = json || {};
23
+ if (state.phase === 'running' || state.phase === 'done' || state.phase === 'error') return watchRun();
24
+ if (state.configured) return paneConfigured();
25
+ return paneForm();
26
+ }
27
+
28
+ function paneConfigured() {
29
+ strap('already set up');
30
+ $('configured-address').textContent = state.handle ? `@${state.handle}` : '';
31
+ $('configured-client').hidden = state.kind === 'group';
32
+ show('pane-configured');
33
+ // The address needs the pod host, which only /config knows.
34
+ api('/config').then(({ json }) => {
35
+ if (json?.address) $('configured-address').textContent = json.address;
36
+ });
37
+ }
38
+
39
+ // ---- the form ----
40
+
41
+ function paneForm() {
42
+ // Nothing is asked in a terminal any more — every question is on this page.
43
+ // A fresh run needs no strap at all; a resumed one has something to say.
44
+ strap(state.resumable ? 'finishing a setup that did not complete' : '');
45
+ if (state.handle) $('handle').value = state.handle;
46
+ // A signup page's carry-over: the form opens with its answers already in.
47
+ const fr = state.firstRun;
48
+ if (fr && !state.resumable) {
49
+ if (fr.kind) $('form').elements.kind.value = fr.kind;
50
+ if (fr.handle) $('handle').value = fr.handle;
51
+ if (fr.pod) { $('form').elements.mode.value = 'existing'; $('pod').value = fr.pod; }
52
+ // A gateway-arranged signup names its own provider; give the dropdown that
53
+ // choice too, so the carried-over answer survives the narrower control.
54
+ if (fr.issuer) {
55
+ $('issuer').value = fr.issuer;
56
+ const sel = $('issuer-new');
57
+ if (![...sel.options].some(o => o.value === fr.issuer)) {
58
+ const o = document.createElement('option');
59
+ o.value = fr.issuer;
60
+ o.textContent = host(fr.issuer);
61
+ sel.appendChild(o);
62
+ }
63
+ sel.value = fr.issuer;
64
+ }
65
+ if (fr.gatewayHost) strap(`finishing your ${fr.gatewayHost} signup — enter your Solid account details`);
66
+ }
67
+ // Resuming: the account exists and the credential is minted. Asking for a
68
+ // password again would mint a second one and orphan the first, which cannot
69
+ // be recovered.
70
+ // Set both ways, not just hidden-when-resuming: re-entering credentials
71
+ // re-renders this in the other mode, and a one-way hide would strand the
72
+ // account and pod fields off-screen. Resuming settles the account and the
73
+ // credential, so those fields go; a fresh run shows them.
74
+ $('row-reenter').hidden = !state.resumable;
75
+ $('fs-pod').hidden = state.resumable;
76
+ // The password field also goes when AP_PASSWORD supplied it in the environment.
77
+ $('row-password').hidden = state.resumable || state.passwordSupplied;
78
+ $('submit').textContent = state.resumable ? 'Finish setting up' : 'Create it';
79
+ if (state.resumable) $('form-reenter').onclick = reEnter; // the escape from a wrong credential
80
+ show('pane-form');
81
+ // The markup's autofocus was set while this pane was still hidden, so it
82
+ // never fired — move focus to the first field now that the pane is showing.
83
+ $('handle').focus();
84
+ for (const el of $('form').elements) {
85
+ el.addEventListener('input', onEdit);
86
+ el.addEventListener('change', onEdit);
87
+ }
88
+ $('form').addEventListener('submit', onSubmit);
89
+ onEdit();
90
+ }
91
+
92
+ function answers() {
93
+ const f = $('form').elements;
94
+ const kind = f.kind.value;
95
+ const mode = state.resumable ? 'existing' : f.mode.value;
96
+ const a = {
97
+ kind,
98
+ mode,
99
+ handle: f.handle.value.trim(),
100
+ issuer: (mode === 'new' ? f.issuerNew.value : f.issuer.value).trim(),
101
+ email: f.email.value.trim(),
102
+ };
103
+ if (mode === 'new') a.podName = f.podName.value.trim() || a.handle;
104
+ else a.pod = f.pod.value.trim();
105
+ if (!state.resumable && !state.passwordSupplied) a.password = f.password.value;
106
+ return a;
107
+ }
108
+
109
+ // Every warning the CLI printed before it asked "create pod and fediverse
110
+ // account?", in the same words, as you type.
111
+ const NOTES = {
112
+ 'pod-is-a-path': (a) =>
113
+ `This pod is a path on ${host(a.pod)}, not the root of its own host. WebFinger is `
114
+ + 'answered only at a host root, which this pod cannot write to, so other servers '
115
+ + 'will not find this address. Posting and reading still work; being discovered does not.',
116
+ };
117
+ const REFUSALS = {
118
+ 'group-needs-host-root': () =>
119
+ 'A group needs a pod at the root of its own host, or nobody could ever find it. '
120
+ + 'Give the group a pod of its own, or set this up as a person.',
121
+ };
122
+ const host = (u) => { try { return new URL(u).host; } catch { return u; } };
123
+
124
+ let editTimer = null;
125
+ function onEdit() {
126
+ const mode = state.resumable ? 'existing' : $('form').elements.mode.value;
127
+ $('row-podname').hidden = mode !== 'new';
128
+ $('row-pod').hidden = mode === 'new';
129
+ $('row-issuer-new').hidden = mode !== 'new';
130
+ $('row-issuer-existing').hidden = mode === 'new';
131
+ clearTimeout(editTimer);
132
+ editTimer = setTimeout(preview, 150);
133
+ }
134
+
135
+ async function preview() {
136
+ const a = answers();
137
+ $('preview-notes').textContent = '';
138
+ if (!a.handle || (a.mode === 'existing' && !a.pod)) { $('preview').textContent = '…'; return; }
139
+ if (state.resumable) { $('preview').textContent = `@${a.handle}@…`; return; }
140
+ const { json } = await postJson('/setup/check', a);
141
+ if (!json) return;
142
+ $('preview').textContent = json.address || '…';
143
+ const notes = $('preview-notes');
144
+ for (const w of json.warnings || []) {
145
+ const p = document.createElement('p');
146
+ p.className = 'warn';
147
+ p.textContent = NOTES[w] ? NOTES[w](a) : w;
148
+ notes.appendChild(p);
149
+ }
150
+ if (json.refusal) {
151
+ const p = document.createElement('p');
152
+ p.className = 'err';
153
+ p.textContent = REFUSALS[json.refusal] ? REFUSALS[json.refusal]() : json.refusal;
154
+ notes.appendChild(p);
155
+ }
156
+ $('submit').disabled = !!json.refusal;
157
+ }
158
+
159
+ async function onSubmit(ev) {
160
+ ev.preventDefault();
161
+ $('form-error').textContent = '';
162
+ $('submit').disabled = true;
163
+ const { status, json } = await postJson('/setup', answers());
164
+ if (status !== 202) {
165
+ $('submit').disabled = false;
166
+ $('form-error').textContent = json?.error || `setup refused (HTTP ${status})`;
167
+ return;
168
+ }
169
+ watchRun();
170
+ }
171
+
172
+ // ---- the run ----
173
+
174
+ const LABELS = {
175
+ account: 'Creating the account and pod',
176
+ credential: 'Minting a credential for this machine',
177
+ bootstrap: 'Provisioning the pod',
178
+ connect: 'Connecting',
179
+ publish: 'Publishing the actor',
180
+ verify: 'Checking the world can see it',
181
+ };
182
+ const MARKS = { waiting: '·', running: '⟳', ok: '✓', skipped: '–', error: '✗' };
183
+
184
+ async function watchRun() {
185
+ show('pane-run');
186
+ strap('setting up');
187
+ const { json: run } = await api('/setup/progress');
188
+ if (!run) return;
189
+ renderRun(run);
190
+ if (run.phase === 'running') return setTimeout(watchRun, 700);
191
+ if (run.phase === 'done') return paneDone(run.result);
192
+ $('run-title').textContent = 'Setup stopped';
193
+ $('run-error').textContent = run.error || 'unknown error';
194
+ $('run-again').hidden = false;
195
+ $('run-again').onclick = async () => {
196
+ // Re-read the state: a run that got as far as the credential resumes from
197
+ // there rather than minting a second one.
198
+ const { json } = await api('/setup/state');
199
+ state = json || state;
200
+ paneForm();
201
+ };
202
+ // A failure past the credential (a wrong pod answering 401 to the first
203
+ // write) leaves a credential that "Try again" can only re-use. Offer to
204
+ // discard it and re-enter the account and pod — only when there is one.
205
+ const { json: st } = await api('/setup/state');
206
+ const reenter = $('run-reenter');
207
+ reenter.hidden = !st?.hasCredential;
208
+ reenter.onclick = reEnter;
209
+ }
210
+
211
+ // Discard the saved credential (server confirms, then the full form returns).
212
+ // Destructive and unrecoverable — a minted credential is shown once — so it
213
+ // asks first.
214
+ async function reEnter() {
215
+ if (!confirm('Discard the saved account credential and enter the account and pod again?\n\n'
216
+ + 'The old credential is left on your account — revoke it from the account dashboard if you want it gone.')) return;
217
+ const { status, json } = await postJson('/setup/reset', {});
218
+ if (status !== 200) {
219
+ const box = $('pane-form').hidden ? $('run-error') : $('form-error');
220
+ box.textContent = json?.error || `could not reset (HTTP ${status})`;
221
+ return;
222
+ }
223
+ const { json: st } = await api('/setup/state');
224
+ state = st || {};
225
+ paneForm();
226
+ }
227
+
228
+ function renderRun(run) {
229
+ const ul = $('run-steps');
230
+ ul.textContent = '';
231
+ for (const s of run.steps) {
232
+ const li = document.createElement('li');
233
+ const mark = document.createElement('span');
234
+ mark.className = 'state';
235
+ mark.textContent = MARKS[s.state] || '·';
236
+ // The glyph is the whole of what says how a step went, and `⟳` read aloud
237
+ // is not a word. Naming it leaves the visual result exactly as it was.
238
+ mark.setAttribute('aria-label', s.state || 'waiting');
239
+ li.appendChild(mark);
240
+ li.appendChild(document.createTextNode(LABELS[s.key] || s.key));
241
+ if (s.note) {
242
+ const note = document.createElement('div');
243
+ note.className = 'note';
244
+ note.textContent = s.note;
245
+ li.appendChild(note);
246
+ }
247
+ ul.appendChild(li);
248
+ }
249
+ // One polite line rather than a live region over the whole list: the list is
250
+ // rebuilt from scratch on every poll, and announcing all six steps twice a
251
+ // second is not progress, it is noise. Only the step actually moving is said.
252
+ const now = run.steps.find(s => s.state === 'running')
253
+ || [...run.steps].reverse().find(s => s.state && s.state !== 'waiting');
254
+ const say = now ? `${LABELS[now.key] || now.key}: ${now.state}` : '';
255
+ const line = $('run-say');
256
+ if (line && line.textContent !== say) line.textContent = say;
257
+ }
258
+
259
+ function paneDone(result) {
260
+ // Straight to the thing you set it up FOR. A group has no client of its own,
261
+ // so it goes to its console instead. Warnings are the exception: an address
262
+ // nobody can resolve, or documents a stranger cannot read, are worth stopping
263
+ // for — the client would just look empty and not say why.
264
+ const stop = !result?.resolvable || result?.unreachable?.length;
265
+ if (!stop) {
266
+ location.href = result?.kind === 'group' ? '/admin/' : '/admin/client/';
267
+ return;
268
+ }
269
+ strap('set up');
270
+ show('pane-done');
271
+ $('done-address').textContent = result?.address || `@${result?.handle} — no resolvable address`;
272
+ const notes = [];
273
+ if (!result?.resolvable) {
274
+ notes.push(`${result?.pod} is not the root of its own host, so this address cannot be `
275
+ + 'discovered by other servers. Posting and reading still work.');
276
+ }
277
+ if (result?.unreachable?.length) {
278
+ notes.push(`Not readable without credentials yet: ${result.unreachable.join(', ')}. `
279
+ + 'Other servers cannot fetch this actor until that clears.');
280
+ }
281
+ $('done-note').className = notes.length ? 'warn' : '';
282
+ $('done-note').textContent = notes.join(' ');
283
+
284
+ const links = $('done-links');
285
+ links.textContent = '';
286
+ const add = (href, text) => {
287
+ if (links.hasChildNodes()) links.appendChild(document.createTextNode(' · '));
288
+ const a = document.createElement('a');
289
+ a.href = href;
290
+ a.textContent = text;
291
+ links.appendChild(a);
292
+ };
293
+ if (result?.kind !== 'group') add('/', 'Open the client');
294
+ add('/admin/', 'Edit the record');
295
+ }
296
+
297
+ start();
@@ -0,0 +1,56 @@
1
+ /* tokens.css — the FediPod design tokens: the front page's palette
2
+ (web/app/index.html), so the front, the record page, the bar and the client
3
+ shell are one visual system. The front is the reference; these values mirror
4
+ its :root. Declared under every name the pages already use — plus aliases
5
+ (--ink=--fg, --muted=--sub, --link=--accent, --field-edge=--field-border,
6
+ --danger=--err, --ring=--focus) — so a page only has to drop its own :root
7
+ colours and inherit these. */
8
+ :root {
9
+ color-scheme: light dark;
10
+
11
+ /* ground + text */
12
+ --bg: #ffffff; --fg: #1a1a1a; --ink: #1a1a1a;
13
+ --surface: #f0f2f5; /* the page ground behind cards */
14
+ --raised: #ffffff; /* cards, the rail, the bar band, dialogs */
15
+
16
+ /* structure + action */
17
+ --heading: #123f74; --accent: #12467e; --link: #12467e;
18
+ --sub: #565656; --muted: #565656; --line: #b0b0b0;
19
+
20
+ /* fields */
21
+ --field-bg: #ececec; --field-bg-hover: #e2e4e8; --field-hover: #e2e4e8;
22
+ --field-text: #141414; --field-edge: #6f6f6f; --field-border: #6f6f6f;
23
+
24
+ /* buttons — filled, muted forest green (like the front) */
25
+ --btn: #3a5f43; --btn-hover: #30503a; --btn-text: #ffffff;
26
+ --btn-bg: #ececec; --btn-bg-hover: #e2e4e8; --btn-bg-active: #d8dade; --btn-edge: #6f6f6f;
27
+
28
+ /* status + focus */
29
+ --err: #b00020; --danger: #b00020; --warn: #8a4b00; --ok: #1f7a3d;
30
+ --focus: #12467e; --ring: #12467e;
31
+
32
+ --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='%23124f8a' stroke-width='1.6' stroke-linecap='round'/></svg>");
33
+ }
34
+
35
+ @media (prefers-color-scheme: dark) {
36
+ :root {
37
+ --bg: #191919; --fg: #f3f3f3; --ink: #f3f3f3;
38
+ --surface: #18191a; --raised: #242526;
39
+
40
+ --heading: #8fc2f2; --accent: #8fc2f2; --link: #8fc2f2;
41
+ --sub: #d2d2d2; --muted: #d2d2d2; --line: #6d6d6d;
42
+
43
+ --field-bg: #474747; --field-bg-hover: #515151; --field-hover: #515151;
44
+ --field-text: #f5f5f5; --field-edge: #9aa0a6; --field-border: #9aa0a6;
45
+
46
+ /* a touch brighter on the dark page so white text and the fill-vs-page edge
47
+ both clear their contrast floors — still muted (kept in sync with the front) */
48
+ --btn: #4a7a54; --btn-hover: #3f6a49; --btn-text: #ffffff;
49
+ --btn-bg: #474747; --btn-bg-hover: #515151; --btn-bg-active: #5b5b5b; --btn-edge: #9aa0a6;
50
+
51
+ --err: #ff9b9b; --danger: #ff9b9b; --warn: #ffb366; --ok: #7fd39a;
52
+ --focus: #8fc2f2; --ring: #8fc2f2;
53
+
54
+ --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='%23bcd8f0' stroke-width='1.6' stroke-linecap='round'/></svg>");
55
+ }
56
+ }
@@ -0,0 +1,170 @@
1
+ // upkeep.js — upkeep and the lifecycle: the output pane, drain, recover,
2
+ // log and dead letters; the confirmations for rotate, retire and move; and
3
+ // the inbox backlog panel.
4
+
5
+ // Which button filled the output pane, so clicking that one again closes it
6
+ // rather than re-fetching the same thing under an already-open panel.
7
+ let outputSource = null;
8
+
9
+ const OUTPUT_TITLES = { log: 'Log', deadletter: 'Dead letters', drain: 'Inbox drain', rebuild: 'Recovered posts' };
10
+ const output = (obj, source = null) => {
11
+ closePanels('output');
12
+ outputSource = source;
13
+ solWindow.show('output', OUTPUT_TITLES[source] || 'Output');
14
+ $('output').textContent = typeof obj === 'string' ? obj : JSON.stringify(obj, null, 2);
15
+ };
16
+
17
+ const showingFrom = (source) => outputSource === source && solWindow.openId() === 'output';
18
+
19
+ // Drain is the one that does work rather than reveals it, so a second click
20
+ // re-runs instead of closing. It can take a while — the agent joins a sweep
21
+ // already in flight and queues another after it — and a button that looks dead
22
+ // for a minute reads as broken.
23
+ $('do-drain').addEventListener('click', async (ev) => {
24
+ const b = ev.currentTarget;
25
+ b.disabled = true;
26
+ b.textContent = 'draining…';
27
+ say('draining the inbox — if a sweep is already running this waits for it to finish');
28
+ const r = await write('/drain', {}, 'inbox drained');
29
+ b.disabled = false;
30
+ b.textContent = 'Drain the inbox';
31
+ if (r) {
32
+ const box = r.inbox || {};
33
+ say(`inbox drained — ${box.count ?? 0} still waiting`);
34
+ output(r, 'drain');
35
+ }
36
+ });
37
+
38
+ // Like the drain, this does work rather than reveals it, so a second click
39
+ // re-runs. Nothing here can lose anything: it only adds posts back.
40
+ $('do-rebuild').addEventListener('click', async (ev) => {
41
+ const b = ev.currentTarget;
42
+ b.disabled = true;
43
+ b.textContent = 'recovering…';
44
+ say('reading what the pod still holds — one request per post, so this takes a moment');
45
+ const r = await write('/rebuild', {}, 'checked');
46
+ b.disabled = false;
47
+ b.textContent = 'Recover posts';
48
+ if (!r) return;
49
+ if (r.why) { say(r.why, 'err'); return; }
50
+ if (!r.landed) { say('recovered posts could NOT be saved — see the log', 'err'); return; }
51
+ say(r.recovered
52
+ ? `recovered ${r.recovered} post(s) the pod had and this machine did not`
53
+ : `nothing was missing — the pod indexed ${r.indexed} post(s), all of them already here`);
54
+ output(r, 'rebuild');
55
+ });
56
+
57
+ $('do-log').addEventListener('click', async () => {
58
+ if (showingFrom('log')) { closePanels(); return; }
59
+ const { json } = await api('/log');
60
+ output((json?.lines || []).slice(-60).join('\n') || 'nothing logged yet', 'log');
61
+ });
62
+
63
+ $('do-deadletter').addEventListener('click', async () => {
64
+ if (showingFrom('deadletter')) { closePanels(); return; }
65
+ const { json } = await api('/deadletter');
66
+ output(json?.items?.length ? json.items : 'no dead letters', 'deadletter');
67
+ });
68
+
69
+ // ---- lifecycle ----
70
+ // None of these can be taken back by clicking again, so the button only opens
71
+ // the matching warning in the markup; the second click is the one that acts.
72
+ // Retire also wants the handle typed, because a misclick cannot produce it.
73
+
74
+ const LIFECYCLE = {
75
+ 'rotate-key': { path: '/rotate-key', title: 'Rotate the signing key', done: (r) => (r.changed ? 'rotated and republished' : 'no change — the key was already fresh') },
76
+ retire: { path: '/retire', title: 'Retire this identity', go: 'Retire it', danger: true, done: (r) => `retired ${r.deletedAt}: Delete delivered to ${r.inboxes} inbox(es)` },
77
+ move: { path: '/move', title: 'Transfer this account away', go: 'Transfer it', focus: 'move-target',
78
+ done: (r) => `transferred to ${r.target}: Move delivered to ${r.inboxes} inbox(es), unfollowed ${r.unfollowed}/${r.following}` },
79
+ };
80
+ let pending = null;
81
+
82
+ const closeConfirm = () => closePanels();
83
+
84
+ for (const btn of document.querySelectorAll('[data-confirm]')) {
85
+ btn.addEventListener('click', () => {
86
+ const what = btn.dataset.confirm;
87
+ // The same button again closes its question rather than re-asking it.
88
+ if (pending === what && solWindow.openId() === 'confirm-form') { closePanels(); return; }
89
+ closePanels(); // including whatever else was open
90
+ pending = what;
91
+ const spec = LIFECYCLE[what];
92
+ $(`warn-${what}`).hidden = false;
93
+ solWindow.show('confirm-form', spec.title);
94
+ $('confirm-go').className = spec.danger ? 'danger' : 'primary';
95
+ $('confirm-go').textContent = spec.go || 'Confirm';
96
+ if (spec.focus) $(spec.focus).focus();
97
+ else if (what === 'retire') $('confirm-handle').focus();
98
+ });
99
+ }
100
+
101
+ // Offered from inside the retire warning: someone reading it has already said
102
+ // what they want ("not this account, here, any more") and these are the two
103
+ // answers that are not destruction. Switching panels rather than closing means
104
+ // they do not have to go and find the button themselves.
105
+ const openConfirm = (what) => {
106
+ closePanels(); // or `show` toggles: same panel, different warning
107
+ document.querySelector(`[data-confirm="${what}"]`).click();
108
+ };
109
+ // Park lives on the status control now; from inside the retire warning it is
110
+ // still one click — close the question and park.
111
+ $('go-park').addEventListener('click', () => { closePanels(); setStatus(true); });
112
+ $('go-move').addEventListener('click', () => openConfirm('move'));
113
+
114
+ $('confirm-cancel').addEventListener('click', () => { closeConfirm(); say('nothing changed'); });
115
+
116
+ $('confirm-form').addEventListener('submit', async (ev) => {
117
+ ev.preventDefault();
118
+ if (!pending) return;
119
+ const what = pending;
120
+ const body = what === 'retire' ? { confirm: $('confirm-handle').value.trim() }
121
+ : what === 'move' ? { target: $('move-target').value.trim(), confirm: $('confirm-handle-move').value.trim() }
122
+ // Only where the field is shown (the browser build — see index.html).
123
+ // On the Node agent the row stays hidden and nothing is sent, which is
124
+ // what that agent expects.
125
+ : what === 'rotate-key' && !$('rotate-pw-row').hidden
126
+ ? { password: $('rotate-password').value }
127
+ : {};
128
+ if (what === 'move' && !body.target) { say('name the account to transfer to', 'err'); return; }
129
+ $('confirm-go').disabled = true;
130
+ say(`${what} — this talks to the pod and to other servers, so it takes a moment`);
131
+ const r = await write(LIFECYCLE[what].path, body, what);
132
+ $('confirm-go').disabled = false;
133
+ if (!r) return; // write() already said why
134
+ closeConfirm();
135
+ say(LIFECYCLE[what].done(r));
136
+ load(); // mode, and whether it is retired, both changed
137
+ });
138
+
139
+ // ---- inbox ----
140
+ // Only appears when there is enough waiting to be worth a decision. The agent
141
+ // drains oldest-first regardless; this exists to let the owner say "do not
142
+ // bother with that fortnight", which is not a call an agent should make on
143
+ // someone's mail by itself.
144
+ const INBOX_PROMPT_AT = 500;
145
+ let dismissed = false;
146
+
147
+ async function renderInbox() {
148
+ if (dismissed) return;
149
+ const { json: st } = await api('/status');
150
+ const box = st?.inbox;
151
+ const panel = $('pane-inbox');
152
+ if (!box || box.count < INBOX_PROMPT_AT) { panel.hidden = true; return; }
153
+ const mb = box.bytes >= 1048576
154
+ ? `${(box.bytes / 1048576).toFixed(1)} MB` : `${Math.round(box.bytes / 1024)} kB`;
155
+ const since = box.oldest ? new Date(box.oldest).toLocaleDateString() : 'unknown';
156
+ $('inbox-summary').textContent =
157
+ `${box.count.toLocaleString()} deliveries waiting (${mb}), the oldest from ${since}.`;
158
+ // One request each to read and delete, and the agent holds itself to 60 a
159
+ // minute, so the honest number is minutes not seconds.
160
+ $('inbox-warn').hidden = box.count < 2000;
161
+ $('inbox-warn').textContent = box.count >= 2000
162
+ ? `At 60 requests a minute this is roughly ${Math.ceil(box.count * 2 / 60)} minutes of `
163
+ + 'draining if you keep everything. Discarding the old content is much quicker.'
164
+ : '';
165
+ panel.hidden = false;
166
+ }
167
+
168
+ // The gateway, as a facts row under software: attach through a multi-user
169
+ // front with this agent's own credential, detach back to the pod inbox. The
170
+ // forms live in the floating window, like every other disclosure.
@@ -0,0 +1,55 @@
1
+ /* window.css — the floating panel every page of ours puts a form in.
2
+ Shared like bar.css, so the three surfaces cannot drift apart. */
3
+
4
+ /* Fixed, so it floats over the page rather than pushing it about — which is
5
+ what the accordions did, moving the line you were reading every time one
6
+ opened. Resizing is the browser's own grip: `resize` needs `overflow` set to
7
+ anything but visible, which is also what keeps a long form inside the frame. */
8
+ #win {
9
+ position: fixed; z-index: 40;
10
+ top: 5rem; left: 50%; transform: translateX(-50%);
11
+ width: min(46rem, calc(100vw - 2rem));
12
+ /* Tall enough for what is in it and no taller: a fixed height made a short
13
+ panel roomy and a long one scroll for no reason. `auto` up to a cap, so it
14
+ only ever scrolls when the content genuinely will not fit the screen — and
15
+ the moment you drag the corner, that inline height wins over both. */
16
+ height: auto;
17
+ min-width: 18rem; min-height: 8rem;
18
+ max-width: 100vw; max-height: calc(100vh - 7rem);
19
+ resize: both; overflow: hidden;
20
+ display: flex; flex-direction: column;
21
+ /* Raised, like the bar: it sits ON the page, so it takes the same colour the
22
+ bar does rather than the page's own. The shadow lifts it; the colour says
23
+ which layer it belongs to. */
24
+ background: var(--raised); color: CanvasText;
25
+ border: 1px solid var(--btn-edge); border-radius: .5rem;
26
+ box-shadow: 0 .6rem 2rem #0004;
27
+ }
28
+ /* Dragged by the bar, so the grab area is obvious and a text selection inside
29
+ the form never moves the window. */
30
+ /* Its own title bar keeps a rule: this one IS two surfaces meeting inside a
31
+ single small box, where a tone change alone is too quiet to read as a grab
32
+ handle. Different problem from the page bar, different answer. */
33
+ #win-bar {
34
+ flex: 0 0 auto; display: flex; align-items: baseline; gap: .6rem;
35
+ padding: .45rem .7rem; cursor: move; user-select: none;
36
+ border-bottom: 1px solid var(--btn-edge); border-radius: .5rem .5rem 0 0;
37
+ background: var(--surface);
38
+ }
39
+ #win-title { flex: 1 1 auto; font-weight: 600; color: var(--heading); }
40
+ /* 24x24 is the 2.5.8 minimum; at .1rem of vertical padding this came out
41
+ 21.6px tall, which is a small target for the one control that dismisses the
42
+ thing covering your page. Same glyph, same place, bigger hit area. */
43
+ #win-close {
44
+ font: inherit; line-height: 1; cursor: pointer;
45
+ min-width: 24px; min-height: 24px; padding: .2rem .5rem;
46
+ display: inline-flex; align-items: center; justify-content: center;
47
+ border: 0; background: none; color: var(--link);
48
+ }
49
+ /* The window scrolls, never the page — same rule the rest of the app follows. */
50
+ #win-body { flex: 1 1 auto; min-height: 0; overflow: auto; padding: .2rem 1rem 1rem; }
51
+ #win-body > * { margin-top: .6rem; }
52
+ #win-body pre { overflow-x: auto; background: #0001; padding: .6rem; border-radius: .3rem;
53
+ font-size: .9rem; }
54
+ /* Dragging with the pointer over the frame would otherwise select text inside it. */
55
+ body.dragging { user-select: none; }