fedipod-server 0.29.0 → 0.31.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 (444) hide show
  1. package/README.md +16 -6
  2. package/dist/claims.d.ts +2 -0
  3. package/dist/claims.js +13 -4
  4. package/dist/door.d.ts +4 -0
  5. package/dist/door.js +31 -0
  6. package/dist/fedipod.d.ts +15 -0
  7. package/dist/fedipod.js +13 -0
  8. package/dist/front.d.ts +3 -0
  9. package/dist/front.js +92 -0
  10. package/dist/handler.d.ts +36 -101
  11. package/dist/handler.js +93 -644
  12. package/dist/handler.jsonld +2 -30
  13. package/dist/identities.d.ts +71 -0
  14. package/dist/identities.js +236 -0
  15. package/dist/mounts.d.ts +43 -0
  16. package/dist/mounts.js +123 -0
  17. package/dist/opt-in.d.ts +46 -0
  18. package/dist/opt-in.js +176 -0
  19. package/dist/private-read.d.ts +4 -0
  20. package/dist/private-read.js +34 -0
  21. package/package.json +12 -20
  22. package/lib/client/c2s.mjs +0 -636
  23. package/lib/client/localapi.mjs +0 -87
  24. package/lib/client/masto/accounts.mjs +0 -310
  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 -387
  65. package/lib/core/graphview.mjs +0 -327
  66. package/lib/core/intake/activities.mjs +0 -649
  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 -258
  70. package/lib/core/intake/index.mjs +0 -732
  71. package/lib/core/intake/notes.mjs +0 -367
  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/place.mjs +0 -82
  76. package/lib/core/polls.mjs +0 -105
  77. package/lib/core/profile-page.mjs +0 -104
  78. package/lib/core/proof.mjs +0 -79
  79. package/lib/core/publisher/collections.mjs +0 -272
  80. package/lib/core/publisher/index.mjs +0 -522
  81. package/lib/core/publisher/notes.mjs +0 -519
  82. package/lib/core/publisher/own.mjs +0 -143
  83. package/lib/core/publisher/questions.mjs +0 -241
  84. package/lib/core/publisher/restore.mjs +0 -224
  85. package/lib/core/shapes/activitystreams.ttl +0 -157
  86. package/lib/core/shapes/index.mjs +0 -132
  87. package/lib/core/shapes/shapes-text.mjs +0 -13
  88. package/lib/core/social.mjs +0 -561
  89. package/lib/core/storage.mjs +0 -176
  90. package/lib/core/store.mjs +0 -578
  91. package/lib/core/wire-quotes.mjs +0 -71
  92. package/lib/core/wire.mjs +0 -925
  93. package/lib/device/account.mjs +0 -87
  94. package/lib/device/admin/index.mjs +0 -13
  95. package/lib/device/admin/origins.mjs +0 -35
  96. package/lib/device/admin/routes/connections.mjs +0 -144
  97. package/lib/device/admin/routes/gateway.mjs +0 -199
  98. package/lib/device/admin/routes/lifecycle.mjs +0 -200
  99. package/lib/device/admin/routes/owner.mjs +0 -322
  100. package/lib/device/admin/routes/setup.mjs +0 -428
  101. package/lib/device/admin/routes/social.mjs +0 -188
  102. package/lib/device/admin/server.mjs +0 -95
  103. package/lib/device/admin/static.mjs +0 -247
  104. package/lib/device/admin/surface.mjs +0 -315
  105. package/lib/device/certs.mjs +0 -281
  106. package/lib/device/cli/commands/account.mjs +0 -586
  107. package/lib/device/cli/commands/run.mjs +0 -278
  108. package/lib/device/cli/commands/service.mjs +0 -221
  109. package/lib/device/cli/commands/setup.mjs +0 -489
  110. package/lib/device/cli/commands/state.mjs +0 -561
  111. package/lib/device/cli/context.mjs +0 -288
  112. package/lib/device/export-collections.mjs +0 -161
  113. package/lib/device/gateway-move.mjs +0 -58
  114. package/lib/device/home.mjs +0 -161
  115. package/lib/device/migrate.mjs +0 -115
  116. package/lib/device/ports.mjs +0 -27
  117. package/lib/device/remote.mjs +0 -54
  118. package/lib/device/setup.mjs +0 -496
  119. package/lib/device/update.mjs +0 -96
  120. package/lib/gateway/caches.mjs +0 -36
  121. package/lib/gateway/directory.mjs +0 -90
  122. package/lib/gateway/front-core.mjs +0 -966
  123. package/lib/gateway/gateway-core.mjs +0 -244
  124. package/lib/gateway/headers.mjs +0 -49
  125. package/lib/gateway/httpsig.mjs +0 -159
  126. package/lib/gateway/notices.mjs +0 -84
  127. package/lib/gateway/quiet.mjs +0 -189
  128. package/lib/gateway/relay-extras.mjs +0 -89
  129. package/lib/gateway/token-claims.mjs +0 -16
  130. package/lib/pod/README.md +0 -61
  131. package/lib/pod/actor.mjs +0 -96
  132. package/lib/pod/collection.mjs +0 -77
  133. package/lib/pod/containers.mjs +0 -140
  134. package/lib/pod/discovery.mjs +0 -46
  135. package/lib/pod/featured.mjs +0 -18
  136. package/lib/pod/followers.mjs +0 -24
  137. package/lib/pod/following.mjs +0 -10
  138. package/lib/pod/http.mjs +0 -74
  139. package/lib/pod/inbox.mjs +0 -166
  140. package/lib/pod/links.mjs +0 -35
  141. package/lib/pod/location.mjs +0 -52
  142. package/lib/pod/media.mjs +0 -27
  143. package/lib/pod/notes.mjs +0 -78
  144. package/lib/pod/notifications.mjs +0 -89
  145. package/lib/pod/outbox.mjs +0 -18
  146. package/lib/pod/package.json +0 -19
  147. package/lib/pod/policy.mjs +0 -58
  148. package/lib/pod/private.mjs +0 -19
  149. package/lib/pod/root.mjs +0 -104
  150. package/lib/pod/state.mjs +0 -40
  151. package/lib/pod/transport.mjs +0 -674
  152. package/lib/pod/type-index.mjs +0 -101
  153. package/lib/pod/urls.mjs +0 -91
  154. package/lib/server/embed.mjs +0 -424
  155. package/lib/session/README.md +0 -137
  156. package/lib/session/demo.html +0 -94
  157. package/lib/session/fedi-account.mjs +0 -417
  158. package/lib/session/fedi-login.mjs +0 -123
  159. package/lib/session/oidc-session.mjs +0 -233
  160. package/lib/session/package.json +0 -49
  161. package/lib/shared/guard.mjs +0 -213
  162. package/lib/shared/links.mjs +0 -9
  163. package/lib/shared/safefetch.mjs +0 -235
  164. package/lib/shared/ua.mjs +0 -29
  165. package/phanpy/dist/404.html +0 -40
  166. package/phanpy/dist/_headers +0 -2
  167. package/phanpy/dist/apple-touch-icon.png +0 -0
  168. package/phanpy/dist/assets/ICONS-DIPJeU0C.js +0 -2
  169. package/phanpy/dist/assets/ICONS-DIPJeU0C.js.map +0 -1
  170. package/phanpy/dist/assets/ScheduledAtField-D-Abfo4J.js +0 -183
  171. package/phanpy/dist/assets/ScheduledAtField-D-Abfo4J.js.map +0 -1
  172. package/phanpy/dist/assets/Temml-CCjoacWt.woff2 +0 -0
  173. package/phanpy/dist/assets/__vite-browser-external-C7iut881.js +0 -2
  174. package/phanpy/dist/assets/__vite-browser-external-C7iut881.js.map +0 -1
  175. package/phanpy/dist/assets/boosts-carousel-BiOaNdMT.jpg +0 -0
  176. package/phanpy/dist/assets/catch-up-O5q5C75Z.png +0 -0
  177. package/phanpy/dist/assets/chunk-aKtaBQYM.js +0 -1
  178. package/phanpy/dist/assets/compose-CFEx7wb4.js +0 -2
  179. package/phanpy/dist/assets/compose-CFEx7wb4.js.map +0 -1
  180. package/phanpy/dist/assets/compose-ZJN5xSb4.js +0 -35
  181. package/phanpy/dist/assets/compose-ZJN5xSb4.js.map +0 -1
  182. package/phanpy/dist/assets/debug.module-YFQgwnIr.js +0 -52
  183. package/phanpy/dist/assets/debug.module-YFQgwnIr.js.map +0 -1
  184. package/phanpy/dist/assets/dom-p1YcJ5Rw.js +0 -2
  185. package/phanpy/dist/assets/dom-p1YcJ5Rw.js.map +0 -1
  186. package/phanpy/dist/assets/home-mobile-dark@2x-Dc-J8-SM.png +0 -0
  187. package/phanpy/dist/assets/home-mobile-light@2x-Bd3fod8m.png +0 -0
  188. package/phanpy/dist/assets/home-tablet-dark@2x-BgcNI8le.png +0 -0
  189. package/phanpy/dist/assets/home-tablet-light@2x-BW2RV9T6.png +0 -0
  190. package/phanpy/dist/assets/icons/add-circle-line-WD0mwORD.js +0 -1
  191. package/phanpy/dist/assets/icons/alert-line-CpFUMf9s.js +0 -1
  192. package/phanpy/dist/assets/icons/android-2-line-Bzxnrd3S.js +0 -1
  193. package/phanpy/dist/assets/icons/announcement-line-5nwaJFJm.js +0 -1
  194. package/phanpy/dist/assets/icons/arrow-down-circle-line-a7Id9-Iv.js +0 -1
  195. package/phanpy/dist/assets/icons/arrow-down-line-B8uzXzq6.js +0 -1
  196. package/phanpy/dist/assets/icons/arrow-left-line-CGrGT9C-.js +0 -1
  197. package/phanpy/dist/assets/icons/arrow-right-line-CE-HERfX.js +0 -1
  198. package/phanpy/dist/assets/icons/arrow-to-up-line-CvdQjmcy.js +0 -1
  199. package/phanpy/dist/assets/icons/arrow-up-circle-line-DEparSNF.js +0 -1
  200. package/phanpy/dist/assets/icons/arrow-up-line-C7yBeugc.js +0 -1
  201. package/phanpy/dist/assets/icons/arrows-right-line-BtbXmJGP.js +0 -1
  202. package/phanpy/dist/assets/icons/at-line-CF66Bznk.js +0 -1
  203. package/phanpy/dist/assets/icons/attachment-line-BY8It-l9.js +0 -1
  204. package/phanpy/dist/assets/icons/blockquote-line-BS6pU_hm.js +0 -1
  205. package/phanpy/dist/assets/icons/board-line-C0UWXyA6.js +0 -1
  206. package/phanpy/dist/assets/icons/bookmark-line-1CtX5JLF.js +0 -1
  207. package/phanpy/dist/assets/icons/building-5-line-Dd59MzbO.js +0 -1
  208. package/phanpy/dist/assets/icons/bus-2-line-6_dvA0GG.js +0 -1
  209. package/phanpy/dist/assets/icons/calendar-day-line-Crpl_fmu.js +0 -1
  210. package/phanpy/dist/assets/icons/calendar-month-line-vommts2C.js +0 -1
  211. package/phanpy/dist/assets/icons/calendar-time-add-line-BU_DTlQk.js +0 -1
  212. package/phanpy/dist/assets/icons/camera-line-Cv69MEei.js +0 -1
  213. package/phanpy/dist/assets/icons/celebrate-line-B8DvIoi0.js +0 -1
  214. package/phanpy/dist/assets/icons/chart-bar-line-FK-QOmtF.js +0 -1
  215. package/phanpy/dist/assets/icons/chart-line-line-BGcJAFWw.js +0 -1
  216. package/phanpy/dist/assets/icons/chat-3-line-YFK2-hwz.js +0 -1
  217. package/phanpy/dist/assets/icons/check-circle-line-DsAxFr5-.js +0 -1
  218. package/phanpy/dist/assets/icons/clipboard-line-BBp364gJ.js +0 -1
  219. package/phanpy/dist/assets/icons/close-circle-line-CICdiuGz.js +0 -1
  220. package/phanpy/dist/assets/icons/close-line-DYrFWcjv.js +0 -1
  221. package/phanpy/dist/assets/icons/cloud-line-tiQAbah8.js +0 -1
  222. package/phanpy/dist/assets/icons/code-line-fAl8iiV3.js +0 -1
  223. package/phanpy/dist/assets/icons/comment-2-line-CR3g7WeB.js +0 -1
  224. package/phanpy/dist/assets/icons/copy-2-line-CLW69yF5.js +0 -1
  225. package/phanpy/dist/assets/icons/delete-2-line-B8xmbr4n.js +0 -1
  226. package/phanpy/dist/assets/icons/document-3-line-DBGNhAg2.js +0 -1
  227. package/phanpy/dist/assets/icons/document-line-C7mb9sb7.js +0 -1
  228. package/phanpy/dist/assets/icons/down-line-BGqSSThd.js +0 -1
  229. package/phanpy/dist/assets/icons/edit-4-line-DcgGaEhx.js +0 -1
  230. package/phanpy/dist/assets/icons/emoji-2-line-D8YgnM-y.js +0 -1
  231. package/phanpy/dist/assets/icons/exit-line-Dc07BS-x.js +0 -1
  232. package/phanpy/dist/assets/icons/external-link-line-BvpSkmjO.js +0 -1
  233. package/phanpy/dist/assets/icons/eye-2-line-CiWXx-f_.js +0 -1
  234. package/phanpy/dist/assets/icons/eye-close-line-BgTaSJ4P.js +0 -1
  235. package/phanpy/dist/assets/icons/filter-2-line-B_mR2usy.js +0 -1
  236. package/phanpy/dist/assets/icons/filter-line-C_nm-3cU.js +0 -1
  237. package/phanpy/dist/assets/icons/flag-1-line-C2HB05I0.js +0 -1
  238. package/phanpy/dist/assets/icons/forbid-circle-line-D3LTT9yd.js +0 -1
  239. package/phanpy/dist/assets/icons/formula-line-DLrt2d6j.js +0 -1
  240. package/phanpy/dist/assets/icons/grid-line-CgUmEikf.js +0 -1
  241. package/phanpy/dist/assets/icons/group-line-C5fnX0wU.js +0 -1
  242. package/phanpy/dist/assets/icons/hand-finger-2-line-BGgxbuL9.js +0 -1
  243. package/phanpy/dist/assets/icons/hashtag-line-CyTEEXBb.js +0 -1
  244. package/phanpy/dist/assets/icons/heart-crack-line-BZddbO2C.js +0 -1
  245. package/phanpy/dist/assets/icons/heart-line-CJ7K47J4.js +0 -1
  246. package/phanpy/dist/assets/icons/history-2-line-kWWvFzqv.js +0 -1
  247. package/phanpy/dist/assets/icons/history-line-bGQiwbTH.js +0 -1
  248. package/phanpy/dist/assets/icons/home-3-line-fRU1zeLG.js +0 -1
  249. package/phanpy/dist/assets/icons/information-line-iNk6-sOY.js +0 -1
  250. package/phanpy/dist/assets/icons/keyboard-line-DsCyuQNz.js +0 -1
  251. package/phanpy/dist/assets/icons/layout-4-line-CZkOX52Z.js +0 -1
  252. package/phanpy/dist/assets/icons/layout-5-line-B_znt5wO.js +0 -1
  253. package/phanpy/dist/assets/icons/left-line-eUw0tya4.js +0 -1
  254. package/phanpy/dist/assets/icons/lightning-line-DaKoDdix.js +0 -1
  255. package/phanpy/dist/assets/icons/link-2-line-DCqLmMA7.js +0 -1
  256. package/phanpy/dist/assets/icons/list-check-line-BAxosBh9.js +0 -1
  257. package/phanpy/dist/assets/icons/lock-line-CPoaXeUW.js +0 -1
  258. package/phanpy/dist/assets/icons/mail-line-B0P8aa1L.js +0 -1
  259. package/phanpy/dist/assets/icons/moon-line-CEqNAToP.js +0 -1
  260. package/phanpy/dist/assets/icons/more-1-fill-Dw2e5xab.js +0 -1
  261. package/phanpy/dist/assets/icons/more-3-line-BIiVjYTz.js +0 -1
  262. package/phanpy/dist/assets/icons/notification-line-BZZq2Gtu.js +0 -1
  263. package/phanpy/dist/assets/icons/pencil-line-CKh8-A1v.js +0 -1
  264. package/phanpy/dist/assets/icons/photo-album-line-Bnfg2u40.js +0 -1
  265. package/phanpy/dist/assets/icons/pin-line-DpTolyhs.js +0 -1
  266. package/phanpy/dist/assets/icons/play-fill-B0-G_8lX.js +0 -1
  267. package/phanpy/dist/assets/icons/qrcode-2-line-B6SPCaHn.js +0 -1
  268. package/phanpy/dist/assets/icons/quill-pen-line-B6LUrnCP.js +0 -1
  269. package/phanpy/dist/assets/icons/quote-left-fill-hQZ1cj_6.js +0 -1
  270. package/phanpy/dist/assets/icons/radar-line-CKyroTth.js +0 -1
  271. package/phanpy/dist/assets/icons/react-line-CoIKhhtK.js +0 -1
  272. package/phanpy/dist/assets/icons/refresh-2-line-D0mTF97S.js +0 -1
  273. package/phanpy/dist/assets/icons/right-line-B3KWJaqc.js +0 -1
  274. package/phanpy/dist/assets/icons/rocket-line-4KZl537D.js +0 -1
  275. package/phanpy/dist/assets/icons/round-fill-C69NTrZ0.js +0 -1
  276. package/phanpy/dist/assets/icons/round-line-B9k_1uo-.js +0 -1
  277. package/phanpy/dist/assets/icons/route-line-B28ReHcE.js +0 -1
  278. package/phanpy/dist/assets/icons/rows-4-line-CrBniagM.js +0 -1
  279. package/phanpy/dist/assets/icons/scan-line-DSf74YdP.js +0 -1
  280. package/phanpy/dist/assets/icons/search-2-line-B6geIU1z.js +0 -1
  281. package/phanpy/dist/assets/icons/settings-3-line-azJwK47w.js +0 -1
  282. package/phanpy/dist/assets/icons/settings-6-line-C_5zc5YR.js +0 -1
  283. package/phanpy/dist/assets/icons/share-2-line-D5MxWjrm.js +0 -1
  284. package/phanpy/dist/assets/icons/share-forward-line-Cp61fZzZ.js +0 -1
  285. package/phanpy/dist/assets/icons/sparkles-2-line-DYYyXTaW.js +0 -1
  286. package/phanpy/dist/assets/icons/sparkles-line-C2hY1s6I.js +0 -1
  287. package/phanpy/dist/assets/icons/time-line-BF3eet1v.js +0 -1
  288. package/phanpy/dist/assets/icons/transfer-4-line-oisfvTBw.js +0 -1
  289. package/phanpy/dist/assets/icons/translate-line-BFWol2Ce.js +0 -1
  290. package/phanpy/dist/assets/icons/unlock-line-X_x5vwv3.js +0 -1
  291. package/phanpy/dist/assets/icons/upload-3-line-DgzwUJeW.js +0 -1
  292. package/phanpy/dist/assets/icons/user-4-line-BVSynZCp.js +0 -1
  293. package/phanpy/dist/assets/icons/user-add-2-line-BlGS5Q8q.js +0 -1
  294. package/phanpy/dist/assets/icons/user-add-line-BbXdHG4z.js +0 -1
  295. package/phanpy/dist/assets/icons/user-edit-line-BFAHOV0X.js +0 -1
  296. package/phanpy/dist/assets/icons/user-follow-line-BXux0wSd.js +0 -1
  297. package/phanpy/dist/assets/icons/user-star-line-DdYlpHLU.js +0 -1
  298. package/phanpy/dist/assets/icons/user-warning-line-Cj43j0_E.js +0 -1
  299. package/phanpy/dist/assets/icons/user-x-line-DIDgNlPH.js +0 -1
  300. package/phanpy/dist/assets/icons/volume-line-Bs390KQt.js +0 -1
  301. package/phanpy/dist/assets/icons/volume-mute-line-DzlzIqFZ.js +0 -1
  302. package/phanpy/dist/assets/icons/walk-line-yNXfB-md.js +0 -1
  303. package/phanpy/dist/assets/icons/world-2-line-CenkGIwj.js +0 -1
  304. package/phanpy/dist/assets/icons/zoom-in-line-CaENUphf.js +0 -1
  305. package/phanpy/dist/assets/icons/zoom-out-line-BDNk0EWo.js +0 -1
  306. package/phanpy/dist/assets/instances-BNPptnn-.json +0 -333
  307. package/phanpy/dist/assets/locales/ar-SA-CfheRbCu.js +0 -1
  308. package/phanpy/dist/assets/locales/ca-ES-DHRW86Tz.js +0 -1
  309. package/phanpy/dist/assets/locales/cs-CZ-CKoqEuA1.js +0 -1
  310. package/phanpy/dist/assets/locales/de-DE-ByRPMRFy.js +0 -1
  311. package/phanpy/dist/assets/locales/eo-UY-CPC7wLCS.js +0 -1
  312. package/phanpy/dist/assets/locales/es-ES-dJa0Fd9t.js +0 -1
  313. package/phanpy/dist/assets/locales/eu-ES-C3djlPR2.js +0 -1
  314. package/phanpy/dist/assets/locales/fa-IR-DTT490Ck.js +0 -1
  315. package/phanpy/dist/assets/locales/fi-FI-B23MCPuz.js +0 -1
  316. package/phanpy/dist/assets/locales/fr-FR-BcaQsO5C.js +0 -1
  317. package/phanpy/dist/assets/locales/gl-ES-BR-CVKBN.js +0 -1
  318. package/phanpy/dist/assets/locales/he-IL-CfheRbCu.js +0 -1
  319. package/phanpy/dist/assets/locales/hu-HU-CfheRbCu.js +0 -1
  320. package/phanpy/dist/assets/locales/it-IT-C1136UFV.js +0 -1
  321. package/phanpy/dist/assets/locales/ja-JP-CRZmRAhD.js +0 -1
  322. package/phanpy/dist/assets/locales/kab-Cz8xH_TT.js +0 -1
  323. package/phanpy/dist/assets/locales/ko-KR-CG9UiY4o.js +0 -1
  324. package/phanpy/dist/assets/locales/lt-LT-CJcI6sHZ.js +0 -1
  325. package/phanpy/dist/assets/locales/nb-NO-CfheRbCu.js +0 -1
  326. package/phanpy/dist/assets/locales/nl-NL-DxN4Yc1t.js +0 -1
  327. package/phanpy/dist/assets/locales/oc-FR-CfheRbCu.js +0 -1
  328. package/phanpy/dist/assets/locales/pl-PL-DkwGXf6P.js +0 -1
  329. package/phanpy/dist/assets/locales/pseudo-LOCALE-CQ4XxkQ0.js +0 -1
  330. package/phanpy/dist/assets/locales/pt-BR-nGL0HdFS.js +0 -1
  331. package/phanpy/dist/assets/locales/pt-PT-Cp17aj2z.js +0 -1
  332. package/phanpy/dist/assets/locales/ru-RU-0Y8cIoaK.js +0 -1
  333. package/phanpy/dist/assets/locales/th-TH-CfheRbCu.js +0 -1
  334. package/phanpy/dist/assets/locales/tok-CfheRbCu.js +0 -1
  335. package/phanpy/dist/assets/locales/tr-TR-CfheRbCu.js +0 -1
  336. package/phanpy/dist/assets/locales/uk-UA-C5E3dFBf.js +0 -1
  337. package/phanpy/dist/assets/locales/zh-CN-BHJi7_et.js +0 -1
  338. package/phanpy/dist/assets/locales/zh-TW-CfheRbCu.js +0 -1
  339. package/phanpy/dist/assets/main-BdqNbG-a.js +0 -26
  340. package/phanpy/dist/assets/main-BdqNbG-a.js.map +0 -1
  341. package/phanpy/dist/assets/mock-home-DGhR__He.js +0 -2
  342. package/phanpy/dist/assets/mock-home-DGhR__He.js.map +0 -1
  343. package/phanpy/dist/assets/multi-column-ROck0NVt.jpg +0 -0
  344. package/phanpy/dist/assets/multi-hashtag-timeline-DH5INVHi.jpg +0 -0
  345. package/phanpy/dist/assets/nested-comments-thread-hv59kZLc.jpg +0 -0
  346. package/phanpy/dist/assets/polyfill-force-H8sUkLGp.js +0 -2
  347. package/phanpy/dist/assets/polyfill-force-H8sUkLGp.js.map +0 -1
  348. package/phanpy/dist/assets/pwa-viewport-ogKbF51K.js +0 -20
  349. package/phanpy/dist/assets/pwa-viewport-ogKbF51K.js.map +0 -1
  350. package/phanpy/dist/assets/style-B1iLPTPR.css +0 -2
  351. package/phanpy/dist/assets/temml-DuyIQMei.js +0 -97
  352. package/phanpy/dist/assets/temml-DuyIQMei.js.map +0 -1
  353. package/phanpy/dist/assets/tinyld.light.node-DtbBhLYC.js +0 -2
  354. package/phanpy/dist/assets/tinyld.light.node-DtbBhLYC.js.map +0 -1
  355. package/phanpy/dist/assets/year-in-posts-B2lywqdx.png +0 -0
  356. package/phanpy/dist/assets/year-in-posts-DJv_star.js +0 -3
  357. package/phanpy/dist/assets/year-in-posts-DJv_star.js.map +0 -1
  358. package/phanpy/dist/compose/index.html +0 -20
  359. package/phanpy/dist/favicon.ico +0 -0
  360. package/phanpy/dist/index.html +0 -73
  361. package/phanpy/dist/logo-192.png +0 -0
  362. package/phanpy/dist/logo-512.png +0 -0
  363. package/phanpy/dist/logo-badge-72.png +0 -0
  364. package/phanpy/dist/logo-maskable-512.png +0 -0
  365. package/phanpy/dist/logo-monochrome-512.png +0 -0
  366. package/phanpy/dist/logo-monochrome-maskable-512.png +0 -0
  367. package/phanpy/dist/manifest.webmanifest +0 -1
  368. package/phanpy/dist/og-image-2.jpg +0 -0
  369. package/phanpy/dist/og-image.png +0 -0
  370. package/phanpy/dist/robots.txt +0 -2
  371. package/phanpy/dist/sw.js +0 -2
  372. package/phanpy/dist/sw.js.map +0 -1
  373. package/phanpy/dist/version.json +0 -1
  374. package/run-agent.mjs +0 -899
  375. package/vendor/gate.cjs +0 -138
  376. package/vendor/idp-grant.cjs +0 -394
  377. package/vendor/jfetch.cjs +0 -16
  378. package/web/admin/actors.js +0 -147
  379. package/web/admin/bar.css +0 -126
  380. package/web/admin/bar.js +0 -99
  381. package/web/admin/client/client.js +0 -66
  382. package/web/admin/client/index.html +0 -69
  383. package/web/admin/client-pick.js +0 -79
  384. package/web/admin/common.js +0 -23
  385. package/web/admin/connections.js +0 -112
  386. package/web/admin/gateway.js +0 -133
  387. package/web/admin/group.js +0 -258
  388. package/web/admin/index.html +0 -612
  389. package/web/admin/notices-bar.js +0 -90
  390. package/web/admin/oauth-signin.mjs +0 -37
  391. package/web/admin/record.js +0 -393
  392. package/web/admin/setup/index.html +0 -257
  393. package/web/admin/setup/setup.js +0 -339
  394. package/web/admin/tokens.css +0 -68
  395. package/web/admin/upkeep.js +0 -177
  396. package/web/admin/window.css +0 -55
  397. package/web/admin/window.js +0 -136
  398. package/web/app/README.md +0 -81
  399. package/web/app/admin-facade.mjs +0 -588
  400. package/web/app/agent.mjs +0 -672
  401. package/web/app/atproto-browser.mjs +0 -86
  402. package/web/app/authorize_interaction.html +0 -56
  403. package/web/app/authorize_interaction.js +0 -69
  404. package/web/app/boot.mjs +0 -616
  405. package/web/app/deliver-relay.mjs +0 -129
  406. package/web/app/dist/boot.js +0 -34562
  407. package/web/app/dist/boot.js.map +0 -7
  408. package/web/app/dist/sw.js +0 -73456
  409. package/web/app/dist/sw.js.map +0 -7
  410. package/web/app/fediacct-browser.mjs +0 -146
  411. package/web/app/gateway-move.mjs +0 -60
  412. package/web/app/idb-kv.mjs +0 -35
  413. package/web/app/index.html +0 -258
  414. package/web/app/keys-browser.mjs +0 -71
  415. package/web/app/keystore.mjs +0 -82
  416. package/web/app/oidc-session.mjs +0 -6
  417. package/web/app/pod-auth.mjs +0 -248
  418. package/web/app/pod-remote.mjs +0 -84
  419. package/web/app/shims/_globals.mjs +0 -2
  420. package/web/app/shims/fedify-sig.mjs +0 -77
  421. package/web/app/shims/node-crypto.mjs +0 -113
  422. package/web/app/shims/node-fs.mjs +0 -17
  423. package/web/app/shims/node-path.mjs +0 -19
  424. package/web/app/shims/node-url.mjs +0 -7
  425. package/web/app/shims/prelude.js +0 -58
  426. package/web/app/shims/safefetch.mjs +0 -58
  427. package/web/app/shims/shapes-text.mjs +0 -8
  428. package/web/app/shims/web-push.mjs +0 -14
  429. package/web/app/signup.mjs +0 -357
  430. package/web/app/sw-src.mjs +0 -220
  431. package/web/app/update.js +0 -70
  432. package/web/front/admin.html +0 -55
  433. package/web/front/admin.js +0 -89
  434. package/web/front/install.sh +0 -69
  435. package/web/front/new-account.html +0 -35
  436. package/web/front/new-account.js +0 -13
  437. package/web/front/notices.html +0 -69
  438. package/web/front/notices.js +0 -107
  439. package/web/front/run.html +0 -64
  440. package/web/front/run.js +0 -221
  441. package/web/front/solid-oidc-client.js +0 -6
  442. package/web/icons/forum.svg +0 -11
  443. package/web/icons/gateway.svg +0 -14
  444. package/web/icons/local.svg +0 -11
@@ -1,58 +0,0 @@
1
- // safefetch.mjs — browser stand-in for lib/safefetch.mjs.
2
- //
3
- // The Node version pins connections to a validated IP to stop DNS-rebinding and
4
- // refuses private targets, using undici and node:dns. A browser cannot open raw
5
- // sockets and is bound by the same-origin policy and CORS, so the rebinding
6
- // attack this defends against is not reachable from here; the browser agent's
7
- // only cross-origin writes go through the relay, which runs the Node checks.
8
- // This keeps the same surface with plain fetch and no top-level await.
9
- export const HTTP_TIMEOUT_MS = 20_000;
10
- const MAX_BYTES = 5 * 1024 * 1024;
11
-
12
- export function retryAfterMs(res, max = 30 * 60_000) {
13
- const h = res?.headers?.get?.('retry-after');
14
- if (!h) return null;
15
- const secs = /^\d+$/.test(h.trim()) ? Number(h) * 1000 : (Date.parse(h) - Date.now());
16
- return Number.isFinite(secs) ? Math.max(0, Math.min(secs, max)) : null;
17
- }
18
- export function isPrivateAddress() { return false; }
19
- export function isLoopbackHost(host) { return /^(localhost|127\.|\[?::1)/.test(String(host)); }
20
- export function insecureUrlReason(url, what = 'address') {
21
- try { const u = new URL(url); if (u.protocol === 'https:' || isLoopbackHost(u.hostname)) return null;
22
- if (u.protocol !== 'http:') return `the ${what} must be http(s)`; return null; } catch { return `"${url}" is not a ${what}`; }
23
- }
24
- export async function assertPublicUrl() { return null; } // no socket pinning in a browser
25
- export async function pinnedFor() { return undefined; }
26
- // The one thing in this file that is NOT made unnecessary by being in a
27
- // browser. Pinning and the private-address checks answer DNS rebinding, which
28
- // a browser closes on its own; a byte budget answers a server that simply
29
- // keeps sending, which it does not. This used to check `content-length` and
30
- // then hand back `res.text()` — and a chunked response carries no
31
- // `content-length` at all, so every caller below dereferences a URL a stranger
32
- // chose (the note, the actor, the receipt, the Tombstone check, WebFinger)
33
- // with no bound on what comes back. Stream it and stop at the budget, the way
34
- // the Node version does. TextDecoder rather than Buffer, and `stream: true` so
35
- // a UTF-8 sequence split across two chunks still decodes.
36
- export async function readCapped(res, max = MAX_BYTES) {
37
- const len = Number(res.headers?.get?.('content-length') || 0);
38
- if (len > max) throw new Error(`response too large (${len} bytes)`);
39
- if (!res.body) return res.text();
40
- const reader = res.body.getReader();
41
- const decoder = new TextDecoder('utf-8');
42
- let out = '';
43
- let total = 0;
44
- for (;;) {
45
- const { done, value } = await reader.read();
46
- if (done) break;
47
- total += value.length;
48
- if (total > max) {
49
- await reader.cancel();
50
- throw new Error(`response exceeded ${max} bytes`);
51
- }
52
- out += decoder.decode(value, { stream: true });
53
- }
54
- return out + decoder.decode();
55
- }
56
- export async function safeFetch(url, init = {}, fetchImpl = fetch) {
57
- return fetchImpl(url, { ...init, signal: init.signal || AbortSignal.timeout(HTTP_TIMEOUT_MS) });
58
- }
@@ -1,8 +0,0 @@
1
- // shapes-text.mjs — the shapes document in the browser agent.
2
- //
3
- // Same activitystreams.ttl the Node agent reads; esbuild's text loader turns
4
- // it into a string at build time, so there is no filesystem here and no second
5
- // copy of the shapes.
6
- import ttl from '../../../lib/core/shapes/activitystreams.ttl';
7
-
8
- export const SHAPES_TTL = ttl;
@@ -1,14 +0,0 @@
1
- // web-push.mjs — browser stand-in. The facade needs a VAPID public key to put
2
- // in its instance document; server-sent push while the tab is closed is not
3
- // part of the browser build, so sendNotification is a no-op. The keypair is a
4
- // well-formed placeholder (a real one needs a matching private key, which the
5
- // browser push model does not use this way).
6
- const b64u = (u) => btoa(String.fromCharCode(...u)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
7
- export function generateVAPIDKeys() {
8
- const pub = new Uint8Array(65); pub[0] = 0x04; crypto.getRandomValues(pub.subarray(1));
9
- const priv = new Uint8Array(32); crypto.getRandomValues(priv);
10
- return { publicKey: b64u(pub), privateKey: b64u(priv) };
11
- }
12
- export function setVapidDetails() { /* no-op */ }
13
- export async function sendNotification() { return { statusCode: 201, body: '', headers: {} }; }
14
- export default { generateVAPIDKeys, setVapidDetails, sendNotification };
@@ -1,357 +0,0 @@
1
- // signup.mjs — the setup flow of `fedipod setup`, run in the browser, on the
2
- // pod session the person already holds. The pod exists before this runs: a
3
- // new one is made on the provider's own sign-up page, an existing one is
4
- // signed in to as it is. So there is no account API here, no credential and
5
- // no password — the session that signed in at the pod is the session that
6
- // writes to it. Publishing the actor is the agent's first-run job, exactly as
7
- // `connect()` publishes after `setup` mints — so this module needs nothing
8
- // from lib beyond the pod helpers.
9
- //
10
- // What it writes to the pod: an owner-only state container holding the
11
- // signing key and the account config; and, with a gateway, the attach.
12
-
13
- import { generateKeys, isKeyEnvelope } from './keystore.mjs';
14
- import { BrowserRemotePod } from './pod-remote.mjs';
15
- import * as podState from '../../lib/pod/state.mjs';
16
- import { resourceExists } from '../../lib/pod/root.mjs';
17
- import { podBaseOfWebId } from '../../lib/pod/urls.mjs';
18
- import { cacheOpenedKeys } from './keys-browser.mjs';
19
- import { chosenRoot, findAccount, recordPlace, hasPublicIndex } from '../../lib/core/place.mjs';
20
-
21
- // Everything the agent publishes hangs under a container named `fedipod`, in
22
- // the container its owner chose (lib/core/place.mjs). The root is stored on the
23
- // config, so the agent reads it rather than guessing, and the place is recorded
24
- // in the owner's public type index, which is how the account is found again.
25
- const actorUrlFor = (pod, root) => `${pod}${root}ap/actor`;
26
- const keysDocFor = (pod, root) => `${pod}${root}ap-state/keys.json`;
27
- const stateOf = (pod, root) => `${pod}${root}ap-state/`;
28
-
29
- /** The account's config as it sits under `root`, read as its owner. */
30
- const readConfigAt = (remote, pod) => (root) => podState.readConfig(remote, { state: stateOf(pod, root) });
31
-
32
- const HANDLE_RE = /^[a-z0-9-]{2,30}$/;
33
-
34
- export function handleProblem(handle) {
35
- if (!handle) return 'a handle is required';
36
- if (!HANDLE_RE.test(handle)) return 'letters, digits and hyphens only, 2–30 characters';
37
- if (handle.startsWith('-') || handle.endsWith('-')) return 'cannot start or end with a hyphen';
38
- return null;
39
- }
40
-
41
- // Resume across a failed attempt. A run that throws part-way keeps what it
42
- // already achieved in this page session — the gateway it attached, the key it
43
- // wrote — so a second attempt (the failure screen's Try again) continues from
44
- // the first unfinished step instead of restarting. The record lives only in
45
- // memory here and is dropped the moment the run completes. Keyed by the
46
- // identity being built, so changing the handle on the form starts a clean
47
- // attempt.
48
- const PROGRESS = new Map();
49
- const progressKey = (webId, a) => [webId, a.handle, a.shape || 'pod', a.container || ''].join('|');
50
-
51
- /** A fronted name is one per gateway; a taken one is refused before anything is made. */
52
- async function assertFrontNameFree(frontOrigin, handle) {
53
- const res = await fetch(`${frontOrigin.replace(/\/$/, '')}/api/handle?handle=${encodeURIComponent(handle)}`,
54
- { headers: { accept: 'application/json' } }).catch(() => null);
55
- const d = res ? await res.json().catch(() => ({})) : null;
56
- if (!d) throw new Error(`${new URL(frontOrigin).host} did not answer whether @${handle} is free`);
57
- if (!d.available) throw new Error(d.reason || `the name @${handle}@${new URL(frontOrigin).host} is taken — choose another handle`);
58
- }
59
-
60
- /**
61
- * Run sign-up on a pod session. Steps are reported through onStep(key, state,
62
- * note) so a page can draw the same tick list the CLI setup shows. Resumable:
63
- * see PROGRESS above.
64
- *
65
- * answers: { handle, shape?: 'pod'|'front', gateway?, container?, createIndex? }
66
- * container the path of the container that holds `fedipod/` (the pod root
67
- * when empty)
68
- * createIndex the person's yes to making a public type index when their
69
- * profile names none; without it, sign-up stops before writing
70
- * anything and says so (error code 'needs-index')
71
- * session: { webId, issuer, fetch } — the Solid-OIDC session from the pod's
72
- * own login (oidc-session.mjs). The pod is where the WebID lives.
73
- *
74
- * `shape` is where the address lives. On the pod, `@handle@yourpod`, with the
75
- * gateway as a mail door only — the default. At the gateway, `@handle@front`,
76
- * a fronted identity whose documents still live on the pod. A pod on a path of
77
- * a suffix-based host cannot answer WebFinger, so it is fronted whatever was asked.
78
- *
79
- * returns: { config, actorUrl, address, pod, keys, keysPublic }
80
- */
81
- export async function signUp(answers, { session, onStep = () => {}, frontOrigin = null } = {}) {
82
- const { handle } = answers;
83
- const bad = handleProblem(handle);
84
- if (bad) throw new Error(bad);
85
- if (!session?.webId || typeof session.fetch !== 'function') throw new Error('sign in at your pod first');
86
- const wantsFront = answers.shape === 'front';
87
- if (wantsFront && !frontOrigin) throw new Error('an address at the gateway needs a gateway, and this page has none');
88
- // A name at the gateway is one per gateway: settle it before making anything.
89
- if (wantsFront) await assertFrontNameFree(frontOrigin, handle);
90
-
91
- const webId = session.webId;
92
- const pod = podBaseOfWebId(webId); // its own host, or a path on a suffix-based one
93
- const key = progressKey(webId, answers);
94
- const prog = PROGRESS.get(key) || {};
95
- PROGRESS.set(key, prog); // resume record for this identity
96
-
97
- const step = (key) => ({
98
- running: (note) => onStep(key, 'running', note),
99
- ok: (note) => onStep(key, 'ok', note),
100
- skip: (note) => onStep(key, 'skipped', note),
101
- });
102
-
103
- const place = chosenRoot(pod, answers.container);
104
- if (place.problem) throw new Error(`Where to store it: ${place.problem}.`);
105
- const root = place.root;
106
- const remote = new BrowserRemotePod(session, { webId, role: 'signup', log: () => {} });
107
-
108
- // --- the pod --- (the session came from it; what is checked is that it has no account yet)
109
- const podStep = step('pod');
110
- if (!prog.pod) {
111
- podStep.running('checking your pod');
112
- if (await findAccount(remote, pod, readConfigAt(remote, pod))
113
- || await resourceExists(session.fetch, actorUrlFor(pod, root))) {
114
- throw new Error('The pod already hosts a FediPod account. If you want a second account, put it on a different pod.');
115
- }
116
- if (await resourceExists(session.fetch, `${pod}${root}`)) {
117
- throw new Error(`${new URL(pod + root).pathname} is already there and is not a FediPod account — choose another container.`);
118
- }
119
- // Where the account lives is recorded in the public type index. With none,
120
- // the person is asked first; nothing is written before they have said yes.
121
- if (!answers.createIndex && !await hasPublicIndex(remote, pod)) {
122
- throw Object.assign(new Error('Your profile names no public type index.'), { code: 'needs-index' });
123
- }
124
- prog.pod = pod;
125
- podStep.ok(pod);
126
- } else {
127
- podStep.ok(prog.pod); // resumed
128
- }
129
-
130
- // WebFinger is answered only at a host root. A pod on a path of a shared
131
- // host therefore takes its address at the gateway, whatever was asked; a
132
- // pod at its own root keeps the choice made on the form.
133
- const pathPod = new URL(pod).pathname !== '/';
134
- const fronted = pathPod || wantsFront;
135
- if (fronted && !frontOrigin) {
136
- throw new Error(`${pod} is a suffixed pod, so its address must live at a gateway, and this page has none.`);
137
- }
138
- if (pathPod && !wantsFront) await assertFrontNameFree(frontOrigin, handle);
139
-
140
- const actorUrl = actorUrlFor(pod, root); // where the documents live, always
141
- const frontActor = fronted ? `${frontOrigin.replace(/\/$/, '')}/u/${handle}/ap/actor` : null;
142
-
143
- // Connect the gateway (fedipod.net) before the key is made, because a
144
- // fronted identity's key is stamped with the gateway actor. Inbox-only: the
145
- // actor advertises the door as its inbox and keeps its own ids. Fronted: the
146
- // gateway answers WebFinger for @handle@front and serves the actor at its own
147
- // address, rewriting reads onto the pod. Either way the door verifies each
148
- // delivery and forwards the clean mail to the pod; the agent trusts the
149
- // door's receipt. The pod session itself proves the pod to the gateway.
150
- let gateway = answers.gateway || prog.gateway || null;
151
- if (frontOrigin && !gateway) {
152
- const gw = step('gateway');
153
- gw.running(fronted ? `taking your address at ${new URL(frontOrigin).host}` : `connecting your mail door on ${new URL(frontOrigin).host}`);
154
- const res = await session.fetch(`${frontOrigin.replace(/\/$/, '')}/api/attach`, {
155
- method: 'POST', headers: { 'content-type': 'application/json' },
156
- // podHome is the AP CONTAINER, not the pod root: the front builds the
157
- // delivery target as `podHome + 'ap/inbox/'` (lib/front-core.mjs), so a
158
- // bare pod root sends this identity's mail to <pod>/ap/inbox/ — outside
159
- // the container the agent drains, where nothing would ever read it. The
160
- // manage surface has always sent `urls.home`; this is the same value.
161
- body: JSON.stringify({ handle, podHome: `${pod}${root}`, actorUrl, kind: 'person', fronted }),
162
- });
163
- const d = await res.json().catch(() => ({}));
164
- if (res.status !== 201 || !d.hmacSecret) {
165
- throw new Error(`could not connect the gateway (HTTP ${res.status}): ${d.error || ''}`);
166
- }
167
- gateway = fronted
168
- ? { url: `${frontOrigin.replace(/\/$/, '')}/u/${handle}/ap/inbox/`, frontActor: String(d.frontActor || frontActor), hmacSecret: d.hmacSecret, mode: 'trust' }
169
- : { url: d.doorInbox, hmacSecret: d.hmacSecret, mode: 'trust' };
170
- prog.gateway = gateway;
171
- gw.ok();
172
- } else if (frontOrigin && gateway) {
173
- step('gateway').ok(); // resumed
174
- }
175
-
176
- // --- keys, in an owner-only container on the pod ---
177
- //
178
- // The durable copy is on the pod, because a browser has no disk you can carry
179
- // to the next machine. It is stored as it is, behind the container's access
180
- // rule: the same rule every private document on the pod lives under, so it
181
- // is reachable through the pod's own login and by nobody else.
182
- //
183
- // Two things in that order, deliberately: the ACL BEFORE the key. Writing the
184
- // key first leaves a window where a pod whose root is world-readable serves it
185
- // to anyone who asks, and a brought pod is exactly the case where that root
186
- // may be public.
187
- //
188
- // The opened copy is kept in this browser (IndexedDB) so the worker can boot
189
- // itself after an idle kill with no read. A browser that has no copy reads
190
- // the pod's with its session — see keys-browser.mjs.
191
- const keysStep = step('keys');
192
- let keys;
193
- if (!prog.keysStored) {
194
- keysStep.running('making your signing key and storing it on your pod');
195
- keys = await generateKeys();
196
- // One key, one actor (lib/keys.mjs): the actor the world knows, which for
197
- // a fronted identity is the gateway's address for it.
198
- keys.mintedFor = gateway?.frontActor || actorUrl;
199
- // Owner-only, and THEN the key — one operation, so the order cannot be got
200
- // wrong here or anywhere else. A pod that refuses the ACL write is not a
201
- // pod this key may sit on.
202
- try {
203
- await podState.provisionKey(remote, {
204
- stateUrl: stateOf(pod, root),
205
- keysUrl: keysDocFor(pod, root),
206
- keys,
207
- });
208
- } catch (e) {
209
- throw new Error(`could not store the signing key on the pod (${e.message}). `
210
- + `You are signed in as ${webId} — that WebID must own ${pod} and ${root} in it must be writable by it.`);
211
- }
212
- // This browser's own opened copy, so the boot that follows needs no read.
213
- // Best effort: a browser that refuses IndexedDB (private mode) reads the
214
- // pod's copy on the way back in.
215
- await cacheOpenedKeys(actorUrl, keys);
216
- prog.keys = keys; prog.keysStored = true;
217
- keysStep.ok();
218
- } else {
219
- keys = prog.keys;
220
- keysStep.ok();
221
- }
222
-
223
- const config = {
224
- remotePod: pod, root, handle, name: handle, issuer: String(session.issuer || '').replace(/\/+$/, ''),
225
- createdAt: new Date().toISOString(),
226
- ...(gateway ? { gateway } : {}),
227
- };
228
- // Write the config to the pod (owner-only, beside the key) so a returning
229
- // sign-in — which arrives with only an OIDC session — can read the account's
230
- // config and key from the pod and boot.
231
- try {
232
- await podState.writeConfig(remote, { state: stateOf(pod, root) }, config);
233
- } catch (e) {
234
- throw new Error(`could not store the config on the pod (${e.message})`);
235
- }
236
-
237
- // --- where it lives, in the public type index ---
238
- if (!prog.placed) {
239
- const placeStep = step('place');
240
- placeStep.running('recording where your account lives');
241
- try {
242
- await recordPlace(remote, pod, actorUrl, { create: !!answers.createIndex });
243
- } catch (e) {
244
- throw new Error(`could not record where your account lives in your type index (${e.message})`);
245
- }
246
- prog.placed = true;
247
- placeStep.ok();
248
- }
249
-
250
- PROGRESS.delete(key); // finished — nothing left to resume
251
-
252
- const host = gateway?.frontActor ? new URL(gateway.frontActor).host : new URL(pod).host;
253
- return {
254
- config, actorUrl, pod,
255
- address: `@${handle}@${host}`,
256
- // The opened keys, for booting the agent in THIS browser session right away.
257
- keys,
258
- keysPublic: { rsa: keys.rsa.publicPem, ed25519: keys.ed25519?.publicPem || null },
259
- };
260
- }
261
-
262
- // ---- an account already on the pod ----
263
-
264
- /**
265
- * What the pod already holds: null when there is no FediPod account on it,
266
- * else its config, the pod, and where its address lives — `frontHost` is
267
- * the gateway's host for an address at a gateway, null for one on the pod.
268
- */
269
- export async function readAccount(session) {
270
- const pod = podBaseOfWebId(session.webId);
271
- const remote = new BrowserRemotePod(session, { webId: session.webId, role: 'signup', log: () => {} });
272
- // A person account: the browser build is persons only.
273
- const found = await findAccount(remote, pod, readConfigAt(remote, pod), (c) => (c.kind || 'person') === 'person');
274
- if (!found) return null;
275
- const { root, config } = found;
276
- const frontActor = config.gateway?.frontActor || null;
277
- let frontHost = null;
278
- try { frontHost = frontActor ? new URL(frontActor).host : null; } catch { frontHost = null; }
279
- let podHost = '';
280
- try { podHost = new URL(config.remotePod || pod).host; } catch { podHost = ''; }
281
- return { pod, root, config, frontActor, frontHost, address: `@${config.handle}@${frontHost || podHost}` };
282
- }
283
-
284
- /**
285
- * Move an account whose address lives at another gateway to this one. The
286
- * pod, its posts, its followers and its key all stay where they are; what
287
- * changes is the gateway that answers for it. This half does the pod's
288
- * part: attach here, rewrite the config to name this gateway, restamp the
289
- * key. The agent's first boot from that config publishes under the new
290
- * ids, and then tells the old gateway and the followers (gateway-move.mjs).
291
- *
292
- * answers: { handle } — the name here; the old one is the default.
293
- * returns: { config, address, movedFrom }
294
- */
295
- export async function moveIn(answers, { session, onStep = () => {}, frontOrigin = null } = {}) {
296
- const { handle } = answers;
297
- const bad = handleProblem(handle);
298
- if (bad) throw new Error(bad);
299
- if (!session?.webId || typeof session.fetch !== 'function') throw new Error('sign in at your pod first');
300
- if (!frontOrigin) throw new Error('a move needs a gateway to move to, and this page has none');
301
- const origin = frontOrigin.replace(/\/$/, '');
302
- const step = (key) => ({ running: (note) => onStep(key, 'running', note), ok: (note) => onStep(key, 'ok', note) });
303
-
304
- // --- the pod: the account that is there, and its key ---
305
- const podStep = step('pod');
306
- podStep.running('reading your account on your pod');
307
- const here = await readAccount(session);
308
- if (!here?.frontActor) throw new Error('this pod holds no account at another gateway to move here');
309
- if (here.frontHost === new URL(origin).host) throw new Error('this account already lives at this gateway');
310
- const { pod, root, config: old } = here;
311
- const remote = new BrowserRemotePod(session, { webId: session.webId, role: 'signup', log: () => {} });
312
- const urls = { state: stateOf(pod, root) };
313
- const keys = await podState.readKeys(remote, urls);
314
- if (!keys?.rsa) throw new Error('could not read this account\'s signing key on the pod');
315
- if (isKeyEnvelope(keys)) throw new Error(`this account's key is still under a password from an earlier version — sign in once at ${here.frontHost} first, then come back`);
316
- podStep.ok(here.address);
317
-
318
- // --- this gateway: the name, then the attach, fronted ---
319
- const gw = step('gateway');
320
- gw.running(`taking your address at ${new URL(origin).host}`);
321
- await assertFrontNameFree(origin, handle);
322
- const res = await session.fetch(`${origin}/api/attach`, {
323
- method: 'POST', headers: { 'content-type': 'application/json' },
324
- body: JSON.stringify({ handle, podHome: `${pod}${root}`, actorUrl: actorUrlFor(pod, root), kind: old.kind || 'person', fronted: true }),
325
- });
326
- const d = await res.json().catch(() => ({}));
327
- if (res.status !== 201 || !d.hmacSecret) throw new Error(`could not attach to this gateway (HTTP ${res.status}): ${d.error || ''}`);
328
- const gateway = {
329
- url: `${origin}/u/${handle}/ap/inbox/`, frontActor: String(d.frontActor || `${origin}/u/${handle}/ap/actor`),
330
- hmacSecret: d.hmacSecret, mode: 'trust',
331
- };
332
- gw.ok();
333
-
334
- // --- the pod again: the config names this gateway, the key is restamped ---
335
- //
336
- // The old actor becomes an alias, which is what a server checks on the new
337
- // actor before it honours a Move. `movedFrom` is the pending half: the
338
- // agent completes it after its first boot under the new ids.
339
- const keysStep = step('keys');
340
- keysStep.running('moving your key and account record to the new address');
341
- const aliases = [...new Set([...(old.aliases || []), here.frontActor])];
342
- const config = {
343
- ...old, handle, gateway, aliases,
344
- movedFrom: { actor: here.frontActor, gateway: new URL(here.frontActor).origin, handle: old.handle, at: new Date().toISOString() },
345
- };
346
- keys.mintedFor = gateway.frontActor; // one key, one actor (lib/keys.mjs)
347
- try {
348
- await podState.writeKeys(remote, urls, keys);
349
- await podState.writeConfig(remote, urls, config);
350
- } catch (e) {
351
- throw new Error(`could not update your account on the pod (${e.message})`);
352
- }
353
- await cacheOpenedKeys(actorUrlFor(pod, root), keys);
354
- keysStep.ok();
355
-
356
- return { config, address: `@${handle}@${new URL(origin).host}`, movedFrom: config.movedFrom };
357
- }
@@ -1,220 +0,0 @@
1
- // sw-src.mjs — the service worker that hosts the in-browser agent.
2
- //
3
- // Phanpy (served static on this origin) calls the Mastodon API on its own
4
- // origin. This worker answers those calls from the agent running inside it, so
5
- // there is no server behind the client. The page boots the agent by posting it
6
- // the sign-up material (credential, config, plaintext keys); the worker holds
7
- // the agent for the life of the worker and intercepts the facade paths. Static
8
- // files fall through to the network.
9
- import { BrowserAgent } from './agent.mjs';
10
- import { getSession } from './oidc-session.mjs';
11
- import { ADMIN_PATHS } from './admin-facade.mjs';
12
-
13
- let agent = null;
14
- let booting = null;
15
-
16
- const FACADE = /^\/(api\/v[12]\/|oauth\/|nodeinfo\/)/; // Mastodon paths only — never the front's own /api/handle, /api/attach, /api/relay
17
- const FACADE_EXACT = new Set(['/.well-known/nodeinfo', '/.well-known/oauth-authorization-server', '/nodeinfo/2.0']);
18
- const isFacade = (p) => FACADE.test(p) || FACADE_EXACT.has(p);
19
- // The owner's record/manage data endpoints (see admin-facade.mjs). The web/admin
20
- // PAGES are static files Netlify serves; only these data calls are ours.
21
- const isAdmin = (p) => ADMIN_PATHS.has(p);
22
-
23
- // ---- who may drive these routes ----
24
- //
25
- // Same-origin is the WHOLE trust boundary for this build. The worker answers
26
- // the owner's admin routes with no password and no token — the only interlock
27
- // on the destructive ones is `confirm === handle`, and the handle is public —
28
- // so without this gate any page the owner merely visited could move every
29
- // follower to an attacker's account, retire the identity, rotate the key,
30
- // point the inbox at an attacker's gateway, or set a UI password the owner
31
- // does not know. The Node agent is covered by lib/guard.mjs; nothing from
32
- // that file is in this bundle, and it could not do the job here anyway —
33
- // `Sec-Fetch-*` headers are NOT visible inside a service worker's fetch
34
- // event, and neither is `Origin`. So the gate is built from what a worker
35
- // CAN see: the request's mode, its referrer, and its headers.
36
- //
37
- // The header is what does the work, and it does not need to be a secret to do
38
- // it. A cross-site FORM cannot set a header at all. A cross-origin fetch()
39
- // that sets one is a preflighted request, and nothing here ever answers a
40
- // preflight with CORS headers, so the browser never sends the real one. That
41
- // leaves a top-level navigation, which also carries no custom headers.
42
- //
43
- // (The review suggested making it a per-session secret handed to the page by
44
- // postMessage. Deliberately not carried: the header already refuses every
45
- // cross-ORIGIN caller, and a secret would only add something against an
46
- // attacker already running script on this origin — who could read it straight
47
- // out of the page. The defence at that layer is the CSP, in the site's
48
- // _headers.)
49
- const PAGE_HEADER = 'x-fedipod-page';
50
-
51
- // The one route that MUST survive a cross-site navigation: the other server's
52
- // OAuth redirect lands the browser here, from their origin, by design. Its
53
- // `state` parameter is what stands in for the header.
54
- const NAV_ALLOWED = new Set(['/fediacct/callback']);
55
-
56
- const sameOriginReferrer = (request) => {
57
- const r = request.referrer;
58
- // 'about:client' is the browser saying "the context that asked", which for a
59
- // request this worker intercepts is a page on this origin.
60
- if (!r || r === 'about:client') return r === 'about:client';
61
- try { return new URL(r).origin === self.location.origin; } catch { return false; }
62
- };
63
-
64
- // Returns null when the request may proceed, else the reason to refuse with.
65
- function notAllowed(request, url) {
66
- const p = url.pathname;
67
-
68
- // The Mastodon client API is deliberately open, exactly as it is on the Node
69
- // agent and on any real instance: a bearer is its credential, and a client
70
- // has to be able to call it. It mints nothing without one.
71
- if (!isAdmin(p) && !p.startsWith('/oauth/')) return null;
72
-
73
- if (request.mode === 'navigate') {
74
- if (NAV_ALLOWED.has(p)) return null;
75
- // The bundled client signs in by navigating its frame to /oauth/authorize
76
- // from a page on this origin. A navigation from anywhere else — or from
77
- // nowhere, which is what a page that suppressed its referrer looks like —
78
- // is not that, and /oauth/authorize is where a 90-day bearer is minted.
79
- if (p.startsWith('/oauth/') && sameOriginReferrer(request)) return null;
80
- return 'a navigation may not drive this route';
81
- }
82
-
83
- // /oauth/* is not open the way /api/* is: the only client here is the one
84
- // served from this origin, so a call from another origin is never legitimate.
85
- if (p.startsWith('/oauth/')) {
86
- return sameOriginReferrer(request) ? null : 'this route answers this origin only';
87
- }
88
-
89
- if (request.headers.get(PAGE_HEADER) !== '1') return `missing ${PAGE_HEADER}`;
90
- // Belt and braces on top of the header: the facade parses a body as JSON
91
- // whatever its content type, and `text/plain` is the one a cross-site form
92
- // can send. Nothing that got past the header sends anything else anyway.
93
- if (request.method !== 'GET' && request.method !== 'HEAD') {
94
- const ct = (request.headers.get('content-type') || '').split(';')[0].trim().toLowerCase();
95
- if (ct && ct !== 'application/json') return `unexpected content type "${ct}"`;
96
- }
97
- return null;
98
- }
99
-
100
- self.addEventListener('install', () => self.skipWaiting());
101
- self.addEventListener('activate', (e) => e.waitUntil(self.clients.claim()));
102
-
103
- // Boot the agent from the stored Solid-OIDC session. The sign-in page posts a
104
- // 'boot' to prime it, but the worker ALSO boots itself on demand (see serve):
105
- // browsers kill an idle service worker and restart it fresh, and the client at
106
- // /app/ never posts 'boot' — so without on-demand boot every request after a
107
- // restart 503s. On a boot failure `booting` is cleared so the next request retries.
108
- async function bootFromSession(frontOrigin) {
109
- const oidc = await getSession();
110
- if (!oidc) throw new Error('no session — sign in first');
111
- const a = new BrowserAgent({ log: (m) => console.log('[sw-agent]', m) });
112
- await a.boot({ oidc, frontOrigin: frontOrigin || self.location.origin });
113
- agent = a;
114
- return a;
115
- }
116
- function ensureBooting(frontOrigin) {
117
- if (agent) return Promise.resolve(agent);
118
- if (!booting) booting = bootFromSession(frontOrigin).catch((err) => { booting = null; throw err; });
119
- return booting;
120
- }
121
-
122
- self.addEventListener('message', (e) => {
123
- // Drop the identity this worker is holding. A worker outlives a page, so
124
- // signing in as somebody else left the FIRST account's agent live and serving
125
- // — the new session sat in IndexedDB while every request was still answered
126
- // by the old actor, with the old key. The page posts this before it boots the
127
- // new one; the next request boots from whatever session is stored now.
128
- if (e.data?.type === 'reset') {
129
- const had = !!agent;
130
- agent = null; booting = null;
131
- e.source?.postMessage({ type: 'reset-done', had });
132
- return;
133
- }
134
- if (e.data?.type !== 'boot') return;
135
- ensureBooting(e.data.frontOrigin)
136
- .then(() => e.source?.postMessage({ type: 'booted' }))
137
- // `code` matters: a boot that failed only because this browser has never
138
- // opened the signing key is not a failure the page should show as one — it
139
- // is a question the page can answer (boot.mjs). Everything else is shown.
140
- .catch((err) => e.source?.postMessage({ type: 'boot-error', error: err.message,
141
- code: err.code || null, stack: String(err.stack || '') }));
142
- });
143
-
144
- self.addEventListener('fetch', (e) => {
145
- const url = new URL(e.request.url);
146
- if (url.origin !== self.location.origin || !(isFacade(url.pathname) || isAdmin(url.pathname))) return; // static → network
147
- e.respondWith(serve(e.request, url));
148
- });
149
-
150
- async function serve(request, url) {
151
- // Before anything is booted or read: is this caller allowed to be here at all?
152
- const refuse = notAllowed(request, url);
153
- if (refuse) {
154
- console.warn(`[sw-agent] refused ${request.method} ${url.pathname}: ${refuse}`);
155
- return json(403, { error: `refused: ${refuse}` });
156
- }
157
- // Boot on demand from the stored session, so the client works even when the
158
- // worker was restarted (idle-killed) and nobody posted 'boot' this time.
159
- if (!agent) { try { await ensureBooting(); } catch { /* no session → 503 below */ } }
160
- if (!agent) return json(503, { error: 'not signed in on this browser — sign in at the front page' });
161
- // Bytes, not text. A multipart upload is binary — read as text it comes back
162
- // through a UTF-8 round trip that replaces every byte that is not valid UTF-8,
163
- // which is most of a JPEG, so the boundary search found nothing and every
164
- // media and avatar upload answered "422 file required". readBody() does
165
- // `data += chunk`, which decodes a Buffer the same way it always did, so the
166
- // JSON and form paths are unchanged.
167
- const bodyBytes = (request.method === 'GET' || request.method === 'HEAD')
168
- ? null : Buffer.from(new Uint8Array(await request.arrayBuffer()));
169
- // The admin facade is JSON-only (the gate above enforces the content type),
170
- // and takes its body already decoded.
171
- const bodyText = bodyBytes ? new TextDecoder().decode(bodyBytes) : '';
172
- const reqHeaders = {}; for (const [k, v] of request.headers) reqHeaders[k.toLowerCase()] = v;
173
- // `host` is a forbidden header name, so a fetch Request never carries one and
174
- // the loop above cannot produce it — but it is a header every real request
175
- // arrives with, and the agent reads it to say where it lives. Without it the
176
- // notifications `Link` header named `https://undefined/`, so a client that
177
- // paged by following it (which is how a client is meant to page) walked off
178
- // the origin and saw nothing past the first screen.
179
- reqHeaders.host = url.host;
180
- const listeners = {};
181
- // The body's events fire on the next microtask. A route that reads the body
182
- // may only register for them after an await or two — the facade dispatches
183
- // through its area modules first — so a listener that arrives after the
184
- // events have fired is given them at once, in the order it asks.
185
- let fired = false;
186
- const req = { method: request.method, url: url.pathname + url.search, headers: reqHeaders,
187
- // notAllowed() above let this through, so it came from this origin. Said
188
- // out loud rather than left implicit: MastoApi asks (through the
189
- // authorities object in agent.mjs) whether a request is the owner's own,
190
- // and in a browser that question means exactly this.
191
- sameOrigin: true,
192
- socket: { encrypted: url.protocol === 'https:' },
193
- on(ev, cb) {
194
- (listeners[ev] ||= []).push(cb);
195
- if (fired) { if (ev === 'data' && bodyBytes?.length) cb(bodyBytes); else if (ev === 'end') cb(); }
196
- return req;
197
- },
198
- destroy() {} };
199
- queueMicrotask(() => {
200
- fired = true;
201
- if (bodyBytes?.length) (listeners.data || []).forEach((cb) => cb(bodyBytes));
202
- (listeners.end || []).forEach((cb) => cb());
203
- });
204
- let status = 200; const outHeaders = {}; const chunks = [];
205
- const res = {
206
- writeHead(s, h) { status = s; if (h) Object.assign(outHeaders, h); return res; },
207
- setHeader(k, v) { outHeaders[k] = v; }, getHeader(k) { return outHeaders[k]; },
208
- write(c) { chunks.push(c); }, end(c) { if (c) chunks.push(c); },
209
- };
210
- try {
211
- const handled = isAdmin(url.pathname)
212
- ? await agent.admin.handle(req, res, url.pathname, url, bodyText)
213
- : await agent.masto.handle(req, res, url.pathname, url);
214
- if (!handled) return fetch(request);
215
- return new Response(chunks.join(''), { status, headers: { 'content-type': 'application/json', ...outHeaders } });
216
- } catch (err) {
217
- return json(500, { error: err.message });
218
- }
219
- }
220
- const json = (status, obj) => new Response(JSON.stringify(obj), { status, headers: { 'content-type': 'application/json' } });