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/web/admin/gateway.js
CHANGED
|
@@ -18,12 +18,31 @@ async function refreshGateway() {
|
|
|
18
18
|
GATEWAY_WORD.textContent = frontName ? `${host} — publishing as @${frontName}@${host}` : host;
|
|
19
19
|
GW_OPEN_ATTACH.hidden = true;
|
|
20
20
|
GW_OPEN_DETACH.hidden = false;
|
|
21
|
+
// The account's standing at the gateway — paused, closed. The browser
|
|
22
|
+
// build's agent reports it; a DeviceAgent's does not, and the pause and
|
|
23
|
+
// close controls stay hidden.
|
|
24
|
+
const st = g.standing;
|
|
25
|
+
const known = !!st && (st.status === 200 || st.status === 410);
|
|
26
|
+
$('gateway-pause').hidden = !known || !!st.closed || !!st.paused;
|
|
27
|
+
$('gateway-resume').hidden = !known || !!st.closed || !st.paused;
|
|
28
|
+
$('gateway-close').hidden = !known || !!st.closed;
|
|
29
|
+
if (known && st.closed) GATEWAY_WORD.textContent += ' — this address is closed';
|
|
30
|
+
else if (known && st.paused) GATEWAY_WORD.textContent += st.pausedBy === 'owner' ? ' — paused by you' : ' — paused';
|
|
21
31
|
} else {
|
|
22
32
|
GATEWAY_WORD.textContent = '';
|
|
23
33
|
GW_OPEN_ATTACH.hidden = false;
|
|
24
34
|
GW_OPEN_DETACH.hidden = true;
|
|
35
|
+
for (const id of ['gateway-pause', 'gateway-resume', 'gateway-close']) $(id).hidden = true;
|
|
25
36
|
}
|
|
26
37
|
}
|
|
38
|
+
const setPausedAtGateway = async (paused) => {
|
|
39
|
+
const r = await write('/gateway/pause', { paused },
|
|
40
|
+
paused ? 'paused — posts sent to you are not kept until you resume; follows still arrive'
|
|
41
|
+
: 'resumed — posts sent to you are kept again');
|
|
42
|
+
if (r) refreshGateway();
|
|
43
|
+
};
|
|
44
|
+
$('gateway-pause').addEventListener('click', () => setPausedAtGateway(true));
|
|
45
|
+
$('gateway-resume').addEventListener('click', () => setPausedAtGateway(false));
|
|
27
46
|
const gwShape = () => document.querySelector('input[name=gwShape]:checked')?.value || 'pod';
|
|
28
47
|
function gwPreviews() {
|
|
29
48
|
$('gw-pod-preview').textContent = config?.address || `@${config?.handle || 'you'}@your.pod`;
|
|
@@ -43,6 +62,9 @@ GW_OPEN_ATTACH.addEventListener('click', () => {
|
|
|
43
62
|
});
|
|
44
63
|
GW_OPEN_DETACH.addEventListener('click', () => {
|
|
45
64
|
$('gw-detach-fronted').hidden = !gwState?.frontActor;
|
|
65
|
+
// The move-in hint exists only in the browser build (see index.html).
|
|
66
|
+
const moveHint = $('gw-move-hint-browser');
|
|
67
|
+
if (moveHint) moveHint.hidden = !gwState?.frontActor;
|
|
46
68
|
$('gw-detach-plain').hidden = !!gwState?.frontActor;
|
|
47
69
|
solWindow.show('gateway-detach-form', 'Detach from gateway');
|
|
48
70
|
});
|
package/web/admin/index.html
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
6
|
<title>FediPod — record</title>
|
|
7
|
+
<link rel="icon" href="/icons/local.svg" type="image/svg+xml">
|
|
7
8
|
<link rel="stylesheet" href="tokens.css">
|
|
8
9
|
<style>
|
|
9
10
|
/* Colours are the shared tokens (tokens.css, the front's palette), so the
|
|
@@ -15,7 +16,7 @@
|
|
|
15
16
|
/* The page itself never scrolls: the body is exactly the window; the content
|
|
16
17
|
column beside the rail is the scroll container. */
|
|
17
18
|
html { height: 100%; }
|
|
18
|
-
body { font: 1rem/1.5
|
|
19
|
+
body { font: 1rem/1.5 Arial, Helvetica, sans-serif; margin: 0; height: 100dvh;
|
|
19
20
|
box-sizing: border-box; display: flex; flex-direction: column; overflow: hidden; }
|
|
20
21
|
/* The bar spans the window; the rail and the content column share one centred
|
|
21
22
|
column underneath it — wide enough that the content keeps 960 of its own
|
|
@@ -164,7 +165,7 @@ pre { overflow-x: auto; background: #0001; padding: .6rem; border-radius: .3rem;
|
|
|
164
165
|
fieldset { border-color: #555; } .rows li { border-color: #444; }
|
|
165
166
|
}
|
|
166
167
|
</style>
|
|
167
|
-
<link rel="stylesheet" href="bar.css?v=
|
|
168
|
+
<link rel="stylesheet" href="bar.css?v=4">
|
|
168
169
|
<link rel="stylesheet" href="window.css">
|
|
169
170
|
</head>
|
|
170
171
|
<body>
|
|
@@ -174,8 +175,16 @@ pre { overflow-x: auto; background: #0001; padding: .6rem; border-radius: .3rem;
|
|
|
174
175
|
<h1 class="name">FediPod</h1>
|
|
175
176
|
<select id="actor-pick" aria-label="Local actors"
|
|
176
177
|
title=" Every actor on this machine; choosing one goes to its record"></select>
|
|
177
|
-
|
|
178
|
-
|
|
178
|
+
<!-- The account group, styled like the client group beside it: one label,
|
|
179
|
+
then the places to go. The browser build adds "sign out" to it. -->
|
|
180
|
+
<span id="account-pick"><span id="account-now">account:</span>
|
|
181
|
+
<a id="bar-fediverse" href="client/" title=" Open the client and read your timeline">visit</a>
|
|
182
|
+
<a id="bar-manage" href="./" title=" The record: what this actor is, and what you can do to it" aria-current="page">manage</a>
|
|
183
|
+
</span>
|
|
184
|
+
<!-- Notices from whoever runs this site. Shown only when the site has any
|
|
185
|
+
(notices-bar.js asks /api/notices); the count is how many are new here. -->
|
|
186
|
+
<button type="button" id="bar-notices" hidden aria-haspopup="dialog" aria-controls="notices-list"
|
|
187
|
+
title=" Notices from the operator of this site">🔔<span id="bar-notices-count" aria-live="polite"></span></button>
|
|
179
188
|
</nav>
|
|
180
189
|
<main id="page">
|
|
181
190
|
|
|
@@ -320,6 +329,14 @@ pre { overflow-x: auto; background: #0001; padding: .6rem; border-radius: .3rem;
|
|
|
320
329
|
title=" Attach this account to a mail-filtering gateway">Attach to a gateway</button>
|
|
321
330
|
<button type="button" id="gateway-open-detach" class="inline danger" hidden
|
|
322
331
|
title=" Go back to your pod's own inbox">Detach from gateway</button>
|
|
332
|
+
<!-- Shown only when the agent reports the account's standing at the gateway
|
|
333
|
+
(the browser build does; a DeviceAgent's admin does not). -->
|
|
334
|
+
<button type="button" id="gateway-pause" class="inline" hidden
|
|
335
|
+
title=" Stop keeping the posts sent to you until you resume; follows still arrive">Pause my account</button>
|
|
336
|
+
<button type="button" id="gateway-resume" class="inline" hidden
|
|
337
|
+
title=" Keep the posts sent to you again">Resume my account</button>
|
|
338
|
+
<button type="button" id="gateway-close" class="inline danger" hidden data-confirm="close-address"
|
|
339
|
+
title=" Close your address at this gateway for good; nothing on your pod is touched">Close this address</button>
|
|
323
340
|
</span>
|
|
324
341
|
|
|
325
342
|
<section id="pane-group" hidden>
|
|
@@ -485,6 +502,12 @@ pre { overflow-x: auto; background: #0001; padding: .6rem; border-radius: .3rem;
|
|
|
485
502
|
<p class="warn" id="gw-detach-fronted" hidden>This identity publishes under the gateway's
|
|
486
503
|
name. Detaching moves every published id back to the pod — a rename other servers
|
|
487
504
|
see, not just a mail change.</p>
|
|
505
|
+
<!-- Browser build only: stage-site.mjs renames this id to gw-move-hint-browser,
|
|
506
|
+
and gateway.js shows it for an address that lives at a gateway. The
|
|
507
|
+
DeviceAgent has no move-in yet, so here it stays hidden under this id. -->
|
|
508
|
+
<p class="hint" id="gw-move-hint" hidden>To move this address to another gateway, open that
|
|
509
|
+
gateway, choose “create an account”, and sign in with this pod. Your posts, followers and
|
|
510
|
+
key come with you, and your followers are told the new address.</p>
|
|
488
511
|
<p>
|
|
489
512
|
<button id="gw-detach" type="button" class="primary"
|
|
490
513
|
title=" Forget the gateway and republish the actor with the pod inbox">Detach</button>
|
|
@@ -494,15 +517,6 @@ pre { overflow-x: auto; background: #0001; padding: .6rem; border-radius: .3rem;
|
|
|
494
517
|
<div id="warn-rotate-key" class="warn" hidden>
|
|
495
518
|
<p>A new keypair replaces the one in this home and the actor is republished so other
|
|
496
519
|
servers learn it. Any other device still holding the old key stops being able to sign.</p>
|
|
497
|
-
<!-- Browser build only: there the key on the pod is locked under the account
|
|
498
|
-
password, so a new one has to be locked too. The Node agent keeps its key
|
|
499
|
-
on disk and needs nothing here — stage-site.mjs reveals this row. -->
|
|
500
|
-
<div id="rotate-pw-row" hidden>
|
|
501
|
-
<label for="rotate-password">Your account password</label>
|
|
502
|
-
<input type="password" id="rotate-password" autocomplete="current-password">
|
|
503
|
-
<p class="hint">Your signing key is kept on your pod locked under this, so the
|
|
504
|
-
replacement has to be locked under it too. It is not sent anywhere else.</p>
|
|
505
|
-
</div>
|
|
506
520
|
</div>
|
|
507
521
|
<div id="warn-retire" class="warn" hidden>
|
|
508
522
|
<p><strong>This cannot be undone.</strong> A Delete goes to every follower's inbox telling
|
|
@@ -518,6 +532,14 @@ pre { overflow-x: auto; background: #0001; padding: .6rem; border-radius: .3rem;
|
|
|
518
532
|
<label for="confirm-handle">Type the handle to confirm</label>
|
|
519
533
|
<input type="text" id="confirm-handle" autocomplete="off">
|
|
520
534
|
</div>
|
|
535
|
+
<div id="warn-close-address" class="warn" hidden>
|
|
536
|
+
<p><strong>This cannot be undone.</strong> Your address at the gateway is closed for good:
|
|
537
|
+
mail to it is turned away, other servers drop the account the next time they look, and
|
|
538
|
+
nobody can take the name. Nothing on your pod is touched — your posts, key and data stay
|
|
539
|
+
where they are.</p>
|
|
540
|
+
<label for="confirm-handle-close">Type the handle to confirm</label>
|
|
541
|
+
<input type="text" id="confirm-handle-close" autocomplete="off">
|
|
542
|
+
</div>
|
|
521
543
|
<!-- "Move private data" (POST /state-move) had a panel here and no way to
|
|
522
544
|
open it: nothing in this page has ever carried data-confirm="move-state",
|
|
523
545
|
so the panel was only ever hidden. Removed 2026-09-09 rather than left as
|
|
@@ -558,5 +580,24 @@ pre { overflow-x: auto; background: #0001; padding: .6rem; border-radius: .3rem;
|
|
|
558
580
|
<script src="group.js"></script>
|
|
559
581
|
<script src="upkeep.js"></script>
|
|
560
582
|
<script src="gateway.js"></script>
|
|
583
|
+
|
|
584
|
+
<!-- The notices: the list, and one notice open. Declared here and filled by
|
|
585
|
+
notices-bar.js from /api/notices; nothing in them is markup from a notice. -->
|
|
586
|
+
<dialog id="notices-list" aria-labelledby="notices-title">
|
|
587
|
+
<section>
|
|
588
|
+
<h2 id="notices-title">Notices</h2>
|
|
589
|
+
<ul id="notices-items"></ul>
|
|
590
|
+
<p class="row"><button type="button" id="notices-close" class="primary" title=" Close the list of notices">Close</button></p>
|
|
591
|
+
</section>
|
|
592
|
+
</dialog>
|
|
593
|
+
<dialog id="notice-view" aria-labelledby="notice-view-title">
|
|
594
|
+
<section>
|
|
595
|
+
<h2 id="notice-view-title"></h2>
|
|
596
|
+
<p class="when" id="notice-view-when"></p>
|
|
597
|
+
<div id="notice-view-body"></div>
|
|
598
|
+
<p class="row"><button type="button" id="notice-view-close" class="primary" title=" Back to the list of notices">Close</button></p>
|
|
599
|
+
</section>
|
|
600
|
+
</dialog>
|
|
601
|
+
<script src="notices-bar.js"></script>
|
|
561
602
|
</body>
|
|
562
603
|
</html>
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// notices-bar.js — the notices bell in the bar, on every page of ours.
|
|
2
|
+
//
|
|
3
|
+
// Whoever runs the site writes notices (the /notices page, admin only); every
|
|
4
|
+
// signed-in account sees the bell. This asks /api/notices once per page load,
|
|
5
|
+
// hides the bell where the site has none to offer (a DeviceAgent's own pages
|
|
6
|
+
// answer no such thing), lists the titles, and opens one notice at a time.
|
|
7
|
+
// Which notices this browser has already opened is this browser's business
|
|
8
|
+
// alone, so it lives in localStorage and nowhere else.
|
|
9
|
+
//
|
|
10
|
+
// Every element here is declared in the page's markup; this fills them.
|
|
11
|
+
(() => {
|
|
12
|
+
const bell = document.getElementById('bar-notices');
|
|
13
|
+
const list = document.getElementById('notices-list');
|
|
14
|
+
const view = document.getElementById('notice-view');
|
|
15
|
+
if (!bell || !list || !view) return;
|
|
16
|
+
const $ = (id) => document.getElementById(id);
|
|
17
|
+
const SEEN = 'fedipod-notices-seen';
|
|
18
|
+
const readSeen = () => { try { return JSON.parse(localStorage.getItem(SEEN) || '[]'); } catch { return []; } };
|
|
19
|
+
const writeSeen = (ids) => { try { localStorage.setItem(SEEN, JSON.stringify(ids.slice(-200))); } catch { /* keeps no site data */ } };
|
|
20
|
+
|
|
21
|
+
const when = (iso) => { try { return new Date(iso).toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' }); } catch { return ''; } };
|
|
22
|
+
|
|
23
|
+
// A notice's body is plain text: paragraphs at blank lines, and a bare
|
|
24
|
+
// https link becomes a link. Nothing in it is ever read as markup.
|
|
25
|
+
const renderBody = (el, text) => {
|
|
26
|
+
el.textContent = '';
|
|
27
|
+
for (const para of String(text || '').split(/\n\s*\n/u)) {
|
|
28
|
+
const p = document.createElement('p');
|
|
29
|
+
for (const part of para.split(/(https?:\/\/[^\s<>"']+)/u)) {
|
|
30
|
+
if (/^https?:\/\//u.test(part)) {
|
|
31
|
+
const a = document.createElement('a');
|
|
32
|
+
a.href = part; a.textContent = part; a.target = '_blank'; a.rel = 'noopener';
|
|
33
|
+
p.append(a);
|
|
34
|
+
} else p.append(part);
|
|
35
|
+
}
|
|
36
|
+
el.append(p);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
let notices = [];
|
|
41
|
+
const render = () => {
|
|
42
|
+
const seen = new Set(readSeen());
|
|
43
|
+
const fresh = notices.filter((n) => !seen.has(n.id)).length;
|
|
44
|
+
$('bar-notices-count').textContent = fresh ? String(fresh) : '';
|
|
45
|
+
bell.dataset.new = String(fresh);
|
|
46
|
+
bell.setAttribute('aria-label', fresh ? `Notices, ${fresh} new` : 'Notices');
|
|
47
|
+
const ul = $('notices-items');
|
|
48
|
+
ul.textContent = '';
|
|
49
|
+
if (!notices.length) {
|
|
50
|
+
const p = document.createElement('p'); p.className = 'none'; p.textContent = 'No notices.';
|
|
51
|
+
ul.append(p);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
for (const n of notices) {
|
|
55
|
+
const li = document.createElement('li');
|
|
56
|
+
if (!seen.has(n.id)) li.classList.add('new');
|
|
57
|
+
const b = document.createElement('button');
|
|
58
|
+
b.type = 'button'; b.className = 'notice-open'; b.textContent = n.title;
|
|
59
|
+
b.addEventListener('click', () => open(n));
|
|
60
|
+
const w = document.createElement('span'); w.className = 'when'; w.textContent = when(n.at);
|
|
61
|
+
li.append(b, w);
|
|
62
|
+
ul.append(li);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const open = (n) => {
|
|
67
|
+
$('notice-view-title').textContent = n.title;
|
|
68
|
+
$('notice-view-when').textContent = when(n.at);
|
|
69
|
+
renderBody($('notice-view-body'), n.body);
|
|
70
|
+
const seen = readSeen();
|
|
71
|
+
if (!seen.includes(n.id)) { seen.push(n.id); writeSeen(seen); }
|
|
72
|
+
render();
|
|
73
|
+
list.close();
|
|
74
|
+
view.showModal();
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
bell.addEventListener('click', () => { render(); list.showModal(); });
|
|
78
|
+
$('notices-close').addEventListener('click', () => list.close());
|
|
79
|
+
$('notice-view-close').addEventListener('click', () => { view.close(); list.showModal(); });
|
|
80
|
+
|
|
81
|
+
fetch(location.origin + '/api/notices', { headers: { accept: 'application/json' } })
|
|
82
|
+
.then((r) => (r.ok ? r.json() : null))
|
|
83
|
+
.then((j) => {
|
|
84
|
+
if (!j || !Array.isArray(j.notices)) return;
|
|
85
|
+
notices = j.notices;
|
|
86
|
+
render();
|
|
87
|
+
bell.hidden = false;
|
|
88
|
+
})
|
|
89
|
+
.catch(() => { /* no site notices here; the bell stays hidden */ });
|
|
90
|
+
})();
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// oauth-signin.mjs — a client asked this account to sign its owner in. The
|
|
2
|
+
// owner signs in at their own pod, with the fediverse-account library, and the
|
|
3
|
+
// page proves that sign-in to the account with one signed request; the account
|
|
4
|
+
// then hands the client its code. No password of ours anywhere.
|
|
5
|
+
import { fediLogin } from './fedi-login.mjs';
|
|
6
|
+
|
|
7
|
+
const $ = (id) => document.getElementById(id);
|
|
8
|
+
const say = (text, error = false) => { const s = $('signin-status'); s.textContent = text; s.setAttribute('role', error ? 'alert' : 'status'); };
|
|
9
|
+
const webId = $('webid').href;
|
|
10
|
+
const here = location.origin + location.pathname;
|
|
11
|
+
const login = fediLogin({ dbName: 'fedipod-signin', clientName: 'FediPod sign-in', redirectUri: here });
|
|
12
|
+
|
|
13
|
+
// Back from the pod: this finishes the sign-in and returns to the client's
|
|
14
|
+
// request, which carries the client's own parameters.
|
|
15
|
+
try { await login.resume(); } catch (e) { say(e.message, true); }
|
|
16
|
+
|
|
17
|
+
async function prove() {
|
|
18
|
+
const s = await login.getSession();
|
|
19
|
+
if (!s) return false;
|
|
20
|
+
if (s.webId !== webId) { say(`You signed in as ${s.webId}, but this account belongs to ${webId}. Sign out there and sign in as the owner.`, true); return true; }
|
|
21
|
+
say('Signing you in…');
|
|
22
|
+
const res = await s.fetch(here, {
|
|
23
|
+
method: 'POST', headers: { 'content-type': 'application/x-www-form-urlencoded' },
|
|
24
|
+
body: location.search.replace(/^\?/u, ''),
|
|
25
|
+
});
|
|
26
|
+
const d = await res.json().catch(() => ({}));
|
|
27
|
+
if (res.ok && d.redirect) { location.href = d.redirect; return true; }
|
|
28
|
+
if (res.ok && d.code) { say(`Your authorization code: ${d.code}`); return true; }
|
|
29
|
+
say(d.error || `The account refused the sign-in (HTTP ${res.status}).`, true);
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (location.search.includes('client_id=') && !(await prove())) $('signin').hidden = false;
|
|
34
|
+
$('signin').addEventListener('click', async () => {
|
|
35
|
+
say('Taking you to your pod…');
|
|
36
|
+
try { await login.login(webId, { returnTo: location.href, redirectUri: here }); } catch (e) { say(e.message, true); }
|
|
37
|
+
});
|
package/web/admin/record.js
CHANGED
|
@@ -61,6 +61,18 @@ async function load() {
|
|
|
61
61
|
$('fatal').appendChild(a);
|
|
62
62
|
return;
|
|
63
63
|
}
|
|
64
|
+
// No agent answering on this browser: nobody is signed in here (the
|
|
65
|
+
// worker answers 503 with no session, and nothing answers at all before
|
|
66
|
+
// the worker exists). Say so, with the way on, rather than the reason.
|
|
67
|
+
if (status === 503 || status === 404) {
|
|
68
|
+
$('fatal').textContent = 'Not signed in on this browser. Sign in ';
|
|
69
|
+
const a = document.createElement('a');
|
|
70
|
+
a.href = '/';
|
|
71
|
+
a.textContent = 'here';
|
|
72
|
+
$('fatal').appendChild(a);
|
|
73
|
+
$('fatal').appendChild(document.createTextNode('.'));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
64
76
|
if (status !== 200 || !json) {
|
|
65
77
|
$('fatal').textContent = json?.error || `could not read the record (HTTP ${status})`;
|
|
66
78
|
return;
|
|
@@ -82,8 +94,8 @@ function resetConfirm() {
|
|
|
82
94
|
for (const k of Object.keys(LIFECYCLE)) $(`warn-${k}`)?.hidden !== undefined && ($(`warn-${k}`).hidden = true);
|
|
83
95
|
$('confirm-handle').value = '';
|
|
84
96
|
$('confirm-handle-move').value = '';
|
|
97
|
+
$('confirm-handle-close').value = '';
|
|
85
98
|
$('move-target').value = '';
|
|
86
|
-
$('rotate-password').value = '';
|
|
87
99
|
}
|
|
88
100
|
function closePanels(keep = null) {
|
|
89
101
|
if (!keep) solWindow.close();
|
|
@@ -4,11 +4,12 @@
|
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
6
|
<title>FediPod — setup</title>
|
|
7
|
+
<link rel="icon" href="/icons/local.svg" type="image/svg+xml">
|
|
7
8
|
<link rel="stylesheet" href="../tokens.css">
|
|
8
9
|
<style>
|
|
9
10
|
/* 20px on the root, not on body, so every rem below scales with it. */
|
|
10
11
|
:root { color-scheme: light dark; font-size: 125%; --heading: #1a4f8a; }
|
|
11
|
-
body { font: 1rem/1.5
|
|
12
|
+
body { font: 1rem/1.5 Arial, Helvetica, sans-serif; margin: 0; }
|
|
12
13
|
/* The bar spans the window; the form stays in a centred column under it. */
|
|
13
14
|
/* The same 960 column the bar and the record use, so the three pages line up. */
|
|
14
15
|
#page { max-width: 960px; margin: 0 auto; padding: 1.75rem 1rem 4rem; box-sizing: border-box; }
|
|
@@ -58,8 +59,16 @@ button:disabled { opacity: .5; cursor: default; }
|
|
|
58
59
|
<nav id="bar" aria-label="Site">
|
|
59
60
|
<h1 class="name">FediPod</h1>
|
|
60
61
|
<span id="bar-handle"></span>
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
<!-- The account group, styled like the client group beside it: one label,
|
|
63
|
+
then the places to go. The browser build adds "sign out" to it. -->
|
|
64
|
+
<span id="account-pick"><span id="account-now">account:</span>
|
|
65
|
+
<a id="bar-fediverse" href="../client/" title=" Open the client and read your timeline">visit</a>
|
|
66
|
+
<a id="bar-manage" href="../" title=" The record: what this actor is, and what you can do to it">manage</a>
|
|
67
|
+
</span>
|
|
68
|
+
<!-- Notices from whoever runs this site. Shown only when the site has any
|
|
69
|
+
(notices-bar.js asks /api/notices); the count is how many are new here. -->
|
|
70
|
+
<button type="button" id="bar-notices" hidden aria-haspopup="dialog" aria-controls="notices-list"
|
|
71
|
+
title=" Notices from the operator of this site">🔔<span id="bar-notices-count" aria-live="polite"></span></button>
|
|
63
72
|
<a id="bar-add" href="../?new=1" title=" Set up another actor on this machine, with its own pod" aria-current="page">add new account</a>
|
|
64
73
|
</nav>
|
|
65
74
|
<main id="page">
|
|
@@ -209,5 +218,24 @@ button:disabled { opacity: .5; cursor: default; }
|
|
|
209
218
|
<script src="../bar.js"></script>
|
|
210
219
|
<script src="../common.js"></script>
|
|
211
220
|
<script src="setup.js"></script>
|
|
221
|
+
|
|
222
|
+
<!-- The notices: the list, and one notice open. Declared here and filled by
|
|
223
|
+
notices-bar.js from /api/notices; nothing in them is markup from a notice. -->
|
|
224
|
+
<dialog id="notices-list" aria-labelledby="notices-title">
|
|
225
|
+
<section>
|
|
226
|
+
<h2 id="notices-title">Notices</h2>
|
|
227
|
+
<ul id="notices-items"></ul>
|
|
228
|
+
<p class="row"><button type="button" id="notices-close" class="primary" title=" Close the list of notices">Close</button></p>
|
|
229
|
+
</section>
|
|
230
|
+
</dialog>
|
|
231
|
+
<dialog id="notice-view" aria-labelledby="notice-view-title">
|
|
232
|
+
<section>
|
|
233
|
+
<h2 id="notice-view-title"></h2>
|
|
234
|
+
<p class="when" id="notice-view-when"></p>
|
|
235
|
+
<div id="notice-view-body"></div>
|
|
236
|
+
<p class="row"><button type="button" id="notice-view-close" class="primary" title=" Back to the list of notices">Close</button></p>
|
|
237
|
+
</section>
|
|
238
|
+
</dialog>
|
|
239
|
+
<script src="../notices-bar.js"></script>
|
|
212
240
|
</body>
|
|
213
241
|
</html>
|
package/web/admin/tokens.css
CHANGED
|
@@ -4,9 +4,12 @@
|
|
|
4
4
|
its :root. Declared under every name the pages already use — plus aliases
|
|
5
5
|
(--ink=--fg, --muted=--sub, --link=--accent, --field-edge=--field-border,
|
|
6
6
|
--danger=--err, --ring=--focus) — so a page only has to drop its own :root
|
|
7
|
-
colours and inherit these.
|
|
7
|
+
colours and inherit these. The base size and the font family live here too:
|
|
8
|
+
20px base, Arial, and nothing on any page below 16px. */
|
|
8
9
|
:root {
|
|
9
10
|
color-scheme: light dark;
|
|
11
|
+
font-size: 125%; /* 20px base (125% of 16) */
|
|
12
|
+
--font: Arial, Helvetica, sans-serif; /* one family across the site */
|
|
10
13
|
|
|
11
14
|
/* ground + text */
|
|
12
15
|
--bg: #ffffff; --fg: #1a1a1a; --ink: #1a1a1a;
|
|
@@ -14,7 +17,7 @@
|
|
|
14
17
|
--raised: #ffffff; /* cards, the rail, the bar band, dialogs */
|
|
15
18
|
|
|
16
19
|
/* structure + action */
|
|
17
|
-
--heading: #
|
|
20
|
+
--heading: #2c4a35; --accent: #12467e; --link: #12467e;
|
|
18
21
|
--sub: #565656; --muted: #565656; --line: #b0b0b0;
|
|
19
22
|
|
|
20
23
|
/* fields */
|
|
@@ -29,6 +32,13 @@
|
|
|
29
32
|
--err: #b00020; --danger: #b00020; --warn: #8a4b00; --ok: #1f7a3d;
|
|
30
33
|
--focus: #12467e; --ring: #12467e;
|
|
31
34
|
|
|
35
|
+
/* the edge of a dialog: bright against the page behind it */
|
|
36
|
+
--dialog-edge: #b8b8b8;
|
|
37
|
+
|
|
38
|
+
/* every other row of a table. A stripe nobody can see is not a stripe:
|
|
39
|
+
this is a real step from the page, not a hint of one. */
|
|
40
|
+
--stripe: #d3dce8;
|
|
41
|
+
|
|
32
42
|
--chevron: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='6'><path d='M1 1l4 4 4-4' fill='none' stroke='%23124f8a' stroke-width='1.6' stroke-linecap='round'/></svg>");
|
|
33
43
|
}
|
|
34
44
|
|
|
@@ -37,7 +47,7 @@
|
|
|
37
47
|
--bg: #191919; --fg: #f3f3f3; --ink: #f3f3f3;
|
|
38
48
|
--surface: #18191a; --raised: #242526;
|
|
39
49
|
|
|
40
|
-
--heading: #
|
|
50
|
+
--heading: #a7d4b5; --accent: #8fc2f2; --link: #8fc2f2;
|
|
41
51
|
--sub: #d2d2d2; --muted: #d2d2d2; --line: #6d6d6d;
|
|
42
52
|
|
|
43
53
|
--field-bg: #474747; --field-bg-hover: #515151; --field-hover: #515151;
|
|
@@ -50,6 +60,8 @@
|
|
|
50
60
|
|
|
51
61
|
--err: #ff9b9b; --danger: #ff9b9b; --warn: #ffb366; --ok: #7fd39a;
|
|
52
62
|
--focus: #8fc2f2; --ring: #8fc2f2;
|
|
63
|
+
--dialog-edge: #f3f3f3;
|
|
64
|
+
--stripe: #363c45;
|
|
53
65
|
|
|
54
66
|
--chevron: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='6'><path d='M1 1l4 4 4-4' fill='none' stroke='%23bcd8f0' stroke-width='1.6' stroke-linecap='round'/></svg>");
|
|
55
67
|
}
|
package/web/admin/upkeep.js
CHANGED
|
@@ -76,6 +76,9 @@ const LIFECYCLE = {
|
|
|
76
76
|
retire: { path: '/retire', title: 'Retire this identity', go: 'Retire it', danger: true, done: (r) => `retired ${r.deletedAt}: Delete delivered to ${r.inboxes} inbox(es)` },
|
|
77
77
|
move: { path: '/move', title: 'Transfer this account away', go: 'Transfer it', focus: 'move-target',
|
|
78
78
|
done: (r) => `transferred to ${r.target}: Move delivered to ${r.inboxes} inbox(es), unfollowed ${r.unfollowed}/${r.following}` },
|
|
79
|
+
// The address at the gateway, not the identity: the pod keeps everything.
|
|
80
|
+
'close-address': { path: '/gateway/close', title: 'Close this address', go: 'Close it', danger: true, focus: 'confirm-handle-close',
|
|
81
|
+
done: (r) => (r.closed ? 'closed — this address is gone for good; everything on your pod is untouched' : 'not closed') },
|
|
79
82
|
};
|
|
80
83
|
let pending = null;
|
|
81
84
|
|
|
@@ -119,11 +122,7 @@ $('confirm-form').addEventListener('submit', async (ev) => {
|
|
|
119
122
|
const what = pending;
|
|
120
123
|
const body = what === 'retire' ? { confirm: $('confirm-handle').value.trim() }
|
|
121
124
|
: what === 'move' ? { target: $('move-target').value.trim(), confirm: $('confirm-handle-move').value.trim() }
|
|
122
|
-
|
|
123
|
-
// On the Node agent the row stays hidden and nothing is sent, which is
|
|
124
|
-
// what that agent expects.
|
|
125
|
-
: what === 'rotate-key' && !$('rotate-pw-row').hidden
|
|
126
|
-
? { password: $('rotate-password').value }
|
|
125
|
+
: what === 'close-address' ? { confirm: $('confirm-handle-close').value.trim() }
|
|
127
126
|
: {};
|
|
128
127
|
if (what === 'move' && !body.target) { say('name the account to transfer to', 'err'); return; }
|
|
129
128
|
$('confirm-go').disabled = true;
|
package/web/app/README.md
CHANGED
|
@@ -5,10 +5,11 @@ See `claude/plans/browser-agent.md` for the whole design and status.
|
|
|
5
5
|
|
|
6
6
|
| file | what |
|
|
7
7
|
|---|---|
|
|
8
|
-
| `pod-auth.mjs` |
|
|
9
|
-
| `keystore.mjs` | WebCrypto RSA/Ed25519 key generation
|
|
10
|
-
| `keys-browser.mjs` | Importing a keys record for signing, and finding one: this browser's opened copy in IndexedDB first, else the pod's
|
|
11
|
-
| `signup.mjs` | The `fedipod setup` flow, in the browser, up to publish:
|
|
8
|
+
| `pod-auth.mjs` | A CSS account API client, browser-native: create an account + pod, mint a client credential, and a DPoP-bound `fetch`. Since 1.28.0 the app itself uses none of it — a pod is made on the provider's own page and the app runs on the pod's Solid-OIDC login — but the harnesses do, to make scratch pods and stand in for a login form. The twin of `lib/device/account.mjs` + `vendor/idp-grant.cjs`. |
|
|
9
|
+
| `keystore.mjs` | WebCrypto RSA/Ed25519 key generation. The key is stored on the pod as it is, in the owner-only state container, since 1.28.0 (2026-09-22); `unwrapKeys` remains for accounts from before then, whose key is under the sign-up password until it is opened once. |
|
|
10
|
+
| `keys-browser.mjs` | Importing a keys record for signing, and finding one: this browser's opened copy in IndexedDB first, else the pod's, read with the pod session. A pre-1.28.0 envelope raises `KeyPasswordNeeded`, which `boot.mjs` answers with the open-once pane; the same pane offers a new key to someone who no longer has that password. |
|
|
11
|
+
| `signup.mjs` | The `fedipod setup` flow, in the browser, up to publish, on the pod session the person already holds: pod check, gateway attach, key stored on the pod (owner-only ACL written *before* the key), config. Takes no password and no credential. Also `readAccount` (what the pod already holds) and `moveIn` (an address at another gateway moving here: attach fronted, rewrite the config with the old actor as an alias, restamp the key, leave the Move pending). |
|
|
12
|
+
| `gateway-move.mjs` | The pending half of a move, run when the agent goes active under the new ids: tells the old gateway (`/api/move`) and sends the Move to every follower from the old actor, signed under the old key id through the old relay. Safe to run again. |
|
|
12
13
|
| `shims/fedify-sig.mjs` | Browser stand-in for `@fedify/fedify/sig` (which will not bundle for a browser). `sign()` returns signed headers as data for the relay; `signRequest()` wraps it Fedify-shaped. Proven byte-identical to Fedify. |
|
|
13
14
|
| `shims/node-crypto.mjs` | Browser stand-in for `node:crypto` — the small synchronous slice the agent uses, via crypto-browserify, plus native WebCrypto. |
|
|
14
15
|
| `shims/safefetch.mjs` | Browser stand-in for `lib/shared/safefetch.mjs`. Pinning and the private-address checks are unnecessary here (a browser closes DNS rebinding itself); the BYTE BUDGET is not, so `readCapped` streams and stops at the cap exactly as the Node one does. |
|
|
@@ -26,7 +27,7 @@ Build: `node scripts/build-app.mjs`. Tests live in `claude/validation/`:
|
|
|
26
27
|
| `deliver-relay.mjs` | lib's Deliverer, sending through the relay (a browser cannot set Date/Host). |
|
|
27
28
|
| `agent.mjs` | Wires lib's store, publisher, intake and Mastodon facade with the browser edges. |
|
|
28
29
|
| `sw-src.mjs` | The service worker that hosts the agent and answers the facade (Mastodon paths + the owner/manage paths); static files fall through. Built to `dist/sw.js`. |
|
|
29
|
-
| `boot.mjs` | Page side: sign
|
|
30
|
+
| `boot.mjs` | Page side: sign in at the pod (also the first half of sign-up), the identity screen on the way back, sign in by address, register the worker, route into `/admin/client/`; `?signout` / `?add` teardown. Built to `dist/boot.js`. |
|
|
30
31
|
| `shims/` | Browser stand-ins the bundle needs: `node-crypto`, `node-path`, `node-url`, `node-fs`, `web-push`, `safefetch`, `prelude` (process + Buffer). |
|
|
31
32
|
|
|
32
33
|
## The manage surface & connected accounts (built 2026-09-08)
|
|
@@ -55,11 +56,14 @@ Tests: `claude/smoke-tests/admin-facade-smoke.mjs`, and eight harnesses in
|
|
|
55
56
|
Solid server — `run` (the agent and the facade), `sw-run` (the same through the
|
|
56
57
|
service worker, plus media upload and notification paging), `full-run` (the
|
|
57
58
|
staged site, sign-up to Phanpy rendering), `unlock-run` (a browser with no opened
|
|
58
|
-
key), `oidc-run`, `resume-run`,
|
|
59
|
-
sign-up page's own policy, read from
|
|
60
|
-
helpers sit beside them: `idp-login.mjs`
|
|
61
|
-
screens, `
|
|
62
|
-
|
|
59
|
+
key, and a pre-1.28.0 account opened once), `oidc-run`, `resume-run`,
|
|
60
|
+
`gateway-run`, and `provider-csp-run` (the sign-up page's own policy, read from
|
|
61
|
+
the browser's refusal reports). Four helpers sit beside them: `idp-login.mjs`
|
|
62
|
+
fills the pod's login and consent screens, `page-signup.mjs` drives the page
|
|
63
|
+
through sign-up the way a person does since 1.28.0 (pod first, then the pod's
|
|
64
|
+
login, then the identity screen), `worker-log.mjs` reads the agent's console
|
|
65
|
+
out of the service worker, and the attach and relay stubs in `sw-run.mjs` stand
|
|
66
|
+
in for the front.
|
|
63
67
|
|
|
64
68
|
## What this build does not have
|
|
65
69
|
|
package/web/app/admin-facade.mjs
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
import { publicHandle, webfingerHost } from '../../lib/core/wire.mjs';
|
|
18
18
|
import * as podInbox from '../../lib/pod/inbox.mjs';
|
|
19
19
|
import { normalizeImport, IMPORT_KINDS } from '../../lib/connections/import.mjs';
|
|
20
|
+
import { followActor, followHandle } from '../../lib/core/social.mjs';
|
|
20
21
|
import { hashPassword } from '../../lib/client/masto/index.mjs';
|
|
21
22
|
|
|
22
23
|
// The identity itself — changing any means a different actor, i.e. a new setup.
|
|
@@ -29,9 +30,10 @@ const WIRE_CONFIG = ['name', 'summary', 'icon', 'image', 'fields', 'aliases'];
|
|
|
29
30
|
// not here falls through to the network as a static file or the gateway.
|
|
30
31
|
export const ADMIN_PATHS = new Set([
|
|
31
32
|
'/status', '/config', '/gateway', '/alias', '/rotate-key', '/import',
|
|
32
|
-
'/deadletter', '/blocks', '/profiles', '/modqueue', '/log', '/fediacct', '/describe',
|
|
33
|
+
'/deadletter', '/blocks', '/profiles', '/modqueue', '/log', '/fediacct', '/describe', '/follow',
|
|
33
34
|
'/atproto', '/atproto/connect', '/atproto/disconnect',
|
|
34
|
-
'/rebuild', '/move', '/retire', '/inbox/prune', '/park', '/revive',
|
|
35
|
+
'/rebuild', '/move', '/retire', '/inbox/prune', '/park', '/revive', '/takeover',
|
|
36
|
+
'/gateway/pause', '/gateway/close',
|
|
35
37
|
'/fediacct/connect', '/fediacct/disconnect', '/fediacct/callback',
|
|
36
38
|
]);
|
|
37
39
|
|
|
@@ -137,6 +139,10 @@ export class AdminFacade {
|
|
|
137
139
|
configured: !!(g && g.url), url: g?.url || null, webId: g?.webId || null,
|
|
138
140
|
frontActor: g?.frontActor || null, mode: g?.mode || 'off', hasSecret: !!g?.hmacSecret,
|
|
139
141
|
stats: a.store.read('gateway-stats.json', { verified: 0, unverified: 0, lastAt: null }),
|
|
142
|
+
// The account's standing at the gateway — paused, closed — as the
|
|
143
|
+
// gateway last said it (agent.mjs tellGateway). The record page
|
|
144
|
+
// shows its pause and close controls only when this is here.
|
|
145
|
+
standing: a.gatewayStanding || null,
|
|
140
146
|
});
|
|
141
147
|
}
|
|
142
148
|
case '/deadletter': return json(200, { items: a.store.getDeadLetters() });
|
|
@@ -241,6 +247,47 @@ export class AdminFacade {
|
|
|
241
247
|
return json(200, { ok: true, summary: cfg.summary || null, icon: cfg.icon || null });
|
|
242
248
|
}
|
|
243
249
|
|
|
250
|
+
// Following somebody, the same call the Node agent's admin surface takes
|
|
251
|
+
// (lib/device/admin/routes/social.mjs): by handle normally, by actor url
|
|
252
|
+
// when WebFinger cannot answer for them. The remote-follow page
|
|
253
|
+
// (/authorize_interaction) is what asks for it here — another server
|
|
254
|
+
// sends its reader to that page when they press Follow and say they are
|
|
255
|
+
// at this host.
|
|
256
|
+
case '/follow': {
|
|
257
|
+
await a.requestTakeover?.();
|
|
258
|
+
if (body.actor) { await followActor(a, String(body.actor)); return json(200, { ok: true, actor: String(body.actor) }); }
|
|
259
|
+
if (!body.handle) return json(400, { error: 'handle or actor required' });
|
|
260
|
+
return json(200, await followHandle(a, String(body.handle)));
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Taking the account back. A viewer publishes nothing and drains
|
|
264
|
+
// nothing, so an account whose lease is held by something that is not
|
|
265
|
+
// running any more stays silent with no way to say so. The owner acting
|
|
266
|
+
// HERE outranks whatever holds it.
|
|
267
|
+
case '/takeover': {
|
|
268
|
+
if (!await a.requestTakeover?.()) return json(503, { error: 'the lease could not be taken — the pod refused the write' });
|
|
269
|
+
return json(200, { ok: true, mode: a.status().mode });
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// The account's standing at its gateway: paused by its owner, or closed
|
|
273
|
+
// for good (front-core: accounts that go quiet). Both go to the gateway
|
|
274
|
+
// with the pod session as proof, and its answer is the new standing.
|
|
275
|
+
case '/gateway/pause': {
|
|
276
|
+
if (typeof body.paused !== 'boolean') return json(400, { error: 'paused must be true or false' });
|
|
277
|
+
if (!a.pauseAtGateway || !a.gatewayApi) return json(501, { error: 'this account is not at a gateway' });
|
|
278
|
+
const r = await a.pauseAtGateway(body.paused);
|
|
279
|
+
if (!r) return json(502, { error: 'the gateway could not be reached' });
|
|
280
|
+
return json(r.status === 200 ? 200 : r.status, r);
|
|
281
|
+
}
|
|
282
|
+
case '/gateway/close': {
|
|
283
|
+
const handle = String(a.store.getConfig()?.handle || '').toLowerCase();
|
|
284
|
+
if (!handle || String(body.confirm || '').toLowerCase() !== handle) return json(400, { error: 'type the handle to confirm' });
|
|
285
|
+
if (!a.closeAtGateway || !a.gatewayApi) return json(501, { error: 'this account is not at a gateway' });
|
|
286
|
+
const r = await a.closeAtGateway();
|
|
287
|
+
if (!r) return json(502, { error: 'the gateway could not be reached' });
|
|
288
|
+
return json(r.status === 200 ? 200 : r.status, r);
|
|
289
|
+
}
|
|
290
|
+
|
|
244
291
|
// Going quiet, and coming back. The record page's active/parked select.
|
|
245
292
|
case '/park': {
|
|
246
293
|
if (!await a.requestTakeover?.()) return json(503, { error: 'another device is active for this pod — park from there' });
|
|
@@ -253,16 +300,8 @@ export class AdminFacade {
|
|
|
253
300
|
|
|
254
301
|
case '/rotate-key': {
|
|
255
302
|
await a.requestTakeover?.();
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
// with the request, something is required before it can be made.
|
|
259
|
-
try {
|
|
260
|
-
const r = await a.rotateKey({ password: body.password });
|
|
261
|
-
return json(200, { ok: true, changed: !!r?.changed });
|
|
262
|
-
} catch (e) {
|
|
263
|
-
if (e.code !== 'key-password-needed') throw e;
|
|
264
|
-
return json(428, { error: e.message, needsPassword: true });
|
|
265
|
-
}
|
|
303
|
+
const r = await a.rotateKey();
|
|
304
|
+
return json(200, { ok: true, changed: !!r?.changed });
|
|
266
305
|
}
|
|
267
306
|
|
|
268
307
|
// Recover posts this browser lost, from what the pod still holds.
|