fedipod-server 0.9.0 → 0.11.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 (319) hide show
  1. package/dist/claims.js +1 -1
  2. package/dist/handler.d.ts +1 -1
  3. package/dist/handler.js +35 -2
  4. package/dist/handler.jsonld +1 -1
  5. package/lib/admin.mjs +3 -3
  6. package/lib/c2s.mjs +27 -2
  7. package/lib/deliver.mjs +6 -0
  8. package/lib/embed.mjs +1 -0
  9. package/lib/export-collections.mjs +1 -1
  10. package/lib/front-core.mjs +272 -49
  11. package/lib/gateway-core.mjs +3 -7
  12. package/lib/httpsig.mjs +33 -2
  13. package/lib/import.mjs +1 -1
  14. package/lib/intake.mjs +357 -118
  15. package/lib/links.mjs +6 -32
  16. package/lib/mastoapi.mjs +449 -38
  17. package/lib/migrate.mjs +4 -18
  18. package/lib/pod/README.md +61 -0
  19. package/lib/pod/actor.mjs +96 -0
  20. package/lib/pod/collection.mjs +77 -0
  21. package/lib/pod/containers.mjs +136 -0
  22. package/lib/pod/discovery.mjs +46 -0
  23. package/lib/pod/featured.mjs +18 -0
  24. package/lib/pod/followers.mjs +24 -0
  25. package/lib/pod/following.mjs +10 -0
  26. package/lib/pod/http.mjs +74 -0
  27. package/lib/pod/inbox.mjs +144 -0
  28. package/lib/pod/links.mjs +35 -0
  29. package/lib/pod/media.mjs +27 -0
  30. package/lib/pod/notes.mjs +80 -0
  31. package/lib/pod/notifications.mjs +89 -0
  32. package/lib/pod/outbox.mjs +18 -0
  33. package/lib/pod/package.json +19 -0
  34. package/lib/pod/policy.mjs +58 -0
  35. package/lib/pod/private.mjs +19 -0
  36. package/lib/pod/root.mjs +63 -0
  37. package/lib/pod/state.mjs +39 -0
  38. package/lib/pod/transport.mjs +477 -0
  39. package/lib/pod/urls.mjs +71 -0
  40. package/lib/polls.mjs +105 -0
  41. package/lib/publisher.mjs +357 -188
  42. package/lib/remote.mjs +30 -395
  43. package/lib/social.mjs +20 -9
  44. package/lib/storage.mjs +2 -2
  45. package/lib/store.mjs +8 -1
  46. package/lib/tagfeed.mjs +19 -2
  47. package/lib/wire.mjs +87 -67
  48. package/package.json +1 -1
  49. package/run-agent.mjs +20 -39
  50. package/web/admin/admin.js +141 -74
  51. package/web/admin/bar.css +9 -13
  52. package/web/admin/bar.js +2 -2
  53. package/web/admin/client/client.js +1 -1
  54. package/web/admin/client/index.html +5 -6
  55. package/web/admin/index.html +53 -65
  56. package/web/admin/setup/index.html +2 -1
  57. package/web/admin/setup/setup.js +1 -1
  58. package/web/admin/tokens.css +56 -0
  59. package/web/app/README.md +77 -0
  60. package/web/app/admin-facade.mjs +549 -0
  61. package/web/app/agent.mjs +490 -0
  62. package/web/app/atproto-browser.mjs +86 -0
  63. package/web/app/boot.mjs +340 -0
  64. package/web/app/deliver-relay.mjs +72 -0
  65. package/web/app/dist/boot.js +34067 -0
  66. package/web/app/dist/boot.js.map +7 -0
  67. package/web/app/dist/sw.js +53745 -0
  68. package/web/app/dist/sw.js.map +7 -0
  69. package/web/app/fediacct-browser.mjs +146 -0
  70. package/web/app/idb-kv.mjs +35 -0
  71. package/web/app/index.html +203 -0
  72. package/web/app/index.html~ +152 -0
  73. package/web/app/keys-browser.mjs +46 -0
  74. package/web/app/keystore.mjs +99 -0
  75. package/web/app/oidc-session.mjs +189 -0
  76. package/web/app/pod-auth.mjs +248 -0
  77. package/web/app/pod-remote.mjs +84 -0
  78. package/web/app/shims/_globals.mjs +2 -0
  79. package/web/app/shims/fedify-sig.mjs +77 -0
  80. package/web/app/shims/node-crypto.mjs +113 -0
  81. package/web/app/shims/node-fs.mjs +17 -0
  82. package/web/app/shims/node-path.mjs +19 -0
  83. package/web/app/shims/node-url.mjs +7 -0
  84. package/web/app/shims/prelude.js +58 -0
  85. package/web/app/shims/safefetch.mjs +58 -0
  86. package/web/app/shims/web-push.mjs +14 -0
  87. package/web/app/signup.mjs +248 -0
  88. package/web/app/site/_headers +9 -0
  89. package/web/app/site/_redirects +21 -0
  90. package/web/app/site/admin/admin.js +1181 -0
  91. package/web/app/site/admin/bar.css +60 -0
  92. package/web/app/site/admin/bar.js +61 -0
  93. package/web/app/site/admin/client/client.js +66 -0
  94. package/web/app/site/admin/client/index.html +43 -0
  95. package/web/app/site/admin/index.html +556 -0
  96. package/web/app/site/admin/setup/index.html +191 -0
  97. package/web/app/site/admin/setup/setup.js +308 -0
  98. package/web/app/site/admin/tokens.css +56 -0
  99. package/web/app/site/admin/window.css +55 -0
  100. package/web/app/site/admin/window.js +136 -0
  101. package/web/app/site/app/404.html +40 -0
  102. package/web/app/site/app/_headers +2 -0
  103. package/web/app/site/app/apple-touch-icon.png +0 -0
  104. package/web/app/site/app/assets/ICONS-DIPJeU0C.js +2 -0
  105. package/web/app/site/app/assets/ICONS-DIPJeU0C.js.map +1 -0
  106. package/web/app/site/app/assets/ScheduledAtField-D-Abfo4J.js +183 -0
  107. package/web/app/site/app/assets/ScheduledAtField-D-Abfo4J.js.map +1 -0
  108. package/web/app/site/app/assets/Temml-CCjoacWt.woff2 +0 -0
  109. package/web/app/site/app/assets/__vite-browser-external-C7iut881.js +2 -0
  110. package/web/app/site/app/assets/__vite-browser-external-C7iut881.js.map +1 -0
  111. package/web/app/site/app/assets/boosts-carousel-BiOaNdMT.jpg +0 -0
  112. package/web/app/site/app/assets/catch-up-O5q5C75Z.png +0 -0
  113. package/web/app/site/app/assets/chunk-aKtaBQYM.js +1 -0
  114. package/web/app/site/app/assets/compose-CFEx7wb4.js +2 -0
  115. package/web/app/site/app/assets/compose-CFEx7wb4.js.map +1 -0
  116. package/web/app/site/app/assets/compose-ZJN5xSb4.js +35 -0
  117. package/web/app/site/app/assets/compose-ZJN5xSb4.js.map +1 -0
  118. package/web/app/site/app/assets/debug.module-YFQgwnIr.js +52 -0
  119. package/web/app/site/app/assets/debug.module-YFQgwnIr.js.map +1 -0
  120. package/web/app/site/app/assets/dom-p1YcJ5Rw.js +2 -0
  121. package/web/app/site/app/assets/dom-p1YcJ5Rw.js.map +1 -0
  122. package/web/app/site/app/assets/home-mobile-dark@2x-Dc-J8-SM.png +0 -0
  123. package/web/app/site/app/assets/home-mobile-light@2x-Bd3fod8m.png +0 -0
  124. package/web/app/site/app/assets/home-tablet-dark@2x-BgcNI8le.png +0 -0
  125. package/web/app/site/app/assets/home-tablet-light@2x-BW2RV9T6.png +0 -0
  126. package/web/app/site/app/assets/icons/add-circle-line-WD0mwORD.js +1 -0
  127. package/web/app/site/app/assets/icons/alert-line-CpFUMf9s.js +1 -0
  128. package/web/app/site/app/assets/icons/android-2-line-Bzxnrd3S.js +1 -0
  129. package/web/app/site/app/assets/icons/announcement-line-5nwaJFJm.js +1 -0
  130. package/web/app/site/app/assets/icons/arrow-down-circle-line-a7Id9-Iv.js +1 -0
  131. package/web/app/site/app/assets/icons/arrow-down-line-B8uzXzq6.js +1 -0
  132. package/web/app/site/app/assets/icons/arrow-left-line-CGrGT9C-.js +1 -0
  133. package/web/app/site/app/assets/icons/arrow-right-line-CE-HERfX.js +1 -0
  134. package/web/app/site/app/assets/icons/arrow-to-up-line-CvdQjmcy.js +1 -0
  135. package/web/app/site/app/assets/icons/arrow-up-circle-line-DEparSNF.js +1 -0
  136. package/web/app/site/app/assets/icons/arrow-up-line-C7yBeugc.js +1 -0
  137. package/web/app/site/app/assets/icons/arrows-right-line-BtbXmJGP.js +1 -0
  138. package/web/app/site/app/assets/icons/at-line-CF66Bznk.js +1 -0
  139. package/web/app/site/app/assets/icons/attachment-line-BY8It-l9.js +1 -0
  140. package/web/app/site/app/assets/icons/blockquote-line-BS6pU_hm.js +1 -0
  141. package/web/app/site/app/assets/icons/board-line-C0UWXyA6.js +1 -0
  142. package/web/app/site/app/assets/icons/bookmark-line-1CtX5JLF.js +1 -0
  143. package/web/app/site/app/assets/icons/building-5-line-Dd59MzbO.js +1 -0
  144. package/web/app/site/app/assets/icons/bus-2-line-6_dvA0GG.js +1 -0
  145. package/web/app/site/app/assets/icons/calendar-day-line-Crpl_fmu.js +1 -0
  146. package/web/app/site/app/assets/icons/calendar-month-line-vommts2C.js +1 -0
  147. package/web/app/site/app/assets/icons/calendar-time-add-line-BU_DTlQk.js +1 -0
  148. package/web/app/site/app/assets/icons/camera-line-Cv69MEei.js +1 -0
  149. package/web/app/site/app/assets/icons/celebrate-line-B8DvIoi0.js +1 -0
  150. package/web/app/site/app/assets/icons/chart-bar-line-FK-QOmtF.js +1 -0
  151. package/web/app/site/app/assets/icons/chart-line-line-BGcJAFWw.js +1 -0
  152. package/web/app/site/app/assets/icons/chat-3-line-YFK2-hwz.js +1 -0
  153. package/web/app/site/app/assets/icons/check-circle-line-DsAxFr5-.js +1 -0
  154. package/web/app/site/app/assets/icons/clipboard-line-BBp364gJ.js +1 -0
  155. package/web/app/site/app/assets/icons/close-circle-line-CICdiuGz.js +1 -0
  156. package/web/app/site/app/assets/icons/close-line-DYrFWcjv.js +1 -0
  157. package/web/app/site/app/assets/icons/cloud-line-tiQAbah8.js +1 -0
  158. package/web/app/site/app/assets/icons/code-line-fAl8iiV3.js +1 -0
  159. package/web/app/site/app/assets/icons/comment-2-line-CR3g7WeB.js +1 -0
  160. package/web/app/site/app/assets/icons/copy-2-line-CLW69yF5.js +1 -0
  161. package/web/app/site/app/assets/icons/delete-2-line-B8xmbr4n.js +1 -0
  162. package/web/app/site/app/assets/icons/document-3-line-DBGNhAg2.js +1 -0
  163. package/web/app/site/app/assets/icons/document-line-C7mb9sb7.js +1 -0
  164. package/web/app/site/app/assets/icons/down-line-BGqSSThd.js +1 -0
  165. package/web/app/site/app/assets/icons/edit-4-line-DcgGaEhx.js +1 -0
  166. package/web/app/site/app/assets/icons/emoji-2-line-D8YgnM-y.js +1 -0
  167. package/web/app/site/app/assets/icons/exit-line-Dc07BS-x.js +1 -0
  168. package/web/app/site/app/assets/icons/external-link-line-BvpSkmjO.js +1 -0
  169. package/web/app/site/app/assets/icons/eye-2-line-CiWXx-f_.js +1 -0
  170. package/web/app/site/app/assets/icons/eye-close-line-BgTaSJ4P.js +1 -0
  171. package/web/app/site/app/assets/icons/filter-2-line-B_mR2usy.js +1 -0
  172. package/web/app/site/app/assets/icons/filter-line-C_nm-3cU.js +1 -0
  173. package/web/app/site/app/assets/icons/flag-1-line-C2HB05I0.js +1 -0
  174. package/web/app/site/app/assets/icons/forbid-circle-line-D3LTT9yd.js +1 -0
  175. package/web/app/site/app/assets/icons/formula-line-DLrt2d6j.js +1 -0
  176. package/web/app/site/app/assets/icons/grid-line-CgUmEikf.js +1 -0
  177. package/web/app/site/app/assets/icons/group-line-C5fnX0wU.js +1 -0
  178. package/web/app/site/app/assets/icons/hand-finger-2-line-BGgxbuL9.js +1 -0
  179. package/web/app/site/app/assets/icons/hashtag-line-CyTEEXBb.js +1 -0
  180. package/web/app/site/app/assets/icons/heart-crack-line-BZddbO2C.js +1 -0
  181. package/web/app/site/app/assets/icons/heart-line-CJ7K47J4.js +1 -0
  182. package/web/app/site/app/assets/icons/history-2-line-kWWvFzqv.js +1 -0
  183. package/web/app/site/app/assets/icons/history-line-bGQiwbTH.js +1 -0
  184. package/web/app/site/app/assets/icons/home-3-line-fRU1zeLG.js +1 -0
  185. package/web/app/site/app/assets/icons/information-line-iNk6-sOY.js +1 -0
  186. package/web/app/site/app/assets/icons/keyboard-line-DsCyuQNz.js +1 -0
  187. package/web/app/site/app/assets/icons/layout-4-line-CZkOX52Z.js +1 -0
  188. package/web/app/site/app/assets/icons/layout-5-line-B_znt5wO.js +1 -0
  189. package/web/app/site/app/assets/icons/left-line-eUw0tya4.js +1 -0
  190. package/web/app/site/app/assets/icons/lightning-line-DaKoDdix.js +1 -0
  191. package/web/app/site/app/assets/icons/link-2-line-DCqLmMA7.js +1 -0
  192. package/web/app/site/app/assets/icons/list-check-line-BAxosBh9.js +1 -0
  193. package/web/app/site/app/assets/icons/lock-line-CPoaXeUW.js +1 -0
  194. package/web/app/site/app/assets/icons/mail-line-B0P8aa1L.js +1 -0
  195. package/web/app/site/app/assets/icons/moon-line-CEqNAToP.js +1 -0
  196. package/web/app/site/app/assets/icons/more-1-fill-Dw2e5xab.js +1 -0
  197. package/web/app/site/app/assets/icons/more-3-line-BIiVjYTz.js +1 -0
  198. package/web/app/site/app/assets/icons/notification-line-BZZq2Gtu.js +1 -0
  199. package/web/app/site/app/assets/icons/pencil-line-CKh8-A1v.js +1 -0
  200. package/web/app/site/app/assets/icons/photo-album-line-Bnfg2u40.js +1 -0
  201. package/web/app/site/app/assets/icons/pin-line-DpTolyhs.js +1 -0
  202. package/web/app/site/app/assets/icons/play-fill-B0-G_8lX.js +1 -0
  203. package/web/app/site/app/assets/icons/qrcode-2-line-B6SPCaHn.js +1 -0
  204. package/web/app/site/app/assets/icons/quill-pen-line-B6LUrnCP.js +1 -0
  205. package/web/app/site/app/assets/icons/quote-left-fill-hQZ1cj_6.js +1 -0
  206. package/web/app/site/app/assets/icons/radar-line-CKyroTth.js +1 -0
  207. package/web/app/site/app/assets/icons/react-line-CoIKhhtK.js +1 -0
  208. package/web/app/site/app/assets/icons/refresh-2-line-D0mTF97S.js +1 -0
  209. package/web/app/site/app/assets/icons/right-line-B3KWJaqc.js +1 -0
  210. package/web/app/site/app/assets/icons/rocket-line-4KZl537D.js +1 -0
  211. package/web/app/site/app/assets/icons/round-fill-C69NTrZ0.js +1 -0
  212. package/web/app/site/app/assets/icons/round-line-B9k_1uo-.js +1 -0
  213. package/web/app/site/app/assets/icons/route-line-B28ReHcE.js +1 -0
  214. package/web/app/site/app/assets/icons/rows-4-line-CrBniagM.js +1 -0
  215. package/web/app/site/app/assets/icons/scan-line-DSf74YdP.js +1 -0
  216. package/web/app/site/app/assets/icons/search-2-line-B6geIU1z.js +1 -0
  217. package/web/app/site/app/assets/icons/settings-3-line-azJwK47w.js +1 -0
  218. package/web/app/site/app/assets/icons/settings-6-line-C_5zc5YR.js +1 -0
  219. package/web/app/site/app/assets/icons/share-2-line-D5MxWjrm.js +1 -0
  220. package/web/app/site/app/assets/icons/share-forward-line-Cp61fZzZ.js +1 -0
  221. package/web/app/site/app/assets/icons/sparkles-2-line-DYYyXTaW.js +1 -0
  222. package/web/app/site/app/assets/icons/sparkles-line-C2hY1s6I.js +1 -0
  223. package/web/app/site/app/assets/icons/time-line-BF3eet1v.js +1 -0
  224. package/web/app/site/app/assets/icons/transfer-4-line-oisfvTBw.js +1 -0
  225. package/web/app/site/app/assets/icons/translate-line-BFWol2Ce.js +1 -0
  226. package/web/app/site/app/assets/icons/unlock-line-X_x5vwv3.js +1 -0
  227. package/web/app/site/app/assets/icons/upload-3-line-DgzwUJeW.js +1 -0
  228. package/web/app/site/app/assets/icons/user-4-line-BVSynZCp.js +1 -0
  229. package/web/app/site/app/assets/icons/user-add-2-line-BlGS5Q8q.js +1 -0
  230. package/web/app/site/app/assets/icons/user-add-line-BbXdHG4z.js +1 -0
  231. package/web/app/site/app/assets/icons/user-edit-line-BFAHOV0X.js +1 -0
  232. package/web/app/site/app/assets/icons/user-follow-line-BXux0wSd.js +1 -0
  233. package/web/app/site/app/assets/icons/user-star-line-DdYlpHLU.js +1 -0
  234. package/web/app/site/app/assets/icons/user-warning-line-Cj43j0_E.js +1 -0
  235. package/web/app/site/app/assets/icons/user-x-line-DIDgNlPH.js +1 -0
  236. package/web/app/site/app/assets/icons/volume-line-Bs390KQt.js +1 -0
  237. package/web/app/site/app/assets/icons/volume-mute-line-DzlzIqFZ.js +1 -0
  238. package/web/app/site/app/assets/icons/walk-line-yNXfB-md.js +1 -0
  239. package/web/app/site/app/assets/icons/world-2-line-CenkGIwj.js +1 -0
  240. package/web/app/site/app/assets/icons/zoom-in-line-CaENUphf.js +1 -0
  241. package/web/app/site/app/assets/icons/zoom-out-line-BDNk0EWo.js +1 -0
  242. package/web/app/site/app/assets/instances-BNPptnn-.json +333 -0
  243. package/web/app/site/app/assets/locales/ar-SA-CfheRbCu.js +1 -0
  244. package/web/app/site/app/assets/locales/ca-ES-DHRW86Tz.js +1 -0
  245. package/web/app/site/app/assets/locales/cs-CZ-CKoqEuA1.js +1 -0
  246. package/web/app/site/app/assets/locales/de-DE-ByRPMRFy.js +1 -0
  247. package/web/app/site/app/assets/locales/eo-UY-CPC7wLCS.js +1 -0
  248. package/web/app/site/app/assets/locales/es-ES-dJa0Fd9t.js +1 -0
  249. package/web/app/site/app/assets/locales/eu-ES-C3djlPR2.js +1 -0
  250. package/web/app/site/app/assets/locales/fa-IR-DTT490Ck.js +1 -0
  251. package/web/app/site/app/assets/locales/fi-FI-B23MCPuz.js +1 -0
  252. package/web/app/site/app/assets/locales/fr-FR-BcaQsO5C.js +1 -0
  253. package/web/app/site/app/assets/locales/gl-ES-BR-CVKBN.js +1 -0
  254. package/web/app/site/app/assets/locales/he-IL-CfheRbCu.js +1 -0
  255. package/web/app/site/app/assets/locales/hu-HU-CfheRbCu.js +1 -0
  256. package/web/app/site/app/assets/locales/it-IT-C1136UFV.js +1 -0
  257. package/web/app/site/app/assets/locales/ja-JP-CRZmRAhD.js +1 -0
  258. package/web/app/site/app/assets/locales/kab-Cz8xH_TT.js +1 -0
  259. package/web/app/site/app/assets/locales/ko-KR-CG9UiY4o.js +1 -0
  260. package/web/app/site/app/assets/locales/lt-LT-CJcI6sHZ.js +1 -0
  261. package/web/app/site/app/assets/locales/nb-NO-CfheRbCu.js +1 -0
  262. package/web/app/site/app/assets/locales/nl-NL-DxN4Yc1t.js +1 -0
  263. package/web/app/site/app/assets/locales/oc-FR-CfheRbCu.js +1 -0
  264. package/web/app/site/app/assets/locales/pl-PL-DkwGXf6P.js +1 -0
  265. package/web/app/site/app/assets/locales/pseudo-LOCALE-CQ4XxkQ0.js +1 -0
  266. package/web/app/site/app/assets/locales/pt-BR-nGL0HdFS.js +1 -0
  267. package/web/app/site/app/assets/locales/pt-PT-Cp17aj2z.js +1 -0
  268. package/web/app/site/app/assets/locales/ru-RU-0Y8cIoaK.js +1 -0
  269. package/web/app/site/app/assets/locales/th-TH-CfheRbCu.js +1 -0
  270. package/web/app/site/app/assets/locales/tok-CfheRbCu.js +1 -0
  271. package/web/app/site/app/assets/locales/tr-TR-CfheRbCu.js +1 -0
  272. package/web/app/site/app/assets/locales/uk-UA-C5E3dFBf.js +1 -0
  273. package/web/app/site/app/assets/locales/zh-CN-BHJi7_et.js +1 -0
  274. package/web/app/site/app/assets/locales/zh-TW-CfheRbCu.js +1 -0
  275. package/web/app/site/app/assets/main-BdqNbG-a.js +26 -0
  276. package/web/app/site/app/assets/main-BdqNbG-a.js.map +1 -0
  277. package/web/app/site/app/assets/mock-home-DGhR__He.js +2 -0
  278. package/web/app/site/app/assets/mock-home-DGhR__He.js.map +1 -0
  279. package/web/app/site/app/assets/multi-column-ROck0NVt.jpg +0 -0
  280. package/web/app/site/app/assets/multi-hashtag-timeline-DH5INVHi.jpg +0 -0
  281. package/web/app/site/app/assets/nested-comments-thread-hv59kZLc.jpg +0 -0
  282. package/web/app/site/app/assets/polyfill-force-H8sUkLGp.js +2 -0
  283. package/web/app/site/app/assets/polyfill-force-H8sUkLGp.js.map +1 -0
  284. package/web/app/site/app/assets/pwa-viewport-ogKbF51K.js +20 -0
  285. package/web/app/site/app/assets/pwa-viewport-ogKbF51K.js.map +1 -0
  286. package/web/app/site/app/assets/style-B1iLPTPR.css +2 -0
  287. package/web/app/site/app/assets/temml-DuyIQMei.js +97 -0
  288. package/web/app/site/app/assets/temml-DuyIQMei.js.map +1 -0
  289. package/web/app/site/app/assets/tinyld.light.node-DtbBhLYC.js +2 -0
  290. package/web/app/site/app/assets/tinyld.light.node-DtbBhLYC.js.map +1 -0
  291. package/web/app/site/app/assets/year-in-posts-B2lywqdx.png +0 -0
  292. package/web/app/site/app/assets/year-in-posts-DJv_star.js +3 -0
  293. package/web/app/site/app/assets/year-in-posts-DJv_star.js.map +1 -0
  294. package/web/app/site/app/compose/index.html +20 -0
  295. package/web/app/site/app/favicon.ico +0 -0
  296. package/web/app/site/app/index.html +73 -0
  297. package/web/app/site/app/logo-192.png +0 -0
  298. package/web/app/site/app/logo-512.png +0 -0
  299. package/web/app/site/app/logo-badge-72.png +0 -0
  300. package/web/app/site/app/logo-maskable-512.png +0 -0
  301. package/web/app/site/app/logo-monochrome-512.png +0 -0
  302. package/web/app/site/app/logo-monochrome-maskable-512.png +0 -0
  303. package/web/app/site/app/manifest.webmanifest +1 -0
  304. package/web/app/site/app/og-image-2.jpg +0 -0
  305. package/web/app/site/app/og-image.png +0 -0
  306. package/web/app/site/app/robots.txt +2 -0
  307. package/web/app/site/app/version.json +1 -0
  308. package/web/app/site/boot.js +34067 -0
  309. package/web/app/site/index.html +203 -0
  310. package/web/app/site/sw.js +53745 -0
  311. package/web/app/sw-src.mjs +205 -0
  312. package/web/front/#new-account.html# +43 -0
  313. package/web/front/admin.html +1 -81
  314. package/web/front/admin.js +85 -0
  315. package/web/front/new-account.html +1 -9
  316. package/web/front/new-account.js +13 -0
  317. package/web/front/run.html +1 -92
  318. package/web/front/run.js +107 -0
  319. package/lib/podrdf.mjs +0 -132
@@ -0,0 +1,144 @@
1
+ // inbox.mjs — ap/inbox/: what arrives, and who may touch it.
2
+ //
3
+ // One file for the whole inbox, because the inbox is where two different
4
+ // parties meet and neither half is comprehensible alone: a delivery gateway
5
+ // may only ADD to it, holding at most an Append token; the owner's agent
6
+ // lists, reads and removes, holding the pod's own credential. Splitting them
7
+ // by who does them would put the two ends of one contract in two files.
8
+
9
+ const DELIVERY_CT = 'application/activity+json';
10
+ const RECEIPT_CT = 'application/json';
11
+
12
+ // ---- a delivery gateway: appends verified mail, and can do nothing else ----
13
+
14
+ /**
15
+ * PUT one document into a pod inbox, with an Append token when the caller
16
+ * holds one and anonymously when it does not.
17
+ *
18
+ * Sending no `authorization` header is not the same as sending an empty one:
19
+ * `Bearer undefined` is a credential the pod will reject, where absence is a
20
+ * public Append the pod may well allow. This is the one place that gets to
21
+ * decide that, because it lived in two adapters and had to be right in both.
22
+ *
23
+ * @returns {Promise<boolean>} whether it landed
24
+ */
25
+ export async function appendWithToken(url, body, contentType, { appendToken = null, fetchImpl = fetch } = {}) {
26
+ const headers = { 'content-type': contentType,
27
+ ...(appendToken ? { authorization: `Bearer ${appendToken}` } : {}) };
28
+ const r = await fetchImpl(url, { method: 'PUT', headers, body }).catch(() => null);
29
+ return !!r && r.status < 400;
30
+ }
31
+
32
+ /**
33
+ * The delivery itself, named by the hash of its own bytes.
34
+ *
35
+ * Content-addressed on purpose: the same activity delivered twice writes the
36
+ * same document twice, so a retrying origin cannot produce duplicates in
37
+ * someone's inbox.
38
+ *
39
+ * A failure here must reach the ORIGIN as a 5xx so it retries over its own
40
+ * ladder — that is what preserves the pod's buffer property without the
41
+ * gateway holding any state of its own.
42
+ */
43
+ export async function appendVerifiedDelivery(podPut, inboxUrl, hash, raw) {
44
+ return podPut(inboxUrl + hash, raw, DELIVERY_CT);
45
+ }
46
+
47
+ /**
48
+ * The verification receipt, written beside the delivery it vouches for.
49
+ *
50
+ * Best-effort by design: a delivery that landed but whose receipt did not is
51
+ * read as unverified at the drain, which is a lesser thing than losing it.
52
+ */
53
+ export async function writeReceiptBeside(podPut, inboxUrl, hash, receipt) {
54
+ await podPut(inboxUrl + hash + '.receipt.json', JSON.stringify(receipt), RECEIPT_CT)
55
+ .catch(() => {});
56
+ }
57
+
58
+ // ---- the owner's agent: brings it into being and decides who may post ----
59
+
60
+ /**
61
+ * The canary that makes the container exist.
62
+ *
63
+ * Written without an access rule of its own, because the rule that matters is
64
+ * the container's and is stated separately — see setPosture, which a caller
65
+ * must reach for deliberately rather than inherit by accident.
66
+ */
67
+ export async function writeKeep(pod, urls) {
68
+ await pod.putJson(urls.inbox + '.keep', { keep: true }, 'application/json');
69
+ }
70
+
71
+ /**
72
+ * Who may deliver here.
73
+ *
74
+ * 'open' anyone may Append — an inbox the fediverse can post to
75
+ * 'closed' nobody may — a quiesced actor, still resolving, taking no mail
76
+ * { gatewayWebId } only that WebID may — mail arrives through a door that
77
+ * verifies it first, and nobody may go round the door
78
+ *
79
+ * One operation because the three are one decision, and they were made in five
80
+ * places: the profile publish, three separate methods, and the manage surface
81
+ * reaching past all of them to set the ACL directly. A posture set in one of
82
+ * those and not the others is an inbox that is open when it should be shut.
83
+ */
84
+ export async function setPosture(pod, urls, posture) {
85
+ if (posture === 'open') return pod.setAcl(urls.inbox, ['Append']);
86
+ if (posture === 'closed') return pod.setAcl(urls.inbox, []);
87
+ const webId = posture?.gatewayWebId;
88
+ if (!webId) throw new Error(`inbox.setPosture: unknown posture ${JSON.stringify(posture)}`);
89
+ // Public loses Append; the door keeps it. Both halves in one write, because
90
+ // between two writes the inbox is either open to everyone or shut to the door.
91
+ return pod.setAcl(urls.inbox, [], { appendAgents: [webId] });
92
+ }
93
+
94
+ /**
95
+ * What is waiting, oldest first.
96
+ *
97
+ * A defensive copy, because the transport hands back the SAME array on a 304
98
+ * and the inbox is polled constantly — a caller that sorted or spliced in
99
+ * place would corrupt the cache for every later reader.
100
+ */
101
+ export async function list(pod, urls) {
102
+ const children = await pod.listContainer(urls.inbox);
103
+ return children.map((c) => ({ ...c }));
104
+ }
105
+
106
+ /**
107
+ * One item's bytes.
108
+ *
109
+ * A pod that would not GIVE us the item has told us nothing about it. Reading
110
+ * a 500 as an empty body once made it "unparsable JSON" — which is a
111
+ * REJECTION, so the delivery was dead-lettered with nothing recorded and then
112
+ * DELETED: destroyed by a transient fault, with no trace of what it had been.
113
+ * Anything but 404 throws, and the caller's retry path takes it. 404 is the
114
+ * exception: the item is already gone, so removing it is right.
115
+ *
116
+ * Capped rather than read whole: the listing's size is advisory, the inbox is
117
+ * public-Append, and the body is whatever a stranger chose to send.
118
+ *
119
+ * @returns {Promise<{status:number, raw:string|null}>}
120
+ */
121
+ export async function readItem(pod, url, { maxBytes, readCapped }) {
122
+ const res = await pod.fetch(url, { headers: { accept: '*/*' } });
123
+ if (res.status >= 400 && res.status !== 404) throw new Error(`inbox item GET → ${res.status}`);
124
+ return { status: res.status, raw: res.status < 400 ? await readCapped(res, maxBytes) : null };
125
+ }
126
+
127
+ /** The gateway's receipt for an item, unparsed and unverified, or null. */
128
+ export async function readDeliveryReceipt(pod, itemUrl, { maxBytes, readCapped }) {
129
+ const res = await pod.fetch(itemUrl + '.receipt.json', { headers: { accept: 'application/json' } });
130
+ if (res.status >= 400) return null;
131
+ return JSON.parse(await readCapped(res, maxBytes));
132
+ }
133
+
134
+ /**
135
+ * Remove an item that has been dealt with.
136
+ *
137
+ * This must NOT know about the caller's state: the drain deletes only after
138
+ * its own state write has landed, and folding that gate in here would put the
139
+ * decision somewhere with no view of whether it did. Getting that wrong
140
+ * deletes deliveries whose record never persisted — silent, and unrecoverable.
141
+ *
142
+ * @returns {Promise<boolean>} whether the pod removed it
143
+ */
144
+ export const dropHandledItem = (pod, url) => pod.delete(url);
@@ -0,0 +1,35 @@
1
+ // links.mjs — reading RFC 8288 Link headers.
2
+ //
3
+ // Solid says where a resource's access control lives, where a storage
4
+ // describes itself, and who owns a storage, by putting a link on the response.
5
+ // Working any of those out from the resource's own URL instead is exactly what
6
+ // the specs tell clients not to do, so this is the one place that reads them.
7
+
8
+ /**
9
+ * Every target a Link header gives for one relation, resolved against the URL
10
+ * the header came from. A header may carry several links, and one link may
11
+ * carry several relation names.
12
+ */
13
+ export function linkTargets(headerValue, rel, baseUrl) {
14
+ if (!headerValue) return [];
15
+ const wanted = String(rel).toLowerCase();
16
+ const out = [];
17
+ // Split on the commas BETWEEN links: one inside a URI has its closing angle
18
+ // bracket still ahead of it, and is left alone.
19
+ for (const part of String(headerValue).split(/,(?![^<]*>)/u)) {
20
+ const link = /^\s*<([^>]*)>\s*(.*)$/u.exec(part);
21
+ if (!link) continue;
22
+ const relParam = /(?:^|;)\s*rel\s*=\s*(?:"([^"]*)"|([^;"\s]+))/iu.exec(link[2]);
23
+ const names = (relParam?.[1] ?? relParam?.[2] ?? '').toLowerCase().split(/\s+/u);
24
+ if (!names.includes(wanted)) continue;
25
+ try { out.push(new URL(link[1], baseUrl).href); } catch { /* not a URL we can follow */ }
26
+ }
27
+ return out;
28
+ }
29
+
30
+ /** The relations this library follows. */
31
+ export const REL = {
32
+ acl: 'acl',
33
+ storageDescription: 'http://www.w3.org/ns/solid/terms#storageDescription',
34
+ owner: 'http://www.w3.org/ns/solid/terms#owner',
35
+ };
@@ -0,0 +1,27 @@
1
+ // media.mjs — ap/media/: the bytes an attachment points at.
2
+ //
3
+ // The only tree here holding things that are not documents. Public-Read,
4
+ // because an attachment url travels inside a post to servers that will fetch
5
+ // it without credentials — and unlike an actor or a note, nothing checks that
6
+ // the url belongs to the identity that posted it, which is why media stays on
7
+ // the pod even for an identity whose ids are advertised elsewhere: proxying
8
+ // blobs would be pure cost for no verification gained.
9
+
10
+ const PUBLIC_READ = ['Read'];
11
+
12
+ // ---- the owner's agent ----
13
+
14
+ export async function provisionContainer(pod, urls) {
15
+ await pod.putJson(urls.media + '.keep', { keep: true }, 'application/json');
16
+ await pod.setAcl(urls.media, PUBLIC_READ);
17
+ }
18
+
19
+ /**
20
+ * One uploaded file.
21
+ *
22
+ * Takes the container as given: an upload into a container that was never
23
+ * provisioned is a 404, or worse a file nobody outside can read, so the caller
24
+ * ensures the container first — see provisionContainer, and the `exists` probe
25
+ * in containers.mjs that keeps a worker restart from re-provisioning.
26
+ */
27
+ export const write = (pod, url, bytes, contentType) => pod.put(url, bytes, contentType);
@@ -0,0 +1,80 @@
1
+ // notes.mjs — ap/notes/: the posts themselves, and the two documents each one
2
+ // drags with it.
3
+ //
4
+ // A published post is not one document. It is the Note, the Create activity
5
+ // that announced it, and a replies collection remote servers will dereference
6
+ // — and they have different lifetimes, which is the whole reason this file
7
+ // exists. Deleting a post REPLACES the Note with a Tombstone (a peer learns
8
+ // the post was deleted, rather than getting a bare 404 it reads as a temporary
9
+ // failure) while DELETING the Create outright, because the Create was an
10
+ // action that has been withdrawn rather than an object that still exists.
11
+ //
12
+ // The container is public-Read and the notes inherit it. The Tombstone is the
13
+ // exception that has to state its own rule: a private post lives in a
14
+ // different, owner-only container, so there is nothing public to inherit.
15
+
16
+ const PUBLIC_READ = ['Read'];
17
+
18
+ // ---- the owner's agent ----
19
+
20
+ /** The notes container itself, public-Read so everything under it inherits. */
21
+ export async function provisionContainer(pod, urls) {
22
+ await pod.putJson(urls.notes + '.keep', { keep: true }, 'application/json');
23
+ await pod.setAcl(urls.notes, PUBLIC_READ);
24
+ }
25
+
26
+ /** The Note. Inherits the container's rule; states none of its own. */
27
+ export const write = (pod, noteId, doc) => pod.putJson(noteId, doc);
28
+
29
+ /** The Create that announced it — its own document, at a derived id. */
30
+ export const writeCreate = (pod, createId, doc) => pod.putJson(createId, doc);
31
+
32
+ /**
33
+ * An empty replies collection, written with the note.
34
+ *
35
+ * Remote servers dereference `replies`, and a dangling pointer that 404s is
36
+ * worse than no pointer at all.
37
+ */
38
+ export const writeEmptyReplies = (pod, repliesId, doc) => pod.putJson(repliesId, doc);
39
+
40
+ /** The replies collection as it stands. */
41
+ export const readReplies = (pod, repliesId) => pod.getJson(repliesId);
42
+ export const writeReplies = (pod, repliesId, doc) => pod.putJson(repliesId, doc);
43
+
44
+ /** One published note, as the pod currently serves it. */
45
+ export const read = (pod, noteId) => pod.getJson(noteId);
46
+
47
+ /**
48
+ * Replace a note with a Tombstone, and keep it readable.
49
+ *
50
+ * The Read rule is stated explicitly here even though the public container
51
+ * grants it: a private or direct post lives in the owner-only container, and
52
+ * its Tombstone still has to be fetchable by the servers being told about the
53
+ * deletion.
54
+ */
55
+ export async function writeTombstone(pod, noteId, doc) {
56
+ await pod.putJson(noteId, doc);
57
+ await pod.setAcl(noteId, PUBLIC_READ);
58
+ }
59
+
60
+ /** Withdraw the Create. Returns whether the pod actually removed it. */
61
+ export const dropCreate = (pod, createId) => pod.delete(createId).catch(() => false);
62
+
63
+ /** The replies collection, once the note it belonged to is gone. */
64
+ export const dropReplies = (pod, repliesId) => pod.delete(repliesId).catch(() => {});
65
+
66
+ /**
67
+ * What the container holds, with the derived documents filtered out.
68
+ *
69
+ * A listing returns the Note, its `-create` and its `-replies` alike; only the
70
+ * first is a post, and handing the other two to a reader that expects posts
71
+ * produces failures that read like corruption.
72
+ */
73
+ export async function list(pod, urls) {
74
+ const children = await pod.listContainer(urls.notes);
75
+ return children
76
+ .filter((c) => !/(-create|-replies)$/.test(c.url) && !c.url.endsWith('.keep'))
77
+ // A defensive copy: the transport hands back the same array on a 304, so a
78
+ // caller that sorts in place would corrupt it for every later reader.
79
+ .map((c) => ({ ...c }));
80
+ }
@@ -0,0 +1,89 @@
1
+ // notifications.mjs — being told when the inbox changes, instead of asking.
2
+ //
3
+ // A pod that offers Solid Notifications lets a client open a socket and hear
4
+ // about a container the moment it changes, which is the difference between
5
+ // mail arriving and mail being noticed two minutes later. Discovering that
6
+ // service takes two unauthenticated reads of the pod's own description, and
7
+ // then one authenticated POST to subscribe.
8
+ //
9
+ // This is the ONLY module here that parses RDF itself, which is why it is its
10
+ // own file: a service that only ever appends to an inbox can import inbox.mjs
11
+ // without pulling a parser in behind it.
12
+
13
+ import * as $rdf from 'rdflib';
14
+ import { readCapped } from './http.mjs';
15
+ import { linkTargets, REL } from './links.mjs';
16
+
17
+ const RDF = $rdf.Namespace('http://www.w3.org/1999/02/22-rdf-syntax-ns#');
18
+ const NOTIFY = $rdf.Namespace('http://www.w3.org/ns/solid/notifications#');
19
+ const WS_CHANNEL = 'WebSocketChannel2023';
20
+
21
+ // ---- anyone at all: a pod describes its own services publicly ----
22
+
23
+ /**
24
+ * Where this pod describes the services it offers.
25
+ *
26
+ * The pod says so on any response about one of its resources; the well-known
27
+ * path is only what a pod that says nothing has always used. Unauthenticated
28
+ * on purpose — this is a public fact about the server, asked before there is
29
+ * anything to authenticate about.
30
+ */
31
+ export async function storageDescriptionUrl(podBase, { fetchImpl = fetch, headers = {}, timeoutMs = 20_000 } = {}) {
32
+ try {
33
+ const head = await fetchImpl(podBase, {
34
+ method: 'HEAD', headers, signal: AbortSignal.timeout(timeoutMs),
35
+ });
36
+ const [found] = linkTargets(head.headers.get('link'), REL.storageDescription, podBase);
37
+ if (found) return found;
38
+ } catch { /* the well-known path below */ }
39
+ return podBase + '.well-known/solid';
40
+ }
41
+
42
+ /**
43
+ * The websocket channel service this pod offers, or null when it offers none.
44
+ *
45
+ * Asked of rdflib rather than pattern-matched: the description is RDF, and the
46
+ * gap between "a string that appears in the document" and "the service this
47
+ * pod actually advertises" is where quiet bugs live. A pod may name the type
48
+ * on the service, or name the service's channelType — both are asked for.
49
+ */
50
+ export async function readWebSocketChannel(descUrl, { fetchImpl = fetch, headers = {}, timeoutMs = 20_000 } = {}) {
51
+ const res = await fetchImpl(descUrl, {
52
+ headers: { accept: 'text/turtle', ...headers },
53
+ signal: AbortSignal.timeout(timeoutMs),
54
+ });
55
+ const g = $rdf.graph();
56
+ try {
57
+ $rdf.parse(await readCapped(res), g, descUrl, 'text/turtle');
58
+ } catch (e) {
59
+ return { channel: null, error: `service description unparsable (${e.message})` };
60
+ }
61
+ const channel = g.each(null, RDF('type'), NOTIFY(WS_CHANNEL), null).map((n) => n.value).find(Boolean)
62
+ || g.each(null, NOTIFY('channelType'), NOTIFY(WS_CHANNEL), null).map((n) => n.value).find(Boolean);
63
+ return { channel: channel || null, error: channel ? null : `no ${WS_CHANNEL} service` };
64
+ }
65
+
66
+ // ---- the owner's agent ----
67
+
68
+ /**
69
+ * Subscribe to changes on the inbox.
70
+ *
71
+ * `podTopicUrl` is POD-SPACE, and the parameter is named that way because
72
+ * getting it wrong fails in a way nothing else here does: the topic travels in
73
+ * the BODY, so the advertised-to-pod url map a transport applies to the
74
+ * request LINE never reaches it. An identity whose ids are advertised
75
+ * elsewhere would name a container the pod cannot grant read on, and the
76
+ * subscription comes back 403.
77
+ */
78
+ export async function subscribeToInbox(pod, { channelUrl, podTopicUrl, ...rest }) {
79
+ return pod.fetch(channelUrl, {
80
+ method: 'POST',
81
+ headers: { 'content-type': 'application/ld+json' },
82
+ body: JSON.stringify({
83
+ '@context': ['https://www.w3.org/ns/solid/notification/v1'],
84
+ type: `http://www.w3.org/ns/solid/notifications#${WS_CHANNEL}`,
85
+ topic: podTopicUrl,
86
+ ...rest,
87
+ }),
88
+ });
89
+ }
@@ -0,0 +1,18 @@
1
+ // outbox.mjs — ap/outbox: everything this actor has published, paged.
2
+ //
3
+ // Public by definition, and read by strangers rather than by us: a remote
4
+ // server walks it to backfill an account it has just met. It is also what a
5
+ // rebuild reads to recover posts a lost machine no longer has, which is why
6
+ // readPublished tolerates the flat shape this project used to write.
7
+
8
+ import * as collection from './collection.mjs';
9
+
10
+ // ---- the owner's agent ----
11
+
12
+ export const writePage = (pod, pageUrl, doc, opts) => collection.writePage(pod, pageUrl, doc, opts);
13
+ export const writeHead = (pod, urls, doc, opts) => collection.writeHead(pod, urls.outbox, doc, opts);
14
+ export const dropPage = (pod, pageUrl) => collection.dropPage(pod, pageUrl);
15
+
16
+ /** Every activity the pod currently publishes, or null if it publishes none. */
17
+ export const readPublished = (pod, urls) =>
18
+ collection.readPaged(pod, urls.outbox, { max: 10_000 });
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "solid-ap-pod",
3
+ "version": "0.1.0",
4
+ "description": "Read and write an ActivityPub actor's documents on a Solid pod: the container layout, an injected-session transport, WAC access control, and one module per pod resource.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "exports": {
8
+ "./urls.mjs": "./urls.mjs",
9
+ "./transport.mjs": "./transport.mjs",
10
+ "./http.mjs": "./http.mjs",
11
+ "./links.mjs": "./links.mjs"
12
+ },
13
+ "dependencies": {
14
+ "rdflib": "^2.4.0"
15
+ },
16
+ "engines": {
17
+ "node": ">=20"
18
+ }
19
+ }
@@ -0,0 +1,58 @@
1
+ // policy.mjs — ap/gateway-policy.json: what the pod's owner has published
2
+ // about which mail they want.
3
+ //
4
+ // The point of the document is that a service filtering on someone's behalf
5
+ // reads their stated wishes from their own pod rather than holding a private
6
+ // copy of them. So the reader here is deliberately unauthenticated: the
7
+ // document is public, and a service that needed a credential to read it would
8
+ // be holding something about the person it should not have to.
9
+
10
+ import { readCapped } from './http.mjs';
11
+
12
+ const POLICY_TTL_MS = 5 * 60_000;
13
+ const POLICY_MAX_BYTES = 256 * 1024;
14
+
15
+ /**
16
+ * The published policy, or null when there is none to read.
17
+ *
18
+ * The cache is passed IN rather than kept here. A library that holds
19
+ * process-global state decides its own lifetime, and the lifetime that matters
20
+ * belongs to the service: on a warm serverless container one Map is shared
21
+ * across every user it serves, so what it is keyed by is a correctness
22
+ * property, not an optimisation. Keyed by `podHome`, it is one person's policy
23
+ * under one person's key.
24
+ *
25
+ * Cached at all because a delivery flood must not become one read per delivery
26
+ * against the person's pod — which is the load the whole arrangement exists to
27
+ * spare them.
28
+ *
29
+ * ---- asked by: a delivery gateway, deciding what concerns this person ----
30
+ */
31
+ export async function read(fetchImpl, podHome, { cache = null, ttlMs = POLICY_TTL_MS } = {}) {
32
+ const hit = cache?.get(podHome);
33
+ if (hit && Date.now() - hit.at < ttlMs) return hit.policy;
34
+ let policy = null;
35
+ try {
36
+ const res = await fetchImpl(podHome + 'ap/gateway-policy.json',
37
+ { headers: { accept: 'application/json' } });
38
+ if (res && res.status < 400) policy = JSON.parse(await readCapped(res, POLICY_MAX_BYTES));
39
+ } catch { /* unpublished or unreachable: the caller's own fields stand */ }
40
+ cache?.set(podHome, { at: Date.now(), policy });
41
+ return policy;
42
+ }
43
+
44
+ // ---- the owner's agent ----
45
+
46
+ /**
47
+ * Publish the policy.
48
+ *
49
+ * Public-Read on purpose, and the only document in this library written
50
+ * public specifically so that a service holding no credential can act on the
51
+ * owner's behalf. What goes IN it — which follows count, which blocks — is the
52
+ * caller's to decide; that it is readable is the point of it existing.
53
+ */
54
+ export async function write(pod, urls, doc) {
55
+ const url = urls.home + 'ap/gateway-policy.json';
56
+ await pod.putJson(url, doc, 'application/json');
57
+ await pod.setAcl(url, ['Read']);
58
+ }
@@ -0,0 +1,19 @@
1
+ // private.mjs — ap/private/: the collections that are nobody else's business.
2
+ //
3
+ // pending-followers and pending-following are FEP-4ccd's follows in limbo;
4
+ // blocked is FEP-c648's block list. All three live in the private container so
5
+ // the owner-only rule is INHERITED rather than re-stated per document — which
6
+ // is why none of these writes sets an ACL of its own, and why writing one into
7
+ // the wrong container would silently publish it.
8
+
9
+ import * as collection from './collection.mjs';
10
+
11
+ // ---- the owner's agent ----
12
+
13
+ export async function writePending(pod, urls, { followers, following }) {
14
+ await collection.writeFlat(pod, urls.pendingFollowers, followers);
15
+ await collection.writeFlat(pod, urls.pendingFollowing, following);
16
+ }
17
+
18
+ export const writeBlocked = (pod, urls, doc) =>
19
+ collection.writeFlat(pod, urls.blocked, doc);
@@ -0,0 +1,63 @@
1
+ // root.mjs — the pod itself, before any of its containers.
2
+ //
3
+ // Three questions get asked of a pod root rather than of a document in it: who
4
+ // owns it, whether it is there at all, and — for a service publishing someone
5
+ // else's pod on their behalf — what one of its public documents says.
6
+ //
7
+ // Every operation here takes a plain fetcher, not a transport. That is the
8
+ // honest shape: these are asked by parties that hold no credential for the pod
9
+ // — a keyless delivery gateway, a sign-up page looking at a pod the person
10
+ // brought with them — and giving them an authenticated transport would let
11
+ // them ask questions they have no business asking.
12
+
13
+ import { readCapped } from './http.mjs';
14
+ import { linkTargets, REL } from './links.mjs';
15
+
16
+ const OWNER_LOOKUP_MS = 5_000;
17
+ const PUBLIC_DOC_MAX_BYTES = 1024 * 1024;
18
+
19
+ /**
20
+ * Who the pod server says owns this pod.
21
+ *
22
+ * The server hosting it is the authority on that, so when it answers, its
23
+ * answer decides. A server that says nothing leaves where the WebID lives as
24
+ * the only evidence there is — which is weaker, and the caller's problem.
25
+ *
26
+ * ---- asked by: a service deciding whether someone may speak for a pod ----
27
+ */
28
+ export async function readOwnerLinks(fetchImpl, podBase, { timeoutMs = OWNER_LOOKUP_MS } = {}) {
29
+ try {
30
+ const res = await fetchImpl(podBase, {
31
+ method: 'HEAD', signal: AbortSignal.timeout(timeoutMs),
32
+ });
33
+ return linkTargets(res?.headers?.get?.('link'), REL.owner, podBase);
34
+ } catch { return []; }
35
+ }
36
+
37
+ /**
38
+ * Does anything answer at this pod?
39
+ *
40
+ * ---- asked by: a provisioning client, about a pod the person brought ----
41
+ */
42
+ export async function probeAnswers(podUrl, fetchImpl = fetch) {
43
+ try {
44
+ const res = await fetchImpl(podUrl, { method: 'HEAD' });
45
+ return { ok: res.status < 400, status: res.status };
46
+ } catch (e) { return { ok: false, status: 0, error: e.message }; }
47
+ }
48
+
49
+ /**
50
+ * One public document, read on behalf of someone who is not its owner.
51
+ *
52
+ * `getter` is how the read happens, which is not always a network fetch: a
53
+ * component running inside the pod server reads its own store directly and has
54
+ * no access control of its own, so the pod this read belongs to travels with
55
+ * the request and the getter is told what it may reach.
56
+ *
57
+ * ---- asked by: a front publishing a pod's public face under its own name ----
58
+ */
59
+ export async function readPublicDocument(getter, podTarget, { podHome, maxBytes = PUBLIC_DOC_MAX_BYTES } = {}) {
60
+ const res = await getter(podTarget, { podHome });
61
+ if (!res || res.status >= 400) return { status: res?.status || 502, text: null };
62
+ return { status: 200, text: await readCapped(res, maxBytes) };
63
+ }
@@ -0,0 +1,39 @@
1
+ // state.mjs — ap-state/: what the agent knows, kept where the agent is not.
2
+ //
3
+ // A browser has no disk you can carry to the next machine, so the durable copy
4
+ // of an identity's state lives on its pod. Most of that tree is handled by a
5
+ // caching store; what is here is the two documents read and written OUTSIDE
6
+ // it, before there is a store to read them with.
7
+ //
8
+ // The container is owner-only, and every operation here assumes that has
9
+ // already been established — see provisionKey, which is the one place that
10
+ // establishes it, and does so in an order that is not negotiable.
11
+
12
+ // ---- the owner's agent ----
13
+
14
+ export const readWrappedKeys = (pod, urls) => pod.getJson(urls.state + 'keys.json');
15
+ export const readConfig = (pod, urls) => pod.getJson(urls.state + 'config.json');
16
+
17
+ export const writeWrappedKeys = (pod, urls, envelope) =>
18
+ pod.putJson(urls.state + 'keys.json', envelope, 'application/json');
19
+ export const writeConfig = (pod, urls, config) =>
20
+ pod.putJson(urls.state + 'config.json', config, 'application/json');
21
+
22
+ // ---- a provisioning client, setting an identity up for the first time ----
23
+
24
+ /**
25
+ * Lock the state container, THEN write the signing key into it.
26
+ *
27
+ * The order is the operation. Writing the key first leaves a window in which a
28
+ * pod whose root is world-readable serves it to anyone who asks — and a pod
29
+ * the person brought with them is exactly the case where that root may be
30
+ * public. As two statements this was two chances to get it wrong; as one
31
+ * operation there is nowhere to put them in the other order.
32
+ *
33
+ * The envelope is built by the caller. This library does not choose how a key
34
+ * is wrapped, and should not be able to write an unwrapped one by accident.
35
+ */
36
+ export async function provisionKey(pod, { stateUrl, keysUrl, envelope }) {
37
+ await pod.setAcl(stateUrl, []);
38
+ await pod.putJson(keysUrl, envelope, 'application/json');
39
+ }