fedipod-server 0.10.0 → 0.12.1
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.
- package/README.md +15 -6
- package/dist/claims.js +1 -1
- package/dist/handler.d.ts +7 -1
- package/dist/handler.js +58 -12
- package/dist/handler.jsonld +5 -1
- package/dist/store-pod.js +18 -4
- package/lib/{c2s.mjs → client/c2s.mjs} +8 -3
- package/lib/{localapi.mjs → client/localapi.mjs} +2 -2
- package/lib/client/masto/accounts.mjs +264 -0
- package/lib/client/masto/body.mjs +69 -0
- package/lib/client/masto/index.mjs +183 -0
- package/lib/client/masto/instance.mjs +104 -0
- package/lib/client/masto/media.mjs +133 -0
- package/lib/client/masto/oauth.mjs +599 -0
- package/lib/client/masto/render.mjs +459 -0
- package/lib/client/masto/statuses.mjs +331 -0
- package/lib/client/masto/timelines.mjs +316 -0
- package/lib/{streaming.mjs → client/streaming.mjs} +1 -1
- package/lib/{acctfeed.mjs → connections/acctfeed.mjs} +1 -1
- package/lib/{atproto.mjs → connections/atproto.mjs} +15 -16
- package/lib/{bskygroup.mjs → connections/bskygroup.mjs} +1 -1
- package/lib/{fediacct.mjs → connections/fediacct.mjs} +31 -35
- package/lib/{import.mjs → connections/import.mjs} +2 -2
- package/lib/{tagfeed.mjs → connections/tagfeed.mjs} +21 -4
- package/lib/connections/vault.mjs +114 -0
- package/lib/core/as2.mjs +124 -0
- package/lib/core/contexts/activitystreams.json +379 -0
- package/lib/core/contexts/did-v1.json +57 -0
- package/lib/core/contexts/fep-5711.json +36 -0
- package/lib/core/contexts/gotosocial.json +86 -0
- package/lib/core/contexts/identity-v1.json +152 -0
- package/lib/core/contexts/index.mjs +45 -0
- package/lib/core/contexts/join-lemmy.json +33 -0
- package/lib/core/contexts/joinmastodon.json +28 -0
- package/lib/core/contexts/map.json +16 -0
- package/lib/core/contexts/miscellany.json +19 -0
- package/lib/core/contexts/schemaorg.json +8845 -0
- package/lib/core/contexts/security-data-integrity-v1.json +78 -0
- package/lib/core/contexts/security-data-integrity-v2.json +81 -0
- package/lib/core/contexts/security-multikey-v1.json +35 -0
- package/lib/core/contexts/security-v1.json +74 -0
- package/lib/core/contexts/webfinger.json +10 -0
- package/lib/{deliver.mjs → core/deliver.mjs} +8 -2
- package/lib/core/intake/activities.mjs +437 -0
- package/lib/core/intake/activity.mjs +240 -0
- package/lib/core/intake/channel.mjs +144 -0
- package/lib/core/intake/group.mjs +222 -0
- package/lib/core/intake/index.mjs +629 -0
- package/lib/core/intake/notes.mjs +288 -0
- package/lib/core/intake/verify.mjs +141 -0
- package/lib/{keys.mjs → core/keys.mjs} +1 -1
- package/lib/core/publisher/collections.mjs +229 -0
- package/lib/core/publisher/index.mjs +421 -0
- package/lib/core/publisher/notes.mjs +188 -0
- package/lib/core/publisher/questions.mjs +233 -0
- package/lib/core/publisher/restore.mjs +196 -0
- package/lib/core/shapes/activitystreams.ttl +129 -0
- package/lib/core/shapes/index.mjs +107 -0
- package/lib/core/shapes/shapes-text.mjs +13 -0
- package/lib/{social.mjs → core/social.mjs} +19 -10
- package/lib/{storage.mjs → core/storage.mjs} +2 -2
- package/lib/{store.mjs → core/store.mjs} +12 -1
- package/lib/{wire.mjs → core/wire.mjs} +48 -67
- package/lib/device/admin/index.mjs +13 -0
- package/lib/device/admin/origins.mjs +35 -0
- package/lib/device/admin/routes/connections.mjs +144 -0
- package/lib/device/admin/routes/gateway.mjs +199 -0
- package/lib/device/admin/routes/lifecycle.mjs +191 -0
- package/lib/device/admin/routes/owner.mjs +322 -0
- package/lib/device/admin/routes/setup.mjs +393 -0
- package/lib/device/admin/routes/social.mjs +188 -0
- package/lib/device/admin/server.mjs +95 -0
- package/lib/device/admin/static.mjs +244 -0
- package/lib/device/admin/surface.mjs +274 -0
- package/lib/device/cli/commands/account.mjs +586 -0
- package/lib/device/cli/commands/run.mjs +278 -0
- package/lib/device/cli/commands/service.mjs +221 -0
- package/lib/device/cli/commands/setup.mjs +410 -0
- package/lib/device/cli/commands/state.mjs +559 -0
- package/lib/device/cli/context.mjs +288 -0
- package/lib/{export-collections.mjs → device/export-collections.mjs} +1 -1
- package/lib/{migrate.mjs → device/migrate.mjs} +5 -19
- package/lib/device/remote.mjs +54 -0
- package/lib/{setup.mjs → device/setup.mjs} +3 -3
- package/lib/{update.mjs → device/update.mjs} +1 -1
- package/lib/{directory.mjs → gateway/directory.mjs} +1 -1
- package/lib/{front-core.mjs → gateway/front-core.mjs} +272 -49
- package/lib/{gateway-core.mjs → gateway/gateway-core.mjs} +3 -7
- package/lib/{httpsig.mjs → gateway/httpsig.mjs} +34 -3
- package/lib/pod/README.md +61 -0
- package/lib/pod/actor.mjs +96 -0
- package/lib/pod/collection.mjs +77 -0
- package/lib/pod/containers.mjs +136 -0
- package/lib/pod/discovery.mjs +46 -0
- package/lib/pod/featured.mjs +18 -0
- package/lib/pod/followers.mjs +24 -0
- package/lib/pod/following.mjs +10 -0
- package/lib/pod/http.mjs +74 -0
- package/lib/pod/inbox.mjs +144 -0
- package/lib/{links.mjs → pod/links.mjs} +1 -1
- package/lib/pod/media.mjs +27 -0
- package/lib/pod/notes.mjs +80 -0
- package/lib/pod/notifications.mjs +89 -0
- package/lib/pod/outbox.mjs +18 -0
- package/lib/pod/package.json +19 -0
- package/lib/pod/policy.mjs +58 -0
- package/lib/pod/private.mjs +19 -0
- package/lib/pod/root.mjs +63 -0
- package/lib/pod/state.mjs +39 -0
- package/lib/{remote.mjs → pod/transport.mjs} +217 -159
- package/lib/pod/urls.mjs +71 -0
- package/lib/{embed.mjs → server/embed.mjs} +149 -22
- package/lib/shared/links.mjs +9 -0
- package/lib/{ua.mjs → shared/ua.mjs} +1 -1
- package/package.json +1 -1
- package/run-agent.mjs +47 -64
- package/web/admin/actors.js +145 -0
- package/web/admin/bar.css +9 -13
- package/web/admin/bar.js +2 -2
- package/web/admin/client/client.js +1 -1
- package/web/admin/client/index.html +5 -6
- package/web/admin/common.js +23 -0
- package/web/admin/connections.js +112 -0
- package/web/admin/gateway.js +111 -0
- package/web/admin/group.js +258 -0
- package/web/admin/index.html +60 -66
- package/web/admin/record.js +378 -0
- package/web/admin/setup/index.html +3 -1
- package/web/admin/setup/setup.js +2 -13
- package/web/admin/tokens.css +56 -0
- package/web/admin/upkeep.js +170 -0
- package/web/app/README.md +77 -0
- package/web/app/admin-facade.mjs +549 -0
- package/web/app/agent.mjs +490 -0
- package/web/app/atproto-browser.mjs +86 -0
- package/web/app/boot.mjs +340 -0
- package/web/app/deliver-relay.mjs +72 -0
- package/web/app/dist/boot.js +34067 -0
- package/web/app/dist/boot.js.map +7 -0
- package/web/app/dist/sw.js +70014 -0
- package/web/app/dist/sw.js.map +7 -0
- package/web/app/fediacct-browser.mjs +146 -0
- package/web/app/idb-kv.mjs +35 -0
- package/web/app/index.html +203 -0
- package/web/app/index.html~ +152 -0
- package/web/app/keys-browser.mjs +46 -0
- package/web/app/keystore.mjs +99 -0
- package/web/app/oidc-session.mjs +189 -0
- package/web/app/pod-auth.mjs +248 -0
- package/web/app/pod-remote.mjs +84 -0
- package/web/app/shims/_globals.mjs +2 -0
- package/web/app/shims/fedify-sig.mjs +77 -0
- package/web/app/shims/node-crypto.mjs +113 -0
- package/web/app/shims/node-fs.mjs +17 -0
- package/web/app/shims/node-path.mjs +19 -0
- package/web/app/shims/node-url.mjs +7 -0
- package/web/app/shims/prelude.js +58 -0
- package/web/app/shims/safefetch.mjs +58 -0
- package/web/app/shims/shapes-text.mjs +8 -0
- package/web/app/shims/web-push.mjs +14 -0
- package/web/app/signup.mjs +248 -0
- package/web/app/site/_headers +9 -0
- package/web/app/site/_redirects +21 -0
- package/web/app/site/admin/actors.js +145 -0
- package/web/app/site/admin/bar.css +60 -0
- package/web/app/site/admin/bar.js +61 -0
- package/web/app/site/admin/client/client.js +66 -0
- package/web/app/site/admin/client/index.html +43 -0
- package/web/app/site/admin/common.js +23 -0
- package/web/app/site/admin/connections.js +112 -0
- package/web/app/site/admin/gateway.js +111 -0
- package/web/app/site/admin/group.js +258 -0
- package/web/app/site/admin/index.html +562 -0
- package/web/app/site/admin/record.js +378 -0
- package/web/app/site/admin/setup/index.html +192 -0
- package/web/app/site/admin/setup/setup.js +297 -0
- package/web/app/site/admin/tokens.css +56 -0
- package/web/app/site/admin/upkeep.js +170 -0
- package/web/app/site/admin/window.css +55 -0
- package/web/app/site/admin/window.js +136 -0
- package/web/app/site/app/404.html +40 -0
- package/web/app/site/app/_headers +2 -0
- package/web/app/site/app/apple-touch-icon.png +0 -0
- package/web/app/site/app/assets/ICONS-DIPJeU0C.js +2 -0
- package/web/app/site/app/assets/ICONS-DIPJeU0C.js.map +1 -0
- package/web/app/site/app/assets/ScheduledAtField-D-Abfo4J.js +183 -0
- package/web/app/site/app/assets/ScheduledAtField-D-Abfo4J.js.map +1 -0
- package/web/app/site/app/assets/Temml-CCjoacWt.woff2 +0 -0
- package/web/app/site/app/assets/__vite-browser-external-C7iut881.js +2 -0
- package/web/app/site/app/assets/__vite-browser-external-C7iut881.js.map +1 -0
- package/web/app/site/app/assets/boosts-carousel-BiOaNdMT.jpg +0 -0
- package/web/app/site/app/assets/catch-up-O5q5C75Z.png +0 -0
- package/web/app/site/app/assets/chunk-aKtaBQYM.js +1 -0
- package/web/app/site/app/assets/compose-CFEx7wb4.js +2 -0
- package/web/app/site/app/assets/compose-CFEx7wb4.js.map +1 -0
- package/web/app/site/app/assets/compose-ZJN5xSb4.js +35 -0
- package/web/app/site/app/assets/compose-ZJN5xSb4.js.map +1 -0
- package/web/app/site/app/assets/debug.module-YFQgwnIr.js +52 -0
- package/web/app/site/app/assets/debug.module-YFQgwnIr.js.map +1 -0
- package/web/app/site/app/assets/dom-p1YcJ5Rw.js +2 -0
- package/web/app/site/app/assets/dom-p1YcJ5Rw.js.map +1 -0
- package/web/app/site/app/assets/home-mobile-dark@2x-Dc-J8-SM.png +0 -0
- package/web/app/site/app/assets/home-mobile-light@2x-Bd3fod8m.png +0 -0
- package/web/app/site/app/assets/home-tablet-dark@2x-BgcNI8le.png +0 -0
- package/web/app/site/app/assets/home-tablet-light@2x-BW2RV9T6.png +0 -0
- package/web/app/site/app/assets/icons/add-circle-line-WD0mwORD.js +1 -0
- package/web/app/site/app/assets/icons/alert-line-CpFUMf9s.js +1 -0
- package/web/app/site/app/assets/icons/android-2-line-Bzxnrd3S.js +1 -0
- package/web/app/site/app/assets/icons/announcement-line-5nwaJFJm.js +1 -0
- package/web/app/site/app/assets/icons/arrow-down-circle-line-a7Id9-Iv.js +1 -0
- package/web/app/site/app/assets/icons/arrow-down-line-B8uzXzq6.js +1 -0
- package/web/app/site/app/assets/icons/arrow-left-line-CGrGT9C-.js +1 -0
- package/web/app/site/app/assets/icons/arrow-right-line-CE-HERfX.js +1 -0
- package/web/app/site/app/assets/icons/arrow-to-up-line-CvdQjmcy.js +1 -0
- package/web/app/site/app/assets/icons/arrow-up-circle-line-DEparSNF.js +1 -0
- package/web/app/site/app/assets/icons/arrow-up-line-C7yBeugc.js +1 -0
- package/web/app/site/app/assets/icons/arrows-right-line-BtbXmJGP.js +1 -0
- package/web/app/site/app/assets/icons/at-line-CF66Bznk.js +1 -0
- package/web/app/site/app/assets/icons/attachment-line-BY8It-l9.js +1 -0
- package/web/app/site/app/assets/icons/blockquote-line-BS6pU_hm.js +1 -0
- package/web/app/site/app/assets/icons/board-line-C0UWXyA6.js +1 -0
- package/web/app/site/app/assets/icons/bookmark-line-1CtX5JLF.js +1 -0
- package/web/app/site/app/assets/icons/building-5-line-Dd59MzbO.js +1 -0
- package/web/app/site/app/assets/icons/bus-2-line-6_dvA0GG.js +1 -0
- package/web/app/site/app/assets/icons/calendar-day-line-Crpl_fmu.js +1 -0
- package/web/app/site/app/assets/icons/calendar-month-line-vommts2C.js +1 -0
- package/web/app/site/app/assets/icons/calendar-time-add-line-BU_DTlQk.js +1 -0
- package/web/app/site/app/assets/icons/camera-line-Cv69MEei.js +1 -0
- package/web/app/site/app/assets/icons/celebrate-line-B8DvIoi0.js +1 -0
- package/web/app/site/app/assets/icons/chart-bar-line-FK-QOmtF.js +1 -0
- package/web/app/site/app/assets/icons/chart-line-line-BGcJAFWw.js +1 -0
- package/web/app/site/app/assets/icons/chat-3-line-YFK2-hwz.js +1 -0
- package/web/app/site/app/assets/icons/check-circle-line-DsAxFr5-.js +1 -0
- package/web/app/site/app/assets/icons/clipboard-line-BBp364gJ.js +1 -0
- package/web/app/site/app/assets/icons/close-circle-line-CICdiuGz.js +1 -0
- package/web/app/site/app/assets/icons/close-line-DYrFWcjv.js +1 -0
- package/web/app/site/app/assets/icons/cloud-line-tiQAbah8.js +1 -0
- package/web/app/site/app/assets/icons/code-line-fAl8iiV3.js +1 -0
- package/web/app/site/app/assets/icons/comment-2-line-CR3g7WeB.js +1 -0
- package/web/app/site/app/assets/icons/copy-2-line-CLW69yF5.js +1 -0
- package/web/app/site/app/assets/icons/delete-2-line-B8xmbr4n.js +1 -0
- package/web/app/site/app/assets/icons/document-3-line-DBGNhAg2.js +1 -0
- package/web/app/site/app/assets/icons/document-line-C7mb9sb7.js +1 -0
- package/web/app/site/app/assets/icons/down-line-BGqSSThd.js +1 -0
- package/web/app/site/app/assets/icons/edit-4-line-DcgGaEhx.js +1 -0
- package/web/app/site/app/assets/icons/emoji-2-line-D8YgnM-y.js +1 -0
- package/web/app/site/app/assets/icons/exit-line-Dc07BS-x.js +1 -0
- package/web/app/site/app/assets/icons/external-link-line-BvpSkmjO.js +1 -0
- package/web/app/site/app/assets/icons/eye-2-line-CiWXx-f_.js +1 -0
- package/web/app/site/app/assets/icons/eye-close-line-BgTaSJ4P.js +1 -0
- package/web/app/site/app/assets/icons/filter-2-line-B_mR2usy.js +1 -0
- package/web/app/site/app/assets/icons/filter-line-C_nm-3cU.js +1 -0
- package/web/app/site/app/assets/icons/flag-1-line-C2HB05I0.js +1 -0
- package/web/app/site/app/assets/icons/forbid-circle-line-D3LTT9yd.js +1 -0
- package/web/app/site/app/assets/icons/formula-line-DLrt2d6j.js +1 -0
- package/web/app/site/app/assets/icons/grid-line-CgUmEikf.js +1 -0
- package/web/app/site/app/assets/icons/group-line-C5fnX0wU.js +1 -0
- package/web/app/site/app/assets/icons/hand-finger-2-line-BGgxbuL9.js +1 -0
- package/web/app/site/app/assets/icons/hashtag-line-CyTEEXBb.js +1 -0
- package/web/app/site/app/assets/icons/heart-crack-line-BZddbO2C.js +1 -0
- package/web/app/site/app/assets/icons/heart-line-CJ7K47J4.js +1 -0
- package/web/app/site/app/assets/icons/history-2-line-kWWvFzqv.js +1 -0
- package/web/app/site/app/assets/icons/history-line-bGQiwbTH.js +1 -0
- package/web/app/site/app/assets/icons/home-3-line-fRU1zeLG.js +1 -0
- package/web/app/site/app/assets/icons/information-line-iNk6-sOY.js +1 -0
- package/web/app/site/app/assets/icons/keyboard-line-DsCyuQNz.js +1 -0
- package/web/app/site/app/assets/icons/layout-4-line-CZkOX52Z.js +1 -0
- package/web/app/site/app/assets/icons/layout-5-line-B_znt5wO.js +1 -0
- package/web/app/site/app/assets/icons/left-line-eUw0tya4.js +1 -0
- package/web/app/site/app/assets/icons/lightning-line-DaKoDdix.js +1 -0
- package/web/app/site/app/assets/icons/link-2-line-DCqLmMA7.js +1 -0
- package/web/app/site/app/assets/icons/list-check-line-BAxosBh9.js +1 -0
- package/web/app/site/app/assets/icons/lock-line-CPoaXeUW.js +1 -0
- package/web/app/site/app/assets/icons/mail-line-B0P8aa1L.js +1 -0
- package/web/app/site/app/assets/icons/moon-line-CEqNAToP.js +1 -0
- package/web/app/site/app/assets/icons/more-1-fill-Dw2e5xab.js +1 -0
- package/web/app/site/app/assets/icons/more-3-line-BIiVjYTz.js +1 -0
- package/web/app/site/app/assets/icons/notification-line-BZZq2Gtu.js +1 -0
- package/web/app/site/app/assets/icons/pencil-line-CKh8-A1v.js +1 -0
- package/web/app/site/app/assets/icons/photo-album-line-Bnfg2u40.js +1 -0
- package/web/app/site/app/assets/icons/pin-line-DpTolyhs.js +1 -0
- package/web/app/site/app/assets/icons/play-fill-B0-G_8lX.js +1 -0
- package/web/app/site/app/assets/icons/qrcode-2-line-B6SPCaHn.js +1 -0
- package/web/app/site/app/assets/icons/quill-pen-line-B6LUrnCP.js +1 -0
- package/web/app/site/app/assets/icons/quote-left-fill-hQZ1cj_6.js +1 -0
- package/web/app/site/app/assets/icons/radar-line-CKyroTth.js +1 -0
- package/web/app/site/app/assets/icons/react-line-CoIKhhtK.js +1 -0
- package/web/app/site/app/assets/icons/refresh-2-line-D0mTF97S.js +1 -0
- package/web/app/site/app/assets/icons/right-line-B3KWJaqc.js +1 -0
- package/web/app/site/app/assets/icons/rocket-line-4KZl537D.js +1 -0
- package/web/app/site/app/assets/icons/round-fill-C69NTrZ0.js +1 -0
- package/web/app/site/app/assets/icons/round-line-B9k_1uo-.js +1 -0
- package/web/app/site/app/assets/icons/route-line-B28ReHcE.js +1 -0
- package/web/app/site/app/assets/icons/rows-4-line-CrBniagM.js +1 -0
- package/web/app/site/app/assets/icons/scan-line-DSf74YdP.js +1 -0
- package/web/app/site/app/assets/icons/search-2-line-B6geIU1z.js +1 -0
- package/web/app/site/app/assets/icons/settings-3-line-azJwK47w.js +1 -0
- package/web/app/site/app/assets/icons/settings-6-line-C_5zc5YR.js +1 -0
- package/web/app/site/app/assets/icons/share-2-line-D5MxWjrm.js +1 -0
- package/web/app/site/app/assets/icons/share-forward-line-Cp61fZzZ.js +1 -0
- package/web/app/site/app/assets/icons/sparkles-2-line-DYYyXTaW.js +1 -0
- package/web/app/site/app/assets/icons/sparkles-line-C2hY1s6I.js +1 -0
- package/web/app/site/app/assets/icons/time-line-BF3eet1v.js +1 -0
- package/web/app/site/app/assets/icons/transfer-4-line-oisfvTBw.js +1 -0
- package/web/app/site/app/assets/icons/translate-line-BFWol2Ce.js +1 -0
- package/web/app/site/app/assets/icons/unlock-line-X_x5vwv3.js +1 -0
- package/web/app/site/app/assets/icons/upload-3-line-DgzwUJeW.js +1 -0
- package/web/app/site/app/assets/icons/user-4-line-BVSynZCp.js +1 -0
- package/web/app/site/app/assets/icons/user-add-2-line-BlGS5Q8q.js +1 -0
- package/web/app/site/app/assets/icons/user-add-line-BbXdHG4z.js +1 -0
- package/web/app/site/app/assets/icons/user-edit-line-BFAHOV0X.js +1 -0
- package/web/app/site/app/assets/icons/user-follow-line-BXux0wSd.js +1 -0
- package/web/app/site/app/assets/icons/user-star-line-DdYlpHLU.js +1 -0
- package/web/app/site/app/assets/icons/user-warning-line-Cj43j0_E.js +1 -0
- package/web/app/site/app/assets/icons/user-x-line-DIDgNlPH.js +1 -0
- package/web/app/site/app/assets/icons/volume-line-Bs390KQt.js +1 -0
- package/web/app/site/app/assets/icons/volume-mute-line-DzlzIqFZ.js +1 -0
- package/web/app/site/app/assets/icons/walk-line-yNXfB-md.js +1 -0
- package/web/app/site/app/assets/icons/world-2-line-CenkGIwj.js +1 -0
- package/web/app/site/app/assets/icons/zoom-in-line-CaENUphf.js +1 -0
- package/web/app/site/app/assets/icons/zoom-out-line-BDNk0EWo.js +1 -0
- package/web/app/site/app/assets/instances-BNPptnn-.json +333 -0
- package/web/app/site/app/assets/locales/ar-SA-CfheRbCu.js +1 -0
- package/web/app/site/app/assets/locales/ca-ES-DHRW86Tz.js +1 -0
- package/web/app/site/app/assets/locales/cs-CZ-CKoqEuA1.js +1 -0
- package/web/app/site/app/assets/locales/de-DE-ByRPMRFy.js +1 -0
- package/web/app/site/app/assets/locales/eo-UY-CPC7wLCS.js +1 -0
- package/web/app/site/app/assets/locales/es-ES-dJa0Fd9t.js +1 -0
- package/web/app/site/app/assets/locales/eu-ES-C3djlPR2.js +1 -0
- package/web/app/site/app/assets/locales/fa-IR-DTT490Ck.js +1 -0
- package/web/app/site/app/assets/locales/fi-FI-B23MCPuz.js +1 -0
- package/web/app/site/app/assets/locales/fr-FR-BcaQsO5C.js +1 -0
- package/web/app/site/app/assets/locales/gl-ES-BR-CVKBN.js +1 -0
- package/web/app/site/app/assets/locales/he-IL-CfheRbCu.js +1 -0
- package/web/app/site/app/assets/locales/hu-HU-CfheRbCu.js +1 -0
- package/web/app/site/app/assets/locales/it-IT-C1136UFV.js +1 -0
- package/web/app/site/app/assets/locales/ja-JP-CRZmRAhD.js +1 -0
- package/web/app/site/app/assets/locales/kab-Cz8xH_TT.js +1 -0
- package/web/app/site/app/assets/locales/ko-KR-CG9UiY4o.js +1 -0
- package/web/app/site/app/assets/locales/lt-LT-CJcI6sHZ.js +1 -0
- package/web/app/site/app/assets/locales/nb-NO-CfheRbCu.js +1 -0
- package/web/app/site/app/assets/locales/nl-NL-DxN4Yc1t.js +1 -0
- package/web/app/site/app/assets/locales/oc-FR-CfheRbCu.js +1 -0
- package/web/app/site/app/assets/locales/pl-PL-DkwGXf6P.js +1 -0
- package/web/app/site/app/assets/locales/pseudo-LOCALE-CQ4XxkQ0.js +1 -0
- package/web/app/site/app/assets/locales/pt-BR-nGL0HdFS.js +1 -0
- package/web/app/site/app/assets/locales/pt-PT-Cp17aj2z.js +1 -0
- package/web/app/site/app/assets/locales/ru-RU-0Y8cIoaK.js +1 -0
- package/web/app/site/app/assets/locales/th-TH-CfheRbCu.js +1 -0
- package/web/app/site/app/assets/locales/tok-CfheRbCu.js +1 -0
- package/web/app/site/app/assets/locales/tr-TR-CfheRbCu.js +1 -0
- package/web/app/site/app/assets/locales/uk-UA-C5E3dFBf.js +1 -0
- package/web/app/site/app/assets/locales/zh-CN-BHJi7_et.js +1 -0
- package/web/app/site/app/assets/locales/zh-TW-CfheRbCu.js +1 -0
- package/web/app/site/app/assets/main-BdqNbG-a.js +26 -0
- package/web/app/site/app/assets/main-BdqNbG-a.js.map +1 -0
- package/web/app/site/app/assets/mock-home-DGhR__He.js +2 -0
- package/web/app/site/app/assets/mock-home-DGhR__He.js.map +1 -0
- package/web/app/site/app/assets/multi-column-ROck0NVt.jpg +0 -0
- package/web/app/site/app/assets/multi-hashtag-timeline-DH5INVHi.jpg +0 -0
- package/web/app/site/app/assets/nested-comments-thread-hv59kZLc.jpg +0 -0
- package/web/app/site/app/assets/polyfill-force-H8sUkLGp.js +2 -0
- package/web/app/site/app/assets/polyfill-force-H8sUkLGp.js.map +1 -0
- package/web/app/site/app/assets/pwa-viewport-ogKbF51K.js +20 -0
- package/web/app/site/app/assets/pwa-viewport-ogKbF51K.js.map +1 -0
- package/web/app/site/app/assets/style-B1iLPTPR.css +2 -0
- package/web/app/site/app/assets/temml-DuyIQMei.js +97 -0
- package/web/app/site/app/assets/temml-DuyIQMei.js.map +1 -0
- package/web/app/site/app/assets/tinyld.light.node-DtbBhLYC.js +2 -0
- package/web/app/site/app/assets/tinyld.light.node-DtbBhLYC.js.map +1 -0
- package/web/app/site/app/assets/year-in-posts-B2lywqdx.png +0 -0
- package/web/app/site/app/assets/year-in-posts-DJv_star.js +3 -0
- package/web/app/site/app/assets/year-in-posts-DJv_star.js.map +1 -0
- package/web/app/site/app/compose/index.html +20 -0
- package/web/app/site/app/favicon.ico +0 -0
- package/web/app/site/app/index.html +73 -0
- package/web/app/site/app/logo-192.png +0 -0
- package/web/app/site/app/logo-512.png +0 -0
- package/web/app/site/app/logo-badge-72.png +0 -0
- package/web/app/site/app/logo-maskable-512.png +0 -0
- package/web/app/site/app/logo-monochrome-512.png +0 -0
- package/web/app/site/app/logo-monochrome-maskable-512.png +0 -0
- package/web/app/site/app/manifest.webmanifest +1 -0
- package/web/app/site/app/og-image-2.jpg +0 -0
- package/web/app/site/app/og-image.png +0 -0
- package/web/app/site/app/robots.txt +2 -0
- package/web/app/site/app/version.json +1 -0
- package/web/app/site/boot.js +34067 -0
- package/web/app/site/index.html +203 -0
- package/web/app/site/sw.js +70014 -0
- package/web/app/sw-src.mjs +220 -0
- package/web/front/#new-account.html# +43 -0
- package/web/front/admin.html +1 -81
- package/web/front/admin.js +85 -0
- package/web/front/new-account.html +1 -9
- package/web/front/new-account.js +13 -0
- package/web/front/run.html +1 -92
- package/web/front/run.js +107 -0
- package/lib/admin.mjs +0 -1913
- package/lib/intake.mjs +0 -1764
- package/lib/mastoapi.mjs +0 -1947
- package/lib/podrdf.mjs +0 -132
- package/lib/publisher.mjs +0 -1263
- package/web/admin/admin.js +0 -1114
- /package/lib/{oidc-auth.mjs → client/oidc-auth.mjs} +0 -0
- /package/lib/{webpush.mjs → client/webpush.mjs} +0 -0
- /package/lib/{bskyfeed.mjs → connections/bskyfeed.mjs} +0 -0
- /package/lib/{lease.mjs → core/lease.mjs} +0 -0
- /package/lib/{polls.mjs → core/polls.mjs} +0 -0
- /package/lib/{proof.mjs → core/proof.mjs} +0 -0
- /package/lib/{account.mjs → device/account.mjs} +0 -0
- /package/lib/{certs.mjs → device/certs.mjs} +0 -0
- /package/lib/{home.mjs → device/home.mjs} +0 -0
- /package/lib/{ports.mjs → device/ports.mjs} +0 -0
- /package/lib/{guard.mjs → shared/guard.mjs} +0 -0
- /package/lib/{safefetch.mjs → shared/safefetch.mjs} +0 -0
|
@@ -1,11 +1,20 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
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.
|
|
4
14
|
|
|
5
|
-
import { createRequire } from 'node:module';
|
|
6
|
-
import path from 'node:path';
|
|
7
|
-
import { fileURLToPath } from 'node:url';
|
|
8
15
|
import * as $rdf from 'rdflib';
|
|
16
|
+
import { readCapped, retryAfterMs } from './http.mjs';
|
|
17
|
+
import { linkTargets, REL } from './links.mjs';
|
|
9
18
|
|
|
10
19
|
const LDP = $rdf.Namespace('http://www.w3.org/ns/ldp#');
|
|
11
20
|
const DC = $rdf.Namespace('http://purl.org/dc/terms/');
|
|
@@ -15,24 +24,8 @@ const ACL = $rdf.Namespace('http://www.w3.org/ns/auth/acl#');
|
|
|
15
24
|
const FOAF = $rdf.Namespace('http://xmlns.com/foaf/0.1/');
|
|
16
25
|
const AS = $rdf.Namespace('https://www.w3.org/ns/activitystreams#');
|
|
17
26
|
|
|
18
|
-
const require = createRequire(import.meta.url);
|
|
19
|
-
const vendorDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../vendor');
|
|
20
|
-
const { mintCredential, discoverTokenEndpoint, createGrantSession, revokeCredentialViaAccount } =
|
|
21
|
-
require(path.join(vendorDir, 'idp-grant.cjs'));
|
|
22
|
-
|
|
23
|
-
export { mintCredential, discoverTokenEndpoint, revokeCredentialViaAccount };
|
|
24
|
-
|
|
25
|
-
// A pod that answers 429 or 503 is asking to be left alone; Retry-After says
|
|
26
|
-
// for how long. Every request to this pod goes through one cooldown, so a
|
|
27
|
-
// server under strain gets silence rather than each timer discovering the
|
|
28
|
-
// refusal separately. Requests during the window fail fast — no socket is
|
|
29
|
-
// opened — and the callers' existing retry paths take it from there.
|
|
30
|
-
// The parser is shared with the outbound path; see lib/safefetch.mjs.
|
|
31
|
-
import { retryAfterMs, readCapped } from './safefetch.mjs';
|
|
32
|
-
import { linkTargets, REL } from './links.mjs';
|
|
33
|
-
|
|
34
27
|
// A pod whose access rules are ACP policies, not WAC authorizations. This
|
|
35
|
-
//
|
|
28
|
+
// library writes WAC; over an ACP resource that would be noise where the pod's
|
|
36
29
|
// real rules used to be, so it stops instead.
|
|
37
30
|
const ACP_NS = 'http://www.w3.org/ns/solid/acp#';
|
|
38
31
|
|
|
@@ -40,13 +33,19 @@ const ACP_NS = 'http://www.w3.org/ns/solid/acp#';
|
|
|
40
33
|
// hands; reading it whole must still have a ceiling.
|
|
41
34
|
const LISTING_MAX_BYTES = 10 * 1024 * 1024;
|
|
42
35
|
|
|
36
|
+
// No Retry-After header still means "not now". Sixty seconds is the answer
|
|
37
|
+
// when the server declined to give one.
|
|
38
|
+
const COOLDOWN_DEFAULT_MS = 60_000;
|
|
39
|
+
|
|
40
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
41
|
+
|
|
43
42
|
// A pod's ability to BE a pod. Not data, and never ours to remove: delete one
|
|
44
43
|
// of these and the pod does not degrade, it stops — and it cannot be repaired
|
|
45
44
|
// by the tool that broke it, because that tool can no longer authenticate. A
|
|
46
45
|
// pod was crippled exactly this way.
|
|
47
46
|
//
|
|
48
|
-
// Here rather than in any one
|
|
49
|
-
// goes through
|
|
47
|
+
// Here rather than in any one caller, because every DELETE this library sends
|
|
48
|
+
// goes through PodTransport.fetch — so this is the one place that cannot be
|
|
50
49
|
// routed around. A DENY-list, not an allow-prefix: the next script will have a
|
|
51
50
|
// different prefix and the same list of things it must never touch.
|
|
52
51
|
const PROTECTED = [
|
|
@@ -70,100 +69,160 @@ export function protectedFromDeletion(url) {
|
|
|
70
69
|
if (p === '/' || p === '') throw new Error(`refusing to DELETE the pod root: ${url}`);
|
|
71
70
|
for (const [re, why] of PROTECTED) {
|
|
72
71
|
if (re.test(p)) {
|
|
73
|
-
throw new Error(`refusing to DELETE ${url} — ${why}. Deny-list: lib/
|
|
72
|
+
throw new Error(`refusing to DELETE ${url} — ${why}. Deny-list: lib/pod/transport.mjs.`);
|
|
74
73
|
}
|
|
75
74
|
}
|
|
76
75
|
}
|
|
77
76
|
|
|
78
|
-
export class
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
77
|
+
export class PodTransport {
|
|
78
|
+
/**
|
|
79
|
+
* @param {object} session `{ fetch(url, init) }` — injected, never built here
|
|
80
|
+
* @param {object} opts
|
|
81
|
+
* @param {string} opts.webId who this transport acts as; the ACL owner
|
|
82
|
+
* @param {string} [opts.role] 'agent' | 'gateway' | 'signup' — what is talking
|
|
83
|
+
* @param {string} [opts.runtime] 'node' | 'browser' — where it is talking from
|
|
84
|
+
* @param {string} [opts.cooldownMode] 'refuse' fails fast for the window;
|
|
85
|
+
* 'wait' sleeps it out and continues
|
|
86
|
+
* @param {number} [opts.maxCooldownMs] ceiling on a Retry-After we will honour
|
|
87
|
+
*/
|
|
88
|
+
constructor(session, {
|
|
89
|
+
webId, log = () => {}, role = 'agent', runtime = 'node',
|
|
90
|
+
cooldownMode = 'refuse', maxCooldownMs = 30 * 60_000,
|
|
91
|
+
} = {}) {
|
|
92
|
+
this.session = session;
|
|
93
|
+
this.webId = webId;
|
|
94
|
+
this.log = log;
|
|
95
|
+
this.role = role;
|
|
96
|
+
this.runtime = runtime;
|
|
97
|
+
this.cooldownMode = cooldownMode;
|
|
98
|
+
this.maxCooldownMs = maxCooldownMs;
|
|
88
99
|
this.pausedUntil = 0;
|
|
89
100
|
this.probeCount = 0;
|
|
90
|
-
this.log = log;
|
|
91
101
|
// Where each resource's access control lives, as the pod itself said. WAC
|
|
92
102
|
// forbids working it out from the resource's own URL, so it is asked for
|
|
93
103
|
// and remembered rather than assembled.
|
|
94
104
|
this.aclUrls = new Map();
|
|
95
105
|
this.aclFlavour = null; // null until the first write asks what this pod speaks
|
|
106
|
+
this._listCache = new Map();
|
|
96
107
|
// A fronted identity advertises ids on a shared domain but writes to the
|
|
97
|
-
// pod.
|
|
98
|
-
// built from an advertised id lands on the pod — one choke point
|
|
99
|
-
//
|
|
100
|
-
// the unfronted behaviour.
|
|
108
|
+
// pod. The caller installs the advertised→pod mapping here, so every
|
|
109
|
+
// request built from an advertised id lands on the pod — one choke point.
|
|
110
|
+
// Unset → identity, byte-for-byte the unfronted behaviour.
|
|
101
111
|
this.toPod = null;
|
|
102
112
|
}
|
|
103
113
|
|
|
104
|
-
|
|
114
|
+
/** Who is talking, and from where — the prefix on every log line and error. */
|
|
115
|
+
get label() { return `${this.role}/${this.runtime}`; }
|
|
116
|
+
|
|
117
|
+
/** One line for a boot log: what this transport is and what it points at. */
|
|
118
|
+
describe() {
|
|
119
|
+
return `${this.role} over a ${this.runtime} session → ${this.webId?.split('#')[0] || 'unknown'}`;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Install the advertised→pod url map for a fronted identity (urls.toPod). */
|
|
105
123
|
setUrlMap(fn) { this.toPod = typeof fn === 'function' ? fn : null; }
|
|
106
124
|
|
|
107
|
-
async warmup() { return this.session.warmup(); }
|
|
125
|
+
async warmup() { return this.session.warmup?.(); }
|
|
108
126
|
|
|
109
|
-
|
|
127
|
+
/** What we have asked of this pod, for a status page or an operator. */
|
|
110
128
|
stats() {
|
|
111
129
|
return {
|
|
112
|
-
|
|
130
|
+
role: this.role,
|
|
131
|
+
runtime: this.runtime,
|
|
113
132
|
probes: this.probeCount,
|
|
114
133
|
pausedFor: Math.max(0, Math.round((this.pausedUntil - Date.now()) / 1000)),
|
|
115
134
|
};
|
|
116
135
|
}
|
|
117
136
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
137
|
+
/**
|
|
138
|
+
* Observe a throttling answer and arm the pod-wide cooldown.
|
|
139
|
+
*
|
|
140
|
+
* One pause for the whole pod, not one per request: without it every
|
|
141
|
+
* in-flight call rides its own ladder into a server that has already said it
|
|
142
|
+
* is overloaded, which is how a throttle becomes a stampede. A 429 or 503
|
|
143
|
+
* with NO Retry-After still arms the window — the browser copy used to arm
|
|
144
|
+
* nothing at all in that case, which is most cases.
|
|
145
|
+
*/
|
|
146
|
+
_observe(res) {
|
|
147
|
+
if (res.status !== 429 && res.status !== 503) return res;
|
|
148
|
+
// What the server ASKED for is always honoured. What to do when it asked
|
|
149
|
+
// for nothing depends on what the cooldown costs here: a caller that
|
|
150
|
+
// REFUSES for the window loses nothing by assuming a minute, while one that
|
|
151
|
+
// WAITS it out would freeze a person's tab for a minute over a throttle
|
|
152
|
+
// that clears in seconds — so there, absence arms nothing and the caller's
|
|
153
|
+
// own retry ladder paces it instead.
|
|
154
|
+
const asked = retryAfterMs(res, this.maxCooldownMs);
|
|
155
|
+
const ms = asked ?? (this.cooldownMode === 'refuse' ? COOLDOWN_DEFAULT_MS : 0);
|
|
156
|
+
if (!ms) return res;
|
|
157
|
+
this.pausedUntil = Date.now() + ms;
|
|
158
|
+
this.log(`[${this.label}] pod returned ${res.status}${asked ? ', Retry-After' : ''} — `
|
|
159
|
+
+ `holding all requests for ${Math.round(ms / 1000)}s`);
|
|
160
|
+
return res;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Honour an armed cooldown: fail fast, or wait it out, as configured. */
|
|
164
|
+
async _cooldownGate() {
|
|
125
165
|
const left = this.pausedUntil - Date.now();
|
|
126
|
-
if (left
|
|
127
|
-
|
|
166
|
+
if (left <= 0) return;
|
|
167
|
+
if (this.cooldownMode === 'wait') {
|
|
168
|
+
await sleep(Math.min(left, this.maxCooldownMs));
|
|
169
|
+
return;
|
|
128
170
|
}
|
|
171
|
+
throw new Error(`[${this.label}] pod asked us to back off — ${Math.ceil(left / 1000)}s left of its Retry-After`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* The seam a subclass overrides to add transport behaviour — the browser's
|
|
176
|
+
* retry ladder lives here. Overriding THIS rather than `fetch` is deliberate:
|
|
177
|
+
* an override cannot skip the url map, the cooldown accounting or the
|
|
178
|
+
* deletion deny-list, because those are in `fetch` above it.
|
|
179
|
+
*/
|
|
180
|
+
async _send(url, init) { return this.session.fetch(url, init); }
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* A deliberately CREDENTIAL-FREE request: it asks what a stranger would see,
|
|
184
|
+
* so it cannot go through the session without answering a different question.
|
|
185
|
+
* It still belongs to this pod, though — it opens a socket to it and takes
|
|
186
|
+
* one of its workers — so it observes the same cooldown and is counted.
|
|
187
|
+
*
|
|
188
|
+
* No url map: a probe asks about the ADVERTISED face, which is the whole
|
|
189
|
+
* point of asking.
|
|
190
|
+
*/
|
|
191
|
+
async probe(url, init = {}) {
|
|
192
|
+
await this._cooldownGate();
|
|
129
193
|
this.probeCount++;
|
|
130
|
-
|
|
131
|
-
if (res.status === 429 || res.status === 503) {
|
|
132
|
-
const ms = retryAfterMs(res) ?? 60_000;
|
|
133
|
-
this.pausedUntil = Date.now() + ms;
|
|
134
|
-
this.log(`pod returned ${res.status} to an unauthenticated probe — pausing for ${Math.round(ms / 1000)}s`);
|
|
135
|
-
}
|
|
136
|
-
return res;
|
|
194
|
+
return this._observe(await fetch(url, init));
|
|
137
195
|
}
|
|
138
196
|
|
|
197
|
+
/**
|
|
198
|
+
* Every authenticated request. Nothing below this reaches the session
|
|
199
|
+
* directly: this is the only place the cooldown is both OBSERVED and ARMED,
|
|
200
|
+
* the url map applied, and the deny-list enforced.
|
|
201
|
+
*/
|
|
139
202
|
async fetch(url, init) {
|
|
140
|
-
|
|
141
|
-
if (left > 0) {
|
|
142
|
-
throw new Error(`pod asked us to back off — ${Math.ceil(left / 1000)}s left of its Retry-After`);
|
|
143
|
-
}
|
|
144
|
-
// Fronted identity: an advertised id maps to its pod location here, so the
|
|
145
|
-
// request lands on the pod. A no-op for a pod-native id or an unfronted
|
|
146
|
-
// identity (toPod unset).
|
|
203
|
+
await this._cooldownGate();
|
|
147
204
|
if (this.toPod) url = this.toPod(url);
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
205
|
+
if (String(init?.method || '').toUpperCase() === 'DELETE') this._guardDelete(url);
|
|
206
|
+
return this._observe(await this._send(url, init));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** Both refusals a DELETE must pass, wherever it was issued from. */
|
|
210
|
+
_guardDelete(url) {
|
|
211
|
+
protectedFromDeletion(url);
|
|
212
|
+
// The pattern list knows the usual name for an access-control document.
|
|
213
|
+
// One the pod named itself is just as fatal to remove. Tolerating an
|
|
214
|
+
// absent map matters: this now runs for EVERY delete, including one issued
|
|
215
|
+
// through `fetch` by a caller holding a minimally-built transport.
|
|
216
|
+
for (const acl of this.aclUrls?.values() ?? []) {
|
|
217
|
+
if (acl === url) throw new Error(`refusing to DELETE an access-control document: ${url}`);
|
|
153
218
|
}
|
|
154
|
-
return res;
|
|
155
219
|
}
|
|
156
220
|
|
|
157
|
-
// Everything below goes through this.fetch, never session.fetch directly:
|
|
158
|
-
// that is the only place the Retry-After cooldown is both OBSERVED and ARMED.
|
|
159
|
-
// Writing straight to the session meant a pod could answer 429 to every PUT
|
|
160
|
-
// and DELETE we sent without any of them ever being recorded as a refusal,
|
|
161
|
-
// and meant a cooldown armed by a read did not stop a publish burst.
|
|
162
221
|
async put(url, body, contentType) {
|
|
163
222
|
const res = await this.fetch(url, {
|
|
164
223
|
method: 'PUT', headers: { 'content-type': contentType }, body,
|
|
165
224
|
});
|
|
166
|
-
if (res.status >= 400) throw new Error(`PUT ${url} → ${res.status}`);
|
|
225
|
+
if (res.status >= 400) throw new Error(`[${this.label}] PUT ${url} → ${res.status}`);
|
|
167
226
|
// Writing a document is usually the step before setting its access, and
|
|
168
227
|
// the answer to the write already says where that lives. Taking it here
|
|
169
228
|
// spares the extra request the ACL write would otherwise make.
|
|
@@ -171,6 +230,26 @@ export class RemotePod {
|
|
|
171
230
|
return res;
|
|
172
231
|
}
|
|
173
232
|
|
|
233
|
+
async putJson(url, obj, contentType = 'application/activity+json') {
|
|
234
|
+
return this.put(url, JSON.stringify(obj), contentType);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// A read that FAILED is not a document that is ABSENT. Returning null for
|
|
238
|
+
// both let a 429 read as "no replies yet", and the caller then rewrote the
|
|
239
|
+
// collection from empty — erasing every reply already recorded. Only a real
|
|
240
|
+
// 404/410 is absence; anything else throws and the caller retries later.
|
|
241
|
+
async getJson(url) {
|
|
242
|
+
const res = await this.fetch(url, { headers: { accept: '*/*' } });
|
|
243
|
+
if (res.status === 404 || res.status === 410) return null;
|
|
244
|
+
if (res.status >= 400) throw new Error(`[${this.label}] GET ${url} → ${res.status}`);
|
|
245
|
+
return res.json().catch(() => null);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async delete(url) {
|
|
249
|
+
const res = await this.fetch(url, { method: 'DELETE' });
|
|
250
|
+
return res.status < 400 || res.status === 404;
|
|
251
|
+
}
|
|
252
|
+
|
|
174
253
|
/** Remember an access-control location the pod volunteered on a response. */
|
|
175
254
|
noteAclLink(url, res) {
|
|
176
255
|
if (this.aclUrls.has(url)) return;
|
|
@@ -213,7 +292,7 @@ export class RemotePod {
|
|
|
213
292
|
|| st.object.value.startsWith(ACP_NS));
|
|
214
293
|
if (acp) {
|
|
215
294
|
this.aclFlavour = false;
|
|
216
|
-
this.log(
|
|
295
|
+
this.log(`[${this.label}] this pod states access as ACP policies, which this library does not write — `
|
|
217
296
|
+ 'its access rules are left exactly as they are, and nothing here is published private');
|
|
218
297
|
}
|
|
219
298
|
}
|
|
@@ -221,36 +300,51 @@ export class RemotePod {
|
|
|
221
300
|
return this.aclFlavour;
|
|
222
301
|
}
|
|
223
302
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
//
|
|
229
|
-
//
|
|
230
|
-
//
|
|
231
|
-
//
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
303
|
+
// WAC doc granting the public `publicModes` on target, owner full control.
|
|
304
|
+
// An empty publicModes list yields an owner-only document.
|
|
305
|
+
//
|
|
306
|
+
// Built and serialised by rdflib, like every other document written here.
|
|
307
|
+
// This is the highest-consequence RDF in the project: an ACL that comes out
|
|
308
|
+
// malformed, or naming the wrong subject, either locks the owner out or
|
|
309
|
+
// leaves the private trees world-readable. $rdf.sym() also throws on an
|
|
310
|
+
// illegal IRI, so a pod URL with something odd in it fails here rather than
|
|
311
|
+
// silently producing a document that means something else.
|
|
312
|
+
aclDoc(targetUrl, publicModes, { appendAgents = [], aclUrl = null } = {}) {
|
|
313
|
+
const url = aclUrl || targetUrl + '.acl';
|
|
314
|
+
const doc = $rdf.sym(url);
|
|
315
|
+
const target = $rdf.sym(targetUrl);
|
|
316
|
+
const g = $rdf.graph();
|
|
317
|
+
const authorize = (subject, agentPred, agent, modes) => {
|
|
318
|
+
g.add(subject, RDF('type'), ACL('Authorization'), doc);
|
|
319
|
+
g.add(subject, agentPred, agent, doc);
|
|
320
|
+
g.add(subject, ACL('accessTo'), target, doc);
|
|
321
|
+
g.add(subject, ACL('default'), target, doc);
|
|
322
|
+
for (const m of modes) g.add(subject, ACL('mode'), ACL(m), doc);
|
|
323
|
+
};
|
|
324
|
+
if (publicModes.length) {
|
|
325
|
+
authorize($rdf.sym(url + '#public'), ACL('agentClass'), FOAF('Agent'), publicModes);
|
|
326
|
+
}
|
|
327
|
+
// Named agents granted Append but not public: this is how the inbox is
|
|
328
|
+
// locked to a gateway — public loses Append, the gateway's WebID keeps it.
|
|
329
|
+
appendAgents.forEach((webId, i) =>
|
|
330
|
+
authorize($rdf.sym(url + `#gw${i}`), ACL('agent'), $rdf.sym(webId), ['Append']));
|
|
331
|
+
authorize($rdf.sym(url + '#owner'), ACL('agent'), $rdf.sym(this.webId),
|
|
332
|
+
['Read', 'Write', 'Control']);
|
|
333
|
+
return $rdf.serialize(doc, g, url, 'text/turtle');
|
|
237
334
|
}
|
|
238
335
|
|
|
239
|
-
async
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
for (const acl of this.aclUrls.values()) {
|
|
244
|
-
if (acl === url) throw new Error(`refusing to DELETE an access-control document: ${url}`);
|
|
245
|
-
}
|
|
246
|
-
const res = await this.fetch(url, { method: 'DELETE' });
|
|
247
|
-
return res.status < 400 || res.status === 404;
|
|
336
|
+
async setAcl(targetUrl, publicModes, opts = {}) {
|
|
337
|
+
const url = await this.aclUrlFor(targetUrl);
|
|
338
|
+
if (!await this.aclWritable(url)) return null;
|
|
339
|
+
return this.put(url, this.aclDoc(targetUrl, publicModes, { ...opts, aclUrl: url }), 'text/turtle');
|
|
248
340
|
}
|
|
249
341
|
|
|
250
342
|
// Child documents of an LDP container (URLs under it, excluding aux docs).
|
|
251
|
-
// Revalidated: the inbox is polled every
|
|
343
|
+
// Revalidated: the inbox is polled every couple of minutes and is usually
|
|
252
344
|
// unchanged, so ask conditionally and let the server answer 304.
|
|
253
345
|
async listContainer(url) {
|
|
346
|
+
// Lazily, not only in the constructor: a caller may hold a transport built
|
|
347
|
+
// with Object.create for a test, setting just the few fields it drives.
|
|
254
348
|
this._listCache ||= new Map();
|
|
255
349
|
const known = this._listCache.get(url);
|
|
256
350
|
const res = await this.fetch(url, {
|
|
@@ -260,18 +354,21 @@ export class RemotePod {
|
|
|
260
354
|
if (res.status >= 400) return [];
|
|
261
355
|
// rdflib, not a regex: a container listing is RDF, and the gap between
|
|
262
356
|
// "any angle-bracketed thing in the document" and "what this container
|
|
263
|
-
// actually contains" is where quiet bugs live.
|
|
357
|
+
// actually contains" is where quiet bugs live.
|
|
264
358
|
//
|
|
265
359
|
// CSS puts dc:modified and posix:size on every child of the same listing,
|
|
266
|
-
// so age and weight cost nothing extra — which is what lets
|
|
360
|
+
// so age and weight cost nothing extra — which is what lets a drain work
|
|
267
361
|
// oldest-first and lets a backlog be measured without reading any of it.
|
|
268
362
|
let body;
|
|
269
363
|
try {
|
|
270
364
|
body = await readCapped(res, LISTING_MAX_BYTES);
|
|
271
365
|
} catch (e) {
|
|
272
|
-
this.log(`listing at ${url}: ${e.message} — using the last known listing`);
|
|
366
|
+
this.log(`[${this.label}] listing at ${url}: ${e.message} — using the last known listing`);
|
|
273
367
|
return known?.children ?? [];
|
|
274
368
|
}
|
|
369
|
+
// A parse failure is a real fault, not a cache hit: it propagates. The
|
|
370
|
+
// browser copy used to swallow it and hand back a stale listing, which
|
|
371
|
+
// turns a corrupt container into silently missing work.
|
|
275
372
|
const g = $rdf.graph();
|
|
276
373
|
$rdf.parse(body, g, url, 'text/turtle');
|
|
277
374
|
const here = $rdf.sym(url);
|
|
@@ -289,65 +386,26 @@ export class RemotePod {
|
|
|
289
386
|
modified: g.any(child, DC('modified'), null, here)?.value || null,
|
|
290
387
|
});
|
|
291
388
|
}
|
|
292
|
-
// Oldest first. An LDP listing is a set, so without this
|
|
293
|
-
//
|
|
389
|
+
// Oldest first. An LDP listing is a set, so without this a drain works in
|
|
390
|
+
// whatever order the graph happened to parse — a mention from last week
|
|
294
391
|
// after one from today, and no way to make progress predictable.
|
|
295
392
|
list.sort((a, b) => String(a.modified || '').localeCompare(String(b.modified || '')));
|
|
296
393
|
this._listCache.set(url, { etag: res.headers.get('etag'), children: list });
|
|
297
394
|
return list;
|
|
298
395
|
}
|
|
299
396
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
// $rdf.sym() also throws on an illegal IRI, so a pod URL with something odd
|
|
309
|
-
// in it fails here rather than silently producing a document that means
|
|
310
|
-
// something else.
|
|
311
|
-
aclDoc(targetUrl, publicModes, { appendAgents = [], aclUrl = null } = {}) {
|
|
312
|
-
const url = aclUrl || targetUrl + '.acl';
|
|
313
|
-
const doc = $rdf.sym(url);
|
|
314
|
-
const target = $rdf.sym(targetUrl);
|
|
315
|
-
const g = $rdf.graph();
|
|
316
|
-
const authorize = (subject, agentPred, agent, modes) => {
|
|
317
|
-
g.add(subject, RDF('type'), ACL('Authorization'), doc);
|
|
318
|
-
g.add(subject, agentPred, agent, doc);
|
|
319
|
-
g.add(subject, ACL('accessTo'), target, doc);
|
|
320
|
-
g.add(subject, ACL('default'), target, doc);
|
|
321
|
-
for (const m of modes) g.add(subject, ACL('mode'), ACL(m), doc);
|
|
322
|
-
};
|
|
323
|
-
if (publicModes.length) {
|
|
324
|
-
authorize($rdf.sym(url + '#public'), ACL('agentClass'), FOAF('Agent'), publicModes);
|
|
325
|
-
}
|
|
326
|
-
// Named agents granted Append but not public: this is how the inbox is
|
|
327
|
-
// locked to a gateway — public loses Append, the gateway's WebID keeps it.
|
|
328
|
-
appendAgents.forEach((webId, i) =>
|
|
329
|
-
authorize($rdf.sym(url + `#gw${i}`), ACL('agent'), $rdf.sym(webId), ['Append']));
|
|
330
|
-
authorize($rdf.sym(url + '#owner'), ACL('agent'), $rdf.sym(this.webId),
|
|
331
|
-
['Read', 'Write', 'Control']);
|
|
332
|
-
return $rdf.serialize(doc, g, url, 'text/turtle');
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
async setAcl(targetUrl, publicModes, opts = {}) {
|
|
336
|
-
const url = await this.aclUrlFor(targetUrl);
|
|
337
|
-
if (!await this.aclWritable(url)) return null;
|
|
338
|
-
return this.put(url, this.aclDoc(targetUrl, publicModes, { ...opts, aclUrl: url }), 'text/turtle');
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
// The WebID profile advertises the actor as an account:
|
|
342
|
-
// <webId> foaf:account <actor> .
|
|
343
|
-
// <actor> a foaf:OnlineAccount, as:Person|as:Group ; foaf:accountName "@handle@host" .
|
|
344
|
-
// Read–check–write through rdflib. Returns false when the profile already
|
|
345
|
-
// says all of it. The parsed graph must mention the WebID before anything is
|
|
346
|
-
// written back — an empty or foreign body must never become the new profile.
|
|
397
|
+
/**
|
|
398
|
+
* The WebID profile advertises the actor as an account:
|
|
399
|
+
* <webId> foaf:account <actor> .
|
|
400
|
+
* <actor> a foaf:OnlineAccount, as:Person|as:Group ; foaf:accountName "@handle@host" .
|
|
401
|
+
* Read–check–write through rdflib. Returns false when the profile already
|
|
402
|
+
* says all of it. The parsed graph must mention the WebID before anything is
|
|
403
|
+
* written back — an empty or foreign body must never become the new profile.
|
|
404
|
+
*/
|
|
347
405
|
async linkAccountInProfile({ actorUrl, accountName, kind = 'person' }) {
|
|
348
406
|
const docUrl = this.webId.split('#')[0];
|
|
349
407
|
const res = await this.fetch(docUrl, { headers: { accept: 'text/turtle' } });
|
|
350
|
-
if (res.status >= 400) throw new Error(`GET ${docUrl} → ${res.status}`);
|
|
408
|
+
if (res.status >= 400) throw new Error(`[${this.label}] GET ${docUrl} → ${res.status}`);
|
|
351
409
|
const g = $rdf.graph();
|
|
352
410
|
$rdf.parse(await res.text(), g, docUrl, 'text/turtle');
|
|
353
411
|
const doc = $rdf.sym(docUrl);
|
|
@@ -370,7 +428,7 @@ export class RemotePod {
|
|
|
370
428
|
// A patch touches these statements and nothing else. Rewriting the whole
|
|
371
429
|
// profile re-serialises statements that are not ours — the OIDC issuer
|
|
372
430
|
// among them — and a server is entitled to refuse a write that would.
|
|
373
|
-
const deletes = stale.map(st => [
|
|
431
|
+
const deletes = stale.map(st => [st.subject, st.predicate, st.object]);
|
|
374
432
|
if (await this.patchDocument(docUrl, missing, deletes)) return true;
|
|
375
433
|
for (const st of stale) g.remove(st);
|
|
376
434
|
for (const [s, p, o] of missing) g.add(s, p, o, doc);
|
|
@@ -414,6 +472,6 @@ export class RemotePod {
|
|
|
414
472
|
// remove is not there any more — is a real answer, and rewriting the whole
|
|
415
473
|
// document over the top of it would destroy whatever changed it.
|
|
416
474
|
if (res.status === 405 || res.status === 415 || res.status === 501) return false;
|
|
417
|
-
throw new Error(`PATCH ${docUrl} → ${res.status}`);
|
|
475
|
+
throw new Error(`[${this.label}] PATCH ${docUrl} → ${res.status}`);
|
|
418
476
|
}
|
|
419
477
|
}
|
package/lib/pod/urls.mjs
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// urls.mjs — where an ActivityPub actor's documents live on a Solid pod.
|
|
2
|
+
//
|
|
3
|
+
// Everything the actor owns nests under ONE top-level container (`root`), so
|
|
4
|
+
// the pod stays tidy and one pod could host several actors under different
|
|
5
|
+
// roots. WebFinger and host-meta are the exception: fediverse discovery only
|
|
6
|
+
// ever looks at the host root, so they sit at the pod base.
|
|
7
|
+
//
|
|
8
|
+
// `root` is REQUIRED. It used to default, and the default disagreed with what
|
|
9
|
+
// one of the two callers actually passed — which is the kind of bug that puts
|
|
10
|
+
// an actor's state in one container and its published documents in another.
|
|
11
|
+
// A library has no business having an opinion about which container an
|
|
12
|
+
// application uses; the application states it.
|
|
13
|
+
//
|
|
14
|
+
// When `publicBase` is given — an identity whose ADVERTISED ids live somewhere
|
|
15
|
+
// else, e.g. "https://front.example/u/me/" — the ids a remote server sees build
|
|
16
|
+
// on that instead, while the pod-side trees and every write target stay on the
|
|
17
|
+
// pod. `toPod`/`toPublic` map between the two spaces, and a transport applies
|
|
18
|
+
// `toPod` at its one request choke point, so callers keep passing advertised
|
|
19
|
+
// ids and the writes still land on the pod. With no `publicBase` the returned
|
|
20
|
+
// object is byte-identical to the unfronted form — an invariant worth keeping,
|
|
21
|
+
// because it is what lets the fronted case be tested against the plain one.
|
|
22
|
+
|
|
23
|
+
export function apUrls(remotePod, root, { publicBase = null } = {}) {
|
|
24
|
+
if (!root) throw new Error('apUrls: a container root is required — the caller states it, this library does not guess');
|
|
25
|
+
const base = remotePod.endsWith('/') ? remotePod : remotePod + '/';
|
|
26
|
+
const home = base + (root.endsWith('/') ? root : root + '/');
|
|
27
|
+
// The face a remote sees: the fronted home, or the pod home when unfronted.
|
|
28
|
+
const face = publicBase ? (publicBase.endsWith('/') ? publicBase : publicBase + '/') : home;
|
|
29
|
+
const urls = {
|
|
30
|
+
base, home,
|
|
31
|
+
webfinger: base + '.well-known/webfinger',
|
|
32
|
+
actor: face + 'ap/actor',
|
|
33
|
+
inbox: face + 'ap/inbox/',
|
|
34
|
+
outbox: face + 'ap/outbox',
|
|
35
|
+
followers: face + 'ap/followers',
|
|
36
|
+
following: face + 'ap/following',
|
|
37
|
+
notes: face + 'ap/notes/',
|
|
38
|
+
privateNotes: face + 'ap/private/',
|
|
39
|
+
featured: face + 'ap/featured',
|
|
40
|
+
// FEP-1b12: the moderator roster a recipient validates announced
|
|
41
|
+
// moderation against. Published only when moderators are configured.
|
|
42
|
+
moderators: face + 'ap/moderators',
|
|
43
|
+
// FEP-4ccd and FEP-c648: follows in limbo and the block list, as
|
|
44
|
+
// collections. They live in the private container so the owner-only ACL
|
|
45
|
+
// is inherited, not re-stated per document.
|
|
46
|
+
pendingFollowers: face + 'ap/private/pending-followers',
|
|
47
|
+
pendingFollowing: face + 'ap/private/pending-following',
|
|
48
|
+
blocked: face + 'ap/private/blocked',
|
|
49
|
+
profileHtml: face + 'ap/profile.html',
|
|
50
|
+
// Media stays on the pod even when fronted: attachment urls are not
|
|
51
|
+
// identity-checked by remotes, and proxying blobs would be pure cost.
|
|
52
|
+
media: home + 'ap/media/',
|
|
53
|
+
state: home + 'ap-state/',
|
|
54
|
+
};
|
|
55
|
+
if (publicBase) {
|
|
56
|
+
urls.podHome = home;
|
|
57
|
+
urls.publicHome = face;
|
|
58
|
+
urls.toPod = (u) => (typeof u === 'string' && u.startsWith(face) ? home + u.slice(face.length) : u);
|
|
59
|
+
urls.toPublic = (u) => (typeof u === 'string' && u.startsWith(home) ? face + u.slice(home.length) : u);
|
|
60
|
+
}
|
|
61
|
+
return urls;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// The host a handle would resolve through, or null when no handle can point
|
|
65
|
+
// here. @name@host is looked up at https://host/.well-known/webfinger, so only
|
|
66
|
+
// a pod that owns the root of its host can answer for one; a pod living at
|
|
67
|
+
// https://server/name/ may publish the document but nothing will ever ask.
|
|
68
|
+
export function webfingerHost(podUrl) {
|
|
69
|
+
const u = new URL(podUrl);
|
|
70
|
+
return u.pathname === '/' ? u.host : null;
|
|
71
|
+
}
|