fedipod 0.18.0 → 0.19.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 +20 -0
- package/bin/fedipod.mjs +3 -5
- package/cli.md +13 -0
- package/groups.md +11 -0
- package/gui.md +15 -1
- package/lib/admin.mjs +3 -3
- package/lib/deliver.mjs +6 -0
- package/lib/export-collections.mjs +1 -1
- package/lib/front-core.mjs +272 -49
- package/lib/gateway-core.mjs +3 -7
- package/lib/httpsig.mjs +33 -2
- package/lib/import.mjs +1 -1
- package/lib/intake.mjs +335 -118
- package/lib/links.mjs +6 -32
- package/lib/mastoapi.mjs +372 -35
- package/lib/migrate.mjs +4 -18
- package/lib/pod/README.md +61 -0
- package/lib/pod/actor.mjs +96 -0
- package/lib/pod/collection.mjs +77 -0
- package/lib/pod/containers.mjs +136 -0
- package/lib/pod/discovery.mjs +46 -0
- package/lib/pod/featured.mjs +18 -0
- package/lib/pod/followers.mjs +24 -0
- package/lib/pod/following.mjs +10 -0
- package/lib/pod/http.mjs +74 -0
- package/lib/pod/inbox.mjs +144 -0
- package/lib/pod/links.mjs +35 -0
- package/lib/pod/media.mjs +27 -0
- package/lib/pod/notes.mjs +80 -0
- package/lib/pod/notifications.mjs +89 -0
- package/lib/pod/outbox.mjs +18 -0
- package/lib/pod/package.json +19 -0
- package/lib/pod/policy.mjs +58 -0
- package/lib/pod/private.mjs +19 -0
- package/lib/pod/root.mjs +63 -0
- package/lib/pod/state.mjs +39 -0
- package/lib/pod/transport.mjs +477 -0
- package/lib/pod/urls.mjs +71 -0
- package/lib/publisher.mjs +112 -183
- package/lib/remote.mjs +30 -395
- package/lib/social.mjs +18 -9
- package/lib/storage.mjs +2 -2
- package/lib/store.mjs +8 -1
- package/lib/tagfeed.mjs +19 -2
- package/lib/wire.mjs +48 -67
- package/package.json +9 -2
- package/run-agent.mjs +14 -39
- package/scripts/build-app.mjs +64 -0
- package/scripts/check-pod-calls.mjs +177 -0
- package/scripts/stage-site.mjs +144 -0
- package/web/admin/admin.js +141 -74
- package/web/admin/bar.css +9 -13
- package/web/admin/bar.js +2 -2
- package/web/admin/client/client.js +1 -1
- package/web/admin/client/index.html +5 -6
- package/web/admin/index.html +53 -65
- package/web/admin/setup/index.html +2 -1
- package/web/admin/setup/setup.js +1 -1
- package/web/admin/tokens.css +56 -0
- package/web/app/README.md +77 -0
- package/web/app/admin-facade.mjs +549 -0
- package/web/app/agent.mjs +490 -0
- package/web/app/atproto-browser.mjs +86 -0
- package/web/app/boot.mjs +340 -0
- package/web/app/deliver-relay.mjs +72 -0
- package/web/app/dist/boot.js +34067 -0
- package/web/app/dist/boot.js.map +7 -0
- package/web/app/dist/sw.js +53745 -0
- package/web/app/dist/sw.js.map +7 -0
- package/web/app/fediacct-browser.mjs +146 -0
- package/web/app/idb-kv.mjs +35 -0
- package/web/app/index.html +203 -0
- package/web/app/index.html~ +152 -0
- package/web/app/keys-browser.mjs +46 -0
- package/web/app/keystore.mjs +99 -0
- package/web/app/oidc-session.mjs +189 -0
- package/web/app/pod-auth.mjs +248 -0
- package/web/app/pod-remote.mjs +84 -0
- package/web/app/shims/_globals.mjs +2 -0
- package/web/app/shims/fedify-sig.mjs +77 -0
- package/web/app/shims/node-crypto.mjs +113 -0
- package/web/app/shims/node-fs.mjs +17 -0
- package/web/app/shims/node-path.mjs +19 -0
- package/web/app/shims/node-url.mjs +7 -0
- package/web/app/shims/prelude.js +58 -0
- package/web/app/shims/safefetch.mjs +58 -0
- package/web/app/shims/web-push.mjs +14 -0
- package/web/app/signup.mjs +248 -0
- package/web/app/site/_headers +9 -0
- package/web/app/site/_redirects +21 -0
- package/web/app/site/admin/admin.js +1181 -0
- package/web/app/site/admin/bar.css +60 -0
- package/web/app/site/admin/bar.js +61 -0
- package/web/app/site/admin/client/client.js +66 -0
- package/web/app/site/admin/client/index.html +43 -0
- package/web/app/site/admin/index.html +556 -0
- package/web/app/site/admin/setup/index.html +191 -0
- package/web/app/site/admin/setup/setup.js +308 -0
- package/web/app/site/admin/tokens.css +56 -0
- package/web/app/site/admin/window.css +55 -0
- package/web/app/site/admin/window.js +136 -0
- package/web/app/site/app/404.html +40 -0
- package/web/app/site/app/_headers +2 -0
- package/web/app/site/app/apple-touch-icon.png +0 -0
- package/web/app/site/app/assets/ICONS-DIPJeU0C.js +2 -0
- package/web/app/site/app/assets/ICONS-DIPJeU0C.js.map +1 -0
- package/web/app/site/app/assets/ScheduledAtField-D-Abfo4J.js +183 -0
- package/web/app/site/app/assets/ScheduledAtField-D-Abfo4J.js.map +1 -0
- package/web/app/site/app/assets/Temml-CCjoacWt.woff2 +0 -0
- package/web/app/site/app/assets/__vite-browser-external-C7iut881.js +2 -0
- package/web/app/site/app/assets/__vite-browser-external-C7iut881.js.map +1 -0
- package/web/app/site/app/assets/boosts-carousel-BiOaNdMT.jpg +0 -0
- package/web/app/site/app/assets/catch-up-O5q5C75Z.png +0 -0
- package/web/app/site/app/assets/chunk-aKtaBQYM.js +1 -0
- package/web/app/site/app/assets/compose-CFEx7wb4.js +2 -0
- package/web/app/site/app/assets/compose-CFEx7wb4.js.map +1 -0
- package/web/app/site/app/assets/compose-ZJN5xSb4.js +35 -0
- package/web/app/site/app/assets/compose-ZJN5xSb4.js.map +1 -0
- package/web/app/site/app/assets/debug.module-YFQgwnIr.js +52 -0
- package/web/app/site/app/assets/debug.module-YFQgwnIr.js.map +1 -0
- package/web/app/site/app/assets/dom-p1YcJ5Rw.js +2 -0
- package/web/app/site/app/assets/dom-p1YcJ5Rw.js.map +1 -0
- package/web/app/site/app/assets/home-mobile-dark@2x-Dc-J8-SM.png +0 -0
- package/web/app/site/app/assets/home-mobile-light@2x-Bd3fod8m.png +0 -0
- package/web/app/site/app/assets/home-tablet-dark@2x-BgcNI8le.png +0 -0
- package/web/app/site/app/assets/home-tablet-light@2x-BW2RV9T6.png +0 -0
- package/web/app/site/app/assets/icons/add-circle-line-WD0mwORD.js +1 -0
- package/web/app/site/app/assets/icons/alert-line-CpFUMf9s.js +1 -0
- package/web/app/site/app/assets/icons/android-2-line-Bzxnrd3S.js +1 -0
- package/web/app/site/app/assets/icons/announcement-line-5nwaJFJm.js +1 -0
- package/web/app/site/app/assets/icons/arrow-down-circle-line-a7Id9-Iv.js +1 -0
- package/web/app/site/app/assets/icons/arrow-down-line-B8uzXzq6.js +1 -0
- package/web/app/site/app/assets/icons/arrow-left-line-CGrGT9C-.js +1 -0
- package/web/app/site/app/assets/icons/arrow-right-line-CE-HERfX.js +1 -0
- package/web/app/site/app/assets/icons/arrow-to-up-line-CvdQjmcy.js +1 -0
- package/web/app/site/app/assets/icons/arrow-up-circle-line-DEparSNF.js +1 -0
- package/web/app/site/app/assets/icons/arrow-up-line-C7yBeugc.js +1 -0
- package/web/app/site/app/assets/icons/arrows-right-line-BtbXmJGP.js +1 -0
- package/web/app/site/app/assets/icons/at-line-CF66Bznk.js +1 -0
- package/web/app/site/app/assets/icons/attachment-line-BY8It-l9.js +1 -0
- package/web/app/site/app/assets/icons/blockquote-line-BS6pU_hm.js +1 -0
- package/web/app/site/app/assets/icons/board-line-C0UWXyA6.js +1 -0
- package/web/app/site/app/assets/icons/bookmark-line-1CtX5JLF.js +1 -0
- package/web/app/site/app/assets/icons/building-5-line-Dd59MzbO.js +1 -0
- package/web/app/site/app/assets/icons/bus-2-line-6_dvA0GG.js +1 -0
- package/web/app/site/app/assets/icons/calendar-day-line-Crpl_fmu.js +1 -0
- package/web/app/site/app/assets/icons/calendar-month-line-vommts2C.js +1 -0
- package/web/app/site/app/assets/icons/calendar-time-add-line-BU_DTlQk.js +1 -0
- package/web/app/site/app/assets/icons/camera-line-Cv69MEei.js +1 -0
- package/web/app/site/app/assets/icons/celebrate-line-B8DvIoi0.js +1 -0
- package/web/app/site/app/assets/icons/chart-bar-line-FK-QOmtF.js +1 -0
- package/web/app/site/app/assets/icons/chart-line-line-BGcJAFWw.js +1 -0
- package/web/app/site/app/assets/icons/chat-3-line-YFK2-hwz.js +1 -0
- package/web/app/site/app/assets/icons/check-circle-line-DsAxFr5-.js +1 -0
- package/web/app/site/app/assets/icons/clipboard-line-BBp364gJ.js +1 -0
- package/web/app/site/app/assets/icons/close-circle-line-CICdiuGz.js +1 -0
- package/web/app/site/app/assets/icons/close-line-DYrFWcjv.js +1 -0
- package/web/app/site/app/assets/icons/cloud-line-tiQAbah8.js +1 -0
- package/web/app/site/app/assets/icons/code-line-fAl8iiV3.js +1 -0
- package/web/app/site/app/assets/icons/comment-2-line-CR3g7WeB.js +1 -0
- package/web/app/site/app/assets/icons/copy-2-line-CLW69yF5.js +1 -0
- package/web/app/site/app/assets/icons/delete-2-line-B8xmbr4n.js +1 -0
- package/web/app/site/app/assets/icons/document-3-line-DBGNhAg2.js +1 -0
- package/web/app/site/app/assets/icons/document-line-C7mb9sb7.js +1 -0
- package/web/app/site/app/assets/icons/down-line-BGqSSThd.js +1 -0
- package/web/app/site/app/assets/icons/edit-4-line-DcgGaEhx.js +1 -0
- package/web/app/site/app/assets/icons/emoji-2-line-D8YgnM-y.js +1 -0
- package/web/app/site/app/assets/icons/exit-line-Dc07BS-x.js +1 -0
- package/web/app/site/app/assets/icons/external-link-line-BvpSkmjO.js +1 -0
- package/web/app/site/app/assets/icons/eye-2-line-CiWXx-f_.js +1 -0
- package/web/app/site/app/assets/icons/eye-close-line-BgTaSJ4P.js +1 -0
- package/web/app/site/app/assets/icons/filter-2-line-B_mR2usy.js +1 -0
- package/web/app/site/app/assets/icons/filter-line-C_nm-3cU.js +1 -0
- package/web/app/site/app/assets/icons/flag-1-line-C2HB05I0.js +1 -0
- package/web/app/site/app/assets/icons/forbid-circle-line-D3LTT9yd.js +1 -0
- package/web/app/site/app/assets/icons/formula-line-DLrt2d6j.js +1 -0
- package/web/app/site/app/assets/icons/grid-line-CgUmEikf.js +1 -0
- package/web/app/site/app/assets/icons/group-line-C5fnX0wU.js +1 -0
- package/web/app/site/app/assets/icons/hand-finger-2-line-BGgxbuL9.js +1 -0
- package/web/app/site/app/assets/icons/hashtag-line-CyTEEXBb.js +1 -0
- package/web/app/site/app/assets/icons/heart-crack-line-BZddbO2C.js +1 -0
- package/web/app/site/app/assets/icons/heart-line-CJ7K47J4.js +1 -0
- package/web/app/site/app/assets/icons/history-2-line-kWWvFzqv.js +1 -0
- package/web/app/site/app/assets/icons/history-line-bGQiwbTH.js +1 -0
- package/web/app/site/app/assets/icons/home-3-line-fRU1zeLG.js +1 -0
- package/web/app/site/app/assets/icons/information-line-iNk6-sOY.js +1 -0
- package/web/app/site/app/assets/icons/keyboard-line-DsCyuQNz.js +1 -0
- package/web/app/site/app/assets/icons/layout-4-line-CZkOX52Z.js +1 -0
- package/web/app/site/app/assets/icons/layout-5-line-B_znt5wO.js +1 -0
- package/web/app/site/app/assets/icons/left-line-eUw0tya4.js +1 -0
- package/web/app/site/app/assets/icons/lightning-line-DaKoDdix.js +1 -0
- package/web/app/site/app/assets/icons/link-2-line-DCqLmMA7.js +1 -0
- package/web/app/site/app/assets/icons/list-check-line-BAxosBh9.js +1 -0
- package/web/app/site/app/assets/icons/lock-line-CPoaXeUW.js +1 -0
- package/web/app/site/app/assets/icons/mail-line-B0P8aa1L.js +1 -0
- package/web/app/site/app/assets/icons/moon-line-CEqNAToP.js +1 -0
- package/web/app/site/app/assets/icons/more-1-fill-Dw2e5xab.js +1 -0
- package/web/app/site/app/assets/icons/more-3-line-BIiVjYTz.js +1 -0
- package/web/app/site/app/assets/icons/notification-line-BZZq2Gtu.js +1 -0
- package/web/app/site/app/assets/icons/pencil-line-CKh8-A1v.js +1 -0
- package/web/app/site/app/assets/icons/photo-album-line-Bnfg2u40.js +1 -0
- package/web/app/site/app/assets/icons/pin-line-DpTolyhs.js +1 -0
- package/web/app/site/app/assets/icons/play-fill-B0-G_8lX.js +1 -0
- package/web/app/site/app/assets/icons/qrcode-2-line-B6SPCaHn.js +1 -0
- package/web/app/site/app/assets/icons/quill-pen-line-B6LUrnCP.js +1 -0
- package/web/app/site/app/assets/icons/quote-left-fill-hQZ1cj_6.js +1 -0
- package/web/app/site/app/assets/icons/radar-line-CKyroTth.js +1 -0
- package/web/app/site/app/assets/icons/react-line-CoIKhhtK.js +1 -0
- package/web/app/site/app/assets/icons/refresh-2-line-D0mTF97S.js +1 -0
- package/web/app/site/app/assets/icons/right-line-B3KWJaqc.js +1 -0
- package/web/app/site/app/assets/icons/rocket-line-4KZl537D.js +1 -0
- package/web/app/site/app/assets/icons/round-fill-C69NTrZ0.js +1 -0
- package/web/app/site/app/assets/icons/round-line-B9k_1uo-.js +1 -0
- package/web/app/site/app/assets/icons/route-line-B28ReHcE.js +1 -0
- package/web/app/site/app/assets/icons/rows-4-line-CrBniagM.js +1 -0
- package/web/app/site/app/assets/icons/scan-line-DSf74YdP.js +1 -0
- package/web/app/site/app/assets/icons/search-2-line-B6geIU1z.js +1 -0
- package/web/app/site/app/assets/icons/settings-3-line-azJwK47w.js +1 -0
- package/web/app/site/app/assets/icons/settings-6-line-C_5zc5YR.js +1 -0
- package/web/app/site/app/assets/icons/share-2-line-D5MxWjrm.js +1 -0
- package/web/app/site/app/assets/icons/share-forward-line-Cp61fZzZ.js +1 -0
- package/web/app/site/app/assets/icons/sparkles-2-line-DYYyXTaW.js +1 -0
- package/web/app/site/app/assets/icons/sparkles-line-C2hY1s6I.js +1 -0
- package/web/app/site/app/assets/icons/time-line-BF3eet1v.js +1 -0
- package/web/app/site/app/assets/icons/transfer-4-line-oisfvTBw.js +1 -0
- package/web/app/site/app/assets/icons/translate-line-BFWol2Ce.js +1 -0
- package/web/app/site/app/assets/icons/unlock-line-X_x5vwv3.js +1 -0
- package/web/app/site/app/assets/icons/upload-3-line-DgzwUJeW.js +1 -0
- package/web/app/site/app/assets/icons/user-4-line-BVSynZCp.js +1 -0
- package/web/app/site/app/assets/icons/user-add-2-line-BlGS5Q8q.js +1 -0
- package/web/app/site/app/assets/icons/user-add-line-BbXdHG4z.js +1 -0
- package/web/app/site/app/assets/icons/user-edit-line-BFAHOV0X.js +1 -0
- package/web/app/site/app/assets/icons/user-follow-line-BXux0wSd.js +1 -0
- package/web/app/site/app/assets/icons/user-star-line-DdYlpHLU.js +1 -0
- package/web/app/site/app/assets/icons/user-warning-line-Cj43j0_E.js +1 -0
- package/web/app/site/app/assets/icons/user-x-line-DIDgNlPH.js +1 -0
- package/web/app/site/app/assets/icons/volume-line-Bs390KQt.js +1 -0
- package/web/app/site/app/assets/icons/volume-mute-line-DzlzIqFZ.js +1 -0
- package/web/app/site/app/assets/icons/walk-line-yNXfB-md.js +1 -0
- package/web/app/site/app/assets/icons/world-2-line-CenkGIwj.js +1 -0
- package/web/app/site/app/assets/icons/zoom-in-line-CaENUphf.js +1 -0
- package/web/app/site/app/assets/icons/zoom-out-line-BDNk0EWo.js +1 -0
- package/web/app/site/app/assets/instances-BNPptnn-.json +333 -0
- package/web/app/site/app/assets/locales/ar-SA-CfheRbCu.js +1 -0
- package/web/app/site/app/assets/locales/ca-ES-DHRW86Tz.js +1 -0
- package/web/app/site/app/assets/locales/cs-CZ-CKoqEuA1.js +1 -0
- package/web/app/site/app/assets/locales/de-DE-ByRPMRFy.js +1 -0
- package/web/app/site/app/assets/locales/eo-UY-CPC7wLCS.js +1 -0
- package/web/app/site/app/assets/locales/es-ES-dJa0Fd9t.js +1 -0
- package/web/app/site/app/assets/locales/eu-ES-C3djlPR2.js +1 -0
- package/web/app/site/app/assets/locales/fa-IR-DTT490Ck.js +1 -0
- package/web/app/site/app/assets/locales/fi-FI-B23MCPuz.js +1 -0
- package/web/app/site/app/assets/locales/fr-FR-BcaQsO5C.js +1 -0
- package/web/app/site/app/assets/locales/gl-ES-BR-CVKBN.js +1 -0
- package/web/app/site/app/assets/locales/he-IL-CfheRbCu.js +1 -0
- package/web/app/site/app/assets/locales/hu-HU-CfheRbCu.js +1 -0
- package/web/app/site/app/assets/locales/it-IT-C1136UFV.js +1 -0
- package/web/app/site/app/assets/locales/ja-JP-CRZmRAhD.js +1 -0
- package/web/app/site/app/assets/locales/kab-Cz8xH_TT.js +1 -0
- package/web/app/site/app/assets/locales/ko-KR-CG9UiY4o.js +1 -0
- package/web/app/site/app/assets/locales/lt-LT-CJcI6sHZ.js +1 -0
- package/web/app/site/app/assets/locales/nb-NO-CfheRbCu.js +1 -0
- package/web/app/site/app/assets/locales/nl-NL-DxN4Yc1t.js +1 -0
- package/web/app/site/app/assets/locales/oc-FR-CfheRbCu.js +1 -0
- package/web/app/site/app/assets/locales/pl-PL-DkwGXf6P.js +1 -0
- package/web/app/site/app/assets/locales/pseudo-LOCALE-CQ4XxkQ0.js +1 -0
- package/web/app/site/app/assets/locales/pt-BR-nGL0HdFS.js +1 -0
- package/web/app/site/app/assets/locales/pt-PT-Cp17aj2z.js +1 -0
- package/web/app/site/app/assets/locales/ru-RU-0Y8cIoaK.js +1 -0
- package/web/app/site/app/assets/locales/th-TH-CfheRbCu.js +1 -0
- package/web/app/site/app/assets/locales/tok-CfheRbCu.js +1 -0
- package/web/app/site/app/assets/locales/tr-TR-CfheRbCu.js +1 -0
- package/web/app/site/app/assets/locales/uk-UA-C5E3dFBf.js +1 -0
- package/web/app/site/app/assets/locales/zh-CN-BHJi7_et.js +1 -0
- package/web/app/site/app/assets/locales/zh-TW-CfheRbCu.js +1 -0
- package/web/app/site/app/assets/main-BdqNbG-a.js +26 -0
- package/web/app/site/app/assets/main-BdqNbG-a.js.map +1 -0
- package/web/app/site/app/assets/mock-home-DGhR__He.js +2 -0
- package/web/app/site/app/assets/mock-home-DGhR__He.js.map +1 -0
- package/web/app/site/app/assets/multi-column-ROck0NVt.jpg +0 -0
- package/web/app/site/app/assets/multi-hashtag-timeline-DH5INVHi.jpg +0 -0
- package/web/app/site/app/assets/nested-comments-thread-hv59kZLc.jpg +0 -0
- package/web/app/site/app/assets/polyfill-force-H8sUkLGp.js +2 -0
- package/web/app/site/app/assets/polyfill-force-H8sUkLGp.js.map +1 -0
- package/web/app/site/app/assets/pwa-viewport-ogKbF51K.js +20 -0
- package/web/app/site/app/assets/pwa-viewport-ogKbF51K.js.map +1 -0
- package/web/app/site/app/assets/style-B1iLPTPR.css +2 -0
- package/web/app/site/app/assets/temml-DuyIQMei.js +97 -0
- package/web/app/site/app/assets/temml-DuyIQMei.js.map +1 -0
- package/web/app/site/app/assets/tinyld.light.node-DtbBhLYC.js +2 -0
- package/web/app/site/app/assets/tinyld.light.node-DtbBhLYC.js.map +1 -0
- package/web/app/site/app/assets/year-in-posts-B2lywqdx.png +0 -0
- package/web/app/site/app/assets/year-in-posts-DJv_star.js +3 -0
- package/web/app/site/app/assets/year-in-posts-DJv_star.js.map +1 -0
- package/web/app/site/app/compose/index.html +20 -0
- package/web/app/site/app/favicon.ico +0 -0
- package/web/app/site/app/index.html +73 -0
- package/web/app/site/app/logo-192.png +0 -0
- package/web/app/site/app/logo-512.png +0 -0
- package/web/app/site/app/logo-badge-72.png +0 -0
- package/web/app/site/app/logo-maskable-512.png +0 -0
- package/web/app/site/app/logo-monochrome-512.png +0 -0
- package/web/app/site/app/logo-monochrome-maskable-512.png +0 -0
- package/web/app/site/app/manifest.webmanifest +1 -0
- package/web/app/site/app/og-image-2.jpg +0 -0
- package/web/app/site/app/og-image.png +0 -0
- package/web/app/site/app/robots.txt +2 -0
- package/web/app/site/app/version.json +1 -0
- package/web/app/site/boot.js +34067 -0
- package/web/app/site/index.html +203 -0
- package/web/app/site/sw.js +53745 -0
- package/web/app/sw-src.mjs +205 -0
- package/web/front/#new-account.html# +43 -0
- package/web/front/admin.html +1 -81
- package/web/front/admin.js +85 -0
- package/web/front/new-account.html +1 -9
- package/web/front/new-account.js +13 -0
- package/web/front/run.html +1 -92
- package/web/front/run.js +107 -0
- package/lib/podrdf.mjs +0 -132
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// stage-site.mjs — assemble the deployable static site for the in-browser agent.
|
|
2
|
+
//
|
|
3
|
+
// Output (web/app/site/, gitignored): the sign-up and sign-in pages at the
|
|
4
|
+
// root, the boot glue and the agent service worker (sw.js must be at the root
|
|
5
|
+
// so it controls the whole origin), and Phanpy under /app/ with its own
|
|
6
|
+
// service-worker registration removed so only the agent worker runs.
|
|
7
|
+
//
|
|
8
|
+
// npm run build:app # both bundles, into web/app/dist/
|
|
9
|
+
// npm run stage # this script
|
|
10
|
+
//
|
|
11
|
+
// The bundles are tracked, so rebuild and commit them in the SAME commit as
|
|
12
|
+
// whatever source change caused them — a dist that does not match its own
|
|
13
|
+
// source is the hardest kind of thing to see in the browser build.
|
|
14
|
+
import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url';
|
|
15
|
+
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
16
|
+
const site = path.join(root, 'web/app/site');
|
|
17
|
+
fs.rmSync(site, { recursive: true, force: true }); fs.mkdirSync(path.join(site, 'app'), { recursive: true });
|
|
18
|
+
const cp = (from, to) => fs.copyFileSync(path.join(root, from), path.join(site, to));
|
|
19
|
+
cp('web/app/index.html', 'index.html');
|
|
20
|
+
cp('web/app/dist/boot.js', 'boot.js');
|
|
21
|
+
cp('web/app/dist/sw.js', 'sw.js');
|
|
22
|
+
const copyDir = (from, to) => { fs.mkdirSync(to, { recursive: true });
|
|
23
|
+
for (const e of fs.readdirSync(from, { withFileTypes: true })) {
|
|
24
|
+
const s = path.join(from, e.name), d = path.join(to, e.name);
|
|
25
|
+
if (e.isDirectory()) copyDir(s, d);
|
|
26
|
+
else if (e.name === 'sw.js' || e.name === 'sw.js.map') { /* drop Phanpy's own worker */ }
|
|
27
|
+
else if (e.name === 'index.html') fs.writeFileSync(d, fs.readFileSync(s, 'utf8').replace(/<script id="vite-plugin-pwa:inline-sw">[\s\S]*?<\/script>/i, ''));
|
|
28
|
+
else fs.copyFileSync(s, d);
|
|
29
|
+
} };
|
|
30
|
+
copyDir(path.join(root, 'phanpy/dist'), path.join(site, 'app'));
|
|
31
|
+
// The owner's record/manage surface: the SAME web/admin pages the Node agent
|
|
32
|
+
// serves, reused as static files (the agent answers their data endpoints from
|
|
33
|
+
// the worker — see admin-facade.mjs). Two browser adaptations: the client shell
|
|
34
|
+
// frames the client at /app/ (locally the client is the origin root, so the
|
|
35
|
+
// source says "../../"), and the manual "Drain the inbox" control is hidden —
|
|
36
|
+
// the browser drains automatically over notifications.
|
|
37
|
+
const copyAdmin = (from, to) => { fs.mkdirSync(to, { recursive: true });
|
|
38
|
+
for (const e of fs.readdirSync(from, { withFileTypes: true })) {
|
|
39
|
+
const s = path.join(from, e.name), d = path.join(to, e.name);
|
|
40
|
+
if (e.isDirectory()) { copyAdmin(s, d); continue; }
|
|
41
|
+
if (e.name !== 'index.html') { fs.copyFileSync(s, d); continue; }
|
|
42
|
+
// Every bar in the browser build: no "Local actors" dropdown (there is one
|
|
43
|
+
// identity per browser), and the FediPod brand is a link to the sign-in /
|
|
44
|
+
// add-a-pod page — /?add shows it without bouncing a signed-in user back
|
|
45
|
+
// into the client, so setup is always one click from the bar.
|
|
46
|
+
let text = fs.readFileSync(s, 'utf8')
|
|
47
|
+
.replace('<h1 class="name">FediPod</h1>', '<h1 class="name"><a href="/?add">FediPod</a></h1>')
|
|
48
|
+
// A sign-out beside "manage account", on every bar. It clears the FediPod
|
|
49
|
+
// session, the client's stored account, browser-stored connected keys, and
|
|
50
|
+
// the worker — the /?signout page (boot.js) does the teardown.
|
|
51
|
+
.replace('>manage account</a>', '>manage account</a>\n <a id="bar-signout" href="/?signout" title=" Sign out of FediPod and clear this browser">sign out</a>')
|
|
52
|
+
.replace('</head>', '<style>#actor-pick{display:none!important}</style>\n</head>');
|
|
53
|
+
if (/[/\\]client$/.test(from)) {
|
|
54
|
+
// The client shell frames the client at /app/ (locally the client is the
|
|
55
|
+
// origin root, so the source says "../../").
|
|
56
|
+
text = text.replace('src="../../"', 'src="/app/"');
|
|
57
|
+
} else if (/[/\\]admin$/.test(from)) {
|
|
58
|
+
// Hide controls the browser build does not carry: the manual drain (it
|
|
59
|
+
// drains automatically over notifications), the local logfile viewer
|
|
60
|
+
// (there is no local log in a browser), and the "create a handle at the
|
|
61
|
+
// gateway" choice — a fronted @you@front identity is not the browser
|
|
62
|
+
// model, and admin-facade.mjs refuses one outright. Leaving the radio
|
|
63
|
+
// visible meant the page checked the name's availability as you typed,
|
|
64
|
+
// told you it was free, and then refused the attach. With it hidden the
|
|
65
|
+
// pod-based choice stays selected, so that check never runs either.
|
|
66
|
+
// And rename "Recover posts" to the plainer "Refresh Feed" for this
|
|
67
|
+
// audience.
|
|
68
|
+
text = text
|
|
69
|
+
.replace('<style>#actor-pick', '<style>#gw-shape-front,#do-drain,#do-log,#actor-pick')
|
|
70
|
+
.replace('>Recover posts<', '>Refresh Feed<')
|
|
71
|
+
// Reveal the password row on the rotate-key warning: here the key on
|
|
72
|
+
// the pod is wrapped under the account password (keystore.mjs), so a
|
|
73
|
+
// replacement has to be wrapped too and there is nowhere to get one
|
|
74
|
+
// from but the owner. The Node agent keeps its key on disk and leaves
|
|
75
|
+
// this row hidden.
|
|
76
|
+
.replace('<div id="rotate-pw-row" hidden>', '<div id="rotate-pw-row">')
|
|
77
|
+
.replace('Put back posts this machine lost, from what the pod still holds',
|
|
78
|
+
'Refresh your feed from what your pod still holds');
|
|
79
|
+
}
|
|
80
|
+
fs.writeFileSync(d, text);
|
|
81
|
+
} };
|
|
82
|
+
copyAdmin(path.join(root, 'web/admin'), path.join(site, 'admin'));
|
|
83
|
+
// Netlify routing. The project sends every path to the gateway function by
|
|
84
|
+
// default, which would shadow these static files, so file-based rules (which
|
|
85
|
+
// win over the project's UI redirect) serve the app directly and forward only
|
|
86
|
+
// the gateway's own paths to the function. First match wins.
|
|
87
|
+
fs.writeFileSync(path.join(site, '_redirects'), [
|
|
88
|
+
'# gateway + front API → the function',
|
|
89
|
+
'/api/handle /.netlify/functions/front 200',
|
|
90
|
+
'/api/attach /.netlify/functions/front 200',
|
|
91
|
+
'/api/relay /.netlify/functions/front 200',
|
|
92
|
+
'/api/roster /.netlify/functions/front 200',
|
|
93
|
+
'/api/revoke /.netlify/functions/front 200',
|
|
94
|
+
'/api/agent /.netlify/functions/front 200',
|
|
95
|
+
'/u/* /.netlify/functions/front 200',
|
|
96
|
+
'/.well-known/* /.netlify/functions/front 200',
|
|
97
|
+
'/install /.netlify/functions/front 200',
|
|
98
|
+
'/run /.netlify/functions/front 200',
|
|
99
|
+
'/roster /.netlify/functions/front 200',
|
|
100
|
+
'/solid-oidc-client.js /.netlify/functions/front 200',
|
|
101
|
+
'# the in-browser app, served static (these win over the project catch-all)',
|
|
102
|
+
'/ /index.html 200',
|
|
103
|
+
'/sw.js /sw.js 200',
|
|
104
|
+
'/boot.js /boot.js 200',
|
|
105
|
+
|
|
106
|
+
'/app /app/ 301',
|
|
107
|
+
'/app/* /app/:splat 200',
|
|
108
|
+
'/admin /admin/ 301',
|
|
109
|
+
'/admin/* /admin/:splat 200',
|
|
110
|
+
'', ].join('\n'));
|
|
111
|
+
// Response headers. Same-origin IS the trust boundary for this build — the
|
|
112
|
+
// worker answers the admin routes on this origin, and one page here renders
|
|
113
|
+
// fediverse HTML from strangers — so `script-src 'self'` is what stands
|
|
114
|
+
// between a remote post and the identity. Only `/app/*` and `/admin/*` are
|
|
115
|
+
// given a CSP header here: the root page carries its own `<meta>` CSP, tuned
|
|
116
|
+
// to its issuer, and a header would intersect with it and break the login.
|
|
117
|
+
//
|
|
118
|
+
// Netlify reads ONE `_headers`, at the publish root. (Phanpy ships its own
|
|
119
|
+
// under `app/`, which is why that copy has never done anything.)
|
|
120
|
+
//
|
|
121
|
+
// `Referrer-Policy: same-origin` and not `no-referrer`: the worker's own gate
|
|
122
|
+
// reads `request.referrer` to tell the bundled client's sign-in navigation
|
|
123
|
+
// from a cross-site one (sw-src.mjs), and stripping it everywhere would refuse
|
|
124
|
+
// the legitimate one. Same-origin sends it to us and to nobody else.
|
|
125
|
+
//
|
|
126
|
+
// `style-src` keeps `'unsafe-inline'`: the admin pages get a `<style>` block
|
|
127
|
+
// injected above, and `innerHTML` with a `style=` attribute is common in both.
|
|
128
|
+
// Inline STYLE cannot exfiltrate the way inline SCRIPT can, and tightening it
|
|
129
|
+
// would cost the boundary nothing. `/app/*` needs `https:` for images and
|
|
130
|
+
// media because the whole point of the client is rendering other servers'
|
|
131
|
+
// avatars and attachments.
|
|
132
|
+
fs.writeFileSync(path.join(site, '_headers'), [
|
|
133
|
+
'/app/*',
|
|
134
|
+
" Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' https: data: blob:; media-src 'self' https: blob:; connect-src 'self' https:; font-src 'self' data:; object-src 'none'; base-uri 'none'; frame-ancestors 'self'; form-action 'self'",
|
|
135
|
+
' X-Content-Type-Options: nosniff',
|
|
136
|
+
' Referrer-Policy: same-origin',
|
|
137
|
+
'',
|
|
138
|
+
'/admin/*',
|
|
139
|
+
" Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' https: data: blob:; connect-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'self'; form-action 'self'",
|
|
140
|
+
' X-Content-Type-Options: nosniff',
|
|
141
|
+
' Referrer-Policy: same-origin',
|
|
142
|
+
'', ].join('\n'));
|
|
143
|
+
let n = 0; (function count(d) { for (const e of fs.readdirSync(d, { withFileTypes: true })) e.isDirectory() ? count(path.join(d, e.name)) : n++; })(site);
|
|
144
|
+
console.log(`staged web/app/site — ${n} files`);
|
package/web/admin/admin.js
CHANGED
|
@@ -13,8 +13,15 @@ const $ = (id) => document.getElementById(id);
|
|
|
13
13
|
// machine, `/app` when the identity is hosted by its own pod server. Read from
|
|
14
14
|
// the page's own address, so one build serves both.
|
|
15
15
|
const BASE = location.pathname.replace(/\/admin\/.*$/u, '');
|
|
16
|
+
// Every call carries `x-fedipod-page`. On the Node agent it is ignored — the
|
|
17
|
+
// Origin check there already refuses a page on somebody else's site. In the
|
|
18
|
+
// browser build the agent IS this origin's service worker, which cannot see
|
|
19
|
+
// Sec-Fetch-* and answers these routes with no password, so this header is the
|
|
20
|
+
// whole door: a cross-site form cannot set one, and a cross-origin fetch that
|
|
21
|
+
// sets one is stopped at a preflight the worker never grants. See sw-src.mjs.
|
|
22
|
+
const PAGE_HEADER = { 'x-fedipod-page': '1' };
|
|
16
23
|
const api = async (path, init) => {
|
|
17
|
-
const res = await fetch(BASE + path, init);
|
|
24
|
+
const res = await fetch(BASE + path, { ...init, headers: { ...(init?.headers || {}), ...PAGE_HEADER } });
|
|
18
25
|
return { status: res.status, json: await res.json().catch(() => null) };
|
|
19
26
|
};
|
|
20
27
|
const postJson = (path, body) => api(path, {
|
|
@@ -27,14 +34,12 @@ let config = null;
|
|
|
27
34
|
const MODERATION = $('moderation');
|
|
28
35
|
const STATUS = $('status-ctl');
|
|
29
36
|
const STATUS_PICK = $('status-pick'); // held: getElementById can't see it mid-render
|
|
30
|
-
const BSKY_CTL = $('bsky-ctl');
|
|
31
37
|
const IDENT_CTL = $('ident-ctl');
|
|
32
38
|
const FOLLOWS_CTL = $('follows-ctl');
|
|
33
39
|
const FOLLOWS_PICK = $('follows-pick');
|
|
34
40
|
const UPDATE_CTL = $('update-ctl');
|
|
35
41
|
const UPDATE_WORD = $('update-word');
|
|
36
42
|
const UPDATE_GO = $('update-go');
|
|
37
|
-
const BSKY_CROSSPOST = $('bsky-crosspost'); // held: it rides BSKY_CTL into a generated row
|
|
38
43
|
|
|
39
44
|
// #say and #fatal live in the accessibility tree from load and hide by being
|
|
40
45
|
// empty (see the stylesheet). Unhiding a live region and filling it in the same
|
|
@@ -95,10 +100,7 @@ function resetConfirm() {
|
|
|
95
100
|
$('confirm-handle').value = '';
|
|
96
101
|
$('confirm-handle-move').value = '';
|
|
97
102
|
$('move-target').value = '';
|
|
98
|
-
$('
|
|
99
|
-
$('state-url').value = '';
|
|
100
|
-
document.querySelector('input[name=stateWhere][value=path]').checked = true;
|
|
101
|
-
stateRows();
|
|
103
|
+
$('rotate-password').value = '';
|
|
102
104
|
}
|
|
103
105
|
function closePanels(keep = null) {
|
|
104
106
|
if (!keep) solWindow.close();
|
|
@@ -126,34 +128,129 @@ function identityRows() {
|
|
|
126
128
|
dd.append(...nodes);
|
|
127
129
|
out.push([dt, dd]);
|
|
128
130
|
};
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
131
|
+
// One descriptor shape for every connected account — Bluesky and each
|
|
132
|
+
// fediverse account — so the row and its Options menu build the same way.
|
|
133
|
+
const accounts = [];
|
|
134
|
+
if (config.atproto?.connected) accounts.push({
|
|
135
|
+
label: 'Bluesky', handle: `@${config.atproto.handle}`,
|
|
136
|
+
href: `https://bsky.app/profile/${config.atproto.handle}`,
|
|
137
|
+
storage: config.atproto.storage || null,
|
|
138
|
+
feedPaused: 'feedPaused' in config.atproto ? config.atproto.feedPaused : null,
|
|
139
|
+
crossPost: 'crossPost' in config.atproto ? config.atproto.crossPost : null,
|
|
140
|
+
feed: (paused) => ['/atproto', { feedPaused: paused }],
|
|
141
|
+
cross: (on) => ['/atproto', { crossPost: on }],
|
|
142
|
+
store: (w) => ['/atproto', { storage: w }],
|
|
143
|
+
off: () => ['/atproto/disconnect', {}],
|
|
144
|
+
});
|
|
145
|
+
for (const acct of config.fediAccounts || []) accounts.push({
|
|
146
|
+
label: acct.host,
|
|
147
|
+
handle: acct.needsReconnect ? `${acct.handle} — sign in again` : acct.handle,
|
|
148
|
+
warn: acct.needsReconnect,
|
|
149
|
+
storage: acct.storage || null,
|
|
150
|
+
feedPaused: !acct.enabled,
|
|
151
|
+
crossPost: null,
|
|
152
|
+
feed: (paused) => ['/fediacct', { id: acct.id, enabled: !paused }],
|
|
153
|
+
store: (w) => ['/fediacct', { id: acct.id, storage: w }],
|
|
154
|
+
off: () => ['/fediacct/disconnect', { id: acct.id }],
|
|
155
|
+
});
|
|
156
|
+
for (const acct of accounts) {
|
|
157
|
+
const name = document.createElement(acct.href ? 'a' : 'span');
|
|
158
|
+
name.textContent = acct.handle;
|
|
159
|
+
if (acct.href) { name.href = acct.href; name.target = '_blank'; name.rel = 'noopener'; name.title = ' Open this account elsewhere in a new tab'; }
|
|
160
|
+
if (acct.warn) name.className = 'warn';
|
|
161
|
+
// Where storage is a real choice (every browser-build account today), two
|
|
162
|
+
// dropdowns sit in the row itself. Where it isn't (the Node agent's
|
|
163
|
+
// Bluesky, which shows a cross-post toggle instead), the Options
|
|
164
|
+
// disclosure it always has is unchanged.
|
|
165
|
+
row(acct.label, name, ' ', ...(acct.storage ? connectionControls(acct) : [optionsMenu(acct)]));
|
|
166
|
+
}
|
|
167
|
+
return out;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Connected, Paused or Disconnected, and where the key lives — one dropdown
|
|
171
|
+
// each, in the row. Replaces the separate feed and connect dropdowns that used
|
|
172
|
+
// to sit behind an Options disclosure; only reached where acct.storage is set.
|
|
173
|
+
function connectionControls(acct) {
|
|
174
|
+
const send = async ([path, payload], msg) => { if (await write(path, payload, msg)) await load(); };
|
|
175
|
+
const pick = (opts, current, onChange, title) => {
|
|
176
|
+
const el = document.createElement('select');
|
|
177
|
+
if (title) el.title = title;
|
|
178
|
+
for (const [v, t] of opts) { const o = document.createElement('option'); o.value = v; o.textContent = t; o.selected = v === current; el.appendChild(o); }
|
|
179
|
+
el.addEventListener('change', () => onChange(el.value));
|
|
180
|
+
return el;
|
|
181
|
+
};
|
|
182
|
+
const conn = pick(
|
|
183
|
+
[['connected', 'Connected'], ['paused', 'Paused'], ['disconnected', 'Disconnected']],
|
|
184
|
+
acct.feedPaused ? 'paused' : 'connected',
|
|
185
|
+
(v) => (v === 'disconnected'
|
|
186
|
+
? send(acct.off(), 'disconnected')
|
|
187
|
+
: send(acct.feed(v === 'paused'), v === 'paused' ? 'paused adding to your feed' : 'added to your home feed')),
|
|
188
|
+
' Connected feeds this account into your home feed here; Paused keeps the account but stops adding its posts; Disconnected removes it.');
|
|
189
|
+
const storage = pick(
|
|
190
|
+
[['pod', 'Store key on Pod'], ['browser', 'Store key in Browser']],
|
|
191
|
+
acct.storage,
|
|
192
|
+
(v) => {
|
|
193
|
+
if (v === 'pod' && !confirm('Store this account on your pod?\n\nIt will follow you to any browser you sign in from — but a full-access token to that account then lives on your pod.')) { load(); return; }
|
|
194
|
+
send(acct.store(v), v === 'pod' ? 'stored on your pod' : 'stored in this browser');
|
|
195
|
+
},
|
|
196
|
+
' On your pod the key follows you to any browser you sign in from, and a full-access token then lives there; in this browser the token never leaves this device.');
|
|
197
|
+
const wrap = document.createElement('span');
|
|
198
|
+
wrap.className = 'conn-controls';
|
|
199
|
+
wrap.append(conn, ' ', storage);
|
|
200
|
+
return [wrap];
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Every connected account without a storage choice carries an Options menu
|
|
204
|
+
// instead: pause its feed, its cross-post toggle (the Node agent's Bluesky),
|
|
205
|
+
// disconnect.
|
|
206
|
+
function optionsMenu(acct) {
|
|
207
|
+
const d = document.createElement('details');
|
|
208
|
+
d.className = 'opts';
|
|
209
|
+
const sum = document.createElement('summary');
|
|
210
|
+
sum.textContent = 'Options';
|
|
211
|
+
d.appendChild(sum);
|
|
212
|
+
const body = document.createElement('div');
|
|
213
|
+
d.appendChild(body);
|
|
214
|
+
|
|
215
|
+
// Each control is one dropdown whose options ARE the states, so it needs no
|
|
216
|
+
// separate label — the chosen option reads as the current state.
|
|
217
|
+
const pick = (opts, current, onChange) => {
|
|
218
|
+
const el = document.createElement('select');
|
|
219
|
+
for (const [v, t] of opts) { const o = document.createElement('option'); o.value = v; o.textContent = t; o.selected = v === current; el.appendChild(o); }
|
|
220
|
+
el.addEventListener('change', () => onChange(el.value));
|
|
221
|
+
body.appendChild(el);
|
|
222
|
+
};
|
|
223
|
+
const send = async ([path, payload], msg) => { if (await write(path, payload, msg)) await load(); };
|
|
224
|
+
|
|
225
|
+
// Feed: shown in / hidden from the feed you read here.
|
|
226
|
+
if (acct.feedPaused !== null) {
|
|
227
|
+
pick([['show', 'Add to home feed'], ['hide', 'Pause adding to home feed']], acct.feedPaused ? 'hide' : 'show',
|
|
228
|
+
(v) => send(acct.feed(v === 'hide'), v === 'hide' ? 'paused adding to your feed' : 'added to your home feed'));
|
|
141
229
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
off.className = 'inline danger';
|
|
149
|
-
off.textContent = 'Disconnect';
|
|
150
|
-
off.title = ` Stop reading ${acct.handle} and remove its stored token`;
|
|
151
|
-
off.addEventListener('click', async () => {
|
|
152
|
-
if (await write('/fediacct/disconnect', { id: acct.id }, `${acct.handle} disconnected`)) await load();
|
|
230
|
+
// Storage where the browser offers the choice; the Node agent's Bluesky shows
|
|
231
|
+
// its cross-post toggle in the same slot instead.
|
|
232
|
+
if (acct.storage) {
|
|
233
|
+
pick([['browser', 'Store key in Browser'], ['pod', 'Store key on Pod']], acct.storage, (v) => {
|
|
234
|
+
if (v === 'pod' && !confirm('Store this account on your pod?\n\nIt will follow you to any browser you sign in from — but a full-access token to that account then lives on your pod.')) { load(); return; }
|
|
235
|
+
send(acct.store(v), v === 'pod' ? 'stored on your pod' : 'stored in this browser');
|
|
153
236
|
});
|
|
154
|
-
|
|
237
|
+
} else if (acct.crossPost !== null) {
|
|
238
|
+
pick([['on', 'Cross-post On'], ['off', 'Cross-post Off']], acct.crossPost ? 'on' : 'off',
|
|
239
|
+
(v) => send(acct.cross(v === 'on'), ''));
|
|
155
240
|
}
|
|
156
|
-
|
|
241
|
+
// Connection: staying connected, or disconnecting.
|
|
242
|
+
pick([['connected', 'Connected'], ['disconnect', 'Disconnect']], 'connected',
|
|
243
|
+
(v) => { if (v === 'disconnect') send(acct.off(), 'disconnected'); else load(); });
|
|
244
|
+
|
|
245
|
+
// The storage consequence, at the bottom of the box under every dropdown.
|
|
246
|
+
if (acct.storage) {
|
|
247
|
+
const hint = document.createElement('p'); hint.className = 'opt-hint';
|
|
248
|
+
hint.textContent = acct.storage === 'pod'
|
|
249
|
+
? 'Follows you to every browser you sign in from; a full-access token lives on your pod.'
|
|
250
|
+
: 'Stays in this browser; the token never leaves this device.';
|
|
251
|
+
body.appendChild(hint);
|
|
252
|
+
}
|
|
253
|
+
return d;
|
|
157
254
|
}
|
|
158
255
|
|
|
159
256
|
function render() {
|
|
@@ -180,9 +277,10 @@ function render() {
|
|
|
180
277
|
['Other identities', 'ctl'],
|
|
181
278
|
['local store', config.home],
|
|
182
279
|
// The address you actually open, not the bare number — the named origin when
|
|
183
|
-
// there is one, since that is what the client and the OAuth redirect use.
|
|
184
|
-
|
|
185
|
-
|
|
280
|
+
// there is one, since that is what the client and the OAuth redirect use. An
|
|
281
|
+
// agent with no local host of its own (embedded in a pod server, or in a
|
|
282
|
+
// browser) sends none, and then there is no row rather than a bare number.
|
|
283
|
+
['local host', (origins.named || origins.loopback || '').replace(/\/$/, '') || null],
|
|
186
284
|
];
|
|
187
285
|
if (config.version || config.update || config.pendingUpgrade?.length) rows.push(['software', 'ctl']);
|
|
188
286
|
rows.push(['gateway', 'ctl']);
|
|
@@ -502,13 +600,8 @@ $('fediacct-form').addEventListener('submit', async (ev) => {
|
|
|
502
600
|
} finally { fediBusy = false; }
|
|
503
601
|
});
|
|
504
602
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
bskyBusy = true;
|
|
508
|
-
try {
|
|
509
|
-
if (await write('/atproto/disconnect', {}, 'bluesky account disconnected')) await load();
|
|
510
|
-
} finally { bskyBusy = false; }
|
|
511
|
-
});
|
|
603
|
+
// Disconnecting Bluesky, pausing its feed and choosing its storage all live in
|
|
604
|
+
// the account's Options menu now (see optionsMenu). Connecting is still here.
|
|
512
605
|
// Adding resolves the old account on the agent side, so what lands in
|
|
513
606
|
// alsoKnownAs is its canonical id, not the string typed here.
|
|
514
607
|
// The accounts elsewhere this one may receive a Move from — the chips in the
|
|
@@ -553,15 +646,6 @@ $('alias-add').addEventListener('click', async () => {
|
|
|
553
646
|
} finally { aliasBusy = false; }
|
|
554
647
|
});
|
|
555
648
|
|
|
556
|
-
// Silent on success — the select already shows the new state.
|
|
557
|
-
const setCrossPost = async (on) => {
|
|
558
|
-
if (bskyBusy || on === !!config.atproto?.crossPost) return;
|
|
559
|
-
bskyBusy = true;
|
|
560
|
-
try {
|
|
561
|
-
if (await write('/atproto', { crossPost: on }, '')) config.atproto.crossPost = on;
|
|
562
|
-
BSKY_CROSSPOST.value = config.atproto?.crossPost ? 'on' : 'off';
|
|
563
|
-
} finally { bskyBusy = false; }
|
|
564
|
-
};
|
|
565
649
|
|
|
566
650
|
// ---- group ----
|
|
567
651
|
|
|
@@ -655,8 +739,6 @@ function onPick(el, apply, repaint = renderGroupToggles) {
|
|
|
655
739
|
onPick($('joins-mod'), (v) => setJoins(v === MOD.on));
|
|
656
740
|
onPick($('review-mod'), (v) => setReview(v === MOD.on));
|
|
657
741
|
onPick(STATUS_PICK, (v) => setStatus(v === 'parked'), renderStatus);
|
|
658
|
-
onPick(BSKY_CROSSPOST, (v) => setCrossPost(v === 'on'),
|
|
659
|
-
() => { BSKY_CROSSPOST.value = config?.atproto?.crossPost ? 'on' : 'off'; });
|
|
660
742
|
|
|
661
743
|
// A person's follower gate. Automatic is what a migration wave needs: every
|
|
662
744
|
// follower's server re-follows at once, and the waiting queue caps at 500.
|
|
@@ -897,21 +979,7 @@ const LIFECYCLE = {
|
|
|
897
979
|
retire: { path: '/retire', title: 'Retire this identity', go: 'Retire it', danger: true, done: (r) => `retired ${r.deletedAt}: Delete delivered to ${r.inboxes} inbox(es)` },
|
|
898
980
|
move: { path: '/move', title: 'Transfer this account away', go: 'Transfer it', focus: 'move-target',
|
|
899
981
|
done: (r) => `transferred to ${r.target}: Move delivered to ${r.inboxes} inbox(es), unfollowed ${r.unfollowed}/${r.following}` },
|
|
900
|
-
'move-state': { path: '/state-move', title: 'Move private data', go: 'Move it', focus: 'state-path',
|
|
901
|
-
done: (r) => (r.unchanged ? 'already there — nothing moved'
|
|
902
|
-
: `moved ${r.docs} document(s) and ${r.notes} post(s) — private data is now ${r.now}`) },
|
|
903
982
|
};
|
|
904
|
-
|
|
905
|
-
// The destination choice reveals the field it needs: a directory for this
|
|
906
|
-
// device, an address for another one, nothing for the pod.
|
|
907
|
-
function stateRows() {
|
|
908
|
-
const w = picked('stateWhere');
|
|
909
|
-
$('state-row-path').hidden = w !== 'path';
|
|
910
|
-
$('state-row-url').hidden = w !== 'url';
|
|
911
|
-
}
|
|
912
|
-
for (const el of document.querySelectorAll('input[name=stateWhere]')) {
|
|
913
|
-
el.addEventListener('change', stateRows);
|
|
914
|
-
}
|
|
915
983
|
let pending = null;
|
|
916
984
|
|
|
917
985
|
const closeConfirm = () => closePanels();
|
|
@@ -954,16 +1022,15 @@ $('confirm-form').addEventListener('submit', async (ev) => {
|
|
|
954
1022
|
const what = pending;
|
|
955
1023
|
const body = what === 'retire' ? { confirm: $('confirm-handle').value.trim() }
|
|
956
1024
|
: what === 'move' ? { target: $('move-target').value.trim(), confirm: $('confirm-handle-move').value.trim() }
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
1025
|
+
// Only where the field is shown (the browser build — see index.html).
|
|
1026
|
+
// On the Node agent the row stays hidden and nothing is sent, which is
|
|
1027
|
+
// what that agent expects.
|
|
1028
|
+
: what === 'rotate-key' && !$('rotate-pw-row').hidden
|
|
1029
|
+
? { password: $('rotate-password').value }
|
|
961
1030
|
: {};
|
|
962
1031
|
if (what === 'move' && !body.target) { say('name the account to transfer to', 'err'); return; }
|
|
963
|
-
if (what === 'move-state' && !body.to) { say('name the destination', 'err'); return; }
|
|
964
1032
|
$('confirm-go').disabled = true;
|
|
965
|
-
say(what
|
|
966
|
-
: `${what} — this talks to the pod and to other servers, so it takes a moment`);
|
|
1033
|
+
say(`${what} — this talks to the pod and to other servers, so it takes a moment`);
|
|
967
1034
|
const r = await write(LIFECYCLE[what].path, body, what);
|
|
968
1035
|
$('confirm-go').disabled = false;
|
|
969
1036
|
if (!r) return; // write() already said why
|
package/web/admin/bar.css
CHANGED
|
@@ -4,18 +4,9 @@
|
|
|
4
4
|
page's left edge and the buttons over its right rather than out at the window
|
|
5
5
|
borders. `width: 100%` with `box-sizing: border-box` keeps the padding inside
|
|
6
6
|
that 960 instead of adding to it, which is what makes the two margins equal. */
|
|
7
|
-
/*
|
|
8
|
-
|
|
9
|
-
Phanpy
|
|
10
|
-
because bar.css is on all three pages, so they cannot drift. */
|
|
11
|
-
:root { --surface: #f0f2f5; --raised: #fff;
|
|
12
|
-
--ink: #1b1c1e; --field-bg: #ffffff; --field-bg-hover: #f7f4ee; --field-edge: #767676;
|
|
13
|
-
--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='%23444444' stroke-width='1.6' stroke-linecap='round'/></svg>"); }
|
|
14
|
-
@media (prefers-color-scheme: dark) {
|
|
15
|
-
:root { --surface: #18191a; --raised: #242526;
|
|
16
|
-
--ink: #e8eaed; --field-bg: #383d44; --field-bg-hover: #40464e; --field-edge: #9aa0a6;
|
|
17
|
-
--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='%23dddddd' stroke-width='1.6' stroke-linecap='round'/></svg>"); }
|
|
18
|
-
}
|
|
7
|
+
/* Colours come from tokens.css (the front's palette), linked by every page
|
|
8
|
+
before this file, so the bar matches the front and the record rather than
|
|
9
|
+
Phanpy: --surface is the page ground, --raised the bar band on top of it. */
|
|
19
10
|
body { background: var(--surface); }
|
|
20
11
|
/* The BAND spans the window; its CONTENTS stay in the 960 column. Constraining
|
|
21
12
|
the element itself left the colour as a stripe with bare page either side.
|
|
@@ -51,8 +42,13 @@ body { background: var(--surface); }
|
|
|
51
42
|
#bar select:hover { background-color: var(--field-bg-hover); }
|
|
52
43
|
/* The app title, in px rather than rem: it is chrome, the same on every page,
|
|
53
44
|
not something that should move when a page changes its base size. */
|
|
54
|
-
#bar .name { flex: 1 1 auto; font-weight: 600; font-size:
|
|
45
|
+
#bar .name { flex: 1 1 auto; font-weight: 600; font-size: 40px; color: var(--heading);
|
|
55
46
|
margin: 0; line-height: 1.1; }
|
|
47
|
+
/* The browser build wraps the brand text in a link (to /?add) — the general
|
|
48
|
+
#bar a rule below would otherwise pin it back down to nav-link size and
|
|
49
|
+
colour, since a rule on the anchor itself always wins over inheriting the
|
|
50
|
+
heading's size. */
|
|
51
|
+
#bar .name a { font: inherit; color: inherit; text-decoration: none; }
|
|
56
52
|
/* Plain text, not chrome: three destinations in a row read better without a
|
|
57
53
|
box round each. */
|
|
58
54
|
#bar button, #bar a { font: inherit; font-size: .95rem; padding: .15rem .2rem;
|
package/web/admin/bar.js
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
// open windows apart without putting the handle in the page twice.
|
|
18
18
|
// /status, not /config: an agent that is not set up yet answers this one and
|
|
19
19
|
// 409s the other, which logged a failed request on every setup page.
|
|
20
|
-
fetch(location.pathname.replace(/\/admin\/.*$/u, '') + '/status').then(r => (r.ok ? r.json() : null)).then((s) => {
|
|
20
|
+
fetch(location.pathname.replace(/\/admin\/.*$/u, '') + '/status', { headers: { 'x-fedipod-page': '1' } }).then(r => (r.ok ? r.json() : null)).then((s) => {
|
|
21
21
|
if (s?.handle) document.title = `FediPod — ${s.handle}`;
|
|
22
22
|
// The full fediverse handle, centred in the bar on every page.
|
|
23
23
|
const el = document.getElementById('bar-handle');
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
const pick = document.getElementById('actor-pick');
|
|
33
33
|
if (pick && !document.getElementById('new-actor-form')) {
|
|
34
34
|
const base = location.pathname.replace(/\/admin\/.*$/u, '');
|
|
35
|
-
fetch(base + '/profiles').then(r => (r.ok ? r.json() : null)).then((j) => {
|
|
35
|
+
fetch(base + '/profiles', { headers: { 'x-fedipod-page': '1' } }).then(r => (r.ok ? r.json() : null)).then((j) => {
|
|
36
36
|
const actors = j?.identities || [];
|
|
37
37
|
pick.textContent = '';
|
|
38
38
|
const label = (r) => (r.address || r.handle || r.name)
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
// "which account logged in here" is a different question, and it is the one
|
|
28
28
|
// that goes wrong when the two origins get mixed.
|
|
29
29
|
const base = location.pathname.replace(/\/admin\/.*$/u, '');
|
|
30
|
-
const status = await fetch(base + '/status').then(r => (r.ok ? r.json() : null)).catch(() => null);
|
|
30
|
+
const status = await fetch(base + '/status', { headers: { 'x-fedipod-page': '1' } }).then(r => (r.ok ? r.json() : null)).catch(() => null);
|
|
31
31
|
if (!status?.actor) return; // not set up yet; setup owns that
|
|
32
32
|
|
|
33
33
|
// A deep link into the client — the record links an actor's own page this way
|
|
@@ -4,18 +4,17 @@
|
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
6
|
<title>FediPod — client</title>
|
|
7
|
+
<link rel="stylesheet" href="../tokens.css">
|
|
7
8
|
<style>
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
/* Colours are the shared tokens (tokens.css); only layout is here. The bar is
|
|
10
|
+
a fixed height, the client takes the rest — the page never scrolls, the frame
|
|
11
|
+
does. */
|
|
12
|
+
:root { color-scheme: light dark; font-size: 125%; }
|
|
11
13
|
html { height: 100%; }
|
|
12
14
|
body { font: 1rem/1.5 system-ui, sans-serif; margin: 0; height: 100dvh;
|
|
13
15
|
display: flex; flex-direction: column; overflow: hidden; }
|
|
14
16
|
main { flex: 1 1 auto; min-height: 0; display: flex; }
|
|
15
17
|
#client { flex: 1 1 auto; min-height: 0; width: 100%; border: 0; }
|
|
16
|
-
@media (prefers-color-scheme: dark) {
|
|
17
|
-
:root { --link: #7fb3e8; --heading: #b9a3e3; }
|
|
18
|
-
}
|
|
19
18
|
</style>
|
|
20
19
|
<link rel="stylesheet" href="../bar.css">
|
|
21
20
|
</head>
|