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/agent.mjs
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// the signing key comes from WebCrypto (keys-browser), and delivery goes through
|
|
6
6
|
// the relay (deliver-relay). Everything between — wire, the store, the
|
|
7
7
|
// publisher, intake, the Mastodon facade — is lib/, unchanged.
|
|
8
|
+
import { kvGet, kvPut } from './idb-kv.mjs';
|
|
8
9
|
import { apUrls } from '../../lib/core/wire.mjs';
|
|
9
10
|
import * as containers from '../../lib/pod/containers.mjs';
|
|
10
11
|
import * as podState from '../../lib/pod/state.mjs';
|
|
@@ -19,9 +20,10 @@ import { TagFeed } from '../../lib/connections/tagfeed.mjs';
|
|
|
19
20
|
import { makeDpopSession } from './pod-auth.mjs';
|
|
20
21
|
import { BrowserRemotePod } from './pod-remote.mjs';
|
|
21
22
|
import { importSigningKey, loadKeysFromPod, cacheOpenedKeys, podActorOf } from './keys-browser.mjs';
|
|
22
|
-
import { generateKeys
|
|
23
|
+
import { generateKeys } from './keystore.mjs';
|
|
23
24
|
import { RelayDeliverer, doorKeyOf } from './deliver-relay.mjs';
|
|
24
25
|
import { AdminFacade } from './admin-facade.mjs';
|
|
26
|
+
import { completeGatewayMove } from './gateway-move.mjs';
|
|
25
27
|
import { BrowserAtproto } from './atproto-browser.mjs';
|
|
26
28
|
import { BskyFeed } from '../../lib/connections/bskyfeed.mjs';
|
|
27
29
|
import { BrowserFediAccounts } from './fediacct-browser.mjs';
|
|
@@ -49,6 +51,69 @@ const originAuthorities = (host) => ({
|
|
|
49
51
|
wsAuthorities() { return []; }, // fetch-only: this build serves no socket
|
|
50
52
|
});
|
|
51
53
|
|
|
54
|
+
// Why an account did not open, in words for the person it happened to, and a
|
|
55
|
+
// `code` the sign-in page turns into a title and the one button that leads
|
|
56
|
+
// somewhere (boot.mjs). Every one of these used to be the same sentence —
|
|
57
|
+
// "no account config on this pod — sign up first" — which named the wrong
|
|
58
|
+
// cause, offered the wrong cure, and left the reader with nothing to press.
|
|
59
|
+
//
|
|
60
|
+
// Each message says three things and stops: what the pod did, what that means
|
|
61
|
+
// for the account, what to do about it. The pod's own wording is carried only
|
|
62
|
+
// where it adds something the status code does not.
|
|
63
|
+
async function accountNotRead({ unread, podBase, state, webId, actorUrl = null, fetchImpl = globalThis.fetch }) {
|
|
64
|
+
const host = (() => { try { return new URL(podBase).host; } catch { return podBase; } })();
|
|
65
|
+
const of = (code, message) => { const e = new Error(message); e.code = code; return e; };
|
|
66
|
+
if (!unread) {
|
|
67
|
+
// A pod that holds the account's actor and no record is an account a
|
|
68
|
+
// DeviceAgent runs: the record lives on the device, and only the device
|
|
69
|
+
// can open it. Said so, rather than "there is no account".
|
|
70
|
+
const actor = actorUrl
|
|
71
|
+
? await fetchImpl(actorUrl, { headers: { accept: 'application/activity+json' } }).then((r) => (r.ok ? r.json() : null)).catch(() => null)
|
|
72
|
+
: null;
|
|
73
|
+
if (actor?.preferredUsername) {
|
|
74
|
+
const at = (() => { try { return `@${actor.preferredUsername}@${new URL(actor.id || actorUrl).host}`; } catch { return actor.preferredUsername; } })();
|
|
75
|
+
return of('device-account', `${host} holds the account ${at}, but not its record: this account is run by a`
|
|
76
|
+
+ ` DeviceAgent, and its record lives on that device. Open it from that device's admin page, not here.`
|
|
77
|
+
+ ` The sign-in used here was ${webId}.`);
|
|
78
|
+
}
|
|
79
|
+
return of('no-account', `${host} answered, and there is no FediPod account in it.`
|
|
80
|
+
+ ` Sign in with the pod that holds your account, or make an account in this one —`
|
|
81
|
+
+ ` both start from the sign-in page. The sign-in used here was ${webId}.`);
|
|
82
|
+
}
|
|
83
|
+
const status = Number((/HTTP (\d{3})/u.exec(unread.message) || [])[1]) || 0;
|
|
84
|
+
if (status === 401 || status === 403) {
|
|
85
|
+
// The page renews a refused sign-in by itself, and takes the person to
|
|
86
|
+
// their pod's login when the renewal is refused too (boot.mjs). By the
|
|
87
|
+
// time anybody reads this, both have already been tried.
|
|
88
|
+
return of('sign-in-refused', `${host} would not let this sign-in read your account — it answered`
|
|
89
|
+
+ ` "not allowed" (HTTP ${status}) — and renewing the sign-in did not change its mind.`
|
|
90
|
+
+ ` Your account and everything in it are untouched. Signing in at ${host} again is the`
|
|
91
|
+
+ ` one thing that helps.`);
|
|
92
|
+
}
|
|
93
|
+
if (status === 429) {
|
|
94
|
+
return of('pod-busy', `${host} is telling this browser it has asked for too much too quickly`
|
|
95
|
+
+ ` (HTTP 429), so your account could not be read. Nothing is wrong with your account, and`
|
|
96
|
+
+ ` nothing here needs doing: this page is waiting the throttle out and will ask again by`
|
|
97
|
+
+ ` itself. While a pod is rate-limiting you, only time helps.`);
|
|
98
|
+
}
|
|
99
|
+
if (status >= 500) {
|
|
100
|
+
return of('pod-error', `${host} broke while handing your account over (HTTP ${status}).`
|
|
101
|
+
+ ` That is the pod's own failure, not your account's. This page asks again by itself in`
|
|
102
|
+
+ ` a moment; if it keeps saying this, the pod is down and there is nothing to do here.`);
|
|
103
|
+
}
|
|
104
|
+
if (status === 404) {
|
|
105
|
+
return of('no-account-here', `${host} says there is nothing at ${state} (HTTP 404).`
|
|
106
|
+
+ ` Either no FediPod account has ever been set up in this pod, or you signed in with a`
|
|
107
|
+
+ ` different pod than the one holding yours. Sign in with that one, or make an account`
|
|
108
|
+
+ ` in this one — both start from the sign-in page.`);
|
|
109
|
+
}
|
|
110
|
+
// No status at all: the request never got an answer. The pod's own words are
|
|
111
|
+
// the only thing that says anything here, so they come along.
|
|
112
|
+
return of('pod-unreachable', `This browser could not reach ${host} to read your account`
|
|
113
|
+
+ ` (${unread.message}). Your account is still there. This page tries again by itself;`
|
|
114
|
+
+ ` if it keeps saying this, check that you are online and that the pod is up.`);
|
|
115
|
+
}
|
|
116
|
+
|
|
52
117
|
export class BrowserAgent {
|
|
53
118
|
constructor({ log = console.log } = {}) {
|
|
54
119
|
this.log = log;
|
|
@@ -72,10 +137,35 @@ export class BrowserAgent {
|
|
|
72
137
|
return true;
|
|
73
138
|
}
|
|
74
139
|
|
|
140
|
+
// One call to the gateway's owner API, proved with the pod session. What
|
|
141
|
+
// comes back on 200 or 410 is the account's standing there, kept for the
|
|
142
|
+
// manage page; anything else is logged and forgotten.
|
|
143
|
+
async tellGateway(what, body) {
|
|
144
|
+
if (!this.gatewayApi) return null;
|
|
145
|
+
let res;
|
|
146
|
+
try {
|
|
147
|
+
res = await this.sessionFetch(`${this.gatewayApi}/${what}`, {
|
|
148
|
+
method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body),
|
|
149
|
+
});
|
|
150
|
+
} catch (e) { this.log(`gateway ${what}: ${e.message}`); return null; }
|
|
151
|
+
const json = await res.json().catch(() => ({}));
|
|
152
|
+
const out = { status: res.status, ...json };
|
|
153
|
+
if (res.status === 200 || res.status === 410) this.gatewayStanding = out;
|
|
154
|
+
else this.log(`gateway ${what}: ${res.status} ${json.error || ''}`);
|
|
155
|
+
return out;
|
|
156
|
+
}
|
|
157
|
+
openAtGateway() { return this.tellGateway('open', { handle: this.doorKey }); }
|
|
158
|
+
pauseAtGateway(paused) { return this.tellGateway('pause', { handle: this.doorKey, paused: !!paused }); }
|
|
159
|
+
closeAtGateway() { return this.tellGateway('close', { handle: this.doorKey, confirm: true }); }
|
|
160
|
+
static OPEN_EVERY_MS = 60 * 60_000;
|
|
161
|
+
|
|
75
162
|
// Become the active agent: renew the lease, then start what a viewer skips —
|
|
76
163
|
// publish the face, drain the inbox, run the mirrors.
|
|
77
164
|
async goActive() {
|
|
78
165
|
this.viewer = false;
|
|
166
|
+
// Reading here is being here: the gateway hears so once an hour.
|
|
167
|
+
clearInterval(this._openTimer);
|
|
168
|
+
this._openTimer = setInterval(() => { this.openAtGateway(); }, BrowserAgent.OPEN_EVERY_MS);
|
|
79
169
|
this.lease.onLost = () => this.demote();
|
|
80
170
|
this.lease.startRenewal();
|
|
81
171
|
try {
|
|
@@ -94,6 +184,9 @@ export class BrowserAgent {
|
|
|
94
184
|
this.deliverer?.startQueue?.();
|
|
95
185
|
await this.publisher.publishProfile();
|
|
96
186
|
await this.store.flush?.();
|
|
187
|
+
// An address that just moved here from another gateway: the new
|
|
188
|
+
// actor is published, so the old gateway and the followers can be told.
|
|
189
|
+
await completeGatewayMove(this).catch((e) => this.log(`gateway move: ${e.message}`));
|
|
97
190
|
await this.intake.start();
|
|
98
191
|
this.startBsky();
|
|
99
192
|
this.startAccts();
|
|
@@ -111,6 +204,7 @@ export class BrowserAgent {
|
|
|
111
204
|
this.viewer = true;
|
|
112
205
|
this.log('another device took over — read-only here');
|
|
113
206
|
this.lease.stopRenewal();
|
|
207
|
+
clearInterval(this._openTimer); this._openTimer = null;
|
|
114
208
|
this.intake?.stop?.();
|
|
115
209
|
// The delivery queue as well. Its timer starts in the Deliverer's
|
|
116
210
|
// constructor and nothing here ever switched it off, so a demoted device
|
|
@@ -160,6 +254,22 @@ export class BrowserAgent {
|
|
|
160
254
|
* - { credential, keysRecord, config }: a client-credential session with the
|
|
161
255
|
* material in hand (the offline test path).
|
|
162
256
|
*/
|
|
257
|
+
// This browser's name for itself, minted once and kept. Not an identity and
|
|
258
|
+
// not a secret — it says only "the same browser as last time" to the lease.
|
|
259
|
+
// If storage cannot be reached, a fresh one is honest: this browser cannot
|
|
260
|
+
// prove it is the one that held the lease, so it should not claim to be.
|
|
261
|
+
async deviceId() {
|
|
262
|
+
try {
|
|
263
|
+
const had = await kvGet('device-id');
|
|
264
|
+
if (had) return had;
|
|
265
|
+
const made = crypto.randomUUID();
|
|
266
|
+
await kvPut('device-id', made);
|
|
267
|
+
return made;
|
|
268
|
+
} catch {
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
163
273
|
async boot({ oidc, credential, keysRecord, config, frontOrigin }) {
|
|
164
274
|
let session; let webId; let remotePod;
|
|
165
275
|
if (oidc) {
|
|
@@ -173,6 +283,9 @@ export class BrowserAgent {
|
|
|
173
283
|
remotePod = credential.remotePod;
|
|
174
284
|
}
|
|
175
285
|
this.webId = webId;
|
|
286
|
+
// The pod session's own fetch, for the gateway APIs that take it as
|
|
287
|
+
// proof of the pod (attach, move) — the transport below is for the pod.
|
|
288
|
+
this.sessionFetch = session.fetch;
|
|
176
289
|
const root = (config && config.root) || 'fedipod/';
|
|
177
290
|
this.remote = new BrowserRemotePod(session, { webId, log: this.log });
|
|
178
291
|
// Pod-native for now: the state store below is read with these, and only
|
|
@@ -186,10 +299,18 @@ export class BrowserAgent {
|
|
|
186
299
|
// The Node agent has always passed remote.fetch here.
|
|
187
300
|
const podFetch = (u, i) => this.remote.fetch(u, i);
|
|
188
301
|
this.store = new PodStore({ storage: new HttpStorage(this.urls.state, podFetch), log: this.log });
|
|
189
|
-
|
|
302
|
+
// A read that FAILED is not an account that is not there. load() already
|
|
303
|
+
// tells those apart — a missing container, a refused token, a pod saying
|
|
304
|
+
// "too many requests" each come back with their own reason — and throwing
|
|
305
|
+
// the reason away turned every one of them into "sign up first", said to
|
|
306
|
+
// somebody who had just signed in and whose account was sitting on the pod
|
|
307
|
+
// unread. Keep it: below is the only place that can tell whether it
|
|
308
|
+
// mattered, because a sign-up hands its config in and needs no read at all.
|
|
309
|
+
let unread = null;
|
|
310
|
+
await this.store.load().catch((e) => { unread = e; });
|
|
190
311
|
// Config: handed in on sign-up, or read from the pod on a returning sign-in.
|
|
191
312
|
const cfg = config || this.store.getConfig();
|
|
192
|
-
if (!cfg) throw
|
|
313
|
+
if (!cfg) throw await accountNotRead({ unread, podBase: remotePod, state: this.urls.state, webId, actorUrl: this.urls.actor });
|
|
193
314
|
// `root` is written INTO the config, not just used above. The Publisher
|
|
194
315
|
// builds its own urls from `config.root` (publisher.mjs), and a config
|
|
195
316
|
// without one falls to the Node default — so an account set up elsewhere
|
|
@@ -227,6 +348,28 @@ export class BrowserAgent {
|
|
|
227
348
|
onGone: () => this.publisher.publishCollections({ followers: true }),
|
|
228
349
|
});
|
|
229
350
|
|
|
351
|
+
// The gateway this account's mail comes through, and the account's own
|
|
352
|
+
// standing there — paused, closed, how much arrived unread (front-core:
|
|
353
|
+
// accounts that go quiet). The gateway is told on every sign-in and
|
|
354
|
+
// hourly while this device is active: that is what keeps an account
|
|
355
|
+
// somebody reads from counting as a quiet one. Only a closed address
|
|
356
|
+
// stops the boot; a gateway that cannot be reached is no reason to
|
|
357
|
+
// refuse a sign-in.
|
|
358
|
+
this.gatewayApi = null;
|
|
359
|
+
try { if (config.gateway?.url) this.gatewayApi = `${new URL(config.gateway.url).origin}/api`; } catch { this.gatewayApi = null; }
|
|
360
|
+
this.doorKey = doorKeyOf(config.gateway?.url) || config.handle;
|
|
361
|
+
this.gatewayStanding = null;
|
|
362
|
+
const standing = await this.openAtGateway();
|
|
363
|
+
if (standing?.status === 410) {
|
|
364
|
+
const host = (() => { try { return new URL(this.gatewayApi).host; } catch { return 'the gateway'; } })();
|
|
365
|
+
const address = this.doorKey.includes('@') ? `@${this.doorKey}` : `@${this.doorKey}@${host}`;
|
|
366
|
+
const why = standing.closedBy === 'owner' ? 'you closed it' : 'nothing opened it for six months';
|
|
367
|
+
const e = new Error(`${address} is closed at ${host}: ${why}, and a closed address does not come back.`
|
|
368
|
+
+ ` Everything on your pod is untouched. The sign-in used here was ${webId}.`);
|
|
369
|
+
e.code = 'address-closed';
|
|
370
|
+
throw e;
|
|
371
|
+
}
|
|
372
|
+
|
|
230
373
|
this.publisher = new Publisher({
|
|
231
374
|
config: this.store.getConfig(), remote: this.remote, store: this.store,
|
|
232
375
|
deliverer: this.deliverer, publicKeyPem: keys.rsaPublicPem, assertionKey: null, log: this.log,
|
|
@@ -263,7 +406,13 @@ export class BrowserAgent {
|
|
|
263
406
|
// one browser/device may ACT on a pod at a time — a later arrival runs
|
|
264
407
|
// read-only until the owner acts on it and it takes over. Written with fresh
|
|
265
408
|
// fetches, never the cached store. Passed into Intake so the drain checks it.
|
|
266
|
-
this.lease = new Lease({ url: this.urls.state + 'lease.json', fetchImpl: podFetch, log: this.log
|
|
409
|
+
this.lease = new Lease({ url: this.urls.state + 'lease.json', fetchImpl: podFetch, log: this.log,
|
|
410
|
+
// Kept on this origin, so THIS browser is one holder however many times
|
|
411
|
+
// its worker is killed and restarted. Without it every restart was a new
|
|
412
|
+
// holder: the old lease still had minutes to run, so the browser found
|
|
413
|
+
// its own account "active on another device" and asked to take it over.
|
|
414
|
+
// Switching between the clients did it every time, being a navigation.
|
|
415
|
+
id: await this.deviceId() });
|
|
267
416
|
// The client-to-server dispatcher, here only for what the Gateway's
|
|
268
417
|
// outbox door takes on the owner's behalf: the browser answers no
|
|
269
418
|
// /ap/outbox of its own.
|
|
@@ -377,24 +526,11 @@ export class BrowserAgent {
|
|
|
377
526
|
// actor so the new public key is on the wire. The old key stops signing the
|
|
378
527
|
// moment this returns — same one-way change as the Node agent's rotateKey
|
|
379
528
|
// (run-agent.mjs).
|
|
380
|
-
|
|
381
|
-
// The pod's copy is wrapped, so this needs the account password. There is
|
|
382
|
-
// nowhere to get it from without asking: the worker boots from a stored
|
|
383
|
-
// session and holds no password, and caching one to save a prompt on a
|
|
384
|
-
// once-in-a-while action would put the account password in storage to avoid
|
|
385
|
-
// typing it. So the caller supplies it, and rotating without one is refused
|
|
386
|
-
// rather than quietly writing a bare key back where a wrapped one was.
|
|
387
|
-
async rotateKey({ password } = {}) {
|
|
388
|
-
if (!password) {
|
|
389
|
-
const e = new Error('rotating the signing key needs your account password — '
|
|
390
|
-
+ 'it is what the new key is locked under on the pod');
|
|
391
|
-
e.code = 'key-password-needed';
|
|
392
|
-
throw e;
|
|
393
|
-
}
|
|
529
|
+
async rotateKey() {
|
|
394
530
|
const before = this.publisher.publicKeyPem;
|
|
395
531
|
const rec = await generateKeys();
|
|
396
532
|
rec.mintedFor = this.urls.actor; // one key, one actor (lib/keys.mjs)
|
|
397
|
-
await podState.
|
|
533
|
+
await podState.writeKeys(this.remote, this.urls, rec);
|
|
398
534
|
const keys = await cacheOpenedKeys(podActorOf(this.urls), rec);
|
|
399
535
|
this.publisher.publicKeyPem = keys.rsaPublicPem;
|
|
400
536
|
this.deliverer.rsaPrivate = keys.rsaPrivate;
|
|
@@ -518,3 +654,5 @@ export class BrowserAgent {
|
|
|
518
654
|
return { ...moved, ...r, snapshot: following.length };
|
|
519
655
|
}
|
|
520
656
|
}
|
|
657
|
+
|
|
658
|
+
export { accountNotRead };
|
|
@@ -0,0 +1,56 @@
|
|
|
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
|
+
<!-- The remote-follow door, at the path every Fediverse server sends its
|
|
7
|
+
readers to: /authorize_interaction?uri=<who>. Another server's Follow
|
|
8
|
+
button asks its reader which server they are on; when they say this one,
|
|
9
|
+
that server looks up the subscribe link in our WebFinger (front-core.mjs)
|
|
10
|
+
and lands them here. A static page, not a function: the account that does
|
|
11
|
+
the following is the agent in THIS browser, and the page asks it over the
|
|
12
|
+
same origin routes the record page uses. The underscore is Mastodon's
|
|
13
|
+
spelling of the path, kept so the address is the one other servers
|
|
14
|
+
already know. -->
|
|
15
|
+
<title>Follow — FediPod</title>
|
|
16
|
+
<link rel="icon" href="/icons/local.svg" type="image/svg+xml">
|
|
17
|
+
<link rel="stylesheet" href="/admin/tokens.css">
|
|
18
|
+
<style>
|
|
19
|
+
body { margin: 0; background: var(--surface); color: var(--fg); font-family: var(--font); }
|
|
20
|
+
main { max-width: 34rem; margin: 0 auto; padding: 2.5rem 1.25rem 3rem; }
|
|
21
|
+
h1 { color: var(--heading); margin: 0 0 1.5rem; font-size: 1.6rem; }
|
|
22
|
+
.address { background: var(--raised); border: 1px solid var(--line); border-radius: 8px;
|
|
23
|
+
padding: 1rem 1.1rem; margin: 0 0 1.25rem; word-break: break-word; }
|
|
24
|
+
.as { color: var(--ok); margin: 0 0 1.5rem; }
|
|
25
|
+
.row { margin: 0 0 1.25rem; }
|
|
26
|
+
button { font: inherit; padding: 0.6rem 1.4rem; border-radius: 8px; border: 1px solid var(--btn);
|
|
27
|
+
background: var(--btn); color: var(--btn-text); cursor: pointer; }
|
|
28
|
+
button:hover { background: var(--btn-hover); }
|
|
29
|
+
button:focus-visible { outline: 3px solid var(--focus); outline-offset: 2px; }
|
|
30
|
+
.msg { margin: 0 0 1.25rem; }
|
|
31
|
+
.msg.err { color: var(--err); }
|
|
32
|
+
.msg.ok { color: var(--ok); }
|
|
33
|
+
p { line-height: 1.55; }
|
|
34
|
+
a { color: var(--accent); }
|
|
35
|
+
.hint { color: var(--sub); }
|
|
36
|
+
</style>
|
|
37
|
+
<script src="/authorize_interaction.js" defer></script>
|
|
38
|
+
</head>
|
|
39
|
+
<body>
|
|
40
|
+
<main>
|
|
41
|
+
<h1>Follow</h1>
|
|
42
|
+
<p class="address" id="target">…</p>
|
|
43
|
+
<p class="as" id="as" hidden></p>
|
|
44
|
+
<p class="row"><button id="go" hidden>Follow</button></p>
|
|
45
|
+
<p class="msg" id="msg" role="status" aria-live="polite"></p>
|
|
46
|
+
<div id="by-hand" hidden>
|
|
47
|
+
<p>No FediPod account is signed in to this browser, so the follow cannot be
|
|
48
|
+
sent from here. If your agent runs on your own machine, or you are signed
|
|
49
|
+
in somewhere else, paste the address above into your client's search box
|
|
50
|
+
and follow from the profile it finds.</p>
|
|
51
|
+
<p><a href="/">Sign in to FediPod</a></p>
|
|
52
|
+
</div>
|
|
53
|
+
<p class="hint"><a href="/admin/client/">Your account</a></p>
|
|
54
|
+
</main>
|
|
55
|
+
</body>
|
|
56
|
+
</html>
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// authorize_interaction.js — the script for the remote-follow page.
|
|
2
|
+
//
|
|
3
|
+
// It reads who is to be followed out of the `uri` parameter, asks the agent in
|
|
4
|
+
// this browser who it is, and sends the follow through the same same-origin
|
|
5
|
+
// route the record page uses (/follow, admin-facade.mjs). The header is what
|
|
6
|
+
// the service worker's gate reads: a cross-site form cannot set one, and a
|
|
7
|
+
// cross-origin fetch that does is preflighted and never answered (sw-src.mjs).
|
|
8
|
+
const PAGE = { 'x-fedipod-page': '1' };
|
|
9
|
+
const $ = (id) => document.getElementById(id);
|
|
10
|
+
|
|
11
|
+
const say = (text, kind = '') => { const m = $('msg'); m.textContent = text; m.className = `msg ${kind}`.trim(); };
|
|
12
|
+
|
|
13
|
+
// What the other server put in `uri`: an acct URI, a bare or @-prefixed
|
|
14
|
+
// handle, or the actor's address. Anything with a scheme is taken as the
|
|
15
|
+
// actor itself — pasting an actor url is a normal way to follow.
|
|
16
|
+
function readTarget(raw) {
|
|
17
|
+
const s = String(raw || '').trim();
|
|
18
|
+
if (!s) return null;
|
|
19
|
+
if (/^https?:\/\//iu.test(s)) return { actor: s, label: s };
|
|
20
|
+
const handle = s.replace(/^acct:/iu, '').replace(/^@/u, '');
|
|
21
|
+
if (!/^[^@\s/]+@[^@\s/]+$/u.test(handle)) return null;
|
|
22
|
+
return { handle, label: `@${handle}` };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const target = readTarget(new URLSearchParams(location.search).get('uri'));
|
|
26
|
+
|
|
27
|
+
async function whoIsHere() {
|
|
28
|
+
try {
|
|
29
|
+
const res = await fetch('/config', { headers: PAGE });
|
|
30
|
+
if (!res.ok) return null;
|
|
31
|
+
const cfg = await res.json();
|
|
32
|
+
return cfg.address || (cfg.handle ? `@${cfg.handle}` : null);
|
|
33
|
+
} catch { return null; }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function follow() {
|
|
37
|
+
$('go').disabled = true;
|
|
38
|
+
say('sending…');
|
|
39
|
+
try {
|
|
40
|
+
const res = await fetch('/follow', {
|
|
41
|
+
method: 'POST', headers: { ...PAGE, 'content-type': 'application/json' },
|
|
42
|
+
body: JSON.stringify(target.actor ? { actor: target.actor } : { handle: target.handle }),
|
|
43
|
+
});
|
|
44
|
+
const out = await res.json().catch(() => ({}));
|
|
45
|
+
if (!res.ok) { say(out.error || `the follow was refused (${res.status})`, 'err'); $('go').disabled = false; return; }
|
|
46
|
+
// A Follow is delivered, not granted: it counts once the other server
|
|
47
|
+
// answers it, and a locked account answers when its owner gets to it.
|
|
48
|
+
say(`sent to ${target.label} — it counts once their server accepts it`, 'ok');
|
|
49
|
+
$('go').hidden = true;
|
|
50
|
+
} catch (err) {
|
|
51
|
+
say(err.message || 'the follow could not be sent', 'err');
|
|
52
|
+
$('go').disabled = false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
(async () => {
|
|
57
|
+
if (!target) {
|
|
58
|
+
$('target').textContent = 'nobody';
|
|
59
|
+
say('This address opens from another server’s Follow button, and it did not name anybody to follow.', 'err');
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
$('target').textContent = target.label;
|
|
63
|
+
const me = await whoIsHere();
|
|
64
|
+
if (!me) { $('by-hand').hidden = false; return; }
|
|
65
|
+
$('as').textContent = `signed in as ${me}`;
|
|
66
|
+
$('as').hidden = false;
|
|
67
|
+
$('go').hidden = false;
|
|
68
|
+
$('go').addEventListener('click', follow);
|
|
69
|
+
})();
|