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