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
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// actor.mjs — ap/actor and the documents that speak for it.
|
|
2
|
+
//
|
|
3
|
+
// "Actor" here always means the ActivityPub actor DOCUMENT — the thing remote
|
|
4
|
+
// servers fetch to learn this identity's inbox, keys and collections. It is
|
|
5
|
+
// not the party performing an operation; a transport carries that as its
|
|
6
|
+
// `role`.
|
|
7
|
+
//
|
|
8
|
+
// The actor is public, and its Read rule travels with every write for the same
|
|
9
|
+
// reason it does in discovery.mjs: an actor nobody can fetch is an identity
|
|
10
|
+
// nobody can follow, and the failure is silent from this side.
|
|
11
|
+
|
|
12
|
+
const PUBLIC_READ = ['Read'];
|
|
13
|
+
|
|
14
|
+
// What ActivityPub §3.2 requires a client to send: the profiled JSON-LD type is
|
|
15
|
+
// the MUST, activity+json only the SHOULD a server ought to treat as equivalent.
|
|
16
|
+
// Asking for both is the only form every implementation answers.
|
|
17
|
+
const ACCEPT_AP = 'application/activity+json, application/ld+json; profile="https://www.w3.org/ns/activitystreams"';
|
|
18
|
+
|
|
19
|
+
// ---- the owner's agent ----
|
|
20
|
+
|
|
21
|
+
/** Publish the actor document. */
|
|
22
|
+
export async function write(pod, urls, doc) {
|
|
23
|
+
await pod.putJson(urls.actor, doc);
|
|
24
|
+
await pod.setAcl(urls.actor, PUBLIC_READ);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** What this pod currently publishes as its actor, or null if nothing does. */
|
|
28
|
+
export function read(pod, urls) {
|
|
29
|
+
return pod.getJson(urls.actor);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Replace the actor with a Tombstone.
|
|
34
|
+
*
|
|
35
|
+
* Still public-Read, deliberately: a retired actor that 404s reads to a remote
|
|
36
|
+
* server as a temporary failure worth retrying, where a Tombstone reads as an
|
|
37
|
+
* answer.
|
|
38
|
+
*/
|
|
39
|
+
export async function writeTombstone(pod, urls, doc) {
|
|
40
|
+
await pod.putJson(urls.actor, doc);
|
|
41
|
+
await pod.setAcl(urls.actor, PUBLIC_READ);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Publish the actor carrying `movedTo`.
|
|
46
|
+
*
|
|
47
|
+
* The same document in the same place — but named apart from `write` because
|
|
48
|
+
* what it means is not "the profile changed", it is "this identity is now
|
|
49
|
+
* somewhere else", and a caller should have to say which it intends.
|
|
50
|
+
*/
|
|
51
|
+
export async function writeMoved(pod, urls, doc) {
|
|
52
|
+
await pod.putJson(urls.actor, doc);
|
|
53
|
+
await pod.setAcl(urls.actor, PUBLIC_READ);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** The human half: a page a person can open and follow from. */
|
|
57
|
+
export async function writeProfilePage(pod, urls, html) {
|
|
58
|
+
await pod.put(urls.profileHtml, html, 'text/html');
|
|
59
|
+
await pod.setAcl(urls.profileHtml, PUBLIC_READ);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Record the actor in the pod owner's WebID profile, as `foaf:account`.
|
|
64
|
+
*
|
|
65
|
+
* The inverse of the actor's own `alsoKnownAs`: together they let a reader of
|
|
66
|
+
* either document verify the other, rather than taking one side's word.
|
|
67
|
+
*
|
|
68
|
+
* Delegated to the transport, which reads the profile, refuses to write if the
|
|
69
|
+
* parsed graph does not mention the WebID, and patches exactly the statements
|
|
70
|
+
* involved rather than rewriting a document full of things that are not ours.
|
|
71
|
+
*/
|
|
72
|
+
export function linkInWebIdProfile(pod, { actorUrl, accountName, kind = 'person' }) {
|
|
73
|
+
return pod.linkAccountInProfile({ actorUrl, accountName, kind });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ---- anyone at all ----
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* The OIDC issuer a published actor names, read without credentials.
|
|
80
|
+
*
|
|
81
|
+
* Asked of a stranger's pod during sign-in, before there is any session to ask
|
|
82
|
+
* with — so a plain fetch, and a null rather than a throw when the actor is
|
|
83
|
+
* not there or says nothing.
|
|
84
|
+
*/
|
|
85
|
+
export async function readIssuer(actorUrl, fetchImpl = fetch) {
|
|
86
|
+
try {
|
|
87
|
+
// Both media types, and the profile with it. `application/activity+json`
|
|
88
|
+
// alone is what ActivityPub calls the SHOULD, not the MUST — and a pod that
|
|
89
|
+
// stores its actor as JSON-LD answers 501 to it, which would read here as
|
|
90
|
+
// "this pod names no issuer" and send the caller off to guess one.
|
|
91
|
+
const res = await fetchImpl(actorUrl, { headers: { accept: ACCEPT_AP } });
|
|
92
|
+
if (res.status >= 400) return null;
|
|
93
|
+
const doc = await res.json();
|
|
94
|
+
return doc?.endpoints?.oauthAuthorizationEndpoint || null;
|
|
95
|
+
} catch { return null; }
|
|
96
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// collection.mjs — the mechanics every AS2 collection on a pod shares.
|
|
2
|
+
//
|
|
3
|
+
// Not a resource itself: the outbox, the followers list and the block list are
|
|
4
|
+
// each their own document with their own meaning, and each has a module that
|
|
5
|
+
// says so. What they have in common is how a collection is written and walked,
|
|
6
|
+
// and that is here so it is written once.
|
|
7
|
+
//
|
|
8
|
+
// A paged collection is a HEAD carrying counts and bounds, plus page documents
|
|
9
|
+
// carrying the items. A remote server reads the small head and walks only as
|
|
10
|
+
// far as it needs, instead of pulling one enormous document.
|
|
11
|
+
|
|
12
|
+
const PUBLIC_READ = ['Read'];
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* One page.
|
|
16
|
+
*
|
|
17
|
+
* `publicRead` is false for a page that already exists: the ACL is set when a
|
|
18
|
+
* page is first created and not rewritten on every republish, because a page
|
|
19
|
+
* is written on every change and its rule is not what changed.
|
|
20
|
+
*/
|
|
21
|
+
export async function writePage(pod, pageUrl, doc, { publicRead = false } = {}) {
|
|
22
|
+
await pod.putJson(pageUrl, doc);
|
|
23
|
+
if (publicRead) await pod.setAcl(pageUrl, PUBLIC_READ);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** The head — counts, bounds, and the pointer at the first page. */
|
|
27
|
+
export async function writeHead(pod, url, doc, { publicRead = false } = {}) {
|
|
28
|
+
await pod.putJson(url, doc);
|
|
29
|
+
if (publicRead) await pod.setAcl(url, PUBLIC_READ);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Remove a page that is no longer within bounds.
|
|
34
|
+
*
|
|
35
|
+
* Best-effort: a surplus page left behind is stale but harmless — the head no
|
|
36
|
+
* longer points at it — where a failed publish over a delete is not.
|
|
37
|
+
*/
|
|
38
|
+
export async function dropPage(pod, pageUrl) {
|
|
39
|
+
await pod.delete(pageUrl).catch(() => {});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** A whole flat collection, written as one document. */
|
|
43
|
+
export async function writeFlat(pod, url, doc, { publicRead = false } = {}) {
|
|
44
|
+
await pod.putJson(url, doc);
|
|
45
|
+
if (publicRead) await pod.setAcl(url, PUBLIC_READ);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Every item in a published collection, walking pages.
|
|
50
|
+
*
|
|
51
|
+
* Also reads a FLAT collection, so a collection published before this project
|
|
52
|
+
* paged them is still readable — which matters because a rebuild reads these
|
|
53
|
+
* to recover what a lost machine no longer has.
|
|
54
|
+
*
|
|
55
|
+
* `seen` guards a cycle and `max` a collection that lies about its own size;
|
|
56
|
+
* both are the pod's data but not necessarily ours, since a restored backup or
|
|
57
|
+
* a half-finished write can leave either.
|
|
58
|
+
*
|
|
59
|
+
* @returns items, or null when there is no collection there at all
|
|
60
|
+
*/
|
|
61
|
+
export async function readPaged(pod, headUrl, { max = 10_000, alsoItems = false } = {}) {
|
|
62
|
+
const head = await pod.getJson(headUrl).catch(() => null);
|
|
63
|
+
if (!head) return null;
|
|
64
|
+
if (Array.isArray(head.orderedItems) && head.orderedItems.length) return head.orderedItems;
|
|
65
|
+
if (alsoItems && Array.isArray(head.items) && head.items.length) return head.items;
|
|
66
|
+
const items = [];
|
|
67
|
+
const seen = new Set();
|
|
68
|
+
let next = head.first;
|
|
69
|
+
while (next && !seen.has(next) && items.length < max) {
|
|
70
|
+
seen.add(next);
|
|
71
|
+
const page = await pod.getJson(next).catch(() => null);
|
|
72
|
+
if (!page) break;
|
|
73
|
+
items.push(...(page.orderedItems || []));
|
|
74
|
+
next = page.next;
|
|
75
|
+
}
|
|
76
|
+
return items;
|
|
77
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
// containers.mjs — making a container exist, and making its access right.
|
|
2
|
+
//
|
|
3
|
+
// A container on a Solid pod is not created; it is implied by something being
|
|
4
|
+
// in it. So every provisioning here writes one small canary document and then
|
|
5
|
+
// states the access rule the container is supposed to have. The two are one
|
|
6
|
+
// operation on purpose: a container that exists without its ACL is either
|
|
7
|
+
// world-readable when it should not be, or unreachable when it should not be,
|
|
8
|
+
// and both have happened by writing one and not the other.
|
|
9
|
+
//
|
|
10
|
+
// The probes at the bottom take a `probe` function rather than a transport,
|
|
11
|
+
// and that is the whole point of them: they ask what a STRANGER sees. Handing
|
|
12
|
+
// them a credential would answer a different question, and answer it wrongly.
|
|
13
|
+
|
|
14
|
+
const KEEP = { keep: true };
|
|
15
|
+
const KEEP_CT = 'application/json';
|
|
16
|
+
|
|
17
|
+
/** The canary this module writes, and reads back to ask whether it was here. */
|
|
18
|
+
const keepUrl = (base) => `${base}.keep`;
|
|
19
|
+
|
|
20
|
+
// ---- the owner's agent, holding the pod's own credential ----
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Whether this container has already been provisioned.
|
|
24
|
+
*
|
|
25
|
+
* Only a definite "yes" counts: anything else falls through to the write,
|
|
26
|
+
* which is idempotent anyway, so a failed probe costs a request and never
|
|
27
|
+
* correctness. Worth asking because the alternative is re-provisioning — and
|
|
28
|
+
* rewriting an ACL — every time a service worker restarts, which is whenever
|
|
29
|
+
* the browser feels like it.
|
|
30
|
+
*/
|
|
31
|
+
export async function exists(pod, base) {
|
|
32
|
+
try {
|
|
33
|
+
const r = await pod.fetch(keepUrl(base), { method: 'HEAD' });
|
|
34
|
+
return r?.status >= 200 && r.status < 300;
|
|
35
|
+
} catch { return false; }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Bring a container into being with an owner-only rule. */
|
|
39
|
+
export async function provisionOwnerOnly(pod, base) {
|
|
40
|
+
await pod.putJson(keepUrl(base), KEEP, KEEP_CT);
|
|
41
|
+
await pod.setAcl(base, []);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Bring a container into being, world-readable — a published tree. */
|
|
45
|
+
export async function provisionPublic(pod, base) {
|
|
46
|
+
await pod.putJson(keepUrl(base), KEEP, KEEP_CT);
|
|
47
|
+
await pod.setAcl(base, ['Read']);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The private half: the home itself and the state container.
|
|
52
|
+
*
|
|
53
|
+
* Idempotent, so two devices doing it at once is harmless — which is what
|
|
54
|
+
* makes it safe to run on every boot rather than only at setup.
|
|
55
|
+
*/
|
|
56
|
+
export async function provisionPrivate(pod, urls) {
|
|
57
|
+
await pod.putJson(keepUrl(urls.state), KEEP, KEEP_CT);
|
|
58
|
+
await pod.setAcl(urls.state, []);
|
|
59
|
+
await pod.setAcl(urls.home, []);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Check the private trees are actually private, and put back any that are not.
|
|
64
|
+
*
|
|
65
|
+
* An ACL write that silently failed — or that something else changed
|
|
66
|
+
* afterwards — leaves the private trees, signing keys among them, readable by
|
|
67
|
+
* anyone. Setup writes them once and never returns, so this is what makes it a
|
|
68
|
+
* standing property rather than a one-time hope.
|
|
69
|
+
*
|
|
70
|
+
* Returns what it found rather than logging: what a finding MEANS, and how
|
|
71
|
+
* loudly to say it, belongs to the application.
|
|
72
|
+
*
|
|
73
|
+
* `isPublic` is supplied rather than assumed: how you decide a tree is
|
|
74
|
+
* world-readable — which headers, which timeout — is the caller's business.
|
|
75
|
+
*
|
|
76
|
+
* @returns {Promise<Array<{url, rewritten, stillPublic, error}>>} one entry per
|
|
77
|
+
* tree that was readable without credentials; empty when all is well.
|
|
78
|
+
*/
|
|
79
|
+
export async function repairPrivateAcls(pod, trees, { isPublic } = {}) {
|
|
80
|
+
const findings = [];
|
|
81
|
+
for (const url of trees) {
|
|
82
|
+
if (!await isPublic(url)) continue;
|
|
83
|
+
const finding = { url, rewritten: false, stillPublic: false, error: null };
|
|
84
|
+
findings.push(finding);
|
|
85
|
+
try {
|
|
86
|
+
await pod.setAcl(url, []);
|
|
87
|
+
finding.rewritten = true;
|
|
88
|
+
} catch (e) {
|
|
89
|
+
finding.error = e.message;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
finding.stillPublic = await isPublic(url);
|
|
93
|
+
}
|
|
94
|
+
return findings;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ---- anyone at all: what a stranger can see ----
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Is this readable with no credentials?
|
|
101
|
+
*
|
|
102
|
+
* `accept: *\/*` matters. Asking for turtle makes a server answer 501 on the
|
|
103
|
+
* JSON documents — webfinger, the actor — which reads as "unreachable" when
|
|
104
|
+
* the world can in fact see them perfectly well.
|
|
105
|
+
*
|
|
106
|
+
* Unreachable is not a finding: a pod that failed to answer has not been shown
|
|
107
|
+
* to be public, and treating that as one would rewrite ACLs on a network blip.
|
|
108
|
+
*/
|
|
109
|
+
export async function probePublicReadability(probe, url, { headers = {}, timeoutMs = 20_000 } = {}) {
|
|
110
|
+
try {
|
|
111
|
+
const res = await probe(url, {
|
|
112
|
+
headers: { accept: '*/*', ...headers },
|
|
113
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
114
|
+
});
|
|
115
|
+
return res.status < 400;
|
|
116
|
+
} catch { return false; }
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Does this pod actually ENFORCE an owner-only rule?
|
|
121
|
+
*
|
|
122
|
+
* A bare, unauthenticated read of the private container's canary must be
|
|
123
|
+
* refused. Anything else — including a redirect to a login page, which is why
|
|
124
|
+
* redirects are not followed — means documents meant to be private would not
|
|
125
|
+
* be, and the caller should decline to put private things here.
|
|
126
|
+
*
|
|
127
|
+
* Takes a POD-SPACE url: an identity whose ids are advertised elsewhere must
|
|
128
|
+
* map back to the pod first, because the rule being tested is the pod's.
|
|
129
|
+
*
|
|
130
|
+
* @returns {Promise<true|string>} true, or why not
|
|
131
|
+
*/
|
|
132
|
+
export async function probePrivateEnforcement(probe, podKeepUrl) {
|
|
133
|
+
const r = await probe(podKeepUrl, { redirect: 'manual' });
|
|
134
|
+
if (r.status === 401 || r.status === 403) return true;
|
|
135
|
+
return `this pod serves private documents to strangers (HTTP ${r.status})`;
|
|
136
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// discovery.mjs — the documents that make a handle resolve.
|
|
2
|
+
//
|
|
3
|
+
// These live at the HOST root, not in the actor's container, because that is
|
|
4
|
+
// the only place the fediverse looks: @name@host is resolved at
|
|
5
|
+
// https://host/.well-known/webfinger and nowhere else. A pod that does not own
|
|
6
|
+
// the root of its host can still publish them, but nothing will ever ask.
|
|
7
|
+
//
|
|
8
|
+
// Every one is public by definition — a discovery document nobody may read
|
|
9
|
+
// discovers nothing — so the Read rule is not a parameter here. It travels
|
|
10
|
+
// with the write because publishing one of these unreadable is a failure mode
|
|
11
|
+
// with an entire verification pass devoted to catching it.
|
|
12
|
+
|
|
13
|
+
const PUBLIC_READ = ['Read'];
|
|
14
|
+
|
|
15
|
+
// ---- the owner's agent ----
|
|
16
|
+
|
|
17
|
+
/** The JRD that answers `acct:name@host`. */
|
|
18
|
+
export async function writeWebfinger(pod, urls, jrd) {
|
|
19
|
+
await pod.putJson(urls.webfinger, jrd, 'application/jrd+json');
|
|
20
|
+
await pod.setAcl(urls.webfinger, PUBLIC_READ);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** RFC 6415 host-meta: the LRDD template some implementations prefer. */
|
|
24
|
+
export async function writeHostMeta(pod, urls, xml) {
|
|
25
|
+
const url = urls.base + '.well-known/host-meta';
|
|
26
|
+
await pod.put(url, xml, 'application/xrd+xml');
|
|
27
|
+
await pod.setAcl(url, PUBLIC_READ);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* NodeInfo, which is two documents: a pointer at the protocol-required root,
|
|
32
|
+
* and the document itself in the actor's own tree.
|
|
33
|
+
*
|
|
34
|
+
* Four requests, one fact — "this pod answers nodeinfo". Split into separate
|
|
35
|
+
* operations, a caller could write the pointer and not what it points at,
|
|
36
|
+
* which is worse than neither.
|
|
37
|
+
*/
|
|
38
|
+
export async function writeNodeinfo(pod, urls, { pointer, doc }) {
|
|
39
|
+
const pointerUrl = urls.base + '.well-known/nodeinfo';
|
|
40
|
+
const docUrl = urls.home + 'ap/nodeinfo-2.0';
|
|
41
|
+
await pod.putJson(pointerUrl, pointer, 'application/json');
|
|
42
|
+
await pod.setAcl(pointerUrl, PUBLIC_READ);
|
|
43
|
+
await pod.putJson(docUrl, doc, 'application/json');
|
|
44
|
+
await pod.setAcl(docUrl, PUBLIC_READ);
|
|
45
|
+
return docUrl;
|
|
46
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// featured.mjs — ap/featured and ap/moderators: the two small public rosters.
|
|
2
|
+
//
|
|
3
|
+
// Together because both are short, flat, public lists that say "these are the
|
|
4
|
+
// ones that matter", and neither is big enough to page. `featured` is the
|
|
5
|
+
// pinned posts; `moderators` is FEP-1b12's roster, which a recipient validates
|
|
6
|
+
// a group's announced moderation against — published only when a group
|
|
7
|
+
// actually has moderators, since an empty roster and no roster mean different
|
|
8
|
+
// things.
|
|
9
|
+
|
|
10
|
+
import * as collection from './collection.mjs';
|
|
11
|
+
|
|
12
|
+
// ---- the owner's agent ----
|
|
13
|
+
|
|
14
|
+
export const write = (pod, urls, doc) =>
|
|
15
|
+
collection.writeFlat(pod, urls.featured, doc, { publicRead: true });
|
|
16
|
+
|
|
17
|
+
export const writeModerators = (pod, urls, doc) =>
|
|
18
|
+
collection.writeFlat(pod, urls.moderators, doc, { publicRead: true });
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// followers.mjs — ap/followers: who follows this actor, paged.
|
|
2
|
+
//
|
|
3
|
+
// Paged like the outbox, and for the same reason: a popular account's follower
|
|
4
|
+
// list is large, and a remote server should read a small head rather than pull
|
|
5
|
+
// all of it. Read back to reconcile — anyone the pod says follows us that we
|
|
6
|
+
// have no record of is a sign the local half is BEHIND the pod, and publishing
|
|
7
|
+
// blindly over them would drop them from the wire.
|
|
8
|
+
|
|
9
|
+
import * as collection from './collection.mjs';
|
|
10
|
+
|
|
11
|
+
// ---- the owner's agent ----
|
|
12
|
+
|
|
13
|
+
export const writePage = (pod, pageUrl, doc, opts) => collection.writePage(pod, pageUrl, doc, opts);
|
|
14
|
+
export const writeHead = (pod, urls, doc, opts) => collection.writeHead(pod, urls.followers, doc, opts);
|
|
15
|
+
export const dropPage = (pod, pageUrl) => collection.dropPage(pod, pageUrl);
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Every follower the pod currently publishes, or null if it publishes none.
|
|
19
|
+
*
|
|
20
|
+
* `alsoItems`, unlike the outbox: the flat followers collection this project
|
|
21
|
+
* used to write put them under `items` rather than `orderedItems`.
|
|
22
|
+
*/
|
|
23
|
+
export const readPublished = (pod, urls) =>
|
|
24
|
+
collection.readPaged(pod, urls.followers, { max: 100_000, alsoItems: true });
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// following.mjs — ap/following: who this actor follows.
|
|
2
|
+
//
|
|
3
|
+
// Flat, not paged: it is bounded by what one person chose to do, where the
|
|
4
|
+
// followers list is bounded by what everyone else chose.
|
|
5
|
+
|
|
6
|
+
import * as collection from './collection.mjs';
|
|
7
|
+
|
|
8
|
+
// ---- the owner's agent ----
|
|
9
|
+
|
|
10
|
+
export const write = (pod, urls, doc, opts) => collection.writeFlat(pod, urls.following, doc, opts);
|
package/lib/pod/http.mjs
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// http.mjs — the two HTTP manners this library needs, and nothing else.
|
|
2
|
+
//
|
|
3
|
+
// FediPod has lib/safefetch.mjs, which does far more: DNS pinning, private
|
|
4
|
+
// address refusal, redirect re-checking. None of that can come here, because
|
|
5
|
+
// it imports node:dns and node:net and this library must run unmodified inside
|
|
6
|
+
// a service worker. What IS needed on both sides is a byte budget and a reading
|
|
7
|
+
// of Retry-After, so they live here in a form with no runtime of their own.
|
|
8
|
+
//
|
|
9
|
+
// Deliberately NOT a fetch wrapper. The library never opens a connection by
|
|
10
|
+
// itself; every request goes through a transport or a fetcher the caller
|
|
11
|
+
// supplies, which is what lets the same code serve an authenticated agent, a
|
|
12
|
+
// keyless gateway, and a test with no network at all.
|
|
13
|
+
|
|
14
|
+
// Five megabytes is what the wider project uses as a body budget, and a
|
|
15
|
+
// document this library reads is a pod resource, not a stranger's timeline.
|
|
16
|
+
const MAX_BYTES = 5 * 1024 * 1024;
|
|
17
|
+
|
|
18
|
+
// Thirty minutes is a ceiling, not a policy: a hostile or confused header must
|
|
19
|
+
// not be able to park a caller for a day. Callers that want a tighter ceiling
|
|
20
|
+
// pass their own — the browser transport does.
|
|
21
|
+
const COOLDOWN_MAX_MS = 30 * 60_000;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* How long a server asked to be left alone, or **null** when it did not say.
|
|
25
|
+
*
|
|
26
|
+
* The null matters and must not become a default. The caller decides what
|
|
27
|
+
* absence means: a pod falls back to a flat cooldown, while a delivery queue
|
|
28
|
+
* keeps its exponential ladder — and returning a default here once collapsed
|
|
29
|
+
* that ladder to a flat 60s for every 503 without the header, which is most
|
|
30
|
+
* of them.
|
|
31
|
+
*/
|
|
32
|
+
export function retryAfterMs(res, max = COOLDOWN_MAX_MS) {
|
|
33
|
+
const raw = res?.headers?.get?.('retry-after');
|
|
34
|
+
if (!raw) return null;
|
|
35
|
+
const secs = Number(raw);
|
|
36
|
+
// A floor of one second: a server answering `Retry-After: 0` is still asking
|
|
37
|
+
// for a pause, and honouring it as "none" is how a throttle becomes a spin.
|
|
38
|
+
if (Number.isFinite(secs)) return Math.min(Math.max(secs, 1) * 1000, max);
|
|
39
|
+
const when = Date.parse(raw); // the HTTP-date spelling
|
|
40
|
+
if (Number.isFinite(when)) return Math.min(Math.max(when - Date.now(), 1000), max);
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Read a response body with a hard byte budget.
|
|
46
|
+
*
|
|
47
|
+
* Checking `content-length` and then handing back `res.text()` is not a cap: a
|
|
48
|
+
* chunked response carries no `content-length` at all, and an inbox listing is
|
|
49
|
+
* public-Append, so its size is in other people's hands. Stream it and stop at
|
|
50
|
+
* the budget.
|
|
51
|
+
*
|
|
52
|
+
* TextDecoder rather than Buffer — it exists in both runtimes — and
|
|
53
|
+
* `stream: true` so a UTF-8 sequence split across two chunks still decodes.
|
|
54
|
+
*/
|
|
55
|
+
export async function readCapped(res, max = MAX_BYTES) {
|
|
56
|
+
const len = Number(res.headers?.get?.('content-length') || 0);
|
|
57
|
+
if (len > max) throw new Error(`response too large (${len} bytes)`);
|
|
58
|
+
if (!res.body) return res.text();
|
|
59
|
+
const reader = res.body.getReader();
|
|
60
|
+
const decoder = new TextDecoder('utf-8');
|
|
61
|
+
let out = '';
|
|
62
|
+
let total = 0;
|
|
63
|
+
for (;;) {
|
|
64
|
+
const { done, value } = await reader.read();
|
|
65
|
+
if (done) break;
|
|
66
|
+
total += value.length;
|
|
67
|
+
if (total > max) {
|
|
68
|
+
await reader.cancel();
|
|
69
|
+
throw new Error(`response exceeded ${max} bytes`);
|
|
70
|
+
}
|
|
71
|
+
out += decoder.decode(value, { stream: true });
|
|
72
|
+
}
|
|
73
|
+
return out + decoder.decode();
|
|
74
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// inbox.mjs — ap/inbox/: what arrives, and who may touch it.
|
|
2
|
+
//
|
|
3
|
+
// One file for the whole inbox, because the inbox is where two different
|
|
4
|
+
// parties meet and neither half is comprehensible alone: a delivery gateway
|
|
5
|
+
// may only ADD to it, holding at most an Append token; the owner's agent
|
|
6
|
+
// lists, reads and removes, holding the pod's own credential. Splitting them
|
|
7
|
+
// by who does them would put the two ends of one contract in two files.
|
|
8
|
+
|
|
9
|
+
const DELIVERY_CT = 'application/activity+json';
|
|
10
|
+
const RECEIPT_CT = 'application/json';
|
|
11
|
+
|
|
12
|
+
// ---- a delivery gateway: appends verified mail, and can do nothing else ----
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* PUT one document into a pod inbox, with an Append token when the caller
|
|
16
|
+
* holds one and anonymously when it does not.
|
|
17
|
+
*
|
|
18
|
+
* Sending no `authorization` header is not the same as sending an empty one:
|
|
19
|
+
* `Bearer undefined` is a credential the pod will reject, where absence is a
|
|
20
|
+
* public Append the pod may well allow. This is the one place that gets to
|
|
21
|
+
* decide that, because it lived in two adapters and had to be right in both.
|
|
22
|
+
*
|
|
23
|
+
* @returns {Promise<boolean>} whether it landed
|
|
24
|
+
*/
|
|
25
|
+
export async function appendWithToken(url, body, contentType, { appendToken = null, fetchImpl = fetch } = {}) {
|
|
26
|
+
const headers = { 'content-type': contentType,
|
|
27
|
+
...(appendToken ? { authorization: `Bearer ${appendToken}` } : {}) };
|
|
28
|
+
const r = await fetchImpl(url, { method: 'PUT', headers, body }).catch(() => null);
|
|
29
|
+
return !!r && r.status < 400;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The delivery itself, named by the hash of its own bytes.
|
|
34
|
+
*
|
|
35
|
+
* Content-addressed on purpose: the same activity delivered twice writes the
|
|
36
|
+
* same document twice, so a retrying origin cannot produce duplicates in
|
|
37
|
+
* someone's inbox.
|
|
38
|
+
*
|
|
39
|
+
* A failure here must reach the ORIGIN as a 5xx so it retries over its own
|
|
40
|
+
* ladder — that is what preserves the pod's buffer property without the
|
|
41
|
+
* gateway holding any state of its own.
|
|
42
|
+
*/
|
|
43
|
+
export async function appendVerifiedDelivery(podPut, inboxUrl, hash, raw) {
|
|
44
|
+
return podPut(inboxUrl + hash, raw, DELIVERY_CT);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The verification receipt, written beside the delivery it vouches for.
|
|
49
|
+
*
|
|
50
|
+
* Best-effort by design: a delivery that landed but whose receipt did not is
|
|
51
|
+
* read as unverified at the drain, which is a lesser thing than losing it.
|
|
52
|
+
*/
|
|
53
|
+
export async function writeReceiptBeside(podPut, inboxUrl, hash, receipt) {
|
|
54
|
+
await podPut(inboxUrl + hash + '.receipt.json', JSON.stringify(receipt), RECEIPT_CT)
|
|
55
|
+
.catch(() => {});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ---- the owner's agent: brings it into being and decides who may post ----
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The canary that makes the container exist.
|
|
62
|
+
*
|
|
63
|
+
* Written without an access rule of its own, because the rule that matters is
|
|
64
|
+
* the container's and is stated separately — see setPosture, which a caller
|
|
65
|
+
* must reach for deliberately rather than inherit by accident.
|
|
66
|
+
*/
|
|
67
|
+
export async function writeKeep(pod, urls) {
|
|
68
|
+
await pod.putJson(urls.inbox + '.keep', { keep: true }, 'application/json');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Who may deliver here.
|
|
73
|
+
*
|
|
74
|
+
* 'open' anyone may Append — an inbox the fediverse can post to
|
|
75
|
+
* 'closed' nobody may — a quiesced actor, still resolving, taking no mail
|
|
76
|
+
* { gatewayWebId } only that WebID may — mail arrives through a door that
|
|
77
|
+
* verifies it first, and nobody may go round the door
|
|
78
|
+
*
|
|
79
|
+
* One operation because the three are one decision, and they were made in five
|
|
80
|
+
* places: the profile publish, three separate methods, and the manage surface
|
|
81
|
+
* reaching past all of them to set the ACL directly. A posture set in one of
|
|
82
|
+
* those and not the others is an inbox that is open when it should be shut.
|
|
83
|
+
*/
|
|
84
|
+
export async function setPosture(pod, urls, posture) {
|
|
85
|
+
if (posture === 'open') return pod.setAcl(urls.inbox, ['Append']);
|
|
86
|
+
if (posture === 'closed') return pod.setAcl(urls.inbox, []);
|
|
87
|
+
const webId = posture?.gatewayWebId;
|
|
88
|
+
if (!webId) throw new Error(`inbox.setPosture: unknown posture ${JSON.stringify(posture)}`);
|
|
89
|
+
// Public loses Append; the door keeps it. Both halves in one write, because
|
|
90
|
+
// between two writes the inbox is either open to everyone or shut to the door.
|
|
91
|
+
return pod.setAcl(urls.inbox, [], { appendAgents: [webId] });
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* What is waiting, oldest first.
|
|
96
|
+
*
|
|
97
|
+
* A defensive copy, because the transport hands back the SAME array on a 304
|
|
98
|
+
* and the inbox is polled constantly — a caller that sorted or spliced in
|
|
99
|
+
* place would corrupt the cache for every later reader.
|
|
100
|
+
*/
|
|
101
|
+
export async function list(pod, urls) {
|
|
102
|
+
const children = await pod.listContainer(urls.inbox);
|
|
103
|
+
return children.map((c) => ({ ...c }));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* One item's bytes.
|
|
108
|
+
*
|
|
109
|
+
* A pod that would not GIVE us the item has told us nothing about it. Reading
|
|
110
|
+
* a 500 as an empty body once made it "unparsable JSON" — which is a
|
|
111
|
+
* REJECTION, so the delivery was dead-lettered with nothing recorded and then
|
|
112
|
+
* DELETED: destroyed by a transient fault, with no trace of what it had been.
|
|
113
|
+
* Anything but 404 throws, and the caller's retry path takes it. 404 is the
|
|
114
|
+
* exception: the item is already gone, so removing it is right.
|
|
115
|
+
*
|
|
116
|
+
* Capped rather than read whole: the listing's size is advisory, the inbox is
|
|
117
|
+
* public-Append, and the body is whatever a stranger chose to send.
|
|
118
|
+
*
|
|
119
|
+
* @returns {Promise<{status:number, raw:string|null}>}
|
|
120
|
+
*/
|
|
121
|
+
export async function readItem(pod, url, { maxBytes, readCapped }) {
|
|
122
|
+
const res = await pod.fetch(url, { headers: { accept: '*/*' } });
|
|
123
|
+
if (res.status >= 400 && res.status !== 404) throw new Error(`inbox item GET → ${res.status}`);
|
|
124
|
+
return { status: res.status, raw: res.status < 400 ? await readCapped(res, maxBytes) : null };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** The gateway's receipt for an item, unparsed and unverified, or null. */
|
|
128
|
+
export async function readDeliveryReceipt(pod, itemUrl, { maxBytes, readCapped }) {
|
|
129
|
+
const res = await pod.fetch(itemUrl + '.receipt.json', { headers: { accept: 'application/json' } });
|
|
130
|
+
if (res.status >= 400) return null;
|
|
131
|
+
return JSON.parse(await readCapped(res, maxBytes));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Remove an item that has been dealt with.
|
|
136
|
+
*
|
|
137
|
+
* This must NOT know about the caller's state: the drain deletes only after
|
|
138
|
+
* its own state write has landed, and folding that gate in here would put the
|
|
139
|
+
* decision somewhere with no view of whether it did. Getting that wrong
|
|
140
|
+
* deletes deliveries whose record never persisted — silent, and unrecoverable.
|
|
141
|
+
*
|
|
142
|
+
* @returns {Promise<boolean>} whether the pod removed it
|
|
143
|
+
*/
|
|
144
|
+
export const dropHandledItem = (pod, url) => pod.delete(url);
|
|
@@ -27,7 +27,7 @@ export function linkTargets(headerValue, rel, baseUrl) {
|
|
|
27
27
|
return out;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
/** The relations this
|
|
30
|
+
/** The relations this library follows. */
|
|
31
31
|
export const REL = {
|
|
32
32
|
acl: 'acl',
|
|
33
33
|
storageDescription: 'http://www.w3.org/ns/solid/terms#storageDescription',
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// media.mjs — ap/media/: the bytes an attachment points at.
|
|
2
|
+
//
|
|
3
|
+
// The only tree here holding things that are not documents. Public-Read,
|
|
4
|
+
// because an attachment url travels inside a post to servers that will fetch
|
|
5
|
+
// it without credentials — and unlike an actor or a note, nothing checks that
|
|
6
|
+
// the url belongs to the identity that posted it, which is why media stays on
|
|
7
|
+
// the pod even for an identity whose ids are advertised elsewhere: proxying
|
|
8
|
+
// blobs would be pure cost for no verification gained.
|
|
9
|
+
|
|
10
|
+
const PUBLIC_READ = ['Read'];
|
|
11
|
+
|
|
12
|
+
// ---- the owner's agent ----
|
|
13
|
+
|
|
14
|
+
export async function provisionContainer(pod, urls) {
|
|
15
|
+
await pod.putJson(urls.media + '.keep', { keep: true }, 'application/json');
|
|
16
|
+
await pod.setAcl(urls.media, PUBLIC_READ);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* One uploaded file.
|
|
21
|
+
*
|
|
22
|
+
* Takes the container as given: an upload into a container that was never
|
|
23
|
+
* provisioned is a 404, or worse a file nobody outside can read, so the caller
|
|
24
|
+
* ensures the container first — see provisionContainer, and the `exists` probe
|
|
25
|
+
* in containers.mjs that keeps a worker restart from re-provisioning.
|
|
26
|
+
*/
|
|
27
|
+
export const write = (pod, url, bytes, contentType) => pod.put(url, bytes, contentType);
|