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/app/signup.mjs
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
// signup.mjs — the setup flow of `fedipod setup`, run in the browser
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
1
|
+
// signup.mjs — the setup flow of `fedipod setup`, run in the browser, on the
|
|
2
|
+
// pod session the person already holds. The pod exists before this runs: a
|
|
3
|
+
// new one is made on the provider's own sign-up page, an existing one is
|
|
4
|
+
// signed in to as it is. So there is no account API here, no credential and
|
|
5
|
+
// no password — the session that signed in at the pod is the session that
|
|
6
|
+
// writes to it. Publishing the actor is the agent's first-run job, exactly as
|
|
7
|
+
// `connect()` publishes after `setup` mints — so this module needs nothing
|
|
8
|
+
// from lib beyond the pod helpers.
|
|
7
9
|
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
// write key, so the caller stores it in this browser only, never on the pod.
|
|
11
|
-
// The only thing this writes to the pod is the password-wrapped signing key.
|
|
10
|
+
// What it writes to the pod: an owner-only state container holding the
|
|
11
|
+
// signing key and the account config; and, with a gateway, the attach.
|
|
12
12
|
|
|
13
|
-
import {
|
|
14
|
-
import { generateKeys, wrapKeys } from './keystore.mjs';
|
|
13
|
+
import { generateKeys, isKeyEnvelope } from './keystore.mjs';
|
|
15
14
|
import { BrowserRemotePod } from './pod-remote.mjs';
|
|
16
15
|
import * as podState from '../../lib/pod/state.mjs';
|
|
17
16
|
import { resourceExists } from '../../lib/pod/root.mjs';
|
|
17
|
+
import { podBaseOfWebId } from '../../lib/pod/urls.mjs';
|
|
18
18
|
import { cacheOpenedKeys } from './keys-browser.mjs';
|
|
19
19
|
|
|
20
20
|
// The container everything the agent publishes hangs under. New pods made here
|
|
21
21
|
// use `fedipod/`; new installs default to `fedipod/` too, and older ones keep
|
|
22
22
|
// that predate this, so those pods are untouched. The name is stored on the
|
|
23
|
-
//
|
|
23
|
+
// config, so the agent reads it rather than guessing.
|
|
24
24
|
export const AP_ROOT = 'fedipod/';
|
|
25
25
|
const actorUrlFor = (pod) => `${pod}${AP_ROOT}ap/actor`;
|
|
26
26
|
const keysDocFor = (pod) => `${pod}${AP_ROOT}ap-state/keys.json`;
|
|
@@ -35,29 +35,15 @@ export function handleProblem(handle) {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
// Resume across a failed attempt. A run that throws part-way keeps what it
|
|
38
|
-
// already achieved in this page session — the
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
//
|
|
38
|
+
// already achieved in this page session — the gateway it attached, the key it
|
|
39
|
+
// wrote — so a second attempt (the failure screen's Try again) continues from
|
|
40
|
+
// the first unfinished step instead of restarting. The record lives only in
|
|
41
|
+
// memory here and is dropped the moment the run completes. Keyed by the
|
|
42
|
+
// identity being built, so changing the handle on the form starts a clean
|
|
43
|
+
// attempt.
|
|
44
44
|
const PROGRESS = new Map();
|
|
45
|
-
const progressKey = (a) => [
|
|
45
|
+
const progressKey = (webId, a) => [webId, a.handle, a.shape || 'pod'].join('|');
|
|
46
46
|
|
|
47
|
-
/**
|
|
48
|
-
* Run sign-up. Steps are reported through onStep(key, state, note) so a page can
|
|
49
|
-
* draw the same tick list the CLI setup shows. Resumable: see PROGRESS above.
|
|
50
|
-
*
|
|
51
|
-
* answers: { mode:'new'|'existing', issuer, email, password, handle,
|
|
52
|
-
* podName?, pod?, gateway?, shape?: 'pod'|'front' }
|
|
53
|
-
*
|
|
54
|
-
* `shape` is where the address lives. On the pod, `@handle@yourpod`, with the
|
|
55
|
-
* gateway as a mail door only — the default. At the gateway, `@handle@front`,
|
|
56
|
-
* a fronted identity whose documents still live on the pod. A pod on a path of
|
|
57
|
-
* a suffix-based host cannot answer WebFinger, so it is fronted whatever was asked.
|
|
58
|
-
*
|
|
59
|
-
* returns: { credential, config, actorUrl, address, keysPublic }
|
|
60
|
-
*/
|
|
61
47
|
/** A fronted name is one per gateway; a taken one is refused before anything is made. */
|
|
62
48
|
async function assertFrontNameFree(frontOrigin, handle) {
|
|
63
49
|
const res = await fetch(`${frontOrigin.replace(/\/$/, '')}/api/handle?handle=${encodeURIComponent(handle)}`,
|
|
@@ -67,19 +53,35 @@ async function assertFrontNameFree(frontOrigin, handle) {
|
|
|
67
53
|
if (!d.available) throw new Error(d.reason || `the name @${handle}@${new URL(frontOrigin).host} is taken — choose another handle`);
|
|
68
54
|
}
|
|
69
55
|
|
|
70
|
-
|
|
71
|
-
|
|
56
|
+
/**
|
|
57
|
+
* Run sign-up on a pod session. Steps are reported through onStep(key, state,
|
|
58
|
+
* note) so a page can draw the same tick list the CLI setup shows. Resumable:
|
|
59
|
+
* see PROGRESS above.
|
|
60
|
+
*
|
|
61
|
+
* answers: { handle, shape?: 'pod'|'front', gateway? }
|
|
62
|
+
* session: { webId, issuer, fetch } — the Solid-OIDC session from the pod's
|
|
63
|
+
* own login (oidc-session.mjs). The pod is where the WebID lives.
|
|
64
|
+
*
|
|
65
|
+
* `shape` is where the address lives. On the pod, `@handle@yourpod`, with the
|
|
66
|
+
* gateway as a mail door only — the default. At the gateway, `@handle@front`,
|
|
67
|
+
* a fronted identity whose documents still live on the pod. A pod on a path of
|
|
68
|
+
* a suffix-based host cannot answer WebFinger, so it is fronted whatever was asked.
|
|
69
|
+
*
|
|
70
|
+
* returns: { config, actorUrl, address, pod, keys, keysPublic }
|
|
71
|
+
*/
|
|
72
|
+
export async function signUp(answers, { session, onStep = () => {}, frontOrigin = null } = {}) {
|
|
73
|
+
const { handle } = answers;
|
|
72
74
|
const bad = handleProblem(handle);
|
|
73
75
|
if (bad) throw new Error(bad);
|
|
74
|
-
if (!
|
|
75
|
-
if (!password) throw new Error('a password is required');
|
|
76
|
-
if (mode === 'existing' && !answers.pod) throw new Error('a pod address is required');
|
|
76
|
+
if (!session?.webId || typeof session.fetch !== 'function') throw new Error('sign in at your pod first');
|
|
77
77
|
const wantsFront = answers.shape === 'front';
|
|
78
78
|
if (wantsFront && !frontOrigin) throw new Error('an address at the gateway needs a gateway, and this page has none');
|
|
79
79
|
// A name at the gateway is one per gateway: settle it before making anything.
|
|
80
80
|
if (wantsFront) await assertFrontNameFree(frontOrigin, handle);
|
|
81
81
|
|
|
82
|
-
const
|
|
82
|
+
const webId = session.webId;
|
|
83
|
+
const pod = podBaseOfWebId(webId); // its own host, or a path on a suffix-based one
|
|
84
|
+
const key = progressKey(webId, answers);
|
|
83
85
|
const prog = PROGRESS.get(key) || {};
|
|
84
86
|
PROGRESS.set(key, prog); // resume record for this identity
|
|
85
87
|
|
|
@@ -89,28 +91,16 @@ export async function signUp(answers, { onStep = () => {}, frontOrigin = null }
|
|
|
89
91
|
skip: (note) => onStep(key, 'skipped', note),
|
|
90
92
|
});
|
|
91
93
|
|
|
92
|
-
// ---
|
|
93
|
-
|
|
94
|
-
const acct = step('account');
|
|
94
|
+
// --- the pod --- (the session came from it; what is checked is that it has no account yet)
|
|
95
|
+
const podStep = step('pod');
|
|
95
96
|
if (!prog.pod) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
acct.ok(made.pod);
|
|
101
|
-
} else {
|
|
102
|
-
const brought = answers.pod.endsWith('/') ? answers.pod : answers.pod + '/';
|
|
103
|
-
acct.running('checking your pod');
|
|
104
|
-
const head = await fetch(brought, { method: 'HEAD' }).catch(() => null);
|
|
105
|
-
if (!head || head.status >= 400) throw new Error(`the pod at ${brought} did not answer (HTTP ${head?.status || 'no response'})`);
|
|
106
|
-
if (await resourceExists(fetch, actorUrlFor(brought))) throw new Error('The pod already hosts a FediPod account. If you want a second account, put it on a different pod.');
|
|
107
|
-
prog.pod = brought;
|
|
108
|
-
acct.skip('using the pod you brought');
|
|
109
|
-
}
|
|
97
|
+
podStep.running('checking your pod');
|
|
98
|
+
if (await resourceExists(session.fetch, actorUrlFor(pod))) throw new Error('The pod already hosts a FediPod account. If you want a second account, put it on a different pod.');
|
|
99
|
+
prog.pod = pod;
|
|
100
|
+
podStep.ok(pod);
|
|
110
101
|
} else {
|
|
111
|
-
|
|
102
|
+
podStep.ok(prog.pod); // resumed
|
|
112
103
|
}
|
|
113
|
-
const pod = prog.pod; const webId = prog.webId || null;
|
|
114
104
|
|
|
115
105
|
// WebFinger is answered only at a host root. A pod on a path of a shared
|
|
116
106
|
// host therefore takes its address at the gateway, whatever was asked; a
|
|
@@ -125,54 +115,13 @@ export async function signUp(answers, { onStep = () => {}, frontOrigin = null }
|
|
|
125
115
|
const actorUrl = actorUrlFor(pod); // where the documents live, always
|
|
126
116
|
const frontActor = fronted ? `${frontOrigin.replace(/\/$/, '')}/u/${handle}/ap/actor` : null;
|
|
127
117
|
|
|
128
|
-
// --- credential --- (a fresh attempt re-mints; a resumed one reuses it)
|
|
129
|
-
const cred = step('credential');
|
|
130
|
-
let credential;
|
|
131
|
-
if (!prog.credential) {
|
|
132
|
-
cred.running('minting a credential for this browser');
|
|
133
|
-
// accountToken is null when the account step was skipped (a resume), so
|
|
134
|
-
// mintCredential logs in fresh with email+password rather than reusing a
|
|
135
|
-
// token that a prior attempt may have let go stale.
|
|
136
|
-
credential = await mintCredential({ issuer, email, password, webId, podUrl: pod, accountToken });
|
|
137
|
-
credential.remotePod = pod;
|
|
138
|
-
credential.root = AP_ROOT; // the agent reads the container name from here
|
|
139
|
-
prog.credential = credential;
|
|
140
|
-
cred.ok();
|
|
141
|
-
} else {
|
|
142
|
-
credential = prog.credential;
|
|
143
|
-
cred.ok();
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// A pod-writing session for the remaining pod writes and the gateway proof.
|
|
147
|
-
const session = await makeDpopSession(credential);
|
|
148
|
-
|
|
149
|
-
// --- keys, encrypted, in an owner-only container on the pod ---
|
|
150
|
-
//
|
|
151
|
-
// The durable copy is on the pod, because a browser has no disk you can carry
|
|
152
|
-
// to the next machine. That copy used to be the bare record, on the argument
|
|
153
|
-
// that the pod's owner-only ACL is protection enough and the pod host already
|
|
154
|
-
// holds your data. It is not the same thing: your data is your data, and the
|
|
155
|
-
// signing key IS you — whoever holds it is you to every server in the
|
|
156
|
-
// fediverse, for as long as the key lives, and no ACL reaches the host itself.
|
|
157
|
-
// So it is wrapped under the account password first, and the host stores
|
|
158
|
-
// ciphertext. (Three documents already said this was happening. Now it is.)
|
|
159
|
-
//
|
|
160
|
-
// Two things in that order, deliberately: the ACL BEFORE the key. Writing the
|
|
161
|
-
// key first leaves a window where a pod whose root is world-readable serves it
|
|
162
|
-
// to anyone who asks, and a brought pod is exactly the case where that root
|
|
163
|
-
// may be public.
|
|
164
|
-
//
|
|
165
|
-
// The opened copy is kept in this browser (IndexedDB) so the worker can boot
|
|
166
|
-
// itself after an idle kill with nobody there to type a password. A browser
|
|
167
|
-
// that has no copy asks for the password once — see boot.mjs.
|
|
168
118
|
// Connect the gateway (fedipod.net) before the key is made, because a
|
|
169
119
|
// fronted identity's key is stamped with the gateway actor. Inbox-only: the
|
|
170
120
|
// actor advertises the door as its inbox and keeps its own ids. Fronted: the
|
|
171
121
|
// gateway answers WebFinger for @handle@front and serves the actor at its own
|
|
172
122
|
// address, rewriting reads onto the pod. Either way the door verifies each
|
|
173
123
|
// delivery and forwards the clean mail to the pod; the agent trusts the
|
|
174
|
-
// door's receipt. The
|
|
175
|
-
// password reaches it.
|
|
124
|
+
// door's receipt. The pod session itself proves the pod to the gateway.
|
|
176
125
|
let gateway = answers.gateway || prog.gateway || null;
|
|
177
126
|
if (frontOrigin && !gateway) {
|
|
178
127
|
const gw = step('gateway');
|
|
@@ -199,31 +148,46 @@ export async function signUp(answers, { onStep = () => {}, frontOrigin = null }
|
|
|
199
148
|
step('gateway').ok(); // resumed
|
|
200
149
|
}
|
|
201
150
|
|
|
151
|
+
// --- keys, in an owner-only container on the pod ---
|
|
152
|
+
//
|
|
153
|
+
// The durable copy is on the pod, because a browser has no disk you can carry
|
|
154
|
+
// to the next machine. It is stored as it is, behind the container's access
|
|
155
|
+
// rule: the same rule every private document on the pod lives under, so it
|
|
156
|
+
// is reachable through the pod's own login and by nobody else.
|
|
157
|
+
//
|
|
158
|
+
// Two things in that order, deliberately: the ACL BEFORE the key. Writing the
|
|
159
|
+
// key first leaves a window where a pod whose root is world-readable serves it
|
|
160
|
+
// to anyone who asks, and a brought pod is exactly the case where that root
|
|
161
|
+
// may be public.
|
|
162
|
+
//
|
|
163
|
+
// The opened copy is kept in this browser (IndexedDB) so the worker can boot
|
|
164
|
+
// itself after an idle kill with no read. A browser that has no copy reads
|
|
165
|
+
// the pod's with its session — see keys-browser.mjs.
|
|
166
|
+
const remote = new BrowserRemotePod(session, { webId, role: 'signup', log: () => {} });
|
|
202
167
|
const keysStep = step('keys');
|
|
203
168
|
let keys;
|
|
204
169
|
if (!prog.keysStored) {
|
|
205
|
-
keysStep.running('making your signing key and
|
|
170
|
+
keysStep.running('making your signing key and storing it on your pod');
|
|
206
171
|
keys = await generateKeys();
|
|
207
172
|
// One key, one actor (lib/keys.mjs): the actor the world knows, which for
|
|
208
173
|
// a fronted identity is the gateway's address for it.
|
|
209
174
|
keys.mintedFor = gateway?.frontActor || actorUrl;
|
|
210
|
-
const remote = new BrowserRemotePod(session, { webId: credential.webId, role: 'signup', log: () => {} });
|
|
211
175
|
// Owner-only, and THEN the key — one operation, so the order cannot be got
|
|
212
176
|
// wrong here or anywhere else. A pod that refuses the ACL write is not a
|
|
213
|
-
// pod this key may sit on
|
|
177
|
+
// pod this key may sit on.
|
|
214
178
|
try {
|
|
215
179
|
await podState.provisionKey(remote, {
|
|
216
180
|
stateUrl: `${pod}${AP_ROOT}ap-state/`,
|
|
217
181
|
keysUrl: keysDocFor(pod),
|
|
218
|
-
|
|
182
|
+
keys,
|
|
219
183
|
});
|
|
220
184
|
} catch (e) {
|
|
221
185
|
throw new Error(`could not store the signing key on the pod (${e.message}). `
|
|
222
|
-
+ `
|
|
186
|
+
+ `You are signed in as ${webId} — that WebID must own ${pod} and its ${AP_ROOT} must be writable by it.`);
|
|
223
187
|
}
|
|
224
|
-
// This browser's own opened copy, so the boot
|
|
225
|
-
//
|
|
226
|
-
//
|
|
188
|
+
// This browser's own opened copy, so the boot that follows needs no read.
|
|
189
|
+
// Best effort: a browser that refuses IndexedDB (private mode) reads the
|
|
190
|
+
// pod's copy on the way back in.
|
|
227
191
|
await cacheOpenedKeys(actorUrl, keys);
|
|
228
192
|
prog.keys = keys; prog.keysStored = true;
|
|
229
193
|
keysStep.ok();
|
|
@@ -233,46 +197,122 @@ export async function signUp(answers, { onStep = () => {}, frontOrigin = null }
|
|
|
233
197
|
}
|
|
234
198
|
|
|
235
199
|
const config = {
|
|
236
|
-
remotePod: pod, root: AP_ROOT, handle, name: handle, issuer:
|
|
200
|
+
remotePod: pod, root: AP_ROOT, handle, name: handle, issuer: String(session.issuer || '').replace(/\/+$/, ''),
|
|
237
201
|
createdAt: new Date().toISOString(),
|
|
238
202
|
...(gateway ? { gateway } : {}),
|
|
239
203
|
};
|
|
240
204
|
// Write the config to the pod (owner-only, beside the key) so a returning
|
|
241
205
|
// sign-in — which arrives with only an OIDC session — can read the account's
|
|
242
|
-
// config and key from the pod and boot
|
|
243
|
-
const cfgRemote = new BrowserRemotePod(session, { webId: credential.webId, role: 'signup', log: () => {} });
|
|
206
|
+
// config and key from the pod and boot.
|
|
244
207
|
try {
|
|
245
|
-
await podState.writeConfig(
|
|
208
|
+
await podState.writeConfig(remote, { state: `${pod}${AP_ROOT}ap-state/` }, config);
|
|
246
209
|
} catch (e) {
|
|
247
210
|
throw new Error(`could not store the config on the pod (${e.message})`);
|
|
248
211
|
}
|
|
249
212
|
|
|
250
|
-
// The credential was for sign-up, and sign-up is over. The agent runs on the
|
|
251
|
-
// Solid-OIDC session from here on and never needs it again, so leaving it
|
|
252
|
-
// alive would leave permanent full access to the pod in a key nothing holds.
|
|
253
|
-
// Best effort: a server that will not delete it is a credential the owner can
|
|
254
|
-
// still revoke from their pod's account page, and not a reason to fail a
|
|
255
|
-
// sign-up that otherwise worked.
|
|
256
|
-
const revoked = await revokeCredential({
|
|
257
|
-
resource: credential.resource, accountToken: credential.accountToken,
|
|
258
|
-
});
|
|
259
|
-
delete credential.accountToken; // never leaves this function
|
|
260
|
-
if (!revoked && credential.resource) {
|
|
261
|
-
onStep('credential', 'ok', 'this browser is ready (the setup credential could not be '
|
|
262
|
-
+ 'revoked automatically — you can remove it from your pod\'s account page)');
|
|
263
|
-
}
|
|
264
|
-
|
|
265
213
|
PROGRESS.delete(key); // finished — nothing left to resume
|
|
266
214
|
|
|
267
215
|
const host = gateway?.frontActor ? new URL(gateway.frontActor).host : new URL(pod).host;
|
|
268
216
|
return {
|
|
269
|
-
|
|
217
|
+
config, actorUrl, pod,
|
|
270
218
|
address: `@${handle}@${host}`,
|
|
271
219
|
// The opened keys, for booting the agent in THIS browser session right away.
|
|
272
|
-
// The durable copy on the pod is wrapped under the account password; this
|
|
273
|
-
// browser also holds an opened one in IndexedDB (above), which is what the
|
|
274
|
-
// worker reads. A fresh browser asks for the password once and makes its own.
|
|
275
220
|
keys,
|
|
276
221
|
keysPublic: { rsa: keys.rsa.publicPem, ed25519: keys.ed25519?.publicPem || null },
|
|
277
222
|
};
|
|
278
223
|
}
|
|
224
|
+
|
|
225
|
+
// ---- an account already on the pod ----
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* What the pod already holds: null when there is no FediPod account on it,
|
|
229
|
+
* else its config, the pod, and where its address lives — `frontHost` is
|
|
230
|
+
* the gateway's host for an address at a gateway, null for one on the pod.
|
|
231
|
+
*/
|
|
232
|
+
export async function readAccount(session) {
|
|
233
|
+
const pod = podBaseOfWebId(session.webId);
|
|
234
|
+
const remote = new BrowserRemotePod(session, { webId: session.webId, role: 'signup', log: () => {} });
|
|
235
|
+
const config = await podState.readConfig(remote, { state: `${pod}${AP_ROOT}ap-state/` }).catch(() => null);
|
|
236
|
+
if (!config) return null;
|
|
237
|
+
const frontActor = config.gateway?.frontActor || null;
|
|
238
|
+
let frontHost = null;
|
|
239
|
+
try { frontHost = frontActor ? new URL(frontActor).host : null; } catch { frontHost = null; }
|
|
240
|
+
let podHost = '';
|
|
241
|
+
try { podHost = new URL(config.remotePod || pod).host; } catch { podHost = ''; }
|
|
242
|
+
return { pod, config, frontActor, frontHost, address: `@${config.handle}@${frontHost || podHost}` };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Move an account whose address lives at another gateway to this one. The
|
|
247
|
+
* pod, its posts, its followers and its key all stay where they are; what
|
|
248
|
+
* changes is the gateway that answers for it. This half does the pod's
|
|
249
|
+
* part: attach here, rewrite the config to name this gateway, restamp the
|
|
250
|
+
* key. The agent's first boot from that config publishes under the new
|
|
251
|
+
* ids, and then tells the old gateway and the followers (gateway-move.mjs).
|
|
252
|
+
*
|
|
253
|
+
* answers: { handle } — the name here; the old one is the default.
|
|
254
|
+
* returns: { config, address, movedFrom }
|
|
255
|
+
*/
|
|
256
|
+
export async function moveIn(answers, { session, onStep = () => {}, frontOrigin = null } = {}) {
|
|
257
|
+
const { handle } = answers;
|
|
258
|
+
const bad = handleProblem(handle);
|
|
259
|
+
if (bad) throw new Error(bad);
|
|
260
|
+
if (!session?.webId || typeof session.fetch !== 'function') throw new Error('sign in at your pod first');
|
|
261
|
+
if (!frontOrigin) throw new Error('a move needs a gateway to move to, and this page has none');
|
|
262
|
+
const origin = frontOrigin.replace(/\/$/, '');
|
|
263
|
+
const step = (key) => ({ running: (note) => onStep(key, 'running', note), ok: (note) => onStep(key, 'ok', note) });
|
|
264
|
+
|
|
265
|
+
// --- the pod: the account that is there, and its key ---
|
|
266
|
+
const podStep = step('pod');
|
|
267
|
+
podStep.running('reading your account on your pod');
|
|
268
|
+
const here = await readAccount(session);
|
|
269
|
+
if (!here?.frontActor) throw new Error('this pod holds no account at another gateway to move here');
|
|
270
|
+
if (here.frontHost === new URL(origin).host) throw new Error('this account already lives at this gateway');
|
|
271
|
+
const { pod, config: old } = here;
|
|
272
|
+
const remote = new BrowserRemotePod(session, { webId: session.webId, role: 'signup', log: () => {} });
|
|
273
|
+
const urls = { state: `${pod}${AP_ROOT}ap-state/` };
|
|
274
|
+
const keys = await podState.readKeys(remote, urls);
|
|
275
|
+
if (!keys?.rsa) throw new Error('could not read this account\'s signing key on the pod');
|
|
276
|
+
if (isKeyEnvelope(keys)) throw new Error(`this account's key is still under a password from an earlier version — sign in once at ${here.frontHost} first, then come back`);
|
|
277
|
+
podStep.ok(here.address);
|
|
278
|
+
|
|
279
|
+
// --- this gateway: the name, then the attach, fronted ---
|
|
280
|
+
const gw = step('gateway');
|
|
281
|
+
gw.running(`taking your address at ${new URL(origin).host}`);
|
|
282
|
+
await assertFrontNameFree(origin, handle);
|
|
283
|
+
const res = await session.fetch(`${origin}/api/attach`, {
|
|
284
|
+
method: 'POST', headers: { 'content-type': 'application/json' },
|
|
285
|
+
body: JSON.stringify({ handle, podHome: `${pod}${AP_ROOT}`, actorUrl: actorUrlFor(pod), kind: old.kind || 'person', fronted: true }),
|
|
286
|
+
});
|
|
287
|
+
const d = await res.json().catch(() => ({}));
|
|
288
|
+
if (res.status !== 201 || !d.hmacSecret) throw new Error(`could not attach to this gateway (HTTP ${res.status}): ${d.error || ''}`);
|
|
289
|
+
const gateway = {
|
|
290
|
+
url: `${origin}/u/${handle}/ap/inbox/`, frontActor: String(d.frontActor || `${origin}/u/${handle}/ap/actor`),
|
|
291
|
+
hmacSecret: d.hmacSecret, mode: 'trust',
|
|
292
|
+
};
|
|
293
|
+
gw.ok();
|
|
294
|
+
|
|
295
|
+
// --- the pod again: the config names this gateway, the key is restamped ---
|
|
296
|
+
//
|
|
297
|
+
// The old actor becomes an alias, which is what a server checks on the new
|
|
298
|
+
// actor before it honours a Move. `movedFrom` is the pending half: the
|
|
299
|
+
// agent completes it after its first boot under the new ids.
|
|
300
|
+
const keysStep = step('keys');
|
|
301
|
+
keysStep.running('moving your key and account record to the new address');
|
|
302
|
+
const aliases = [...new Set([...(old.aliases || []), here.frontActor])];
|
|
303
|
+
const config = {
|
|
304
|
+
...old, handle, gateway, aliases,
|
|
305
|
+
movedFrom: { actor: here.frontActor, gateway: new URL(here.frontActor).origin, handle: old.handle, at: new Date().toISOString() },
|
|
306
|
+
};
|
|
307
|
+
keys.mintedFor = gateway.frontActor; // one key, one actor (lib/keys.mjs)
|
|
308
|
+
try {
|
|
309
|
+
await podState.writeKeys(remote, urls, keys);
|
|
310
|
+
await podState.writeConfig(remote, urls, config);
|
|
311
|
+
} catch (e) {
|
|
312
|
+
throw new Error(`could not update your account on the pod (${e.message})`);
|
|
313
|
+
}
|
|
314
|
+
await cacheOpenedKeys(actorUrlFor(pod), keys);
|
|
315
|
+
keysStep.ok();
|
|
316
|
+
|
|
317
|
+
return { config, address: `@${handle}@${new URL(origin).host}`, movedFrom: config.movedFrom };
|
|
318
|
+
}
|
package/web/app/sw-src.mjs
CHANGED
|
@@ -157,7 +157,7 @@ async function serve(request, url) {
|
|
|
157
157
|
// Boot on demand from the stored session, so the client works even when the
|
|
158
158
|
// worker was restarted (idle-killed) and nobody posted 'boot' this time.
|
|
159
159
|
if (!agent) { try { await ensureBooting(); } catch { /* no session → 503 below */ } }
|
|
160
|
-
if (!agent) return json(503, { error: '
|
|
160
|
+
if (!agent) return json(503, { error: 'not signed in on this browser — sign in at the front page' });
|
|
161
161
|
// Bytes, not text. A multipart upload is binary — read as text it comes back
|
|
162
162
|
// through a UTF-8 round trip that replaces every byte that is not valid UTF-8,
|
|
163
163
|
// which is most of a JPEG, so the boundary search found nothing and every
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// update.js — a fedipod.net page notices a newer build and reloads itself once.
|
|
2
|
+
//
|
|
3
|
+
// The page carries the version it was staged with (a meta tag); the site
|
|
4
|
+
// answers its current version at the handle probe. When they differ, the
|
|
5
|
+
// worker is asked to update — it installs and takes over at once (sw-src.mjs
|
|
6
|
+
// skips waiting and claims) — and the page reloads when the new worker takes
|
|
7
|
+
// over, or after a moment if only the pages changed. A page with text in a
|
|
8
|
+
// composer is not reloaded from under it: it shows a line with a button.
|
|
9
|
+
(() => {
|
|
10
|
+
const mine = document.querySelector('meta[name="fedipod-version"]')?.content;
|
|
11
|
+
// The build this page was staged from. A version changes when a release is
|
|
12
|
+
// cut; a build changes every time the site is deployed, which is what a
|
|
13
|
+
// reader with the page open actually needs to hear about.
|
|
14
|
+
const myBuild = document.querySelector('meta[name="fedipod-build"]')?.content || null;
|
|
15
|
+
if (!mine) return;
|
|
16
|
+
const loadedAt = Date.now();
|
|
17
|
+
let acted = false;
|
|
18
|
+
const drafting = () => [...document.querySelectorAll('textarea')].some((t) => t.value.trim());
|
|
19
|
+
const banner = () => {
|
|
20
|
+
if (document.getElementById('fedipod-update')) return;
|
|
21
|
+
const d = document.createElement('div');
|
|
22
|
+
d.id = 'fedipod-update';
|
|
23
|
+
d.setAttribute('role', 'status');
|
|
24
|
+
d.style.cssText = 'position:fixed;left:0;right:0;bottom:0;z-index:2147483647;padding:1rem 1.25rem;'
|
|
25
|
+
+ 'font:1rem/1.5 Arial, Helvetica, sans-serif;background:#1d1d1d;color:#fff;display:flex;gap:1rem;align-items:center;justify-content:center;flex-wrap:wrap';
|
|
26
|
+
const t = document.createElement('span');
|
|
27
|
+
t.textContent = 'A new version of FediPod is ready. Reload when you have finished writing.';
|
|
28
|
+
const b = document.createElement('button');
|
|
29
|
+
b.type = 'button'; b.textContent = 'Reload now';
|
|
30
|
+
b.style.cssText = 'font:inherit;padding:.5rem 1rem;border-radius:.5rem;border:1px solid #fff;background:#fff;color:#1d1d1d;cursor:pointer';
|
|
31
|
+
b.addEventListener('click', () => location.reload());
|
|
32
|
+
d.append(t, b);
|
|
33
|
+
document.body.append(d);
|
|
34
|
+
};
|
|
35
|
+
const go = () => {
|
|
36
|
+
if (acted) return;
|
|
37
|
+
acted = true;
|
|
38
|
+
if (drafting()) { acted = false; banner(); return; }
|
|
39
|
+
location.reload();
|
|
40
|
+
};
|
|
41
|
+
// A worker taking over a page that has been open a while is a new build;
|
|
42
|
+
// in a page's first seconds it is the boot, not an update.
|
|
43
|
+
if ('serviceWorker' in navigator) {
|
|
44
|
+
navigator.serviceWorker.addEventListener('controllerchange', () => { if (Date.now() - loadedAt > 5000) go(); });
|
|
45
|
+
}
|
|
46
|
+
const check = async () => {
|
|
47
|
+
try {
|
|
48
|
+
// The build first: it moves with every deploy. The version is the
|
|
49
|
+
// fallback for a page staged before builds were stamped.
|
|
50
|
+
if (myBuild) {
|
|
51
|
+
const b = await fetch('/build.json', { cache: 'no-store', headers: { accept: 'application/json' } })
|
|
52
|
+
.then(x => (x.ok ? x.json() : null)).catch(() => null);
|
|
53
|
+
if (b?.build && b.build !== myBuild) { go(); return; }
|
|
54
|
+
}
|
|
55
|
+
const r = await fetch('/api/handle?handle=__probe__', { cache: 'no-store', headers: { accept: 'application/json' } });
|
|
56
|
+
const v = (await r.json())?.version;
|
|
57
|
+
if (!v || v === mine) return;
|
|
58
|
+
const reg = await navigator.serviceWorker.getRegistration();
|
|
59
|
+
await reg?.update().catch(() => {});
|
|
60
|
+
setTimeout(go, 4000); // a pages-only change: no new worker will take over
|
|
61
|
+
} catch { /* offline, or the site did not answer: try again later */ }
|
|
62
|
+
};
|
|
63
|
+
// Often enough that a reader watching a page being worked on sees it.
|
|
64
|
+
setTimeout(check, 5000);
|
|
65
|
+
setInterval(check, 60 * 1000);
|
|
66
|
+
})();
|
package/web/front/admin.html
CHANGED
|
@@ -4,10 +4,11 @@
|
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
6
|
<title>FediPod — accounts on this server</title>
|
|
7
|
+
<link rel="icon" href="/icons/gateway.svg" type="image/svg+xml">
|
|
7
8
|
<style>
|
|
8
9
|
:root { color-scheme: light dark; --line:#d9d4c7; --dim:#45443d; --accent:#6a8a5a; }
|
|
9
10
|
@media (prefers-color-scheme: dark) { :root { --dim:#c2c0b4; } }
|
|
10
|
-
body { font:
|
|
11
|
+
body { font: 20px/1.6 Arial, Helvetica, sans-serif; max-width: 44rem; margin: 2rem auto; padding: 0 1.1rem; }
|
|
11
12
|
h1 { font-size: 1.7rem; margin: 0 0 .2rem; }
|
|
12
13
|
p.lede { color: var(--dim); margin-top: 0; }
|
|
13
14
|
label { display: block; font-weight: 600; margin: .8rem 0 .2rem; }
|
|
@@ -27,7 +28,9 @@
|
|
|
27
28
|
<body>
|
|
28
29
|
<h1>Accounts on this server</h1>
|
|
29
30
|
<p class="lede">Every name this server answers for: accounts whose identity lives here, and
|
|
30
|
-
accounts that only use it as their gateway. Sign in as the server's admin to see them
|
|
31
|
+
accounts that only use it as their gateway. Sign in as the server's admin to see them.
|
|
32
|
+
<a href="/notices">Notices</a> — what every account sees behind the bell in its bar — are
|
|
33
|
+
written on their own page.</p>
|
|
31
34
|
|
|
32
35
|
<form id="roster-form">
|
|
33
36
|
<p class="field">
|
|
@@ -48,6 +51,5 @@ accounts that only use it as their gateway. Sign in as the server's admin to see
|
|
|
48
51
|
</table>
|
|
49
52
|
|
|
50
53
|
<script type="module" src="/admin.js"></script>
|
|
51
|
-
<i>(cc) 4.0 By, Jeff Zucker, 2026</i>
|
|
52
54
|
</body>
|
|
53
55
|
</html>
|
|
@@ -4,10 +4,11 @@
|
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
6
|
<title>FediPod — access the Fediverse and ATProto from a Solid pod</title>
|
|
7
|
+
<link rel="icon" href="/icons/gateway.svg" type="image/svg+xml">
|
|
7
8
|
<style>
|
|
8
9
|
:root { color-scheme: light dark; --line:#d9d4c7; --dim:#45443d; --accent:#6a8a5a; }
|
|
9
10
|
@media (prefers-color-scheme: dark) { :root { --dim:#c2c0b4; } }
|
|
10
|
-
body { font:
|
|
11
|
+
body { font: 20px/1.6 Arial, Helvetica, sans-serif; max-width: 44rem; margin: 2rem auto; padding: 0 1.1rem; }
|
|
11
12
|
h1 { font-size: 1.7rem; margin: 0 0 .2rem; }
|
|
12
13
|
h2 { font-size: 1.2rem; margin: 1.4rem 0 .4rem; }
|
|
13
14
|
p.lede { color: var(--dim); margin-top: 0; }
|
|
@@ -30,6 +31,5 @@ FediPod is experimental and free to use. See the <a href="https://github.com/je
|
|
|
30
31
|
</p>
|
|
31
32
|
|
|
32
33
|
<script src="/new-account.js"></script>
|
|
33
|
-
<i style="text-align:right">(cc) 4.0 By, Jeff Zucker, 2026</i>
|
|
34
34
|
</body>
|
|
35
35
|
</html>
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>FediPod — notices</title>
|
|
7
|
+
<link rel="icon" href="/icons/gateway.svg" type="image/svg+xml">
|
|
8
|
+
<style>
|
|
9
|
+
:root { color-scheme: light dark; --line:#d9d4c7; --dim:#45443d; --accent:#6a8a5a; --danger:#b00020; }
|
|
10
|
+
@media (prefers-color-scheme: dark) { :root { --dim:#c2c0b4; --danger:#ff8a9a; } }
|
|
11
|
+
body { font: 20px/1.6 Arial, Helvetica, sans-serif; max-width: 44rem; margin: 2rem auto; padding: 0 1.1rem; }
|
|
12
|
+
h1 { font-size: 1.7rem; margin: 0 0 .2rem; }
|
|
13
|
+
h2 { font-size: 1.25rem; margin: 1.6rem 0 .4rem; }
|
|
14
|
+
p.lede { color: var(--dim); margin-top: 0; }
|
|
15
|
+
label { display: block; font-weight: 600; margin: .8rem 0 .2rem; }
|
|
16
|
+
input[type=url], input[type=text], textarea { width: 100%; padding: .5rem .6rem; font: inherit;
|
|
17
|
+
border: 1px solid var(--line); border-radius: 8px; box-sizing: border-box; }
|
|
18
|
+
textarea { min-height: 9rem; resize: vertical; }
|
|
19
|
+
.hint { color: var(--dim); font-size: .95rem; }
|
|
20
|
+
.primary { background: var(--accent); color: #fff; border: none; border-radius: 9px;
|
|
21
|
+
padding: .6rem 1.1rem; font: inherit; cursor: pointer; }
|
|
22
|
+
.primary[disabled] { opacity: .5; cursor: not-allowed; }
|
|
23
|
+
button { font: inherit; cursor: pointer; }
|
|
24
|
+
button.danger { color: var(--danger); background: none; border: 1px solid var(--danger); border-radius: 9px; padding: .4rem .8rem; }
|
|
25
|
+
[hidden] { display: none !important; }
|
|
26
|
+
ul.notices { list-style: none; padding: 0; margin: 1rem 0; }
|
|
27
|
+
ul.notices li { padding: .8rem 0; border-bottom: 1px solid var(--line); }
|
|
28
|
+
ul.notices .title { font-weight: 600; }
|
|
29
|
+
ul.notices .when { color: var(--dim); font-size: .95rem; margin-left: .6rem; }
|
|
30
|
+
ul.notices .body { white-space: pre-wrap; margin: .3rem 0 .6rem; }
|
|
31
|
+
ul.notices .actions button { margin-right: .6rem; }
|
|
32
|
+
</style>
|
|
33
|
+
</head>
|
|
34
|
+
<body>
|
|
35
|
+
<h1>Notices</h1>
|
|
36
|
+
<p class="lede">What every account on this server sees behind the bell in its bar. Sign in as the
|
|
37
|
+
server's admin to write, change or remove them. <a href="/roster">Back to the roster.</a></p>
|
|
38
|
+
|
|
39
|
+
<form id="notices-form">
|
|
40
|
+
<p class="field">
|
|
41
|
+
<label for="notices-issuer">Your identity provider</label>
|
|
42
|
+
<input type="url" id="notices-issuer" autocomplete="off" value="https://solidcommunity.net">
|
|
43
|
+
</p>
|
|
44
|
+
<p class="actions">
|
|
45
|
+
<button type="submit" id="notices-signin" class="primary">Sign in & manage notices</button>
|
|
46
|
+
</p>
|
|
47
|
+
<p class="hint" id="notices-note" hidden></p>
|
|
48
|
+
</form>
|
|
49
|
+
|
|
50
|
+
<section id="notices-editor" hidden>
|
|
51
|
+
<h2 id="editor-title">New notice</h2>
|
|
52
|
+
<form id="editor-form">
|
|
53
|
+
<label for="notice-title">Title</label>
|
|
54
|
+
<input type="text" id="notice-title" maxlength="120" autocomplete="off" required>
|
|
55
|
+
<label for="notice-body">Text</label>
|
|
56
|
+
<textarea id="notice-body" maxlength="4000" required></textarea>
|
|
57
|
+
<p class="hint">Plain text. A blank line starts a new paragraph; a web address becomes a link.</p>
|
|
58
|
+
<p class="actions">
|
|
59
|
+
<button type="submit" id="notice-save" class="primary">Publish</button>
|
|
60
|
+
<button type="button" id="notice-cancel" hidden>Cancel</button>
|
|
61
|
+
</p>
|
|
62
|
+
</form>
|
|
63
|
+
<h2>Published</h2>
|
|
64
|
+
<ul class="notices" id="notices-list"></ul>
|
|
65
|
+
</section>
|
|
66
|
+
|
|
67
|
+
<script type="module" src="/notices.js"></script>
|
|
68
|
+
</body>
|
|
69
|
+
</html>
|