fedipod-server 0.29.0 → 0.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (444) hide show
  1. package/README.md +16 -6
  2. package/dist/claims.d.ts +2 -0
  3. package/dist/claims.js +13 -4
  4. package/dist/door.d.ts +4 -0
  5. package/dist/door.js +31 -0
  6. package/dist/fedipod.d.ts +15 -0
  7. package/dist/fedipod.js +13 -0
  8. package/dist/front.d.ts +3 -0
  9. package/dist/front.js +92 -0
  10. package/dist/handler.d.ts +36 -101
  11. package/dist/handler.js +93 -644
  12. package/dist/handler.jsonld +2 -30
  13. package/dist/identities.d.ts +71 -0
  14. package/dist/identities.js +236 -0
  15. package/dist/mounts.d.ts +43 -0
  16. package/dist/mounts.js +123 -0
  17. package/dist/opt-in.d.ts +46 -0
  18. package/dist/opt-in.js +176 -0
  19. package/dist/private-read.d.ts +4 -0
  20. package/dist/private-read.js +34 -0
  21. package/package.json +12 -20
  22. package/lib/client/c2s.mjs +0 -636
  23. package/lib/client/localapi.mjs +0 -87
  24. package/lib/client/masto/accounts.mjs +0 -310
  25. package/lib/client/masto/body.mjs +0 -156
  26. package/lib/client/masto/index.mjs +0 -206
  27. package/lib/client/masto/instance.mjs +0 -112
  28. package/lib/client/masto/media.mjs +0 -79
  29. package/lib/client/masto/oauth.mjs +0 -656
  30. package/lib/client/masto/render.mjs +0 -518
  31. package/lib/client/masto/statuses.mjs +0 -372
  32. package/lib/client/masto/timelines.mjs +0 -438
  33. package/lib/client/oidc-auth.mjs +0 -62
  34. package/lib/client/streaming.mjs +0 -154
  35. package/lib/client/webpush.mjs +0 -98
  36. package/lib/connections/acctfeed.mjs +0 -257
  37. package/lib/connections/atproto.mjs +0 -309
  38. package/lib/connections/bskyfeed.mjs +0 -243
  39. package/lib/connections/bskygroup.mjs +0 -162
  40. package/lib/connections/fediacct.mjs +0 -253
  41. package/lib/connections/import.mjs +0 -357
  42. package/lib/connections/tagfeed.mjs +0 -212
  43. package/lib/connections/vault.mjs +0 -114
  44. package/lib/core/as2.mjs +0 -206
  45. package/lib/core/contexts/activitystreams.json +0 -379
  46. package/lib/core/contexts/anno.json +0 -126
  47. package/lib/core/contexts/did-v1.json +0 -57
  48. package/lib/core/contexts/fep-5711.json +0 -36
  49. package/lib/core/contexts/gotosocial.json +0 -86
  50. package/lib/core/contexts/identity-v1.json +0 -152
  51. package/lib/core/contexts/index.mjs +0 -63
  52. package/lib/core/contexts/join-lemmy.json +0 -33
  53. package/lib/core/contexts/joinmastodon.json +0 -28
  54. package/lib/core/contexts/litepub-0.1.json +0 -60
  55. package/lib/core/contexts/map.json +0 -19
  56. package/lib/core/contexts/miscellany.json +0 -19
  57. package/lib/core/contexts/quotes.json +0 -18
  58. package/lib/core/contexts/schemaorg.json +0 -8845
  59. package/lib/core/contexts/security-data-integrity-v1.json +0 -78
  60. package/lib/core/contexts/security-data-integrity-v2.json +0 -81
  61. package/lib/core/contexts/security-multikey-v1.json +0 -35
  62. package/lib/core/contexts/security-v1.json +0 -74
  63. package/lib/core/contexts/webfinger.json +0 -10
  64. package/lib/core/deliver.mjs +0 -387
  65. package/lib/core/graphview.mjs +0 -327
  66. package/lib/core/intake/activities.mjs +0 -649
  67. package/lib/core/intake/activity.mjs +0 -318
  68. package/lib/core/intake/channel.mjs +0 -144
  69. package/lib/core/intake/group.mjs +0 -258
  70. package/lib/core/intake/index.mjs +0 -732
  71. package/lib/core/intake/notes.mjs +0 -367
  72. package/lib/core/intake/verify.mjs +0 -142
  73. package/lib/core/keys.mjs +0 -153
  74. package/lib/core/lease.mjs +0 -206
  75. package/lib/core/place.mjs +0 -82
  76. package/lib/core/polls.mjs +0 -105
  77. package/lib/core/profile-page.mjs +0 -104
  78. package/lib/core/proof.mjs +0 -79
  79. package/lib/core/publisher/collections.mjs +0 -272
  80. package/lib/core/publisher/index.mjs +0 -522
  81. package/lib/core/publisher/notes.mjs +0 -519
  82. package/lib/core/publisher/own.mjs +0 -143
  83. package/lib/core/publisher/questions.mjs +0 -241
  84. package/lib/core/publisher/restore.mjs +0 -224
  85. package/lib/core/shapes/activitystreams.ttl +0 -157
  86. package/lib/core/shapes/index.mjs +0 -132
  87. package/lib/core/shapes/shapes-text.mjs +0 -13
  88. package/lib/core/social.mjs +0 -561
  89. package/lib/core/storage.mjs +0 -176
  90. package/lib/core/store.mjs +0 -578
  91. package/lib/core/wire-quotes.mjs +0 -71
  92. package/lib/core/wire.mjs +0 -925
  93. package/lib/device/account.mjs +0 -87
  94. package/lib/device/admin/index.mjs +0 -13
  95. package/lib/device/admin/origins.mjs +0 -35
  96. package/lib/device/admin/routes/connections.mjs +0 -144
  97. package/lib/device/admin/routes/gateway.mjs +0 -199
  98. package/lib/device/admin/routes/lifecycle.mjs +0 -200
  99. package/lib/device/admin/routes/owner.mjs +0 -322
  100. package/lib/device/admin/routes/setup.mjs +0 -428
  101. package/lib/device/admin/routes/social.mjs +0 -188
  102. package/lib/device/admin/server.mjs +0 -95
  103. package/lib/device/admin/static.mjs +0 -247
  104. package/lib/device/admin/surface.mjs +0 -315
  105. package/lib/device/certs.mjs +0 -281
  106. package/lib/device/cli/commands/account.mjs +0 -586
  107. package/lib/device/cli/commands/run.mjs +0 -278
  108. package/lib/device/cli/commands/service.mjs +0 -221
  109. package/lib/device/cli/commands/setup.mjs +0 -489
  110. package/lib/device/cli/commands/state.mjs +0 -561
  111. package/lib/device/cli/context.mjs +0 -288
  112. package/lib/device/export-collections.mjs +0 -161
  113. package/lib/device/gateway-move.mjs +0 -58
  114. package/lib/device/home.mjs +0 -161
  115. package/lib/device/migrate.mjs +0 -115
  116. package/lib/device/ports.mjs +0 -27
  117. package/lib/device/remote.mjs +0 -54
  118. package/lib/device/setup.mjs +0 -496
  119. package/lib/device/update.mjs +0 -96
  120. package/lib/gateway/caches.mjs +0 -36
  121. package/lib/gateway/directory.mjs +0 -90
  122. package/lib/gateway/front-core.mjs +0 -966
  123. package/lib/gateway/gateway-core.mjs +0 -244
  124. package/lib/gateway/headers.mjs +0 -49
  125. package/lib/gateway/httpsig.mjs +0 -159
  126. package/lib/gateway/notices.mjs +0 -84
  127. package/lib/gateway/quiet.mjs +0 -189
  128. package/lib/gateway/relay-extras.mjs +0 -89
  129. package/lib/gateway/token-claims.mjs +0 -16
  130. package/lib/pod/README.md +0 -61
  131. package/lib/pod/actor.mjs +0 -96
  132. package/lib/pod/collection.mjs +0 -77
  133. package/lib/pod/containers.mjs +0 -140
  134. package/lib/pod/discovery.mjs +0 -46
  135. package/lib/pod/featured.mjs +0 -18
  136. package/lib/pod/followers.mjs +0 -24
  137. package/lib/pod/following.mjs +0 -10
  138. package/lib/pod/http.mjs +0 -74
  139. package/lib/pod/inbox.mjs +0 -166
  140. package/lib/pod/links.mjs +0 -35
  141. package/lib/pod/location.mjs +0 -52
  142. package/lib/pod/media.mjs +0 -27
  143. package/lib/pod/notes.mjs +0 -78
  144. package/lib/pod/notifications.mjs +0 -89
  145. package/lib/pod/outbox.mjs +0 -18
  146. package/lib/pod/package.json +0 -19
  147. package/lib/pod/policy.mjs +0 -58
  148. package/lib/pod/private.mjs +0 -19
  149. package/lib/pod/root.mjs +0 -104
  150. package/lib/pod/state.mjs +0 -40
  151. package/lib/pod/transport.mjs +0 -674
  152. package/lib/pod/type-index.mjs +0 -101
  153. package/lib/pod/urls.mjs +0 -91
  154. package/lib/server/embed.mjs +0 -424
  155. package/lib/session/README.md +0 -137
  156. package/lib/session/demo.html +0 -94
  157. package/lib/session/fedi-account.mjs +0 -417
  158. package/lib/session/fedi-login.mjs +0 -123
  159. package/lib/session/oidc-session.mjs +0 -233
  160. package/lib/session/package.json +0 -49
  161. package/lib/shared/guard.mjs +0 -213
  162. package/lib/shared/links.mjs +0 -9
  163. package/lib/shared/safefetch.mjs +0 -235
  164. package/lib/shared/ua.mjs +0 -29
  165. package/phanpy/dist/404.html +0 -40
  166. package/phanpy/dist/_headers +0 -2
  167. package/phanpy/dist/apple-touch-icon.png +0 -0
  168. package/phanpy/dist/assets/ICONS-DIPJeU0C.js +0 -2
  169. package/phanpy/dist/assets/ICONS-DIPJeU0C.js.map +0 -1
  170. package/phanpy/dist/assets/ScheduledAtField-D-Abfo4J.js +0 -183
  171. package/phanpy/dist/assets/ScheduledAtField-D-Abfo4J.js.map +0 -1
  172. package/phanpy/dist/assets/Temml-CCjoacWt.woff2 +0 -0
  173. package/phanpy/dist/assets/__vite-browser-external-C7iut881.js +0 -2
  174. package/phanpy/dist/assets/__vite-browser-external-C7iut881.js.map +0 -1
  175. package/phanpy/dist/assets/boosts-carousel-BiOaNdMT.jpg +0 -0
  176. package/phanpy/dist/assets/catch-up-O5q5C75Z.png +0 -0
  177. package/phanpy/dist/assets/chunk-aKtaBQYM.js +0 -1
  178. package/phanpy/dist/assets/compose-CFEx7wb4.js +0 -2
  179. package/phanpy/dist/assets/compose-CFEx7wb4.js.map +0 -1
  180. package/phanpy/dist/assets/compose-ZJN5xSb4.js +0 -35
  181. package/phanpy/dist/assets/compose-ZJN5xSb4.js.map +0 -1
  182. package/phanpy/dist/assets/debug.module-YFQgwnIr.js +0 -52
  183. package/phanpy/dist/assets/debug.module-YFQgwnIr.js.map +0 -1
  184. package/phanpy/dist/assets/dom-p1YcJ5Rw.js +0 -2
  185. package/phanpy/dist/assets/dom-p1YcJ5Rw.js.map +0 -1
  186. package/phanpy/dist/assets/home-mobile-dark@2x-Dc-J8-SM.png +0 -0
  187. package/phanpy/dist/assets/home-mobile-light@2x-Bd3fod8m.png +0 -0
  188. package/phanpy/dist/assets/home-tablet-dark@2x-BgcNI8le.png +0 -0
  189. package/phanpy/dist/assets/home-tablet-light@2x-BW2RV9T6.png +0 -0
  190. package/phanpy/dist/assets/icons/add-circle-line-WD0mwORD.js +0 -1
  191. package/phanpy/dist/assets/icons/alert-line-CpFUMf9s.js +0 -1
  192. package/phanpy/dist/assets/icons/android-2-line-Bzxnrd3S.js +0 -1
  193. package/phanpy/dist/assets/icons/announcement-line-5nwaJFJm.js +0 -1
  194. package/phanpy/dist/assets/icons/arrow-down-circle-line-a7Id9-Iv.js +0 -1
  195. package/phanpy/dist/assets/icons/arrow-down-line-B8uzXzq6.js +0 -1
  196. package/phanpy/dist/assets/icons/arrow-left-line-CGrGT9C-.js +0 -1
  197. package/phanpy/dist/assets/icons/arrow-right-line-CE-HERfX.js +0 -1
  198. package/phanpy/dist/assets/icons/arrow-to-up-line-CvdQjmcy.js +0 -1
  199. package/phanpy/dist/assets/icons/arrow-up-circle-line-DEparSNF.js +0 -1
  200. package/phanpy/dist/assets/icons/arrow-up-line-C7yBeugc.js +0 -1
  201. package/phanpy/dist/assets/icons/arrows-right-line-BtbXmJGP.js +0 -1
  202. package/phanpy/dist/assets/icons/at-line-CF66Bznk.js +0 -1
  203. package/phanpy/dist/assets/icons/attachment-line-BY8It-l9.js +0 -1
  204. package/phanpy/dist/assets/icons/blockquote-line-BS6pU_hm.js +0 -1
  205. package/phanpy/dist/assets/icons/board-line-C0UWXyA6.js +0 -1
  206. package/phanpy/dist/assets/icons/bookmark-line-1CtX5JLF.js +0 -1
  207. package/phanpy/dist/assets/icons/building-5-line-Dd59MzbO.js +0 -1
  208. package/phanpy/dist/assets/icons/bus-2-line-6_dvA0GG.js +0 -1
  209. package/phanpy/dist/assets/icons/calendar-day-line-Crpl_fmu.js +0 -1
  210. package/phanpy/dist/assets/icons/calendar-month-line-vommts2C.js +0 -1
  211. package/phanpy/dist/assets/icons/calendar-time-add-line-BU_DTlQk.js +0 -1
  212. package/phanpy/dist/assets/icons/camera-line-Cv69MEei.js +0 -1
  213. package/phanpy/dist/assets/icons/celebrate-line-B8DvIoi0.js +0 -1
  214. package/phanpy/dist/assets/icons/chart-bar-line-FK-QOmtF.js +0 -1
  215. package/phanpy/dist/assets/icons/chart-line-line-BGcJAFWw.js +0 -1
  216. package/phanpy/dist/assets/icons/chat-3-line-YFK2-hwz.js +0 -1
  217. package/phanpy/dist/assets/icons/check-circle-line-DsAxFr5-.js +0 -1
  218. package/phanpy/dist/assets/icons/clipboard-line-BBp364gJ.js +0 -1
  219. package/phanpy/dist/assets/icons/close-circle-line-CICdiuGz.js +0 -1
  220. package/phanpy/dist/assets/icons/close-line-DYrFWcjv.js +0 -1
  221. package/phanpy/dist/assets/icons/cloud-line-tiQAbah8.js +0 -1
  222. package/phanpy/dist/assets/icons/code-line-fAl8iiV3.js +0 -1
  223. package/phanpy/dist/assets/icons/comment-2-line-CR3g7WeB.js +0 -1
  224. package/phanpy/dist/assets/icons/copy-2-line-CLW69yF5.js +0 -1
  225. package/phanpy/dist/assets/icons/delete-2-line-B8xmbr4n.js +0 -1
  226. package/phanpy/dist/assets/icons/document-3-line-DBGNhAg2.js +0 -1
  227. package/phanpy/dist/assets/icons/document-line-C7mb9sb7.js +0 -1
  228. package/phanpy/dist/assets/icons/down-line-BGqSSThd.js +0 -1
  229. package/phanpy/dist/assets/icons/edit-4-line-DcgGaEhx.js +0 -1
  230. package/phanpy/dist/assets/icons/emoji-2-line-D8YgnM-y.js +0 -1
  231. package/phanpy/dist/assets/icons/exit-line-Dc07BS-x.js +0 -1
  232. package/phanpy/dist/assets/icons/external-link-line-BvpSkmjO.js +0 -1
  233. package/phanpy/dist/assets/icons/eye-2-line-CiWXx-f_.js +0 -1
  234. package/phanpy/dist/assets/icons/eye-close-line-BgTaSJ4P.js +0 -1
  235. package/phanpy/dist/assets/icons/filter-2-line-B_mR2usy.js +0 -1
  236. package/phanpy/dist/assets/icons/filter-line-C_nm-3cU.js +0 -1
  237. package/phanpy/dist/assets/icons/flag-1-line-C2HB05I0.js +0 -1
  238. package/phanpy/dist/assets/icons/forbid-circle-line-D3LTT9yd.js +0 -1
  239. package/phanpy/dist/assets/icons/formula-line-DLrt2d6j.js +0 -1
  240. package/phanpy/dist/assets/icons/grid-line-CgUmEikf.js +0 -1
  241. package/phanpy/dist/assets/icons/group-line-C5fnX0wU.js +0 -1
  242. package/phanpy/dist/assets/icons/hand-finger-2-line-BGgxbuL9.js +0 -1
  243. package/phanpy/dist/assets/icons/hashtag-line-CyTEEXBb.js +0 -1
  244. package/phanpy/dist/assets/icons/heart-crack-line-BZddbO2C.js +0 -1
  245. package/phanpy/dist/assets/icons/heart-line-CJ7K47J4.js +0 -1
  246. package/phanpy/dist/assets/icons/history-2-line-kWWvFzqv.js +0 -1
  247. package/phanpy/dist/assets/icons/history-line-bGQiwbTH.js +0 -1
  248. package/phanpy/dist/assets/icons/home-3-line-fRU1zeLG.js +0 -1
  249. package/phanpy/dist/assets/icons/information-line-iNk6-sOY.js +0 -1
  250. package/phanpy/dist/assets/icons/keyboard-line-DsCyuQNz.js +0 -1
  251. package/phanpy/dist/assets/icons/layout-4-line-CZkOX52Z.js +0 -1
  252. package/phanpy/dist/assets/icons/layout-5-line-B_znt5wO.js +0 -1
  253. package/phanpy/dist/assets/icons/left-line-eUw0tya4.js +0 -1
  254. package/phanpy/dist/assets/icons/lightning-line-DaKoDdix.js +0 -1
  255. package/phanpy/dist/assets/icons/link-2-line-DCqLmMA7.js +0 -1
  256. package/phanpy/dist/assets/icons/list-check-line-BAxosBh9.js +0 -1
  257. package/phanpy/dist/assets/icons/lock-line-CPoaXeUW.js +0 -1
  258. package/phanpy/dist/assets/icons/mail-line-B0P8aa1L.js +0 -1
  259. package/phanpy/dist/assets/icons/moon-line-CEqNAToP.js +0 -1
  260. package/phanpy/dist/assets/icons/more-1-fill-Dw2e5xab.js +0 -1
  261. package/phanpy/dist/assets/icons/more-3-line-BIiVjYTz.js +0 -1
  262. package/phanpy/dist/assets/icons/notification-line-BZZq2Gtu.js +0 -1
  263. package/phanpy/dist/assets/icons/pencil-line-CKh8-A1v.js +0 -1
  264. package/phanpy/dist/assets/icons/photo-album-line-Bnfg2u40.js +0 -1
  265. package/phanpy/dist/assets/icons/pin-line-DpTolyhs.js +0 -1
  266. package/phanpy/dist/assets/icons/play-fill-B0-G_8lX.js +0 -1
  267. package/phanpy/dist/assets/icons/qrcode-2-line-B6SPCaHn.js +0 -1
  268. package/phanpy/dist/assets/icons/quill-pen-line-B6LUrnCP.js +0 -1
  269. package/phanpy/dist/assets/icons/quote-left-fill-hQZ1cj_6.js +0 -1
  270. package/phanpy/dist/assets/icons/radar-line-CKyroTth.js +0 -1
  271. package/phanpy/dist/assets/icons/react-line-CoIKhhtK.js +0 -1
  272. package/phanpy/dist/assets/icons/refresh-2-line-D0mTF97S.js +0 -1
  273. package/phanpy/dist/assets/icons/right-line-B3KWJaqc.js +0 -1
  274. package/phanpy/dist/assets/icons/rocket-line-4KZl537D.js +0 -1
  275. package/phanpy/dist/assets/icons/round-fill-C69NTrZ0.js +0 -1
  276. package/phanpy/dist/assets/icons/round-line-B9k_1uo-.js +0 -1
  277. package/phanpy/dist/assets/icons/route-line-B28ReHcE.js +0 -1
  278. package/phanpy/dist/assets/icons/rows-4-line-CrBniagM.js +0 -1
  279. package/phanpy/dist/assets/icons/scan-line-DSf74YdP.js +0 -1
  280. package/phanpy/dist/assets/icons/search-2-line-B6geIU1z.js +0 -1
  281. package/phanpy/dist/assets/icons/settings-3-line-azJwK47w.js +0 -1
  282. package/phanpy/dist/assets/icons/settings-6-line-C_5zc5YR.js +0 -1
  283. package/phanpy/dist/assets/icons/share-2-line-D5MxWjrm.js +0 -1
  284. package/phanpy/dist/assets/icons/share-forward-line-Cp61fZzZ.js +0 -1
  285. package/phanpy/dist/assets/icons/sparkles-2-line-DYYyXTaW.js +0 -1
  286. package/phanpy/dist/assets/icons/sparkles-line-C2hY1s6I.js +0 -1
  287. package/phanpy/dist/assets/icons/time-line-BF3eet1v.js +0 -1
  288. package/phanpy/dist/assets/icons/transfer-4-line-oisfvTBw.js +0 -1
  289. package/phanpy/dist/assets/icons/translate-line-BFWol2Ce.js +0 -1
  290. package/phanpy/dist/assets/icons/unlock-line-X_x5vwv3.js +0 -1
  291. package/phanpy/dist/assets/icons/upload-3-line-DgzwUJeW.js +0 -1
  292. package/phanpy/dist/assets/icons/user-4-line-BVSynZCp.js +0 -1
  293. package/phanpy/dist/assets/icons/user-add-2-line-BlGS5Q8q.js +0 -1
  294. package/phanpy/dist/assets/icons/user-add-line-BbXdHG4z.js +0 -1
  295. package/phanpy/dist/assets/icons/user-edit-line-BFAHOV0X.js +0 -1
  296. package/phanpy/dist/assets/icons/user-follow-line-BXux0wSd.js +0 -1
  297. package/phanpy/dist/assets/icons/user-star-line-DdYlpHLU.js +0 -1
  298. package/phanpy/dist/assets/icons/user-warning-line-Cj43j0_E.js +0 -1
  299. package/phanpy/dist/assets/icons/user-x-line-DIDgNlPH.js +0 -1
  300. package/phanpy/dist/assets/icons/volume-line-Bs390KQt.js +0 -1
  301. package/phanpy/dist/assets/icons/volume-mute-line-DzlzIqFZ.js +0 -1
  302. package/phanpy/dist/assets/icons/walk-line-yNXfB-md.js +0 -1
  303. package/phanpy/dist/assets/icons/world-2-line-CenkGIwj.js +0 -1
  304. package/phanpy/dist/assets/icons/zoom-in-line-CaENUphf.js +0 -1
  305. package/phanpy/dist/assets/icons/zoom-out-line-BDNk0EWo.js +0 -1
  306. package/phanpy/dist/assets/instances-BNPptnn-.json +0 -333
  307. package/phanpy/dist/assets/locales/ar-SA-CfheRbCu.js +0 -1
  308. package/phanpy/dist/assets/locales/ca-ES-DHRW86Tz.js +0 -1
  309. package/phanpy/dist/assets/locales/cs-CZ-CKoqEuA1.js +0 -1
  310. package/phanpy/dist/assets/locales/de-DE-ByRPMRFy.js +0 -1
  311. package/phanpy/dist/assets/locales/eo-UY-CPC7wLCS.js +0 -1
  312. package/phanpy/dist/assets/locales/es-ES-dJa0Fd9t.js +0 -1
  313. package/phanpy/dist/assets/locales/eu-ES-C3djlPR2.js +0 -1
  314. package/phanpy/dist/assets/locales/fa-IR-DTT490Ck.js +0 -1
  315. package/phanpy/dist/assets/locales/fi-FI-B23MCPuz.js +0 -1
  316. package/phanpy/dist/assets/locales/fr-FR-BcaQsO5C.js +0 -1
  317. package/phanpy/dist/assets/locales/gl-ES-BR-CVKBN.js +0 -1
  318. package/phanpy/dist/assets/locales/he-IL-CfheRbCu.js +0 -1
  319. package/phanpy/dist/assets/locales/hu-HU-CfheRbCu.js +0 -1
  320. package/phanpy/dist/assets/locales/it-IT-C1136UFV.js +0 -1
  321. package/phanpy/dist/assets/locales/ja-JP-CRZmRAhD.js +0 -1
  322. package/phanpy/dist/assets/locales/kab-Cz8xH_TT.js +0 -1
  323. package/phanpy/dist/assets/locales/ko-KR-CG9UiY4o.js +0 -1
  324. package/phanpy/dist/assets/locales/lt-LT-CJcI6sHZ.js +0 -1
  325. package/phanpy/dist/assets/locales/nb-NO-CfheRbCu.js +0 -1
  326. package/phanpy/dist/assets/locales/nl-NL-DxN4Yc1t.js +0 -1
  327. package/phanpy/dist/assets/locales/oc-FR-CfheRbCu.js +0 -1
  328. package/phanpy/dist/assets/locales/pl-PL-DkwGXf6P.js +0 -1
  329. package/phanpy/dist/assets/locales/pseudo-LOCALE-CQ4XxkQ0.js +0 -1
  330. package/phanpy/dist/assets/locales/pt-BR-nGL0HdFS.js +0 -1
  331. package/phanpy/dist/assets/locales/pt-PT-Cp17aj2z.js +0 -1
  332. package/phanpy/dist/assets/locales/ru-RU-0Y8cIoaK.js +0 -1
  333. package/phanpy/dist/assets/locales/th-TH-CfheRbCu.js +0 -1
  334. package/phanpy/dist/assets/locales/tok-CfheRbCu.js +0 -1
  335. package/phanpy/dist/assets/locales/tr-TR-CfheRbCu.js +0 -1
  336. package/phanpy/dist/assets/locales/uk-UA-C5E3dFBf.js +0 -1
  337. package/phanpy/dist/assets/locales/zh-CN-BHJi7_et.js +0 -1
  338. package/phanpy/dist/assets/locales/zh-TW-CfheRbCu.js +0 -1
  339. package/phanpy/dist/assets/main-BdqNbG-a.js +0 -26
  340. package/phanpy/dist/assets/main-BdqNbG-a.js.map +0 -1
  341. package/phanpy/dist/assets/mock-home-DGhR__He.js +0 -2
  342. package/phanpy/dist/assets/mock-home-DGhR__He.js.map +0 -1
  343. package/phanpy/dist/assets/multi-column-ROck0NVt.jpg +0 -0
  344. package/phanpy/dist/assets/multi-hashtag-timeline-DH5INVHi.jpg +0 -0
  345. package/phanpy/dist/assets/nested-comments-thread-hv59kZLc.jpg +0 -0
  346. package/phanpy/dist/assets/polyfill-force-H8sUkLGp.js +0 -2
  347. package/phanpy/dist/assets/polyfill-force-H8sUkLGp.js.map +0 -1
  348. package/phanpy/dist/assets/pwa-viewport-ogKbF51K.js +0 -20
  349. package/phanpy/dist/assets/pwa-viewport-ogKbF51K.js.map +0 -1
  350. package/phanpy/dist/assets/style-B1iLPTPR.css +0 -2
  351. package/phanpy/dist/assets/temml-DuyIQMei.js +0 -97
  352. package/phanpy/dist/assets/temml-DuyIQMei.js.map +0 -1
  353. package/phanpy/dist/assets/tinyld.light.node-DtbBhLYC.js +0 -2
  354. package/phanpy/dist/assets/tinyld.light.node-DtbBhLYC.js.map +0 -1
  355. package/phanpy/dist/assets/year-in-posts-B2lywqdx.png +0 -0
  356. package/phanpy/dist/assets/year-in-posts-DJv_star.js +0 -3
  357. package/phanpy/dist/assets/year-in-posts-DJv_star.js.map +0 -1
  358. package/phanpy/dist/compose/index.html +0 -20
  359. package/phanpy/dist/favicon.ico +0 -0
  360. package/phanpy/dist/index.html +0 -73
  361. package/phanpy/dist/logo-192.png +0 -0
  362. package/phanpy/dist/logo-512.png +0 -0
  363. package/phanpy/dist/logo-badge-72.png +0 -0
  364. package/phanpy/dist/logo-maskable-512.png +0 -0
  365. package/phanpy/dist/logo-monochrome-512.png +0 -0
  366. package/phanpy/dist/logo-monochrome-maskable-512.png +0 -0
  367. package/phanpy/dist/manifest.webmanifest +0 -1
  368. package/phanpy/dist/og-image-2.jpg +0 -0
  369. package/phanpy/dist/og-image.png +0 -0
  370. package/phanpy/dist/robots.txt +0 -2
  371. package/phanpy/dist/sw.js +0 -2
  372. package/phanpy/dist/sw.js.map +0 -1
  373. package/phanpy/dist/version.json +0 -1
  374. package/run-agent.mjs +0 -899
  375. package/vendor/gate.cjs +0 -138
  376. package/vendor/idp-grant.cjs +0 -394
  377. package/vendor/jfetch.cjs +0 -16
  378. package/web/admin/actors.js +0 -147
  379. package/web/admin/bar.css +0 -126
  380. package/web/admin/bar.js +0 -99
  381. package/web/admin/client/client.js +0 -66
  382. package/web/admin/client/index.html +0 -69
  383. package/web/admin/client-pick.js +0 -79
  384. package/web/admin/common.js +0 -23
  385. package/web/admin/connections.js +0 -112
  386. package/web/admin/gateway.js +0 -133
  387. package/web/admin/group.js +0 -258
  388. package/web/admin/index.html +0 -612
  389. package/web/admin/notices-bar.js +0 -90
  390. package/web/admin/oauth-signin.mjs +0 -37
  391. package/web/admin/record.js +0 -393
  392. package/web/admin/setup/index.html +0 -257
  393. package/web/admin/setup/setup.js +0 -339
  394. package/web/admin/tokens.css +0 -68
  395. package/web/admin/upkeep.js +0 -177
  396. package/web/admin/window.css +0 -55
  397. package/web/admin/window.js +0 -136
  398. package/web/app/README.md +0 -81
  399. package/web/app/admin-facade.mjs +0 -588
  400. package/web/app/agent.mjs +0 -672
  401. package/web/app/atproto-browser.mjs +0 -86
  402. package/web/app/authorize_interaction.html +0 -56
  403. package/web/app/authorize_interaction.js +0 -69
  404. package/web/app/boot.mjs +0 -616
  405. package/web/app/deliver-relay.mjs +0 -129
  406. package/web/app/dist/boot.js +0 -34562
  407. package/web/app/dist/boot.js.map +0 -7
  408. package/web/app/dist/sw.js +0 -73456
  409. package/web/app/dist/sw.js.map +0 -7
  410. package/web/app/fediacct-browser.mjs +0 -146
  411. package/web/app/gateway-move.mjs +0 -60
  412. package/web/app/idb-kv.mjs +0 -35
  413. package/web/app/index.html +0 -258
  414. package/web/app/keys-browser.mjs +0 -71
  415. package/web/app/keystore.mjs +0 -82
  416. package/web/app/oidc-session.mjs +0 -6
  417. package/web/app/pod-auth.mjs +0 -248
  418. package/web/app/pod-remote.mjs +0 -84
  419. package/web/app/shims/_globals.mjs +0 -2
  420. package/web/app/shims/fedify-sig.mjs +0 -77
  421. package/web/app/shims/node-crypto.mjs +0 -113
  422. package/web/app/shims/node-fs.mjs +0 -17
  423. package/web/app/shims/node-path.mjs +0 -19
  424. package/web/app/shims/node-url.mjs +0 -7
  425. package/web/app/shims/prelude.js +0 -58
  426. package/web/app/shims/safefetch.mjs +0 -58
  427. package/web/app/shims/shapes-text.mjs +0 -8
  428. package/web/app/shims/web-push.mjs +0 -14
  429. package/web/app/signup.mjs +0 -357
  430. package/web/app/sw-src.mjs +0 -220
  431. package/web/app/update.js +0 -70
  432. package/web/front/admin.html +0 -55
  433. package/web/front/admin.js +0 -89
  434. package/web/front/install.sh +0 -69
  435. package/web/front/new-account.html +0 -35
  436. package/web/front/new-account.js +0 -13
  437. package/web/front/notices.html +0 -69
  438. package/web/front/notices.js +0 -107
  439. package/web/front/run.html +0 -64
  440. package/web/front/run.js +0 -221
  441. package/web/front/solid-oidc-client.js +0 -6
  442. package/web/icons/forum.svg +0 -11
  443. package/web/icons/gateway.svg +0 -14
  444. package/web/icons/local.svg +0 -11
@@ -1,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
- }