fedipod-server 0.21.0 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -4
- package/dist/claims.d.ts +3 -1
- package/dist/claims.js +20 -7
- package/dist/components/context.jsonld +6 -0
- package/dist/handler.d.ts +29 -1
- package/dist/handler.js +89 -4
- package/dist/handler.jsonld +36 -1
- package/lib/client/c2s.mjs +100 -3
- package/lib/client/masto/accounts.mjs +44 -4
- package/lib/client/masto/body.mjs +90 -3
- package/lib/client/masto/index.mjs +5 -1
- package/lib/client/masto/instance.mjs +11 -3
- package/lib/client/masto/media.mjs +3 -57
- package/lib/client/masto/oauth.mjs +66 -9
- package/lib/client/masto/render.mjs +62 -5
- package/lib/client/masto/statuses.mjs +25 -1
- package/lib/client/masto/timelines.mjs +128 -6
- package/lib/connections/tagfeed.mjs +42 -5
- package/lib/core/as2.mjs +36 -5
- package/lib/core/contexts/index.mjs +14 -0
- package/lib/core/contexts/litepub-0.1.json +60 -0
- package/lib/core/contexts/map.json +3 -1
- package/lib/core/contexts/quotes.json +18 -0
- package/lib/core/graphview.mjs +41 -3
- package/lib/core/intake/activities.mjs +136 -2
- package/lib/core/intake/activity.mjs +79 -1
- package/lib/core/intake/group.mjs +37 -3
- package/lib/core/intake/index.mjs +32 -2
- package/lib/core/intake/notes.mjs +60 -3
- package/lib/core/lease.mjs +12 -2
- package/lib/core/profile-page.mjs +104 -0
- package/lib/core/publisher/collections.mjs +6 -1
- package/lib/core/publisher/index.mjs +35 -17
- package/lib/core/publisher/notes.mjs +104 -3
- package/lib/core/shapes/activitystreams.ttl +28 -0
- package/lib/core/shapes/index.mjs +26 -1
- package/lib/core/wire-quotes.mjs +71 -0
- package/lib/core/wire.mjs +59 -103
- package/lib/device/admin/routes/lifecycle.mjs +9 -0
- package/lib/device/admin/static.mjs +4 -1
- package/lib/device/admin/surface.mjs +26 -2
- package/lib/device/certs.mjs +18 -2
- package/lib/device/cli/commands/setup.mjs +6 -2
- package/lib/device/cli/commands/state.mjs +3 -1
- package/lib/device/gateway-move.mjs +58 -0
- package/lib/device/migrate.mjs +3 -1
- package/lib/device/setup.mjs +67 -7
- package/lib/gateway/front-core.mjs +307 -36
- package/lib/gateway/gateway-core.mjs +15 -6
- package/lib/gateway/notices.mjs +84 -0
- package/lib/gateway/quiet.mjs +184 -0
- package/lib/pod/root.mjs +6 -3
- package/lib/pod/state.mjs +8 -7
- package/lib/pod/transport.mjs +5 -1
- package/lib/session/README.md +123 -0
- package/lib/session/demo.html +94 -0
- package/lib/session/fedi-account.mjs +346 -0
- package/lib/session/fedi-login.mjs +123 -0
- package/lib/session/oidc-session.mjs +233 -0
- package/lib/session/package.json +41 -0
- package/package.json +1 -1
- package/run-agent.mjs +28 -0
- package/vendor/gate.cjs +6 -3
- package/web/admin/bar.css +66 -0
- package/web/admin/bar.js +38 -0
- package/web/admin/client/index.html +31 -3
- package/web/admin/client-pick.js +79 -0
- package/web/admin/gateway.js +22 -0
- package/web/admin/index.html +54 -13
- package/web/admin/notices-bar.js +90 -0
- package/web/admin/oauth-signin.mjs +37 -0
- package/web/admin/record.js +13 -1
- package/web/admin/setup/index.html +31 -3
- package/web/admin/tokens.css +15 -3
- package/web/admin/upkeep.js +4 -5
- package/web/app/README.md +14 -10
- package/web/app/admin-facade.mjs +51 -12
- package/web/app/agent.mjs +157 -19
- package/web/app/authorize_interaction.html +56 -0
- package/web/app/authorize_interaction.js +69 -0
- package/web/app/boot.mjs +233 -95
- package/web/app/dist/boot.js +477 -569
- package/web/app/dist/boot.js.map +4 -4
- package/web/app/dist/sw.js +1861 -532
- package/web/app/dist/sw.js.map +4 -4
- package/web/app/gateway-move.mjs +60 -0
- package/web/app/index.html +40 -49
- package/web/app/keys-browser.mjs +12 -15
- package/web/app/keystore.mjs +19 -36
- package/web/app/oidc-session.mjs +5 -188
- package/web/app/signup.mjs +172 -132
- package/web/app/sw-src.mjs +1 -1
- package/web/app/update.js +66 -0
- package/web/front/admin.html +5 -3
- package/web/front/new-account.html +2 -2
- package/web/front/notices.html +69 -0
- package/web/front/notices.js +107 -0
- package/web/front/run.html +15 -8
- package/web/front/run.js +124 -36
- package/web/icons/forum.svg +11 -0
- package/web/icons/gateway.svg +14 -0
- package/web/icons/local.svg +11 -0
- package/web/app/index.html~ +0 -152
- package/web/app/site/_headers +0 -9
- package/web/app/site/_redirects +0 -22
- package/web/app/site/admin/actors.js +0 -145
- package/web/app/site/admin/bar.css +0 -60
- package/web/app/site/admin/bar.js +0 -61
- package/web/app/site/admin/client/client.js +0 -66
- package/web/app/site/admin/client/index.html +0 -43
- package/web/app/site/admin/common.js +0 -23
- package/web/app/site/admin/connections.js +0 -112
- package/web/app/site/admin/gateway.js +0 -111
- package/web/app/site/admin/group.js +0 -258
- package/web/app/site/admin/index.html +0 -564
- package/web/app/site/admin/record.js +0 -378
- package/web/app/site/admin/setup/index.html +0 -215
- package/web/app/site/admin/setup/setup.js +0 -326
- package/web/app/site/admin/tokens.css +0 -56
- package/web/app/site/admin/upkeep.js +0 -178
- package/web/app/site/admin/window.css +0 -55
- package/web/app/site/admin/window.js +0 -136
- package/web/app/site/app/404.html +0 -40
- package/web/app/site/app/_headers +0 -2
- package/web/app/site/app/apple-touch-icon.png +0 -0
- package/web/app/site/app/assets/ICONS-DIPJeU0C.js +0 -2
- package/web/app/site/app/assets/ICONS-DIPJeU0C.js.map +0 -1
- package/web/app/site/app/assets/ScheduledAtField-D-Abfo4J.js +0 -183
- package/web/app/site/app/assets/ScheduledAtField-D-Abfo4J.js.map +0 -1
- package/web/app/site/app/assets/Temml-CCjoacWt.woff2 +0 -0
- package/web/app/site/app/assets/__vite-browser-external-C7iut881.js +0 -2
- package/web/app/site/app/assets/__vite-browser-external-C7iut881.js.map +0 -1
- 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 +0 -1
- package/web/app/site/app/assets/compose-CFEx7wb4.js +0 -2
- package/web/app/site/app/assets/compose-CFEx7wb4.js.map +0 -1
- package/web/app/site/app/assets/compose-ZJN5xSb4.js +0 -35
- package/web/app/site/app/assets/compose-ZJN5xSb4.js.map +0 -1
- package/web/app/site/app/assets/debug.module-YFQgwnIr.js +0 -52
- package/web/app/site/app/assets/debug.module-YFQgwnIr.js.map +0 -1
- package/web/app/site/app/assets/dom-p1YcJ5Rw.js +0 -2
- package/web/app/site/app/assets/dom-p1YcJ5Rw.js.map +0 -1
- 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 +0 -1
- package/web/app/site/app/assets/icons/alert-line-CpFUMf9s.js +0 -1
- package/web/app/site/app/assets/icons/android-2-line-Bzxnrd3S.js +0 -1
- package/web/app/site/app/assets/icons/announcement-line-5nwaJFJm.js +0 -1
- package/web/app/site/app/assets/icons/arrow-down-circle-line-a7Id9-Iv.js +0 -1
- package/web/app/site/app/assets/icons/arrow-down-line-B8uzXzq6.js +0 -1
- package/web/app/site/app/assets/icons/arrow-left-line-CGrGT9C-.js +0 -1
- package/web/app/site/app/assets/icons/arrow-right-line-CE-HERfX.js +0 -1
- package/web/app/site/app/assets/icons/arrow-to-up-line-CvdQjmcy.js +0 -1
- package/web/app/site/app/assets/icons/arrow-up-circle-line-DEparSNF.js +0 -1
- package/web/app/site/app/assets/icons/arrow-up-line-C7yBeugc.js +0 -1
- package/web/app/site/app/assets/icons/arrows-right-line-BtbXmJGP.js +0 -1
- package/web/app/site/app/assets/icons/at-line-CF66Bznk.js +0 -1
- package/web/app/site/app/assets/icons/attachment-line-BY8It-l9.js +0 -1
- package/web/app/site/app/assets/icons/blockquote-line-BS6pU_hm.js +0 -1
- package/web/app/site/app/assets/icons/board-line-C0UWXyA6.js +0 -1
- package/web/app/site/app/assets/icons/bookmark-line-1CtX5JLF.js +0 -1
- package/web/app/site/app/assets/icons/building-5-line-Dd59MzbO.js +0 -1
- package/web/app/site/app/assets/icons/bus-2-line-6_dvA0GG.js +0 -1
- package/web/app/site/app/assets/icons/calendar-day-line-Crpl_fmu.js +0 -1
- package/web/app/site/app/assets/icons/calendar-month-line-vommts2C.js +0 -1
- package/web/app/site/app/assets/icons/calendar-time-add-line-BU_DTlQk.js +0 -1
- package/web/app/site/app/assets/icons/camera-line-Cv69MEei.js +0 -1
- package/web/app/site/app/assets/icons/celebrate-line-B8DvIoi0.js +0 -1
- package/web/app/site/app/assets/icons/chart-bar-line-FK-QOmtF.js +0 -1
- package/web/app/site/app/assets/icons/chart-line-line-BGcJAFWw.js +0 -1
- package/web/app/site/app/assets/icons/chat-3-line-YFK2-hwz.js +0 -1
- package/web/app/site/app/assets/icons/check-circle-line-DsAxFr5-.js +0 -1
- package/web/app/site/app/assets/icons/clipboard-line-BBp364gJ.js +0 -1
- package/web/app/site/app/assets/icons/close-circle-line-CICdiuGz.js +0 -1
- package/web/app/site/app/assets/icons/close-line-DYrFWcjv.js +0 -1
- package/web/app/site/app/assets/icons/cloud-line-tiQAbah8.js +0 -1
- package/web/app/site/app/assets/icons/code-line-fAl8iiV3.js +0 -1
- package/web/app/site/app/assets/icons/comment-2-line-CR3g7WeB.js +0 -1
- package/web/app/site/app/assets/icons/copy-2-line-CLW69yF5.js +0 -1
- package/web/app/site/app/assets/icons/delete-2-line-B8xmbr4n.js +0 -1
- package/web/app/site/app/assets/icons/document-3-line-DBGNhAg2.js +0 -1
- package/web/app/site/app/assets/icons/document-line-C7mb9sb7.js +0 -1
- package/web/app/site/app/assets/icons/down-line-BGqSSThd.js +0 -1
- package/web/app/site/app/assets/icons/edit-4-line-DcgGaEhx.js +0 -1
- package/web/app/site/app/assets/icons/emoji-2-line-D8YgnM-y.js +0 -1
- package/web/app/site/app/assets/icons/exit-line-Dc07BS-x.js +0 -1
- package/web/app/site/app/assets/icons/external-link-line-BvpSkmjO.js +0 -1
- package/web/app/site/app/assets/icons/eye-2-line-CiWXx-f_.js +0 -1
- package/web/app/site/app/assets/icons/eye-close-line-BgTaSJ4P.js +0 -1
- package/web/app/site/app/assets/icons/filter-2-line-B_mR2usy.js +0 -1
- package/web/app/site/app/assets/icons/filter-line-C_nm-3cU.js +0 -1
- package/web/app/site/app/assets/icons/flag-1-line-C2HB05I0.js +0 -1
- package/web/app/site/app/assets/icons/forbid-circle-line-D3LTT9yd.js +0 -1
- package/web/app/site/app/assets/icons/formula-line-DLrt2d6j.js +0 -1
- package/web/app/site/app/assets/icons/grid-line-CgUmEikf.js +0 -1
- package/web/app/site/app/assets/icons/group-line-C5fnX0wU.js +0 -1
- package/web/app/site/app/assets/icons/hand-finger-2-line-BGgxbuL9.js +0 -1
- package/web/app/site/app/assets/icons/hashtag-line-CyTEEXBb.js +0 -1
- package/web/app/site/app/assets/icons/heart-crack-line-BZddbO2C.js +0 -1
- package/web/app/site/app/assets/icons/heart-line-CJ7K47J4.js +0 -1
- package/web/app/site/app/assets/icons/history-2-line-kWWvFzqv.js +0 -1
- package/web/app/site/app/assets/icons/history-line-bGQiwbTH.js +0 -1
- package/web/app/site/app/assets/icons/home-3-line-fRU1zeLG.js +0 -1
- package/web/app/site/app/assets/icons/information-line-iNk6-sOY.js +0 -1
- package/web/app/site/app/assets/icons/keyboard-line-DsCyuQNz.js +0 -1
- package/web/app/site/app/assets/icons/layout-4-line-CZkOX52Z.js +0 -1
- package/web/app/site/app/assets/icons/layout-5-line-B_znt5wO.js +0 -1
- package/web/app/site/app/assets/icons/left-line-eUw0tya4.js +0 -1
- package/web/app/site/app/assets/icons/lightning-line-DaKoDdix.js +0 -1
- package/web/app/site/app/assets/icons/link-2-line-DCqLmMA7.js +0 -1
- package/web/app/site/app/assets/icons/list-check-line-BAxosBh9.js +0 -1
- package/web/app/site/app/assets/icons/lock-line-CPoaXeUW.js +0 -1
- package/web/app/site/app/assets/icons/mail-line-B0P8aa1L.js +0 -1
- package/web/app/site/app/assets/icons/moon-line-CEqNAToP.js +0 -1
- package/web/app/site/app/assets/icons/more-1-fill-Dw2e5xab.js +0 -1
- package/web/app/site/app/assets/icons/more-3-line-BIiVjYTz.js +0 -1
- package/web/app/site/app/assets/icons/notification-line-BZZq2Gtu.js +0 -1
- package/web/app/site/app/assets/icons/pencil-line-CKh8-A1v.js +0 -1
- package/web/app/site/app/assets/icons/photo-album-line-Bnfg2u40.js +0 -1
- package/web/app/site/app/assets/icons/pin-line-DpTolyhs.js +0 -1
- package/web/app/site/app/assets/icons/play-fill-B0-G_8lX.js +0 -1
- package/web/app/site/app/assets/icons/qrcode-2-line-B6SPCaHn.js +0 -1
- package/web/app/site/app/assets/icons/quill-pen-line-B6LUrnCP.js +0 -1
- package/web/app/site/app/assets/icons/quote-left-fill-hQZ1cj_6.js +0 -1
- package/web/app/site/app/assets/icons/radar-line-CKyroTth.js +0 -1
- package/web/app/site/app/assets/icons/react-line-CoIKhhtK.js +0 -1
- package/web/app/site/app/assets/icons/refresh-2-line-D0mTF97S.js +0 -1
- package/web/app/site/app/assets/icons/right-line-B3KWJaqc.js +0 -1
- package/web/app/site/app/assets/icons/rocket-line-4KZl537D.js +0 -1
- package/web/app/site/app/assets/icons/round-fill-C69NTrZ0.js +0 -1
- package/web/app/site/app/assets/icons/round-line-B9k_1uo-.js +0 -1
- package/web/app/site/app/assets/icons/route-line-B28ReHcE.js +0 -1
- package/web/app/site/app/assets/icons/rows-4-line-CrBniagM.js +0 -1
- package/web/app/site/app/assets/icons/scan-line-DSf74YdP.js +0 -1
- package/web/app/site/app/assets/icons/search-2-line-B6geIU1z.js +0 -1
- package/web/app/site/app/assets/icons/settings-3-line-azJwK47w.js +0 -1
- package/web/app/site/app/assets/icons/settings-6-line-C_5zc5YR.js +0 -1
- package/web/app/site/app/assets/icons/share-2-line-D5MxWjrm.js +0 -1
- package/web/app/site/app/assets/icons/share-forward-line-Cp61fZzZ.js +0 -1
- package/web/app/site/app/assets/icons/sparkles-2-line-DYYyXTaW.js +0 -1
- package/web/app/site/app/assets/icons/sparkles-line-C2hY1s6I.js +0 -1
- package/web/app/site/app/assets/icons/time-line-BF3eet1v.js +0 -1
- package/web/app/site/app/assets/icons/transfer-4-line-oisfvTBw.js +0 -1
- package/web/app/site/app/assets/icons/translate-line-BFWol2Ce.js +0 -1
- package/web/app/site/app/assets/icons/unlock-line-X_x5vwv3.js +0 -1
- package/web/app/site/app/assets/icons/upload-3-line-DgzwUJeW.js +0 -1
- package/web/app/site/app/assets/icons/user-4-line-BVSynZCp.js +0 -1
- package/web/app/site/app/assets/icons/user-add-2-line-BlGS5Q8q.js +0 -1
- package/web/app/site/app/assets/icons/user-add-line-BbXdHG4z.js +0 -1
- package/web/app/site/app/assets/icons/user-edit-line-BFAHOV0X.js +0 -1
- package/web/app/site/app/assets/icons/user-follow-line-BXux0wSd.js +0 -1
- package/web/app/site/app/assets/icons/user-star-line-DdYlpHLU.js +0 -1
- package/web/app/site/app/assets/icons/user-warning-line-Cj43j0_E.js +0 -1
- package/web/app/site/app/assets/icons/user-x-line-DIDgNlPH.js +0 -1
- package/web/app/site/app/assets/icons/volume-line-Bs390KQt.js +0 -1
- package/web/app/site/app/assets/icons/volume-mute-line-DzlzIqFZ.js +0 -1
- package/web/app/site/app/assets/icons/walk-line-yNXfB-md.js +0 -1
- package/web/app/site/app/assets/icons/world-2-line-CenkGIwj.js +0 -1
- package/web/app/site/app/assets/icons/zoom-in-line-CaENUphf.js +0 -1
- package/web/app/site/app/assets/icons/zoom-out-line-BDNk0EWo.js +0 -1
- package/web/app/site/app/assets/instances-BNPptnn-.json +0 -333
- package/web/app/site/app/assets/locales/ar-SA-CfheRbCu.js +0 -1
- package/web/app/site/app/assets/locales/ca-ES-DHRW86Tz.js +0 -1
- package/web/app/site/app/assets/locales/cs-CZ-CKoqEuA1.js +0 -1
- package/web/app/site/app/assets/locales/de-DE-ByRPMRFy.js +0 -1
- package/web/app/site/app/assets/locales/eo-UY-CPC7wLCS.js +0 -1
- package/web/app/site/app/assets/locales/es-ES-dJa0Fd9t.js +0 -1
- package/web/app/site/app/assets/locales/eu-ES-C3djlPR2.js +0 -1
- package/web/app/site/app/assets/locales/fa-IR-DTT490Ck.js +0 -1
- package/web/app/site/app/assets/locales/fi-FI-B23MCPuz.js +0 -1
- package/web/app/site/app/assets/locales/fr-FR-BcaQsO5C.js +0 -1
- package/web/app/site/app/assets/locales/gl-ES-BR-CVKBN.js +0 -1
- package/web/app/site/app/assets/locales/he-IL-CfheRbCu.js +0 -1
- package/web/app/site/app/assets/locales/hu-HU-CfheRbCu.js +0 -1
- package/web/app/site/app/assets/locales/it-IT-C1136UFV.js +0 -1
- package/web/app/site/app/assets/locales/ja-JP-CRZmRAhD.js +0 -1
- package/web/app/site/app/assets/locales/kab-Cz8xH_TT.js +0 -1
- package/web/app/site/app/assets/locales/ko-KR-CG9UiY4o.js +0 -1
- package/web/app/site/app/assets/locales/lt-LT-CJcI6sHZ.js +0 -1
- package/web/app/site/app/assets/locales/nb-NO-CfheRbCu.js +0 -1
- package/web/app/site/app/assets/locales/nl-NL-DxN4Yc1t.js +0 -1
- package/web/app/site/app/assets/locales/oc-FR-CfheRbCu.js +0 -1
- package/web/app/site/app/assets/locales/pl-PL-DkwGXf6P.js +0 -1
- package/web/app/site/app/assets/locales/pseudo-LOCALE-CQ4XxkQ0.js +0 -1
- package/web/app/site/app/assets/locales/pt-BR-nGL0HdFS.js +0 -1
- package/web/app/site/app/assets/locales/pt-PT-Cp17aj2z.js +0 -1
- package/web/app/site/app/assets/locales/ru-RU-0Y8cIoaK.js +0 -1
- package/web/app/site/app/assets/locales/th-TH-CfheRbCu.js +0 -1
- package/web/app/site/app/assets/locales/tok-CfheRbCu.js +0 -1
- package/web/app/site/app/assets/locales/tr-TR-CfheRbCu.js +0 -1
- package/web/app/site/app/assets/locales/uk-UA-C5E3dFBf.js +0 -1
- package/web/app/site/app/assets/locales/zh-CN-BHJi7_et.js +0 -1
- package/web/app/site/app/assets/locales/zh-TW-CfheRbCu.js +0 -1
- package/web/app/site/app/assets/main-BdqNbG-a.js +0 -26
- package/web/app/site/app/assets/main-BdqNbG-a.js.map +0 -1
- package/web/app/site/app/assets/mock-home-DGhR__He.js +0 -2
- package/web/app/site/app/assets/mock-home-DGhR__He.js.map +0 -1
- 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 +0 -2
- package/web/app/site/app/assets/polyfill-force-H8sUkLGp.js.map +0 -1
- package/web/app/site/app/assets/pwa-viewport-ogKbF51K.js +0 -20
- package/web/app/site/app/assets/pwa-viewport-ogKbF51K.js.map +0 -1
- package/web/app/site/app/assets/style-B1iLPTPR.css +0 -2
- package/web/app/site/app/assets/temml-DuyIQMei.js +0 -97
- package/web/app/site/app/assets/temml-DuyIQMei.js.map +0 -1
- package/web/app/site/app/assets/tinyld.light.node-DtbBhLYC.js +0 -2
- package/web/app/site/app/assets/tinyld.light.node-DtbBhLYC.js.map +0 -1
- 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 +0 -3
- package/web/app/site/app/assets/year-in-posts-DJv_star.js.map +0 -1
- package/web/app/site/app/compose/index.html +0 -20
- package/web/app/site/app/favicon.ico +0 -0
- package/web/app/site/app/index.html +0 -73
- 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 +0 -1
- 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 +0 -2
- package/web/app/site/app/version.json +0 -1
- package/web/app/site/boot.js +0 -34277
- package/web/app/site/index.html +0 -251
- package/web/app/site/sw.js +0 -71331
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
This is a CSS component. Install it in a fresh server instance or an instance
|
|
6
6
|
you already run for pods, and turn sign-up on. A pod owner signs in at the
|
|
7
|
-
server's
|
|
7
|
+
server's `/.fediverse-account` page and opts in; from then on their pod is a full fediverse
|
|
8
8
|
account: it accepts follows, delivers their posts, and serves their Mastodon
|
|
9
9
|
client at the pod's own address. Signing up is the only way an account is
|
|
10
10
|
made, and opting out is the only way one ends.
|
|
@@ -110,7 +110,8 @@ naming the pod the identity runs on. Nothing private is in it.
|
|
|
110
110
|
| `agentDataDir` | Where each identity keeps the file naming its pod; its log lines go to the server's own log. Required whenever sign-up is on. |
|
|
111
111
|
| `agentUiPath` | Where the owner's pages live on the pod's origin. `/fp/` by default; empty serves no pages. On a suffix pod it is under the pod's path, e.g. `/aisha/fp/`. |
|
|
112
112
|
| `agentRegistryContainer` | The internal container holding the sign-up rows. |
|
|
113
|
-
| `runPage` | The HTML
|
|
113
|
+
| `runPage` | The HTML of the page where a pod owner opts in or out. The package's own `web/front/run.html` is served unless you set this. |
|
|
114
|
+
| `runPath` | Where that page answers. `/.fediverse-account` unless you set it. The path it takes is one the pod no longer serves, so it is yours to name; the dot keeps it out of the way of anybody's data. |
|
|
114
115
|
| `agentWebIdSuffix` | Path from a pod's base to its owner's WebID. Defaults to `profile/card#me`. |
|
|
115
116
|
| `agentPollSeconds` | How often the inbox is swept. Deliveries also wake the sweep as they land, so this is the fallback. |
|
|
116
117
|
| `agentAutoAcceptFollows` | Whether a newly provisioned identity accepts follows without review. On by default. |
|
|
@@ -122,7 +123,7 @@ naming the pod the identity runs on. Nothing private is in it.
|
|
|
122
123
|
| `directoryContainer` | The internal container holding the handle directory. |
|
|
123
124
|
| `agentAutoFront` | Whether every identity that starts also gets a `@handle@frontHost` address — an inbox-only directory row, written once. Off by default. |
|
|
124
125
|
| `offersPods` | Whether the signup page offers to create pods here. |
|
|
125
|
-
| `signupPage` |
|
|
126
|
+
| `signupPage` | Unused on a pod server: making an account and a pod is the server's own, and this component does not answer `/`, `/signup` or `/new-account`. A gateway, which has no pod server behind it, serves this page. |
|
|
126
127
|
| `gatewayWebId` | The WebID stamped on verification receipts. |
|
|
127
128
|
|
|
128
129
|
`signupPage` and `runPage` are the page's HTML itself, not a path to a file.
|
|
@@ -195,7 +196,7 @@ as before.
|
|
|
195
196
|
|
|
196
197
|
## How sign-up works
|
|
197
198
|
|
|
198
|
-
A pod owner opens the
|
|
199
|
+
A pod owner opens the `/.fediverse-account` page, signs in with their pod,
|
|
199
200
|
and opts in or out. Underneath it is one endpoint:
|
|
200
201
|
|
|
201
202
|
```
|
package/dist/claims.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
/** Where a pod owner opts their identity in, when the operator named it. */
|
|
2
|
+
export declare const DEFAULT_RUN_PATH = "/.fediverse-account";
|
|
1
3
|
export declare function claims(input: {
|
|
2
4
|
host?: string;
|
|
3
5
|
pathname: string;
|
|
4
|
-
}, frontHost: string): boolean;
|
|
6
|
+
}, frontHost: string, runPath?: string): boolean;
|
|
5
7
|
export declare const isInboxPath: (pathname: string) => boolean;
|
|
6
8
|
/**
|
|
7
9
|
* True when this path belongs to an identity's client surface.
|
package/dist/claims.js
CHANGED
|
@@ -4,22 +4,35 @@
|
|
|
4
4
|
// front's apex the gateway answers the fediverse routes; a pod subdomain is a
|
|
5
5
|
// real Solid pod and is never claimed.
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.isInboxPath = void 0;
|
|
7
|
+
exports.isInboxPath = exports.DEFAULT_RUN_PATH = void 0;
|
|
8
8
|
exports.claims = claims;
|
|
9
9
|
exports.agentClaims = agentClaims;
|
|
10
|
-
|
|
10
|
+
// Signing up is CSS's, untouched: a person makes an account and a pod the way
|
|
11
|
+
// this server already does it, and nothing here needs saying to them about
|
|
12
|
+
// FediPod. Becoming a fediverse identity comes later and separately — they
|
|
13
|
+
// have a pod, they are logged in, and they opt it in at /run. So none of '/',
|
|
14
|
+
// '/signup' or '/new-account' is claimed on a pod server. A gateway is the
|
|
15
|
+
// other case: it has no pod server behind it, arranging the pod IS its job,
|
|
16
|
+
// and its own front keeps those pages.
|
|
17
|
+
const FRONT_PATHS = new Set(['/roster',
|
|
11
18
|
'/.well-known/webfinger', '/api/handle', '/api/attach', '/api/agent',
|
|
12
19
|
'/api/roster', '/api/revoke',
|
|
13
|
-
// What the pages load: the sign-in library the
|
|
14
|
-
// the installer
|
|
15
|
-
'/solid-oidc-client.js', '/install'
|
|
16
|
-
|
|
20
|
+
// What the pages that remain load: the sign-in library the opt-in and
|
|
21
|
+
// roster pages use, and the installer, which is FediPod's own to hand out.
|
|
22
|
+
'/solid-oidc-client.js', '/install',
|
|
23
|
+
// Each page's own script, served beside it; a page without its script has
|
|
24
|
+
// buttons that never wake up.
|
|
25
|
+
'/new-account.js', '/run.js', '/admin.js']);
|
|
26
|
+
/** Where a pod owner opts their identity in, when the operator named it. */
|
|
27
|
+
exports.DEFAULT_RUN_PATH = '/.fediverse-account';
|
|
28
|
+
function claims(input, frontHost, runPath = exports.DEFAULT_RUN_PATH) {
|
|
17
29
|
if (!input.host || !frontHost)
|
|
18
30
|
return false;
|
|
19
31
|
const bare = String(input.host).split(':')[0].toLowerCase();
|
|
20
32
|
if (bare !== String(frontHost).toLowerCase())
|
|
21
33
|
return false; // a pod subdomain → not ours
|
|
22
|
-
return FRONT_PATHS.has(input.pathname) || input.pathname
|
|
34
|
+
return FRONT_PATHS.has(input.pathname) || input.pathname === runPath
|
|
35
|
+
|| input.pathname.startsWith('/u/')
|
|
23
36
|
|| input.pathname.startsWith('/@'); // the short profile address
|
|
24
37
|
}
|
|
25
38
|
// What an identity answers on its own pod's origin: the protocol routes other
|
|
@@ -32,6 +32,9 @@
|
|
|
32
32
|
"args_runPage": {
|
|
33
33
|
"@id": "fps:dist/handler.jsonld#FediPodServerHandler_args_runPage"
|
|
34
34
|
},
|
|
35
|
+
"args_runPath": {
|
|
36
|
+
"@id": "fps:dist/handler.jsonld#FediPodServerHandler_args_runPath"
|
|
37
|
+
},
|
|
35
38
|
"args_adminPage": {
|
|
36
39
|
"@id": "fps:dist/handler.jsonld#FediPodServerHandler_args_adminPage"
|
|
37
40
|
},
|
|
@@ -86,6 +89,9 @@
|
|
|
86
89
|
"runPage": {
|
|
87
90
|
"@id": "fps:dist/handler.jsonld#FediPodServerHandler_args_runPage"
|
|
88
91
|
},
|
|
92
|
+
"runPath": {
|
|
93
|
+
"@id": "fps:dist/handler.jsonld#FediPodServerHandler_args_runPath"
|
|
94
|
+
},
|
|
89
95
|
"adminPage": {
|
|
90
96
|
"@id": "fps:dist/handler.jsonld#FediPodServerHandler_args_adminPage"
|
|
91
97
|
},
|
package/dist/handler.d.ts
CHANGED
|
@@ -16,8 +16,10 @@ export interface FediPodServerArgs {
|
|
|
16
16
|
offersPods?: boolean;
|
|
17
17
|
/** The new-account page HTML served at the root. */
|
|
18
18
|
signupPage?: string;
|
|
19
|
-
/** The run-your-identity page HTML
|
|
19
|
+
/** The run-your-identity page HTML. */
|
|
20
20
|
runPage?: string;
|
|
21
|
+
/** Where that page answers. Defaults to `/.fediverse-account`; the path it takes is one the pod server no longer serves, so an operator may name it. */
|
|
22
|
+
runPath?: string;
|
|
21
23
|
/** The accounts-roster page HTML served at /roster. */
|
|
22
24
|
adminPage?: string;
|
|
23
25
|
/** Directory holding each agent identity's signing key and log. Required when runtime opt-in is on. */
|
|
@@ -63,6 +65,7 @@ export declare class FediPodServerHandler extends HttpHandler implements Initial
|
|
|
63
65
|
private readonly args;
|
|
64
66
|
private readonly io;
|
|
65
67
|
readonly dir: Directory;
|
|
68
|
+
private readonly runPath;
|
|
66
69
|
private readonly podPut;
|
|
67
70
|
private readonly logger;
|
|
68
71
|
private readonly claimed;
|
|
@@ -149,12 +152,37 @@ export declare class FediPodServerHandler extends HttpHandler implements Initial
|
|
|
149
152
|
* reply and nowhere else. Re-opting-in rotates the secret — that is how a
|
|
150
153
|
* lost one is recovered.
|
|
151
154
|
*/
|
|
155
|
+
/**
|
|
156
|
+
* The WebIDs behind the account session in the request, or none. The server
|
|
157
|
+
* keeps that session in a `css-account` cookie; its own account API is what
|
|
158
|
+
* says which WebIDs the session owns, and it takes the same cookie. Nothing
|
|
159
|
+
* here trusts the cookie's presence — the account API does the deciding, and
|
|
160
|
+
* the caller picks the WebID that owns the pod being claimed.
|
|
161
|
+
*/
|
|
162
|
+
webIdsFromSession(request: {
|
|
163
|
+
headers: {
|
|
164
|
+
get(name: string): string | null;
|
|
165
|
+
};
|
|
166
|
+
}): Promise<string[]>;
|
|
152
167
|
optInPod({ podBase, webId }: {
|
|
153
168
|
podBase: string;
|
|
154
169
|
webId: string;
|
|
155
170
|
}): Promise<Record<string, unknown> & {
|
|
156
171
|
httpStatus: number;
|
|
157
172
|
}>;
|
|
173
|
+
/**
|
|
174
|
+
* What a pod would be as an identity here, without changing anything: its
|
|
175
|
+
* handle, the host its address carries, and whether it runs here already.
|
|
176
|
+
*/
|
|
177
|
+
describePod({ podBase }: {
|
|
178
|
+
podBase: string;
|
|
179
|
+
}): Promise<{
|
|
180
|
+
handle: string;
|
|
181
|
+
host: string;
|
|
182
|
+
address: string;
|
|
183
|
+
running: boolean;
|
|
184
|
+
manage: string;
|
|
185
|
+
}>;
|
|
158
186
|
/** The reverse: stop the identity and let the pod be plain LDP again. */
|
|
159
187
|
optOutPod({ podBase }: {
|
|
160
188
|
podBase: string;
|
package/dist/handler.js
CHANGED
|
@@ -58,6 +58,13 @@ function deriveHandle(podBase) {
|
|
|
58
58
|
const segments = u.pathname.split('/').filter((seg) => seg.length > 0);
|
|
59
59
|
return segments.length > 0 ? segments[segments.length - 1] : u.hostname.split('.')[0];
|
|
60
60
|
}
|
|
61
|
+
/** The opt-in page's path: leading slash, no trailing one, the default when unset. */
|
|
62
|
+
function normalizeRunPath(raw) {
|
|
63
|
+
const said = String(raw ?? '').trim().replace(/\/+$/u, '');
|
|
64
|
+
if (!said)
|
|
65
|
+
return claims_1.DEFAULT_RUN_PATH;
|
|
66
|
+
return said.startsWith('/') ? said : `/${said}`;
|
|
67
|
+
}
|
|
61
68
|
/** A door path always has both slashes, so claiming and stripping agree. */
|
|
62
69
|
function normalizeUiPath(raw) {
|
|
63
70
|
if (raw === '')
|
|
@@ -69,6 +76,7 @@ class FediPodServerHandler extends community_server_1.HttpHandler {
|
|
|
69
76
|
args;
|
|
70
77
|
io;
|
|
71
78
|
dir;
|
|
79
|
+
runPath;
|
|
72
80
|
podPut;
|
|
73
81
|
logger = (0, community_server_1.getLoggerFor)(this);
|
|
74
82
|
// Every pod whose routes this server claims, keyed by pod base. A claim
|
|
@@ -107,6 +115,9 @@ class FediPodServerHandler extends community_server_1.HttpHandler {
|
|
|
107
115
|
// hostname, not host: the claim check compares bare hostnames, so a port
|
|
108
116
|
// carried here would stop the front matching its own requests.
|
|
109
117
|
this.frontHost = args.frontHost || new URL(args.frontOrigin).hostname;
|
|
118
|
+
// One answer for both the claim and the route, so a server cannot answer
|
|
119
|
+
// at one path and refuse at another.
|
|
120
|
+
this.runPath = normalizeRunPath(args.runPath);
|
|
110
121
|
this.uiPath = normalizeUiPath(args.agentUiPath);
|
|
111
122
|
this.registry = args.agentRuntimeOptIn
|
|
112
123
|
? (0, directory_1.makeAgentRegistry)(this.io, absolute(args.agentRegistryContainer ?? '/.internal/fedipod/agents/'))
|
|
@@ -391,7 +402,7 @@ class FediPodServerHandler extends community_server_1.HttpHandler {
|
|
|
391
402
|
// The front's own routes win first, so a suffix pod can never shadow the
|
|
392
403
|
// door's dispatch, its WebFinger or its API even where its mount would
|
|
393
404
|
// otherwise contain that path.
|
|
394
|
-
if ((0, claims_1.claims)({ host, pathname }, this.frontHost))
|
|
405
|
+
if ((0, claims_1.claims)({ host, pathname }, this.frontHost, this.runPath))
|
|
395
406
|
return;
|
|
396
407
|
// Claimed from the opt-in roster, never from what is running: a pod resource
|
|
397
408
|
// must not be served by CSS for the seconds before an identity finishes
|
|
@@ -432,6 +443,37 @@ class FediPodServerHandler extends community_server_1.HttpHandler {
|
|
|
432
443
|
* reply and nowhere else. Re-opting-in rotates the secret — that is how a
|
|
433
444
|
* lost one is recovered.
|
|
434
445
|
*/
|
|
446
|
+
/**
|
|
447
|
+
* The WebIDs behind the account session in the request, or none. The server
|
|
448
|
+
* keeps that session in a `css-account` cookie; its own account API is what
|
|
449
|
+
* says which WebIDs the session owns, and it takes the same cookie. Nothing
|
|
450
|
+
* here trusts the cookie's presence — the account API does the deciding, and
|
|
451
|
+
* the caller picks the WebID that owns the pod being claimed.
|
|
452
|
+
*/
|
|
453
|
+
async webIdsFromSession(request) {
|
|
454
|
+
const cookie = request.headers.get('cookie');
|
|
455
|
+
if (!cookie || !/(?:^|;\s*)css-account=/u.test(cookie))
|
|
456
|
+
return [];
|
|
457
|
+
const base = this.args.frontOrigin.replace(/\/$/u, '');
|
|
458
|
+
const asJson = { cookie, accept: 'application/json' };
|
|
459
|
+
try {
|
|
460
|
+
const index = await fetch(`${base}/.account/`, { headers: asJson });
|
|
461
|
+
if (!index.ok)
|
|
462
|
+
return [];
|
|
463
|
+
const controls = (await index.json()).controls;
|
|
464
|
+
const webIdLink = controls?.account?.webId;
|
|
465
|
+
if (!webIdLink)
|
|
466
|
+
return [];
|
|
467
|
+
const linked = await fetch(webIdLink, { headers: asJson });
|
|
468
|
+
if (!linked.ok)
|
|
469
|
+
return [];
|
|
470
|
+
const links = (await linked.json()).webIdLinks ?? {};
|
|
471
|
+
return Object.keys(links);
|
|
472
|
+
}
|
|
473
|
+
catch {
|
|
474
|
+
return [];
|
|
475
|
+
}
|
|
476
|
+
}
|
|
435
477
|
async optInPod({ podBase, webId }) {
|
|
436
478
|
if (!this.registry)
|
|
437
479
|
return { httpStatus: 501, error: 'this server does not offer runtime opt-in' };
|
|
@@ -474,6 +516,39 @@ class FediPodServerHandler extends community_server_1.HttpHandler {
|
|
|
474
516
|
return { httpStatus: 201, ok: true, handle, host,
|
|
475
517
|
doorSecret: door.secret, doorPath: this.uiPath, status: 'starting' };
|
|
476
518
|
}
|
|
519
|
+
/**
|
|
520
|
+
* What a pod would be as an identity here, without changing anything: its
|
|
521
|
+
* handle, the host its address carries, and whether it runs here already.
|
|
522
|
+
*/
|
|
523
|
+
async describePod({ podBase }) {
|
|
524
|
+
const base = podBase.endsWith('/') ? podBase : `${podBase}/`;
|
|
525
|
+
const handle = deriveHandle(base);
|
|
526
|
+
let host;
|
|
527
|
+
try {
|
|
528
|
+
host = this.validateAgentPod(base).host;
|
|
529
|
+
}
|
|
530
|
+
catch {
|
|
531
|
+
host = new URL(base).host.toLowerCase();
|
|
532
|
+
}
|
|
533
|
+
const running = this.agentHandles.get(handle) === base;
|
|
534
|
+
// The management page's door takes its key once in the address and keeps
|
|
535
|
+
// the browser in with a cookie. Only the owner's own page ever asks for
|
|
536
|
+
// this description, so the key rides on the link for a running account.
|
|
537
|
+
// Read from the pod, where the key always is, rather than from what this
|
|
538
|
+
// process happens to hold: an account still starting has none in memory.
|
|
539
|
+
let key;
|
|
540
|
+
if (running) {
|
|
541
|
+
try {
|
|
542
|
+
key = (await this.doorSecretFor(base)).secret;
|
|
543
|
+
}
|
|
544
|
+
catch {
|
|
545
|
+
key = this.doorSecrets.get(base);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
const door = base.replace(/\/$/u, '') + this.uiPath;
|
|
549
|
+
return { handle, host, address: `@${handle}@${host}`, running,
|
|
550
|
+
manage: key ? `${door}?dk-token=${encodeURIComponent(key)}` : door };
|
|
551
|
+
}
|
|
477
552
|
/** The reverse: stop the identity and let the pod be plain LDP again. */
|
|
478
553
|
async optOutPod({ podBase }) {
|
|
479
554
|
if (!this.registry)
|
|
@@ -530,7 +605,7 @@ class FediPodServerHandler extends community_server_1.HttpHandler {
|
|
|
530
605
|
// The front's own routes win first — its dispatch, WebFinger and API sit at
|
|
531
606
|
// the apex above every suffix pod — so a claim is consulted only where the
|
|
532
607
|
// front does not answer.
|
|
533
|
-
const claimed = (0, claims_1.claims)({ host, pathname }, this.frontHost) ? null : this.resolveClaim(host, pathname);
|
|
608
|
+
const claimed = (0, claims_1.claims)({ host, pathname }, this.frontHost, this.runPath) ? null : this.resolveClaim(host, pathname);
|
|
534
609
|
if (claimed) {
|
|
535
610
|
const identity = this.surfaces.get(claimed.podBase);
|
|
536
611
|
if (!identity) {
|
|
@@ -572,13 +647,21 @@ class FediPodServerHandler extends community_server_1.HttpHandler {
|
|
|
572
647
|
const out = await routeFront(whatwg, {
|
|
573
648
|
host: this.frontHost,
|
|
574
649
|
frontOrigin: this.args.frontOrigin,
|
|
650
|
+
// This server issued the session the reader is already using, so it can
|
|
651
|
+
// read it rather than sending them to an identity provider that has just
|
|
652
|
+
// told them who they are. Same-origin only; front-core decides that.
|
|
653
|
+
webIdsFromSession: (req) => this.webIdsFromSession(req),
|
|
575
654
|
gatewayWebId: this.args.gatewayWebId,
|
|
576
655
|
offersPods: !!this.args.offersPods,
|
|
577
656
|
signupPage: this.args.signupPage || webFile('new-account.html'),
|
|
578
657
|
runPage: this.args.runPage || webFile('run.html'),
|
|
658
|
+
runPath: this.runPath,
|
|
579
659
|
adminPage: this.args.adminPage || webFile('admin.html'),
|
|
580
|
-
// The
|
|
581
|
-
//
|
|
660
|
+
// The notices page renders here too, and says the server keeps none:
|
|
661
|
+
// a notices store is the Netlify front's, not this component's.
|
|
662
|
+
noticesPage: webFile('notices.html'),
|
|
663
|
+
// The opt-in and roster pages load the sign-in library, and the installer
|
|
664
|
+
// command is handed out with it; without these the pages render but
|
|
582
665
|
// cannot be used.
|
|
583
666
|
authBundle: webFile('solid-oidc-client.js'),
|
|
584
667
|
// Each page's own script — inline until 2026-09-09, so that the pages can
|
|
@@ -587,6 +670,7 @@ class FediPodServerHandler extends community_server_1.HttpHandler {
|
|
|
587
670
|
'new-account.js': webFile('new-account.js'),
|
|
588
671
|
'run.js': webFile('run.js'),
|
|
589
672
|
'admin.js': webFile('admin.js'),
|
|
673
|
+
'notices.js': webFile('notices.js'),
|
|
590
674
|
},
|
|
591
675
|
installScript: webFile('install.sh'),
|
|
592
676
|
lookup: (h) => this.dir.lookup(h),
|
|
@@ -628,6 +712,7 @@ class FediPodServerHandler extends community_server_1.HttpHandler {
|
|
|
628
712
|
? {
|
|
629
713
|
optIn: (a) => this.optInPod(a),
|
|
630
714
|
optOut: (a) => this.optOutPod(a),
|
|
715
|
+
describe: (a) => this.describePod(a),
|
|
631
716
|
}
|
|
632
717
|
: undefined,
|
|
633
718
|
});
|
package/dist/handler.jsonld
CHANGED
|
@@ -93,7 +93,20 @@
|
|
|
93
93
|
}
|
|
94
94
|
]
|
|
95
95
|
},
|
|
96
|
-
"comment": "The run-your-identity page HTML
|
|
96
|
+
"comment": "The run-your-identity page HTML."
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"@id": "fps:dist/handler.jsonld#FediPodServerHandler_args_runPath",
|
|
100
|
+
"range": {
|
|
101
|
+
"@type": "ParameterRangeUnion",
|
|
102
|
+
"parameterRangeElements": [
|
|
103
|
+
"xsd:string",
|
|
104
|
+
{
|
|
105
|
+
"@type": "ParameterRangeUndefined"
|
|
106
|
+
}
|
|
107
|
+
]
|
|
108
|
+
},
|
|
109
|
+
"comment": "Where that page answers. Defaults to `/.fediverse-account`; the path it takes is one the pod server no longer serves, so an operator may name it."
|
|
97
110
|
},
|
|
98
111
|
{
|
|
99
112
|
"@id": "fps:dist/handler.jsonld#FediPodServerHandler_args_adminPage",
|
|
@@ -240,6 +253,10 @@
|
|
|
240
253
|
"memberFieldName": "dir",
|
|
241
254
|
"range": "fps:dist/directory.jsonld#Directory"
|
|
242
255
|
},
|
|
256
|
+
{
|
|
257
|
+
"@id": "fps:dist/handler.jsonld#FediPodServerHandler__member_runPath",
|
|
258
|
+
"memberFieldName": "runPath"
|
|
259
|
+
},
|
|
243
260
|
{
|
|
244
261
|
"@id": "fps:dist/handler.jsonld#FediPodServerHandler__member_podPut",
|
|
245
262
|
"memberFieldName": "podPut"
|
|
@@ -344,10 +361,18 @@
|
|
|
344
361
|
"@id": "fps:dist/handler.jsonld#FediPodServerHandler__member_matchIdentity",
|
|
345
362
|
"memberFieldName": "matchIdentity"
|
|
346
363
|
},
|
|
364
|
+
{
|
|
365
|
+
"@id": "fps:dist/handler.jsonld#FediPodServerHandler__member_webIdsFromSession",
|
|
366
|
+
"memberFieldName": "webIdsFromSession"
|
|
367
|
+
},
|
|
347
368
|
{
|
|
348
369
|
"@id": "fps:dist/handler.jsonld#FediPodServerHandler__member_optInPod",
|
|
349
370
|
"memberFieldName": "optInPod"
|
|
350
371
|
},
|
|
372
|
+
{
|
|
373
|
+
"@id": "fps:dist/handler.jsonld#FediPodServerHandler__member_describePod",
|
|
374
|
+
"memberFieldName": "describePod"
|
|
375
|
+
},
|
|
351
376
|
{
|
|
352
377
|
"@id": "fps:dist/handler.jsonld#FediPodServerHandler__member_optOutPod",
|
|
353
378
|
"memberFieldName": "optOutPod"
|
|
@@ -413,6 +438,12 @@
|
|
|
413
438
|
"@id": "fps:dist/handler.jsonld#FediPodServerHandler_args_runPage"
|
|
414
439
|
}
|
|
415
440
|
},
|
|
441
|
+
{
|
|
442
|
+
"keyRaw": "runPath",
|
|
443
|
+
"value": {
|
|
444
|
+
"@id": "fps:dist/handler.jsonld#FediPodServerHandler_args_runPath"
|
|
445
|
+
}
|
|
446
|
+
},
|
|
416
447
|
{
|
|
417
448
|
"keyRaw": "adminPage",
|
|
418
449
|
"value": {
|
|
@@ -515,6 +546,10 @@
|
|
|
515
546
|
"@id": "fps:dist/handler.jsonld#FediPodServerArgs__member_runPage",
|
|
516
547
|
"memberFieldName": "runPage"
|
|
517
548
|
},
|
|
549
|
+
{
|
|
550
|
+
"@id": "fps:dist/handler.jsonld#FediPodServerArgs__member_runPath",
|
|
551
|
+
"memberFieldName": "runPath"
|
|
552
|
+
},
|
|
518
553
|
{
|
|
519
554
|
"@id": "fps:dist/handler.jsonld#FediPodServerArgs__member_adminPage",
|
|
520
555
|
"memberFieldName": "adminPage"
|
package/lib/client/c2s.mjs
CHANGED
|
@@ -5,8 +5,12 @@
|
|
|
5
5
|
// SAME helper the facade and admin surfaces use — this module re-implements
|
|
6
6
|
// no persistence and no delivery, so one write path stays one.
|
|
7
7
|
//
|
|
8
|
-
// GETs on the actor and outbox are redirects
|
|
9
|
-
//
|
|
8
|
+
// GETs on the actor and outbox are redirects to the pod's documents wherever
|
|
9
|
+
// those are reachable, which is the Server. Standalone they are not: the actor
|
|
10
|
+
// published there names no address a local client could write to, and on a
|
|
11
|
+
// fronted account the published outbox address is the Gateway's write door,
|
|
12
|
+
// which does not answer a read. So on that build both are served here, to the
|
|
13
|
+
// client already talking to this agent.
|
|
10
14
|
//
|
|
11
15
|
// `dispatch` takes an activity and answers with { status, body, headers }; it
|
|
12
16
|
// touches no request or response. The HTTP handler below is one caller. The
|
|
@@ -32,6 +36,9 @@ const MAX_BODY = 512 * 1024; // same ceiling the inbox drain enforces
|
|
|
32
36
|
// the pod an unbounded read; the log says when that happened.
|
|
33
37
|
const MAX_INBOX_PAGE = 500;
|
|
34
38
|
|
|
39
|
+
// How many recorded activities one page of the locally-served outbox carries.
|
|
40
|
+
const MAX_OUTBOX_PAGE = 50;
|
|
41
|
+
|
|
35
42
|
// §6 names activities; anything else with a type is an object to wrap.
|
|
36
43
|
const ACTIVITY_TYPES = new Set([
|
|
37
44
|
'Create', 'Update', 'Delete', 'Follow', 'Like', 'Announce', 'Undo',
|
|
@@ -56,10 +63,15 @@ function readBody(req) {
|
|
|
56
63
|
}
|
|
57
64
|
|
|
58
65
|
export class C2S {
|
|
59
|
-
constructor({ agent, log = console.log, auth }) {
|
|
66
|
+
constructor({ agent, log = console.log, auth, scheme = null, mount = '' }) {
|
|
60
67
|
this.agent = agent;
|
|
61
68
|
this.log = log;
|
|
62
69
|
this.auth = auth;
|
|
70
|
+
// What this agent calls itself, for the actor it serves a local client.
|
|
71
|
+
// The same two values oidc-auth rebuilds the DPoP `htu` from, so the two
|
|
72
|
+
// surfaces agree on the address a client just used.
|
|
73
|
+
this.scheme = scheme;
|
|
74
|
+
this.mount = mount;
|
|
63
75
|
}
|
|
64
76
|
|
|
65
77
|
get store() { return this.agent.store; }
|
|
@@ -157,6 +169,85 @@ export class C2S {
|
|
|
157
169
|
});
|
|
158
170
|
}
|
|
159
171
|
|
|
172
|
+
// The actor a client-to-server client on this machine reads. The same
|
|
173
|
+
// document the pod holds, but for the fields naming where a write goes —
|
|
174
|
+
// the outbox and the two OAuth endpoints — and for `id`, which names this
|
|
175
|
+
// address so the client files its credential here and comes back. The pod's
|
|
176
|
+
// id stays on as an alias; the two are one identity.
|
|
177
|
+
// What this agent calls itself, to the client that just asked. The Host
|
|
178
|
+
// passed the Authorities firewall before any route ran, so whichever alias
|
|
179
|
+
// the client used is one this agent answers on. The scheme is read off the
|
|
180
|
+
// connection unless the caller named one: only the Server passes a scheme,
|
|
181
|
+
// and standalone this listener is its own TLS.
|
|
182
|
+
localOrigin(req) {
|
|
183
|
+
const host = req.headers.host;
|
|
184
|
+
if (!host) return null;
|
|
185
|
+
const scheme = this.scheme
|
|
186
|
+
|| (req.socket?.encrypted || req.headers['x-forwarded-proto'] === 'https' ? 'https' : 'http');
|
|
187
|
+
return `${scheme}://${host}${this.mount}/`;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* The owner's own outbox, from what this agent recorded. The pod holds the
|
|
192
|
+
* published copy and it stays the canonical one; this is served only where
|
|
193
|
+
* that copy cannot be read by the client asking.
|
|
194
|
+
*/
|
|
195
|
+
sendLocalOutbox(res, url, origin) {
|
|
196
|
+
const id = `${origin}ap/outbox`;
|
|
197
|
+
const outbox = this.store.read('outbox.json', []); // newest first
|
|
198
|
+
const page = url?.searchParams?.get('page') || null;
|
|
199
|
+
const ct = { 'content-type': 'application/activity+json; charset=utf-8' };
|
|
200
|
+
if (!page) {
|
|
201
|
+
return this.send(res, 200, {
|
|
202
|
+
'@context': wire.AS_CTX, id, type: 'OrderedCollection', totalItems: outbox.length,
|
|
203
|
+
...(outbox.length ? { first: `${id}?page=1` } : { orderedItems: [] }),
|
|
204
|
+
}, ct);
|
|
205
|
+
}
|
|
206
|
+
const n = Number(page);
|
|
207
|
+
if (!Number.isInteger(n) || n < 1) {
|
|
208
|
+
return this.send(res, 400, { error: 'page is a whole number from 1' });
|
|
209
|
+
}
|
|
210
|
+
const start = (n - 1) * MAX_OUTBOX_PAGE;
|
|
211
|
+
const items = outbox.slice(start, start + MAX_OUTBOX_PAGE);
|
|
212
|
+
return this.send(res, 200, {
|
|
213
|
+
'@context': wire.AS_CTX,
|
|
214
|
+
id: `${id}?page=${n}`,
|
|
215
|
+
type: 'OrderedCollectionPage',
|
|
216
|
+
partOf: id,
|
|
217
|
+
...(start + items.length < outbox.length ? { next: `${id}?page=${n + 1}` } : {}),
|
|
218
|
+
// A post is recorded by its object id and published as the Create beside
|
|
219
|
+
// it — the same mapping the pod's copy uses, from the same helper.
|
|
220
|
+
orderedItems: items.map(wire.outboxWireItem),
|
|
221
|
+
}, ct);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
sendLocalActor(req, res, origin) {
|
|
225
|
+
const pub = this.agent.publisher;
|
|
226
|
+
const cfg = pub.config || {};
|
|
227
|
+
const gw = cfg.gateway;
|
|
228
|
+
const gwActive = gw && gw.url && gw.mode && gw.mode !== 'off';
|
|
229
|
+
const doc = pub.actorDocFor({
|
|
230
|
+
inbox: gwActive ? gw.url : (cfg.inboxUrl || null),
|
|
231
|
+
clientOrigin: origin,
|
|
232
|
+
});
|
|
233
|
+
// A client files its credential under the id it reads here and comes back
|
|
234
|
+
// to that address for everything after, so this document has to name
|
|
235
|
+
// itself. The pod's id stays on as an alias rather than being dropped —
|
|
236
|
+
// the two are one identity. Nothing published ever carries this id: every
|
|
237
|
+
// activity the dispatcher builds names the pod's actor.
|
|
238
|
+
const canonical = doc.id;
|
|
239
|
+
doc.id = `${origin}ap/actor`;
|
|
240
|
+
doc.alsoKnownAs = [...new Set([...(doc.alsoKnownAs || []), canonical])];
|
|
241
|
+
const body = JSON.stringify(doc);
|
|
242
|
+
res.writeHead(200, {
|
|
243
|
+
'content-type': 'application/activity+json; charset=utf-8',
|
|
244
|
+
'cache-control': 'no-store',
|
|
245
|
+
'content-length': Buffer.byteLength(body),
|
|
246
|
+
});
|
|
247
|
+
res.end(req.method === 'HEAD' ? undefined : body);
|
|
248
|
+
return true;
|
|
249
|
+
}
|
|
250
|
+
|
|
160
251
|
async handle(req, res, pathname, url) { // eslint-disable-line no-unused-vars
|
|
161
252
|
if (pathname !== '/ap/outbox' && pathname !== '/ap/actor' && pathname !== '/ap/inbox') return false;
|
|
162
253
|
if (req.method === 'OPTIONS') {
|
|
@@ -179,6 +270,12 @@ export class C2S {
|
|
|
179
270
|
return this.sendInbox(res, url);
|
|
180
271
|
}
|
|
181
272
|
if (req.method === 'GET' || req.method === 'HEAD') {
|
|
273
|
+
// Where the client surface answers on an address a stranger can reach,
|
|
274
|
+
// the pod's copies already name it and are the documents. Standalone
|
|
275
|
+
// they are not reachable by the client asking, so it is answered here.
|
|
276
|
+
const local = this.agent.publisher?.clientOrigin ? null : this.localOrigin(req);
|
|
277
|
+
if (local && pathname === '/ap/actor') return this.sendLocalActor(req, res, local);
|
|
278
|
+
if (local && pathname === '/ap/outbox') return this.sendLocalOutbox(res, url, local);
|
|
182
279
|
// The pod's copy is the document; send the reader there.
|
|
183
280
|
const target = pathname === '/ap/actor' ? this.urls.actor : this.urls.outbox;
|
|
184
281
|
res.writeHead(303, { location: target, 'cache-control': 'no-store' });
|
|
@@ -6,8 +6,7 @@
|
|
|
6
6
|
import crypto from 'node:crypto';
|
|
7
7
|
import * as podMedia from '../../pod/media.mjs';
|
|
8
8
|
import * as social from '../../core/social.mjs';
|
|
9
|
-
import { readBody } from './body.mjs';
|
|
10
|
-
import { readMultipart } from './media.mjs';
|
|
9
|
+
import { readBody, readMultipart } from './body.mjs';
|
|
11
10
|
|
|
12
11
|
export async function handle(api, ctx) {
|
|
13
12
|
const { req, res, pathname, url, send } = ctx; // eslint-disable-line no-unused-vars
|
|
@@ -226,6 +225,39 @@ export async function handle(api, ctx) {
|
|
|
226
225
|
|
|
227
226
|
if (/^\/api\/v1\/accounts\/[a-f0-9]+\/featured_tags$/.test(pathname)) return send(200, []);
|
|
228
227
|
|
|
228
|
+
// The instance's own directory is its one account; there is nothing to
|
|
229
|
+
// suggest and nothing trending, so those screens are empty rather than 404.
|
|
230
|
+
if (pathname === '/api/v1/directory') return send(200, api.urls?.actor ? [api.account(api.urls.actor)] : []);
|
|
231
|
+
if (pathname === '/api/v1/suggestions') return send(200, []);
|
|
232
|
+
|
|
233
|
+
// The lists this account is on — what "add to list" from a profile reads.
|
|
234
|
+
const mAccLists = /^\/api\/v1\/accounts\/([a-f0-9]+)\/lists$/.exec(pathname);
|
|
235
|
+
if (mAccLists && req.method === 'GET') {
|
|
236
|
+
const actorUrl = api.store.urlFor(mAccLists[1]);
|
|
237
|
+
if (!actorUrl) return send(404, { error: 'Record not found' });
|
|
238
|
+
return send(200, api.store.getLists().filter(l => (l.members || []).includes(actorUrl))
|
|
239
|
+
.map(l => ({ id: l.id, title: l.title, replies_policy: l.repliesPolicy || 'list', exclusive: false })));
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// The people muted and blocked, as accounts; the domains blocked, as names.
|
|
243
|
+
if (pathname === '/api/v1/mutes' && req.method === 'GET') {
|
|
244
|
+
return send(200, (api.store.getMuted().actors || []).map(a => api.account(a)));
|
|
245
|
+
}
|
|
246
|
+
if (pathname === '/api/v1/blocks' && req.method === 'GET') {
|
|
247
|
+
return send(200, (api.store.getBlocklist().actors || []).map(a => api.account(a)));
|
|
248
|
+
}
|
|
249
|
+
if (pathname === '/api/v1/domain_blocks') {
|
|
250
|
+
const b = api.store.getBlocklist();
|
|
251
|
+
if (req.method === 'GET') return send(200, [...b.domains]);
|
|
252
|
+
const body = await readBody(req);
|
|
253
|
+
const domain = String(body.domain || url.searchParams.get('domain') || '').trim().toLowerCase();
|
|
254
|
+
if (!/^[a-z0-9.-]+\.[a-z]{2,}$/u.test(domain)) return send(422, { error: 'a domain is required' });
|
|
255
|
+
if (req.method === 'POST' && !b.domains.includes(domain)) b.domains.push(domain);
|
|
256
|
+
if (req.method === 'DELETE') b.domains = b.domains.filter(d => d !== domain);
|
|
257
|
+
api.store.setBlocklist(b);
|
|
258
|
+
return send(200, {});
|
|
259
|
+
}
|
|
260
|
+
|
|
229
261
|
// The counts these back are rendered from the same two arrays (see
|
|
230
262
|
// `account`), so a client that shows a number here can always open it.
|
|
231
263
|
const mAccList = /^\/api\/v1\/accounts\/([a-f0-9]+)\/(following|followers)$/.exec(pathname);
|
|
@@ -259,9 +291,17 @@ export async function handle(api, ctx) {
|
|
|
259
291
|
if (mAccStatuses) {
|
|
260
292
|
const actorUrl = api.store.urlFor(mAccStatuses[1]);
|
|
261
293
|
const all = api.store.getStatuses();
|
|
262
|
-
const
|
|
294
|
+
const q = url.searchParams;
|
|
295
|
+
const is = (name) => q.get(name) === 'true' || q.get(name) === '1';
|
|
296
|
+
const pinnedOnly = is('pinned');
|
|
297
|
+
const tagged = String(q.get('tagged') || '').replace(/^#/, '').toLowerCase();
|
|
298
|
+
const isBoost = (s) => !!s.via && s.via !== s.actor;
|
|
263
299
|
const { items, headers } = api.page(
|
|
264
|
-
all.filter(s => s.actor === actorUrl && (!pinnedOnly || s.pinned))
|
|
300
|
+
all.filter(s => s.actor === actorUrl && (!pinnedOnly || s.pinned))
|
|
301
|
+
.filter(s => !is('exclude_replies') || !s.inReplyTo)
|
|
302
|
+
.filter(s => !is('exclude_reblogs') || !isBoost(s))
|
|
303
|
+
.filter(s => !is('only_media') || (s.attachments || []).length > 0)
|
|
304
|
+
.filter(s => !tagged || (s.tags || []).some(t => String(t.name).toLowerCase() === tagged)), url);
|
|
265
305
|
return send(200, items.map(s => api.statusOrBoost(s, { all })), headers);
|
|
266
306
|
}
|
|
267
307
|
|