fedipod-server 0.21.0 → 0.28.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 (332) hide show
  1. package/README.md +5 -4
  2. package/dist/claims.d.ts +3 -1
  3. package/dist/claims.js +20 -7
  4. package/dist/components/context.jsonld +6 -0
  5. package/dist/handler.d.ts +29 -1
  6. package/dist/handler.js +89 -4
  7. package/dist/handler.jsonld +36 -1
  8. package/lib/client/c2s.mjs +100 -3
  9. package/lib/client/masto/accounts.mjs +44 -4
  10. package/lib/client/masto/body.mjs +90 -3
  11. package/lib/client/masto/index.mjs +5 -1
  12. package/lib/client/masto/instance.mjs +11 -3
  13. package/lib/client/masto/media.mjs +3 -57
  14. package/lib/client/masto/oauth.mjs +66 -9
  15. package/lib/client/masto/render.mjs +62 -5
  16. package/lib/client/masto/statuses.mjs +25 -1
  17. package/lib/client/masto/timelines.mjs +128 -6
  18. package/lib/connections/tagfeed.mjs +42 -5
  19. package/lib/core/as2.mjs +36 -5
  20. package/lib/core/contexts/index.mjs +14 -0
  21. package/lib/core/contexts/litepub-0.1.json +60 -0
  22. package/lib/core/contexts/map.json +3 -1
  23. package/lib/core/contexts/quotes.json +18 -0
  24. package/lib/core/graphview.mjs +41 -3
  25. package/lib/core/intake/activities.mjs +136 -2
  26. package/lib/core/intake/activity.mjs +79 -1
  27. package/lib/core/intake/group.mjs +37 -3
  28. package/lib/core/intake/index.mjs +32 -2
  29. package/lib/core/intake/notes.mjs +60 -3
  30. package/lib/core/lease.mjs +12 -2
  31. package/lib/core/profile-page.mjs +104 -0
  32. package/lib/core/publisher/collections.mjs +6 -1
  33. package/lib/core/publisher/index.mjs +35 -17
  34. package/lib/core/publisher/notes.mjs +104 -3
  35. package/lib/core/shapes/activitystreams.ttl +28 -0
  36. package/lib/core/shapes/index.mjs +26 -1
  37. package/lib/core/wire-quotes.mjs +71 -0
  38. package/lib/core/wire.mjs +59 -103
  39. package/lib/device/admin/routes/lifecycle.mjs +9 -0
  40. package/lib/device/admin/static.mjs +4 -1
  41. package/lib/device/admin/surface.mjs +26 -2
  42. package/lib/device/certs.mjs +18 -2
  43. package/lib/device/cli/commands/setup.mjs +6 -2
  44. package/lib/device/cli/commands/state.mjs +3 -1
  45. package/lib/device/gateway-move.mjs +58 -0
  46. package/lib/device/migrate.mjs +3 -1
  47. package/lib/device/setup.mjs +67 -7
  48. package/lib/gateway/front-core.mjs +307 -36
  49. package/lib/gateway/gateway-core.mjs +15 -6
  50. package/lib/gateway/notices.mjs +84 -0
  51. package/lib/gateway/quiet.mjs +184 -0
  52. package/lib/pod/root.mjs +6 -3
  53. package/lib/pod/state.mjs +8 -7
  54. package/lib/pod/transport.mjs +5 -1
  55. package/lib/session/README.md +123 -0
  56. package/lib/session/demo.html +94 -0
  57. package/lib/session/fedi-account.mjs +346 -0
  58. package/lib/session/fedi-login.mjs +123 -0
  59. package/lib/session/oidc-session.mjs +233 -0
  60. package/lib/session/package.json +41 -0
  61. package/package.json +1 -1
  62. package/run-agent.mjs +28 -0
  63. package/vendor/gate.cjs +6 -3
  64. package/web/admin/bar.css +66 -0
  65. package/web/admin/bar.js +38 -0
  66. package/web/admin/client/index.html +31 -3
  67. package/web/admin/client-pick.js +79 -0
  68. package/web/admin/gateway.js +22 -0
  69. package/web/admin/index.html +54 -13
  70. package/web/admin/notices-bar.js +90 -0
  71. package/web/admin/oauth-signin.mjs +37 -0
  72. package/web/admin/record.js +13 -1
  73. package/web/admin/setup/index.html +31 -3
  74. package/web/admin/tokens.css +15 -3
  75. package/web/admin/upkeep.js +4 -5
  76. package/web/app/README.md +14 -10
  77. package/web/app/admin-facade.mjs +51 -12
  78. package/web/app/agent.mjs +157 -19
  79. package/web/app/authorize_interaction.html +56 -0
  80. package/web/app/authorize_interaction.js +69 -0
  81. package/web/app/boot.mjs +233 -95
  82. package/web/app/dist/boot.js +477 -569
  83. package/web/app/dist/boot.js.map +4 -4
  84. package/web/app/dist/sw.js +1861 -532
  85. package/web/app/dist/sw.js.map +4 -4
  86. package/web/app/gateway-move.mjs +60 -0
  87. package/web/app/index.html +40 -49
  88. package/web/app/keys-browser.mjs +12 -15
  89. package/web/app/keystore.mjs +19 -36
  90. package/web/app/oidc-session.mjs +5 -188
  91. package/web/app/signup.mjs +172 -132
  92. package/web/app/sw-src.mjs +1 -1
  93. package/web/app/update.js +66 -0
  94. package/web/front/admin.html +5 -3
  95. package/web/front/new-account.html +2 -2
  96. package/web/front/notices.html +69 -0
  97. package/web/front/notices.js +107 -0
  98. package/web/front/run.html +15 -8
  99. package/web/front/run.js +124 -36
  100. package/web/icons/forum.svg +11 -0
  101. package/web/icons/gateway.svg +14 -0
  102. package/web/icons/local.svg +11 -0
  103. package/web/app/index.html~ +0 -152
  104. package/web/app/site/_headers +0 -9
  105. package/web/app/site/_redirects +0 -22
  106. package/web/app/site/admin/actors.js +0 -145
  107. package/web/app/site/admin/bar.css +0 -60
  108. package/web/app/site/admin/bar.js +0 -61
  109. package/web/app/site/admin/client/client.js +0 -66
  110. package/web/app/site/admin/client/index.html +0 -43
  111. package/web/app/site/admin/common.js +0 -23
  112. package/web/app/site/admin/connections.js +0 -112
  113. package/web/app/site/admin/gateway.js +0 -111
  114. package/web/app/site/admin/group.js +0 -258
  115. package/web/app/site/admin/index.html +0 -564
  116. package/web/app/site/admin/record.js +0 -378
  117. package/web/app/site/admin/setup/index.html +0 -215
  118. package/web/app/site/admin/setup/setup.js +0 -326
  119. package/web/app/site/admin/tokens.css +0 -56
  120. package/web/app/site/admin/upkeep.js +0 -178
  121. package/web/app/site/admin/window.css +0 -55
  122. package/web/app/site/admin/window.js +0 -136
  123. package/web/app/site/app/404.html +0 -40
  124. package/web/app/site/app/_headers +0 -2
  125. package/web/app/site/app/apple-touch-icon.png +0 -0
  126. package/web/app/site/app/assets/ICONS-DIPJeU0C.js +0 -2
  127. package/web/app/site/app/assets/ICONS-DIPJeU0C.js.map +0 -1
  128. package/web/app/site/app/assets/ScheduledAtField-D-Abfo4J.js +0 -183
  129. package/web/app/site/app/assets/ScheduledAtField-D-Abfo4J.js.map +0 -1
  130. package/web/app/site/app/assets/Temml-CCjoacWt.woff2 +0 -0
  131. package/web/app/site/app/assets/__vite-browser-external-C7iut881.js +0 -2
  132. package/web/app/site/app/assets/__vite-browser-external-C7iut881.js.map +0 -1
  133. package/web/app/site/app/assets/boosts-carousel-BiOaNdMT.jpg +0 -0
  134. package/web/app/site/app/assets/catch-up-O5q5C75Z.png +0 -0
  135. package/web/app/site/app/assets/chunk-aKtaBQYM.js +0 -1
  136. package/web/app/site/app/assets/compose-CFEx7wb4.js +0 -2
  137. package/web/app/site/app/assets/compose-CFEx7wb4.js.map +0 -1
  138. package/web/app/site/app/assets/compose-ZJN5xSb4.js +0 -35
  139. package/web/app/site/app/assets/compose-ZJN5xSb4.js.map +0 -1
  140. package/web/app/site/app/assets/debug.module-YFQgwnIr.js +0 -52
  141. package/web/app/site/app/assets/debug.module-YFQgwnIr.js.map +0 -1
  142. package/web/app/site/app/assets/dom-p1YcJ5Rw.js +0 -2
  143. package/web/app/site/app/assets/dom-p1YcJ5Rw.js.map +0 -1
  144. package/web/app/site/app/assets/home-mobile-dark@2x-Dc-J8-SM.png +0 -0
  145. package/web/app/site/app/assets/home-mobile-light@2x-Bd3fod8m.png +0 -0
  146. package/web/app/site/app/assets/home-tablet-dark@2x-BgcNI8le.png +0 -0
  147. package/web/app/site/app/assets/home-tablet-light@2x-BW2RV9T6.png +0 -0
  148. package/web/app/site/app/assets/icons/add-circle-line-WD0mwORD.js +0 -1
  149. package/web/app/site/app/assets/icons/alert-line-CpFUMf9s.js +0 -1
  150. package/web/app/site/app/assets/icons/android-2-line-Bzxnrd3S.js +0 -1
  151. package/web/app/site/app/assets/icons/announcement-line-5nwaJFJm.js +0 -1
  152. package/web/app/site/app/assets/icons/arrow-down-circle-line-a7Id9-Iv.js +0 -1
  153. package/web/app/site/app/assets/icons/arrow-down-line-B8uzXzq6.js +0 -1
  154. package/web/app/site/app/assets/icons/arrow-left-line-CGrGT9C-.js +0 -1
  155. package/web/app/site/app/assets/icons/arrow-right-line-CE-HERfX.js +0 -1
  156. package/web/app/site/app/assets/icons/arrow-to-up-line-CvdQjmcy.js +0 -1
  157. package/web/app/site/app/assets/icons/arrow-up-circle-line-DEparSNF.js +0 -1
  158. package/web/app/site/app/assets/icons/arrow-up-line-C7yBeugc.js +0 -1
  159. package/web/app/site/app/assets/icons/arrows-right-line-BtbXmJGP.js +0 -1
  160. package/web/app/site/app/assets/icons/at-line-CF66Bznk.js +0 -1
  161. package/web/app/site/app/assets/icons/attachment-line-BY8It-l9.js +0 -1
  162. package/web/app/site/app/assets/icons/blockquote-line-BS6pU_hm.js +0 -1
  163. package/web/app/site/app/assets/icons/board-line-C0UWXyA6.js +0 -1
  164. package/web/app/site/app/assets/icons/bookmark-line-1CtX5JLF.js +0 -1
  165. package/web/app/site/app/assets/icons/building-5-line-Dd59MzbO.js +0 -1
  166. package/web/app/site/app/assets/icons/bus-2-line-6_dvA0GG.js +0 -1
  167. package/web/app/site/app/assets/icons/calendar-day-line-Crpl_fmu.js +0 -1
  168. package/web/app/site/app/assets/icons/calendar-month-line-vommts2C.js +0 -1
  169. package/web/app/site/app/assets/icons/calendar-time-add-line-BU_DTlQk.js +0 -1
  170. package/web/app/site/app/assets/icons/camera-line-Cv69MEei.js +0 -1
  171. package/web/app/site/app/assets/icons/celebrate-line-B8DvIoi0.js +0 -1
  172. package/web/app/site/app/assets/icons/chart-bar-line-FK-QOmtF.js +0 -1
  173. package/web/app/site/app/assets/icons/chart-line-line-BGcJAFWw.js +0 -1
  174. package/web/app/site/app/assets/icons/chat-3-line-YFK2-hwz.js +0 -1
  175. package/web/app/site/app/assets/icons/check-circle-line-DsAxFr5-.js +0 -1
  176. package/web/app/site/app/assets/icons/clipboard-line-BBp364gJ.js +0 -1
  177. package/web/app/site/app/assets/icons/close-circle-line-CICdiuGz.js +0 -1
  178. package/web/app/site/app/assets/icons/close-line-DYrFWcjv.js +0 -1
  179. package/web/app/site/app/assets/icons/cloud-line-tiQAbah8.js +0 -1
  180. package/web/app/site/app/assets/icons/code-line-fAl8iiV3.js +0 -1
  181. package/web/app/site/app/assets/icons/comment-2-line-CR3g7WeB.js +0 -1
  182. package/web/app/site/app/assets/icons/copy-2-line-CLW69yF5.js +0 -1
  183. package/web/app/site/app/assets/icons/delete-2-line-B8xmbr4n.js +0 -1
  184. package/web/app/site/app/assets/icons/document-3-line-DBGNhAg2.js +0 -1
  185. package/web/app/site/app/assets/icons/document-line-C7mb9sb7.js +0 -1
  186. package/web/app/site/app/assets/icons/down-line-BGqSSThd.js +0 -1
  187. package/web/app/site/app/assets/icons/edit-4-line-DcgGaEhx.js +0 -1
  188. package/web/app/site/app/assets/icons/emoji-2-line-D8YgnM-y.js +0 -1
  189. package/web/app/site/app/assets/icons/exit-line-Dc07BS-x.js +0 -1
  190. package/web/app/site/app/assets/icons/external-link-line-BvpSkmjO.js +0 -1
  191. package/web/app/site/app/assets/icons/eye-2-line-CiWXx-f_.js +0 -1
  192. package/web/app/site/app/assets/icons/eye-close-line-BgTaSJ4P.js +0 -1
  193. package/web/app/site/app/assets/icons/filter-2-line-B_mR2usy.js +0 -1
  194. package/web/app/site/app/assets/icons/filter-line-C_nm-3cU.js +0 -1
  195. package/web/app/site/app/assets/icons/flag-1-line-C2HB05I0.js +0 -1
  196. package/web/app/site/app/assets/icons/forbid-circle-line-D3LTT9yd.js +0 -1
  197. package/web/app/site/app/assets/icons/formula-line-DLrt2d6j.js +0 -1
  198. package/web/app/site/app/assets/icons/grid-line-CgUmEikf.js +0 -1
  199. package/web/app/site/app/assets/icons/group-line-C5fnX0wU.js +0 -1
  200. package/web/app/site/app/assets/icons/hand-finger-2-line-BGgxbuL9.js +0 -1
  201. package/web/app/site/app/assets/icons/hashtag-line-CyTEEXBb.js +0 -1
  202. package/web/app/site/app/assets/icons/heart-crack-line-BZddbO2C.js +0 -1
  203. package/web/app/site/app/assets/icons/heart-line-CJ7K47J4.js +0 -1
  204. package/web/app/site/app/assets/icons/history-2-line-kWWvFzqv.js +0 -1
  205. package/web/app/site/app/assets/icons/history-line-bGQiwbTH.js +0 -1
  206. package/web/app/site/app/assets/icons/home-3-line-fRU1zeLG.js +0 -1
  207. package/web/app/site/app/assets/icons/information-line-iNk6-sOY.js +0 -1
  208. package/web/app/site/app/assets/icons/keyboard-line-DsCyuQNz.js +0 -1
  209. package/web/app/site/app/assets/icons/layout-4-line-CZkOX52Z.js +0 -1
  210. package/web/app/site/app/assets/icons/layout-5-line-B_znt5wO.js +0 -1
  211. package/web/app/site/app/assets/icons/left-line-eUw0tya4.js +0 -1
  212. package/web/app/site/app/assets/icons/lightning-line-DaKoDdix.js +0 -1
  213. package/web/app/site/app/assets/icons/link-2-line-DCqLmMA7.js +0 -1
  214. package/web/app/site/app/assets/icons/list-check-line-BAxosBh9.js +0 -1
  215. package/web/app/site/app/assets/icons/lock-line-CPoaXeUW.js +0 -1
  216. package/web/app/site/app/assets/icons/mail-line-B0P8aa1L.js +0 -1
  217. package/web/app/site/app/assets/icons/moon-line-CEqNAToP.js +0 -1
  218. package/web/app/site/app/assets/icons/more-1-fill-Dw2e5xab.js +0 -1
  219. package/web/app/site/app/assets/icons/more-3-line-BIiVjYTz.js +0 -1
  220. package/web/app/site/app/assets/icons/notification-line-BZZq2Gtu.js +0 -1
  221. package/web/app/site/app/assets/icons/pencil-line-CKh8-A1v.js +0 -1
  222. package/web/app/site/app/assets/icons/photo-album-line-Bnfg2u40.js +0 -1
  223. package/web/app/site/app/assets/icons/pin-line-DpTolyhs.js +0 -1
  224. package/web/app/site/app/assets/icons/play-fill-B0-G_8lX.js +0 -1
  225. package/web/app/site/app/assets/icons/qrcode-2-line-B6SPCaHn.js +0 -1
  226. package/web/app/site/app/assets/icons/quill-pen-line-B6LUrnCP.js +0 -1
  227. package/web/app/site/app/assets/icons/quote-left-fill-hQZ1cj_6.js +0 -1
  228. package/web/app/site/app/assets/icons/radar-line-CKyroTth.js +0 -1
  229. package/web/app/site/app/assets/icons/react-line-CoIKhhtK.js +0 -1
  230. package/web/app/site/app/assets/icons/refresh-2-line-D0mTF97S.js +0 -1
  231. package/web/app/site/app/assets/icons/right-line-B3KWJaqc.js +0 -1
  232. package/web/app/site/app/assets/icons/rocket-line-4KZl537D.js +0 -1
  233. package/web/app/site/app/assets/icons/round-fill-C69NTrZ0.js +0 -1
  234. package/web/app/site/app/assets/icons/round-line-B9k_1uo-.js +0 -1
  235. package/web/app/site/app/assets/icons/route-line-B28ReHcE.js +0 -1
  236. package/web/app/site/app/assets/icons/rows-4-line-CrBniagM.js +0 -1
  237. package/web/app/site/app/assets/icons/scan-line-DSf74YdP.js +0 -1
  238. package/web/app/site/app/assets/icons/search-2-line-B6geIU1z.js +0 -1
  239. package/web/app/site/app/assets/icons/settings-3-line-azJwK47w.js +0 -1
  240. package/web/app/site/app/assets/icons/settings-6-line-C_5zc5YR.js +0 -1
  241. package/web/app/site/app/assets/icons/share-2-line-D5MxWjrm.js +0 -1
  242. package/web/app/site/app/assets/icons/share-forward-line-Cp61fZzZ.js +0 -1
  243. package/web/app/site/app/assets/icons/sparkles-2-line-DYYyXTaW.js +0 -1
  244. package/web/app/site/app/assets/icons/sparkles-line-C2hY1s6I.js +0 -1
  245. package/web/app/site/app/assets/icons/time-line-BF3eet1v.js +0 -1
  246. package/web/app/site/app/assets/icons/transfer-4-line-oisfvTBw.js +0 -1
  247. package/web/app/site/app/assets/icons/translate-line-BFWol2Ce.js +0 -1
  248. package/web/app/site/app/assets/icons/unlock-line-X_x5vwv3.js +0 -1
  249. package/web/app/site/app/assets/icons/upload-3-line-DgzwUJeW.js +0 -1
  250. package/web/app/site/app/assets/icons/user-4-line-BVSynZCp.js +0 -1
  251. package/web/app/site/app/assets/icons/user-add-2-line-BlGS5Q8q.js +0 -1
  252. package/web/app/site/app/assets/icons/user-add-line-BbXdHG4z.js +0 -1
  253. package/web/app/site/app/assets/icons/user-edit-line-BFAHOV0X.js +0 -1
  254. package/web/app/site/app/assets/icons/user-follow-line-BXux0wSd.js +0 -1
  255. package/web/app/site/app/assets/icons/user-star-line-DdYlpHLU.js +0 -1
  256. package/web/app/site/app/assets/icons/user-warning-line-Cj43j0_E.js +0 -1
  257. package/web/app/site/app/assets/icons/user-x-line-DIDgNlPH.js +0 -1
  258. package/web/app/site/app/assets/icons/volume-line-Bs390KQt.js +0 -1
  259. package/web/app/site/app/assets/icons/volume-mute-line-DzlzIqFZ.js +0 -1
  260. package/web/app/site/app/assets/icons/walk-line-yNXfB-md.js +0 -1
  261. package/web/app/site/app/assets/icons/world-2-line-CenkGIwj.js +0 -1
  262. package/web/app/site/app/assets/icons/zoom-in-line-CaENUphf.js +0 -1
  263. package/web/app/site/app/assets/icons/zoom-out-line-BDNk0EWo.js +0 -1
  264. package/web/app/site/app/assets/instances-BNPptnn-.json +0 -333
  265. package/web/app/site/app/assets/locales/ar-SA-CfheRbCu.js +0 -1
  266. package/web/app/site/app/assets/locales/ca-ES-DHRW86Tz.js +0 -1
  267. package/web/app/site/app/assets/locales/cs-CZ-CKoqEuA1.js +0 -1
  268. package/web/app/site/app/assets/locales/de-DE-ByRPMRFy.js +0 -1
  269. package/web/app/site/app/assets/locales/eo-UY-CPC7wLCS.js +0 -1
  270. package/web/app/site/app/assets/locales/es-ES-dJa0Fd9t.js +0 -1
  271. package/web/app/site/app/assets/locales/eu-ES-C3djlPR2.js +0 -1
  272. package/web/app/site/app/assets/locales/fa-IR-DTT490Ck.js +0 -1
  273. package/web/app/site/app/assets/locales/fi-FI-B23MCPuz.js +0 -1
  274. package/web/app/site/app/assets/locales/fr-FR-BcaQsO5C.js +0 -1
  275. package/web/app/site/app/assets/locales/gl-ES-BR-CVKBN.js +0 -1
  276. package/web/app/site/app/assets/locales/he-IL-CfheRbCu.js +0 -1
  277. package/web/app/site/app/assets/locales/hu-HU-CfheRbCu.js +0 -1
  278. package/web/app/site/app/assets/locales/it-IT-C1136UFV.js +0 -1
  279. package/web/app/site/app/assets/locales/ja-JP-CRZmRAhD.js +0 -1
  280. package/web/app/site/app/assets/locales/kab-Cz8xH_TT.js +0 -1
  281. package/web/app/site/app/assets/locales/ko-KR-CG9UiY4o.js +0 -1
  282. package/web/app/site/app/assets/locales/lt-LT-CJcI6sHZ.js +0 -1
  283. package/web/app/site/app/assets/locales/nb-NO-CfheRbCu.js +0 -1
  284. package/web/app/site/app/assets/locales/nl-NL-DxN4Yc1t.js +0 -1
  285. package/web/app/site/app/assets/locales/oc-FR-CfheRbCu.js +0 -1
  286. package/web/app/site/app/assets/locales/pl-PL-DkwGXf6P.js +0 -1
  287. package/web/app/site/app/assets/locales/pseudo-LOCALE-CQ4XxkQ0.js +0 -1
  288. package/web/app/site/app/assets/locales/pt-BR-nGL0HdFS.js +0 -1
  289. package/web/app/site/app/assets/locales/pt-PT-Cp17aj2z.js +0 -1
  290. package/web/app/site/app/assets/locales/ru-RU-0Y8cIoaK.js +0 -1
  291. package/web/app/site/app/assets/locales/th-TH-CfheRbCu.js +0 -1
  292. package/web/app/site/app/assets/locales/tok-CfheRbCu.js +0 -1
  293. package/web/app/site/app/assets/locales/tr-TR-CfheRbCu.js +0 -1
  294. package/web/app/site/app/assets/locales/uk-UA-C5E3dFBf.js +0 -1
  295. package/web/app/site/app/assets/locales/zh-CN-BHJi7_et.js +0 -1
  296. package/web/app/site/app/assets/locales/zh-TW-CfheRbCu.js +0 -1
  297. package/web/app/site/app/assets/main-BdqNbG-a.js +0 -26
  298. package/web/app/site/app/assets/main-BdqNbG-a.js.map +0 -1
  299. package/web/app/site/app/assets/mock-home-DGhR__He.js +0 -2
  300. package/web/app/site/app/assets/mock-home-DGhR__He.js.map +0 -1
  301. package/web/app/site/app/assets/multi-column-ROck0NVt.jpg +0 -0
  302. package/web/app/site/app/assets/multi-hashtag-timeline-DH5INVHi.jpg +0 -0
  303. package/web/app/site/app/assets/nested-comments-thread-hv59kZLc.jpg +0 -0
  304. package/web/app/site/app/assets/polyfill-force-H8sUkLGp.js +0 -2
  305. package/web/app/site/app/assets/polyfill-force-H8sUkLGp.js.map +0 -1
  306. package/web/app/site/app/assets/pwa-viewport-ogKbF51K.js +0 -20
  307. package/web/app/site/app/assets/pwa-viewport-ogKbF51K.js.map +0 -1
  308. package/web/app/site/app/assets/style-B1iLPTPR.css +0 -2
  309. package/web/app/site/app/assets/temml-DuyIQMei.js +0 -97
  310. package/web/app/site/app/assets/temml-DuyIQMei.js.map +0 -1
  311. package/web/app/site/app/assets/tinyld.light.node-DtbBhLYC.js +0 -2
  312. package/web/app/site/app/assets/tinyld.light.node-DtbBhLYC.js.map +0 -1
  313. package/web/app/site/app/assets/year-in-posts-B2lywqdx.png +0 -0
  314. package/web/app/site/app/assets/year-in-posts-DJv_star.js +0 -3
  315. package/web/app/site/app/assets/year-in-posts-DJv_star.js.map +0 -1
  316. package/web/app/site/app/compose/index.html +0 -20
  317. package/web/app/site/app/favicon.ico +0 -0
  318. package/web/app/site/app/index.html +0 -73
  319. package/web/app/site/app/logo-192.png +0 -0
  320. package/web/app/site/app/logo-512.png +0 -0
  321. package/web/app/site/app/logo-badge-72.png +0 -0
  322. package/web/app/site/app/logo-maskable-512.png +0 -0
  323. package/web/app/site/app/logo-monochrome-512.png +0 -0
  324. package/web/app/site/app/logo-monochrome-maskable-512.png +0 -0
  325. package/web/app/site/app/manifest.webmanifest +0 -1
  326. package/web/app/site/app/og-image-2.jpg +0 -0
  327. package/web/app/site/app/og-image.png +0 -0
  328. package/web/app/site/app/robots.txt +0 -2
  329. package/web/app/site/app/version.json +0 -1
  330. package/web/app/site/boot.js +0 -34277
  331. package/web/app/site/index.html +0 -251
  332. package/web/app/site/sw.js +0 -71331
@@ -1,26 +1,26 @@
1
- // signup.mjs — the setup flow of `fedipod setup`, run in the browser. It does
2
- // what lib/setup.mjs's runSetup does up to the publish step: create the account
3
- // and pod (or accept one you bring), mint a credential, make the signing keys
4
- // and lock them on the pod under the password. Publishing the actor is the
5
- // agent's first-run job, exactly as `connect()` publishes after `setup` mints —
6
- // so this module needs nothing from lib.
1
+ // signup.mjs — the setup flow of `fedipod setup`, run in the browser, on the
2
+ // pod session the person already holds. The pod exists before this runs: a
3
+ // new one is made on the provider's own sign-up page, an existing one is
4
+ // signed in to as it is. So there is no account API here, no credential and
5
+ // no password — the session that signed in at the pod is the session that
6
+ // writes to it. Publishing the actor is the agent's first-run job, exactly as
7
+ // `connect()` publishes after `setup` mints — so this module needs nothing
8
+ // from lib beyond the pod helpers.
7
9
  //
8
- // It returns the two things a browser must keep for itself: the credential and
9
- // the config. Neither is secret to the pod owner, but the credential is a pod
10
- // write key, so the caller stores it in this browser only, never on the pod.
11
- // The only thing this writes to the pod is the password-wrapped signing key.
10
+ // What it writes to the pod: an owner-only state container holding the
11
+ // signing key and the account config; and, with a gateway, the attach.
12
12
 
13
- import { createAccountWithPod, mintCredential, makeDpopSession, revokeCredential } from './pod-auth.mjs';
14
- import { generateKeys, wrapKeys } from './keystore.mjs';
13
+ import { generateKeys, isKeyEnvelope } from './keystore.mjs';
15
14
  import { BrowserRemotePod } from './pod-remote.mjs';
16
15
  import * as podState from '../../lib/pod/state.mjs';
17
16
  import { resourceExists } from '../../lib/pod/root.mjs';
17
+ import { podBaseOfWebId } from '../../lib/pod/urls.mjs';
18
18
  import { cacheOpenedKeys } from './keys-browser.mjs';
19
19
 
20
20
  // The container everything the agent publishes hangs under. New pods made here
21
21
  // use `fedipod/`; new installs default to `fedipod/` too, and older ones keep
22
22
  // that predate this, so those pods are untouched. The name is stored on the
23
- // credential and the config, so the agent reads it rather than guessing.
23
+ // config, so the agent reads it rather than guessing.
24
24
  export const AP_ROOT = 'fedipod/';
25
25
  const actorUrlFor = (pod) => `${pod}${AP_ROOT}ap/actor`;
26
26
  const keysDocFor = (pod) => `${pod}${AP_ROOT}ap-state/keys.json`;
@@ -35,29 +35,15 @@ export function handleProblem(handle) {
35
35
  }
36
36
 
37
37
  // Resume across a failed attempt. A run that throws part-way keeps what it
38
- // already achieved in this page session — the pod it made, the credential it
39
- // minted, the key it wrote — so a second attempt (the failure screen's Try
40
- // again) continues from the first unfinished step instead of restarting. The
41
- // record lives only in memory here: no secret is written to disk, and it is
42
- // dropped the moment the run completes. Keyed by the identity being built, so
43
- // changing the handle or pod on the form starts a clean attempt.
38
+ // already achieved in this page session — the gateway it attached, the key it
39
+ // wrote — so a second attempt (the failure screen's Try again) continues from
40
+ // the first unfinished step instead of restarting. The record lives only in
41
+ // memory here and is dropped the moment the run completes. Keyed by the
42
+ // identity being built, so changing the handle on the form starts a clean
43
+ // attempt.
44
44
  const PROGRESS = new Map();
45
- const progressKey = (a) => [a.issuer, a.mode, a.handle, a.mode === 'new' ? (a.podName || a.handle) : a.pod].join('|');
45
+ const progressKey = (webId, a) => [webId, a.handle, a.shape || 'pod'].join('|');
46
46
 
47
- /**
48
- * Run sign-up. Steps are reported through onStep(key, state, note) so a page can
49
- * draw the same tick list the CLI setup shows. Resumable: see PROGRESS above.
50
- *
51
- * answers: { mode:'new'|'existing', issuer, email, password, handle,
52
- * podName?, pod?, gateway?, shape?: 'pod'|'front' }
53
- *
54
- * `shape` is where the address lives. On the pod, `@handle@yourpod`, with the
55
- * gateway as a mail door only — the default. At the gateway, `@handle@front`,
56
- * a fronted identity whose documents still live on the pod. A pod on a path of
57
- * a suffix-based host cannot answer WebFinger, so it is fronted whatever was asked.
58
- *
59
- * returns: { credential, config, actorUrl, address, keysPublic }
60
- */
61
47
  /** A fronted name is one per gateway; a taken one is refused before anything is made. */
62
48
  async function assertFrontNameFree(frontOrigin, handle) {
63
49
  const res = await fetch(`${frontOrigin.replace(/\/$/, '')}/api/handle?handle=${encodeURIComponent(handle)}`,
@@ -67,19 +53,35 @@ async function assertFrontNameFree(frontOrigin, handle) {
67
53
  if (!d.available) throw new Error(d.reason || `the name @${handle}@${new URL(frontOrigin).host} is taken — choose another handle`);
68
54
  }
69
55
 
70
- export async function signUp(answers, { onStep = () => {}, frontOrigin = null } = {}) {
71
- const { mode, issuer, email, password, handle } = answers;
56
+ /**
57
+ * Run sign-up on a pod session. Steps are reported through onStep(key, state,
58
+ * note) so a page can draw the same tick list the CLI setup shows. Resumable:
59
+ * see PROGRESS above.
60
+ *
61
+ * answers: { handle, shape?: 'pod'|'front', gateway? }
62
+ * session: { webId, issuer, fetch } — the Solid-OIDC session from the pod's
63
+ * own login (oidc-session.mjs). The pod is where the WebID lives.
64
+ *
65
+ * `shape` is where the address lives. On the pod, `@handle@yourpod`, with the
66
+ * gateway as a mail door only — the default. At the gateway, `@handle@front`,
67
+ * a fronted identity whose documents still live on the pod. A pod on a path of
68
+ * a suffix-based host cannot answer WebFinger, so it is fronted whatever was asked.
69
+ *
70
+ * returns: { config, actorUrl, address, pod, keys, keysPublic }
71
+ */
72
+ export async function signUp(answers, { session, onStep = () => {}, frontOrigin = null } = {}) {
73
+ const { handle } = answers;
72
74
  const bad = handleProblem(handle);
73
75
  if (bad) throw new Error(bad);
74
- if (!email) throw new Error('an email is required'); // recovery + account login
75
- if (!password) throw new Error('a password is required');
76
- if (mode === 'existing' && !answers.pod) throw new Error('a pod address is required');
76
+ if (!session?.webId || typeof session.fetch !== 'function') throw new Error('sign in at your pod first');
77
77
  const wantsFront = answers.shape === 'front';
78
78
  if (wantsFront && !frontOrigin) throw new Error('an address at the gateway needs a gateway, and this page has none');
79
79
  // A name at the gateway is one per gateway: settle it before making anything.
80
80
  if (wantsFront) await assertFrontNameFree(frontOrigin, handle);
81
81
 
82
- const key = progressKey(answers);
82
+ const webId = session.webId;
83
+ const pod = podBaseOfWebId(webId); // its own host, or a path on a suffix-based one
84
+ const key = progressKey(webId, answers);
83
85
  const prog = PROGRESS.get(key) || {};
84
86
  PROGRESS.set(key, prog); // resume record for this identity
85
87
 
@@ -89,28 +91,16 @@ export async function signUp(answers, { onStep = () => {}, frontOrigin = null }
89
91
  skip: (note) => onStep(key, 'skipped', note),
90
92
  });
91
93
 
92
- // --- account + pod --- (skipped outright once a prior attempt has made it)
93
- let accountToken = null; // valid only within this call
94
- const acct = step('account');
94
+ // --- the pod --- (the session came from it; what is checked is that it has no account yet)
95
+ const podStep = step('pod');
95
96
  if (!prog.pod) {
96
- if (mode === 'new') {
97
- acct.running('creating the account and pod');
98
- const made = await createAccountWithPod({ issuer, email, password, podName: answers.podName || handle });
99
- prog.pod = made.pod; prog.webId = made.webId; accountToken = made.accountToken;
100
- acct.ok(made.pod);
101
- } else {
102
- const brought = answers.pod.endsWith('/') ? answers.pod : answers.pod + '/';
103
- acct.running('checking your pod');
104
- const head = await fetch(brought, { method: 'HEAD' }).catch(() => null);
105
- if (!head || head.status >= 400) throw new Error(`the pod at ${brought} did not answer (HTTP ${head?.status || 'no response'})`);
106
- if (await resourceExists(fetch, actorUrlFor(brought))) throw new Error('The pod already hosts a FediPod account. If you want a second account, put it on a different pod.');
107
- prog.pod = brought;
108
- acct.skip('using the pod you brought');
109
- }
97
+ podStep.running('checking your pod');
98
+ if (await resourceExists(session.fetch, actorUrlFor(pod))) throw new Error('The pod already hosts a FediPod account. If you want a second account, put it on a different pod.');
99
+ prog.pod = pod;
100
+ podStep.ok(pod);
110
101
  } else {
111
- acct.ok(prog.pod); // resumed: the pod is already there
102
+ podStep.ok(prog.pod); // resumed
112
103
  }
113
- const pod = prog.pod; const webId = prog.webId || null;
114
104
 
115
105
  // WebFinger is answered only at a host root. A pod on a path of a shared
116
106
  // host therefore takes its address at the gateway, whatever was asked; a
@@ -125,54 +115,13 @@ export async function signUp(answers, { onStep = () => {}, frontOrigin = null }
125
115
  const actorUrl = actorUrlFor(pod); // where the documents live, always
126
116
  const frontActor = fronted ? `${frontOrigin.replace(/\/$/, '')}/u/${handle}/ap/actor` : null;
127
117
 
128
- // --- credential --- (a fresh attempt re-mints; a resumed one reuses it)
129
- const cred = step('credential');
130
- let credential;
131
- if (!prog.credential) {
132
- cred.running('minting a credential for this browser');
133
- // accountToken is null when the account step was skipped (a resume), so
134
- // mintCredential logs in fresh with email+password rather than reusing a
135
- // token that a prior attempt may have let go stale.
136
- credential = await mintCredential({ issuer, email, password, webId, podUrl: pod, accountToken });
137
- credential.remotePod = pod;
138
- credential.root = AP_ROOT; // the agent reads the container name from here
139
- prog.credential = credential;
140
- cred.ok();
141
- } else {
142
- credential = prog.credential;
143
- cred.ok();
144
- }
145
-
146
- // A pod-writing session for the remaining pod writes and the gateway proof.
147
- const session = await makeDpopSession(credential);
148
-
149
- // --- keys, encrypted, in an owner-only container on the pod ---
150
- //
151
- // The durable copy is on the pod, because a browser has no disk you can carry
152
- // to the next machine. That copy used to be the bare record, on the argument
153
- // that the pod's owner-only ACL is protection enough and the pod host already
154
- // holds your data. It is not the same thing: your data is your data, and the
155
- // signing key IS you — whoever holds it is you to every server in the
156
- // fediverse, for as long as the key lives, and no ACL reaches the host itself.
157
- // So it is wrapped under the account password first, and the host stores
158
- // ciphertext. (Three documents already said this was happening. Now it is.)
159
- //
160
- // Two things in that order, deliberately: the ACL BEFORE the key. Writing the
161
- // key first leaves a window where a pod whose root is world-readable serves it
162
- // to anyone who asks, and a brought pod is exactly the case where that root
163
- // may be public.
164
- //
165
- // The opened copy is kept in this browser (IndexedDB) so the worker can boot
166
- // itself after an idle kill with nobody there to type a password. A browser
167
- // that has no copy asks for the password once — see boot.mjs.
168
118
  // Connect the gateway (fedipod.net) before the key is made, because a
169
119
  // fronted identity's key is stamped with the gateway actor. Inbox-only: the
170
120
  // actor advertises the door as its inbox and keeps its own ids. Fronted: the
171
121
  // gateway answers WebFinger for @handle@front and serves the actor at its own
172
122
  // address, rewriting reads onto the pod. Either way the door verifies each
173
123
  // delivery and forwards the clean mail to the pod; the agent trusts the
174
- // door's receipt. The same DPoP session proves the pod to the gateway — no
175
- // password reaches it.
124
+ // door's receipt. The pod session itself proves the pod to the gateway.
176
125
  let gateway = answers.gateway || prog.gateway || null;
177
126
  if (frontOrigin && !gateway) {
178
127
  const gw = step('gateway');
@@ -199,31 +148,46 @@ export async function signUp(answers, { onStep = () => {}, frontOrigin = null }
199
148
  step('gateway').ok(); // resumed
200
149
  }
201
150
 
151
+ // --- keys, in an owner-only container on the pod ---
152
+ //
153
+ // The durable copy is on the pod, because a browser has no disk you can carry
154
+ // to the next machine. It is stored as it is, behind the container's access
155
+ // rule: the same rule every private document on the pod lives under, so it
156
+ // is reachable through the pod's own login and by nobody else.
157
+ //
158
+ // Two things in that order, deliberately: the ACL BEFORE the key. Writing the
159
+ // key first leaves a window where a pod whose root is world-readable serves it
160
+ // to anyone who asks, and a brought pod is exactly the case where that root
161
+ // may be public.
162
+ //
163
+ // The opened copy is kept in this browser (IndexedDB) so the worker can boot
164
+ // itself after an idle kill with no read. A browser that has no copy reads
165
+ // the pod's with its session — see keys-browser.mjs.
166
+ const remote = new BrowserRemotePod(session, { webId, role: 'signup', log: () => {} });
202
167
  const keysStep = step('keys');
203
168
  let keys;
204
169
  if (!prog.keysStored) {
205
- keysStep.running('making your signing key and locking it under your password');
170
+ keysStep.running('making your signing key and storing it on your pod');
206
171
  keys = await generateKeys();
207
172
  // One key, one actor (lib/keys.mjs): the actor the world knows, which for
208
173
  // a fronted identity is the gateway's address for it.
209
174
  keys.mintedFor = gateway?.frontActor || actorUrl;
210
- const remote = new BrowserRemotePod(session, { webId: credential.webId, role: 'signup', log: () => {} });
211
175
  // Owner-only, and THEN the key — one operation, so the order cannot be got
212
176
  // wrong here or anywhere else. A pod that refuses the ACL write is not a
213
- // pod this key may sit on, wrapped or not.
177
+ // pod this key may sit on.
214
178
  try {
215
179
  await podState.provisionKey(remote, {
216
180
  stateUrl: `${pod}${AP_ROOT}ap-state/`,
217
181
  keysUrl: keysDocFor(pod),
218
- envelope: await wrapKeys(keys, password),
182
+ keys,
219
183
  });
220
184
  } catch (e) {
221
185
  throw new Error(`could not store the signing key on the pod (${e.message}). `
222
- + `The credential is for ${credential.webId} — that WebID must own ${pod} and its ${AP_ROOT} must be writable by it.`);
186
+ + `You are signed in as ${webId} — that WebID must own ${pod} and its ${AP_ROOT} must be writable by it.`);
223
187
  }
224
- // This browser's own opened copy, so the boot after the login redirect
225
- // needs no password. Best effort: a browser that refuses IndexedDB (private
226
- // mode) simply asks for the password on the way back in.
188
+ // This browser's own opened copy, so the boot that follows needs no read.
189
+ // Best effort: a browser that refuses IndexedDB (private mode) reads the
190
+ // pod's copy on the way back in.
227
191
  await cacheOpenedKeys(actorUrl, keys);
228
192
  prog.keys = keys; prog.keysStored = true;
229
193
  keysStep.ok();
@@ -233,46 +197,122 @@ export async function signUp(answers, { onStep = () => {}, frontOrigin = null }
233
197
  }
234
198
 
235
199
  const config = {
236
- remotePod: pod, root: AP_ROOT, handle, name: handle, issuer: credential.issuerOrigin,
200
+ remotePod: pod, root: AP_ROOT, handle, name: handle, issuer: String(session.issuer || '').replace(/\/+$/, ''),
237
201
  createdAt: new Date().toISOString(),
238
202
  ...(gateway ? { gateway } : {}),
239
203
  };
240
204
  // Write the config to the pod (owner-only, beside the key) so a returning
241
205
  // sign-in — which arrives with only an OIDC session — can read the account's
242
- // config and key from the pod and boot with no password.
243
- const cfgRemote = new BrowserRemotePod(session, { webId: credential.webId, role: 'signup', log: () => {} });
206
+ // config and key from the pod and boot.
244
207
  try {
245
- await podState.writeConfig(cfgRemote, { state: `${pod}${AP_ROOT}ap-state/` }, config);
208
+ await podState.writeConfig(remote, { state: `${pod}${AP_ROOT}ap-state/` }, config);
246
209
  } catch (e) {
247
210
  throw new Error(`could not store the config on the pod (${e.message})`);
248
211
  }
249
212
 
250
- // The credential was for sign-up, and sign-up is over. The agent runs on the
251
- // Solid-OIDC session from here on and never needs it again, so leaving it
252
- // alive would leave permanent full access to the pod in a key nothing holds.
253
- // Best effort: a server that will not delete it is a credential the owner can
254
- // still revoke from their pod's account page, and not a reason to fail a
255
- // sign-up that otherwise worked.
256
- const revoked = await revokeCredential({
257
- resource: credential.resource, accountToken: credential.accountToken,
258
- });
259
- delete credential.accountToken; // never leaves this function
260
- if (!revoked && credential.resource) {
261
- onStep('credential', 'ok', 'this browser is ready (the setup credential could not be '
262
- + 'revoked automatically — you can remove it from your pod\'s account page)');
263
- }
264
-
265
213
  PROGRESS.delete(key); // finished — nothing left to resume
266
214
 
267
215
  const host = gateway?.frontActor ? new URL(gateway.frontActor).host : new URL(pod).host;
268
216
  return {
269
- credential, config, actorUrl,
217
+ config, actorUrl, pod,
270
218
  address: `@${handle}@${host}`,
271
219
  // The opened keys, for booting the agent in THIS browser session right away.
272
- // The durable copy on the pod is wrapped under the account password; this
273
- // browser also holds an opened one in IndexedDB (above), which is what the
274
- // worker reads. A fresh browser asks for the password once and makes its own.
275
220
  keys,
276
221
  keysPublic: { rsa: keys.rsa.publicPem, ed25519: keys.ed25519?.publicPem || null },
277
222
  };
278
223
  }
224
+
225
+ // ---- an account already on the pod ----
226
+
227
+ /**
228
+ * What the pod already holds: null when there is no FediPod account on it,
229
+ * else its config, the pod, and where its address lives — `frontHost` is
230
+ * the gateway's host for an address at a gateway, null for one on the pod.
231
+ */
232
+ export async function readAccount(session) {
233
+ const pod = podBaseOfWebId(session.webId);
234
+ const remote = new BrowserRemotePod(session, { webId: session.webId, role: 'signup', log: () => {} });
235
+ const config = await podState.readConfig(remote, { state: `${pod}${AP_ROOT}ap-state/` }).catch(() => null);
236
+ if (!config) return null;
237
+ const frontActor = config.gateway?.frontActor || null;
238
+ let frontHost = null;
239
+ try { frontHost = frontActor ? new URL(frontActor).host : null; } catch { frontHost = null; }
240
+ let podHost = '';
241
+ try { podHost = new URL(config.remotePod || pod).host; } catch { podHost = ''; }
242
+ return { pod, config, frontActor, frontHost, address: `@${config.handle}@${frontHost || podHost}` };
243
+ }
244
+
245
+ /**
246
+ * Move an account whose address lives at another gateway to this one. The
247
+ * pod, its posts, its followers and its key all stay where they are; what
248
+ * changes is the gateway that answers for it. This half does the pod's
249
+ * part: attach here, rewrite the config to name this gateway, restamp the
250
+ * key. The agent's first boot from that config publishes under the new
251
+ * ids, and then tells the old gateway and the followers (gateway-move.mjs).
252
+ *
253
+ * answers: { handle } — the name here; the old one is the default.
254
+ * returns: { config, address, movedFrom }
255
+ */
256
+ export async function moveIn(answers, { session, onStep = () => {}, frontOrigin = null } = {}) {
257
+ const { handle } = answers;
258
+ const bad = handleProblem(handle);
259
+ if (bad) throw new Error(bad);
260
+ if (!session?.webId || typeof session.fetch !== 'function') throw new Error('sign in at your pod first');
261
+ if (!frontOrigin) throw new Error('a move needs a gateway to move to, and this page has none');
262
+ const origin = frontOrigin.replace(/\/$/, '');
263
+ const step = (key) => ({ running: (note) => onStep(key, 'running', note), ok: (note) => onStep(key, 'ok', note) });
264
+
265
+ // --- the pod: the account that is there, and its key ---
266
+ const podStep = step('pod');
267
+ podStep.running('reading your account on your pod');
268
+ const here = await readAccount(session);
269
+ if (!here?.frontActor) throw new Error('this pod holds no account at another gateway to move here');
270
+ if (here.frontHost === new URL(origin).host) throw new Error('this account already lives at this gateway');
271
+ const { pod, config: old } = here;
272
+ const remote = new BrowserRemotePod(session, { webId: session.webId, role: 'signup', log: () => {} });
273
+ const urls = { state: `${pod}${AP_ROOT}ap-state/` };
274
+ const keys = await podState.readKeys(remote, urls);
275
+ if (!keys?.rsa) throw new Error('could not read this account\'s signing key on the pod');
276
+ if (isKeyEnvelope(keys)) throw new Error(`this account's key is still under a password from an earlier version — sign in once at ${here.frontHost} first, then come back`);
277
+ podStep.ok(here.address);
278
+
279
+ // --- this gateway: the name, then the attach, fronted ---
280
+ const gw = step('gateway');
281
+ gw.running(`taking your address at ${new URL(origin).host}`);
282
+ await assertFrontNameFree(origin, handle);
283
+ const res = await session.fetch(`${origin}/api/attach`, {
284
+ method: 'POST', headers: { 'content-type': 'application/json' },
285
+ body: JSON.stringify({ handle, podHome: `${pod}${AP_ROOT}`, actorUrl: actorUrlFor(pod), kind: old.kind || 'person', fronted: true }),
286
+ });
287
+ const d = await res.json().catch(() => ({}));
288
+ if (res.status !== 201 || !d.hmacSecret) throw new Error(`could not attach to this gateway (HTTP ${res.status}): ${d.error || ''}`);
289
+ const gateway = {
290
+ url: `${origin}/u/${handle}/ap/inbox/`, frontActor: String(d.frontActor || `${origin}/u/${handle}/ap/actor`),
291
+ hmacSecret: d.hmacSecret, mode: 'trust',
292
+ };
293
+ gw.ok();
294
+
295
+ // --- the pod again: the config names this gateway, the key is restamped ---
296
+ //
297
+ // The old actor becomes an alias, which is what a server checks on the new
298
+ // actor before it honours a Move. `movedFrom` is the pending half: the
299
+ // agent completes it after its first boot under the new ids.
300
+ const keysStep = step('keys');
301
+ keysStep.running('moving your key and account record to the new address');
302
+ const aliases = [...new Set([...(old.aliases || []), here.frontActor])];
303
+ const config = {
304
+ ...old, handle, gateway, aliases,
305
+ movedFrom: { actor: here.frontActor, gateway: new URL(here.frontActor).origin, handle: old.handle, at: new Date().toISOString() },
306
+ };
307
+ keys.mintedFor = gateway.frontActor; // one key, one actor (lib/keys.mjs)
308
+ try {
309
+ await podState.writeKeys(remote, urls, keys);
310
+ await podState.writeConfig(remote, urls, config);
311
+ } catch (e) {
312
+ throw new Error(`could not update your account on the pod (${e.message})`);
313
+ }
314
+ await cacheOpenedKeys(actorUrlFor(pod), keys);
315
+ keysStep.ok();
316
+
317
+ return { config, address: `@${handle}@${new URL(origin).host}`, movedFrom: config.movedFrom };
318
+ }
@@ -157,7 +157,7 @@ async function serve(request, url) {
157
157
  // Boot on demand from the stored session, so the client works even when the
158
158
  // worker was restarted (idle-killed) and nobody posted 'boot' this time.
159
159
  if (!agent) { try { await ensureBooting(); } catch { /* no session → 503 below */ } }
160
- if (!agent) return json(503, { error: 'the agent is not booted yet — open the app from the sign-in page' });
160
+ if (!agent) return json(503, { error: 'not signed in on this browser — sign in at the front page' });
161
161
  // Bytes, not text. A multipart upload is binary — read as text it comes back
162
162
  // through a UTF-8 round trip that replaces every byte that is not valid UTF-8,
163
163
  // which is most of a JPEG, so the boundary search found nothing and every
@@ -0,0 +1,66 @@
1
+ // update.js — a fedipod.net page notices a newer build and reloads itself once.
2
+ //
3
+ // The page carries the version it was staged with (a meta tag); the site
4
+ // answers its current version at the handle probe. When they differ, the
5
+ // worker is asked to update — it installs and takes over at once (sw-src.mjs
6
+ // skips waiting and claims) — and the page reloads when the new worker takes
7
+ // over, or after a moment if only the pages changed. A page with text in a
8
+ // composer is not reloaded from under it: it shows a line with a button.
9
+ (() => {
10
+ const mine = document.querySelector('meta[name="fedipod-version"]')?.content;
11
+ // The build this page was staged from. A version changes when a release is
12
+ // cut; a build changes every time the site is deployed, which is what a
13
+ // reader with the page open actually needs to hear about.
14
+ const myBuild = document.querySelector('meta[name="fedipod-build"]')?.content || null;
15
+ if (!mine) return;
16
+ const loadedAt = Date.now();
17
+ let acted = false;
18
+ const drafting = () => [...document.querySelectorAll('textarea')].some((t) => t.value.trim());
19
+ const banner = () => {
20
+ if (document.getElementById('fedipod-update')) return;
21
+ const d = document.createElement('div');
22
+ d.id = 'fedipod-update';
23
+ d.setAttribute('role', 'status');
24
+ d.style.cssText = 'position:fixed;left:0;right:0;bottom:0;z-index:2147483647;padding:1rem 1.25rem;'
25
+ + 'font:1rem/1.5 Arial, Helvetica, sans-serif;background:#1d1d1d;color:#fff;display:flex;gap:1rem;align-items:center;justify-content:center;flex-wrap:wrap';
26
+ const t = document.createElement('span');
27
+ t.textContent = 'A new version of FediPod is ready. Reload when you have finished writing.';
28
+ const b = document.createElement('button');
29
+ b.type = 'button'; b.textContent = 'Reload now';
30
+ b.style.cssText = 'font:inherit;padding:.5rem 1rem;border-radius:.5rem;border:1px solid #fff;background:#fff;color:#1d1d1d;cursor:pointer';
31
+ b.addEventListener('click', () => location.reload());
32
+ d.append(t, b);
33
+ document.body.append(d);
34
+ };
35
+ const go = () => {
36
+ if (acted) return;
37
+ acted = true;
38
+ if (drafting()) { acted = false; banner(); return; }
39
+ location.reload();
40
+ };
41
+ // A worker taking over a page that has been open a while is a new build;
42
+ // in a page's first seconds it is the boot, not an update.
43
+ if ('serviceWorker' in navigator) {
44
+ navigator.serviceWorker.addEventListener('controllerchange', () => { if (Date.now() - loadedAt > 5000) go(); });
45
+ }
46
+ const check = async () => {
47
+ try {
48
+ // The build first: it moves with every deploy. The version is the
49
+ // fallback for a page staged before builds were stamped.
50
+ if (myBuild) {
51
+ const b = await fetch('/build.json', { cache: 'no-store', headers: { accept: 'application/json' } })
52
+ .then(x => (x.ok ? x.json() : null)).catch(() => null);
53
+ if (b?.build && b.build !== myBuild) { go(); return; }
54
+ }
55
+ const r = await fetch('/api/handle?handle=__probe__', { cache: 'no-store', headers: { accept: 'application/json' } });
56
+ const v = (await r.json())?.version;
57
+ if (!v || v === mine) return;
58
+ const reg = await navigator.serviceWorker.getRegistration();
59
+ await reg?.update().catch(() => {});
60
+ setTimeout(go, 4000); // a pages-only change: no new worker will take over
61
+ } catch { /* offline, or the site did not answer: try again later */ }
62
+ };
63
+ // Often enough that a reader watching a page being worked on sees it.
64
+ setTimeout(check, 5000);
65
+ setInterval(check, 60 * 1000);
66
+ })();
@@ -4,10 +4,11 @@
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
6
  <title>FediPod — accounts on this server</title>
7
+ <link rel="icon" href="/icons/gateway.svg" type="image/svg+xml">
7
8
  <style>
8
9
  :root { color-scheme: light dark; --line:#d9d4c7; --dim:#45443d; --accent:#6a8a5a; }
9
10
  @media (prefers-color-scheme: dark) { :root { --dim:#c2c0b4; } }
10
- body { font: 19px/1.6 system-ui, sans-serif; max-width: 44rem; margin: 2rem auto; padding: 0 1.1rem; }
11
+ body { font: 20px/1.6 Arial, Helvetica, sans-serif; max-width: 44rem; margin: 2rem auto; padding: 0 1.1rem; }
11
12
  h1 { font-size: 1.7rem; margin: 0 0 .2rem; }
12
13
  p.lede { color: var(--dim); margin-top: 0; }
13
14
  label { display: block; font-weight: 600; margin: .8rem 0 .2rem; }
@@ -27,7 +28,9 @@
27
28
  <body>
28
29
  <h1>Accounts on this server</h1>
29
30
  <p class="lede">Every name this server answers for: accounts whose identity lives here, and
30
- accounts that only use it as their gateway. Sign in as the server's admin to see them.</p>
31
+ accounts that only use it as their gateway. Sign in as the server's admin to see them.
32
+ <a href="/notices">Notices</a> — what every account sees behind the bell in its bar — are
33
+ written on their own page.</p>
31
34
 
32
35
  <form id="roster-form">
33
36
  <p class="field">
@@ -48,6 +51,5 @@ accounts that only use it as their gateway. Sign in as the server's admin to see
48
51
  </table>
49
52
 
50
53
  <script type="module" src="/admin.js"></script>
51
- <i>(cc) 4.0 By, Jeff Zucker, 2026</i>
52
54
  </body>
53
55
  </html>
@@ -4,10 +4,11 @@
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
6
  <title>FediPod — access the Fediverse and ATProto from a Solid pod</title>
7
+ <link rel="icon" href="/icons/gateway.svg" type="image/svg+xml">
7
8
  <style>
8
9
  :root { color-scheme: light dark; --line:#d9d4c7; --dim:#45443d; --accent:#6a8a5a; }
9
10
  @media (prefers-color-scheme: dark) { :root { --dim:#c2c0b4; } }
10
- body { font: 19px/1.6 system-ui, sans-serif; max-width: 44rem; margin: 2rem auto; padding: 0 1.1rem; }
11
+ body { font: 20px/1.6 Arial, Helvetica, sans-serif; max-width: 44rem; margin: 2rem auto; padding: 0 1.1rem; }
11
12
  h1 { font-size: 1.7rem; margin: 0 0 .2rem; }
12
13
  h2 { font-size: 1.2rem; margin: 1.4rem 0 .4rem; }
13
14
  p.lede { color: var(--dim); margin-top: 0; }
@@ -30,6 +31,5 @@ FediPod is experimental and free to use. See the <a href="https://github.com/je
30
31
  </p>
31
32
 
32
33
  <script src="/new-account.js"></script>
33
- <i style="text-align:right">(cc) 4.0 By, Jeff Zucker, 2026</i>
34
34
  </body>
35
35
  </html>
@@ -0,0 +1,69 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>FediPod — notices</title>
7
+ <link rel="icon" href="/icons/gateway.svg" type="image/svg+xml">
8
+ <style>
9
+ :root { color-scheme: light dark; --line:#d9d4c7; --dim:#45443d; --accent:#6a8a5a; --danger:#b00020; }
10
+ @media (prefers-color-scheme: dark) { :root { --dim:#c2c0b4; --danger:#ff8a9a; } }
11
+ body { font: 20px/1.6 Arial, Helvetica, sans-serif; max-width: 44rem; margin: 2rem auto; padding: 0 1.1rem; }
12
+ h1 { font-size: 1.7rem; margin: 0 0 .2rem; }
13
+ h2 { font-size: 1.25rem; margin: 1.6rem 0 .4rem; }
14
+ p.lede { color: var(--dim); margin-top: 0; }
15
+ label { display: block; font-weight: 600; margin: .8rem 0 .2rem; }
16
+ input[type=url], input[type=text], textarea { width: 100%; padding: .5rem .6rem; font: inherit;
17
+ border: 1px solid var(--line); border-radius: 8px; box-sizing: border-box; }
18
+ textarea { min-height: 9rem; resize: vertical; }
19
+ .hint { color: var(--dim); font-size: .95rem; }
20
+ .primary { background: var(--accent); color: #fff; border: none; border-radius: 9px;
21
+ padding: .6rem 1.1rem; font: inherit; cursor: pointer; }
22
+ .primary[disabled] { opacity: .5; cursor: not-allowed; }
23
+ button { font: inherit; cursor: pointer; }
24
+ button.danger { color: var(--danger); background: none; border: 1px solid var(--danger); border-radius: 9px; padding: .4rem .8rem; }
25
+ [hidden] { display: none !important; }
26
+ ul.notices { list-style: none; padding: 0; margin: 1rem 0; }
27
+ ul.notices li { padding: .8rem 0; border-bottom: 1px solid var(--line); }
28
+ ul.notices .title { font-weight: 600; }
29
+ ul.notices .when { color: var(--dim); font-size: .95rem; margin-left: .6rem; }
30
+ ul.notices .body { white-space: pre-wrap; margin: .3rem 0 .6rem; }
31
+ ul.notices .actions button { margin-right: .6rem; }
32
+ </style>
33
+ </head>
34
+ <body>
35
+ <h1>Notices</h1>
36
+ <p class="lede">What every account on this server sees behind the bell in its bar. Sign in as the
37
+ server's admin to write, change or remove them. <a href="/roster">Back to the roster.</a></p>
38
+
39
+ <form id="notices-form">
40
+ <p class="field">
41
+ <label for="notices-issuer">Your identity provider</label>
42
+ <input type="url" id="notices-issuer" autocomplete="off" value="https://solidcommunity.net">
43
+ </p>
44
+ <p class="actions">
45
+ <button type="submit" id="notices-signin" class="primary">Sign in &amp; manage notices</button>
46
+ </p>
47
+ <p class="hint" id="notices-note" hidden></p>
48
+ </form>
49
+
50
+ <section id="notices-editor" hidden>
51
+ <h2 id="editor-title">New notice</h2>
52
+ <form id="editor-form">
53
+ <label for="notice-title">Title</label>
54
+ <input type="text" id="notice-title" maxlength="120" autocomplete="off" required>
55
+ <label for="notice-body">Text</label>
56
+ <textarea id="notice-body" maxlength="4000" required></textarea>
57
+ <p class="hint">Plain text. A blank line starts a new paragraph; a web address becomes a link.</p>
58
+ <p class="actions">
59
+ <button type="submit" id="notice-save" class="primary">Publish</button>
60
+ <button type="button" id="notice-cancel" hidden>Cancel</button>
61
+ </p>
62
+ </form>
63
+ <h2>Published</h2>
64
+ <ul class="notices" id="notices-list"></ul>
65
+ </section>
66
+
67
+ <script type="module" src="/notices.js"></script>
68
+ </body>
69
+ </html>