fedipod-server 0.28.1 → 0.30.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 (438) hide show
  1. package/README.md +38 -34
  2. package/config/server.json +22 -0
  3. package/dist/claims.js +3 -2
  4. package/dist/directory.d.ts +2 -0
  5. package/dist/door.d.ts +4 -0
  6. package/dist/door.js +31 -0
  7. package/dist/fedipod.d.ts +16 -0
  8. package/dist/fedipod.js +13 -0
  9. package/dist/front.d.ts +3 -0
  10. package/dist/front.js +94 -0
  11. package/dist/handler.d.ts +39 -101
  12. package/dist/handler.js +86 -617
  13. package/dist/handler.jsonld +6 -30
  14. package/dist/identities.d.ts +62 -0
  15. package/dist/identities.js +236 -0
  16. package/dist/mounts.d.ts +43 -0
  17. package/dist/mounts.js +123 -0
  18. package/dist/opt-in.d.ts +46 -0
  19. package/dist/opt-in.js +176 -0
  20. package/dist/store-pod.js +37 -0
  21. package/package.json +10 -18
  22. package/lib/client/c2s.mjs +0 -582
  23. package/lib/client/localapi.mjs +0 -87
  24. package/lib/client/masto/accounts.mjs +0 -309
  25. package/lib/client/masto/body.mjs +0 -156
  26. package/lib/client/masto/index.mjs +0 -206
  27. package/lib/client/masto/instance.mjs +0 -112
  28. package/lib/client/masto/media.mjs +0 -79
  29. package/lib/client/masto/oauth.mjs +0 -656
  30. package/lib/client/masto/render.mjs +0 -518
  31. package/lib/client/masto/statuses.mjs +0 -372
  32. package/lib/client/masto/timelines.mjs +0 -438
  33. package/lib/client/oidc-auth.mjs +0 -62
  34. package/lib/client/streaming.mjs +0 -154
  35. package/lib/client/webpush.mjs +0 -98
  36. package/lib/connections/acctfeed.mjs +0 -257
  37. package/lib/connections/atproto.mjs +0 -309
  38. package/lib/connections/bskyfeed.mjs +0 -243
  39. package/lib/connections/bskygroup.mjs +0 -162
  40. package/lib/connections/fediacct.mjs +0 -253
  41. package/lib/connections/import.mjs +0 -357
  42. package/lib/connections/tagfeed.mjs +0 -212
  43. package/lib/connections/vault.mjs +0 -114
  44. package/lib/core/as2.mjs +0 -206
  45. package/lib/core/contexts/activitystreams.json +0 -379
  46. package/lib/core/contexts/anno.json +0 -126
  47. package/lib/core/contexts/did-v1.json +0 -57
  48. package/lib/core/contexts/fep-5711.json +0 -36
  49. package/lib/core/contexts/gotosocial.json +0 -86
  50. package/lib/core/contexts/identity-v1.json +0 -152
  51. package/lib/core/contexts/index.mjs +0 -63
  52. package/lib/core/contexts/join-lemmy.json +0 -33
  53. package/lib/core/contexts/joinmastodon.json +0 -28
  54. package/lib/core/contexts/litepub-0.1.json +0 -60
  55. package/lib/core/contexts/map.json +0 -19
  56. package/lib/core/contexts/miscellany.json +0 -19
  57. package/lib/core/contexts/quotes.json +0 -18
  58. package/lib/core/contexts/schemaorg.json +0 -8845
  59. package/lib/core/contexts/security-data-integrity-v1.json +0 -78
  60. package/lib/core/contexts/security-data-integrity-v2.json +0 -81
  61. package/lib/core/contexts/security-multikey-v1.json +0 -35
  62. package/lib/core/contexts/security-v1.json +0 -74
  63. package/lib/core/contexts/webfinger.json +0 -10
  64. package/lib/core/deliver.mjs +0 -371
  65. package/lib/core/graphview.mjs +0 -327
  66. package/lib/core/intake/activities.mjs +0 -633
  67. package/lib/core/intake/activity.mjs +0 -318
  68. package/lib/core/intake/channel.mjs +0 -144
  69. package/lib/core/intake/group.mjs +0 -256
  70. package/lib/core/intake/index.mjs +0 -701
  71. package/lib/core/intake/notes.mjs +0 -360
  72. package/lib/core/intake/verify.mjs +0 -142
  73. package/lib/core/keys.mjs +0 -153
  74. package/lib/core/lease.mjs +0 -206
  75. package/lib/core/polls.mjs +0 -105
  76. package/lib/core/profile-page.mjs +0 -104
  77. package/lib/core/proof.mjs +0 -79
  78. package/lib/core/publisher/collections.mjs +0 -237
  79. package/lib/core/publisher/index.mjs +0 -500
  80. package/lib/core/publisher/notes.mjs +0 -453
  81. package/lib/core/publisher/questions.mjs +0 -239
  82. package/lib/core/publisher/restore.mjs +0 -224
  83. package/lib/core/shapes/activitystreams.ttl +0 -157
  84. package/lib/core/shapes/index.mjs +0 -132
  85. package/lib/core/shapes/shapes-text.mjs +0 -13
  86. package/lib/core/social.mjs +0 -531
  87. package/lib/core/storage.mjs +0 -176
  88. package/lib/core/store.mjs +0 -568
  89. package/lib/core/wire-quotes.mjs +0 -71
  90. package/lib/core/wire.mjs +0 -889
  91. package/lib/device/account.mjs +0 -87
  92. package/lib/device/admin/index.mjs +0 -13
  93. package/lib/device/admin/origins.mjs +0 -35
  94. package/lib/device/admin/routes/connections.mjs +0 -144
  95. package/lib/device/admin/routes/gateway.mjs +0 -199
  96. package/lib/device/admin/routes/lifecycle.mjs +0 -200
  97. package/lib/device/admin/routes/owner.mjs +0 -322
  98. package/lib/device/admin/routes/setup.mjs +0 -412
  99. package/lib/device/admin/routes/social.mjs +0 -188
  100. package/lib/device/admin/server.mjs +0 -95
  101. package/lib/device/admin/static.mjs +0 -247
  102. package/lib/device/admin/surface.mjs +0 -315
  103. package/lib/device/certs.mjs +0 -281
  104. package/lib/device/cli/commands/account.mjs +0 -586
  105. package/lib/device/cli/commands/run.mjs +0 -278
  106. package/lib/device/cli/commands/service.mjs +0 -221
  107. package/lib/device/cli/commands/setup.mjs +0 -447
  108. package/lib/device/cli/commands/state.mjs +0 -561
  109. package/lib/device/cli/context.mjs +0 -288
  110. package/lib/device/export-collections.mjs +0 -161
  111. package/lib/device/gateway-move.mjs +0 -58
  112. package/lib/device/home.mjs +0 -161
  113. package/lib/device/migrate.mjs +0 -115
  114. package/lib/device/ports.mjs +0 -27
  115. package/lib/device/remote.mjs +0 -54
  116. package/lib/device/setup.mjs +0 -459
  117. package/lib/device/update.mjs +0 -96
  118. package/lib/gateway/caches.mjs +0 -36
  119. package/lib/gateway/directory.mjs +0 -90
  120. package/lib/gateway/front-core.mjs +0 -970
  121. package/lib/gateway/gateway-core.mjs +0 -165
  122. package/lib/gateway/headers.mjs +0 -49
  123. package/lib/gateway/httpsig.mjs +0 -159
  124. package/lib/gateway/notices.mjs +0 -84
  125. package/lib/gateway/quiet.mjs +0 -189
  126. package/lib/pod/README.md +0 -61
  127. package/lib/pod/actor.mjs +0 -96
  128. package/lib/pod/collection.mjs +0 -77
  129. package/lib/pod/containers.mjs +0 -140
  130. package/lib/pod/discovery.mjs +0 -46
  131. package/lib/pod/featured.mjs +0 -18
  132. package/lib/pod/followers.mjs +0 -24
  133. package/lib/pod/following.mjs +0 -10
  134. package/lib/pod/http.mjs +0 -74
  135. package/lib/pod/inbox.mjs +0 -166
  136. package/lib/pod/links.mjs +0 -35
  137. package/lib/pod/media.mjs +0 -27
  138. package/lib/pod/notes.mjs +0 -80
  139. package/lib/pod/notifications.mjs +0 -89
  140. package/lib/pod/outbox.mjs +0 -18
  141. package/lib/pod/package.json +0 -19
  142. package/lib/pod/policy.mjs +0 -58
  143. package/lib/pod/private.mjs +0 -19
  144. package/lib/pod/root.mjs +0 -104
  145. package/lib/pod/state.mjs +0 -40
  146. package/lib/pod/transport.mjs +0 -525
  147. package/lib/pod/urls.mjs +0 -85
  148. package/lib/server/embed.mjs +0 -423
  149. package/lib/session/README.md +0 -137
  150. package/lib/session/demo.html +0 -94
  151. package/lib/session/fedi-account.mjs +0 -408
  152. package/lib/session/fedi-login.mjs +0 -123
  153. package/lib/session/oidc-session.mjs +0 -233
  154. package/lib/session/package.json +0 -49
  155. package/lib/shared/guard.mjs +0 -213
  156. package/lib/shared/links.mjs +0 -9
  157. package/lib/shared/safefetch.mjs +0 -235
  158. package/lib/shared/ua.mjs +0 -29
  159. package/phanpy/dist/404.html +0 -40
  160. package/phanpy/dist/_headers +0 -2
  161. package/phanpy/dist/apple-touch-icon.png +0 -0
  162. package/phanpy/dist/assets/ICONS-DIPJeU0C.js +0 -2
  163. package/phanpy/dist/assets/ICONS-DIPJeU0C.js.map +0 -1
  164. package/phanpy/dist/assets/ScheduledAtField-D-Abfo4J.js +0 -183
  165. package/phanpy/dist/assets/ScheduledAtField-D-Abfo4J.js.map +0 -1
  166. package/phanpy/dist/assets/Temml-CCjoacWt.woff2 +0 -0
  167. package/phanpy/dist/assets/__vite-browser-external-C7iut881.js +0 -2
  168. package/phanpy/dist/assets/__vite-browser-external-C7iut881.js.map +0 -1
  169. package/phanpy/dist/assets/boosts-carousel-BiOaNdMT.jpg +0 -0
  170. package/phanpy/dist/assets/catch-up-O5q5C75Z.png +0 -0
  171. package/phanpy/dist/assets/chunk-aKtaBQYM.js +0 -1
  172. package/phanpy/dist/assets/compose-CFEx7wb4.js +0 -2
  173. package/phanpy/dist/assets/compose-CFEx7wb4.js.map +0 -1
  174. package/phanpy/dist/assets/compose-ZJN5xSb4.js +0 -35
  175. package/phanpy/dist/assets/compose-ZJN5xSb4.js.map +0 -1
  176. package/phanpy/dist/assets/debug.module-YFQgwnIr.js +0 -52
  177. package/phanpy/dist/assets/debug.module-YFQgwnIr.js.map +0 -1
  178. package/phanpy/dist/assets/dom-p1YcJ5Rw.js +0 -2
  179. package/phanpy/dist/assets/dom-p1YcJ5Rw.js.map +0 -1
  180. package/phanpy/dist/assets/home-mobile-dark@2x-Dc-J8-SM.png +0 -0
  181. package/phanpy/dist/assets/home-mobile-light@2x-Bd3fod8m.png +0 -0
  182. package/phanpy/dist/assets/home-tablet-dark@2x-BgcNI8le.png +0 -0
  183. package/phanpy/dist/assets/home-tablet-light@2x-BW2RV9T6.png +0 -0
  184. package/phanpy/dist/assets/icons/add-circle-line-WD0mwORD.js +0 -1
  185. package/phanpy/dist/assets/icons/alert-line-CpFUMf9s.js +0 -1
  186. package/phanpy/dist/assets/icons/android-2-line-Bzxnrd3S.js +0 -1
  187. package/phanpy/dist/assets/icons/announcement-line-5nwaJFJm.js +0 -1
  188. package/phanpy/dist/assets/icons/arrow-down-circle-line-a7Id9-Iv.js +0 -1
  189. package/phanpy/dist/assets/icons/arrow-down-line-B8uzXzq6.js +0 -1
  190. package/phanpy/dist/assets/icons/arrow-left-line-CGrGT9C-.js +0 -1
  191. package/phanpy/dist/assets/icons/arrow-right-line-CE-HERfX.js +0 -1
  192. package/phanpy/dist/assets/icons/arrow-to-up-line-CvdQjmcy.js +0 -1
  193. package/phanpy/dist/assets/icons/arrow-up-circle-line-DEparSNF.js +0 -1
  194. package/phanpy/dist/assets/icons/arrow-up-line-C7yBeugc.js +0 -1
  195. package/phanpy/dist/assets/icons/arrows-right-line-BtbXmJGP.js +0 -1
  196. package/phanpy/dist/assets/icons/at-line-CF66Bznk.js +0 -1
  197. package/phanpy/dist/assets/icons/attachment-line-BY8It-l9.js +0 -1
  198. package/phanpy/dist/assets/icons/blockquote-line-BS6pU_hm.js +0 -1
  199. package/phanpy/dist/assets/icons/board-line-C0UWXyA6.js +0 -1
  200. package/phanpy/dist/assets/icons/bookmark-line-1CtX5JLF.js +0 -1
  201. package/phanpy/dist/assets/icons/building-5-line-Dd59MzbO.js +0 -1
  202. package/phanpy/dist/assets/icons/bus-2-line-6_dvA0GG.js +0 -1
  203. package/phanpy/dist/assets/icons/calendar-day-line-Crpl_fmu.js +0 -1
  204. package/phanpy/dist/assets/icons/calendar-month-line-vommts2C.js +0 -1
  205. package/phanpy/dist/assets/icons/calendar-time-add-line-BU_DTlQk.js +0 -1
  206. package/phanpy/dist/assets/icons/camera-line-Cv69MEei.js +0 -1
  207. package/phanpy/dist/assets/icons/celebrate-line-B8DvIoi0.js +0 -1
  208. package/phanpy/dist/assets/icons/chart-bar-line-FK-QOmtF.js +0 -1
  209. package/phanpy/dist/assets/icons/chart-line-line-BGcJAFWw.js +0 -1
  210. package/phanpy/dist/assets/icons/chat-3-line-YFK2-hwz.js +0 -1
  211. package/phanpy/dist/assets/icons/check-circle-line-DsAxFr5-.js +0 -1
  212. package/phanpy/dist/assets/icons/clipboard-line-BBp364gJ.js +0 -1
  213. package/phanpy/dist/assets/icons/close-circle-line-CICdiuGz.js +0 -1
  214. package/phanpy/dist/assets/icons/close-line-DYrFWcjv.js +0 -1
  215. package/phanpy/dist/assets/icons/cloud-line-tiQAbah8.js +0 -1
  216. package/phanpy/dist/assets/icons/code-line-fAl8iiV3.js +0 -1
  217. package/phanpy/dist/assets/icons/comment-2-line-CR3g7WeB.js +0 -1
  218. package/phanpy/dist/assets/icons/copy-2-line-CLW69yF5.js +0 -1
  219. package/phanpy/dist/assets/icons/delete-2-line-B8xmbr4n.js +0 -1
  220. package/phanpy/dist/assets/icons/document-3-line-DBGNhAg2.js +0 -1
  221. package/phanpy/dist/assets/icons/document-line-C7mb9sb7.js +0 -1
  222. package/phanpy/dist/assets/icons/down-line-BGqSSThd.js +0 -1
  223. package/phanpy/dist/assets/icons/edit-4-line-DcgGaEhx.js +0 -1
  224. package/phanpy/dist/assets/icons/emoji-2-line-D8YgnM-y.js +0 -1
  225. package/phanpy/dist/assets/icons/exit-line-Dc07BS-x.js +0 -1
  226. package/phanpy/dist/assets/icons/external-link-line-BvpSkmjO.js +0 -1
  227. package/phanpy/dist/assets/icons/eye-2-line-CiWXx-f_.js +0 -1
  228. package/phanpy/dist/assets/icons/eye-close-line-BgTaSJ4P.js +0 -1
  229. package/phanpy/dist/assets/icons/filter-2-line-B_mR2usy.js +0 -1
  230. package/phanpy/dist/assets/icons/filter-line-C_nm-3cU.js +0 -1
  231. package/phanpy/dist/assets/icons/flag-1-line-C2HB05I0.js +0 -1
  232. package/phanpy/dist/assets/icons/forbid-circle-line-D3LTT9yd.js +0 -1
  233. package/phanpy/dist/assets/icons/formula-line-DLrt2d6j.js +0 -1
  234. package/phanpy/dist/assets/icons/grid-line-CgUmEikf.js +0 -1
  235. package/phanpy/dist/assets/icons/group-line-C5fnX0wU.js +0 -1
  236. package/phanpy/dist/assets/icons/hand-finger-2-line-BGgxbuL9.js +0 -1
  237. package/phanpy/dist/assets/icons/hashtag-line-CyTEEXBb.js +0 -1
  238. package/phanpy/dist/assets/icons/heart-crack-line-BZddbO2C.js +0 -1
  239. package/phanpy/dist/assets/icons/heart-line-CJ7K47J4.js +0 -1
  240. package/phanpy/dist/assets/icons/history-2-line-kWWvFzqv.js +0 -1
  241. package/phanpy/dist/assets/icons/history-line-bGQiwbTH.js +0 -1
  242. package/phanpy/dist/assets/icons/home-3-line-fRU1zeLG.js +0 -1
  243. package/phanpy/dist/assets/icons/information-line-iNk6-sOY.js +0 -1
  244. package/phanpy/dist/assets/icons/keyboard-line-DsCyuQNz.js +0 -1
  245. package/phanpy/dist/assets/icons/layout-4-line-CZkOX52Z.js +0 -1
  246. package/phanpy/dist/assets/icons/layout-5-line-B_znt5wO.js +0 -1
  247. package/phanpy/dist/assets/icons/left-line-eUw0tya4.js +0 -1
  248. package/phanpy/dist/assets/icons/lightning-line-DaKoDdix.js +0 -1
  249. package/phanpy/dist/assets/icons/link-2-line-DCqLmMA7.js +0 -1
  250. package/phanpy/dist/assets/icons/list-check-line-BAxosBh9.js +0 -1
  251. package/phanpy/dist/assets/icons/lock-line-CPoaXeUW.js +0 -1
  252. package/phanpy/dist/assets/icons/mail-line-B0P8aa1L.js +0 -1
  253. package/phanpy/dist/assets/icons/moon-line-CEqNAToP.js +0 -1
  254. package/phanpy/dist/assets/icons/more-1-fill-Dw2e5xab.js +0 -1
  255. package/phanpy/dist/assets/icons/more-3-line-BIiVjYTz.js +0 -1
  256. package/phanpy/dist/assets/icons/notification-line-BZZq2Gtu.js +0 -1
  257. package/phanpy/dist/assets/icons/pencil-line-CKh8-A1v.js +0 -1
  258. package/phanpy/dist/assets/icons/photo-album-line-Bnfg2u40.js +0 -1
  259. package/phanpy/dist/assets/icons/pin-line-DpTolyhs.js +0 -1
  260. package/phanpy/dist/assets/icons/play-fill-B0-G_8lX.js +0 -1
  261. package/phanpy/dist/assets/icons/qrcode-2-line-B6SPCaHn.js +0 -1
  262. package/phanpy/dist/assets/icons/quill-pen-line-B6LUrnCP.js +0 -1
  263. package/phanpy/dist/assets/icons/quote-left-fill-hQZ1cj_6.js +0 -1
  264. package/phanpy/dist/assets/icons/radar-line-CKyroTth.js +0 -1
  265. package/phanpy/dist/assets/icons/react-line-CoIKhhtK.js +0 -1
  266. package/phanpy/dist/assets/icons/refresh-2-line-D0mTF97S.js +0 -1
  267. package/phanpy/dist/assets/icons/right-line-B3KWJaqc.js +0 -1
  268. package/phanpy/dist/assets/icons/rocket-line-4KZl537D.js +0 -1
  269. package/phanpy/dist/assets/icons/round-fill-C69NTrZ0.js +0 -1
  270. package/phanpy/dist/assets/icons/round-line-B9k_1uo-.js +0 -1
  271. package/phanpy/dist/assets/icons/route-line-B28ReHcE.js +0 -1
  272. package/phanpy/dist/assets/icons/rows-4-line-CrBniagM.js +0 -1
  273. package/phanpy/dist/assets/icons/scan-line-DSf74YdP.js +0 -1
  274. package/phanpy/dist/assets/icons/search-2-line-B6geIU1z.js +0 -1
  275. package/phanpy/dist/assets/icons/settings-3-line-azJwK47w.js +0 -1
  276. package/phanpy/dist/assets/icons/settings-6-line-C_5zc5YR.js +0 -1
  277. package/phanpy/dist/assets/icons/share-2-line-D5MxWjrm.js +0 -1
  278. package/phanpy/dist/assets/icons/share-forward-line-Cp61fZzZ.js +0 -1
  279. package/phanpy/dist/assets/icons/sparkles-2-line-DYYyXTaW.js +0 -1
  280. package/phanpy/dist/assets/icons/sparkles-line-C2hY1s6I.js +0 -1
  281. package/phanpy/dist/assets/icons/time-line-BF3eet1v.js +0 -1
  282. package/phanpy/dist/assets/icons/transfer-4-line-oisfvTBw.js +0 -1
  283. package/phanpy/dist/assets/icons/translate-line-BFWol2Ce.js +0 -1
  284. package/phanpy/dist/assets/icons/unlock-line-X_x5vwv3.js +0 -1
  285. package/phanpy/dist/assets/icons/upload-3-line-DgzwUJeW.js +0 -1
  286. package/phanpy/dist/assets/icons/user-4-line-BVSynZCp.js +0 -1
  287. package/phanpy/dist/assets/icons/user-add-2-line-BlGS5Q8q.js +0 -1
  288. package/phanpy/dist/assets/icons/user-add-line-BbXdHG4z.js +0 -1
  289. package/phanpy/dist/assets/icons/user-edit-line-BFAHOV0X.js +0 -1
  290. package/phanpy/dist/assets/icons/user-follow-line-BXux0wSd.js +0 -1
  291. package/phanpy/dist/assets/icons/user-star-line-DdYlpHLU.js +0 -1
  292. package/phanpy/dist/assets/icons/user-warning-line-Cj43j0_E.js +0 -1
  293. package/phanpy/dist/assets/icons/user-x-line-DIDgNlPH.js +0 -1
  294. package/phanpy/dist/assets/icons/volume-line-Bs390KQt.js +0 -1
  295. package/phanpy/dist/assets/icons/volume-mute-line-DzlzIqFZ.js +0 -1
  296. package/phanpy/dist/assets/icons/walk-line-yNXfB-md.js +0 -1
  297. package/phanpy/dist/assets/icons/world-2-line-CenkGIwj.js +0 -1
  298. package/phanpy/dist/assets/icons/zoom-in-line-CaENUphf.js +0 -1
  299. package/phanpy/dist/assets/icons/zoom-out-line-BDNk0EWo.js +0 -1
  300. package/phanpy/dist/assets/instances-BNPptnn-.json +0 -333
  301. package/phanpy/dist/assets/locales/ar-SA-CfheRbCu.js +0 -1
  302. package/phanpy/dist/assets/locales/ca-ES-DHRW86Tz.js +0 -1
  303. package/phanpy/dist/assets/locales/cs-CZ-CKoqEuA1.js +0 -1
  304. package/phanpy/dist/assets/locales/de-DE-ByRPMRFy.js +0 -1
  305. package/phanpy/dist/assets/locales/eo-UY-CPC7wLCS.js +0 -1
  306. package/phanpy/dist/assets/locales/es-ES-dJa0Fd9t.js +0 -1
  307. package/phanpy/dist/assets/locales/eu-ES-C3djlPR2.js +0 -1
  308. package/phanpy/dist/assets/locales/fa-IR-DTT490Ck.js +0 -1
  309. package/phanpy/dist/assets/locales/fi-FI-B23MCPuz.js +0 -1
  310. package/phanpy/dist/assets/locales/fr-FR-BcaQsO5C.js +0 -1
  311. package/phanpy/dist/assets/locales/gl-ES-BR-CVKBN.js +0 -1
  312. package/phanpy/dist/assets/locales/he-IL-CfheRbCu.js +0 -1
  313. package/phanpy/dist/assets/locales/hu-HU-CfheRbCu.js +0 -1
  314. package/phanpy/dist/assets/locales/it-IT-C1136UFV.js +0 -1
  315. package/phanpy/dist/assets/locales/ja-JP-CRZmRAhD.js +0 -1
  316. package/phanpy/dist/assets/locales/kab-Cz8xH_TT.js +0 -1
  317. package/phanpy/dist/assets/locales/ko-KR-CG9UiY4o.js +0 -1
  318. package/phanpy/dist/assets/locales/lt-LT-CJcI6sHZ.js +0 -1
  319. package/phanpy/dist/assets/locales/nb-NO-CfheRbCu.js +0 -1
  320. package/phanpy/dist/assets/locales/nl-NL-DxN4Yc1t.js +0 -1
  321. package/phanpy/dist/assets/locales/oc-FR-CfheRbCu.js +0 -1
  322. package/phanpy/dist/assets/locales/pl-PL-DkwGXf6P.js +0 -1
  323. package/phanpy/dist/assets/locales/pseudo-LOCALE-CQ4XxkQ0.js +0 -1
  324. package/phanpy/dist/assets/locales/pt-BR-nGL0HdFS.js +0 -1
  325. package/phanpy/dist/assets/locales/pt-PT-Cp17aj2z.js +0 -1
  326. package/phanpy/dist/assets/locales/ru-RU-0Y8cIoaK.js +0 -1
  327. package/phanpy/dist/assets/locales/th-TH-CfheRbCu.js +0 -1
  328. package/phanpy/dist/assets/locales/tok-CfheRbCu.js +0 -1
  329. package/phanpy/dist/assets/locales/tr-TR-CfheRbCu.js +0 -1
  330. package/phanpy/dist/assets/locales/uk-UA-C5E3dFBf.js +0 -1
  331. package/phanpy/dist/assets/locales/zh-CN-BHJi7_et.js +0 -1
  332. package/phanpy/dist/assets/locales/zh-TW-CfheRbCu.js +0 -1
  333. package/phanpy/dist/assets/main-BdqNbG-a.js +0 -26
  334. package/phanpy/dist/assets/main-BdqNbG-a.js.map +0 -1
  335. package/phanpy/dist/assets/mock-home-DGhR__He.js +0 -2
  336. package/phanpy/dist/assets/mock-home-DGhR__He.js.map +0 -1
  337. package/phanpy/dist/assets/multi-column-ROck0NVt.jpg +0 -0
  338. package/phanpy/dist/assets/multi-hashtag-timeline-DH5INVHi.jpg +0 -0
  339. package/phanpy/dist/assets/nested-comments-thread-hv59kZLc.jpg +0 -0
  340. package/phanpy/dist/assets/polyfill-force-H8sUkLGp.js +0 -2
  341. package/phanpy/dist/assets/polyfill-force-H8sUkLGp.js.map +0 -1
  342. package/phanpy/dist/assets/pwa-viewport-ogKbF51K.js +0 -20
  343. package/phanpy/dist/assets/pwa-viewport-ogKbF51K.js.map +0 -1
  344. package/phanpy/dist/assets/style-B1iLPTPR.css +0 -2
  345. package/phanpy/dist/assets/temml-DuyIQMei.js +0 -97
  346. package/phanpy/dist/assets/temml-DuyIQMei.js.map +0 -1
  347. package/phanpy/dist/assets/tinyld.light.node-DtbBhLYC.js +0 -2
  348. package/phanpy/dist/assets/tinyld.light.node-DtbBhLYC.js.map +0 -1
  349. package/phanpy/dist/assets/year-in-posts-B2lywqdx.png +0 -0
  350. package/phanpy/dist/assets/year-in-posts-DJv_star.js +0 -3
  351. package/phanpy/dist/assets/year-in-posts-DJv_star.js.map +0 -1
  352. package/phanpy/dist/compose/index.html +0 -20
  353. package/phanpy/dist/favicon.ico +0 -0
  354. package/phanpy/dist/index.html +0 -73
  355. package/phanpy/dist/logo-192.png +0 -0
  356. package/phanpy/dist/logo-512.png +0 -0
  357. package/phanpy/dist/logo-badge-72.png +0 -0
  358. package/phanpy/dist/logo-maskable-512.png +0 -0
  359. package/phanpy/dist/logo-monochrome-512.png +0 -0
  360. package/phanpy/dist/logo-monochrome-maskable-512.png +0 -0
  361. package/phanpy/dist/manifest.webmanifest +0 -1
  362. package/phanpy/dist/og-image-2.jpg +0 -0
  363. package/phanpy/dist/og-image.png +0 -0
  364. package/phanpy/dist/robots.txt +0 -2
  365. package/phanpy/dist/sw.js +0 -2
  366. package/phanpy/dist/sw.js.map +0 -1
  367. package/phanpy/dist/version.json +0 -1
  368. package/run-agent.mjs +0 -897
  369. package/vendor/gate.cjs +0 -138
  370. package/vendor/idp-grant.cjs +0 -394
  371. package/vendor/jfetch.cjs +0 -16
  372. package/web/admin/actors.js +0 -145
  373. package/web/admin/bar.css +0 -126
  374. package/web/admin/bar.js +0 -99
  375. package/web/admin/client/client.js +0 -66
  376. package/web/admin/client/index.html +0 -69
  377. package/web/admin/client-pick.js +0 -79
  378. package/web/admin/common.js +0 -23
  379. package/web/admin/connections.js +0 -112
  380. package/web/admin/gateway.js +0 -133
  381. package/web/admin/group.js +0 -258
  382. package/web/admin/index.html +0 -603
  383. package/web/admin/notices-bar.js +0 -90
  384. package/web/admin/oauth-signin.mjs +0 -37
  385. package/web/admin/record.js +0 -390
  386. package/web/admin/setup/index.html +0 -241
  387. package/web/admin/setup/setup.js +0 -326
  388. package/web/admin/tokens.css +0 -68
  389. package/web/admin/upkeep.js +0 -177
  390. package/web/admin/window.css +0 -55
  391. package/web/admin/window.js +0 -136
  392. package/web/app/README.md +0 -81
  393. package/web/app/admin-facade.mjs +0 -588
  394. package/web/app/agent.mjs +0 -663
  395. package/web/app/atproto-browser.mjs +0 -86
  396. package/web/app/authorize_interaction.html +0 -56
  397. package/web/app/authorize_interaction.js +0 -69
  398. package/web/app/boot.mjs +0 -576
  399. package/web/app/deliver-relay.mjs +0 -129
  400. package/web/app/dist/boot.js +0 -34218
  401. package/web/app/dist/boot.js.map +0 -7
  402. package/web/app/dist/sw.js +0 -72810
  403. package/web/app/dist/sw.js.map +0 -7
  404. package/web/app/fediacct-browser.mjs +0 -146
  405. package/web/app/gateway-move.mjs +0 -60
  406. package/web/app/idb-kv.mjs +0 -35
  407. package/web/app/index.html +0 -242
  408. package/web/app/keys-browser.mjs +0 -71
  409. package/web/app/keystore.mjs +0 -82
  410. package/web/app/oidc-session.mjs +0 -6
  411. package/web/app/pod-auth.mjs +0 -248
  412. package/web/app/pod-remote.mjs +0 -84
  413. package/web/app/shims/_globals.mjs +0 -2
  414. package/web/app/shims/fedify-sig.mjs +0 -77
  415. package/web/app/shims/node-crypto.mjs +0 -113
  416. package/web/app/shims/node-fs.mjs +0 -17
  417. package/web/app/shims/node-path.mjs +0 -19
  418. package/web/app/shims/node-url.mjs +0 -7
  419. package/web/app/shims/prelude.js +0 -58
  420. package/web/app/shims/safefetch.mjs +0 -58
  421. package/web/app/shims/shapes-text.mjs +0 -8
  422. package/web/app/shims/web-push.mjs +0 -14
  423. package/web/app/signup.mjs +0 -318
  424. package/web/app/sw-src.mjs +0 -220
  425. package/web/app/update.js +0 -70
  426. package/web/front/admin.html +0 -55
  427. package/web/front/admin.js +0 -89
  428. package/web/front/install.sh +0 -69
  429. package/web/front/new-account.html +0 -35
  430. package/web/front/new-account.js +0 -13
  431. package/web/front/notices.html +0 -69
  432. package/web/front/notices.js +0 -107
  433. package/web/front/run.html +0 -58
  434. package/web/front/run.js +0 -195
  435. package/web/front/solid-oidc-client.js +0 -6
  436. package/web/icons/forum.svg +0 -11
  437. package/web/icons/gateway.svg +0 -14
  438. package/web/icons/local.svg +0 -11
@@ -1,588 +0,0 @@
1
- // admin-facade.mjs — the owner's record/manage surface, answered in the browser.
2
- //
3
- // The Node agent serves web/admin over lib/admin.mjs (buildAdminSurface), which
4
- // is Node through and through — filesystem static serving, process control,
5
- // multi-actor, group moderation. The browser reuses the SAME web/admin pages
6
- // (staged static by Netlify) but cannot run that handler: it needs fs, http,
7
- // child_process. So this answers the DATA endpoints those pages call, for the
8
- // one thing a browser identity is: a single, personal, embedded actor.
9
- //
10
- // Personal only. Everything group (joins, members, moderation, mute) or local
11
- // (drain, profiles-of-siblings, start/stop, state-move, update) is either 404
12
- // here or hidden by the page on kind:'person'. What remains is reading and
13
- // editing your own identity: status, config, gateway, alias, key rotation.
14
- //
15
- // It mirrors lib/admin.mjs's handlers for those paths, calling the same agent
16
- // objects (store, publisher, intake, deliverer, remote) the Node agent does.
17
- import { publicHandle, webfingerHost } from '../../lib/core/wire.mjs';
18
- import * as podInbox from '../../lib/pod/inbox.mjs';
19
- import { normalizeImport, IMPORT_KINDS } from '../../lib/connections/import.mjs';
20
- import { followActor, followHandle } from '../../lib/core/social.mjs';
21
- import { hashPassword } from '../../lib/client/masto/index.mjs';
22
-
23
- // The identity itself — changing any means a different actor, i.e. a new setup.
24
- const PERMANENT_CONFIG = ['handle', 'remotePod', 'issuer', 'root', 'kind'];
25
- // Carried in the actor document, so a change is not live until republished.
26
- const WIRE_CONFIG = ['name', 'summary', 'icon', 'image', 'fields', 'aliases'];
27
-
28
- // The data endpoints this agent answers at the origin root (the page computes
29
- // its base as the origin, so it asks for /status, not /admin/status). Anything
30
- // not here falls through to the network as a static file or the gateway.
31
- export const ADMIN_PATHS = new Set([
32
- '/status', '/config', '/gateway', '/alias', '/rotate-key', '/import',
33
- '/deadletter', '/blocks', '/profiles', '/modqueue', '/log', '/fediacct', '/describe', '/follow',
34
- '/atproto', '/atproto/connect', '/atproto/disconnect',
35
- '/rebuild', '/move', '/retire', '/inbox/prune', '/park', '/revive', '/takeover',
36
- '/gateway/pause', '/gateway/close',
37
- '/fediacct/connect', '/fediacct/disconnect', '/fediacct/callback',
38
- ]);
39
-
40
- // The page the fediverse account's OAuth redirect lands on (a top-level
41
- // navigation, so it is HTML, not JSON). On success it steps back to the record
42
- // page on its own; either way it offers the link.
43
- function callbackHtml(ok, msg) {
44
- const esc = (s) => String(s).replace(/[&<>"]/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c]));
45
- return '<!doctype html><html lang="en"><head><meta charset="utf-8">'
46
- + '<meta name="viewport" content="width=device-width, initial-scale=1">'
47
- + (ok ? '<meta http-equiv="refresh" content="2;url=/admin/">' : '')
48
- + `<title>FediPod — ${ok ? 'connected' : 'not connected'}</title>`
49
- + '<link rel="stylesheet" href="/admin/tokens.css">'
50
- + '<style>body{font:20px/1.5 system-ui,sans-serif;margin:0;background:var(--surface);color:var(--fg);'
51
- + 'display:grid;min-height:100dvh;place-items:center}main{max-width:32rem;padding:2rem;text-align:center}'
52
- + 'h1{color:var(--heading)}a{color:var(--accent)}.err{color:var(--err)}</style></head><body><main>'
53
- + `<h1>${ok ? 'Account connected' : 'Could not connect'}</h1>`
54
- + `<p class="${ok ? '' : 'err'}">${esc(msg)}</p>`
55
- + '<p><a href="/admin/">Back to your account</a></p></main></body></html>';
56
- }
57
-
58
- export class AdminFacade {
59
- constructor({ agent, log = console.log }) { this.agent = agent; this.log = log; }
60
-
61
- // Resolve an old account (a URL, or @user@host) to its canonical actor id,
62
- // verifying by dereference the way intake does — a URL may claim any id, so
63
- // that id must answer for itself before it is stored as an alias.
64
- async resolveActor(input) {
65
- const a = this.agent;
66
- const s = String(input || '').trim().replace(/^@/, '');
67
- if (!s) return null;
68
- if (/^https?:\/\//.test(s)) {
69
- let doc = await a.intake.fetchAP(s).catch(() => null);
70
- if (doc?.id && doc.id !== s) {
71
- const own = await a.intake.fetchAP(doc.id).catch(() => null);
72
- doc = own?.id === doc.id ? own : null;
73
- }
74
- return doc?.id || null;
75
- }
76
- const at = s.indexOf('@');
77
- if (at < 1) return null;
78
- const host = s.slice(at + 1);
79
- const wf = `https://${host}/.well-known/webfinger?resource=${encodeURIComponent('acct:' + s)}`;
80
- try {
81
- const res = await a.deliverer.signedFetch(wf, { method: 'GET', headers: { accept: 'application/jrd+json' } });
82
- const jrd = await res.json();
83
- const self = (jrd?.links || []).find(l => l.rel === 'self' && /(activity|ld)\+json/.test(l.type || ''));
84
- if (!self?.href) return null;
85
- const doc = await a.intake.fetchAP(self.href).catch(() => null);
86
- return doc?.id || null;
87
- } catch { return null; }
88
- }
89
-
90
- /** Answer an admin data request. Returns true when it owns the path. */
91
- async handle(req, res, p, url, bodyText) {
92
- const a = this.agent;
93
- const json = (status, obj) => {
94
- res.writeHead(status, { 'content-type': 'application/json' });
95
- res.end(JSON.stringify(obj));
96
- return true;
97
- };
98
- if (!ADMIN_PATHS.has(p)) return false;
99
- const method = req.method;
100
- const isGroup = () => a.store.getConfig()?.kind === 'group';
101
-
102
- // ---- reads ----
103
- if (method === 'GET') {
104
- switch (p) {
105
- case '/status': return json(200, a.status());
106
- case '/config': {
107
- const cfg = a.store.getConfig();
108
- if (!cfg) return json(409, { error: 'agent not configured' });
109
- const urls = a.urls || a.publisher?.urls || null;
110
- const wfHost = urls ? new URL(urls.base).host : null;
111
- const address = cfg.gateway?.frontActor
112
- ? `@${publicHandle(cfg)}@${new URL(cfg.gateway.frontActor).host}`
113
- : (wfHost ? `@${cfg.handle}@${wfHost}` : null);
114
- return json(200, {
115
- handle: cfg.handle, remotePod: cfg.remotePod, issuer: cfg.issuer,
116
- root: cfg.root || null, kind: cfg.kind || 'person',
117
- actor: urls?.actor || null, webId: a.webId || null,
118
- accountId: urls?.actor ? a.store.idFor(urls.actor) : null,
119
- address,
120
- name: cfg.name || null, summary: cfg.summary || null, icon: cfg.icon || null,
121
- image: cfg.image || null, fields: cfg.fields || [],
122
- aliases: cfg.aliases || [],
123
- autoAcceptFollows: !!cfg.autoAcceptFollows,
124
- hasUiPassword: !!cfg.uiPassword,
125
- quiescedAt: cfg.quiescedAt || null, movedTo: cfg.movedTo || null,
126
- mode: a.status().mode,
127
- // Live from the credential's actual backend on THIS browser (a
128
- // browser-stored connection is only present where its token is);
129
- // crossPost stays a default-on config value, no longer a toggle.
130
- atproto: (() => { const s = a.atproto?.status(); return s?.connected
131
- ? { handle: s.handle, service: s.service, did: s.did, connected: true, storage: s.storage, feedPaused: s.feedPaused, crossPost: cfg.atproto?.crossPost ?? true }
132
- : null; })(),
133
- fediAccounts: a.fediaccts?.status() || [],
134
- });
135
- }
136
- case '/gateway': {
137
- const g = a.store.getConfig()?.gateway || null;
138
- return json(200, {
139
- configured: !!(g && g.url), url: g?.url || null, webId: g?.webId || null,
140
- frontActor: g?.frontActor || null, mode: g?.mode || 'off', hasSecret: !!g?.hmacSecret,
141
- stats: a.store.read('gateway-stats.json', { verified: 0, unverified: 0, lastAt: null }),
142
- // The account's standing at the gateway — paused, closed — as the
143
- // gateway last said it (agent.mjs tellGateway). The record page
144
- // shows its pause and close controls only when this is here.
145
- standing: a.gatewayStanding || null,
146
- });
147
- }
148
- case '/deadletter': return json(200, { items: a.store.getDeadLetters() });
149
- case '/blocks': return json(200, a.store.getBlocklist());
150
- // One personal identity — no siblings on a machine to list. The bar's
151
- // dropdown shows this one plus "add an account".
152
- case '/profiles': {
153
- const s = a.status();
154
- const host = s.actor ? new URL(s.actor).host : null;
155
- return json(200, {
156
- identities: [{
157
- name: s.handle, current: true, handle: s.handle,
158
- address: (s.handle && host) ? `${s.handle}@${host}` : null,
159
- admin: '/admin/', app: '/app/', kind: s.kind, mode: s.mode,
160
- }],
161
- });
162
- }
163
- case '/modqueue': return json(200, []); // person: no moderation queue
164
- case '/log': return json(200, { lines: [] }); // no local log file in the browser
165
- case '/fediacct': return json(200, { accounts: a.fediaccts?.status() || [] });
166
- case '/import':
167
- return json(200, a.importer ? a.importer.progress() : { running: false, total: 0, done: 0 });
168
- // The OAuth redirect returns here as a top-level navigation, so this one
169
- // answers with an HTML page, not JSON. It completes the connection, then
170
- // shows the result with a way back to the record page.
171
- case '/fediacct/callback': {
172
- const q = url.searchParams;
173
- const page = (ok, msg) => { res.writeHead(ok ? 200 : 400, { 'content-type': 'text/html; charset=utf-8' }); res.end(callbackHtml(ok, msg)); return true; };
174
- if (q.get('error')) return page(false, q.get('error_description') || q.get('error'));
175
- if (!q.get('code') || !q.get('state')) return page(false, 'that sign-in came back incomplete');
176
- try {
177
- const row = await a.fediaccts.complete({ state: q.get('state'), code: q.get('code') });
178
- // The roster is computed live from the backends by /config; a
179
- // browser-stored token must not put the account on the pod, so
180
- // nothing is written to pod config here.
181
- a.startAccts?.();
182
- return page(true, `${row.handle} is connected.`);
183
- } catch (e) { return page(false, e.message); }
184
- }
185
- default: return json(405, { error: 'GET not supported here' });
186
- }
187
- }
188
-
189
- if (method !== 'POST') return json(405, { error: 'POST only' });
190
- let body = {};
191
- try { body = bodyText ? JSON.parse(bodyText) : {}; }
192
- catch { return json(400, { error: 'expected JSON body' }); }
193
-
194
- // ---- writes ----
195
- switch (p) {
196
- case '/config': {
197
- const fixed = PERMANENT_CONFIG.filter(k => k in body);
198
- if (fixed.length) return json(400, { error: `${fixed.join(', ')} cannot be changed — that is the identity itself` });
199
- // Group-only knobs are not offered on a personal identity.
200
- for (const k of ['approveJoins', 'review', 'moderators']) {
201
- if (k in body) return json(404, { error: 'not a group' });
202
- }
203
- await a.requestTakeover?.();
204
- const cfg = { ...a.store.getConfig() };
205
- if ('name' in body) {
206
- if (!body.name) return json(400, { error: 'a display name is required' });
207
- cfg.name = String(body.name);
208
- }
209
- if ('summary' in body) cfg.summary = body.summary || undefined;
210
- if ('icon' in body) cfg.icon = body.icon || undefined;
211
- if ('image' in body) cfg.image = body.image || undefined;
212
- if ('fields' in body) {
213
- cfg.fields = (Array.isArray(body.fields) ? body.fields : [])
214
- .filter(f => f?.name?.trim())
215
- .map(f => ({ name: String(f.name).trim(), value: String(f.value ?? '').trim() }));
216
- }
217
- if ('autoAcceptFollows' in body) cfg.autoAcceptFollows = !!body.autoAcceptFollows;
218
- if ('password' in body) {
219
- if (body.password) cfg.uiPassword = hashPassword(body.password);
220
- else delete cfg.uiPassword;
221
- }
222
- const republish = WIRE_CONFIG.some(k => k in body);
223
- a.store.setConfig(cfg);
224
- if ('autoAcceptFollows' in body && a.publisher) a.publisher.config.autoAcceptFollows = cfg.autoAcceptFollows;
225
- if (republish && a.publisher) {
226
- Object.assign(a.publisher.config, {
227
- name: cfg.name, summary: cfg.summary, icon: cfg.icon,
228
- image: cfg.image, fields: cfg.fields, aliases: cfg.aliases,
229
- });
230
- }
231
- await a.store.flush();
232
- const pub = republish ? await a.publisher.publishProfile() : null;
233
- return json(200, {
234
- ok: true, published: republish,
235
- ...(pub?.unreachable?.length ? { unreachable: pub.unreachable } : {}),
236
- });
237
- }
238
-
239
- case '/describe': {
240
- const cfg = { ...a.store.getConfig() };
241
- if ('summary' in body) cfg.summary = body.summary || undefined;
242
- if ('icon' in body) cfg.icon = body.icon || undefined;
243
- a.store.setConfig(cfg);
244
- Object.assign(a.publisher.config, { summary: cfg.summary, icon: cfg.icon });
245
- await a.store.flush();
246
- await a.publisher.publishProfile();
247
- return json(200, { ok: true, summary: cfg.summary || null, icon: cfg.icon || null });
248
- }
249
-
250
- // Following somebody, the same call the Node agent's admin surface takes
251
- // (lib/device/admin/routes/social.mjs): by handle normally, by actor url
252
- // when WebFinger cannot answer for them. The remote-follow page
253
- // (/authorize_interaction) is what asks for it here — another server
254
- // sends its reader to that page when they press Follow and say they are
255
- // at this host.
256
- case '/follow': {
257
- await a.requestTakeover?.();
258
- if (body.actor) { await followActor(a, String(body.actor)); return json(200, { ok: true, actor: String(body.actor) }); }
259
- if (!body.handle) return json(400, { error: 'handle or actor required' });
260
- return json(200, await followHandle(a, String(body.handle)));
261
- }
262
-
263
- // Taking the account back. A viewer publishes nothing and drains
264
- // nothing, so an account whose lease is held by something that is not
265
- // running any more stays silent with no way to say so. The owner acting
266
- // HERE outranks whatever holds it.
267
- case '/takeover': {
268
- if (!await a.requestTakeover?.()) return json(503, { error: 'the lease could not be taken — the pod refused the write' });
269
- return json(200, { ok: true, mode: a.status().mode });
270
- }
271
-
272
- // The account's standing at its gateway: paused by its owner, or closed
273
- // for good (front-core: accounts that go quiet). Both go to the gateway
274
- // with the pod session as proof, and its answer is the new standing.
275
- case '/gateway/pause': {
276
- if (typeof body.paused !== 'boolean') return json(400, { error: 'paused must be true or false' });
277
- if (!a.pauseAtGateway || !a.gatewayApi) return json(501, { error: 'this account is not at a gateway' });
278
- const r = await a.pauseAtGateway(body.paused);
279
- if (!r) return json(502, { error: 'the gateway could not be reached' });
280
- return json(r.status === 200 ? 200 : r.status, r);
281
- }
282
- case '/gateway/close': {
283
- const handle = String(a.store.getConfig()?.handle || '').toLowerCase();
284
- if (!handle || String(body.confirm || '').toLowerCase() !== handle) return json(400, { error: 'type the handle to confirm' });
285
- if (!a.closeAtGateway || !a.gatewayApi) return json(501, { error: 'this account is not at a gateway' });
286
- const r = await a.closeAtGateway();
287
- if (!r) return json(502, { error: 'the gateway could not be reached' });
288
- return json(r.status === 200 ? 200 : r.status, r);
289
- }
290
-
291
- // Going quiet, and coming back. The record page's active/parked select.
292
- case '/park': {
293
- if (!await a.requestTakeover?.()) return json(503, { error: 'another device is active for this pod — park from there' });
294
- return json(200, { ok: true, ...await a.park() });
295
- }
296
- case '/revive': {
297
- if (!await a.requestTakeover?.()) return json(503, { error: 'another device is active for this pod — revive from there' });
298
- return json(200, { ok: true, ...await a.revive() });
299
- }
300
-
301
- case '/rotate-key': {
302
- await a.requestTakeover?.();
303
- const r = await a.rotateKey();
304
- return json(200, { ok: true, changed: !!r?.changed });
305
- }
306
-
307
- // Recover posts this browser lost, from what the pod still holds.
308
- case '/rebuild': {
309
- if (!await a.requestTakeover?.()) return json(503, { error: 'another device is active for this pod — recover from there' });
310
- return json(200, await a.publisher.rebuildStatuses({ fromNotes: !!body.fromNotes }));
311
- }
312
-
313
- // Catch up on a backlog: drop old inbox posts (older than `before`) but
314
- // keep applying the follows/unfollows/accepts/deletes in that window. The
315
- // browser's own Intake does the work, exactly as the Node agent's does.
316
- case '/inbox/prune': {
317
- if (!body.before) return json(400, { error: 'before (a date) required' });
318
- if (!await a.requestTakeover?.()) return json(503, { error: 'another device is active for this pod — discard from there' });
319
- return json(200, await a.intake.prune({ before: body.before, ...(body.keepConcerning ? { keepConcerning: true } : {}) }));
320
- }
321
-
322
- // Hand the account away: a federated Move migrates followers to the
323
- // target, this handle is left redirecting. The typed-handle interlock is
324
- // the same as the Node agent's — a stray click cannot produce it.
325
- case '/move': {
326
- if (!body.target) return json(400, { error: 'target required' });
327
- if (!body.confirm || body.confirm !== a.store.getConfig()?.handle) {
328
- return json(400, { error: 'type the handle to confirm' });
329
- }
330
- await a.requestTakeover?.();
331
- let target = String(body.target).trim();
332
- if (!/^https?:\/\//.test(target)) {
333
- const id = await this.resolveActor(target);
334
- if (!id) return json(400, { error: `could not resolve ${body.target}` });
335
- target = id;
336
- }
337
- return json(200, { ok: true, ...await a.moveTo(target) });
338
- }
339
-
340
- // End the identity for good: a Delete to every follower, the actor
341
- // replaced by a Tombstone that stays fetchable. Typed-handle interlock.
342
- case '/retire': {
343
- if (!body.confirm || body.confirm !== a.store.getConfig()?.handle) {
344
- return json(400, { error: 'type the handle to confirm' });
345
- }
346
- await a.requestTakeover?.();
347
- return json(200, { ok: true, ...await a.publisher.retireActor() });
348
- }
349
-
350
- case '/alias': {
351
- const urls = a.publisher?.urls;
352
- if (!urls) return json(409, { error: 'agent not connected yet' });
353
- if (!body.add && !body.remove) return json(400, { error: 'add or remove required' });
354
- await a.requestTakeover?.();
355
- const cfg = { ...a.store.getConfig() };
356
- const aliases = [...(cfg.aliases || [])];
357
- if (body.add) {
358
- if (!webfingerHost(urls.base) && !cfg.gateway?.frontActor) {
359
- return json(400, { error: 'this pod is a suffix-based host, so other servers could never resolve it as a Move target' });
360
- }
361
- const id = await this.resolveActor(body.add);
362
- if (!id) return json(400, { error: `could not fetch the old account (${body.add}) — enter its URL or @user@host, and it must answer` });
363
- if (id === urls.actor) return json(400, { error: 'that is this account' });
364
- if (!aliases.includes(id)) aliases.push(id);
365
- } else {
366
- const target = String(body.remove).trim();
367
- if (!aliases.includes(target)) return json(404, { error: 'no such alias' });
368
- if (!body.confirm) {
369
- return json(409, { error: 'servers still retrying the Move check this alias — send confirm: true to remove it anyway' });
370
- }
371
- aliases.splice(aliases.indexOf(target), 1);
372
- }
373
- cfg.aliases = aliases;
374
- a.store.setConfig(cfg);
375
- a.publisher.config.aliases = aliases;
376
- await a.store.flush();
377
- const pub = await a.publisher.publishProfile();
378
- return json(200, { ok: true, aliases, ...(pub?.unreachable?.length ? { unreachable: pub.unreachable } : {}) });
379
- }
380
-
381
- case '/gateway': {
382
- const cfg = { ...a.store.getConfig() };
383
- const g = { ...(cfg.gateway || {}) };
384
- const inboxUrl = a.publisher?.urls?.inbox;
385
- if (body.action === 'check') {
386
- const front = String(body.front || '').replace(/\/+$/, '');
387
- try { new URL(front); } catch { return json(400, { error: 'front must be a gateway origin URL' }); }
388
- const chk = await fetch(`${front}/api/handle?handle=${encodeURIComponent(String(body.handle || '').toLowerCase())}`,
389
- { headers: { accept: 'application/json' } }).then(r => r.json()).catch(() => null);
390
- if (!chk) return json(502, { error: `${front} did not answer its handle check` });
391
- return json(200, { available: !!chk.available, reason: chk.reason || null });
392
- }
393
- await a.requestTakeover?.();
394
- const persist = async () => {
395
- cfg.gateway = g; a.store.setConfig(cfg);
396
- if (a.publisher) a.publisher.config.gateway = g;
397
- await a.store.flush();
398
- };
399
- if (body.action === 'configure') {
400
- if (!/^https:\/\/\S+$/.test(String(body.url || ''))) return json(400, { error: 'gateway url must be https' });
401
- if (!/^https?:\/\/\S+$/.test(String(body.webId || ''))) return json(400, { error: 'gateway webId must be a URL' });
402
- g.url = String(body.url); g.webId = String(body.webId);
403
- if (!g.hmacSecret) {
404
- const rnd = crypto.getRandomValues(new Uint8Array(32));
405
- g.hmacSecret = btoa(String.fromCharCode(...rnd));
406
- }
407
- g.mode = g.mode || 'off';
408
- if ('frontActor' in body) {
409
- const fa = String(body.frontActor || '');
410
- if (fa && !/^https:\/\/\S+\/ap\/actor$/.test(fa)) return json(400, { error: 'frontActor must be an https …/ap/actor URL' });
411
- g.frontActor = fa || undefined;
412
- }
413
- await persist();
414
- return json(200, { ok: true, mode: g.mode, url: g.url, webId: g.webId, hmacSecret: g.hmacSecret });
415
- }
416
- if (body.action === 'mode') {
417
- const target = body.mode;
418
- if (!['off', 'shadow', 'trust', 'locked'].includes(target)) return json(400, { error: 'mode must be off, shadow, trust or locked' });
419
- if (target !== 'off' && !g.url) return json(400, { error: 'point at a gateway first (action: configure)' });
420
- if (target === 'locked' && !g.webId) return json(400, { error: "locked needs the gateway's WebID — set it with action: configure" });
421
- const prev = g.mode || 'off';
422
- if (target === 'locked') await a.publisher?.lockInboxToGateway(g.webId);
423
- else if (prev === 'locked' && inboxUrl) await podInbox.setPosture(a.remote, a.urls, 'open');
424
- g.mode = target;
425
- await persist();
426
- if ((prev === 'off') !== (target === 'off')) await a.publisher?.publishProfile();
427
- else if (target !== 'off') await a.publisher?.publishGatewayPolicy().catch(() => {});
428
- return json(200, { ok: true, mode: g.mode });
429
- }
430
- // Attach the pod's mail through a gateway (fedipod.net and kin). The
431
- // agent proves the pod with its own DPoP session — no password — and the
432
- // front answers with the door URL and the receipt secret. Browser model
433
- // only: @you@yourpod (inbox door), never a fronted @you@front — the
434
- // latter renames every id and needs a process restart the browser has
435
- // no equivalent for.
436
- if (body.action === 'attach') {
437
- const front = String(body.front || '').replace(/\/+$/, '');
438
- let fu;
439
- try { fu = new URL(front); } catch { return json(400, { error: 'front must be the gateway origin, like https://fedipod.net' }); }
440
- if (fu.protocol !== 'https:' && !/^(localhost|127\.0\.0\.1)$|\.localhost$/.test(fu.hostname)) return json(400, { error: 'front must be https' });
441
- if (body.fronted === true) return json(400, { error: 'a fronted @you@front identity is not the browser model — the browser uses the mail-door form @you@yourpod' });
442
- const named = !!String(body.handle || '').trim();
443
- let handle = String(body.handle || cfg.handle || '').toLowerCase().trim();
444
- if (!handle) return json(400, { error: 'a name at the gateway is required' });
445
- const avail = async (h) => fetch(`${front}/api/handle?handle=${encodeURIComponent(h)}`,
446
- { headers: { accept: 'application/json' } }).then(r => r.json()).catch(() => null);
447
- let chk = await avail(handle);
448
- if (!chk) return json(502, { error: `${front} did not answer its handle check` });
449
- if (!chk.available && !named) {
450
- for (let i = 2; i <= 9 && !chk.available; i++) {
451
- const cand = `${handle}${i}`; const c = await avail(cand);
452
- if (c?.available) { handle = cand; chk = c; }
453
- }
454
- }
455
- if (!chk.available) return json(409, { error: chk.reason || `the name ${handle} is taken at ${front}` });
456
- const attach = await a.remote.session.fetch(`${front}/api/attach`, {
457
- method: 'POST', headers: { 'content-type': 'application/json' },
458
- body: JSON.stringify({ handle, podHome: a.urls.home, kind: cfg.kind || 'person', fronted: false }),
459
- }).catch(() => null);
460
- if (!attach) return json(502, { error: `${front} did not answer the attach` });
461
- const d = await attach.json().catch(() => ({}));
462
- if (attach.status !== 201) {
463
- return json(attach.status >= 400 && attach.status < 500 ? attach.status : 502, { error: d.error || `attach failed (HTTP ${attach.status})` });
464
- }
465
- g.url = String(d.doorInbox || `${front}/u/${handle}/ap/inbox/`);
466
- if (d.hmacSecret) g.hmacSecret = String(d.hmacSecret);
467
- if (!g.mode || g.mode === 'off') g.mode = 'shadow';
468
- await persist();
469
- await a.publisher?.publishProfile();
470
- await a.publisher?.publishGatewayPolicy?.().catch(() => {});
471
- return json(200, { ok: true, mode: g.mode, url: g.url });
472
- }
473
- // Detach: forget the gateway and re-advertise the pod's own inbox.
474
- if (body.action === 'forget') {
475
- if (g.frontActor) return json(400, { error: 'this identity publishes under a gateway front; detaching a front is not supported in the browser build' });
476
- const wasLocked = g.mode === 'locked';
477
- delete cfg.gateway; a.store.setConfig(cfg);
478
- if (a.publisher) a.publisher.config.gateway = undefined;
479
- await a.store.flush();
480
- if (wasLocked && inboxUrl) await podInbox.setPosture(a.remote, a.urls, 'open').catch(() => {});
481
- await a.publisher?.publishProfile();
482
- return json(200, { ok: true, mode: 'off', forgotten: true });
483
- }
484
- return json(400, { error: 'unknown gateway action' });
485
- }
486
-
487
- // Connect a Bluesky account. This is the one place an app password is
488
- // seen; it is stored owner-only on the pod (never returned), and the
489
- // mirror poll starts. Same shape as the Node agent's /atproto/connect.
490
- case '/atproto/connect': {
491
- if (!body.identifier || !body.appPassword) return json(400, { error: 'identifier and appPassword required' });
492
- await a.requestTakeover?.();
493
- let conn;
494
- try { conn = await a.atproto.connect({ service: body.service, identifier: body.identifier, appPassword: body.appPassword }); }
495
- catch (e) { return json(400, { error: e.message }); }
496
- const cfg = a.store.getConfig();
497
- a.store.setConfig({ ...cfg, atproto: { ...conn, crossPost: cfg.atproto?.crossPost ?? true } });
498
- await a.store.flush();
499
- a.startBsky?.();
500
- return json(200, { ok: true, ...a.atproto.status() });
501
- }
502
- case '/atproto/disconnect': {
503
- a.stopBsky?.();
504
- await a.atproto.disconnect();
505
- const { atproto, ...rest } = a.store.getConfig();
506
- a.store.setConfig(rest);
507
- await a.store.flush();
508
- return json(200, { ok: true });
509
- }
510
- // Bluesky settings: pause/resume the mirror, and move the credential
511
- // between this browser and the pod. Cross-post is default-on, not toggled.
512
- case '/atproto': {
513
- if (!a.atproto?.connected()) return json(400, { error: 'no bluesky account connected' });
514
- if ('feedPaused' in body) { a.atproto.setFeedPaused(!!body.feedPaused); a.restartBsky?.(); }
515
- if ('storage' in body) {
516
- if (!['pod', 'browser'].includes(body.storage)) return json(400, { error: 'storage must be pod or browser' });
517
- a.atproto.setStorage(body.storage);
518
- }
519
- return json(200, { ok: true, ...a.atproto.status() });
520
- }
521
-
522
- // Connect a fediverse account on another server: start its OAuth at that
523
- // server. The browser is then sent to `authorize`; the code comes back to
524
- // GET /fediacct/callback (above). The token never touches the pod.
525
- case '/fediacct/connect': {
526
- if (!body.host) return json(400, { error: 'the address of the server is required' });
527
- try {
528
- const { url: authorize } = await a.fediaccts.begin({ host: body.host, redirectUri: `${url.origin}/fediacct/callback` });
529
- return json(200, { ok: true, authorize });
530
- } catch (e) { return json(400, { error: e.message }); }
531
- }
532
- case '/fediacct/disconnect': {
533
- if (!body.id) return json(400, { error: 'id required' });
534
- await a.fediaccts.revoke(body.id); // best-effort revoke at the server, then forget it here
535
- if (!a.fediaccts.remove(body.id)) return json(404, { error: 'no such account' });
536
- a.restartAccts?.();
537
- return json(200, { ok: true });
538
- }
539
- // Pause/resume the mirror, or move the credential between browser and pod.
540
- case '/fediacct': {
541
- if (!body.id) return json(400, { error: 'id required' });
542
- let row;
543
- if ('storage' in body) {
544
- if (!['pod', 'browser'].includes(body.storage)) return json(400, { error: 'storage must be pod or browser' });
545
- row = a.fediaccts.setStorage(body.id, body.storage);
546
- } else row = a.fediaccts.setEnabled(body.id, !!body.enabled);
547
- if (!row) return json(404, { error: 'no such account' });
548
- a.restartAccts?.();
549
- return json(200, { ok: true, account: row });
550
- }
551
-
552
- // CSV import — follows, blocks, mutes, lists and domains, staged onto the
553
- // pod and applied by a worker over the following minutes. The same
554
- // ImportWorker the Node agent runs (lib/import.mjs); it needs only the
555
- // store and social.mjs, both of which are in this bundle.
556
- case '/import': {
557
- if (!a.importer) return json(409, { error: 'agent not connected yet' });
558
- // Staging arms a worker that keeps writing for minutes, so a device
559
- // that could not take the lease must not run one beside the device
560
- // that holds it.
561
- if (!await a.requestTakeover?.()) {
562
- return json(503, { error: 'another device is active for this pod — import from there' });
563
- }
564
- if (body.clear === true) {
565
- a.importer.clear();
566
- return json(200, { ok: true, cleared: true });
567
- }
568
- if (!IMPORT_KINDS.includes(body.kind)) {
569
- return json(400, { error: `kind must be one of: ${IMPORT_KINDS.join(', ')}` });
570
- }
571
- if (typeof body.text !== 'string' || !body.text.trim()) {
572
- return json(400, { error: 'text required — the CSV file contents' });
573
- }
574
- const { values, invalid } = normalizeImport(body.kind, body.text);
575
- const r = a.importer.stage(body.kind, values);
576
- await a.store.flush();
577
- return json(200, {
578
- ok: true, kind: body.kind, ...r,
579
- invalid: invalid.length,
580
- ...(invalid.length ? { invalidSample: invalid.slice(0, 5) } : {}),
581
- });
582
- }
583
-
584
- default:
585
- return json(404, { error: 'not available on a personal browser identity' });
586
- }
587
- }
588
- }