fedipod-server 0.29.0 → 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 (440) hide show
  1. package/README.md +11 -6
  2. package/dist/door.d.ts +4 -0
  3. package/dist/door.js +31 -0
  4. package/dist/fedipod.d.ts +16 -0
  5. package/dist/fedipod.js +13 -0
  6. package/dist/front.d.ts +3 -0
  7. package/dist/front.js +94 -0
  8. package/dist/handler.d.ts +36 -101
  9. package/dist/handler.js +85 -645
  10. package/dist/handler.jsonld +2 -30
  11. package/dist/identities.d.ts +62 -0
  12. package/dist/identities.js +236 -0
  13. package/dist/mounts.d.ts +43 -0
  14. package/dist/mounts.js +123 -0
  15. package/dist/opt-in.d.ts +46 -0
  16. package/dist/opt-in.js +176 -0
  17. package/package.json +10 -18
  18. package/lib/client/c2s.mjs +0 -636
  19. package/lib/client/localapi.mjs +0 -87
  20. package/lib/client/masto/accounts.mjs +0 -310
  21. package/lib/client/masto/body.mjs +0 -156
  22. package/lib/client/masto/index.mjs +0 -206
  23. package/lib/client/masto/instance.mjs +0 -112
  24. package/lib/client/masto/media.mjs +0 -79
  25. package/lib/client/masto/oauth.mjs +0 -656
  26. package/lib/client/masto/render.mjs +0 -518
  27. package/lib/client/masto/statuses.mjs +0 -372
  28. package/lib/client/masto/timelines.mjs +0 -438
  29. package/lib/client/oidc-auth.mjs +0 -62
  30. package/lib/client/streaming.mjs +0 -154
  31. package/lib/client/webpush.mjs +0 -98
  32. package/lib/connections/acctfeed.mjs +0 -257
  33. package/lib/connections/atproto.mjs +0 -309
  34. package/lib/connections/bskyfeed.mjs +0 -243
  35. package/lib/connections/bskygroup.mjs +0 -162
  36. package/lib/connections/fediacct.mjs +0 -253
  37. package/lib/connections/import.mjs +0 -357
  38. package/lib/connections/tagfeed.mjs +0 -212
  39. package/lib/connections/vault.mjs +0 -114
  40. package/lib/core/as2.mjs +0 -206
  41. package/lib/core/contexts/activitystreams.json +0 -379
  42. package/lib/core/contexts/anno.json +0 -126
  43. package/lib/core/contexts/did-v1.json +0 -57
  44. package/lib/core/contexts/fep-5711.json +0 -36
  45. package/lib/core/contexts/gotosocial.json +0 -86
  46. package/lib/core/contexts/identity-v1.json +0 -152
  47. package/lib/core/contexts/index.mjs +0 -63
  48. package/lib/core/contexts/join-lemmy.json +0 -33
  49. package/lib/core/contexts/joinmastodon.json +0 -28
  50. package/lib/core/contexts/litepub-0.1.json +0 -60
  51. package/lib/core/contexts/map.json +0 -19
  52. package/lib/core/contexts/miscellany.json +0 -19
  53. package/lib/core/contexts/quotes.json +0 -18
  54. package/lib/core/contexts/schemaorg.json +0 -8845
  55. package/lib/core/contexts/security-data-integrity-v1.json +0 -78
  56. package/lib/core/contexts/security-data-integrity-v2.json +0 -81
  57. package/lib/core/contexts/security-multikey-v1.json +0 -35
  58. package/lib/core/contexts/security-v1.json +0 -74
  59. package/lib/core/contexts/webfinger.json +0 -10
  60. package/lib/core/deliver.mjs +0 -387
  61. package/lib/core/graphview.mjs +0 -327
  62. package/lib/core/intake/activities.mjs +0 -649
  63. package/lib/core/intake/activity.mjs +0 -318
  64. package/lib/core/intake/channel.mjs +0 -144
  65. package/lib/core/intake/group.mjs +0 -258
  66. package/lib/core/intake/index.mjs +0 -732
  67. package/lib/core/intake/notes.mjs +0 -367
  68. package/lib/core/intake/verify.mjs +0 -142
  69. package/lib/core/keys.mjs +0 -153
  70. package/lib/core/lease.mjs +0 -206
  71. package/lib/core/place.mjs +0 -82
  72. package/lib/core/polls.mjs +0 -105
  73. package/lib/core/profile-page.mjs +0 -104
  74. package/lib/core/proof.mjs +0 -79
  75. package/lib/core/publisher/collections.mjs +0 -272
  76. package/lib/core/publisher/index.mjs +0 -522
  77. package/lib/core/publisher/notes.mjs +0 -519
  78. package/lib/core/publisher/own.mjs +0 -143
  79. package/lib/core/publisher/questions.mjs +0 -241
  80. package/lib/core/publisher/restore.mjs +0 -224
  81. package/lib/core/shapes/activitystreams.ttl +0 -157
  82. package/lib/core/shapes/index.mjs +0 -132
  83. package/lib/core/shapes/shapes-text.mjs +0 -13
  84. package/lib/core/social.mjs +0 -561
  85. package/lib/core/storage.mjs +0 -176
  86. package/lib/core/store.mjs +0 -578
  87. package/lib/core/wire-quotes.mjs +0 -71
  88. package/lib/core/wire.mjs +0 -925
  89. package/lib/device/account.mjs +0 -87
  90. package/lib/device/admin/index.mjs +0 -13
  91. package/lib/device/admin/origins.mjs +0 -35
  92. package/lib/device/admin/routes/connections.mjs +0 -144
  93. package/lib/device/admin/routes/gateway.mjs +0 -199
  94. package/lib/device/admin/routes/lifecycle.mjs +0 -200
  95. package/lib/device/admin/routes/owner.mjs +0 -322
  96. package/lib/device/admin/routes/setup.mjs +0 -428
  97. package/lib/device/admin/routes/social.mjs +0 -188
  98. package/lib/device/admin/server.mjs +0 -95
  99. package/lib/device/admin/static.mjs +0 -247
  100. package/lib/device/admin/surface.mjs +0 -315
  101. package/lib/device/certs.mjs +0 -281
  102. package/lib/device/cli/commands/account.mjs +0 -586
  103. package/lib/device/cli/commands/run.mjs +0 -278
  104. package/lib/device/cli/commands/service.mjs +0 -221
  105. package/lib/device/cli/commands/setup.mjs +0 -489
  106. package/lib/device/cli/commands/state.mjs +0 -561
  107. package/lib/device/cli/context.mjs +0 -288
  108. package/lib/device/export-collections.mjs +0 -161
  109. package/lib/device/gateway-move.mjs +0 -58
  110. package/lib/device/home.mjs +0 -161
  111. package/lib/device/migrate.mjs +0 -115
  112. package/lib/device/ports.mjs +0 -27
  113. package/lib/device/remote.mjs +0 -54
  114. package/lib/device/setup.mjs +0 -496
  115. package/lib/device/update.mjs +0 -96
  116. package/lib/gateway/caches.mjs +0 -36
  117. package/lib/gateway/directory.mjs +0 -90
  118. package/lib/gateway/front-core.mjs +0 -966
  119. package/lib/gateway/gateway-core.mjs +0 -244
  120. package/lib/gateway/headers.mjs +0 -49
  121. package/lib/gateway/httpsig.mjs +0 -159
  122. package/lib/gateway/notices.mjs +0 -84
  123. package/lib/gateway/quiet.mjs +0 -189
  124. package/lib/gateway/relay-extras.mjs +0 -89
  125. package/lib/gateway/token-claims.mjs +0 -16
  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/location.mjs +0 -52
  138. package/lib/pod/media.mjs +0 -27
  139. package/lib/pod/notes.mjs +0 -78
  140. package/lib/pod/notifications.mjs +0 -89
  141. package/lib/pod/outbox.mjs +0 -18
  142. package/lib/pod/package.json +0 -19
  143. package/lib/pod/policy.mjs +0 -58
  144. package/lib/pod/private.mjs +0 -19
  145. package/lib/pod/root.mjs +0 -104
  146. package/lib/pod/state.mjs +0 -40
  147. package/lib/pod/transport.mjs +0 -674
  148. package/lib/pod/type-index.mjs +0 -101
  149. package/lib/pod/urls.mjs +0 -91
  150. package/lib/server/embed.mjs +0 -424
  151. package/lib/session/README.md +0 -137
  152. package/lib/session/demo.html +0 -94
  153. package/lib/session/fedi-account.mjs +0 -417
  154. package/lib/session/fedi-login.mjs +0 -123
  155. package/lib/session/oidc-session.mjs +0 -233
  156. package/lib/session/package.json +0 -49
  157. package/lib/shared/guard.mjs +0 -213
  158. package/lib/shared/links.mjs +0 -9
  159. package/lib/shared/safefetch.mjs +0 -235
  160. package/lib/shared/ua.mjs +0 -29
  161. package/phanpy/dist/404.html +0 -40
  162. package/phanpy/dist/_headers +0 -2
  163. package/phanpy/dist/apple-touch-icon.png +0 -0
  164. package/phanpy/dist/assets/ICONS-DIPJeU0C.js +0 -2
  165. package/phanpy/dist/assets/ICONS-DIPJeU0C.js.map +0 -1
  166. package/phanpy/dist/assets/ScheduledAtField-D-Abfo4J.js +0 -183
  167. package/phanpy/dist/assets/ScheduledAtField-D-Abfo4J.js.map +0 -1
  168. package/phanpy/dist/assets/Temml-CCjoacWt.woff2 +0 -0
  169. package/phanpy/dist/assets/__vite-browser-external-C7iut881.js +0 -2
  170. package/phanpy/dist/assets/__vite-browser-external-C7iut881.js.map +0 -1
  171. package/phanpy/dist/assets/boosts-carousel-BiOaNdMT.jpg +0 -0
  172. package/phanpy/dist/assets/catch-up-O5q5C75Z.png +0 -0
  173. package/phanpy/dist/assets/chunk-aKtaBQYM.js +0 -1
  174. package/phanpy/dist/assets/compose-CFEx7wb4.js +0 -2
  175. package/phanpy/dist/assets/compose-CFEx7wb4.js.map +0 -1
  176. package/phanpy/dist/assets/compose-ZJN5xSb4.js +0 -35
  177. package/phanpy/dist/assets/compose-ZJN5xSb4.js.map +0 -1
  178. package/phanpy/dist/assets/debug.module-YFQgwnIr.js +0 -52
  179. package/phanpy/dist/assets/debug.module-YFQgwnIr.js.map +0 -1
  180. package/phanpy/dist/assets/dom-p1YcJ5Rw.js +0 -2
  181. package/phanpy/dist/assets/dom-p1YcJ5Rw.js.map +0 -1
  182. package/phanpy/dist/assets/home-mobile-dark@2x-Dc-J8-SM.png +0 -0
  183. package/phanpy/dist/assets/home-mobile-light@2x-Bd3fod8m.png +0 -0
  184. package/phanpy/dist/assets/home-tablet-dark@2x-BgcNI8le.png +0 -0
  185. package/phanpy/dist/assets/home-tablet-light@2x-BW2RV9T6.png +0 -0
  186. package/phanpy/dist/assets/icons/add-circle-line-WD0mwORD.js +0 -1
  187. package/phanpy/dist/assets/icons/alert-line-CpFUMf9s.js +0 -1
  188. package/phanpy/dist/assets/icons/android-2-line-Bzxnrd3S.js +0 -1
  189. package/phanpy/dist/assets/icons/announcement-line-5nwaJFJm.js +0 -1
  190. package/phanpy/dist/assets/icons/arrow-down-circle-line-a7Id9-Iv.js +0 -1
  191. package/phanpy/dist/assets/icons/arrow-down-line-B8uzXzq6.js +0 -1
  192. package/phanpy/dist/assets/icons/arrow-left-line-CGrGT9C-.js +0 -1
  193. package/phanpy/dist/assets/icons/arrow-right-line-CE-HERfX.js +0 -1
  194. package/phanpy/dist/assets/icons/arrow-to-up-line-CvdQjmcy.js +0 -1
  195. package/phanpy/dist/assets/icons/arrow-up-circle-line-DEparSNF.js +0 -1
  196. package/phanpy/dist/assets/icons/arrow-up-line-C7yBeugc.js +0 -1
  197. package/phanpy/dist/assets/icons/arrows-right-line-BtbXmJGP.js +0 -1
  198. package/phanpy/dist/assets/icons/at-line-CF66Bznk.js +0 -1
  199. package/phanpy/dist/assets/icons/attachment-line-BY8It-l9.js +0 -1
  200. package/phanpy/dist/assets/icons/blockquote-line-BS6pU_hm.js +0 -1
  201. package/phanpy/dist/assets/icons/board-line-C0UWXyA6.js +0 -1
  202. package/phanpy/dist/assets/icons/bookmark-line-1CtX5JLF.js +0 -1
  203. package/phanpy/dist/assets/icons/building-5-line-Dd59MzbO.js +0 -1
  204. package/phanpy/dist/assets/icons/bus-2-line-6_dvA0GG.js +0 -1
  205. package/phanpy/dist/assets/icons/calendar-day-line-Crpl_fmu.js +0 -1
  206. package/phanpy/dist/assets/icons/calendar-month-line-vommts2C.js +0 -1
  207. package/phanpy/dist/assets/icons/calendar-time-add-line-BU_DTlQk.js +0 -1
  208. package/phanpy/dist/assets/icons/camera-line-Cv69MEei.js +0 -1
  209. package/phanpy/dist/assets/icons/celebrate-line-B8DvIoi0.js +0 -1
  210. package/phanpy/dist/assets/icons/chart-bar-line-FK-QOmtF.js +0 -1
  211. package/phanpy/dist/assets/icons/chart-line-line-BGcJAFWw.js +0 -1
  212. package/phanpy/dist/assets/icons/chat-3-line-YFK2-hwz.js +0 -1
  213. package/phanpy/dist/assets/icons/check-circle-line-DsAxFr5-.js +0 -1
  214. package/phanpy/dist/assets/icons/clipboard-line-BBp364gJ.js +0 -1
  215. package/phanpy/dist/assets/icons/close-circle-line-CICdiuGz.js +0 -1
  216. package/phanpy/dist/assets/icons/close-line-DYrFWcjv.js +0 -1
  217. package/phanpy/dist/assets/icons/cloud-line-tiQAbah8.js +0 -1
  218. package/phanpy/dist/assets/icons/code-line-fAl8iiV3.js +0 -1
  219. package/phanpy/dist/assets/icons/comment-2-line-CR3g7WeB.js +0 -1
  220. package/phanpy/dist/assets/icons/copy-2-line-CLW69yF5.js +0 -1
  221. package/phanpy/dist/assets/icons/delete-2-line-B8xmbr4n.js +0 -1
  222. package/phanpy/dist/assets/icons/document-3-line-DBGNhAg2.js +0 -1
  223. package/phanpy/dist/assets/icons/document-line-C7mb9sb7.js +0 -1
  224. package/phanpy/dist/assets/icons/down-line-BGqSSThd.js +0 -1
  225. package/phanpy/dist/assets/icons/edit-4-line-DcgGaEhx.js +0 -1
  226. package/phanpy/dist/assets/icons/emoji-2-line-D8YgnM-y.js +0 -1
  227. package/phanpy/dist/assets/icons/exit-line-Dc07BS-x.js +0 -1
  228. package/phanpy/dist/assets/icons/external-link-line-BvpSkmjO.js +0 -1
  229. package/phanpy/dist/assets/icons/eye-2-line-CiWXx-f_.js +0 -1
  230. package/phanpy/dist/assets/icons/eye-close-line-BgTaSJ4P.js +0 -1
  231. package/phanpy/dist/assets/icons/filter-2-line-B_mR2usy.js +0 -1
  232. package/phanpy/dist/assets/icons/filter-line-C_nm-3cU.js +0 -1
  233. package/phanpy/dist/assets/icons/flag-1-line-C2HB05I0.js +0 -1
  234. package/phanpy/dist/assets/icons/forbid-circle-line-D3LTT9yd.js +0 -1
  235. package/phanpy/dist/assets/icons/formula-line-DLrt2d6j.js +0 -1
  236. package/phanpy/dist/assets/icons/grid-line-CgUmEikf.js +0 -1
  237. package/phanpy/dist/assets/icons/group-line-C5fnX0wU.js +0 -1
  238. package/phanpy/dist/assets/icons/hand-finger-2-line-BGgxbuL9.js +0 -1
  239. package/phanpy/dist/assets/icons/hashtag-line-CyTEEXBb.js +0 -1
  240. package/phanpy/dist/assets/icons/heart-crack-line-BZddbO2C.js +0 -1
  241. package/phanpy/dist/assets/icons/heart-line-CJ7K47J4.js +0 -1
  242. package/phanpy/dist/assets/icons/history-2-line-kWWvFzqv.js +0 -1
  243. package/phanpy/dist/assets/icons/history-line-bGQiwbTH.js +0 -1
  244. package/phanpy/dist/assets/icons/home-3-line-fRU1zeLG.js +0 -1
  245. package/phanpy/dist/assets/icons/information-line-iNk6-sOY.js +0 -1
  246. package/phanpy/dist/assets/icons/keyboard-line-DsCyuQNz.js +0 -1
  247. package/phanpy/dist/assets/icons/layout-4-line-CZkOX52Z.js +0 -1
  248. package/phanpy/dist/assets/icons/layout-5-line-B_znt5wO.js +0 -1
  249. package/phanpy/dist/assets/icons/left-line-eUw0tya4.js +0 -1
  250. package/phanpy/dist/assets/icons/lightning-line-DaKoDdix.js +0 -1
  251. package/phanpy/dist/assets/icons/link-2-line-DCqLmMA7.js +0 -1
  252. package/phanpy/dist/assets/icons/list-check-line-BAxosBh9.js +0 -1
  253. package/phanpy/dist/assets/icons/lock-line-CPoaXeUW.js +0 -1
  254. package/phanpy/dist/assets/icons/mail-line-B0P8aa1L.js +0 -1
  255. package/phanpy/dist/assets/icons/moon-line-CEqNAToP.js +0 -1
  256. package/phanpy/dist/assets/icons/more-1-fill-Dw2e5xab.js +0 -1
  257. package/phanpy/dist/assets/icons/more-3-line-BIiVjYTz.js +0 -1
  258. package/phanpy/dist/assets/icons/notification-line-BZZq2Gtu.js +0 -1
  259. package/phanpy/dist/assets/icons/pencil-line-CKh8-A1v.js +0 -1
  260. package/phanpy/dist/assets/icons/photo-album-line-Bnfg2u40.js +0 -1
  261. package/phanpy/dist/assets/icons/pin-line-DpTolyhs.js +0 -1
  262. package/phanpy/dist/assets/icons/play-fill-B0-G_8lX.js +0 -1
  263. package/phanpy/dist/assets/icons/qrcode-2-line-B6SPCaHn.js +0 -1
  264. package/phanpy/dist/assets/icons/quill-pen-line-B6LUrnCP.js +0 -1
  265. package/phanpy/dist/assets/icons/quote-left-fill-hQZ1cj_6.js +0 -1
  266. package/phanpy/dist/assets/icons/radar-line-CKyroTth.js +0 -1
  267. package/phanpy/dist/assets/icons/react-line-CoIKhhtK.js +0 -1
  268. package/phanpy/dist/assets/icons/refresh-2-line-D0mTF97S.js +0 -1
  269. package/phanpy/dist/assets/icons/right-line-B3KWJaqc.js +0 -1
  270. package/phanpy/dist/assets/icons/rocket-line-4KZl537D.js +0 -1
  271. package/phanpy/dist/assets/icons/round-fill-C69NTrZ0.js +0 -1
  272. package/phanpy/dist/assets/icons/round-line-B9k_1uo-.js +0 -1
  273. package/phanpy/dist/assets/icons/route-line-B28ReHcE.js +0 -1
  274. package/phanpy/dist/assets/icons/rows-4-line-CrBniagM.js +0 -1
  275. package/phanpy/dist/assets/icons/scan-line-DSf74YdP.js +0 -1
  276. package/phanpy/dist/assets/icons/search-2-line-B6geIU1z.js +0 -1
  277. package/phanpy/dist/assets/icons/settings-3-line-azJwK47w.js +0 -1
  278. package/phanpy/dist/assets/icons/settings-6-line-C_5zc5YR.js +0 -1
  279. package/phanpy/dist/assets/icons/share-2-line-D5MxWjrm.js +0 -1
  280. package/phanpy/dist/assets/icons/share-forward-line-Cp61fZzZ.js +0 -1
  281. package/phanpy/dist/assets/icons/sparkles-2-line-DYYyXTaW.js +0 -1
  282. package/phanpy/dist/assets/icons/sparkles-line-C2hY1s6I.js +0 -1
  283. package/phanpy/dist/assets/icons/time-line-BF3eet1v.js +0 -1
  284. package/phanpy/dist/assets/icons/transfer-4-line-oisfvTBw.js +0 -1
  285. package/phanpy/dist/assets/icons/translate-line-BFWol2Ce.js +0 -1
  286. package/phanpy/dist/assets/icons/unlock-line-X_x5vwv3.js +0 -1
  287. package/phanpy/dist/assets/icons/upload-3-line-DgzwUJeW.js +0 -1
  288. package/phanpy/dist/assets/icons/user-4-line-BVSynZCp.js +0 -1
  289. package/phanpy/dist/assets/icons/user-add-2-line-BlGS5Q8q.js +0 -1
  290. package/phanpy/dist/assets/icons/user-add-line-BbXdHG4z.js +0 -1
  291. package/phanpy/dist/assets/icons/user-edit-line-BFAHOV0X.js +0 -1
  292. package/phanpy/dist/assets/icons/user-follow-line-BXux0wSd.js +0 -1
  293. package/phanpy/dist/assets/icons/user-star-line-DdYlpHLU.js +0 -1
  294. package/phanpy/dist/assets/icons/user-warning-line-Cj43j0_E.js +0 -1
  295. package/phanpy/dist/assets/icons/user-x-line-DIDgNlPH.js +0 -1
  296. package/phanpy/dist/assets/icons/volume-line-Bs390KQt.js +0 -1
  297. package/phanpy/dist/assets/icons/volume-mute-line-DzlzIqFZ.js +0 -1
  298. package/phanpy/dist/assets/icons/walk-line-yNXfB-md.js +0 -1
  299. package/phanpy/dist/assets/icons/world-2-line-CenkGIwj.js +0 -1
  300. package/phanpy/dist/assets/icons/zoom-in-line-CaENUphf.js +0 -1
  301. package/phanpy/dist/assets/icons/zoom-out-line-BDNk0EWo.js +0 -1
  302. package/phanpy/dist/assets/instances-BNPptnn-.json +0 -333
  303. package/phanpy/dist/assets/locales/ar-SA-CfheRbCu.js +0 -1
  304. package/phanpy/dist/assets/locales/ca-ES-DHRW86Tz.js +0 -1
  305. package/phanpy/dist/assets/locales/cs-CZ-CKoqEuA1.js +0 -1
  306. package/phanpy/dist/assets/locales/de-DE-ByRPMRFy.js +0 -1
  307. package/phanpy/dist/assets/locales/eo-UY-CPC7wLCS.js +0 -1
  308. package/phanpy/dist/assets/locales/es-ES-dJa0Fd9t.js +0 -1
  309. package/phanpy/dist/assets/locales/eu-ES-C3djlPR2.js +0 -1
  310. package/phanpy/dist/assets/locales/fa-IR-DTT490Ck.js +0 -1
  311. package/phanpy/dist/assets/locales/fi-FI-B23MCPuz.js +0 -1
  312. package/phanpy/dist/assets/locales/fr-FR-BcaQsO5C.js +0 -1
  313. package/phanpy/dist/assets/locales/gl-ES-BR-CVKBN.js +0 -1
  314. package/phanpy/dist/assets/locales/he-IL-CfheRbCu.js +0 -1
  315. package/phanpy/dist/assets/locales/hu-HU-CfheRbCu.js +0 -1
  316. package/phanpy/dist/assets/locales/it-IT-C1136UFV.js +0 -1
  317. package/phanpy/dist/assets/locales/ja-JP-CRZmRAhD.js +0 -1
  318. package/phanpy/dist/assets/locales/kab-Cz8xH_TT.js +0 -1
  319. package/phanpy/dist/assets/locales/ko-KR-CG9UiY4o.js +0 -1
  320. package/phanpy/dist/assets/locales/lt-LT-CJcI6sHZ.js +0 -1
  321. package/phanpy/dist/assets/locales/nb-NO-CfheRbCu.js +0 -1
  322. package/phanpy/dist/assets/locales/nl-NL-DxN4Yc1t.js +0 -1
  323. package/phanpy/dist/assets/locales/oc-FR-CfheRbCu.js +0 -1
  324. package/phanpy/dist/assets/locales/pl-PL-DkwGXf6P.js +0 -1
  325. package/phanpy/dist/assets/locales/pseudo-LOCALE-CQ4XxkQ0.js +0 -1
  326. package/phanpy/dist/assets/locales/pt-BR-nGL0HdFS.js +0 -1
  327. package/phanpy/dist/assets/locales/pt-PT-Cp17aj2z.js +0 -1
  328. package/phanpy/dist/assets/locales/ru-RU-0Y8cIoaK.js +0 -1
  329. package/phanpy/dist/assets/locales/th-TH-CfheRbCu.js +0 -1
  330. package/phanpy/dist/assets/locales/tok-CfheRbCu.js +0 -1
  331. package/phanpy/dist/assets/locales/tr-TR-CfheRbCu.js +0 -1
  332. package/phanpy/dist/assets/locales/uk-UA-C5E3dFBf.js +0 -1
  333. package/phanpy/dist/assets/locales/zh-CN-BHJi7_et.js +0 -1
  334. package/phanpy/dist/assets/locales/zh-TW-CfheRbCu.js +0 -1
  335. package/phanpy/dist/assets/main-BdqNbG-a.js +0 -26
  336. package/phanpy/dist/assets/main-BdqNbG-a.js.map +0 -1
  337. package/phanpy/dist/assets/mock-home-DGhR__He.js +0 -2
  338. package/phanpy/dist/assets/mock-home-DGhR__He.js.map +0 -1
  339. package/phanpy/dist/assets/multi-column-ROck0NVt.jpg +0 -0
  340. package/phanpy/dist/assets/multi-hashtag-timeline-DH5INVHi.jpg +0 -0
  341. package/phanpy/dist/assets/nested-comments-thread-hv59kZLc.jpg +0 -0
  342. package/phanpy/dist/assets/polyfill-force-H8sUkLGp.js +0 -2
  343. package/phanpy/dist/assets/polyfill-force-H8sUkLGp.js.map +0 -1
  344. package/phanpy/dist/assets/pwa-viewport-ogKbF51K.js +0 -20
  345. package/phanpy/dist/assets/pwa-viewport-ogKbF51K.js.map +0 -1
  346. package/phanpy/dist/assets/style-B1iLPTPR.css +0 -2
  347. package/phanpy/dist/assets/temml-DuyIQMei.js +0 -97
  348. package/phanpy/dist/assets/temml-DuyIQMei.js.map +0 -1
  349. package/phanpy/dist/assets/tinyld.light.node-DtbBhLYC.js +0 -2
  350. package/phanpy/dist/assets/tinyld.light.node-DtbBhLYC.js.map +0 -1
  351. package/phanpy/dist/assets/year-in-posts-B2lywqdx.png +0 -0
  352. package/phanpy/dist/assets/year-in-posts-DJv_star.js +0 -3
  353. package/phanpy/dist/assets/year-in-posts-DJv_star.js.map +0 -1
  354. package/phanpy/dist/compose/index.html +0 -20
  355. package/phanpy/dist/favicon.ico +0 -0
  356. package/phanpy/dist/index.html +0 -73
  357. package/phanpy/dist/logo-192.png +0 -0
  358. package/phanpy/dist/logo-512.png +0 -0
  359. package/phanpy/dist/logo-badge-72.png +0 -0
  360. package/phanpy/dist/logo-maskable-512.png +0 -0
  361. package/phanpy/dist/logo-monochrome-512.png +0 -0
  362. package/phanpy/dist/logo-monochrome-maskable-512.png +0 -0
  363. package/phanpy/dist/manifest.webmanifest +0 -1
  364. package/phanpy/dist/og-image-2.jpg +0 -0
  365. package/phanpy/dist/og-image.png +0 -0
  366. package/phanpy/dist/robots.txt +0 -2
  367. package/phanpy/dist/sw.js +0 -2
  368. package/phanpy/dist/sw.js.map +0 -1
  369. package/phanpy/dist/version.json +0 -1
  370. package/run-agent.mjs +0 -899
  371. package/vendor/gate.cjs +0 -138
  372. package/vendor/idp-grant.cjs +0 -394
  373. package/vendor/jfetch.cjs +0 -16
  374. package/web/admin/actors.js +0 -147
  375. package/web/admin/bar.css +0 -126
  376. package/web/admin/bar.js +0 -99
  377. package/web/admin/client/client.js +0 -66
  378. package/web/admin/client/index.html +0 -69
  379. package/web/admin/client-pick.js +0 -79
  380. package/web/admin/common.js +0 -23
  381. package/web/admin/connections.js +0 -112
  382. package/web/admin/gateway.js +0 -133
  383. package/web/admin/group.js +0 -258
  384. package/web/admin/index.html +0 -612
  385. package/web/admin/notices-bar.js +0 -90
  386. package/web/admin/oauth-signin.mjs +0 -37
  387. package/web/admin/record.js +0 -393
  388. package/web/admin/setup/index.html +0 -257
  389. package/web/admin/setup/setup.js +0 -339
  390. package/web/admin/tokens.css +0 -68
  391. package/web/admin/upkeep.js +0 -177
  392. package/web/admin/window.css +0 -55
  393. package/web/admin/window.js +0 -136
  394. package/web/app/README.md +0 -81
  395. package/web/app/admin-facade.mjs +0 -588
  396. package/web/app/agent.mjs +0 -672
  397. package/web/app/atproto-browser.mjs +0 -86
  398. package/web/app/authorize_interaction.html +0 -56
  399. package/web/app/authorize_interaction.js +0 -69
  400. package/web/app/boot.mjs +0 -616
  401. package/web/app/deliver-relay.mjs +0 -129
  402. package/web/app/dist/boot.js +0 -34562
  403. package/web/app/dist/boot.js.map +0 -7
  404. package/web/app/dist/sw.js +0 -73456
  405. package/web/app/dist/sw.js.map +0 -7
  406. package/web/app/fediacct-browser.mjs +0 -146
  407. package/web/app/gateway-move.mjs +0 -60
  408. package/web/app/idb-kv.mjs +0 -35
  409. package/web/app/index.html +0 -258
  410. package/web/app/keys-browser.mjs +0 -71
  411. package/web/app/keystore.mjs +0 -82
  412. package/web/app/oidc-session.mjs +0 -6
  413. package/web/app/pod-auth.mjs +0 -248
  414. package/web/app/pod-remote.mjs +0 -84
  415. package/web/app/shims/_globals.mjs +0 -2
  416. package/web/app/shims/fedify-sig.mjs +0 -77
  417. package/web/app/shims/node-crypto.mjs +0 -113
  418. package/web/app/shims/node-fs.mjs +0 -17
  419. package/web/app/shims/node-path.mjs +0 -19
  420. package/web/app/shims/node-url.mjs +0 -7
  421. package/web/app/shims/prelude.js +0 -58
  422. package/web/app/shims/safefetch.mjs +0 -58
  423. package/web/app/shims/shapes-text.mjs +0 -8
  424. package/web/app/shims/web-push.mjs +0 -14
  425. package/web/app/signup.mjs +0 -357
  426. package/web/app/sw-src.mjs +0 -220
  427. package/web/app/update.js +0 -70
  428. package/web/front/admin.html +0 -55
  429. package/web/front/admin.js +0 -89
  430. package/web/front/install.sh +0 -69
  431. package/web/front/new-account.html +0 -35
  432. package/web/front/new-account.js +0 -13
  433. package/web/front/notices.html +0 -69
  434. package/web/front/notices.js +0 -107
  435. package/web/front/run.html +0 -64
  436. package/web/front/run.js +0 -221
  437. package/web/front/solid-oidc-client.js +0 -6
  438. package/web/icons/forum.svg +0 -11
  439. package/web/icons/gateway.svg +0 -14
  440. 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 suffixed, 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
- }