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,21 @@
1
+ # gateway + front API → the function
2
+ /api/handle /.netlify/functions/front 200
3
+ /api/attach /.netlify/functions/front 200
4
+ /api/relay /.netlify/functions/front 200
5
+ /api/roster /.netlify/functions/front 200
6
+ /api/revoke /.netlify/functions/front 200
7
+ /api/agent /.netlify/functions/front 200
8
+ /u/* /.netlify/functions/front 200
9
+ /.well-known/* /.netlify/functions/front 200
10
+ /install /.netlify/functions/front 200
11
+ /run /.netlify/functions/front 200
12
+ /roster /.netlify/functions/front 200
13
+ /solid-oidc-client.js /.netlify/functions/front 200
14
+ # the in-browser app, served static (these win over the project catch-all)
15
+ / /index.html 200
16
+ /sw.js /sw.js 200
17
+ /boot.js /boot.js 200
18
+ /app /app/ 301
19
+ /app/* /app/:splat 200
20
+ /admin /admin/ 301
21
+ /admin/* /admin/:splat 200
@@ -0,0 +1,145 @@
1
+ // actors.js — the other actors on this machine: the picker, going to one,
2
+ // starting a stopped one, and the form for a new one.
3
+
4
+ // Somewhere to go, and nothing else. A stopped actor is left out — the link
5
+ // would land on nothing. The one you are on keeps its place in the row but
6
+ // offers only `app`, because its admin page is the page you are reading.
7
+
8
+ let actors = [];
9
+
10
+ async function renderOthers() {
11
+ const { json } = await api('/profiles');
12
+ actors = json?.identities || [];
13
+ const sel = $('actor-pick');
14
+ sel.textContent = '';
15
+ // The fediverse address, which is what the actor IS to everyone else, and the
16
+ // only form that stays distinct: two identities can share a local handle, but
17
+ // never a handle AND a pod. A stopped one has no address to report — nothing
18
+ // answered — so it falls back to the name it is filed under.
19
+ const label = (r) => (r.address || r.handle || r.name)
20
+ + (r.mode && r.mode !== 'active' ? ` (${r.mode})` : '') + (r.mode ? '' : ' (stopped)');
21
+ const seen = {};
22
+ for (const r of actors) seen[label(r)] = (seen[label(r)] || 0) + 1;
23
+
24
+ for (const [i, r] of actors.entries()) {
25
+ const o = document.createElement('option');
26
+ o.value = String(i);
27
+ o.selected = !!r.current; // it opens showing where you already are
28
+ o.textContent = label(r) + (seen[label(r)] > 1 && r.port ? ` :${r.port}` : '');
29
+ sel.appendChild(o);
30
+ }
31
+ // The way to a NEW actor rides the same dropdown as the existing ones.
32
+ const add = document.createElement('option');
33
+ add.value = '__add';
34
+ add.textContent = '+ add a new account…';
35
+ sel.appendChild(add);
36
+ }
37
+
38
+ // Going to an actor commits a navigation and, for a stopped one, a POST that
39
+ // boots its pod — so it must run once per choice. Pressing Enter on the select
40
+ // fires BOTH a keydown (handled below) and a native `change`, so without a
41
+ // guard a keyboard commit would start the actor twice.
42
+ let goingToActor = false;
43
+ const goToActor = async () => {
44
+ if (goingToActor) return;
45
+ if ($('actor-pick').value === '__add') { renderOthers(); openNewActor(); return; }
46
+ const r = actors[Number($('actor-pick').value)];
47
+ if (!r || r.current) return;
48
+ goingToActor = true;
49
+ try {
50
+ if (r.mode) { location.href = r.admin; return; }
51
+ $('actor-pick').disabled = true;
52
+ say(`starting ${r.name}`);
53
+ const started = await write('/start-actor', { name: r.name }, `${r.name} is up`);
54
+ $('actor-pick').disabled = false;
55
+ if (started?.url) location.href = `${started.url}admin/`;
56
+ else renderOthers(); // put the picker back on the current actor
57
+ } finally {
58
+ goingToActor = false;
59
+ }
60
+ };
61
+ // Choosing one goes to its record, as it always has.
62
+ //
63
+ // Arrowing is the one case that must not: on a closed select every Arrow
64
+ // keypress fires `change`, so a keyboard user browsing the list would be
65
+ // carried off to the first actor they passed — and for a stopped one that
66
+ // boots its pod. An arrow key arms a flag and the change it causes is ignored;
67
+ // Enter commits. A mouse never sets the flag, so clicking behaves as before.
68
+ let arrowing = false;
69
+ $('actor-pick').addEventListener('keydown', (ev) => {
70
+ if (['ArrowUp', 'ArrowDown', 'Home', 'End', 'PageUp', 'PageDown'].includes(ev.key)) arrowing = true;
71
+ if (ev.key === 'Enter') { arrowing = false; goToActor(); }
72
+ });
73
+ $('actor-pick').addEventListener('pointerdown', () => { arrowing = false; });
74
+ $('actor-pick').addEventListener('change', () => {
75
+ if (!arrowing) goToActor();
76
+ arrowing = false;
77
+ });
78
+
79
+ // Setting up a new actor asks for exactly what this page cannot already tell
80
+ // it: the pod and the account behind it, and its permanent name. Display name,
81
+ // bio and pictures are the client's job now — Phanpy's profile editor writes
82
+ // them through /api/v1/accounts/update_credentials. Where the private data goes
83
+ // is not a question either: it lands beside the credential, and `state --to`
84
+ // moves it afterwards.
85
+
86
+ const picked = (name) => document.querySelector(`input[name=${name}]:checked`)?.value;
87
+
88
+ function newActorRows() {
89
+ const mode = picked('newMode');
90
+ $('new-row-podname').hidden = mode !== 'new';
91
+ $('new-row-pod').hidden = mode !== 'existing';
92
+ }
93
+ for (const el of document.querySelectorAll('input[name=newMode]')) {
94
+ el.addEventListener('change', newActorRows);
95
+ }
96
+
97
+ // While the form is up the page IS the new actor's setup — so the record of
98
+ // the actor you came from goes away, list included. Which panes were showing
99
+ // is remembered rather than recomputed: whether Group and Inbox belong is a
100
+ // decision render() already made.
101
+ const RECORD_PANES = ['pane-identity', 'pane-group', 'pane-inbox', 'rail'];
102
+ let putBack = [];
103
+
104
+ function showNewActor(on) {
105
+ if (on) closePanels(); // nothing of the old actor left open behind it
106
+ document.body.classList.toggle('adding', on); // see body.adding in the CSS
107
+ $('new-actor-form').hidden = !on;
108
+ if (on) {
109
+ putBack = RECORD_PANES.filter(id => !$(id).hidden);
110
+ for (const id of putBack) $(id).hidden = true;
111
+ } else {
112
+ for (const id of putBack) $(id).hidden = false;
113
+ putBack = [];
114
+ }
115
+ }
116
+
117
+ // `add a new account` is the last item of the Local Actors dropdown; choosing
118
+ // it opens the form in place. Arriving with ?new does the same.
119
+ function openNewActor() {
120
+ showNewActor(true);
121
+ newActorRows();
122
+ $('new-handle').focus();
123
+ }
124
+ $('new-actor-cancel').addEventListener('click', () => { showNewActor(false); say('nothing changed'); });
125
+
126
+ $('new-actor-form').addEventListener('submit', async (ev) => {
127
+ ev.preventDefault();
128
+ const handle = $('new-handle').value.trim();
129
+ const answers = {
130
+ handle,
131
+ kind: picked('newKind'),
132
+ mode: picked('newMode'),
133
+ issuer: $('new-issuer').value.trim(),
134
+ email: $('new-email').value.trim(),
135
+ password: $('new-password').value,
136
+ };
137
+ if (answers.mode === 'new') answers.podName = $('new-podname').value.trim() || handle;
138
+ else answers.pod = $('new-pod').value.trim();
139
+ $('new-actor-go').disabled = true;
140
+ say(`setting up ${handle || 'the new actor'} — this takes a while`);
141
+ const r = await write('/new-actor', answers, `setting up ${handle}`);
142
+ $('new-actor-go').disabled = false;
143
+ if (r?.url) location.href = r.url; // its own page, where the progress is
144
+ });
145
+
@@ -0,0 +1,60 @@
1
+ /* The bar every page of ours carries — the record, the setup form and the
2
+ client view. One file so the three cannot drift apart. */
3
+ /* One column, 960px, centred — the bar included, so the title sits over the
4
+ page's left edge and the buttons over its right rather than out at the window
5
+ borders. `width: 100%` with `box-sizing: border-box` keeps the padding inside
6
+ that 960 instead of adding to it, which is what makes the two margins equal. */
7
+ /* Colours come from tokens.css (the front's palette), linked by every page
8
+ before this file, so the bar matches the front and the record rather than
9
+ Phanpy: --surface is the page ground, --raised the bar band on top of it. */
10
+ body { background: var(--surface); }
11
+ /* The BAND spans the window; its CONTENTS stay in the 960 column. Constraining
12
+ the element itself left the colour as a stripe with bare page either side.
13
+ The inline padding is the column's own margin plus the 1rem the page uses
14
+ inside it, so the title lands exactly on the page's left edge — and `max`
15
+ keeps it at 1rem once the window is narrower than the column.
16
+ No rule underneath: the tone change is the boundary, and a line as well
17
+ would be drawing it twice. */
18
+ /* Wraps, because it does not fit: at 320px the title plus three destinations
19
+ ran 67px past the viewport, and the bar is on every page — so the whole
20
+ document scrolled sideways whatever was under it. */
21
+ #bar { flex: 0 0 auto; display: flex; align-items: center; gap: .6rem; flex-wrap: wrap;
22
+ width: 100%; max-width: none; margin: 0; box-sizing: border-box;
23
+ /* The column width is the page's to declare (--bar-col); 960 stands where a
24
+ page says nothing. */
25
+ padding: .5rem max(1rem, calc((100% - var(--bar-col, 960px)) / 2 + 1rem));
26
+ background: var(--raised); }
27
+ /* The actor's fediverse handle, in the row between the title and the
28
+ destinations. */
29
+ #bar-handle { color: var(--heading); font-size: .95rem; margin-right: .6rem; }
30
+ #bar-handle:empty { display: none; }
31
+ /* The actors dropdown, where a page's bar carries one instead of the static
32
+ handle. Styled here so every page's bar shows the same control: the same
33
+ light well and drawn caret every setting on the record page uses. */
34
+ #bar select { font: inherit; font-size: .95rem; max-width: min(24rem, 55vw);
35
+ margin-right: .6rem; appearance: none; min-width: 0;
36
+ padding: .15rem 1.7rem .15rem .55rem;
37
+ border: 1px solid var(--field-edge); border-radius: .3rem;
38
+ color: var(--ink); cursor: pointer;
39
+ background-color: var(--field-bg); background-image: var(--chevron);
40
+ background-repeat: no-repeat; background-position: right .55rem center;
41
+ background-size: .6rem auto; }
42
+ #bar select:hover { background-color: var(--field-bg-hover); }
43
+ /* The app title, in px rather than rem: it is chrome, the same on every page,
44
+ not something that should move when a page changes its base size. */
45
+ #bar .name { flex: 1 1 auto; font-weight: 600; font-size: 40px; color: var(--heading);
46
+ margin: 0; line-height: 1.1; }
47
+ /* The browser build wraps the brand text in a link (to /?add) — the general
48
+ #bar a rule below would otherwise pin it back down to nav-link size and
49
+ colour, since a rule on the anchor itself always wins over inheriting the
50
+ heading's size. */
51
+ #bar .name a { font: inherit; color: inherit; text-decoration: none; }
52
+ /* Plain text, not chrome: three destinations in a row read better without a
53
+ box round each. */
54
+ #bar button, #bar a { font: inherit; font-size: .95rem; padding: .15rem .2rem;
55
+ border: 0; background: none; color: var(--link); cursor: pointer;
56
+ text-decoration: none; }
57
+ /* The page you are already on stays in the row, so the set never moves — it is
58
+ underlined rather than removed. */
59
+ #bar button[aria-current="page"], #bar a[aria-current="page"] {
60
+ text-decoration: underline; text-underline-offset: .3rem; }
@@ -0,0 +1,61 @@
1
+ // bar.js — the three destinations, shared by the record, the setup form and the
2
+ // client view, so they cannot drift apart. Nothing here touches the client in
3
+ // the frame: it is this agent's own root, and framing it is the whole of the
4
+ // relationship.
5
+
6
+ (() => {
7
+ // The three destinations are <a href> now: they are navigations, so they
8
+ // belong in a screen reader's links list, and middle-click and open-in-new-tab
9
+ // work the way they do everywhere else. Nothing here has to drive them.
10
+ //
11
+ // One exception, and it keeps its href as the fallback: on the record page
12
+ // `add new account` opens the form IN PLACE rather than reloading. admin.js
13
+ // binds that and calls preventDefault; without the form it is a plain link to
14
+ // ?new=1, which opens it on arrival.
15
+
16
+ // The bar says the product; the TAB says which actor, which is what tells two
17
+ // open windows apart without putting the handle in the page twice.
18
+ // /status, not /config: an agent that is not set up yet answers this one and
19
+ // 409s the other, which logged a failed request on every setup page.
20
+ fetch(location.pathname.replace(/\/admin\/.*$/u, '') + '/status', { headers: { 'x-fedipod-page': '1' } }).then(r => (r.ok ? r.json() : null)).then((s) => {
21
+ if (s?.handle) document.title = `FediPod — ${s.handle}`;
22
+ // The full fediverse handle, centred in the bar on every page.
23
+ const el = document.getElementById('bar-handle');
24
+ if (el && s?.handle && s?.actor) {
25
+ try { el.textContent = `@${s.handle}@${new URL(s.actor).host}`; } catch { /* odd actor url */ }
26
+ }
27
+ }).catch(() => { /* not up yet; the bar still works */ });
28
+
29
+ // The actors dropdown, on a page whose bar carries one. The record page
30
+ // wires its own richer version (it can start a stopped actor and open the
31
+ // new-account form in place), so this one stands down there.
32
+ const pick = document.getElementById('actor-pick');
33
+ if (pick && !document.getElementById('new-actor-form')) {
34
+ const base = location.pathname.replace(/\/admin\/.*$/u, '');
35
+ fetch(base + '/profiles', { headers: { 'x-fedipod-page': '1' } }).then(r => (r.ok ? r.json() : null)).then((j) => {
36
+ const actors = j?.identities || [];
37
+ pick.textContent = '';
38
+ const label = (r) => (r.address || r.handle || r.name)
39
+ + (r.mode && r.mode !== 'active' ? ` (${r.mode})` : '') + (r.mode ? '' : ' (stopped)');
40
+ for (const [i, r] of actors.entries()) {
41
+ const o = document.createElement('option');
42
+ o.value = String(i);
43
+ o.selected = !!r.current;
44
+ o.textContent = label(r);
45
+ pick.appendChild(o);
46
+ }
47
+ const add = document.createElement('option');
48
+ add.value = '__add';
49
+ add.textContent = '+ add a new account…';
50
+ pick.appendChild(add);
51
+ pick.addEventListener('change', () => {
52
+ if (pick.value === '__add') { location.href = base + '/admin/?new=1'; return; }
53
+ const r2 = actors[Number(pick.value)];
54
+ if (!r2 || r2.current) return;
55
+ // A running actor's client; a stopped one is started from its record.
56
+ if (r2.mode && r2.admin) location.href = r2.admin + 'client/';
57
+ else if (r2.admin) location.href = r2.admin;
58
+ });
59
+ }).catch(() => { pick.hidden = true; });
60
+ }
61
+ })();
@@ -0,0 +1,66 @@
1
+ // client.js — make the framed client be THIS agent's actor.
2
+ //
3
+ // The client is a general fediverse client: it keeps a list of accounts in
4
+ // localStorage and one of them is current. Nothing makes that one agree with
5
+ // the agent serving the page. A leftover account from another identity wins,
6
+ // and you get somebody else's timeline on this actor's page, with the profile
7
+ // editor pointed at a host that cannot answer.
8
+ //
9
+ // Per-origin storage is what keeps identities apart, and it does work — each
10
+ // actor answers at <handle>.localhost:<port> and each origin has its own
11
+ // accounts list. What was missing is the other half: telling the client which
12
+ // account it is meant to be showing.
13
+ //
14
+ // The frame's markup declares what it loads (`src="/"`), and that does not
15
+ // change. This navigates the loaded app to its own login route, which is app
16
+ // navigation, not an include: `?instance=` prefills and `?submit=` runs it, so
17
+ // the round trip needs no typing. Hash-routed, so the query lives after the
18
+ // hash; `submit` must be non-empty, because the empty string is falsy and the
19
+ // auto-submit never fires.
20
+
21
+ (async () => {
22
+ const frame = document.getElementById('client');
23
+ if (!frame) return;
24
+
25
+ // Who this agent is, in the form the client stores: `info.uri` is the actor
26
+ // URI on the pod. Matching on that rather than on the origin is deliberate —
27
+ // "which account logged in here" is a different question, and it is the one
28
+ // that goes wrong when the two origins get mixed.
29
+ const base = location.pathname.replace(/\/admin\/.*$/u, '');
30
+ const status = await fetch(base + '/status', { headers: { 'x-fedipod-page': '1' } }).then(r => (r.ok ? r.json() : null)).catch(() => null);
31
+ if (!status?.actor) return; // not set up yet; setup owns that
32
+
33
+ // A deep link into the client — the record links an actor's own page this way
34
+ // — arrives as a hash on THIS page, and the frame never sees it. Forward it,
35
+ // so the link lands where it says while keeping the bar above it.
36
+ const wanted = location.hash && location.hash !== '#' ? location.hash : null;
37
+ const sendTo = (hash) => {
38
+ try { frame.contentWindow.location.hash = hash; } catch { /* not ready yet */ }
39
+ };
40
+ const whenReady = (fn) => {
41
+ if (frame.contentWindow?.location?.href && frame.contentWindow.location.href !== 'about:blank') fn();
42
+ else frame.addEventListener('load', fn, { once: true });
43
+ };
44
+
45
+ let accounts = [];
46
+ try { accounts = JSON.parse(localStorage.getItem('accounts') || '[]'); } catch { /* unreadable */ }
47
+ const ours = accounts.find(a => a?.info?.uri === status.actor);
48
+ if (ours) {
49
+ // A stored login can be dead — its token revoked or minted by a run that
50
+ // never finished. Trusting it shows an empty client with no way out, so
51
+ // ask the facade first; a dead one falls through to the same one-trip
52
+ // login a missing account gets.
53
+ const alive = ours.accessToken && await fetch('/api/v1/accounts/verify_credentials', {
54
+ headers: { authorization: `Bearer ${ours.accessToken}` },
55
+ }).then(r => r.ok).catch(() => false);
56
+ if (alive) {
57
+ if (wanted) whenReady(() => sendTo(wanted));
58
+ return;
59
+ }
60
+ }
61
+
62
+ // Same origin, so the frame's own host is the instance to log into. A deep
63
+ // link is dropped here on purpose: logging in navigates the frame anyway, and
64
+ // arriving somewhere unexpected afterwards is worse than arriving home.
65
+ whenReady(() => sendTo(`#/login?instance=${encodeURIComponent(location.host)}&submit=1`));
66
+ })();
@@ -0,0 +1,43 @@
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 — client</title>
7
+ <link rel="stylesheet" href="../tokens.css">
8
+ <style>
9
+ /* Colours are the shared tokens (tokens.css); only layout is here. The bar is
10
+ a fixed height, the client takes the rest — the page never scrolls, the frame
11
+ does. */
12
+ :root { color-scheme: light dark; font-size: 125%; }
13
+ html { height: 100%; }
14
+ body { font: 1rem/1.5 system-ui, sans-serif; margin: 0; height: 100dvh;
15
+ display: flex; flex-direction: column; overflow: hidden; }
16
+ main { flex: 1 1 auto; min-height: 0; display: flex; }
17
+ #client { flex: 1 1 auto; min-height: 0; width: 100%; border: 0; }
18
+ </style>
19
+ <link rel="stylesheet" href="../bar.css">
20
+ <style>#actor-pick{display:none!important}</style>
21
+ </head>
22
+ <body>
23
+
24
+ <!-- The same bar the record carries, so the two surfaces are one thing with two
25
+ views. The client below is this agent's own, at its own root: same origin,
26
+ which is why framing it needs nothing opened beyond frame-ancestors 'self'. -->
27
+ <nav id="bar" aria-label="Site">
28
+ <h1 class="name"><a href="/?add">FediPod</a></h1>
29
+ <select id="actor-pick" aria-label="Local actors"
30
+ title=" Every actor on this machine; choosing one opens its client"></select>
31
+ <a id="bar-fediverse" href="./" title=" Open the client and read your timeline" aria-current="page">visit account</a>
32
+ <a id="bar-manage" href="../" title=" The record: what this actor is, and what you can do to it">manage account</a>
33
+ <a id="bar-signout" href="/?signout" title=" Sign out of FediPod and clear this browser">sign out</a>
34
+ </nav>
35
+
36
+ <main>
37
+ <iframe id="client" src="/app/" title="fediverse client"></iframe>
38
+ </main>
39
+
40
+ <script src="../bar.js"></script>
41
+ <script src="client.js"></script>
42
+ </body>
43
+ </html>
@@ -0,0 +1,23 @@
1
+ // common.js — what every admin page starts from: the element lookup, the
2
+ // door the page is served behind, and the two ways it talks to the agent.
3
+ // Loaded first by the record page and by the setup page.
4
+
5
+ const $ = (id) => document.getElementById(id);
6
+ // The door this page is served behind: nothing when the agent runs on this
7
+ // machine, `/app` when the identity is hosted by its own pod server. Read from
8
+ // the page's own address, so one build serves both.
9
+ const BASE = location.pathname.replace(/\/admin\/.*$/u, '');
10
+ // Every call carries `x-fedipod-page`. On the Node agent it is ignored — the
11
+ // Origin check there already refuses a page on somebody else's site. In the
12
+ // browser build the agent IS this origin's service worker, which cannot see
13
+ // Sec-Fetch-* and answers these routes with no password, so this header is the
14
+ // whole door: a cross-site form cannot set one, and a cross-origin fetch that
15
+ // sets one is stopped at a preflight the worker never grants. See sw-src.mjs.
16
+ const PAGE_HEADER = { 'x-fedipod-page': '1' };
17
+ const api = async (path, init) => {
18
+ const res = await fetch(BASE + path, { ...init, headers: { ...(init?.headers || {}), ...PAGE_HEADER } });
19
+ return { status: res.status, json: await res.json().catch(() => null) };
20
+ };
21
+ const postJson = (path, body) => api(path, {
22
+ method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body || {}),
23
+ });
@@ -0,0 +1,112 @@
1
+ // connections.js — the accounts held elsewhere: the rail, the connect forms
2
+ // for Bluesky and for a fediverse account, and the migration aliases.
3
+
4
+ // The connect form lives in the floating window, like every other form here.
5
+ // The inbound transfer panel: the aliases live here now, not in the facts.
6
+ $('do-transfer-in').addEventListener('click', () => {
7
+ solWindow.show('transfer-in-form', 'Transfer an account here');
8
+ });
9
+
10
+ // The rail: one click hides the actions; on a phone it starts hidden.
11
+ function setRail(closed) {
12
+ $('rail').classList.toggle('closed', closed);
13
+ const t = $('rail-toggle');
14
+ t.setAttribute('aria-expanded', String(!closed));
15
+ t.querySelector('.rail-arrow').textContent = closed ? '»' : '«';
16
+ t.title = closed ? ' Show the action panel' : ' Hide the action panel';
17
+ }
18
+ $('rail-toggle').addEventListener('click', () => setRail(!$('rail').classList.contains('closed')));
19
+ if (matchMedia('(max-width: 47rem)').matches) setRail(true);
20
+
21
+ // One way in for both networks, because from the record they are the same
22
+ // thing: an account elsewhere whose timeline joins the one you read here.
23
+ $('ident-open').addEventListener('click', () => {
24
+ closePanels();
25
+ solWindow.show('ident-form', 'Connect an identity');
26
+ });
27
+ $('ident-pick-fedi').addEventListener('click', () => {
28
+ solWindow.show('fediacct-form', 'Connect a Fediverse account');
29
+ });
30
+ $('ident-pick-bsky').addEventListener('click', () => {
31
+ solWindow.show('bsky-form', 'Connect a Bluesky account');
32
+ });
33
+
34
+ let bskyBusy = false;
35
+ $('bsky-form').addEventListener('submit', async (ev) => {
36
+ ev.preventDefault();
37
+ if (bskyBusy) return;
38
+ bskyBusy = true;
39
+ try {
40
+ const r = await write('/atproto/connect', {
41
+ service: $('bsky-service').value.trim(),
42
+ identifier: $('bsky-identifier').value.trim(),
43
+ appPassword: $('bsky-password').value,
44
+ }, 'bluesky account connected');
45
+ if (r) {
46
+ $('bsky-password').value = '';
47
+ closePanels();
48
+ await load();
49
+ }
50
+ } finally { bskyBusy = false; }
51
+ });
52
+ // The sign-in happens at the other server, so this hands the browser over
53
+ // rather than taking a password. What comes back lands on /fediacct/callback,
54
+ // which this machine answers and nowhere else does.
55
+ let fediBusy = false;
56
+ $('fediacct-form').addEventListener('submit', async (ev) => {
57
+ ev.preventDefault();
58
+ if (fediBusy) return;
59
+ fediBusy = true;
60
+ try {
61
+ const r = await write('/fediacct/connect', { host: $('fediacct-host').value.trim() }, '');
62
+ if (r?.authorize) location.href = r.authorize;
63
+ } finally { fediBusy = false; }
64
+ });
65
+
66
+ // Disconnecting Bluesky, pausing its feed and choosing its storage all live in
67
+ // the account's Options menu now (see optionsMenu). Connecting is still here.
68
+ // Adding resolves the old account on the agent side, so what lands in
69
+ // alsoKnownAs is its canonical id, not the string typed here.
70
+ // The accounts elsewhere this one may receive a Move from — the chips in the
71
+ // Transfer-an-account-here panel. Each entry is removable, behind a second
72
+ // click: servers still retrying a Move check the list, so removal is not a
73
+ // tidy-up.
74
+ function renderAliases() {
75
+ const box = $('alias-chips');
76
+ box.textContent = '';
77
+ for (const alias of config.aliases || []) {
78
+ let armed = false;
79
+ const rm = document.createElement('button');
80
+ rm.type = 'button';
81
+ rm.className = 'inline danger';
82
+ rm.textContent = '✕';
83
+ rm.title = ' Remove this alias';
84
+ rm.addEventListener('click', async () => {
85
+ if (!armed) {
86
+ armed = true;
87
+ rm.textContent = 'confirm ✕';
88
+ rm.title = ' Servers still retrying the Move check this alias — click again to remove it anyway';
89
+ return;
90
+ }
91
+ const r = await write('/alias', { remove: alias, confirm: true }, 'alias removed and the actor republished');
92
+ if (r) { config.aliases = r.aliases; render(); }
93
+ });
94
+ const chip = document.createElement('span');
95
+ chip.append(alias, ' ', rm);
96
+ box.append(chip, ' ');
97
+ }
98
+ }
99
+
100
+ let aliasBusy = false;
101
+ $('alias-add').addEventListener('click', async () => {
102
+ if (aliasBusy) return;
103
+ const v = $('alias-input').value.trim();
104
+ if (!v) { say('enter the old account first — @you@old.server', 'err'); return; }
105
+ aliasBusy = true;
106
+ try {
107
+ const r = await write('/alias', { add: v }, 'alias added and the actor republished');
108
+ if (r) { $('alias-input').value = ''; config.aliases = r.aliases; render(); }
109
+ } finally { aliasBusy = false; }
110
+ });
111
+
112
+
@@ -0,0 +1,111 @@
1
+ // gateway.js — the gateway row and its attach and detach forms, the inbox
2
+ // prune actions, and the load that starts the page.
3
+
4
+ const GATEWAY_CTL = $('gateway-ctl'); // held: it rides into a generated row
5
+ const GATEWAY_WORD = $('gateway-word');
6
+ const GW_OPEN_ATTACH = $('gateway-open-attach');
7
+ const GW_OPEN_DETACH = $('gateway-open-detach');
8
+ let gwState = null;
9
+ async function refreshGateway() {
10
+ const { status, json: g } = await api('/gateway');
11
+ if (status !== 200 || !g) return;
12
+ gwState = g;
13
+ GATEWAY_CTL.hidden = false;
14
+ if (g.configured) {
15
+ let host = g.url;
16
+ try { host = new URL(g.url).host; } catch { /* show it as-is */ }
17
+ const frontName = g.frontActor?.match(/\/u\/([^/]+)\/ap\/actor\/?$/)?.[1];
18
+ GATEWAY_WORD.textContent = frontName ? `${host} — publishing as @${frontName}@${host}` : host;
19
+ GW_OPEN_ATTACH.hidden = true;
20
+ GW_OPEN_DETACH.hidden = false;
21
+ } else {
22
+ GATEWAY_WORD.textContent = '';
23
+ GW_OPEN_ATTACH.hidden = false;
24
+ GW_OPEN_DETACH.hidden = true;
25
+ }
26
+ }
27
+ const gwShape = () => document.querySelector('input[name=gwShape]:checked')?.value || 'pod';
28
+ function gwPreviews() {
29
+ $('gw-pod-preview').textContent = config?.address || `@${config?.handle || 'you'}@your.pod`;
30
+ try { $('gw-front-host').textContent = new URL($('gw-front').value.trim()).host; }
31
+ catch { /* not a URL yet — the placeholder host stands */ }
32
+ }
33
+ function gwShapeChanged() { gwPreviews(); gwCheck(); }
34
+ for (const r of document.querySelectorAll('input[name=gwShape]')) r.addEventListener('change', gwShapeChanged);
35
+ // Typing in the blank IS choosing that shape.
36
+ $('gw-name').addEventListener('focus', () => {
37
+ const r = document.querySelector('input[name=gwShape][value=front]');
38
+ if (!r.checked) { r.checked = true; gwShapeChanged(); }
39
+ });
40
+ GW_OPEN_ATTACH.addEventListener('click', () => {
41
+ gwShapeChanged();
42
+ solWindow.show('gateway-attach-form', 'Attach to a gateway');
43
+ });
44
+ GW_OPEN_DETACH.addEventListener('click', () => {
45
+ $('gw-detach-fronted').hidden = !gwState?.frontActor;
46
+ $('gw-detach-plain').hidden = !!gwState?.frontActor;
47
+ solWindow.show('gateway-detach-form', 'Detach from gateway');
48
+ });
49
+ // Live availability, asked through the agent (the front answers it without CORS).
50
+ let gwTimer = null;
51
+ function gwCheck() {
52
+ clearTimeout(gwTimer);
53
+ const front = $('gw-front').value.trim().replace(/\/+$/, '');
54
+ const name = $('gw-name').value.trim().toLowerCase();
55
+ $('gw-name-msg').textContent = ''; $('gw-name-msg').className = 'hint';
56
+ if (gwShape() !== 'front' || !front || !name) return;
57
+ gwTimer = setTimeout(async () => {
58
+ const { status, json } = await postJson('/gateway', { action: 'check', front, handle: name });
59
+ if (status !== 200 || !json) return;
60
+ $('gw-name-msg').textContent = json.available
61
+ ? `${name} is free at ${front.replace(/^https?:\/\//, '')}`
62
+ : (json.reason || 'that name is taken');
63
+ $('gw-name-msg').className = json.available ? 'hint' : 'warn';
64
+ }, 300);
65
+ }
66
+ $('gw-front').addEventListener('input', () => { gwPreviews(); gwCheck(); });
67
+ $('gw-name').addEventListener('input', () => { gwPreviews(); gwCheck(); });
68
+ $('gw-attach').addEventListener('click', async () => {
69
+ const front = $('gw-front').value.trim().replace(/\/+$/, '');
70
+ const fronted = gwShape() === 'front';
71
+ const name = $('gw-name').value.trim().toLowerCase();
72
+ if (fronted && !name) { say('give the handle you want at the gateway', 'err'); return; }
73
+ $('gw-attach').disabled = true;
74
+ // Pod-based sends no name: the door's label is the agent's business, not
75
+ // the user's, and the agent picks a free variant by itself.
76
+ const r = await write('/gateway',
77
+ { action: 'attach', front, ...(fronted ? { handle: name, fronted: true } : {}) },
78
+ fronted ? 'attached — the agent is restarting to publish under the gateway handle; reload in a moment'
79
+ : 'attached — your mail now arrives through the gateway, filtered');
80
+ $('gw-attach').disabled = false;
81
+ if (r) { closePanels(); refreshGateway(); }
82
+ });
83
+ $('gw-detach').addEventListener('click', async () => {
84
+ const fronted = !!gwState?.frontActor;
85
+ const r = await write('/gateway', { action: 'forget' },
86
+ fronted ? 'detached — the agent is restarting under your pod\'s own name; reload in a moment'
87
+ : 'detached — the actor was republished advertising your pod\'s own inbox');
88
+ if (r) { closePanels(); refreshGateway(); }
89
+ });
90
+
91
+ $('inbox-keep').addEventListener('click', () => {
92
+ dismissed = true;
93
+ $('pane-inbox').hidden = true;
94
+ say('leaving it to the agent — it drains oldest first');
95
+ });
96
+
97
+ $('inbox-prune').addEventListener('click', async () => {
98
+ const days = Number($('inbox-before').value);
99
+ const before = new Date(Date.now() - days * 86400_000).toISOString();
100
+ $('inbox-prune').disabled = true;
101
+ say(`discarding content older than ${days} days — this takes a while`);
102
+ const r = await write('/inbox/prune', { before }, 'done');
103
+ $('inbox-prune').disabled = false;
104
+ if (r) {
105
+ say(`applied ${r.applied} follow/unfollow/delete, discarded ${r.dropped + r.discarded} posts`
106
+ + (r.failed ? `, ${r.failed} failed` : ''));
107
+ renderInbox();
108
+ }
109
+ });
110
+
111
+ load();