fedipod-server 0.29.0 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (440) hide show
  1. package/README.md +11 -6
  2. package/dist/door.d.ts +4 -0
  3. package/dist/door.js +31 -0
  4. package/dist/fedipod.d.ts +16 -0
  5. package/dist/fedipod.js +13 -0
  6. package/dist/front.d.ts +3 -0
  7. package/dist/front.js +94 -0
  8. package/dist/handler.d.ts +36 -101
  9. package/dist/handler.js +85 -645
  10. package/dist/handler.jsonld +2 -30
  11. package/dist/identities.d.ts +62 -0
  12. package/dist/identities.js +236 -0
  13. package/dist/mounts.d.ts +43 -0
  14. package/dist/mounts.js +123 -0
  15. package/dist/opt-in.d.ts +46 -0
  16. package/dist/opt-in.js +176 -0
  17. package/package.json +10 -18
  18. package/lib/client/c2s.mjs +0 -636
  19. package/lib/client/localapi.mjs +0 -87
  20. package/lib/client/masto/accounts.mjs +0 -310
  21. package/lib/client/masto/body.mjs +0 -156
  22. package/lib/client/masto/index.mjs +0 -206
  23. package/lib/client/masto/instance.mjs +0 -112
  24. package/lib/client/masto/media.mjs +0 -79
  25. package/lib/client/masto/oauth.mjs +0 -656
  26. package/lib/client/masto/render.mjs +0 -518
  27. package/lib/client/masto/statuses.mjs +0 -372
  28. package/lib/client/masto/timelines.mjs +0 -438
  29. package/lib/client/oidc-auth.mjs +0 -62
  30. package/lib/client/streaming.mjs +0 -154
  31. package/lib/client/webpush.mjs +0 -98
  32. package/lib/connections/acctfeed.mjs +0 -257
  33. package/lib/connections/atproto.mjs +0 -309
  34. package/lib/connections/bskyfeed.mjs +0 -243
  35. package/lib/connections/bskygroup.mjs +0 -162
  36. package/lib/connections/fediacct.mjs +0 -253
  37. package/lib/connections/import.mjs +0 -357
  38. package/lib/connections/tagfeed.mjs +0 -212
  39. package/lib/connections/vault.mjs +0 -114
  40. package/lib/core/as2.mjs +0 -206
  41. package/lib/core/contexts/activitystreams.json +0 -379
  42. package/lib/core/contexts/anno.json +0 -126
  43. package/lib/core/contexts/did-v1.json +0 -57
  44. package/lib/core/contexts/fep-5711.json +0 -36
  45. package/lib/core/contexts/gotosocial.json +0 -86
  46. package/lib/core/contexts/identity-v1.json +0 -152
  47. package/lib/core/contexts/index.mjs +0 -63
  48. package/lib/core/contexts/join-lemmy.json +0 -33
  49. package/lib/core/contexts/joinmastodon.json +0 -28
  50. package/lib/core/contexts/litepub-0.1.json +0 -60
  51. package/lib/core/contexts/map.json +0 -19
  52. package/lib/core/contexts/miscellany.json +0 -19
  53. package/lib/core/contexts/quotes.json +0 -18
  54. package/lib/core/contexts/schemaorg.json +0 -8845
  55. package/lib/core/contexts/security-data-integrity-v1.json +0 -78
  56. package/lib/core/contexts/security-data-integrity-v2.json +0 -81
  57. package/lib/core/contexts/security-multikey-v1.json +0 -35
  58. package/lib/core/contexts/security-v1.json +0 -74
  59. package/lib/core/contexts/webfinger.json +0 -10
  60. package/lib/core/deliver.mjs +0 -387
  61. package/lib/core/graphview.mjs +0 -327
  62. package/lib/core/intake/activities.mjs +0 -649
  63. package/lib/core/intake/activity.mjs +0 -318
  64. package/lib/core/intake/channel.mjs +0 -144
  65. package/lib/core/intake/group.mjs +0 -258
  66. package/lib/core/intake/index.mjs +0 -732
  67. package/lib/core/intake/notes.mjs +0 -367
  68. package/lib/core/intake/verify.mjs +0 -142
  69. package/lib/core/keys.mjs +0 -153
  70. package/lib/core/lease.mjs +0 -206
  71. package/lib/core/place.mjs +0 -82
  72. package/lib/core/polls.mjs +0 -105
  73. package/lib/core/profile-page.mjs +0 -104
  74. package/lib/core/proof.mjs +0 -79
  75. package/lib/core/publisher/collections.mjs +0 -272
  76. package/lib/core/publisher/index.mjs +0 -522
  77. package/lib/core/publisher/notes.mjs +0 -519
  78. package/lib/core/publisher/own.mjs +0 -143
  79. package/lib/core/publisher/questions.mjs +0 -241
  80. package/lib/core/publisher/restore.mjs +0 -224
  81. package/lib/core/shapes/activitystreams.ttl +0 -157
  82. package/lib/core/shapes/index.mjs +0 -132
  83. package/lib/core/shapes/shapes-text.mjs +0 -13
  84. package/lib/core/social.mjs +0 -561
  85. package/lib/core/storage.mjs +0 -176
  86. package/lib/core/store.mjs +0 -578
  87. package/lib/core/wire-quotes.mjs +0 -71
  88. package/lib/core/wire.mjs +0 -925
  89. package/lib/device/account.mjs +0 -87
  90. package/lib/device/admin/index.mjs +0 -13
  91. package/lib/device/admin/origins.mjs +0 -35
  92. package/lib/device/admin/routes/connections.mjs +0 -144
  93. package/lib/device/admin/routes/gateway.mjs +0 -199
  94. package/lib/device/admin/routes/lifecycle.mjs +0 -200
  95. package/lib/device/admin/routes/owner.mjs +0 -322
  96. package/lib/device/admin/routes/setup.mjs +0 -428
  97. package/lib/device/admin/routes/social.mjs +0 -188
  98. package/lib/device/admin/server.mjs +0 -95
  99. package/lib/device/admin/static.mjs +0 -247
  100. package/lib/device/admin/surface.mjs +0 -315
  101. package/lib/device/certs.mjs +0 -281
  102. package/lib/device/cli/commands/account.mjs +0 -586
  103. package/lib/device/cli/commands/run.mjs +0 -278
  104. package/lib/device/cli/commands/service.mjs +0 -221
  105. package/lib/device/cli/commands/setup.mjs +0 -489
  106. package/lib/device/cli/commands/state.mjs +0 -561
  107. package/lib/device/cli/context.mjs +0 -288
  108. package/lib/device/export-collections.mjs +0 -161
  109. package/lib/device/gateway-move.mjs +0 -58
  110. package/lib/device/home.mjs +0 -161
  111. package/lib/device/migrate.mjs +0 -115
  112. package/lib/device/ports.mjs +0 -27
  113. package/lib/device/remote.mjs +0 -54
  114. package/lib/device/setup.mjs +0 -496
  115. package/lib/device/update.mjs +0 -96
  116. package/lib/gateway/caches.mjs +0 -36
  117. package/lib/gateway/directory.mjs +0 -90
  118. package/lib/gateway/front-core.mjs +0 -966
  119. package/lib/gateway/gateway-core.mjs +0 -244
  120. package/lib/gateway/headers.mjs +0 -49
  121. package/lib/gateway/httpsig.mjs +0 -159
  122. package/lib/gateway/notices.mjs +0 -84
  123. package/lib/gateway/quiet.mjs +0 -189
  124. package/lib/gateway/relay-extras.mjs +0 -89
  125. package/lib/gateway/token-claims.mjs +0 -16
  126. package/lib/pod/README.md +0 -61
  127. package/lib/pod/actor.mjs +0 -96
  128. package/lib/pod/collection.mjs +0 -77
  129. package/lib/pod/containers.mjs +0 -140
  130. package/lib/pod/discovery.mjs +0 -46
  131. package/lib/pod/featured.mjs +0 -18
  132. package/lib/pod/followers.mjs +0 -24
  133. package/lib/pod/following.mjs +0 -10
  134. package/lib/pod/http.mjs +0 -74
  135. package/lib/pod/inbox.mjs +0 -166
  136. package/lib/pod/links.mjs +0 -35
  137. package/lib/pod/location.mjs +0 -52
  138. package/lib/pod/media.mjs +0 -27
  139. package/lib/pod/notes.mjs +0 -78
  140. package/lib/pod/notifications.mjs +0 -89
  141. package/lib/pod/outbox.mjs +0 -18
  142. package/lib/pod/package.json +0 -19
  143. package/lib/pod/policy.mjs +0 -58
  144. package/lib/pod/private.mjs +0 -19
  145. package/lib/pod/root.mjs +0 -104
  146. package/lib/pod/state.mjs +0 -40
  147. package/lib/pod/transport.mjs +0 -674
  148. package/lib/pod/type-index.mjs +0 -101
  149. package/lib/pod/urls.mjs +0 -91
  150. package/lib/server/embed.mjs +0 -424
  151. package/lib/session/README.md +0 -137
  152. package/lib/session/demo.html +0 -94
  153. package/lib/session/fedi-account.mjs +0 -417
  154. package/lib/session/fedi-login.mjs +0 -123
  155. package/lib/session/oidc-session.mjs +0 -233
  156. package/lib/session/package.json +0 -49
  157. package/lib/shared/guard.mjs +0 -213
  158. package/lib/shared/links.mjs +0 -9
  159. package/lib/shared/safefetch.mjs +0 -235
  160. package/lib/shared/ua.mjs +0 -29
  161. package/phanpy/dist/404.html +0 -40
  162. package/phanpy/dist/_headers +0 -2
  163. package/phanpy/dist/apple-touch-icon.png +0 -0
  164. package/phanpy/dist/assets/ICONS-DIPJeU0C.js +0 -2
  165. package/phanpy/dist/assets/ICONS-DIPJeU0C.js.map +0 -1
  166. package/phanpy/dist/assets/ScheduledAtField-D-Abfo4J.js +0 -183
  167. package/phanpy/dist/assets/ScheduledAtField-D-Abfo4J.js.map +0 -1
  168. package/phanpy/dist/assets/Temml-CCjoacWt.woff2 +0 -0
  169. package/phanpy/dist/assets/__vite-browser-external-C7iut881.js +0 -2
  170. package/phanpy/dist/assets/__vite-browser-external-C7iut881.js.map +0 -1
  171. package/phanpy/dist/assets/boosts-carousel-BiOaNdMT.jpg +0 -0
  172. package/phanpy/dist/assets/catch-up-O5q5C75Z.png +0 -0
  173. package/phanpy/dist/assets/chunk-aKtaBQYM.js +0 -1
  174. package/phanpy/dist/assets/compose-CFEx7wb4.js +0 -2
  175. package/phanpy/dist/assets/compose-CFEx7wb4.js.map +0 -1
  176. package/phanpy/dist/assets/compose-ZJN5xSb4.js +0 -35
  177. package/phanpy/dist/assets/compose-ZJN5xSb4.js.map +0 -1
  178. package/phanpy/dist/assets/debug.module-YFQgwnIr.js +0 -52
  179. package/phanpy/dist/assets/debug.module-YFQgwnIr.js.map +0 -1
  180. package/phanpy/dist/assets/dom-p1YcJ5Rw.js +0 -2
  181. package/phanpy/dist/assets/dom-p1YcJ5Rw.js.map +0 -1
  182. package/phanpy/dist/assets/home-mobile-dark@2x-Dc-J8-SM.png +0 -0
  183. package/phanpy/dist/assets/home-mobile-light@2x-Bd3fod8m.png +0 -0
  184. package/phanpy/dist/assets/home-tablet-dark@2x-BgcNI8le.png +0 -0
  185. package/phanpy/dist/assets/home-tablet-light@2x-BW2RV9T6.png +0 -0
  186. package/phanpy/dist/assets/icons/add-circle-line-WD0mwORD.js +0 -1
  187. package/phanpy/dist/assets/icons/alert-line-CpFUMf9s.js +0 -1
  188. package/phanpy/dist/assets/icons/android-2-line-Bzxnrd3S.js +0 -1
  189. package/phanpy/dist/assets/icons/announcement-line-5nwaJFJm.js +0 -1
  190. package/phanpy/dist/assets/icons/arrow-down-circle-line-a7Id9-Iv.js +0 -1
  191. package/phanpy/dist/assets/icons/arrow-down-line-B8uzXzq6.js +0 -1
  192. package/phanpy/dist/assets/icons/arrow-left-line-CGrGT9C-.js +0 -1
  193. package/phanpy/dist/assets/icons/arrow-right-line-CE-HERfX.js +0 -1
  194. package/phanpy/dist/assets/icons/arrow-to-up-line-CvdQjmcy.js +0 -1
  195. package/phanpy/dist/assets/icons/arrow-up-circle-line-DEparSNF.js +0 -1
  196. package/phanpy/dist/assets/icons/arrow-up-line-C7yBeugc.js +0 -1
  197. package/phanpy/dist/assets/icons/arrows-right-line-BtbXmJGP.js +0 -1
  198. package/phanpy/dist/assets/icons/at-line-CF66Bznk.js +0 -1
  199. package/phanpy/dist/assets/icons/attachment-line-BY8It-l9.js +0 -1
  200. package/phanpy/dist/assets/icons/blockquote-line-BS6pU_hm.js +0 -1
  201. package/phanpy/dist/assets/icons/board-line-C0UWXyA6.js +0 -1
  202. package/phanpy/dist/assets/icons/bookmark-line-1CtX5JLF.js +0 -1
  203. package/phanpy/dist/assets/icons/building-5-line-Dd59MzbO.js +0 -1
  204. package/phanpy/dist/assets/icons/bus-2-line-6_dvA0GG.js +0 -1
  205. package/phanpy/dist/assets/icons/calendar-day-line-Crpl_fmu.js +0 -1
  206. package/phanpy/dist/assets/icons/calendar-month-line-vommts2C.js +0 -1
  207. package/phanpy/dist/assets/icons/calendar-time-add-line-BU_DTlQk.js +0 -1
  208. package/phanpy/dist/assets/icons/camera-line-Cv69MEei.js +0 -1
  209. package/phanpy/dist/assets/icons/celebrate-line-B8DvIoi0.js +0 -1
  210. package/phanpy/dist/assets/icons/chart-bar-line-FK-QOmtF.js +0 -1
  211. package/phanpy/dist/assets/icons/chart-line-line-BGcJAFWw.js +0 -1
  212. package/phanpy/dist/assets/icons/chat-3-line-YFK2-hwz.js +0 -1
  213. package/phanpy/dist/assets/icons/check-circle-line-DsAxFr5-.js +0 -1
  214. package/phanpy/dist/assets/icons/clipboard-line-BBp364gJ.js +0 -1
  215. package/phanpy/dist/assets/icons/close-circle-line-CICdiuGz.js +0 -1
  216. package/phanpy/dist/assets/icons/close-line-DYrFWcjv.js +0 -1
  217. package/phanpy/dist/assets/icons/cloud-line-tiQAbah8.js +0 -1
  218. package/phanpy/dist/assets/icons/code-line-fAl8iiV3.js +0 -1
  219. package/phanpy/dist/assets/icons/comment-2-line-CR3g7WeB.js +0 -1
  220. package/phanpy/dist/assets/icons/copy-2-line-CLW69yF5.js +0 -1
  221. package/phanpy/dist/assets/icons/delete-2-line-B8xmbr4n.js +0 -1
  222. package/phanpy/dist/assets/icons/document-3-line-DBGNhAg2.js +0 -1
  223. package/phanpy/dist/assets/icons/document-line-C7mb9sb7.js +0 -1
  224. package/phanpy/dist/assets/icons/down-line-BGqSSThd.js +0 -1
  225. package/phanpy/dist/assets/icons/edit-4-line-DcgGaEhx.js +0 -1
  226. package/phanpy/dist/assets/icons/emoji-2-line-D8YgnM-y.js +0 -1
  227. package/phanpy/dist/assets/icons/exit-line-Dc07BS-x.js +0 -1
  228. package/phanpy/dist/assets/icons/external-link-line-BvpSkmjO.js +0 -1
  229. package/phanpy/dist/assets/icons/eye-2-line-CiWXx-f_.js +0 -1
  230. package/phanpy/dist/assets/icons/eye-close-line-BgTaSJ4P.js +0 -1
  231. package/phanpy/dist/assets/icons/filter-2-line-B_mR2usy.js +0 -1
  232. package/phanpy/dist/assets/icons/filter-line-C_nm-3cU.js +0 -1
  233. package/phanpy/dist/assets/icons/flag-1-line-C2HB05I0.js +0 -1
  234. package/phanpy/dist/assets/icons/forbid-circle-line-D3LTT9yd.js +0 -1
  235. package/phanpy/dist/assets/icons/formula-line-DLrt2d6j.js +0 -1
  236. package/phanpy/dist/assets/icons/grid-line-CgUmEikf.js +0 -1
  237. package/phanpy/dist/assets/icons/group-line-C5fnX0wU.js +0 -1
  238. package/phanpy/dist/assets/icons/hand-finger-2-line-BGgxbuL9.js +0 -1
  239. package/phanpy/dist/assets/icons/hashtag-line-CyTEEXBb.js +0 -1
  240. package/phanpy/dist/assets/icons/heart-crack-line-BZddbO2C.js +0 -1
  241. package/phanpy/dist/assets/icons/heart-line-CJ7K47J4.js +0 -1
  242. package/phanpy/dist/assets/icons/history-2-line-kWWvFzqv.js +0 -1
  243. package/phanpy/dist/assets/icons/history-line-bGQiwbTH.js +0 -1
  244. package/phanpy/dist/assets/icons/home-3-line-fRU1zeLG.js +0 -1
  245. package/phanpy/dist/assets/icons/information-line-iNk6-sOY.js +0 -1
  246. package/phanpy/dist/assets/icons/keyboard-line-DsCyuQNz.js +0 -1
  247. package/phanpy/dist/assets/icons/layout-4-line-CZkOX52Z.js +0 -1
  248. package/phanpy/dist/assets/icons/layout-5-line-B_znt5wO.js +0 -1
  249. package/phanpy/dist/assets/icons/left-line-eUw0tya4.js +0 -1
  250. package/phanpy/dist/assets/icons/lightning-line-DaKoDdix.js +0 -1
  251. package/phanpy/dist/assets/icons/link-2-line-DCqLmMA7.js +0 -1
  252. package/phanpy/dist/assets/icons/list-check-line-BAxosBh9.js +0 -1
  253. package/phanpy/dist/assets/icons/lock-line-CPoaXeUW.js +0 -1
  254. package/phanpy/dist/assets/icons/mail-line-B0P8aa1L.js +0 -1
  255. package/phanpy/dist/assets/icons/moon-line-CEqNAToP.js +0 -1
  256. package/phanpy/dist/assets/icons/more-1-fill-Dw2e5xab.js +0 -1
  257. package/phanpy/dist/assets/icons/more-3-line-BIiVjYTz.js +0 -1
  258. package/phanpy/dist/assets/icons/notification-line-BZZq2Gtu.js +0 -1
  259. package/phanpy/dist/assets/icons/pencil-line-CKh8-A1v.js +0 -1
  260. package/phanpy/dist/assets/icons/photo-album-line-Bnfg2u40.js +0 -1
  261. package/phanpy/dist/assets/icons/pin-line-DpTolyhs.js +0 -1
  262. package/phanpy/dist/assets/icons/play-fill-B0-G_8lX.js +0 -1
  263. package/phanpy/dist/assets/icons/qrcode-2-line-B6SPCaHn.js +0 -1
  264. package/phanpy/dist/assets/icons/quill-pen-line-B6LUrnCP.js +0 -1
  265. package/phanpy/dist/assets/icons/quote-left-fill-hQZ1cj_6.js +0 -1
  266. package/phanpy/dist/assets/icons/radar-line-CKyroTth.js +0 -1
  267. package/phanpy/dist/assets/icons/react-line-CoIKhhtK.js +0 -1
  268. package/phanpy/dist/assets/icons/refresh-2-line-D0mTF97S.js +0 -1
  269. package/phanpy/dist/assets/icons/right-line-B3KWJaqc.js +0 -1
  270. package/phanpy/dist/assets/icons/rocket-line-4KZl537D.js +0 -1
  271. package/phanpy/dist/assets/icons/round-fill-C69NTrZ0.js +0 -1
  272. package/phanpy/dist/assets/icons/round-line-B9k_1uo-.js +0 -1
  273. package/phanpy/dist/assets/icons/route-line-B28ReHcE.js +0 -1
  274. package/phanpy/dist/assets/icons/rows-4-line-CrBniagM.js +0 -1
  275. package/phanpy/dist/assets/icons/scan-line-DSf74YdP.js +0 -1
  276. package/phanpy/dist/assets/icons/search-2-line-B6geIU1z.js +0 -1
  277. package/phanpy/dist/assets/icons/settings-3-line-azJwK47w.js +0 -1
  278. package/phanpy/dist/assets/icons/settings-6-line-C_5zc5YR.js +0 -1
  279. package/phanpy/dist/assets/icons/share-2-line-D5MxWjrm.js +0 -1
  280. package/phanpy/dist/assets/icons/share-forward-line-Cp61fZzZ.js +0 -1
  281. package/phanpy/dist/assets/icons/sparkles-2-line-DYYyXTaW.js +0 -1
  282. package/phanpy/dist/assets/icons/sparkles-line-C2hY1s6I.js +0 -1
  283. package/phanpy/dist/assets/icons/time-line-BF3eet1v.js +0 -1
  284. package/phanpy/dist/assets/icons/transfer-4-line-oisfvTBw.js +0 -1
  285. package/phanpy/dist/assets/icons/translate-line-BFWol2Ce.js +0 -1
  286. package/phanpy/dist/assets/icons/unlock-line-X_x5vwv3.js +0 -1
  287. package/phanpy/dist/assets/icons/upload-3-line-DgzwUJeW.js +0 -1
  288. package/phanpy/dist/assets/icons/user-4-line-BVSynZCp.js +0 -1
  289. package/phanpy/dist/assets/icons/user-add-2-line-BlGS5Q8q.js +0 -1
  290. package/phanpy/dist/assets/icons/user-add-line-BbXdHG4z.js +0 -1
  291. package/phanpy/dist/assets/icons/user-edit-line-BFAHOV0X.js +0 -1
  292. package/phanpy/dist/assets/icons/user-follow-line-BXux0wSd.js +0 -1
  293. package/phanpy/dist/assets/icons/user-star-line-DdYlpHLU.js +0 -1
  294. package/phanpy/dist/assets/icons/user-warning-line-Cj43j0_E.js +0 -1
  295. package/phanpy/dist/assets/icons/user-x-line-DIDgNlPH.js +0 -1
  296. package/phanpy/dist/assets/icons/volume-line-Bs390KQt.js +0 -1
  297. package/phanpy/dist/assets/icons/volume-mute-line-DzlzIqFZ.js +0 -1
  298. package/phanpy/dist/assets/icons/walk-line-yNXfB-md.js +0 -1
  299. package/phanpy/dist/assets/icons/world-2-line-CenkGIwj.js +0 -1
  300. package/phanpy/dist/assets/icons/zoom-in-line-CaENUphf.js +0 -1
  301. package/phanpy/dist/assets/icons/zoom-out-line-BDNk0EWo.js +0 -1
  302. package/phanpy/dist/assets/instances-BNPptnn-.json +0 -333
  303. package/phanpy/dist/assets/locales/ar-SA-CfheRbCu.js +0 -1
  304. package/phanpy/dist/assets/locales/ca-ES-DHRW86Tz.js +0 -1
  305. package/phanpy/dist/assets/locales/cs-CZ-CKoqEuA1.js +0 -1
  306. package/phanpy/dist/assets/locales/de-DE-ByRPMRFy.js +0 -1
  307. package/phanpy/dist/assets/locales/eo-UY-CPC7wLCS.js +0 -1
  308. package/phanpy/dist/assets/locales/es-ES-dJa0Fd9t.js +0 -1
  309. package/phanpy/dist/assets/locales/eu-ES-C3djlPR2.js +0 -1
  310. package/phanpy/dist/assets/locales/fa-IR-DTT490Ck.js +0 -1
  311. package/phanpy/dist/assets/locales/fi-FI-B23MCPuz.js +0 -1
  312. package/phanpy/dist/assets/locales/fr-FR-BcaQsO5C.js +0 -1
  313. package/phanpy/dist/assets/locales/gl-ES-BR-CVKBN.js +0 -1
  314. package/phanpy/dist/assets/locales/he-IL-CfheRbCu.js +0 -1
  315. package/phanpy/dist/assets/locales/hu-HU-CfheRbCu.js +0 -1
  316. package/phanpy/dist/assets/locales/it-IT-C1136UFV.js +0 -1
  317. package/phanpy/dist/assets/locales/ja-JP-CRZmRAhD.js +0 -1
  318. package/phanpy/dist/assets/locales/kab-Cz8xH_TT.js +0 -1
  319. package/phanpy/dist/assets/locales/ko-KR-CG9UiY4o.js +0 -1
  320. package/phanpy/dist/assets/locales/lt-LT-CJcI6sHZ.js +0 -1
  321. package/phanpy/dist/assets/locales/nb-NO-CfheRbCu.js +0 -1
  322. package/phanpy/dist/assets/locales/nl-NL-DxN4Yc1t.js +0 -1
  323. package/phanpy/dist/assets/locales/oc-FR-CfheRbCu.js +0 -1
  324. package/phanpy/dist/assets/locales/pl-PL-DkwGXf6P.js +0 -1
  325. package/phanpy/dist/assets/locales/pseudo-LOCALE-CQ4XxkQ0.js +0 -1
  326. package/phanpy/dist/assets/locales/pt-BR-nGL0HdFS.js +0 -1
  327. package/phanpy/dist/assets/locales/pt-PT-Cp17aj2z.js +0 -1
  328. package/phanpy/dist/assets/locales/ru-RU-0Y8cIoaK.js +0 -1
  329. package/phanpy/dist/assets/locales/th-TH-CfheRbCu.js +0 -1
  330. package/phanpy/dist/assets/locales/tok-CfheRbCu.js +0 -1
  331. package/phanpy/dist/assets/locales/tr-TR-CfheRbCu.js +0 -1
  332. package/phanpy/dist/assets/locales/uk-UA-C5E3dFBf.js +0 -1
  333. package/phanpy/dist/assets/locales/zh-CN-BHJi7_et.js +0 -1
  334. package/phanpy/dist/assets/locales/zh-TW-CfheRbCu.js +0 -1
  335. package/phanpy/dist/assets/main-BdqNbG-a.js +0 -26
  336. package/phanpy/dist/assets/main-BdqNbG-a.js.map +0 -1
  337. package/phanpy/dist/assets/mock-home-DGhR__He.js +0 -2
  338. package/phanpy/dist/assets/mock-home-DGhR__He.js.map +0 -1
  339. package/phanpy/dist/assets/multi-column-ROck0NVt.jpg +0 -0
  340. package/phanpy/dist/assets/multi-hashtag-timeline-DH5INVHi.jpg +0 -0
  341. package/phanpy/dist/assets/nested-comments-thread-hv59kZLc.jpg +0 -0
  342. package/phanpy/dist/assets/polyfill-force-H8sUkLGp.js +0 -2
  343. package/phanpy/dist/assets/polyfill-force-H8sUkLGp.js.map +0 -1
  344. package/phanpy/dist/assets/pwa-viewport-ogKbF51K.js +0 -20
  345. package/phanpy/dist/assets/pwa-viewport-ogKbF51K.js.map +0 -1
  346. package/phanpy/dist/assets/style-B1iLPTPR.css +0 -2
  347. package/phanpy/dist/assets/temml-DuyIQMei.js +0 -97
  348. package/phanpy/dist/assets/temml-DuyIQMei.js.map +0 -1
  349. package/phanpy/dist/assets/tinyld.light.node-DtbBhLYC.js +0 -2
  350. package/phanpy/dist/assets/tinyld.light.node-DtbBhLYC.js.map +0 -1
  351. package/phanpy/dist/assets/year-in-posts-B2lywqdx.png +0 -0
  352. package/phanpy/dist/assets/year-in-posts-DJv_star.js +0 -3
  353. package/phanpy/dist/assets/year-in-posts-DJv_star.js.map +0 -1
  354. package/phanpy/dist/compose/index.html +0 -20
  355. package/phanpy/dist/favicon.ico +0 -0
  356. package/phanpy/dist/index.html +0 -73
  357. package/phanpy/dist/logo-192.png +0 -0
  358. package/phanpy/dist/logo-512.png +0 -0
  359. package/phanpy/dist/logo-badge-72.png +0 -0
  360. package/phanpy/dist/logo-maskable-512.png +0 -0
  361. package/phanpy/dist/logo-monochrome-512.png +0 -0
  362. package/phanpy/dist/logo-monochrome-maskable-512.png +0 -0
  363. package/phanpy/dist/manifest.webmanifest +0 -1
  364. package/phanpy/dist/og-image-2.jpg +0 -0
  365. package/phanpy/dist/og-image.png +0 -0
  366. package/phanpy/dist/robots.txt +0 -2
  367. package/phanpy/dist/sw.js +0 -2
  368. package/phanpy/dist/sw.js.map +0 -1
  369. package/phanpy/dist/version.json +0 -1
  370. package/run-agent.mjs +0 -899
  371. package/vendor/gate.cjs +0 -138
  372. package/vendor/idp-grant.cjs +0 -394
  373. package/vendor/jfetch.cjs +0 -16
  374. package/web/admin/actors.js +0 -147
  375. package/web/admin/bar.css +0 -126
  376. package/web/admin/bar.js +0 -99
  377. package/web/admin/client/client.js +0 -66
  378. package/web/admin/client/index.html +0 -69
  379. package/web/admin/client-pick.js +0 -79
  380. package/web/admin/common.js +0 -23
  381. package/web/admin/connections.js +0 -112
  382. package/web/admin/gateway.js +0 -133
  383. package/web/admin/group.js +0 -258
  384. package/web/admin/index.html +0 -612
  385. package/web/admin/notices-bar.js +0 -90
  386. package/web/admin/oauth-signin.mjs +0 -37
  387. package/web/admin/record.js +0 -393
  388. package/web/admin/setup/index.html +0 -257
  389. package/web/admin/setup/setup.js +0 -339
  390. package/web/admin/tokens.css +0 -68
  391. package/web/admin/upkeep.js +0 -177
  392. package/web/admin/window.css +0 -55
  393. package/web/admin/window.js +0 -136
  394. package/web/app/README.md +0 -81
  395. package/web/app/admin-facade.mjs +0 -588
  396. package/web/app/agent.mjs +0 -672
  397. package/web/app/atproto-browser.mjs +0 -86
  398. package/web/app/authorize_interaction.html +0 -56
  399. package/web/app/authorize_interaction.js +0 -69
  400. package/web/app/boot.mjs +0 -616
  401. package/web/app/deliver-relay.mjs +0 -129
  402. package/web/app/dist/boot.js +0 -34562
  403. package/web/app/dist/boot.js.map +0 -7
  404. package/web/app/dist/sw.js +0 -73456
  405. package/web/app/dist/sw.js.map +0 -7
  406. package/web/app/fediacct-browser.mjs +0 -146
  407. package/web/app/gateway-move.mjs +0 -60
  408. package/web/app/idb-kv.mjs +0 -35
  409. package/web/app/index.html +0 -258
  410. package/web/app/keys-browser.mjs +0 -71
  411. package/web/app/keystore.mjs +0 -82
  412. package/web/app/oidc-session.mjs +0 -6
  413. package/web/app/pod-auth.mjs +0 -248
  414. package/web/app/pod-remote.mjs +0 -84
  415. package/web/app/shims/_globals.mjs +0 -2
  416. package/web/app/shims/fedify-sig.mjs +0 -77
  417. package/web/app/shims/node-crypto.mjs +0 -113
  418. package/web/app/shims/node-fs.mjs +0 -17
  419. package/web/app/shims/node-path.mjs +0 -19
  420. package/web/app/shims/node-url.mjs +0 -7
  421. package/web/app/shims/prelude.js +0 -58
  422. package/web/app/shims/safefetch.mjs +0 -58
  423. package/web/app/shims/shapes-text.mjs +0 -8
  424. package/web/app/shims/web-push.mjs +0 -14
  425. package/web/app/signup.mjs +0 -357
  426. package/web/app/sw-src.mjs +0 -220
  427. package/web/app/update.js +0 -70
  428. package/web/front/admin.html +0 -55
  429. package/web/front/admin.js +0 -89
  430. package/web/front/install.sh +0 -69
  431. package/web/front/new-account.html +0 -35
  432. package/web/front/new-account.js +0 -13
  433. package/web/front/notices.html +0 -69
  434. package/web/front/notices.js +0 -107
  435. package/web/front/run.html +0 -64
  436. package/web/front/run.js +0 -221
  437. package/web/front/solid-oidc-client.js +0 -6
  438. package/web/icons/forum.svg +0 -11
  439. package/web/icons/gateway.svg +0 -14
  440. package/web/icons/local.svg +0 -11
@@ -1,674 +0,0 @@
1
- // transport.mjs — every authenticated request this library makes to a pod, and
2
- // the manners that must hold for all of them.
3
- //
4
- // The session is injected and never built here: a Node agent hands in a DPoP
5
- // client-credentials grant, a browser hands in a Solid-OIDC session, a pod
6
- // server hands in a shim straight onto its own store. That is what lets one
7
- // implementation serve all three, and it is why this file has no `import` of
8
- // anything with a runtime.
9
- //
10
- // Two copies of this used to exist — lib/remote.mjs and web/app/pod-remote.mjs
11
- // — hand-kept in sync, with the browser copy quietly behind on five separate
12
- // hardenings. The subclasses below now add only what genuinely differs: how the
13
- // session is obtained, and whether a throttled pod is waited out or refused.
14
-
15
- import * as $rdf from 'rdflib';
16
- import { readCapped, retryAfterMs } from './http.mjs';
17
- import { linkTargets, REL } from './links.mjs';
18
- import { podBaseOfWebId } from './urls.mjs';
19
-
20
- const LDP = $rdf.Namespace('http://www.w3.org/ns/ldp#');
21
- const DC = $rdf.Namespace('http://purl.org/dc/terms/');
22
- const POSIX = $rdf.Namespace('http://www.w3.org/ns/posix/stat#');
23
- const RDF = $rdf.Namespace('http://www.w3.org/1999/02/22-rdf-syntax-ns#');
24
- const ACL = $rdf.Namespace('http://www.w3.org/ns/auth/acl#');
25
- const FOAF = $rdf.Namespace('http://xmlns.com/foaf/0.1/');
26
- const AS = $rdf.Namespace('https://www.w3.org/ns/activitystreams#');
27
- const RDFS = $rdf.Namespace('http://www.w3.org/2000/01/rdf-schema#');
28
-
29
- // An inserted term must be something a reader can take for what it says it
30
- // is: an absolute http(s) IRI, or a literal.
31
- function termProblem(t) {
32
- if (t?.termType === 'Literal') return null;
33
- if (t?.termType !== 'NamedNode') return `${t?.value ?? t} is not an IRI or a literal`;
34
- try {
35
- return /^https?:$/.test(new URL(t.value).protocol) ? null : `${t.value} is not an http(s) IRI`;
36
- } catch { return `${t.value} is not an absolute IRI`; }
37
- }
38
-
39
- // A pod whose access rules are ACP policies, not WAC authorizations. This
40
- // library writes WAC; over an ACP resource that would be noise where the pod's
41
- // real rules used to be, so it stops instead.
42
- const ACP_NS = 'http://www.w3.org/ns/solid/acp#';
43
-
44
- // The inbox is public-Append, so the listing's size is in other people's
45
- // hands; reading it whole must still have a ceiling.
46
- const LISTING_MAX_BYTES = 10 * 1024 * 1024;
47
-
48
- // No Retry-After header still means "not now". Sixty seconds is the answer
49
- // when the server declined to give one.
50
- const COOLDOWN_DEFAULT_MS = 60_000;
51
-
52
- const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
53
-
54
- // A pod's ability to BE a pod. Not data, and never ours to remove: delete one
55
- // of these and the pod does not degrade, it stops — and it cannot be repaired
56
- // by the tool that broke it, because that tool can no longer authenticate. A
57
- // pod was crippled exactly this way.
58
- //
59
- // Here rather than in any one caller, because every DELETE this library sends
60
- // goes through PodTransport.fetch — so this is the one place that cannot be
61
- // routed around. A DENY-list, not an allow-prefix: the next script will have a
62
- // different prefix and the same list of things it must never touch.
63
- const PROTECTED = [
64
- [/\/profile(\/|$)/, 'the WebID document — nothing could authenticate as this pod again'],
65
- [/\/settings(\/|$)/, "the pod's own settings"],
66
- [/\/\.well-known(\/|$)/, 'discovery — the handle would stop resolving'],
67
- [/\.acl$/, 'an access-control document — what it governs becomes unreachable'],
68
- [/\.meta$/, 'a resource description the server itself reads'],
69
- // The single-active-agent lock. It is on the pod precisely because the
70
- // private half need not be — a lease only one machine can reach coordinates
71
- // nothing — so it survives moving the state off, and nothing ever deletes it:
72
- // release() writes an expiry, it does not remove the document. Two agents
73
- // both believing they hold it is silent, destructive, duplicated inbox
74
- // draining.
75
- [/\/lease\.json$/, 'the lease — two agents would drain the same inbox'],
76
- ];
77
-
78
- export function protectedFromDeletion(url) {
79
- let p;
80
- try { p = new URL(url).pathname; } catch { throw new Error(`refusing to DELETE an unparsable URL: ${url}`); }
81
- if (p === '/' || p === '') throw new Error(`refusing to DELETE the pod root: ${url}`);
82
- for (const [re, why] of PROTECTED) {
83
- if (re.test(p)) {
84
- throw new Error(`refusing to DELETE ${url} — ${why}. Deny-list: lib/pod/transport.mjs.`);
85
- }
86
- }
87
- }
88
-
89
- export class PodTransport {
90
- /**
91
- * @param {object} session `{ fetch(url, init) }` — injected, never built here
92
- * @param {object} opts
93
- * @param {string} opts.webId who this transport acts as; the ACL owner
94
- * @param {string} [opts.role] 'agent' | 'gateway' | 'signup' — what is talking
95
- * @param {string} [opts.runtime] 'node' | 'browser' — where it is talking from
96
- * @param {string} [opts.cooldownMode] 'refuse' fails fast for the window;
97
- * 'wait' sleeps it out and continues
98
- * @param {number} [opts.maxCooldownMs] ceiling on a Retry-After we will honour
99
- */
100
- constructor(session, {
101
- webId, log = () => {}, role = 'agent', runtime = 'node',
102
- cooldownMode = 'refuse', maxCooldownMs = 30 * 60_000,
103
- } = {}) {
104
- this.session = session;
105
- this.webId = webId;
106
- this.log = log;
107
- this.role = role;
108
- this.runtime = runtime;
109
- this.cooldownMode = cooldownMode;
110
- this.maxCooldownMs = maxCooldownMs;
111
- this.pausedUntil = 0;
112
- this.probeCount = 0;
113
- // Where each resource's access control lives, as the pod itself said. WAC
114
- // forbids working it out from the resource's own URL, so it is asked for
115
- // and remembered rather than assembled.
116
- this.aclUrls = new Map();
117
- this.aclFlavour = null; // null until the first write asks what this pod speaks
118
- this._listCache = new Map();
119
- // A fronted identity advertises ids on a shared domain but writes to the
120
- // pod. The caller installs the advertised→pod mapping here, so every
121
- // request built from an advertised id lands on the pod — one choke point.
122
- // Unset → identity, byte-for-byte the unfronted behaviour.
123
- this.toPod = null;
124
- }
125
-
126
- /** Who is talking, and from where — the prefix on every log line and error. */
127
- get label() { return `${this.role}/${this.runtime}`; }
128
-
129
- /** One line for a boot log: what this transport is and what it points at. */
130
- describe() {
131
- return `${this.role} over a ${this.runtime} session → ${this.webId?.split('#')[0] || 'unknown'}`;
132
- }
133
-
134
- /** Install the advertised→pod url map for a fronted identity (urls.toPod). */
135
- setUrlMap(fn) { this.toPod = typeof fn === 'function' ? fn : null; }
136
-
137
- async warmup() { return this.session.warmup?.(); }
138
-
139
- /** What we have asked of this pod, for a status page or an operator. */
140
- stats() {
141
- return {
142
- role: this.role,
143
- runtime: this.runtime,
144
- probes: this.probeCount,
145
- pausedFor: Math.max(0, Math.round((this.pausedUntil - Date.now()) / 1000)),
146
- };
147
- }
148
-
149
- /**
150
- * Observe a throttling answer and arm the pod-wide cooldown.
151
- *
152
- * One pause for the whole pod, not one per request: without it every
153
- * in-flight call rides its own ladder into a server that has already said it
154
- * is overloaded, which is how a throttle becomes a stampede. A 429 or 503
155
- * with NO Retry-After still arms the window — the browser copy used to arm
156
- * nothing at all in that case, which is most cases.
157
- */
158
- _observe(res) {
159
- if (res.status !== 429 && res.status !== 503) return res;
160
- // What the server ASKED for is always honoured. What to do when it asked
161
- // for nothing depends on what the cooldown costs here: a caller that
162
- // REFUSES for the window loses nothing by assuming a minute, while one that
163
- // WAITS it out would freeze a person's tab for a minute over a throttle
164
- // that clears in seconds — so there, absence arms nothing and the caller's
165
- // own retry ladder paces it instead.
166
- const asked = retryAfterMs(res, this.maxCooldownMs);
167
- const ms = asked ?? (this.cooldownMode === 'refuse' ? COOLDOWN_DEFAULT_MS : 0);
168
- if (!ms) return res;
169
- this.pausedUntil = Date.now() + ms;
170
- this.log(`[${this.label}] pod returned ${res.status}${asked ? ', Retry-After' : ''} — `
171
- + `holding all requests for ${Math.round(ms / 1000)}s`);
172
- return res;
173
- }
174
-
175
- /** Honour an armed cooldown: fail fast, or wait it out, as configured. */
176
- async _cooldownGate() {
177
- const left = this.pausedUntil - Date.now();
178
- if (left <= 0) return;
179
- if (this.cooldownMode === 'wait') {
180
- await sleep(Math.min(left, this.maxCooldownMs));
181
- return;
182
- }
183
- throw new Error(`[${this.label}] pod asked us to back off — ${Math.ceil(left / 1000)}s left of its Retry-After`);
184
- }
185
-
186
- /**
187
- * The seam a subclass overrides to add transport behaviour — the browser's
188
- * retry ladder lives here. Overriding THIS rather than `fetch` is deliberate:
189
- * an override cannot skip the url map, the cooldown accounting or the
190
- * deletion deny-list, because those are in `fetch` above it.
191
- */
192
- async _send(url, init) { return this.session.fetch(url, init); }
193
-
194
- /**
195
- * A deliberately CREDENTIAL-FREE request: it asks what a stranger would see,
196
- * so it cannot go through the session without answering a different question.
197
- * It still belongs to this pod, though — it opens a socket to it and takes
198
- * one of its workers — so it observes the same cooldown and is counted.
199
- *
200
- * No url map: a probe asks about the ADVERTISED face, which is the whole
201
- * point of asking.
202
- */
203
- async probe(url, init = {}) {
204
- await this._cooldownGate();
205
- this.probeCount++;
206
- return this._observe(await fetch(url, init));
207
- }
208
-
209
- /**
210
- * Every authenticated request. Nothing below this reaches the session
211
- * directly: this is the only place the cooldown is both OBSERVED and ARMED,
212
- * the url map applied, and the deny-list enforced.
213
- */
214
- async fetch(url, init) {
215
- await this._cooldownGate();
216
- if (this.toPod) url = this.toPod(url);
217
- if (String(init?.method || '').toUpperCase() === 'DELETE') this._guardDelete(url);
218
- return this._observe(await this._send(url, init));
219
- }
220
-
221
- /** Both refusals a DELETE must pass, wherever it was issued from. */
222
- _guardDelete(url) {
223
- protectedFromDeletion(url);
224
- // The pattern list knows the usual name for an access-control document.
225
- // One the pod named itself is just as fatal to remove. Tolerating an
226
- // absent map matters: this now runs for EVERY delete, including one issued
227
- // through `fetch` by a caller holding a minimally-built transport.
228
- for (const acl of this.aclUrls?.values() ?? []) {
229
- if (acl === url) throw new Error(`refusing to DELETE an access-control document: ${url}`);
230
- }
231
- }
232
-
233
- async put(url, body, contentType) {
234
- const res = await this.fetch(url, {
235
- method: 'PUT', headers: { 'content-type': contentType }, body,
236
- });
237
- if (res.status >= 400) throw new Error(`[${this.label}] PUT ${url} → ${res.status}`);
238
- // Writing a document is usually the step before setting its access, and
239
- // the answer to the write already says where that lives. Taking it here
240
- // spares the extra request the ACL write would otherwise make.
241
- this.noteAclLink(url, res);
242
- return res;
243
- }
244
-
245
- async putJson(url, obj, contentType = 'application/activity+json') {
246
- return this.put(url, JSON.stringify(obj), contentType);
247
- }
248
-
249
- // A read that FAILED is not a document that is ABSENT. Returning null for
250
- // both let a 429 read as "no replies yet", and the caller then rewrote the
251
- // collection from empty — erasing every reply already recorded. Only a real
252
- // 404/410 is absence; anything else throws and the caller retries later.
253
- async getJson(url) {
254
- const res = await this.fetch(url, { headers: { accept: '*/*' } });
255
- if (res.status === 404 || res.status === 410) return null;
256
- if (res.status >= 400) throw new Error(`[${this.label}] GET ${url} → ${res.status}`);
257
- return res.json().catch(() => null);
258
- }
259
-
260
- async delete(url) {
261
- const res = await this.fetch(url, { method: 'DELETE' });
262
- return res.status < 400 || res.status === 404;
263
- }
264
-
265
- /** Remember an access-control location the pod volunteered on a response. */
266
- noteAclLink(url, res) {
267
- if (this.aclUrls.has(url)) return;
268
- const [acl] = linkTargets(res?.headers?.get?.('link'), REL.acl, url);
269
- if (acl) this.aclUrls.set(url, acl);
270
- }
271
-
272
- /**
273
- * Where this resource's access control lives. The pod says so on any
274
- * response about the resource; a pod that says nothing is taken to keep it
275
- * at the usual suffix, which is what every server this runs against does.
276
- */
277
- async aclUrlFor(targetUrl) {
278
- const known = this.aclUrls.get(targetUrl);
279
- if (known) return known;
280
- try {
281
- const res = await this.fetch(targetUrl, { method: 'HEAD' });
282
- this.noteAclLink(targetUrl, res);
283
- } catch { /* unreachable or no such resource yet: the suffix below */ }
284
- const resolved = this.aclUrls.get(targetUrl) || targetUrl + '.acl';
285
- this.aclUrls.set(targetUrl, resolved);
286
- return resolved;
287
- }
288
-
289
- /**
290
- * Whether writing a WAC document here is meaningful. Asked once per pod, on
291
- * the first access-control write. A pod that answers with ACP policies is
292
- * left alone: replacing them with authorizations it does not read would take
293
- * away the rules actually protecting it.
294
- */
295
- async aclWritable(aclUrl) {
296
- if (this.aclFlavour !== null) return this.aclFlavour;
297
- this.aclFlavour = true;
298
- try {
299
- const res = await this.fetch(aclUrl, { headers: { accept: 'text/turtle' } });
300
- if (res.status < 300) {
301
- const g = $rdf.graph();
302
- $rdf.parse(await res.text(), g, aclUrl, 'text/turtle');
303
- const acp = g.statements.some(st => st.predicate.value.startsWith(ACP_NS)
304
- || st.object.value.startsWith(ACP_NS));
305
- if (acp) {
306
- this.aclFlavour = false;
307
- this.log(`[${this.label}] this pod states access as ACP policies, which this library does not write — `
308
- + 'its access rules are left exactly as they are, and nothing here is published private');
309
- }
310
- }
311
- } catch { /* absent, unreadable or unparsable: WAC is what we write */ }
312
- return this.aclFlavour;
313
- }
314
-
315
- // WAC doc granting the public `publicModes` on target, owner full control.
316
- // An empty publicModes list yields an owner-only document.
317
- //
318
- // Built and serialised by rdflib, like every other document written here.
319
- // This is the highest-consequence RDF in the project: an ACL that comes out
320
- // malformed, or naming the wrong subject, either locks the owner out or
321
- // leaves the private trees world-readable. $rdf.sym() also throws on an
322
- // illegal IRI, so a pod URL with something odd in it fails here rather than
323
- // silently producing a document that means something else.
324
- aclDoc(targetUrl, publicModes, { appendAgents = [], readAgents = [], aclUrl = null } = {}) {
325
- const url = aclUrl || targetUrl + '.acl';
326
- const doc = $rdf.sym(url);
327
- const target = $rdf.sym(targetUrl);
328
- const g = $rdf.graph();
329
- const authorize = (subject, agentPred, agent, modes) => {
330
- g.add(subject, RDF('type'), ACL('Authorization'), doc);
331
- g.add(subject, agentPred, agent, doc);
332
- g.add(subject, ACL('accessTo'), target, doc);
333
- g.add(subject, ACL('default'), target, doc);
334
- for (const m of modes) g.add(subject, ACL('mode'), ACL(m), doc);
335
- };
336
- if (publicModes.length) {
337
- authorize($rdf.sym(url + '#public'), ACL('agentClass'), FOAF('Agent'), publicModes);
338
- }
339
- // Named agents granted Append but not public: this is how the inbox is
340
- // locked to a gateway — public loses Append, the gateway's WebID keeps it.
341
- appendAgents.forEach((webId, i) =>
342
- authorize($rdf.sym(url + `#gw${i}`), ACL('agent'), $rdf.sym(webId), ['Append']));
343
- // Named agents granted Read but not public: this is how a forum's
344
- // moderators read a queue that names people, without it being public.
345
- readAgents.forEach((webId, i) =>
346
- authorize($rdf.sym(url + `#r${i}`), ACL('agent'), $rdf.sym(webId), ['Read']));
347
- authorize($rdf.sym(url + '#owner'), ACL('agent'), $rdf.sym(this.webId),
348
- ['Read', 'Write', 'Control']);
349
- return $rdf.serialize(doc, g, url, 'text/turtle');
350
- }
351
-
352
- async setAcl(targetUrl, publicModes, opts = {}) {
353
- // The rule names the resource on the POD. A fronted identity hands in
354
- // advertised urls; `fetch` maps the request, but a rule whose accessTo
355
- // named the advertised url would guard a resource the pod does not have,
356
- // and lock the real one to nobody — the owner included.
357
- const podTarget = this.toPod ? this.toPod(targetUrl) : targetUrl;
358
- const url = await this.aclUrlFor(podTarget);
359
- if (!await this.aclWritable(url)) return null;
360
- const doc = this.aclDoc(podTarget, publicModes, { ...opts, aclUrl: url });
361
- // `ifChanged`: read the rule the pod holds and write only when it differs.
362
- // For a rule restated at every start — the inbox door — a read is the
363
- // whole cost, where a write took the pod's lock to say the same thing.
364
- if (opts.ifChanged && await this.aclSame(url, doc)) return { status: 304, unchanged: true };
365
- return this.put(url, doc, 'text/turtle');
366
- }
367
-
368
- // Whether the pod's rule at `aclUrl` states exactly what `doc` states.
369
- // Compared as graphs, not bytes: the pod serialises what it holds its own
370
- // way. Every rule this file writes names its subjects, so triple sets are
371
- // enough; anything unreadable or with blank nodes reads as different.
372
- async aclSame(aclUrl, doc) {
373
- try {
374
- const res = await this.fetch(aclUrl, { headers: { accept: 'text/turtle' } });
375
- if (res.status !== 200) return false;
376
- const triples = (text) => {
377
- const g = $rdf.graph();
378
- $rdf.parse(text, g, aclUrl, 'text/turtle');
379
- if (g.statements.some((st) => st.subject.termType === 'BlankNode' || st.object.termType === 'BlankNode')) return null;
380
- return g.statements.map((st) => `${st.subject.value} ${st.predicate.value} ${st.object.value}`).sort().join('\n');
381
- };
382
- const theirs = triples(await res.text());
383
- return theirs !== null && theirs === triples(doc);
384
- } catch { return false; }
385
- }
386
-
387
- // Child documents of an LDP container (URLs under it, excluding aux docs).
388
- // Revalidated: the inbox is polled every couple of minutes and is usually
389
- // unchanged, so ask conditionally and let the server answer 304.
390
- async listContainer(url) {
391
- // Lazily, not only in the constructor: a caller may hold a transport built
392
- // with Object.create for a test, setting just the few fields it drives.
393
- this._listCache ||= new Map();
394
- const known = this._listCache.get(url);
395
- const res = await this.fetch(url, {
396
- headers: { accept: 'text/turtle', ...(known?.etag ? { 'if-none-match': known.etag } : {}) },
397
- });
398
- if (res.status === 304 && known) return known.children;
399
- if (res.status >= 400) return [];
400
- // rdflib, not a regex: a container listing is RDF, and the gap between
401
- // "any angle-bracketed thing in the document" and "what this container
402
- // actually contains" is where quiet bugs live.
403
- //
404
- // CSS puts dc:modified and posix:size on every child of the same listing,
405
- // so age and weight cost nothing extra — which is what lets a drain work
406
- // oldest-first and lets a backlog be measured without reading any of it.
407
- let body;
408
- try {
409
- body = await readCapped(res, LISTING_MAX_BYTES);
410
- } catch (e) {
411
- this.log(`[${this.label}] listing at ${url}: ${e.message} — using the last known listing`);
412
- return known?.children ?? [];
413
- }
414
- // A parse failure is a real fault, not a cache hit: it propagates. The
415
- // browser copy used to swallow it and hand back a stale listing, which
416
- // turns a corrupt container into silently missing work.
417
- const g = $rdf.graph();
418
- $rdf.parse(body, g, url, 'text/turtle');
419
- const here = $rdf.sym(url);
420
- const seen = new Set();
421
- const receipts = new Set();
422
- const list = [];
423
- for (const child of g.each(here, LDP('contains'), null, here)) {
424
- const u = child.value;
425
- // `.receipt.json` is a verification receipt a gateway wrote beside an
426
- // inbox item — read with the item, never enumerated as an item itself.
427
- if (u.endsWith('.receipt.json')) { receipts.add(u); continue; }
428
- if (!u.startsWith(url) || u === url || /\.(acl|meta)$/.test(u) || seen.has(u)) continue;
429
- seen.add(u);
430
- list.push({
431
- url: u,
432
- size: Number(g.any(child, POSIX('size'), null, here)?.value || 0),
433
- modified: g.any(child, DC('modified'), null, here)?.value || null,
434
- });
435
- }
436
- // Whether a receipt sits beside each item, so the drain asks for one only
437
- // where there is one; and the receipts nothing sits beside any more.
438
- for (const item of list) item.receipt = receipts.has(item.url + '.receipt.json');
439
- const orphans = [...receipts].filter((r) => !seen.has(r.slice(0, -'.receipt.json'.length)));
440
- // Oldest first. An LDP listing is a set, so without this a drain works in
441
- // whatever order the graph happened to parse — a mention from last week
442
- // after one from today, and no way to make progress predictable.
443
- list.sort((a, b) => String(a.modified || '').localeCompare(String(b.modified || '')));
444
- this._listCache.set(url, { etag: res.headers.get('etag'), children: list, orphans });
445
- return list;
446
- }
447
-
448
- /** Receipts in the last listing of `url` whose item is gone. */
449
- orphanReceipts(url) { return this._listCache?.get(url)?.orphans ?? []; }
450
-
451
- /**
452
- * The WebID profile advertises the actor as an account:
453
- * <webId> foaf:account <actor> .
454
- * <actor> a foaf:OnlineAccount, as:Person|as:Group ; foaf:accountName "@handle@host" .
455
- * Read–check–write through rdflib. Returns false when the profile already
456
- * says all of it. The parsed graph must mention the WebID before anything is
457
- * written back — an empty or foreign body must never become the new profile.
458
- */
459
- async linkAccountInProfile({ actorUrl, accountName, kind = 'person', outbox = null }) {
460
- const me = $rdf.sym(this.webId);
461
- const podBase = podBaseOfWebId(this.webId);
462
- // The profile and what its seeAlso names: what either says is said.
463
- const docs = await this.profileDocs(podBase);
464
- const says = (s, p, o) => docs.some(({ g }) => g?.holds(s, p, o));
465
- const actor = $rdf.sym(actorUrl);
466
- const wanted = [
467
- [me, FOAF('account'), actor],
468
- [actor, RDF('type'), FOAF('OnlineAccount')],
469
- [actor, RDF('type'), kind === 'group' ? AS('Group') : AS('Person')],
470
- [actor, FOAF('accountName'), $rdf.literal(accountName)],
471
- // Where a Solid client posts on this person's behalf (`as:outbox` on the
472
- // WebID is what dokieli reads); only where a door exists to take it.
473
- ...(outbox ? [[me, AS('outbox'), $rdf.sym(outbox)]] : []),
474
- ];
475
- const missing = wanted.filter(([s, p, o]) => !says(s, p, o));
476
- // A handle change leaves the old accountName behind; ours is replaced.
477
- // Likewise an outbox that moved.
478
- const stale = [];
479
- for (const { g } of docs) {
480
- for (const st of g?.statementsMatching(actor, FOAF('accountName'), null) || []) {
481
- if (st.object.value !== accountName) stale.push([st.subject, st.predicate, st.object]);
482
- }
483
- if (outbox) {
484
- for (const st of g?.statementsMatching(me, AS('outbox'), null) || []) {
485
- if (st.object.value !== outbox) stale.push([st.subject, st.predicate, st.object]);
486
- }
487
- }
488
- }
489
- if (!missing.length && !stale.length) return false;
490
- // Checked before it is written, to the profile or else to a document its
491
- // seeAlso names (writeAboutWebId); a patch carries only these statements.
492
- await this.writeAboutWebId(podBase, { inserts: missing, deletes: stale });
493
- return true;
494
- }
495
-
496
- /**
497
- * An N3 Patch of exactly these statements, or false when the pod will not
498
- * take one and the caller should write the document instead.
499
- *
500
- * The statements are serialised by rdflib; only the wrapper naming what is
501
- * being patched is assembled here, because N3's braces have no rdflib form.
502
- */
503
- n3Patch(docUrl, inserts, deletes) {
504
- const block = (triples) => {
505
- const g = $rdf.graph();
506
- for (const [s, p, o] of triples) g.add(s, p, o);
507
- return $rdf.serialize(null, g, docUrl, 'application/n-triples').trim();
508
- };
509
- const clauses = [];
510
- if (deletes.length) clauses.push(` solid:deletes { ${block(deletes)} }`);
511
- if (inserts.length) clauses.push(` solid:inserts { ${block(inserts)} }`);
512
- return `@prefix solid: <http://www.w3.org/ns/solid/terms#>.\n`
513
- + `<> a solid:InsertDeletePatch;\n${clauses.join(';\n')}.\n`;
514
- }
515
-
516
- async patchDocument(docUrl, inserts, deletes) {
517
- let res;
518
- try {
519
- res = await this.fetch(docUrl, {
520
- method: 'PATCH',
521
- headers: { 'content-type': 'text/n3' },
522
- body: this.n3Patch(docUrl, inserts, deletes),
523
- });
524
- } catch {
525
- return false; // no PATCH on this transport at all
526
- }
527
- if (res.status < 300) return true;
528
- // The pod cannot patch. Anything else — a 409 saying what we meant to
529
- // remove is not there any more — is a real answer, and rewriting the whole
530
- // document over the top of it would destroy whatever changed it.
531
- if (res.status === 405 || res.status === 415 || res.status === 501) return false;
532
- throw new Error(`[${this.label}] PATCH ${docUrl} → ${res.status}`);
533
- }
534
-
535
- // ---- writing a profile or a type index: valid RDF, or nothing ----
536
- //
537
- // Two rules. A change is written only if the document it leaves behind is
538
- // valid RDF: the graph as it would be afterwards is serialised and parsed
539
- // back, and the subject that must stay described still is. And a profile
540
- // that will not take a write is not the end: the statements go to the first
541
- // document its rdfs:seeAlso names, inside the same pod, that takes them —
542
- // under the same check. A profile is what every Solid app signs in through;
543
- // one left broken breaks them all. Reading follows the same links.
544
-
545
- /** An IRI or a literal as a term, for the operations that pass plain values. */
546
- sym(iri) {
547
- try { return $rdf.sym(iri); } catch { throw new Error(`not written: ${iri} is not an absolute IRI`); }
548
- }
549
- literal(value) { return $rdf.literal(value); }
550
-
551
- /** A document as a graph, or null when it is not there. */
552
- async readRdf(docUrl) {
553
- const res = await this.fetch(docUrl, { headers: { accept: 'text/turtle' } });
554
- if (res.status === 404 || res.status === 410) return null;
555
- if (res.status >= 400) throw new Error(`[${this.label}] GET ${docUrl} → ${res.status}`);
556
- const g = $rdf.graph();
557
- $rdf.parse(await res.text(), g, docUrl, 'text/turtle');
558
- return g;
559
- }
560
-
561
- /**
562
- * The document as it would be after the change, checked. Returns the Turtle
563
- * to write, or throws saying why nothing may be written.
564
- */
565
- checkedRdf(g, docUrl, { inserts = [], deletes = [], mustDescribe = null }) {
566
- const doc = $rdf.sym(docUrl);
567
- for (const [s, p, o] of inserts) {
568
- const bad = termProblem(s) || termProblem(p) || termProblem(o)
569
- || (s?.termType === 'Literal' || p?.termType !== 'NamedNode' ? 'a literal subject or predicate' : null);
570
- if (bad) throw new Error(`not written: ${bad}`);
571
- }
572
- const after = $rdf.graph();
573
- for (const st of g.statementsMatching(null, null, null, doc)) after.add(st.subject, st.predicate, st.object, doc);
574
- for (const [s, p, o] of deletes) for (const st of after.statementsMatching(s, p, o, doc)) after.remove(st);
575
- for (const [s, p, o] of inserts) if (!after.holds(s, p, o, doc)) after.add(s, p, o, doc);
576
- let text;
577
- try { text = $rdf.serialize(doc, after, docUrl, 'text/turtle'); } catch (e) {
578
- throw new Error(`not written: ${docUrl} would not serialise (${e.message})`);
579
- }
580
- const back = $rdf.graph();
581
- try { $rdf.parse(text, back, docUrl, 'text/turtle'); } catch (e) {
582
- throw new Error(`not written: ${docUrl} would not parse back (${e.message})`);
583
- }
584
- if (back.statementsMatching(null, null, null, doc).length !== after.statementsMatching(null, null, null, doc).length) {
585
- throw new Error(`not written: ${docUrl} would not read back as written`);
586
- }
587
- if (mustDescribe && !back.statementsMatching($rdf.sym(mustDescribe), null, null, doc).length) {
588
- throw new Error(`not written: ${docUrl} would no longer describe ${mustDescribe}`);
589
- }
590
- return text;
591
- }
592
-
593
- /**
594
- * Change one document, or refuse. `g` is the document as read (null for a
595
- * new one). A PATCH carries only these statements; where the pod cannot
596
- * patch, the whole checked document is written. Returns the write's status.
597
- */
598
- async writeRdfChecked(docUrl, g, { inserts = [], deletes = [], mustDescribe = null }) {
599
- const current = g || $rdf.graph();
600
- const doc = $rdf.sym(docUrl);
601
- const todo = inserts.filter(([s, p, o]) => !current.holds(s, p, o, doc));
602
- const gone = deletes.filter(([s, p, o]) => current.holds(s, p, o, doc));
603
- if (!todo.length && !gone.length) return 200;
604
- const text = this.checkedRdf(current, docUrl, { inserts: todo, deletes: gone, mustDescribe });
605
- if (g) {
606
- let res = null;
607
- try {
608
- res = await this.fetch(docUrl, { method: 'PATCH', headers: { 'content-type': 'text/n3' },
609
- body: this.n3Patch(docUrl, todo, gone) });
610
- } catch { res = null; }
611
- if (res && res.status < 300) return res.status;
612
- // A pod that cannot patch gets the whole checked document; any other
613
- // answer — a refusal, a conflict — is the answer.
614
- if (res && ![405, 415, 501].includes(res.status)) return res.status;
615
- }
616
- const put = await this.fetch(docUrl, { method: 'PUT', headers: { 'content-type': 'text/turtle' }, body: text });
617
- return put.status;
618
- }
619
-
620
- /**
621
- * The profile and the documents its rdfs:seeAlso names inside this pod,
622
- * each with its graph, the profile first. What any of them says about the
623
- * WebID is what the profile says.
624
- */
625
- async profileDocs(podBase) {
626
- const profileUrl = this.webId.split('#')[0];
627
- const g = await this.readRdf(profileUrl);
628
- if (!g) throw new Error(`no profile at ${profileUrl}`);
629
- const out = [{ url: profileUrl, g, profile: true }];
630
- const seen = new Set([profileUrl]);
631
- for (const st of g.statementsMatching(null, RDFS('seeAlso'), null)) {
632
- const url = st.object.value.split('#')[0];
633
- if (seen.has(url) || !url.startsWith(podBase)) continue; // inside this pod only
634
- seen.add(url);
635
- out.push({ url, g: await this.readRdf(url).catch(() => null), profile: false });
636
- }
637
- return out;
638
- }
639
-
640
- /** Every value the WebID has for `predicate`, across the profile and its seeAlso documents. */
641
- webIdValues(docs, predicate) {
642
- const me = $rdf.sym(this.webId);
643
- const p = $rdf.sym(predicate);
644
- const out = [];
645
- for (const { g } of docs) for (const st of g?.statementsMatching(me, p, null) || []) out.push(st.object.value);
646
- return [...new Set(out)];
647
- }
648
-
649
- /**
650
- * Write statements about the WebID: to the profile, or — if the profile will
651
- * not take them — to the first of its seeAlso documents that will. Every
652
- * write checked. Returns the document written to, or null when nothing
653
- * needed writing; throws when none would take it.
654
- */
655
- async writeAboutWebId(podBase, { inserts = [], deletes = [] }) {
656
- const docs = await this.profileDocs(podBase);
657
- const holds = ([s, p, o]) => docs.some(({ g }) => g?.holds(s, p, o));
658
- const todo = inserts.filter(t => !holds(t));
659
- const gone = deletes.filter(holds);
660
- if (!todo.length && !gone.length) return null;
661
- const refusals = [];
662
- for (const d of docs) {
663
- const here = gone.filter(([s, p, o]) => d.g?.holds(s, p, o));
664
- const status = await this.writeRdfChecked(d.url, d.g, {
665
- inserts: todo, deletes: here, mustDescribe: d.profile ? this.webId : null,
666
- });
667
- if (status < 300) return d.url;
668
- refusals.push(`${d.url} → ${status}`);
669
- if (status !== 401 && status !== 403) break; // a real answer, not "not yours to write"
670
- }
671
- throw new Error(`not written: neither the profile nor a document it names would take it (${refusals.join('; ')})`);
672
- }
673
-
674
- }