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
package/web/app/agent.mjs DELETED
@@ -1,663 +0,0 @@
1
- // agent.mjs — FediPod's agent, running in the browser.
2
- //
3
- // The Node agent (run-agent.mjs) wires the same pieces; this is that wiring with
4
- // the browser's edges: the pod is reached over the DPoP session (pod-remote),
5
- // the signing key comes from WebCrypto (keys-browser), and delivery goes through
6
- // the relay (deliver-relay). Everything between — wire, the store, the
7
- // publisher, intake, the Mastodon facade — is lib/, unchanged.
8
- import { kvGet, kvPut } from './idb-kv.mjs';
9
- import { apUrls } from '../../lib/core/wire.mjs';
10
- import * as containers from '../../lib/pod/containers.mjs';
11
- import * as podState from '../../lib/pod/state.mjs';
12
- import { PodStore } from '../../lib/core/store.mjs';
13
- import { HttpStorage } from '../../lib/core/storage.mjs';
14
- import { Publisher } from '../../lib/core/publisher/index.mjs';
15
- import { Intake } from '../../lib/core/intake/index.mjs';
16
- import { C2S } from '../../lib/client/c2s.mjs';
17
- import { Lease } from '../../lib/core/lease.mjs';
18
- import { MastoApi } from '../../lib/client/masto/index.mjs';
19
- import { TagFeed } from '../../lib/connections/tagfeed.mjs';
20
- import { makeDpopSession } from './pod-auth.mjs';
21
- import { BrowserRemotePod } from './pod-remote.mjs';
22
- import { importSigningKey, loadKeysFromPod, cacheOpenedKeys, podActorOf } from './keys-browser.mjs';
23
- import { generateKeys } from './keystore.mjs';
24
- import { RelayDeliverer, doorKeyOf } from './deliver-relay.mjs';
25
- import { AdminFacade } from './admin-facade.mjs';
26
- import { completeGatewayMove } from './gateway-move.mjs';
27
- import { BrowserAtproto } from './atproto-browser.mjs';
28
- import { BskyFeed } from '../../lib/connections/bskyfeed.mjs';
29
- import { BrowserFediAccounts } from './fediacct-browser.mjs';
30
- import { AcctFeed } from '../../lib/connections/acctfeed.mjs';
31
- import { followActor, unfollowActor, resolveHandle } from '../../lib/core/social.mjs';
32
- import { podBaseOfWebId } from '../../lib/pod/urls.mjs';
33
- import { ImportWorker } from '../../lib/connections/import.mjs';
34
-
35
- // The authorities this identity answers on: exactly one, this origin. The Node
36
- // agent gets this from lib/guard.mjs, which is not in the browser bundle and
37
- // would not fit if it were — its whole subject is loopback and named local
38
- // origins, neither of which means anything here.
39
- //
40
- // Two questions are asked of it. `has()` decides whether an OAuth redirect_uri
41
- // is somewhere this agent actually answers. `isLocalRequest()` decides whether
42
- // a caller is the owner rather than a stranger, and in a browser that question
43
- // has exactly one honest answer: whether the request came from this origin —
44
- // which the service worker has already established before anything reaches the
45
- // facade, and stamps on the request it builds (see sw-src.mjs).
46
- const originAuthorities = (host) => ({
47
- set: new Set([String(host || '').toLowerCase()]),
48
- has(authority) { return this.set.has(String(authority || '').toLowerCase()); },
49
- isLocalRequest(req) { return req?.sameOrigin === true; },
50
- isLocal(h) { return this.has(h); },
51
- wsAuthorities() { return []; }, // fetch-only: this build serves no socket
52
- });
53
-
54
- // Why an account did not open, in words for the person it happened to, and a
55
- // `code` the sign-in page turns into a title and the one button that leads
56
- // somewhere (boot.mjs). Every one of these used to be the same sentence —
57
- // "no account config on this pod — sign up first" — which named the wrong
58
- // cause, offered the wrong cure, and left the reader with nothing to press.
59
- //
60
- // Each message says three things and stops: what the pod did, what that means
61
- // for the account, what to do about it. The pod's own wording is carried only
62
- // where it adds something the status code does not.
63
- async function accountNotRead({ unread, podBase, state, webId, actorUrl = null, fetchImpl = globalThis.fetch }) {
64
- const host = (() => { try { return new URL(podBase).host; } catch { return podBase; } })();
65
- const of = (code, message) => { const e = new Error(message); e.code = code; return e; };
66
- if (!unread) {
67
- // A pod that holds the account's actor and no record is an account a
68
- // DeviceAgent runs: the record lives on the device, and only the device
69
- // can open it. Said so, rather than "there is no account".
70
- const actor = actorUrl
71
- ? await fetchImpl(actorUrl, { headers: { accept: 'application/activity+json' } }).then((r) => (r.ok ? r.json() : null)).catch(() => null)
72
- : null;
73
- if (actor?.preferredUsername) {
74
- const at = (() => { try { return `@${actor.preferredUsername}@${new URL(actor.id || actorUrl).host}`; } catch { return actor.preferredUsername; } })();
75
- return of('device-account', `${host} holds the account ${at}, but not its record: this account is run by a`
76
- + ` DeviceAgent, and its record lives on that device. Open it from that device's admin page, not here.`
77
- + ` The sign-in used here was ${webId}.`);
78
- }
79
- return of('no-account', `${host} answered, and there is no FediPod account in it.`
80
- + ` Sign in with the pod that holds your account, or make an account in this one —`
81
- + ` both start from the sign-in page. The sign-in used here was ${webId}.`);
82
- }
83
- const status = Number((/HTTP (\d{3})/u.exec(unread.message) || [])[1]) || 0;
84
- if (status === 401 || status === 403) {
85
- // The page renews a refused sign-in by itself, and takes the person to
86
- // their pod's login when the renewal is refused too (boot.mjs). By the
87
- // time anybody reads this, both have already been tried.
88
- return of('sign-in-refused', `${host} would not let this sign-in read your account — it answered`
89
- + ` "not allowed" (HTTP ${status}) — and renewing the sign-in did not change its mind.`
90
- + ` Your account and everything in it are untouched. Signing in at ${host} again is the`
91
- + ` one thing that helps.`);
92
- }
93
- if (status === 429) {
94
- return of('pod-busy', `${host} is telling this browser it has asked for too much too quickly`
95
- + ` (HTTP 429), so your account could not be read. Nothing is wrong with your account, and`
96
- + ` nothing here needs doing: this page is waiting the throttle out and will ask again by`
97
- + ` itself. While a pod is rate-limiting you, only time helps.`);
98
- }
99
- if (status >= 500) {
100
- return of('pod-error', `${host} broke while handing your account over (HTTP ${status}).`
101
- + ` That is the pod's own failure, not your account's. This page asks again by itself in`
102
- + ` a moment; if it keeps saying this, the pod is down and there is nothing to do here.`);
103
- }
104
- if (status === 404) {
105
- return of('no-account-here', `${host} says there is nothing at ${state} (HTTP 404).`
106
- + ` Either no FediPod account has ever been set up in this pod, or you signed in with a`
107
- + ` different pod than the one holding yours. Sign in with that one, or make an account`
108
- + ` in this one — both start from the sign-in page.`);
109
- }
110
- // No status at all: the request never got an answer. The pod's own words are
111
- // the only thing that says anything here, so they come along.
112
- return of('pod-unreachable', `This browser could not reach ${host} to read your account`
113
- + ` (${unread.message}). Your account is still there. This page tries again by itself;`
114
- + ` if it keeps saying this, check that you are online and that the pod is up.`);
115
- }
116
-
117
- export class BrowserAgent {
118
- constructor({ log = console.log } = {}) {
119
- this.log = log;
120
- this.viewer = false;
121
- this.fediaccts = null;
122
- this.atproto = null;
123
- this.intake = null;
124
- }
125
-
126
- configured() { return !!this.store?.getConfig(); }
127
-
128
- // Asked by every write / destructive path before it acts. If we already hold
129
- // the lease, proceed. If we are a viewer, the owner acting HERE outranks the
130
- // idle active device: claim the lease outright and become active.
131
- async requestTakeover() {
132
- if (!this.viewer) return true;
133
- if (!(await this.lease.takeover())) return false;
134
- clearTimeout(this._viewerTimer); this._viewerTimer = null;
135
- await this.goActive();
136
- this.log('took over from the other device (action here)');
137
- return true;
138
- }
139
-
140
- // One call to the gateway's owner API, proved with the pod session. What
141
- // comes back on 200 or 410 is the account's standing there, kept for the
142
- // manage page; anything else is logged and forgotten.
143
- async tellGateway(what, body) {
144
- if (!this.gatewayApi) return null;
145
- let res;
146
- try {
147
- res = await this.sessionFetch(`${this.gatewayApi}/${what}`, {
148
- method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body),
149
- });
150
- } catch (e) { this.log(`gateway ${what}: ${e.message}`); return null; }
151
- const json = await res.json().catch(() => ({}));
152
- const out = { status: res.status, ...json };
153
- if (res.status === 200 || res.status === 410) this.gatewayStanding = out;
154
- else this.log(`gateway ${what}: ${res.status} ${json.error || ''}`);
155
- return out;
156
- }
157
- openAtGateway() { return this.tellGateway('open', { handle: this.doorKey }); }
158
- pauseAtGateway(paused) { return this.tellGateway('pause', { handle: this.doorKey, paused: !!paused }); }
159
- closeAtGateway() { return this.tellGateway('close', { handle: this.doorKey, confirm: true }); }
160
- static OPEN_EVERY_MS = 60 * 60_000;
161
-
162
- // Become the active agent: renew the lease, then start what a viewer skips —
163
- // publish the face, drain the inbox, run the mirrors.
164
- async goActive() {
165
- this.viewer = false;
166
- // Reading here is being here: the gateway hears so once an hour.
167
- clearInterval(this._openTimer);
168
- this._openTimer = setInterval(() => { this.openAtGateway(); }, BrowserAgent.OPEN_EVERY_MS);
169
- this.lease.onLost = () => this.demote();
170
- this.lease.startRenewal();
171
- try {
172
- // Forced, not revalidated, when this device WATCHED first: the active
173
- // one was writing, our cache is however stale the last viewer poll left
174
- // it, and the store is write-through — so the first write from here
175
- // would push a whole document back over newer state. A device that
176
- // booted straight into acting read everything a moment ago; asking
177
- // again is one revalidation, not a second download of every document.
178
- await this.store.load({ force: !!this._watched }).catch((e) => this.log(`re-reading state: ${e.message}`));
179
- this._watched = false;
180
- // Own posts the outbox names and the timeline index lacks come back
181
- // here, before anything acts on the index.
182
- await this.publisher.healStatuses().catch((e) => this.log(`healing the timeline index: ${e.message}`));
183
- await this.publisher.publishProfilePage().catch((e) => this.log(`profile page: ${e.message}`));
184
- // And start delivering again, since demote() stopped it. startQueue() is
185
- // idempotent, so a goActive() that was already active costs nothing.
186
- this.deliverer?.startQueue?.();
187
- await this.publisher.publishProfile();
188
- await this.store.flush?.();
189
- // An address that just moved here from another gateway: the new
190
- // actor is published, so the old gateway and the followers can be told.
191
- await completeGatewayMove(this).catch((e) => this.log(`gateway move: ${e.message}`));
192
- await this.intake.start();
193
- this.startBsky();
194
- this.startAccts();
195
- this.tagfeed?.start();
196
- // Resumes a run that was staged on another device, or here before a
197
- // restart — the state lives on the pod, not in this process.
198
- this.importer?.start();
199
- } catch (e) { this.log(`going active: ${e.message}`); }
200
- }
201
-
202
- // Another device took the lease: stop acting (the drain also self-checks the
203
- // lease), keep serving the feed read-only, and poll to take it back if freed.
204
- demote() {
205
- if (this.viewer) return;
206
- this.viewer = true;
207
- this._watched = true;
208
- this.log('another device took over — read-only here');
209
- this.lease.stopRenewal();
210
- clearInterval(this._openTimer); this._openTimer = null;
211
- this.intake?.stop?.();
212
- // The delivery queue as well. Its timer starts in the Deliverer's
213
- // constructor and nothing here ever switched it off, so a demoted device
214
- // went on sending from a queue the active device is also sending from —
215
- // the same activity delivered twice, from two browsers, over one key.
216
- this.deliverer?.stop?.();
217
- this.importer?.stop?.();
218
- this.stopBsky(); this.stopAccts(); this.tagfeed?.stop?.();
219
- this.startViewerPoll();
220
- }
221
-
222
- // A viewer refreshes the feed from the pod (the active device updates it), and
223
- // promotes the moment the lease is free.
224
- // Five minutes with jitter, matching the Node agent (run-agent.mjs). Two
225
- // minutes flat was ~60 pod requests an hour from a device that is not acting,
226
- // and unjittered meant several idle devices on one pod knocking in lockstep.
227
- static VIEWER_POLL_MS = 5 * 60_000;
228
-
229
- startViewerPoll() {
230
- clearTimeout(this._viewerTimer);
231
- const every = () => BrowserAgent.VIEWER_POLL_MS * (0.85 + Math.random() * 0.3);
232
- const tick = () => {
233
- this._viewerTimer = setTimeout(async () => {
234
- try {
235
- if (!this.viewer) return;
236
- await this.store.load().catch(() => {}); // revalidating is enough while watching
237
- if (await this.lease.acquire()) { await this.goActive(); this.log('lease freed — now active'); return; }
238
- } catch (e) { this.log(`viewer poll: ${e.message}`); }
239
- if (this.viewer) tick();
240
- }, every());
241
- };
242
- tick();
243
- }
244
-
245
- /**
246
- * Boot from what sign-up (or sign-in) produced.
247
- * @param credential the pod credential { clientId, secret, tokenEndpoint, webId, remotePod, root }
248
- * @param keysRecord the plaintext signing keys { rsa:{privatePem,publicPem} }
249
- * @param config { remotePod, root, handle, name, issuer, gateway? }
250
- * @param opts.frontOrigin where the relay lives (for delivery)
251
- */
252
- /**
253
- * Boot the agent. Two ways in:
254
- * - { oidc }: a Solid-OIDC session from oidc-session.getSession() (the real,
255
- * redirect-login path). Config and keys are read from the pod, which the
256
- * session is authenticated to; a fresh sign-up writes them first.
257
- * - { credential, keysRecord, config }: a client-credential session with the
258
- * material in hand (the offline test path).
259
- */
260
- // This browser's name for itself, minted once and kept. Not an identity and
261
- // not a secret — it says only "the same browser as last time" to the lease.
262
- // If storage cannot be reached, a fresh one is honest: this browser cannot
263
- // prove it is the one that held the lease, so it should not claim to be.
264
- async deviceId() {
265
- try {
266
- const had = await kvGet('device-id');
267
- if (had) return had;
268
- const made = crypto.randomUUID();
269
- await kvPut('device-id', made);
270
- return made;
271
- } catch {
272
- return null;
273
- }
274
- }
275
-
276
- async boot({ oidc, credential, keysRecord, config, frontOrigin }) {
277
- let session; let webId; let remotePod;
278
- if (oidc) {
279
- session = { fetch: (u, i) => oidc.fetch(u, i) };
280
- webId = oidc.webId;
281
- remotePod = podBaseOfWebId(webId); // the pod, which on a suffix-based host is a path
282
- } else {
283
- const dpop = await makeDpopSession(credential);
284
- session = { fetch: (u, i) => dpop.fetch(u, i) };
285
- webId = credential.webId;
286
- remotePod = credential.remotePod;
287
- }
288
- this.webId = webId;
289
- // The pod session's own fetch, for the gateway APIs that take it as
290
- // proof of the pod (attach, move) — the transport below is for the pod.
291
- this.sessionFetch = session.fetch;
292
- const root = (config && config.root) || 'fedipod/';
293
- this.remote = new BrowserRemotePod(session, { webId, log: this.log });
294
- // Pod-native for now: the state store below is read with these, and only
295
- // the config it holds says whether this identity is fronted.
296
- this.urls = apUrls(remotePod, root);
297
-
298
- // State store, on the pod.
299
- // Through the transport, not the raw session: state writes are pod writes,
300
- // and going round it skipped the Retry-After cooldown, the retry ladder and
301
- // the deletion deny-list that every other write on this agent observes.
302
- // The Node agent has always passed remote.fetch here.
303
- const podFetch = (u, i) => this.remote.fetch(u, i);
304
- this.store = new PodStore({ storage: new HttpStorage(this.urls.state, podFetch), log: this.log });
305
- // A read that FAILED is not an account that is not there. load() already
306
- // tells those apart — a missing container, a refused token, a pod saying
307
- // "too many requests" each come back with their own reason — and throwing
308
- // the reason away turned every one of them into "sign up first", said to
309
- // somebody who had just signed in and whose account was sitting on the pod
310
- // unread. Keep it: below is the only place that can tell whether it
311
- // mattered, because a sign-up hands its config in and needs no read at all.
312
- let unread = null;
313
- await this.store.load().catch((e) => { unread = e; });
314
- // Config: handed in on sign-up, or read from the pod on a returning sign-in.
315
- const cfg = config || this.store.getConfig();
316
- if (!cfg) throw await accountNotRead({ unread, podBase: remotePod, state: this.urls.state, webId, actorUrl: this.urls.actor });
317
- // `root` is written INTO the config, not just used above. The Publisher
318
- // builds its own urls from `config.root` (publisher.mjs), and a config
319
- // without one falls to the Node default — so an account set up elsewhere
320
- // and signed into here would keep its state under `fedipod/` while every
321
- // document it published landed under a different root. One root, decided
322
- // once, carried by the config everything downstream reads.
323
- this.store.setConfig({ ...(this.store.getConfig() || {}), ...cfg, root });
324
- config = this.store.getConfig();
325
- // A fronted identity (config.gateway.frontActor) advertises its ids at the
326
- // gateway; the documents stay on the pod. Same three lines as the Node
327
- // agent (run-agent.mjs connect): the advertised urls, and the map that
328
- // turns an advertised url back into the pod one at the transport's single
329
- // write and read choke point. State and media stay pod-native either way.
330
- const publicBase = config.gateway?.frontActor
331
- ? config.gateway.frontActor.replace(/ap\/actor\/?$/, '') : null;
332
- this.urls = apUrls(remotePod, root, { publicBase });
333
- if (this.urls.toPod) this.remote.setUrlMap(this.urls.toPod);
334
- // Keys: handed in (offline), or the owner-only keys.json read from the pod.
335
- const keys = keysRecord ? await importSigningKey(keysRecord) : await loadKeysFromPod(this.remote, this.urls);
336
-
337
-
338
- // `passive`: no queue-drain timer until this device is the active one.
339
- // Whether it IS the active one is not known here — the lease is acquired
340
- // further down, after everything is constructed — and the timer starts in
341
- // the Deliverer's constructor, so a viewer used to boot delivering. Start
342
- // it in goActive() instead, which is the one place that means "act".
343
- this.deliverer = new RelayDeliverer({
344
- passive: true,
345
- store: this.store, rsaPrivate: keys.rsaPrivate, keyId: this.urls.actor + '#main-key',
346
- actorId: this.urls.actor, log: this.log,
347
- relayUrl: `${frontOrigin.replace(/\/$/, '')}/api/relay`,
348
- // The relay finds the account by the front's own key for it, which for a
349
- // mail-door account is the full address, not the bare handle.
350
- handle: doorKeyOf(config.gateway?.url) || config.handle, sessionFetch: session.fetch,
351
- onGone: () => this.publisher.publishCollections({ followers: true }),
352
- });
353
-
354
- // The gateway this account's mail comes through, and the account's own
355
- // standing there — paused, closed, how much arrived unread (front-core:
356
- // accounts that go quiet). The gateway is told on every sign-in and
357
- // hourly while this device is active: that is what keeps an account
358
- // somebody reads from counting as a quiet one. Only a closed address
359
- // stops the boot; a gateway that cannot be reached is no reason to
360
- // refuse a sign-in.
361
- // At the page's own origin, as the relay is: a page on the test alias
362
- // asked the real site instead and could not get past its preflight.
363
- this.gatewayApi = config.gateway?.url ? `${frontOrigin.replace(/\/$/, '')}/api` : null;
364
- this.doorKey = doorKeyOf(config.gateway?.url) || config.handle;
365
- this.gatewayStanding = null;
366
- const standing = await this.openAtGateway();
367
- if (standing?.status === 410) {
368
- const host = (() => { try { return new URL(this.gatewayApi).host; } catch { return 'the gateway'; } })();
369
- const address = this.doorKey.includes('@') ? `@${this.doorKey}` : `@${this.doorKey}@${host}`;
370
- const why = standing.closedBy === 'owner' ? 'you closed it' : 'nothing opened it for six months';
371
- const e = new Error(`${address} is closed at ${host}: ${why}, and a closed address does not come back.`
372
- + ` Everything on your pod is untouched. The sign-in used here was ${webId}.`);
373
- e.code = 'address-closed';
374
- throw e;
375
- }
376
-
377
- this.publisher = new Publisher({
378
- config: this.store.getConfig(), remote: this.remote, store: this.store,
379
- deliverer: this.deliverer, publicKeyPem: keys.rsaPublicPem, assertionKey: null, log: this.log,
380
- // Who a post names, resolved — the same lookup the DeviceAgent
381
- // gives its publisher. Without it no mention from the browser ever
382
- // resolved: a direct message went to nobody, a mention notified no one.
383
- resolveMention: (h) => resolveHandle(this, h),
384
- resolveActor: (u) => this.intake.fetchAP(u),
385
- });
386
-
387
- // The Bluesky connection, stamped to this actor. The same client the Node
388
- // agent uses; here the credential lives in the owner-only pod state (see
389
- // atproto-browser.mjs). The publisher cross-posts public notes through it
390
- // when one is connected and crossPost is on; BskyFeed mirrors Bluesky back.
391
- this.atproto = new BrowserAtproto({ store: this.store, actorId: this.urls.actor, log: this.log });
392
- this.publisher.atproto = this.atproto;
393
-
394
- // Fediverse accounts the owner holds on OTHER servers, mirrored into the
395
- // feed read here. Their full-access tokens live in this browser (IndexedDB),
396
- // never on the pod — see fediacct-browser.mjs. Records are loaded, and the
397
- // poll started, in the background provisioning below.
398
- this.fediaccts = new BrowserFediAccounts({ store: this.store, actorId: this.urls.actor, log: this.log });
399
-
400
- // The facade and the intake object exist synchronously, so the client can be
401
- // served the instant boot returns. The heavy pod I/O — provisioning the
402
- // owner-only containers, publishing the public face, and starting the inbox
403
- // drain — is a burst of ~20 writes that a pod behind an edge (Cloudflare on
404
- // solidcommunity.net) throttles. Reaching the feed must NOT wait on it, or a
405
- // throttled request hangs the whole sign-in. So it runs in the background,
406
- // where per-request retry rides out the throttling; `this.provisioning`
407
- // resolves when it is done (a test, or a later call, can await it).
408
- // Single-active-agent lease (lib/lease.mjs): the inbox drain is a
409
- // destructive read and the state store is write-through-cached, so exactly
410
- // one browser/device may ACT on a pod at a time — a later arrival runs
411
- // read-only until the owner acts on it and it takes over. Written with fresh
412
- // fetches, never the cached store. Passed into Intake so the drain checks it.
413
- this.lease = new Lease({ url: this.urls.state + 'lease.json', fetchImpl: podFetch, log: this.log,
414
- // Kept on this origin, so THIS browser is one holder however many times
415
- // its worker is killed and restarted. Without it every restart was a new
416
- // holder: the old lease still had minutes to run, so the browser found
417
- // its own account "active on another device" and asked to take it over.
418
- // Switching between the clients did it every time, being a navigation.
419
- id: await this.deviceId() });
420
- // The client-to-server dispatcher, here only for what the Gateway's
421
- // outbox door takes on the owner's behalf: the browser answers no
422
- // /ap/outbox of its own.
423
- this.c2s = new C2S({ agent: this, log: this.log });
424
- this.intake = new Intake({
425
- config: this.store.getConfig(), urls: this.urls, remote: this.remote,
426
- store: this.store, deliverer: this.deliverer, publisher: this.publisher, log: this.log, push: true, lease: this.lease,
427
- ownerPost: (a, o) => this.c2s.dispatch(a, o),
428
- });
429
- // The Mastodon facade the service worker serves.
430
- //
431
- // It used to be handed `allowed: null`, which reads as "no policy" — and
432
- // `redirectAllowed()` waves through EVERY redirect_uri when there is no
433
- // policy (lib/mastoapi.mjs:315). So any page could have a freshly minted
434
- // 90-day bearer delivered to an address of its own. One origin, ours, is
435
- // the whole of the policy here.
436
- // Three capabilities this build does not have, declared rather than
437
- // pretended: no streaming socket (a worker is fetch-only), no web push
438
- // (shims/web-push.mjs is a no-op), and no scheduling (nothing here runs
439
- // between now and the scheduled time to publish). Each is omitted from the
440
- // instance document, so the client hides the control instead of offering
441
- // one that quietly does nothing.
442
- this.masto = new MastoApi({
443
- agent: this, log: this.log, scheme: 'https',
444
- streaming: false, webPush: false, scheduling: false,
445
- allowed: originAuthorities(self.location.host),
446
- });
447
-
448
- // The owner's record/manage surface (the same web/admin page the Node agent
449
- // serves), answered by this agent over the worker. Personal only — the
450
- // group and multi-actor endpoints do not exist here. See admin-facade.mjs.
451
- this.admin = new AdminFacade({ agent: this, log: this.log });
452
-
453
- // The CSV follow/block/mute importer — the same worker the Node agent runs.
454
- // It needs only `agent.store` and social.mjs, both of which are in this
455
- // bundle, so the browser answering 501 was a gap rather than a limitation.
456
- // Started only by goActive(): it follows people over minutes, which is
457
- // exactly the kind of writing a viewer must not do.
458
- this.importer = new ImportWorker({ agent: this, log: this.log });
459
-
460
- // The topical (hashtag) feed, so a fresh account is not a blank wall. It
461
- // polls public tag timelines and mirrors NEW notes as view-only statuses —
462
- // nothing is written to the pod. A browser cannot fetch arbitrary fediverse
463
- // servers cross-origin, so every fetch it makes (the tag timeline and each
464
- // note's verification via intake.fetchAP) goes through the same relay the
465
- // rest of the agent's remote reads use. The user controls it from the
466
- // client's Followed Hashtags surface (see MastoApi's tag endpoints).
467
- const relayGet = (u, i = {}) => this.deliverer.signedFetch(u, { ...i, method: 'GET' });
468
- this.tagfeed = new TagFeed({ store: this.store, intake: this.intake, log: this.log, fetcher: relayGet });
469
- // Started only when this browser is the ACTIVE agent (goActive), so two
470
- // devices do not double-mirror the same hashtags into the pod state.
471
-
472
- this.provisioning = (async () => {
473
- // Owner-only containers first; idempotent, so a second device provisioning
474
- // them too is harmless. Each public document sets its own Read ACL inside
475
- // publishProfile. Mirrors run-agent bootstrap.
476
- await containers.provisionPrivate(this.remote, this.urls);
477
- // Connected-account records are reads — safe whether we act or view.
478
- await this.atproto.load(); // the Bluesky credential, if it is browser-stored
479
- await this.fediaccts.load(); // connected fediverse accounts, from both backends
480
- // The lease decides: this device ACTS on the pod, or reads it read-only.
481
- this.viewer = !(await this.lease.acquire());
482
- if (this.viewer) {
483
- this._watched = true;
484
- this.log(`read-only viewer: another device is active on @${config.handle}`);
485
- this.startViewerPoll(); // reload the feed, and promote if the lease frees
486
- return;
487
- }
488
- await this.goActive();
489
- this.log(`browser agent fully provisioned (active): @${config.handle}`);
490
- })().catch((e) => { this.log(`background provisioning: ${e.message}`); throw e; });
491
- // Never let an unhandled rejection escape when nobody awaits it.
492
- this.provisioning.catch(() => {});
493
-
494
- this.log(`browser agent up: @${config.handle} on ${remotePod}`);
495
- return this;
496
- }
497
-
498
- // What the record page and the bar read (GET /status). The same shape the
499
- // Node agent returns (run-agent.mjs), minus what a browser cannot have — no
500
- // process, no local checkout, no atproto yet, so those are null.
501
- status() {
502
- const cfg = this.store?.getConfig();
503
- const contacts = this.store?.getContacts() || { followers: [], following: [] };
504
- return {
505
- configured: this.configured(),
506
- mode: !this.configured() ? 'unconfigured' : this.viewer ? 'viewer' : 'active',
507
- kind: cfg?.kind || 'person',
508
- handle: cfg?.handle || null,
509
- actor: this.urls?.actor || null,
510
- followers: contacts.followers.length,
511
- following: contacts.following.length,
512
- queue: this.store?.getQueue().length || 0,
513
- deadLetters: this.store?.getDeadLetters().length || 0,
514
- blockedDomains: this.store?.getBlocklist().domains.length || 0,
515
- push: this.intake?.wsState || 'n/a',
516
- inbox: this.intake?.inboxStats || null,
517
- lastDrain: this.intake?.lastDrain || null,
518
- tagfeed: this.tagfeed
519
- ? { ...this.tagfeed.config(), lastSweep: this.tagfeed.lastSweep, lastAdded: this.tagfeed.lastAdded }
520
- : null,
521
- atproto: this.atproto?.status() || null,
522
- podRequests: this.remote?.stats?.() || null,
523
- update: null,
524
- inboxCooldownFor: 0,
525
- stateSkipped: this.store?.lastSkipped || [],
526
- };
527
- }
528
-
529
- // Rotate the signing key (POST /rotate-key). Mint a fresh keypair, replace the
530
- // pod's copy, swap it into the live publisher/deliverer, and republish the
531
- // actor so the new public key is on the wire. The old key stops signing the
532
- // moment this returns — same one-way change as the Node agent's rotateKey
533
- // (run-agent.mjs).
534
- async rotateKey() {
535
- const before = this.publisher.publicKeyPem;
536
- const rec = await generateKeys();
537
- rec.mintedFor = this.urls.actor; // one key, one actor (lib/keys.mjs)
538
- await podState.writeKeys(this.remote, this.urls, rec);
539
- const keys = await cacheOpenedKeys(podActorOf(this.urls), rec);
540
- this.publisher.publicKeyPem = keys.rsaPublicPem;
541
- this.deliverer.rsaPrivate = keys.rsaPrivate;
542
- await this.publisher.publishProfile();
543
- return { changed: before !== keys.rsaPublicPem, publicKeyPem: keys.rsaPublicPem };
544
- }
545
-
546
- // The Bluesky mirror poll — Bluesky notifications and replies into the pod —
547
- // running only while an account is connected. Reused across calls, not
548
- // replaced, so a reconnect never leaves an orphaned timer (as in run-agent).
549
- // A person's mirror only; a group's Bluesky presence is not built here.
550
- startBsky() {
551
- if (!this.atproto?.feedActive()) return; // connected AND not paused
552
- if (this.store.getConfig()?.quiescedAt) return;
553
- this.bskyfeed ||= new BskyFeed({ store: this.store, atproto: this.atproto, log: this.log });
554
- this.bskyfeed.start();
555
- }
556
-
557
- stopBsky() { this.bskyfeed?.stop(); }
558
-
559
- // Pausing/resuming or moving the Bluesky connection changes whether the mirror
560
- // should run — re-evaluate it.
561
- restartBsky() { this.stopBsky(); this.bskyfeed = null; this.startBsky(); }
562
-
563
- // The connected-fediverse-accounts mirror — their home timelines and
564
- // notifications polled into the feed read here — running only while at least
565
- // one is connected. Reused, not replaced, for the same orphaned-timer reason
566
- // as the others (run-agent.mjs).
567
- startAccts() {
568
- if (!this.fediaccts?.connected()) return;
569
- if (this.store.getConfig()?.quiescedAt) return;
570
- this.acctfeed ||= new AcctFeed({ store: this.store, accounts: this.fediaccts, log: this.log });
571
- this.acctfeed.start();
572
- }
573
-
574
- stopAccts() { this.acctfeed?.stop(); }
575
-
576
- // Connecting or disconnecting one changes what there is to poll.
577
- restartAccts() { this.stopAccts(); this.acctfeed = null; this.startAccts(); }
578
-
579
- // The follow graph, written down before it is torn down.
580
- //
581
- // Both parking and moving away unfollow everyone, and the record page offers
582
- // "active" again after either — so both have to leave something to come back
583
- // from. moveTo used to report `snapshot: following.length` without writing
584
- // one, so a browser account set back to active re-followed nobody and said so
585
- // only in a count of zero. Same file, same name, same shape as the Node
586
- // agent's `_snapshotFollowing` (run-agent.mjs).
587
- async _snapshotFollowing() {
588
- const following = this.store.getContacts().following;
589
- this.store.write('parked.json', {
590
- parkedAt: new Date().toISOString(),
591
- following: following.map(f => ({ actor: f.actor, handle: f.handle || null })),
592
- });
593
- await this.store.flush();
594
- return following;
595
- }
596
-
597
- // Keep the handle, take no more mail.
598
- //
599
- // Unfollowing is what actually stops the volume — every account you follow
600
- // pushes its posts into your inbox — and closing the inbox handles what no
601
- // follow graph can gate: stranger mentions, new follow requests, spam.
602
- //
603
- // This matters more here than it does on Node. A Node agent runs as a service
604
- // and keeps draining; close the tab on a browser agent and nothing drains,
605
- // while the gateway goes on delivering into the pod inbox regardless (it
606
- // writes straight to the pod — see lib/gateway-core.mjs). An unattended
607
- // browser account is a pod quietly filling up with nobody collecting, and
608
- // this is the only control that stops it at the source.
609
- async quiesce() {
610
- const following = this.store.getContacts().following.map(f => f.actor).filter(Boolean);
611
- let unfollowed = 0;
612
- for (const actor of following) {
613
- try { await unfollowActor(this, actor); unfollowed++; }
614
- catch (e) { this.log(`unfollow ${actor} failed: ${e.message}`); }
615
- }
616
- const quiescedAt = await this.publisher.closeInbox();
617
- this.log(`quiesced: unfollowed ${unfollowed}/${following.length}, inbox closed`);
618
- return { unfollowed, following: following.length, quiescedAt };
619
- }
620
-
621
- // Park (POST /park). The snapshot is taken FIRST: unfollowing is what stops
622
- // the traffic, but it also destroys the only record of who was being
623
- // followed, and "until I want this back" needs that record.
624
- async park() {
625
- const following = await this._snapshotFollowing();
626
- const r = await this.quiesce();
627
- this.log(`parked: ${r.unfollowed} unfollow(s) recorded for revival, inbox closed`);
628
- return { ...r, snapshot: following.length };
629
- }
630
-
631
- // Undo a park (POST /revive). Re-open the inbox, then re-follow everyone in
632
- // the snapshot. Each Follow needs the far end to Accept, so this is a request
633
- // rather than a restoration — some will not come back, which is the nature of
634
- // the thing, and why the result counts both numbers.
635
- async revive() {
636
- const parked = this.store.read('parked.json', null);
637
- await this.publisher.openInbox();
638
- let refollowed = 0;
639
- for (const f of parked?.following || []) {
640
- try { await followActor(this, f.actor); refollowed++; }
641
- catch (e) { this.log(`re-follow ${f.actor} failed: ${e.message}`); }
642
- }
643
- if (parked) this.store.remove('parked.json').catch(() => {});
644
- await this.store.flush();
645
- this.log(`revived: inbox open, ${refollowed}/${parked?.following?.length || 0} follow(s) re-sent`);
646
- return { refollowed, of: parked?.following?.length || 0, parkedAt: parked?.parkedAt || null };
647
- }
648
-
649
- // Hand the identity to another account (POST /move). The federated act is the
650
- // Move to every follower's server; then this account quiesces — unfollows
651
- // everyone and closes its inbox — since it is being left behind. Same as the
652
- // Node agent's moveTo + quiesce (run-agent.mjs). `target` is an actor URL,
653
- // already resolved by the caller.
654
- async moveTo(target) {
655
- const moved = await this.publisher.publishMove(target);
656
- const following = await this._snapshotFollowing();
657
- const r = await this.quiesce();
658
- this.log(`moved to ${target}: unfollowed ${r.unfollowed}/${r.following}, inbox closed`);
659
- return { ...moved, ...r, snapshot: following.length };
660
- }
661
- }
662
-
663
- export { accountNotRead };