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/dist/sw.js
CHANGED
|
@@ -157,6 +157,116 @@ var init_urls = __esm({
|
|
|
157
157
|
}
|
|
158
158
|
});
|
|
159
159
|
|
|
160
|
+
// lib/core/profile-page.mjs
|
|
161
|
+
function profilePageHtml({
|
|
162
|
+
name,
|
|
163
|
+
address,
|
|
164
|
+
summary = null,
|
|
165
|
+
icon = null,
|
|
166
|
+
image = null,
|
|
167
|
+
fields = [],
|
|
168
|
+
joined = null,
|
|
169
|
+
pinned = [],
|
|
170
|
+
kind = "person"
|
|
171
|
+
}) {
|
|
172
|
+
const esc = (s) => String(s ?? "").replace(/[&<>"]/g, (c) => ({ ...HTML_ESCAPES, '"': """ })[c]);
|
|
173
|
+
const what = kind === "group" ? "a group" : "an account";
|
|
174
|
+
const month = (iso) => {
|
|
175
|
+
const d = new Date(iso);
|
|
176
|
+
return Number.isNaN(d.getTime()) ? null : d.toLocaleDateString("en", { month: "long", year: "numeric", timeZone: "UTC" });
|
|
177
|
+
};
|
|
178
|
+
const day = (iso) => {
|
|
179
|
+
const d = new Date(iso);
|
|
180
|
+
return Number.isNaN(d.getTime()) ? "" : d.toISOString().slice(0, 10);
|
|
181
|
+
};
|
|
182
|
+
const fieldRows = fields.filter((f) => f?.name && f?.value !== void 0 && f?.value !== null && String(f.value) !== "");
|
|
183
|
+
const joinedText = joined ? month(joined) : null;
|
|
184
|
+
const pins = pinned.filter((x) => x && typeof x.content === "string");
|
|
185
|
+
return `<!doctype html>
|
|
186
|
+
<html lang="en">
|
|
187
|
+
<head>
|
|
188
|
+
<meta charset="utf-8">
|
|
189
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
190
|
+
<title>${esc(name)} (${esc(address)})</title>
|
|
191
|
+
<style>
|
|
192
|
+
:root { color-scheme: light dark; }
|
|
193
|
+
body { font: 112.5%/1.5 system-ui, sans-serif; max-width: 34rem; margin: 3rem auto; padding: 0 1rem; }
|
|
194
|
+
img.header { width: 100%; max-height: 12rem; object-fit: cover; border-radius: 1rem; }
|
|
195
|
+
img.avatar { width: 6rem; height: 6rem; border-radius: 1rem; object-fit: cover; margin-top: 1rem; }
|
|
196
|
+
h1 { margin: .5rem 0 0; }
|
|
197
|
+
.address { font-size: 1.1rem; user-select: all; }
|
|
198
|
+
dl.fields { display: grid; grid-template-columns: max-content 1fr; gap: .4rem 1rem; margin: 1.5rem 0; }
|
|
199
|
+
dl.fields dt { font-weight: 600; }
|
|
200
|
+
dl.fields dd { margin: 0; overflow-wrap: anywhere; }
|
|
201
|
+
.joined { margin: 1rem 0; }
|
|
202
|
+
section.pinned h2 { font-size: 1.1rem; margin: 2rem 0 .5rem; }
|
|
203
|
+
article.pin { border: 1px solid #8884; border-radius: 1rem; padding: 1rem 1.2rem; margin: 1rem 0; }
|
|
204
|
+
article.pin p:last-child { margin-bottom: 0; }
|
|
205
|
+
article.pin .when { font-size: 1rem; }
|
|
206
|
+
form { margin-top: 2rem; }
|
|
207
|
+
label { display: block; margin-bottom: .3rem; }
|
|
208
|
+
input { font: inherit; padding: .5rem; width: 14rem; max-width: 100%; }
|
|
209
|
+
button { font: inherit; padding: .5rem 1rem; }
|
|
210
|
+
.hint { color: #666; font-size: 1rem; }
|
|
211
|
+
.err { color: #b00020; }
|
|
212
|
+
.err:empty { display: none; }
|
|
213
|
+
@media (prefers-color-scheme: dark) {
|
|
214
|
+
.hint { color: #aaa; }
|
|
215
|
+
.err { color: #ff8a80; }
|
|
216
|
+
}
|
|
217
|
+
</style>
|
|
218
|
+
</head>
|
|
219
|
+
<body>
|
|
220
|
+
<main>
|
|
221
|
+
${image ? `<img class="header" src="${esc(image)}" alt="">` : ""}
|
|
222
|
+
${icon ? `<img class="avatar" src="${esc(icon)}" alt="">` : ""}
|
|
223
|
+
<h1>${esc(name)}</h1>
|
|
224
|
+
<p class="address">${esc(address)}</p>
|
|
225
|
+
${summary ? `<div>${summary}</div>` : ""}
|
|
226
|
+
${fieldRows.length ? `<dl class="fields">
|
|
227
|
+
${fieldRows.map((f) => `<dt>${esc(f.name)}</dt><dd>${esc(f.value)}</dd>`).join("\n")}
|
|
228
|
+
</dl>` : ""}
|
|
229
|
+
${joinedText ? `<p class="joined">Joined ${esc(joinedText)}</p>` : ""}
|
|
230
|
+
${pins.length ? `<section class="pinned">
|
|
231
|
+
<h2>Pinned</h2>
|
|
232
|
+
${pins.map((x) => `<article class="pin">
|
|
233
|
+
${x.content}
|
|
234
|
+
<p class="when">${x.url ? `<a href="${esc(x.url)}">${esc(day(x.published))}</a>` : esc(day(x.published))}</p>
|
|
235
|
+
</article>`).join("\n")}
|
|
236
|
+
</section>` : ""}
|
|
237
|
+
<p>This is ${what} on the Fediverse. To follow it, paste the address above
|
|
238
|
+
into the search box of Mastodon or any Fediverse app \u2014 or use the form.</p>
|
|
239
|
+
<form id="follow">
|
|
240
|
+
<label for="server">your server</label>
|
|
241
|
+
<input id="server" type="text" placeholder="mastodon.social" autocomplete="off"
|
|
242
|
+
required aria-describedby="follow-err">
|
|
243
|
+
<button type="submit">Follow</button>
|
|
244
|
+
<p class="err" id="follow-err" role="alert"></p>
|
|
245
|
+
</form>
|
|
246
|
+
<p class="hint">The form sends you to your own server's follow screen.</p>
|
|
247
|
+
</main>
|
|
248
|
+
<script>
|
|
249
|
+
document.getElementById('follow').addEventListener('submit', (ev) => {
|
|
250
|
+
ev.preventDefault();
|
|
251
|
+
const s = document.getElementById('server').value.trim().replace(/^https?:\\/\\//, '').replace(/\\/.*$/, '');
|
|
252
|
+
const err = document.getElementById('follow-err');
|
|
253
|
+
const addr = document.querySelector('.address').textContent.trim();
|
|
254
|
+
if (!s) { err.textContent = 'Enter your server, e.g. mastodon.social'; return; }
|
|
255
|
+
err.textContent = '';
|
|
256
|
+
location.href = 'https://' + s + '/authorize_interaction?uri=' + encodeURIComponent(addr);
|
|
257
|
+
});
|
|
258
|
+
<\/script>
|
|
259
|
+
</body>
|
|
260
|
+
</html>
|
|
261
|
+
`;
|
|
262
|
+
}
|
|
263
|
+
var HTML_ESCAPES;
|
|
264
|
+
var init_profile_page = __esm({
|
|
265
|
+
"lib/core/profile-page.mjs"() {
|
|
266
|
+
HTML_ESCAPES = { "&": "&", "<": "<", ">": ">" };
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
|
|
160
270
|
// node_modules/entities/dist/decode-codepoint.js
|
|
161
271
|
function replaceCodePoint(codePoint) {
|
|
162
272
|
if (codePoint >= 55296 && codePoint <= 57343 || codePoint > 1114111) {
|
|
@@ -9590,9 +9700,12 @@ __export(wire_exports, {
|
|
|
9590
9700
|
DEFAULT_ROOT: () => DEFAULT_ROOT,
|
|
9591
9701
|
FOLLOWERS_PAGE_SIZE: () => FOLLOWERS_PAGE_SIZE,
|
|
9592
9702
|
HASHTAG_RE: () => HASHTAG_RE,
|
|
9703
|
+
LEMMY_CTX: () => LEMMY_CTX,
|
|
9593
9704
|
MENTION_RE: () => MENTION_RE,
|
|
9594
9705
|
OUTBOX_PAGE_SIZE: () => OUTBOX_PAGE_SIZE,
|
|
9706
|
+
POLICY_CTX: () => POLICY_CTX,
|
|
9595
9707
|
PUBLIC: () => PUBLIC,
|
|
9708
|
+
QUOTE_CTX: () => QUOTE_CTX,
|
|
9596
9709
|
SEC_CTX: () => SEC_CTX,
|
|
9597
9710
|
acceptActivity: () => acceptActivity,
|
|
9598
9711
|
actorDoc: () => actorDoc,
|
|
@@ -9640,6 +9753,7 @@ __export(wire_exports, {
|
|
|
9640
9753
|
profilePageHtml: () => profilePageHtml,
|
|
9641
9754
|
publicHandle: () => publicHandle,
|
|
9642
9755
|
questionDoc: () => questionDoc,
|
|
9756
|
+
quotePolicy: () => quotePolicy,
|
|
9643
9757
|
rejectActivity: () => rejectActivity,
|
|
9644
9758
|
repliesId: () => repliesId,
|
|
9645
9759
|
sanitizeHtml: () => sanitizeHtml,
|
|
@@ -9699,7 +9813,8 @@ function actorDoc({
|
|
|
9699
9813
|
inbox = null,
|
|
9700
9814
|
outbox = null,
|
|
9701
9815
|
oauthAuthorize = null,
|
|
9702
|
-
oauthToken = null
|
|
9816
|
+
oauthToken = null,
|
|
9817
|
+
postingRestrictedToMods = null
|
|
9703
9818
|
}) {
|
|
9704
9819
|
const context = [AS_CTX, SEC_CTX];
|
|
9705
9820
|
context.push({ toot: "http://joinmastodon.org/ns#", featured: { "@id": "toot:featured", "@type": "@id" } });
|
|
@@ -9722,13 +9837,16 @@ function actorDoc({
|
|
|
9722
9837
|
});
|
|
9723
9838
|
}
|
|
9724
9839
|
if (webId || aliases.length) context.push({ alsoKnownAs: { "@id": "as:alsoKnownAs", "@type": "@id" } });
|
|
9840
|
+
if (kind === "group" && postingRestrictedToMods !== null) context.push(LEMMY_CTX);
|
|
9725
9841
|
if (fields.length) {
|
|
9726
9842
|
context.push({ schema: "http://schema.org#", PropertyValue: "schema:PropertyValue", value: "schema:value" });
|
|
9727
9843
|
}
|
|
9728
9844
|
return {
|
|
9729
9845
|
"@context": context,
|
|
9730
9846
|
id: urls.actor,
|
|
9731
|
-
|
|
9847
|
+
// A group is a community; an application is a service that speaks for a
|
|
9848
|
+
// site rather than a person — the actor a forum publishes for itself.
|
|
9849
|
+
type: kind === "group" ? "Group" : kind === "application" ? "Application" : "Person",
|
|
9732
9850
|
...approveJoins ? { manuallyApprovesFollowers: true } : {},
|
|
9733
9851
|
...assertionKey ? { assertionMethod: [{
|
|
9734
9852
|
id: assertionKeyId(urls),
|
|
@@ -9738,6 +9856,7 @@ function actorDoc({
|
|
|
9738
9856
|
}] } : {},
|
|
9739
9857
|
...movedTo ? { movedTo } : {},
|
|
9740
9858
|
...webId || aliases.length ? { alsoKnownAs: [...webId ? [webId] : [], ...aliases] } : {},
|
|
9859
|
+
...kind === "group" && postingRestrictedToMods !== null ? { postingRestrictedToMods: !!postingRestrictedToMods } : {},
|
|
9741
9860
|
preferredUsername: handle7,
|
|
9742
9861
|
name: name || handle7,
|
|
9743
9862
|
// The bio, and the avatar. For a group, `summary` is where what the group
|
|
@@ -9753,8 +9872,8 @@ function actorDoc({
|
|
|
9753
9872
|
...fields.length ? {
|
|
9754
9873
|
attachment: fields.map((f) => ({
|
|
9755
9874
|
type: "PropertyValue",
|
|
9756
|
-
name: String(f.name).replace(/[&<>]/g, (c) =>
|
|
9757
|
-
value: String(f.value).replace(/[&<>]/g, (c) =>
|
|
9875
|
+
name: String(f.name).replace(/[&<>]/g, (c) => HTML_ESCAPES2[c]),
|
|
9876
|
+
value: String(f.value).replace(/[&<>]/g, (c) => HTML_ESCAPES2[c])
|
|
9758
9877
|
}))
|
|
9759
9878
|
} : {},
|
|
9760
9879
|
// The human page. Remote servers send profile clicks here, so it must be
|
|
@@ -9797,108 +9916,6 @@ function actorDoc({
|
|
|
9797
9916
|
publicKey: { id: urls.actor + "#main-key", owner: urls.actor, publicKeyPem }
|
|
9798
9917
|
};
|
|
9799
9918
|
}
|
|
9800
|
-
function profilePageHtml({
|
|
9801
|
-
name,
|
|
9802
|
-
address,
|
|
9803
|
-
summary = null,
|
|
9804
|
-
icon = null,
|
|
9805
|
-
image = null,
|
|
9806
|
-
fields = [],
|
|
9807
|
-
joined = null,
|
|
9808
|
-
pinned = [],
|
|
9809
|
-
kind = "person"
|
|
9810
|
-
}) {
|
|
9811
|
-
const esc = (s) => String(s ?? "").replace(/[&<>"]/g, (c) => ({ ...HTML_ESCAPES, '"': """ })[c]);
|
|
9812
|
-
const what = kind === "group" ? "a group" : "an account";
|
|
9813
|
-
const month = (iso) => {
|
|
9814
|
-
const d = new Date(iso);
|
|
9815
|
-
return Number.isNaN(d.getTime()) ? null : d.toLocaleDateString("en", { month: "long", year: "numeric", timeZone: "UTC" });
|
|
9816
|
-
};
|
|
9817
|
-
const day = (iso) => {
|
|
9818
|
-
const d = new Date(iso);
|
|
9819
|
-
return Number.isNaN(d.getTime()) ? "" : d.toISOString().slice(0, 10);
|
|
9820
|
-
};
|
|
9821
|
-
const fieldRows = fields.filter((f) => f?.name && f?.value !== void 0 && f?.value !== null && String(f.value) !== "");
|
|
9822
|
-
const joinedText = joined ? month(joined) : null;
|
|
9823
|
-
const pins = pinned.filter((x) => x && typeof x.content === "string");
|
|
9824
|
-
return `<!doctype html>
|
|
9825
|
-
<html lang="en">
|
|
9826
|
-
<head>
|
|
9827
|
-
<meta charset="utf-8">
|
|
9828
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
9829
|
-
<title>${esc(name)} (${esc(address)})</title>
|
|
9830
|
-
<style>
|
|
9831
|
-
:root { color-scheme: light dark; }
|
|
9832
|
-
body { font: 112.5%/1.5 system-ui, sans-serif; max-width: 34rem; margin: 3rem auto; padding: 0 1rem; }
|
|
9833
|
-
img.header { width: 100%; max-height: 12rem; object-fit: cover; border-radius: 1rem; }
|
|
9834
|
-
img.avatar { width: 6rem; height: 6rem; border-radius: 1rem; object-fit: cover; margin-top: 1rem; }
|
|
9835
|
-
h1 { margin: .5rem 0 0; }
|
|
9836
|
-
.address { font-size: 1.1rem; user-select: all; }
|
|
9837
|
-
dl.fields { display: grid; grid-template-columns: max-content 1fr; gap: .4rem 1rem; margin: 1.5rem 0; }
|
|
9838
|
-
dl.fields dt { font-weight: 600; }
|
|
9839
|
-
dl.fields dd { margin: 0; overflow-wrap: anywhere; }
|
|
9840
|
-
.joined { margin: 1rem 0; }
|
|
9841
|
-
section.pinned h2 { font-size: 1.1rem; margin: 2rem 0 .5rem; }
|
|
9842
|
-
article.pin { border: 1px solid #8884; border-radius: 1rem; padding: 1rem 1.2rem; margin: 1rem 0; }
|
|
9843
|
-
article.pin p:last-child { margin-bottom: 0; }
|
|
9844
|
-
article.pin .when { font-size: 1rem; }
|
|
9845
|
-
form { margin-top: 2rem; }
|
|
9846
|
-
label { display: block; margin-bottom: .3rem; }
|
|
9847
|
-
input { font: inherit; padding: .5rem; width: 14rem; max-width: 100%; }
|
|
9848
|
-
button { font: inherit; padding: .5rem 1rem; }
|
|
9849
|
-
.hint { color: #666; font-size: 1rem; }
|
|
9850
|
-
.err { color: #b00020; }
|
|
9851
|
-
.err:empty { display: none; }
|
|
9852
|
-
@media (prefers-color-scheme: dark) {
|
|
9853
|
-
.hint { color: #aaa; }
|
|
9854
|
-
.err { color: #ff8a80; }
|
|
9855
|
-
}
|
|
9856
|
-
</style>
|
|
9857
|
-
</head>
|
|
9858
|
-
<body>
|
|
9859
|
-
<main>
|
|
9860
|
-
${image ? `<img class="header" src="${esc(image)}" alt="">` : ""}
|
|
9861
|
-
${icon ? `<img class="avatar" src="${esc(icon)}" alt="">` : ""}
|
|
9862
|
-
<h1>${esc(name)}</h1>
|
|
9863
|
-
<p class="address">${esc(address)}</p>
|
|
9864
|
-
${summary ? `<div>${summary}</div>` : ""}
|
|
9865
|
-
${fieldRows.length ? `<dl class="fields">
|
|
9866
|
-
${fieldRows.map((f) => `<dt>${esc(f.name)}</dt><dd>${esc(f.value)}</dd>`).join("\n")}
|
|
9867
|
-
</dl>` : ""}
|
|
9868
|
-
${joinedText ? `<p class="joined">Joined ${esc(joinedText)}</p>` : ""}
|
|
9869
|
-
${pins.length ? `<section class="pinned">
|
|
9870
|
-
<h2>Pinned</h2>
|
|
9871
|
-
${pins.map((x) => `<article class="pin">
|
|
9872
|
-
${x.content}
|
|
9873
|
-
<p class="when">${x.url ? `<a href="${esc(x.url)}">${esc(day(x.published))}</a>` : esc(day(x.published))}</p>
|
|
9874
|
-
</article>`).join("\n")}
|
|
9875
|
-
</section>` : ""}
|
|
9876
|
-
<p>This is ${what} on the Fediverse. To follow it, paste the address above
|
|
9877
|
-
into the search box of Mastodon or any Fediverse app \u2014 or use the form.</p>
|
|
9878
|
-
<form id="follow">
|
|
9879
|
-
<label for="server">your server</label>
|
|
9880
|
-
<input id="server" type="text" placeholder="mastodon.social" autocomplete="off"
|
|
9881
|
-
required aria-describedby="follow-err">
|
|
9882
|
-
<button type="submit">Follow</button>
|
|
9883
|
-
<p class="err" id="follow-err" role="alert"></p>
|
|
9884
|
-
</form>
|
|
9885
|
-
<p class="hint">The form sends you to your own server's follow screen.</p>
|
|
9886
|
-
</main>
|
|
9887
|
-
<script>
|
|
9888
|
-
document.getElementById('follow').addEventListener('submit', (ev) => {
|
|
9889
|
-
ev.preventDefault();
|
|
9890
|
-
const s = document.getElementById('server').value.trim().replace(/^https?:\\/\\//, '').replace(/\\/.*$/, '');
|
|
9891
|
-
const err = document.getElementById('follow-err');
|
|
9892
|
-
const addr = document.querySelector('.address').textContent.trim();
|
|
9893
|
-
if (!s) { err.textContent = 'Enter your server, e.g. mastodon.social'; return; }
|
|
9894
|
-
err.textContent = '';
|
|
9895
|
-
location.href = 'https://' + s + '/authorize_interaction?uri=' + encodeURIComponent(addr);
|
|
9896
|
-
});
|
|
9897
|
-
<\/script>
|
|
9898
|
-
</body>
|
|
9899
|
-
</html>
|
|
9900
|
-
`;
|
|
9901
|
-
}
|
|
9902
9919
|
function deleteActorActivity(urls, stamp) {
|
|
9903
9920
|
return {
|
|
9904
9921
|
"@context": AS_CTX,
|
|
@@ -10082,7 +10099,7 @@ function titledContent(note) {
|
|
|
10082
10099
|
if (!title || types.includes("Note")) return html;
|
|
10083
10100
|
const opening = html.replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim().toLowerCase();
|
|
10084
10101
|
if (opening.startsWith(title.toLowerCase())) return html;
|
|
10085
|
-
const esc = String(title).replace(/[&<>]/g, (c) =>
|
|
10102
|
+
const esc = String(title).replace(/[&<>]/g, (c) => HTML_ESCAPES2[c]);
|
|
10086
10103
|
return `<p><strong>${esc}</strong></p>${html}`;
|
|
10087
10104
|
}
|
|
10088
10105
|
function attachmentsOf(note) {
|
|
@@ -10108,18 +10125,18 @@ function tagHref(urls, name) {
|
|
|
10108
10125
|
return urls.profileHtml + "?tag=" + encodeURIComponent(String(name).toLowerCase());
|
|
10109
10126
|
}
|
|
10110
10127
|
function contentHtml(text, mentions = [], hashtags = []) {
|
|
10111
|
-
let html = String(text).replace(/[&<>]/g, (c) =>
|
|
10128
|
+
let html = String(text).replace(/[&<>]/g, (c) => HTML_ESCAPES2[c]);
|
|
10112
10129
|
if (hashtags.length) {
|
|
10113
10130
|
const byName = new Map(hashtags.filter((h) => h?.name && h?.href).map((h) => [h.name.toLowerCase(), h.href]));
|
|
10114
10131
|
html = html.replace(HASHTAG_RE, (all, lead, name) => {
|
|
10115
10132
|
const href = byName.get(name.toLowerCase());
|
|
10116
10133
|
if (!href) return all;
|
|
10117
|
-
return `${lead}<a href="${String(href).replace(/[&<>"]/g, (c) =>
|
|
10134
|
+
return `${lead}<a href="${String(href).replace(/[&<>"]/g, (c) => HTML_ESCAPES2[c] || """)}" class="mention hashtag" rel="tag">#<span>${name}</span></a>`;
|
|
10118
10135
|
});
|
|
10119
10136
|
}
|
|
10120
10137
|
for (const m of mentions) {
|
|
10121
10138
|
if (!m?.handle || !m?.actor) continue;
|
|
10122
|
-
const href = String(m.page || m.actor).replace(/[&<>"]/g, (c) =>
|
|
10139
|
+
const href = String(m.page || m.actor).replace(/[&<>"]/g, (c) => HTML_ESCAPES2[c] || """);
|
|
10123
10140
|
html = html.split("@" + m.handle).join(
|
|
10124
10141
|
`<a href="${href}" class="u-url mention">@${m.handle.split("@")[0]}</a>`
|
|
10125
10142
|
);
|
|
@@ -10134,6 +10151,10 @@ function addressing(urls, visibility, who = []) {
|
|
|
10134
10151
|
direct: { to: who, cc: [] }
|
|
10135
10152
|
}[visibility] || { to: [PUBLIC], cc: [urls.followers, ...who] };
|
|
10136
10153
|
}
|
|
10154
|
+
function quotePolicy(visibility) {
|
|
10155
|
+
const open2 = visibility === "public" || visibility === "unlisted";
|
|
10156
|
+
return { canQuote: { automaticApproval: open2 ? [PUBLIC] : [], manualApproval: [] } };
|
|
10157
|
+
}
|
|
10137
10158
|
function noteDoc({
|
|
10138
10159
|
urls,
|
|
10139
10160
|
slug,
|
|
@@ -10147,7 +10168,8 @@ function noteDoc({
|
|
|
10147
10168
|
sensitive = false,
|
|
10148
10169
|
updated = null,
|
|
10149
10170
|
container = null,
|
|
10150
|
-
also = []
|
|
10171
|
+
also = [],
|
|
10172
|
+
quote = null
|
|
10151
10173
|
}) {
|
|
10152
10174
|
const id = (container || urls.notes) + slug;
|
|
10153
10175
|
const who = mentions.map((m) => m.actor);
|
|
@@ -10155,7 +10177,7 @@ function noteDoc({
|
|
|
10155
10177
|
const addressed = addressing(urls, visibility, [...who, ...extra]);
|
|
10156
10178
|
const hashtags = hashtagsIn(content).map((name) => ({ name, href: tagHref(urls, name) }));
|
|
10157
10179
|
const note = {
|
|
10158
|
-
"@context": AS_CTX,
|
|
10180
|
+
"@context": [AS_CTX, { ...POLICY_CTX, ...quote?.id ? QUOTE_CTX : {} }],
|
|
10159
10181
|
id,
|
|
10160
10182
|
type: "Note",
|
|
10161
10183
|
attributedTo: urls.actor,
|
|
@@ -10163,8 +10185,15 @@ function noteDoc({
|
|
|
10163
10185
|
published,
|
|
10164
10186
|
to: addressed.to,
|
|
10165
10187
|
cc: addressed.cc,
|
|
10166
|
-
replies: repliesId(id)
|
|
10188
|
+
replies: repliesId(id),
|
|
10189
|
+
interactionPolicy: quotePolicy(visibility)
|
|
10167
10190
|
};
|
|
10191
|
+
if (quote?.id) {
|
|
10192
|
+
note.quote = quote.id;
|
|
10193
|
+
note.quoteUri = quote.id;
|
|
10194
|
+
note._misskey_quote = quote.id;
|
|
10195
|
+
if (quote.authorization) note.quoteAuthorization = quote.authorization;
|
|
10196
|
+
}
|
|
10168
10197
|
if (summary) note.summary = summary;
|
|
10169
10198
|
if (summary || sensitive) note.sensitive = true;
|
|
10170
10199
|
if (updated) note.updated = updated;
|
|
@@ -10206,7 +10235,7 @@ function questionDoc({
|
|
|
10206
10235
|
if (endTime) question.endTime = endTime;
|
|
10207
10236
|
if (closed) question.closed = closed;
|
|
10208
10237
|
if (votersCount !== null && votersCount !== void 0) {
|
|
10209
|
-
question["@context"] = [
|
|
10238
|
+
question["@context"] = [...[].concat(note["@context"]), {
|
|
10210
10239
|
toot: "http://joinmastodon.org/ns#",
|
|
10211
10240
|
votersCount: {
|
|
10212
10241
|
"@id": "toot:votersCount",
|
|
@@ -10301,10 +10330,10 @@ function updateActorActivity({ urls, actor, serial, published = (/* @__PURE__ */
|
|
|
10301
10330
|
object
|
|
10302
10331
|
};
|
|
10303
10332
|
}
|
|
10304
|
-
function updateActivity(note, urls) {
|
|
10333
|
+
function updateActivity(note, urls, { serial = null } = {}) {
|
|
10305
10334
|
return {
|
|
10306
10335
|
"@context": AS_CTX,
|
|
10307
|
-
id: note.id + "#update-" +
|
|
10336
|
+
id: note.id + "#update-" + (note.updated ? String(note.updated).replace(/[^0-9TZ]/g, "") : "q" + (serial ?? Date.now())),
|
|
10308
10337
|
type: "Update",
|
|
10309
10338
|
actor: urls.actor,
|
|
10310
10339
|
to: note.to,
|
|
@@ -10350,13 +10379,15 @@ function addRemoveActivity({ urls, type, object, target, serial }) {
|
|
|
10350
10379
|
target
|
|
10351
10380
|
};
|
|
10352
10381
|
}
|
|
10353
|
-
var import_sanitize_html, AS_CTX, SEC_CTX, PUBLIC, DEFAULT_ROOT, assertionKeyId, OUTBOX_PAGE_SIZE, outboxPageId, outboxPageCount, outboxItemId, outboxWireItem, outboxLocalItem, FOLLOWERS_PAGE_SIZE, followersPageId, followersPageCount, ALLOWED_TAGS, ALLOWED_ATTRS, MAX_ATTACHMENTS, MAX_ATTACHMENT_URL, attachmentUrl,
|
|
10382
|
+
var import_sanitize_html, AS_CTX, LEMMY_CTX, SEC_CTX, PUBLIC, DEFAULT_ROOT, assertionKeyId, OUTBOX_PAGE_SIZE, outboxPageId, outboxPageCount, outboxItemId, outboxWireItem, outboxLocalItem, FOLLOWERS_PAGE_SIZE, followersPageId, followersPageCount, ALLOWED_TAGS, ALLOWED_ATTRS, MAX_ATTACHMENTS, MAX_ATTACHMENT_URL, attachmentUrl, HTML_ESCAPES2, MENTION_RE, HASHTAG_RE, QUOTE_CTX, POLICY_CTX;
|
|
10354
10383
|
var init_wire = __esm({
|
|
10355
10384
|
"lib/core/wire.mjs"() {
|
|
10356
10385
|
init_urls();
|
|
10357
10386
|
init_urls();
|
|
10387
|
+
init_profile_page();
|
|
10358
10388
|
import_sanitize_html = __toESM(require_sanitize_html(), 1);
|
|
10359
10389
|
AS_CTX = "https://www.w3.org/ns/activitystreams";
|
|
10390
|
+
LEMMY_CTX = "https://join-lemmy.org/context.json";
|
|
10360
10391
|
SEC_CTX = "https://w3id.org/security/v1";
|
|
10361
10392
|
PUBLIC = "https://www.w3.org/ns/activitystreams#Public";
|
|
10362
10393
|
DEFAULT_ROOT = "fedipod/";
|
|
@@ -10405,9 +10436,22 @@ var init_wire = __esm({
|
|
|
10405
10436
|
return null;
|
|
10406
10437
|
}
|
|
10407
10438
|
};
|
|
10408
|
-
|
|
10439
|
+
HTML_ESCAPES2 = { "&": "&", "<": "<", ">": ">" };
|
|
10409
10440
|
MENTION_RE = /@([A-Za-z0-9_.-]+)@([A-Za-z0-9.-]+\.[A-Za-z]{2,}(?::\d+)?)/g;
|
|
10410
10441
|
HASHTAG_RE = /(^|[^\/\w)])#([\p{L}\p{N}_]*[\p{L}_][\p{L}\p{N}_]*)/gu;
|
|
10442
|
+
QUOTE_CTX = {
|
|
10443
|
+
quote: { "@id": "https://w3id.org/fep/044f#quote", "@type": "@id" },
|
|
10444
|
+
quoteUri: { "@id": "http://fedibird.com/ns#quoteUri", "@type": "@id" },
|
|
10445
|
+
_misskey_quote: { "@id": "https://misskey-hub.net/ns#_misskey_quote", "@type": "@id" },
|
|
10446
|
+
quoteAuthorization: { "@id": "https://w3id.org/fep/044f#quoteAuthorization", "@type": "@id" }
|
|
10447
|
+
};
|
|
10448
|
+
POLICY_CTX = {
|
|
10449
|
+
gts: "https://gotosocial.org/ns#",
|
|
10450
|
+
interactionPolicy: { "@id": "gts:interactionPolicy", "@type": "@id" },
|
|
10451
|
+
canQuote: { "@id": "gts:canQuote", "@type": "@id" },
|
|
10452
|
+
automaticApproval: { "@id": "gts:automaticApproval", "@type": "@id" },
|
|
10453
|
+
manualApproval: { "@id": "gts:manualApproval", "@type": "@id" }
|
|
10454
|
+
};
|
|
10411
10455
|
}
|
|
10412
10456
|
});
|
|
10413
10457
|
|
|
@@ -23600,7 +23644,7 @@ var require_constants = __commonJS({
|
|
|
23600
23644
|
"node_modules/jsonld/lib/constants.js"(exports, module2) {
|
|
23601
23645
|
"use strict";
|
|
23602
23646
|
var RDF5 = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
|
|
23603
|
-
var
|
|
23647
|
+
var XSD3 = "http://www.w3.org/2001/XMLSchema#";
|
|
23604
23648
|
module2.exports = {
|
|
23605
23649
|
// TODO: Deprecated and will be removed later. Use LINK_HEADER_CONTEXT.
|
|
23606
23650
|
LINK_HEADER_REL: "http://www.w3.org/ns/json-ld#context",
|
|
@@ -23616,11 +23660,11 @@ var require_constants = __commonJS({
|
|
|
23616
23660
|
RDF_JSON_LITERAL: RDF5 + "JSON",
|
|
23617
23661
|
RDF_OBJECT: RDF5 + "object",
|
|
23618
23662
|
RDF_LANGSTRING: RDF5 + "langString",
|
|
23619
|
-
XSD:
|
|
23620
|
-
XSD_BOOLEAN:
|
|
23621
|
-
XSD_DOUBLE:
|
|
23622
|
-
XSD_INTEGER:
|
|
23623
|
-
XSD_STRING:
|
|
23663
|
+
XSD: XSD3,
|
|
23664
|
+
XSD_BOOLEAN: XSD3 + "boolean",
|
|
23665
|
+
XSD_DOUBLE: XSD3 + "double",
|
|
23666
|
+
XSD_INTEGER: XSD3 + "integer",
|
|
23667
|
+
XSD_STRING: XSD3 + "string"
|
|
23624
23668
|
};
|
|
23625
23669
|
}
|
|
23626
23670
|
});
|
|
@@ -32650,6 +32694,73 @@ var init_safefetch = __esm({
|
|
|
32650
32694
|
}
|
|
32651
32695
|
});
|
|
32652
32696
|
|
|
32697
|
+
// lib/core/wire-quotes.mjs
|
|
32698
|
+
var wire_quotes_exports = {};
|
|
32699
|
+
__export(wire_quotes_exports, {
|
|
32700
|
+
QUOTE_TYPES_CTX: () => QUOTE_TYPES_CTX,
|
|
32701
|
+
quoteAnswerActivity: () => quoteAnswerActivity,
|
|
32702
|
+
quoteAuthorizationDoc: () => quoteAuthorizationDoc,
|
|
32703
|
+
quoteAuthorizationId: () => quoteAuthorizationId,
|
|
32704
|
+
quoteRequestActivity: () => quoteRequestActivity
|
|
32705
|
+
});
|
|
32706
|
+
function quoteRequestActivity({ urls, note, quoted, quotedActor, serial }) {
|
|
32707
|
+
const { "@context": ctx, ...instrument } = note;
|
|
32708
|
+
return {
|
|
32709
|
+
"@context": [AS_CTX, { ...QUOTE_CTX, ...POLICY_CTX, QuoteRequest: QUOTE_TYPES_CTX.QuoteRequest }],
|
|
32710
|
+
id: urls.actor + "#quote-request-" + serial,
|
|
32711
|
+
type: "QuoteRequest",
|
|
32712
|
+
actor: urls.actor,
|
|
32713
|
+
to: [quotedActor],
|
|
32714
|
+
object: quoted,
|
|
32715
|
+
instrument
|
|
32716
|
+
};
|
|
32717
|
+
}
|
|
32718
|
+
function quoteAuthorizationId(noteId, instrumentId) {
|
|
32719
|
+
return noteId + "-quote-" + node_crypto_default.createHash("sha256").update(String(instrumentId)).digest("hex").slice(0, 16);
|
|
32720
|
+
}
|
|
32721
|
+
function quoteAuthorizationDoc({ urls, id, instrument, target }) {
|
|
32722
|
+
return {
|
|
32723
|
+
"@context": [AS_CTX, QUOTE_TYPES_CTX],
|
|
32724
|
+
id,
|
|
32725
|
+
type: "QuoteAuthorization",
|
|
32726
|
+
attributedTo: urls.actor,
|
|
32727
|
+
interactingObject: instrument,
|
|
32728
|
+
interactionTarget: target
|
|
32729
|
+
};
|
|
32730
|
+
}
|
|
32731
|
+
function quoteAnswerActivity({ urls, type, request, result = null, to, serial }) {
|
|
32732
|
+
const idOf3 = (v) => typeof v === "string" ? v : v?.id;
|
|
32733
|
+
return {
|
|
32734
|
+
"@context": [AS_CTX, { QuoteRequest: QUOTE_TYPES_CTX.QuoteRequest }],
|
|
32735
|
+
id: urls.actor + "#" + type.toLowerCase() + "-" + serial,
|
|
32736
|
+
type,
|
|
32737
|
+
actor: urls.actor,
|
|
32738
|
+
to: [to],
|
|
32739
|
+
object: {
|
|
32740
|
+
id: idOf3(request),
|
|
32741
|
+
type: "QuoteRequest",
|
|
32742
|
+
actor: idOf3(request?.actor),
|
|
32743
|
+
object: idOf3(request?.object),
|
|
32744
|
+
instrument: idOf3(request?.instrument)
|
|
32745
|
+
},
|
|
32746
|
+
...result ? { result } : {}
|
|
32747
|
+
};
|
|
32748
|
+
}
|
|
32749
|
+
var QUOTE_TYPES_CTX;
|
|
32750
|
+
var init_wire_quotes = __esm({
|
|
32751
|
+
"lib/core/wire-quotes.mjs"() {
|
|
32752
|
+
init_node_crypto();
|
|
32753
|
+
init_wire();
|
|
32754
|
+
QUOTE_TYPES_CTX = {
|
|
32755
|
+
QuoteRequest: "https://w3id.org/fep/044f#QuoteRequest",
|
|
32756
|
+
QuoteAuthorization: "https://w3id.org/fep/044f#QuoteAuthorization",
|
|
32757
|
+
gts: "https://gotosocial.org/ns#",
|
|
32758
|
+
interactingObject: { "@id": "gts:interactingObject", "@type": "@id" },
|
|
32759
|
+
interactionTarget: { "@id": "gts:interactionTarget", "@type": "@id" }
|
|
32760
|
+
};
|
|
32761
|
+
}
|
|
32762
|
+
});
|
|
32763
|
+
|
|
32653
32764
|
// web/app/shims/fedify-sig.mjs
|
|
32654
32765
|
async function sign({ url, method = "POST", headers = {}, body }, privateKey, keyId) {
|
|
32655
32766
|
const u = new URL(url);
|
|
@@ -34096,6 +34207,61 @@ var require_src = __commonJS({
|
|
|
34096
34207
|
}
|
|
34097
34208
|
});
|
|
34098
34209
|
|
|
34210
|
+
// web/app/idb-kv.mjs
|
|
34211
|
+
var DB = "fedipod-accounts";
|
|
34212
|
+
function open() {
|
|
34213
|
+
return new Promise((res, rej) => {
|
|
34214
|
+
const r = indexedDB.open(DB, 1);
|
|
34215
|
+
r.onupgradeneeded = () => {
|
|
34216
|
+
const db = r.result;
|
|
34217
|
+
if (!db.objectStoreNames.contains("kv")) db.createObjectStore("kv");
|
|
34218
|
+
};
|
|
34219
|
+
r.onsuccess = () => res(r.result);
|
|
34220
|
+
r.onerror = () => rej(r.error);
|
|
34221
|
+
});
|
|
34222
|
+
}
|
|
34223
|
+
async function kvGet(key) {
|
|
34224
|
+
const db = await open();
|
|
34225
|
+
return new Promise((res, rej) => {
|
|
34226
|
+
const rq = db.transaction("kv", "readonly").objectStore("kv").get(key);
|
|
34227
|
+
rq.onsuccess = () => res(rq.result ?? null);
|
|
34228
|
+
rq.onerror = () => rej(rq.error);
|
|
34229
|
+
});
|
|
34230
|
+
}
|
|
34231
|
+
async function kvAll() {
|
|
34232
|
+
const db = await open();
|
|
34233
|
+
return new Promise((res, rej) => {
|
|
34234
|
+
const out = {};
|
|
34235
|
+
const cur = db.transaction("kv", "readonly").objectStore("kv").openCursor();
|
|
34236
|
+
cur.onsuccess = () => {
|
|
34237
|
+
const c = cur.result;
|
|
34238
|
+
if (c) {
|
|
34239
|
+
out[c.key] = c.value;
|
|
34240
|
+
c.continue();
|
|
34241
|
+
} else res(out);
|
|
34242
|
+
};
|
|
34243
|
+
cur.onerror = () => rej(cur.error);
|
|
34244
|
+
});
|
|
34245
|
+
}
|
|
34246
|
+
async function kvPut(key, val) {
|
|
34247
|
+
const db = await open();
|
|
34248
|
+
return new Promise((res, rej) => {
|
|
34249
|
+
const tx = db.transaction("kv", "readwrite");
|
|
34250
|
+
tx.objectStore("kv").put(val, key);
|
|
34251
|
+
tx.oncomplete = () => res();
|
|
34252
|
+
tx.onerror = () => rej(tx.error);
|
|
34253
|
+
});
|
|
34254
|
+
}
|
|
34255
|
+
async function kvDel(key) {
|
|
34256
|
+
const db = await open();
|
|
34257
|
+
return new Promise((res, rej) => {
|
|
34258
|
+
const tx = db.transaction("kv", "readwrite");
|
|
34259
|
+
tx.objectStore("kv").delete(key);
|
|
34260
|
+
tx.oncomplete = () => res();
|
|
34261
|
+
tx.onerror = () => rej(tx.error);
|
|
34262
|
+
});
|
|
34263
|
+
}
|
|
34264
|
+
|
|
34099
34265
|
// web/app/agent.mjs
|
|
34100
34266
|
init_wire();
|
|
34101
34267
|
|
|
@@ -34159,8 +34325,8 @@ async function probePrivateEnforcement(probe, podKeepUrl) {
|
|
|
34159
34325
|
}
|
|
34160
34326
|
|
|
34161
34327
|
// lib/pod/state.mjs
|
|
34162
|
-
var
|
|
34163
|
-
var
|
|
34328
|
+
var readKeys = (pod, urls) => pod.getJson(urls.state + "keys.json");
|
|
34329
|
+
var writeKeys = (pod, urls, keys) => pod.putJson(urls.state + "keys.json", keys, "application/json");
|
|
34164
34330
|
|
|
34165
34331
|
// lib/core/store.mjs
|
|
34166
34332
|
init_node_crypto();
|
|
@@ -45482,7 +45648,8 @@ var ALL_COLLECTIONS = {
|
|
|
45482
45648
|
outbox: true,
|
|
45483
45649
|
acls: true,
|
|
45484
45650
|
pending: true,
|
|
45485
|
-
blocked: true
|
|
45651
|
+
blocked: true,
|
|
45652
|
+
featured: true
|
|
45486
45653
|
};
|
|
45487
45654
|
async function publishOutbox(publisher, outbox, { acls = false, force = false } = {}) {
|
|
45488
45655
|
const { urls } = publisher;
|
|
@@ -45581,6 +45748,7 @@ async function publishCollections(publisher, which = ALL_COLLECTIONS) {
|
|
|
45581
45748
|
}
|
|
45582
45749
|
if (which.pending) await publisher.publishPending();
|
|
45583
45750
|
if (which.blocked) await publisher.publishBlocked();
|
|
45751
|
+
if (which.featured) await publisher.publishFeatured();
|
|
45584
45752
|
if (which.outbox) {
|
|
45585
45753
|
const outbox = publisher.store.read("outbox.json", []);
|
|
45586
45754
|
const known2 = publisher.store.read("published.json", {}).outboxIndex;
|
|
@@ -55704,6 +55872,88 @@ var anno_default = {
|
|
|
55704
55872
|
}
|
|
55705
55873
|
};
|
|
55706
55874
|
|
|
55875
|
+
// lib/core/contexts/litepub-0.1.json
|
|
55876
|
+
var litepub_0_1_default = {
|
|
55877
|
+
"@context": [
|
|
55878
|
+
"https://www.w3.org/ns/activitystreams",
|
|
55879
|
+
"https://w3id.org/security/v1",
|
|
55880
|
+
{
|
|
55881
|
+
Emoji: "toot:Emoji",
|
|
55882
|
+
Hashtag: "as:Hashtag",
|
|
55883
|
+
PropertyValue: "schema:PropertyValue",
|
|
55884
|
+
atomUri: "ostatus:atomUri",
|
|
55885
|
+
conversation: {
|
|
55886
|
+
"@id": "ostatus:conversation",
|
|
55887
|
+
"@type": "@id"
|
|
55888
|
+
},
|
|
55889
|
+
discoverable: "toot:discoverable",
|
|
55890
|
+
manuallyApprovesFollowers: "as:manuallyApprovesFollowers",
|
|
55891
|
+
capabilities: "litepub:capabilities",
|
|
55892
|
+
ostatus: "http://ostatus.org#",
|
|
55893
|
+
schema: "http://schema.org#",
|
|
55894
|
+
toot: "http://joinmastodon.org/ns#",
|
|
55895
|
+
misskey: "https://misskey-hub.net/ns#",
|
|
55896
|
+
fedibird: "http://fedibird.com/ns#",
|
|
55897
|
+
sharkey: "https://joinsharkey.org/ns#",
|
|
55898
|
+
value: "schema:value",
|
|
55899
|
+
sensitive: "as:sensitive",
|
|
55900
|
+
litepub: "http://litepub.social/ns#",
|
|
55901
|
+
invisible: "litepub:invisible",
|
|
55902
|
+
directMessage: "litepub:directMessage",
|
|
55903
|
+
listMessage: {
|
|
55904
|
+
"@id": "litepub:listMessage",
|
|
55905
|
+
"@type": "@id"
|
|
55906
|
+
},
|
|
55907
|
+
quoteUrl: "as:quoteUrl",
|
|
55908
|
+
quoteUri: "fedibird:quoteUri",
|
|
55909
|
+
oauthRegistrationEndpoint: {
|
|
55910
|
+
"@id": "litepub:oauthRegistrationEndpoint",
|
|
55911
|
+
"@type": "@id"
|
|
55912
|
+
},
|
|
55913
|
+
EmojiReact: "litepub:EmojiReact",
|
|
55914
|
+
ChatMessage: "litepub:ChatMessage",
|
|
55915
|
+
alsoKnownAs: {
|
|
55916
|
+
"@id": "as:alsoKnownAs",
|
|
55917
|
+
"@type": "@id"
|
|
55918
|
+
},
|
|
55919
|
+
vcard: "http://www.w3.org/2006/vcard/ns#",
|
|
55920
|
+
formerRepresentations: "litepub:formerRepresentations",
|
|
55921
|
+
contentMap: {
|
|
55922
|
+
"@id": "as:content",
|
|
55923
|
+
"@container": "@language"
|
|
55924
|
+
},
|
|
55925
|
+
featured: {
|
|
55926
|
+
"@id": "toot:featured",
|
|
55927
|
+
"@type": "@id"
|
|
55928
|
+
},
|
|
55929
|
+
backgroundUrl: {
|
|
55930
|
+
"@id": "sharkey:backgroundUrl",
|
|
55931
|
+
"@type": "@id"
|
|
55932
|
+
}
|
|
55933
|
+
}
|
|
55934
|
+
]
|
|
55935
|
+
};
|
|
55936
|
+
|
|
55937
|
+
// lib/core/contexts/quotes.json
|
|
55938
|
+
var quotes_default = {
|
|
55939
|
+
"@context": {
|
|
55940
|
+
quote: {
|
|
55941
|
+
"@id": "https://w3id.org/fep/044f#quote",
|
|
55942
|
+
"@type": "@id"
|
|
55943
|
+
},
|
|
55944
|
+
quoteAuthorization: {
|
|
55945
|
+
"@id": "https://w3id.org/fep/044f#quoteAuthorization",
|
|
55946
|
+
"@type": "@id"
|
|
55947
|
+
},
|
|
55948
|
+
QuoteRequest: "https://w3id.org/fep/044f#QuoteRequest",
|
|
55949
|
+
QuoteAuthorization: "https://w3id.org/fep/044f#QuoteAuthorization",
|
|
55950
|
+
_misskey_quote: {
|
|
55951
|
+
"@id": "https://misskey-hub.net/ns#_misskey_quote",
|
|
55952
|
+
"@type": "@id"
|
|
55953
|
+
}
|
|
55954
|
+
}
|
|
55955
|
+
};
|
|
55956
|
+
|
|
55707
55957
|
// lib/core/contexts/index.mjs
|
|
55708
55958
|
var CONTEXTS = {
|
|
55709
55959
|
"https://www.w3.org/ns/activitystreams": activitystreams_default,
|
|
@@ -55720,7 +55970,9 @@ var CONTEXTS = {
|
|
|
55720
55970
|
"https://join-lemmy.org/context.json": join_lemmy_default,
|
|
55721
55971
|
"http://joinmastodon.org/ns": joinmastodon_default,
|
|
55722
55972
|
"https://purl.archive.org/miscellany": miscellany_default,
|
|
55723
|
-
"http://www.w3.org/ns/anno.jsonld": anno_default
|
|
55973
|
+
"http://www.w3.org/ns/anno.jsonld": anno_default,
|
|
55974
|
+
"http://litepub.social/ns": litepub_0_1_default,
|
|
55975
|
+
"https://w3id.org/fep/044f": quotes_default
|
|
55724
55976
|
};
|
|
55725
55977
|
|
|
55726
55978
|
// lib/core/graphview.mjs
|
|
@@ -55757,8 +56009,10 @@ function buildTerms() {
|
|
|
55757
56009
|
const terms = /* @__PURE__ */ new Map();
|
|
55758
56010
|
const order = [AS_CTX, ...Object.keys(CONTEXTS).filter((u) => u !== AS_CTX)];
|
|
55759
56011
|
for (const url of order) {
|
|
55760
|
-
const
|
|
55761
|
-
|
|
56012
|
+
const body = CONTEXTS[url]?.["@context"];
|
|
56013
|
+
const maps = (Array.isArray(body) ? body : [body]).filter((c) => c && typeof c === "object");
|
|
56014
|
+
if (!maps.length) continue;
|
|
56015
|
+
const ctx = Object.assign({}, ...maps);
|
|
55762
56016
|
const prefixes = {};
|
|
55763
56017
|
for (const [k, v] of Object.entries(ctx)) {
|
|
55764
56018
|
if (typeof v === "string" && /^https?:\/\//.test(v)) prefixes[k] = v;
|
|
@@ -55843,8 +56097,40 @@ function isListHead(term3, ctx) {
|
|
|
55843
56097
|
if (term3.termType !== "BlankNode" && term3.termType !== "NamedNode") return false;
|
|
55844
56098
|
return !!ctx.bySubject.get(term3.value)?.has(RDF_FIRST);
|
|
55845
56099
|
}
|
|
56100
|
+
var XSD2 = "http://www.w3.org/2001/XMLSchema#";
|
|
56101
|
+
var NUMERIC = new Set([
|
|
56102
|
+
"integer",
|
|
56103
|
+
"decimal",
|
|
56104
|
+
"double",
|
|
56105
|
+
"float",
|
|
56106
|
+
"long",
|
|
56107
|
+
"int",
|
|
56108
|
+
"short",
|
|
56109
|
+
"byte",
|
|
56110
|
+
"nonNegativeInteger",
|
|
56111
|
+
"positiveInteger",
|
|
56112
|
+
"nonPositiveInteger",
|
|
56113
|
+
"negativeInteger",
|
|
56114
|
+
"unsignedLong",
|
|
56115
|
+
"unsignedInt",
|
|
56116
|
+
"unsignedShort",
|
|
56117
|
+
"unsignedByte"
|
|
56118
|
+
].map((t) => XSD2 + t));
|
|
56119
|
+
function fromLiteral(term3) {
|
|
56120
|
+
const type = term3.datatype?.value || "";
|
|
56121
|
+
if (type === `${XSD2}boolean`) {
|
|
56122
|
+
if (term3.value === "true" || term3.value === "1") return true;
|
|
56123
|
+
if (term3.value === "false" || term3.value === "0") return false;
|
|
56124
|
+
return term3.value;
|
|
56125
|
+
}
|
|
56126
|
+
if (NUMERIC.has(type)) {
|
|
56127
|
+
const n = Number(term3.value);
|
|
56128
|
+
return Number.isFinite(n) ? n : term3.value;
|
|
56129
|
+
}
|
|
56130
|
+
return term3.value;
|
|
56131
|
+
}
|
|
55846
56132
|
function toValue(term3, ctx, depth, path) {
|
|
55847
|
-
if (term3.termType === "Literal") return term3
|
|
56133
|
+
if (term3.termType === "Literal") return fromLiteral(term3);
|
|
55848
56134
|
if (isListHead(term3, ctx)) return readList(term3.value, ctx, depth, path);
|
|
55849
56135
|
const preds = ctx.bySubject.get(term3.value);
|
|
55850
56136
|
if (preds && preds.size) return makeView(term3.value, ctx, depth + 1, path);
|
|
@@ -55912,8 +56198,10 @@ var AS2Error = class extends Error {
|
|
|
55912
56198
|
this.context = context;
|
|
55913
56199
|
}
|
|
55914
56200
|
};
|
|
56201
|
+
var LITEPUB = "http://litepub.social/ns";
|
|
56202
|
+
var heldKey = (url) => typeof url === "string" && /\/schemas\/litepub-0\.1\.jsonld$/u.test(url) ? LITEPUB : url;
|
|
55915
56203
|
function contextLoader(url) {
|
|
55916
|
-
const doc = CONTEXTS[url];
|
|
56204
|
+
const doc = CONTEXTS[heldKey(url)];
|
|
55917
56205
|
if (!doc) {
|
|
55918
56206
|
return Promise.reject(new AS2Error(`context not held here: ${url}`, { context: url }));
|
|
55919
56207
|
}
|
|
@@ -55948,7 +56236,7 @@ async function parseAS2(raw) {
|
|
|
55948
56236
|
}
|
|
55949
56237
|
function groundContext(input) {
|
|
55950
56238
|
const ctx = input["@context"];
|
|
55951
|
-
const held = (c) => typeof c === "string" ? Object.hasOwn(CONTEXTS, c) : c && typeof c === "object";
|
|
56239
|
+
const held = (c) => typeof c === "string" ? Object.hasOwn(CONTEXTS, heldKey(c)) : c && typeof c === "object";
|
|
55952
56240
|
let kept;
|
|
55953
56241
|
if (ctx === void 0 || ctx === null) kept = [];
|
|
55954
56242
|
else if (Array.isArray(ctx)) kept = ctx.filter(held);
|
|
@@ -55956,6 +56244,17 @@ function groundContext(input) {
|
|
|
55956
56244
|
if (!kept.some((c) => c === AS_CTX)) kept.unshift(AS_CTX);
|
|
55957
56245
|
return { ...input, "@context": kept.length === 1 ? kept[0] : kept };
|
|
55958
56246
|
}
|
|
56247
|
+
function carryRemovals(input, view) {
|
|
56248
|
+
if (!input || typeof input !== "object" || !view || typeof view !== "object") return view;
|
|
56249
|
+
for (const [key, value] of Object.entries(input)) {
|
|
56250
|
+
if (value === null) {
|
|
56251
|
+
if (!(key in view)) view[key] = null;
|
|
56252
|
+
} else if (key === "object" && typeof value === "object" && !Array.isArray(value) && view.object && typeof view.object === "object" && !Array.isArray(view.object)) {
|
|
56253
|
+
carryRemovals(value, view.object);
|
|
56254
|
+
}
|
|
56255
|
+
}
|
|
56256
|
+
return view;
|
|
56257
|
+
}
|
|
55959
56258
|
async function readLenient(raw) {
|
|
55960
56259
|
let input = null;
|
|
55961
56260
|
try {
|
|
@@ -55966,12 +56265,12 @@ async function readLenient(raw) {
|
|
|
55966
56265
|
const root = typeof own === "string" && /^https?:\/\//u.test(own) ? own : null;
|
|
55967
56266
|
try {
|
|
55968
56267
|
const { doc, graph: graph2 } = await parseAS2(raw);
|
|
55969
|
-
return { doc, graph: graph2, view: graphView(graph2, { root }), degraded: null };
|
|
56268
|
+
return { doc, graph: graph2, view: carryRemovals(input, graphView(graph2, { root })), degraded: null };
|
|
55970
56269
|
} catch (e) {
|
|
55971
56270
|
if (!input || typeof input !== "object") return { doc: null, graph: null, view: null, degraded: e.message };
|
|
55972
56271
|
try {
|
|
55973
56272
|
const { graph: graph2 } = await parseAS2(groundContext(input));
|
|
55974
|
-
return { doc: input, graph: graph2, view: graphView(graph2, { root }), degraded: e.message };
|
|
56273
|
+
return { doc: input, graph: graph2, view: carryRemovals(input, graphView(graph2, { root })), degraded: e.message };
|
|
55975
56274
|
} catch (inner) {
|
|
55976
56275
|
return { doc: input, graph: null, view: null, degraded: `${e.message}; grounded read also failed: ${inner.message}` };
|
|
55977
56276
|
}
|
|
@@ -55981,6 +56280,7 @@ async function readLenient(raw) {
|
|
|
55981
56280
|
// lib/core/publisher/notes.mjs
|
|
55982
56281
|
init_node_crypto();
|
|
55983
56282
|
init_wire();
|
|
56283
|
+
init_wire_quotes();
|
|
55984
56284
|
async function ensureMediaContainer(publisher) {
|
|
55985
56285
|
if (publisher._mediaReady) return;
|
|
55986
56286
|
if (await exists(publisher.remote, publisher.urls.media)) {
|
|
@@ -56084,7 +56384,7 @@ function rowContent(obj) {
|
|
|
56084
56384
|
const esc = (v) => String(v).replace(/[&<>"]/gu, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]);
|
|
56085
56385
|
return `<p><a href="${esc(obj?.id || "")}">${esc(obj?.type || "object")}</a></p>`;
|
|
56086
56386
|
}
|
|
56087
|
-
async function publishNote(publisher, content, { inReplyTo, attachments, visibility = "public", spoilerText = null, sensitive = false, slug: wanted = null, also = [], deliverTo = [] } = {}) {
|
|
56387
|
+
async function publishNote(publisher, content, { inReplyTo, attachments, visibility = "public", spoilerText = null, sensitive = false, slug: wanted = null, also = [], deliverTo = [], quote = null } = {}) {
|
|
56088
56388
|
const { urls } = publisher;
|
|
56089
56389
|
const priv = visibility === "private" || visibility === "direct";
|
|
56090
56390
|
if (priv) {
|
|
@@ -56096,6 +56396,12 @@ async function publishNote(publisher, content, { inReplyTo, attachments, visibil
|
|
|
56096
56396
|
const mentions = await publisher._mentionsFor(content, inReplyTo);
|
|
56097
56397
|
if (visibility === "direct") assertDirectAddressed(content, mentions);
|
|
56098
56398
|
const reply = await replyTarget(publisher, inReplyTo, mentions, visibility);
|
|
56399
|
+
const selfQuote = quote?.id && quote.actor === urls.actor;
|
|
56400
|
+
const quoted = quote?.id ? {
|
|
56401
|
+
id: quote.id,
|
|
56402
|
+
// Our own post: the authorization is minted here, before the note goes out.
|
|
56403
|
+
authorization: selfQuote ? await grantQuote(publisher, quote.id, (priv ? urls.privateNotes : urls.notes) + slug) : null
|
|
56404
|
+
} : null;
|
|
56099
56405
|
const note = noteDoc({
|
|
56100
56406
|
urls,
|
|
56101
56407
|
slug,
|
|
@@ -56108,9 +56414,10 @@ async function publishNote(publisher, content, { inReplyTo, attachments, visibil
|
|
|
56108
56414
|
summary: spoilerText,
|
|
56109
56415
|
sensitive,
|
|
56110
56416
|
container: priv ? urls.privateNotes : urls.notes,
|
|
56111
|
-
also: [...also, ...reply ? [reply.actor] : []]
|
|
56417
|
+
also: [...also, ...reply ? [reply.actor] : [], ...quote?.actor && !selfQuote ? [quote.actor] : []],
|
|
56418
|
+
quote: quoted
|
|
56112
56419
|
});
|
|
56113
|
-
const addressedInboxes = await inboxesFor(publisher, [...also, ...deliverTo]);
|
|
56420
|
+
const addressedInboxes = await inboxesFor(publisher, [...also, ...deliverTo, ...quote?.actor && !selfQuote ? [quote.actor] : []]);
|
|
56114
56421
|
await write4(publisher.remote, note.id, note);
|
|
56115
56422
|
await writeEmptyReplies(
|
|
56116
56423
|
publisher.remote,
|
|
@@ -56118,6 +56425,7 @@ async function publishNote(publisher, content, { inReplyTo, attachments, visibil
|
|
|
56118
56425
|
collection(repliesId(note.id), [])
|
|
56119
56426
|
);
|
|
56120
56427
|
if (!priv) await publisher.recordOutbox(note.id);
|
|
56428
|
+
const request = quote?.id && !selfQuote ? quoteRequestActivity({ urls, note, quoted: quote.id, quotedActor: quote.actor, serial: Date.now() }) : null;
|
|
56121
56429
|
publisher.store.addStatus({
|
|
56122
56430
|
noteId: note.id,
|
|
56123
56431
|
actor: urls.actor,
|
|
@@ -56131,7 +56439,13 @@ async function publishNote(publisher, content, { inReplyTo, attachments, visibil
|
|
|
56131
56439
|
...spoilerText ? { spoiler: spoilerText } : {},
|
|
56132
56440
|
...note.sensitive ? { sensitive: true } : {},
|
|
56133
56441
|
...attachments?.length ? { attachments } : {},
|
|
56134
|
-
...rowTags(note)
|
|
56442
|
+
...rowTags(note),
|
|
56443
|
+
...quote?.id ? {
|
|
56444
|
+
quote: quote.id,
|
|
56445
|
+
quoteState: selfQuote ? "accepted" : "pending",
|
|
56446
|
+
...quoted.authorization ? { quoteAuthorization: quoted.authorization } : {},
|
|
56447
|
+
...request ? { quoteRequest: { id: request.id, actor: quote.actor } } : {}
|
|
56448
|
+
} : {}
|
|
56135
56449
|
});
|
|
56136
56450
|
if (await publisher.store.commit?.() === false) publisher.log(`post published but its timeline row was refused: ${note.id}`);
|
|
56137
56451
|
const create = createActivity(note, urls);
|
|
@@ -56145,6 +56459,11 @@ async function publishNote(publisher, content, { inReplyTo, attachments, visibil
|
|
|
56145
56459
|
].filter(Boolean))];
|
|
56146
56460
|
await publisher.deliverer.deliverToAll(inboxes, create);
|
|
56147
56461
|
publisher.log(`note published: ${note.id} \u2192 ${inboxes.length} inbox(es)`);
|
|
56462
|
+
if (request) {
|
|
56463
|
+
const [inbox] = await inboxesFor(publisher, [quote.actor]);
|
|
56464
|
+
if (inbox) await publisher.deliverer.deliver(inbox, request);
|
|
56465
|
+
else publisher.log(`quote of ${quote.id}: no inbox found for ${quote.actor} \u2014 the quote stays pending`);
|
|
56466
|
+
}
|
|
56148
56467
|
if (visibility === "public" && publisher.atproto?.connected() && publisher.store.getConfig()?.atproto?.crossPost) {
|
|
56149
56468
|
try {
|
|
56150
56469
|
const mirror = await publisher.atproto.crossPost(
|
|
@@ -56253,7 +56572,8 @@ async function updateNote(publisher, s, { content, spoilerText = null, sensitive
|
|
|
56253
56572
|
sensitive: sensitive ?? !!s.sensitive,
|
|
56254
56573
|
updated,
|
|
56255
56574
|
container,
|
|
56256
|
-
also: reply ? [reply.actor] : []
|
|
56575
|
+
also: reply ? [reply.actor] : [],
|
|
56576
|
+
quote: rowQuote(s)
|
|
56257
56577
|
});
|
|
56258
56578
|
await write4(publisher.remote, note.id, note);
|
|
56259
56579
|
await writeCreate(publisher.remote, createActivityId(note.id), createActivity(note, urls));
|
|
@@ -56278,6 +56598,71 @@ async function updateNote(publisher, s, { content, spoilerText = null, sensitive
|
|
|
56278
56598
|
publisher.log(`note edited: ${note.id} \u2192 ${inboxes.length} inbox(es)`);
|
|
56279
56599
|
return patched;
|
|
56280
56600
|
}
|
|
56601
|
+
function rowQuote(s) {
|
|
56602
|
+
if (!s?.quote || s.quoteState === "rejected") return null;
|
|
56603
|
+
return { id: s.quote, ...s.quoteAuthorization ? { authorization: s.quoteAuthorization } : {} };
|
|
56604
|
+
}
|
|
56605
|
+
async function restateNote(publisher, s, { quote }) {
|
|
56606
|
+
const { urls } = publisher;
|
|
56607
|
+
const content = s.text ?? "";
|
|
56608
|
+
const mentions = await publisher._mentionsFor(content, s.inReplyTo);
|
|
56609
|
+
const container = String(s.noteId).startsWith(urls.privateNotes) ? urls.privateNotes : urls.notes;
|
|
56610
|
+
const slug = s.slug || String(s.noteId).slice(container.length);
|
|
56611
|
+
const reply = await replyTarget(publisher, s.inReplyTo, mentions, s.visibility || "public");
|
|
56612
|
+
const note = noteDoc({
|
|
56613
|
+
urls,
|
|
56614
|
+
slug,
|
|
56615
|
+
content,
|
|
56616
|
+
published: s.published,
|
|
56617
|
+
inReplyTo: s.inReplyTo,
|
|
56618
|
+
attachments: s.attachments || [],
|
|
56619
|
+
mentions,
|
|
56620
|
+
visibility: s.visibility || "public",
|
|
56621
|
+
summary: s.spoiler || null,
|
|
56622
|
+
sensitive: !!s.sensitive,
|
|
56623
|
+
updated: s.editedAt || null,
|
|
56624
|
+
container,
|
|
56625
|
+
also: [...reply ? [reply.actor] : [], ...s.quoteRequest?.actor ? [s.quoteRequest.actor] : []],
|
|
56626
|
+
quote
|
|
56627
|
+
});
|
|
56628
|
+
await write4(publisher.remote, note.id, note);
|
|
56629
|
+
await writeCreate(publisher.remote, createActivityId(note.id), createActivity(note, urls));
|
|
56630
|
+
const update = updateActivity(note, urls, { serial: Date.now() });
|
|
56631
|
+
const contacts = publisher.store.getContacts();
|
|
56632
|
+
const inboxes = [...new Set([
|
|
56633
|
+
...s.visibility === "direct" ? [] : contacts.followers.map((f) => f.sharedInbox || f.inbox),
|
|
56634
|
+
...mentions.map((m) => m.inbox),
|
|
56635
|
+
reply?.inbox,
|
|
56636
|
+
...await inboxesFor(publisher, s.quoteRequest?.actor ? [s.quoteRequest.actor] : [])
|
|
56637
|
+
].filter(Boolean))];
|
|
56638
|
+
await publisher.deliverer.deliverToAll(inboxes, update);
|
|
56639
|
+
return inboxes.length;
|
|
56640
|
+
}
|
|
56641
|
+
async function authorizeQuote(publisher, s, authorizationId) {
|
|
56642
|
+
const patched = publisher.store.updateStatus(s.noteId, { quoteState: "accepted", quoteAuthorization: authorizationId });
|
|
56643
|
+
const n = await restateNote(publisher, patched || s, { quote: { id: s.quote, authorization: authorizationId } });
|
|
56644
|
+
publisher.log(`quote allowed by ${s.quoteRequest?.actor || "the author"}: ${s.noteId} \u2192 ${n} inbox(es)`);
|
|
56645
|
+
return patched;
|
|
56646
|
+
}
|
|
56647
|
+
async function withdrawQuote(publisher, s) {
|
|
56648
|
+
const patched = publisher.store.updateStatus(s.noteId, { quoteState: "rejected", quoteAuthorization: void 0 });
|
|
56649
|
+
const n = await restateNote(publisher, patched || s, { quote: null });
|
|
56650
|
+
publisher.log(`quote refused by ${s.quoteRequest?.actor || "the author"}: ${s.noteId} \u2192 ${n} inbox(es)`);
|
|
56651
|
+
return patched;
|
|
56652
|
+
}
|
|
56653
|
+
var MAX_QUOTED_BY = 200;
|
|
56654
|
+
async function grantQuote(publisher, noteId, instrumentId) {
|
|
56655
|
+
const { urls } = publisher;
|
|
56656
|
+
const id = quoteAuthorizationId(noteId, instrumentId);
|
|
56657
|
+
await write4(publisher.remote, id, quoteAuthorizationDoc({ urls, id, instrument: instrumentId, target: noteId }));
|
|
56658
|
+
const s = publisher.store.getStatuses().find((x) => x.noteId === noteId);
|
|
56659
|
+
if (s) {
|
|
56660
|
+
const quotedBy = (s.quotedBy || []).filter((q) => q.note !== instrumentId);
|
|
56661
|
+
quotedBy.unshift({ note: instrumentId, authorization: id });
|
|
56662
|
+
publisher.store.updateStatus(noteId, { quotedBy: quotedBy.slice(0, MAX_QUOTED_BY) });
|
|
56663
|
+
}
|
|
56664
|
+
return id;
|
|
56665
|
+
}
|
|
56281
56666
|
|
|
56282
56667
|
// lib/core/publisher/restore.mjs
|
|
56283
56668
|
var ACCEPT_AP = 'application/activity+json, application/ld+json; profile="https://www.w3.org/ns/activitystreams"';
|
|
@@ -56331,11 +56716,11 @@ async function reconcileOutbox(publisher, outbox) {
|
|
|
56331
56716
|
return 0;
|
|
56332
56717
|
}
|
|
56333
56718
|
if (!Array.isArray(published) || !published.length) return 0;
|
|
56334
|
-
const
|
|
56335
|
-
const known2 = new Set(outbox.map(
|
|
56719
|
+
const idOf3 = (i) => typeof i === "string" ? i : i?.id || null;
|
|
56720
|
+
const known2 = new Set(outbox.map(idOf3).filter(Boolean));
|
|
56336
56721
|
const removed = new Set(publisher.store.read("outbox-removed.json", []).map((r) => r.id));
|
|
56337
56722
|
const missing = published.filter((i) => {
|
|
56338
|
-
const id =
|
|
56723
|
+
const id = idOf3(i);
|
|
56339
56724
|
return id && !known2.has(id) && !removed.has(id);
|
|
56340
56725
|
});
|
|
56341
56726
|
if (!missing.length) return 0;
|
|
@@ -56774,7 +57159,16 @@ var Publisher = class {
|
|
|
56774
57159
|
// Every actor document this identity publishes, from one place: the
|
|
56775
57160
|
// profile publish and a Move both write it, and a Move that rebuilt it
|
|
56776
57161
|
// by hand dropped the gateway inbox, the outbox door and the collections.
|
|
56777
|
-
|
|
57162
|
+
// `clientOrigin` is overridden only by the agent serving a client its own
|
|
57163
|
+
// local actor: standalone, the published document cannot name a loopback
|
|
57164
|
+
// address, but a client already talking to that address can be told it.
|
|
57165
|
+
actorDocFor({
|
|
57166
|
+
priv = false,
|
|
57167
|
+
moderators = null,
|
|
57168
|
+
inbox = null,
|
|
57169
|
+
movedTo = this.config.movedTo || null,
|
|
57170
|
+
clientOrigin = this.clientOrigin
|
|
57171
|
+
} = {}) {
|
|
56778
57172
|
const { urls } = this;
|
|
56779
57173
|
return actorDoc({
|
|
56780
57174
|
urls,
|
|
@@ -56791,6 +57185,9 @@ var Publisher = class {
|
|
|
56791
57185
|
fields: this.config.fields || [],
|
|
56792
57186
|
webId: this.remote.webId || null,
|
|
56793
57187
|
aliases: this.config.aliases || [],
|
|
57188
|
+
// A group says whether only its moderators may open posts (Lemmy's
|
|
57189
|
+
// term); a person's actor carries nothing of the kind.
|
|
57190
|
+
postingRestrictedToMods: this.config.kind === "group" ? !!this.config.postingRestrictedToMods : null,
|
|
56794
57191
|
moderators,
|
|
56795
57192
|
pendingFollowers: priv ? urls.pendingFollowers : null,
|
|
56796
57193
|
pendingFollowing: priv ? urls.pendingFollowing : null,
|
|
@@ -56799,11 +57196,11 @@ var Publisher = class {
|
|
|
56799
57196
|
// The agent's own outbox endpoint, where it is reachable: a client
|
|
56800
57197
|
// following the actor must arrive somewhere that will take a write.
|
|
56801
57198
|
// Otherwise the Gateway's outbox door, when one is attached.
|
|
56802
|
-
outbox:
|
|
57199
|
+
outbox: clientOrigin ? `${clientOrigin}ap/outbox` : this.gatewayOutbox(),
|
|
56803
57200
|
// How a client-to-server client finds the way in with nothing configured
|
|
56804
57201
|
// by hand. Advertised only where the surface is publicly reachable.
|
|
56805
|
-
oauthAuthorize:
|
|
56806
|
-
oauthToken:
|
|
57202
|
+
oauthAuthorize: clientOrigin ? `${clientOrigin}oauth/authorize` : null,
|
|
57203
|
+
oauthToken: clientOrigin ? `${clientOrigin}oauth/token` : null
|
|
56807
57204
|
});
|
|
56808
57205
|
}
|
|
56809
57206
|
// The human half: a page a browser can open and follow from. The actor
|
|
@@ -56847,7 +57244,7 @@ var Publisher = class {
|
|
|
56847
57244
|
const moderators = (this.config.moderators || []).length ? urls.moderators : null;
|
|
56848
57245
|
const gw = this.config.gateway;
|
|
56849
57246
|
const gwActive = gw && gw.url && gw.mode && gw.mode !== "off";
|
|
56850
|
-
const actorDoc2 = this.actorDocFor({ priv, moderators, inbox: gwActive ? gw.url : null });
|
|
57247
|
+
const actorDoc2 = this.actorDocFor({ priv, moderators, inbox: gwActive ? gw.url : this.config.inboxUrl || null });
|
|
56851
57248
|
const surface = node_crypto_default.createHash("sha256").update(JSON.stringify({
|
|
56852
57249
|
actor: actorDoc2,
|
|
56853
57250
|
handle: this.config.handle,
|
|
@@ -56861,18 +57258,20 @@ var Publisher = class {
|
|
|
56861
57258
|
this.log("profile unchanged \u2014 nothing republished");
|
|
56862
57259
|
return { unreachable: [], updated: 0, skipped: true };
|
|
56863
57260
|
}
|
|
56864
|
-
|
|
56865
|
-
|
|
56866
|
-
|
|
56867
|
-
|
|
56868
|
-
|
|
56869
|
-
|
|
56870
|
-
|
|
56871
|
-
|
|
56872
|
-
|
|
56873
|
-
|
|
56874
|
-
|
|
56875
|
-
|
|
57261
|
+
if (!this.config.forum) {
|
|
57262
|
+
await writeWebfinger(
|
|
57263
|
+
this.remote,
|
|
57264
|
+
urls,
|
|
57265
|
+
jrd({ handle: this.config.handle, host, actor: urls.actor, page: urls.profileHtml })
|
|
57266
|
+
);
|
|
57267
|
+
await writeHostMeta(this.remote, urls, hostMeta(urls.base));
|
|
57268
|
+
const nodeinfoDocUrl = urls.home + "ap/nodeinfo-2.0";
|
|
57269
|
+
const localPosts = this.store.getStatuses().filter((s) => s.kind === "post").length;
|
|
57270
|
+
await writeNodeinfo(this.remote, urls, {
|
|
57271
|
+
pointer: nodeinfoPointer(nodeinfoDocUrl),
|
|
57272
|
+
doc: nodeinfoDoc({ version: AGENT_VERSION, localPosts })
|
|
57273
|
+
});
|
|
57274
|
+
}
|
|
56876
57275
|
const actor = actorDoc2;
|
|
56877
57276
|
await write(this.remote, urls, actor);
|
|
56878
57277
|
if (moderators) {
|
|
@@ -56883,7 +57282,8 @@ var Publisher = class {
|
|
|
56883
57282
|
);
|
|
56884
57283
|
}
|
|
56885
57284
|
if (gwActive) await this.publishGatewayPolicy();
|
|
56886
|
-
|
|
57285
|
+
if (this.config.forum) {
|
|
57286
|
+
} else try {
|
|
56887
57287
|
const wrote = await linkInWebIdProfile(this.remote, {
|
|
56888
57288
|
actorUrl: urls.actor,
|
|
56889
57289
|
accountName: `@${this.config.handle}@${host}`,
|
|
@@ -56949,12 +57349,13 @@ var Publisher = class {
|
|
|
56949
57349
|
async verifyPublicSurface() {
|
|
56950
57350
|
const { urls } = this;
|
|
56951
57351
|
const targets = [
|
|
56952
|
-
|
|
56953
|
-
["host-meta", urls.base + ".well-known/host-meta"],
|
|
57352
|
+
// Discovery documents are the pod's, not a forum category's (see publishProfile).
|
|
57353
|
+
...this.config.forum ? [] : [["webfinger", urls.webfinger], ["host-meta", urls.base + ".well-known/host-meta"]],
|
|
56954
57354
|
["actor", urls.actor],
|
|
56955
57355
|
["notes", urls.notes],
|
|
56956
57356
|
["followers", urls.followers],
|
|
56957
57357
|
["following", urls.following],
|
|
57358
|
+
["featured", urls.featured],
|
|
56958
57359
|
["outbox", urls.outbox]
|
|
56959
57360
|
];
|
|
56960
57361
|
const unreachable = [];
|
|
@@ -57152,6 +57553,15 @@ var Publisher = class {
|
|
|
57152
57553
|
publishNote(...a) {
|
|
57153
57554
|
return publishNote(this, ...a);
|
|
57154
57555
|
}
|
|
57556
|
+
authorizeQuote(...a) {
|
|
57557
|
+
return authorizeQuote(this, ...a);
|
|
57558
|
+
}
|
|
57559
|
+
withdrawQuote(...a) {
|
|
57560
|
+
return withdrawQuote(this, ...a);
|
|
57561
|
+
}
|
|
57562
|
+
grantQuote(...a) {
|
|
57563
|
+
return grantQuote(this, ...a);
|
|
57564
|
+
}
|
|
57155
57565
|
publishObject(...a) {
|
|
57156
57566
|
return publishObject(this, ...a);
|
|
57157
57567
|
}
|
|
@@ -57237,15 +57647,25 @@ var MAX_FORWARDS_PER_DRAIN = 20;
|
|
|
57237
57647
|
var ACCEPT_AP2 = 'application/activity+json, application/ld+json; profile="https://www.w3.org/ns/activitystreams"';
|
|
57238
57648
|
function trimActivity(a) {
|
|
57239
57649
|
if (!a || typeof a !== "object") return a ?? null;
|
|
57240
|
-
const
|
|
57650
|
+
const idOf3 = (v) => typeof v === "string" ? v : v?.id ?? null;
|
|
57241
57651
|
const out = {};
|
|
57242
57652
|
for (const k of ["id", "type", "actor", "target"]) {
|
|
57243
|
-
const v =
|
|
57653
|
+
const v = idOf3(a[k]);
|
|
57244
57654
|
if (v) out[k] = String(v).slice(0, 2048);
|
|
57245
57655
|
}
|
|
57246
|
-
const obj =
|
|
57247
|
-
if (obj)
|
|
57248
|
-
|
|
57656
|
+
const obj = idOf3(a.object);
|
|
57657
|
+
if (obj) {
|
|
57658
|
+
const asked = a.object && typeof a.object === "object" ? a.object : null;
|
|
57659
|
+
const said = {};
|
|
57660
|
+
if (asked) {
|
|
57661
|
+
if (typeof asked.name === "string") said.name = asked.name.slice(0, 300);
|
|
57662
|
+
if ("closed" in asked) {
|
|
57663
|
+
const v = asked.closed;
|
|
57664
|
+
said.closed = v === "false" ? false : v === "true" ? true : typeof v === "string" ? v.slice(0, 64) : !!v;
|
|
57665
|
+
}
|
|
57666
|
+
}
|
|
57667
|
+
out.object = Object.keys(said).length ? { id: String(obj).slice(0, 2048), ...said } : String(obj).slice(0, 2048);
|
|
57668
|
+
} else if (a.object && typeof a.object === "object" && a.object.type) {
|
|
57249
57669
|
out.object = { type: String(a.object.type).slice(0, 64) };
|
|
57250
57670
|
}
|
|
57251
57671
|
return out;
|
|
@@ -57297,6 +57717,40 @@ function authorOf(note, delivered = null) {
|
|
|
57297
57717
|
if (!author) return null;
|
|
57298
57718
|
return sameIdentity(note?.id, author) ? author : null;
|
|
57299
57719
|
}
|
|
57720
|
+
var MAX_REACTION_CHARS = 64;
|
|
57721
|
+
function reactionOf(activity) {
|
|
57722
|
+
if (!activity || activity.type !== "EmojiReact" && activity.type !== "Like") return null;
|
|
57723
|
+
const content = typeof activity.content === "string" ? activity.content.trim() : "";
|
|
57724
|
+
if (!content) return null;
|
|
57725
|
+
const emoji = content.slice(0, MAX_REACTION_CHARS);
|
|
57726
|
+
const bare = emoji.replace(/^:|:$/g, "");
|
|
57727
|
+
const custom = emojisOf(activity).find((e) => e.shortcode === bare);
|
|
57728
|
+
return { emoji, ...custom ? { url: custom.url } : {} };
|
|
57729
|
+
}
|
|
57730
|
+
var QUOTE_REL = "https://misskey-hub.net/ns#_misskey_quote";
|
|
57731
|
+
function quoteOf(note) {
|
|
57732
|
+
const idOf3 = (v) => typeof v === "string" ? v : v?.id;
|
|
57733
|
+
const named = [note?.quote, note?.quoteUri, note?._misskey_quote, note?.quoteUrl].map((v) => idOf3(Array.isArray(v) ? v[0] : v)).find(Boolean);
|
|
57734
|
+
const linked = [].concat(note?.tag || []).find((t) => t?.type === "Link" && t.href && [].concat(t.rel || []).includes(QUOTE_REL))?.href;
|
|
57735
|
+
const q = named || linked;
|
|
57736
|
+
return q && q !== note?.id ? httpOnly(String(q).slice(0, MAX_URL_CHARS)) : null;
|
|
57737
|
+
}
|
|
57738
|
+
function quotePolicyOf(note, author) {
|
|
57739
|
+
const can = note?.interactionPolicy?.canQuote;
|
|
57740
|
+
if (!can || typeof can !== "object") return null;
|
|
57741
|
+
const word = (v) => {
|
|
57742
|
+
const id = typeof v === "string" ? v : v?.id;
|
|
57743
|
+
if (!id) return null;
|
|
57744
|
+
if (id === "https://www.w3.org/ns/activitystreams#Public" || id === "as:Public" || id === "Public") return "public";
|
|
57745
|
+
if (author && sameIdentity(id, author)) {
|
|
57746
|
+
if (/\/followers\/?$/u.test(id)) return "followers";
|
|
57747
|
+
if (/\/following\/?$/u.test(id)) return "following";
|
|
57748
|
+
}
|
|
57749
|
+
return null;
|
|
57750
|
+
};
|
|
57751
|
+
const list3 = (v) => [...new Set([].concat(v || []).map(word).filter(Boolean))];
|
|
57752
|
+
return { automatic: list3(can.automaticApproval), manual: list3(can.manualApproval) };
|
|
57753
|
+
}
|
|
57300
57754
|
|
|
57301
57755
|
// lib/pod/links.mjs
|
|
57302
57756
|
function linkTargets(headerValue, rel, baseUrl) {
|
|
@@ -57554,6 +58008,7 @@ async function isGone(intake, url) {
|
|
|
57554
58008
|
|
|
57555
58009
|
// lib/core/intake/group.mjs
|
|
57556
58010
|
function isModerationAsk(intake, activity) {
|
|
58011
|
+
if (intake.isModerationAskExtra?.(activity)) return true;
|
|
57557
58012
|
if (activity.type === "Block") return true;
|
|
57558
58013
|
if (activity.type === "Undo") {
|
|
57559
58014
|
return typeof activity.object === "object" && activity.object?.type === "Block";
|
|
@@ -57603,14 +58058,23 @@ async function amplify(intake, noteId, { approved = false, activity = null } = {
|
|
|
57603
58058
|
const s = intake.store.getStatuses().find((x) => x.noteId === noteId);
|
|
57604
58059
|
if (!s) return;
|
|
57605
58060
|
if (s.announcedAt) return;
|
|
57606
|
-
if (s.direct || s.nonPublic) {
|
|
58061
|
+
if (s.direct || s.nonPublic && !intake.config.private) {
|
|
57607
58062
|
intake.log(`not amplified \u2014 ${noteId} was not addressed publicly, and a group never widens a post's audience`);
|
|
57608
58063
|
return;
|
|
57609
58064
|
}
|
|
57610
58065
|
const contacts = intake.store.getContacts();
|
|
57611
58066
|
if (!contacts.followers.some((f) => f.actor === s.actor)) {
|
|
57612
|
-
intake.
|
|
57613
|
-
|
|
58067
|
+
if (intake.onStranger) {
|
|
58068
|
+
const held2 = await intake.onStranger({ noteId, actor: s.actor, activity }).catch(() => false);
|
|
58069
|
+
if (held2) {
|
|
58070
|
+
intake.log(`held for a moderator \u2014 ${s.actor} is not a member`);
|
|
58071
|
+
return;
|
|
58072
|
+
}
|
|
58073
|
+
}
|
|
58074
|
+
if (!intake.store.getContacts().followers.some((f) => f.actor === s.actor)) {
|
|
58075
|
+
intake.log(`not amplified \u2014 ${s.actor} is not a member`);
|
|
58076
|
+
return;
|
|
58077
|
+
}
|
|
57614
58078
|
}
|
|
57615
58079
|
if (intake.store.getMuted().actors.includes(s.actor)) {
|
|
57616
58080
|
intake.log(`not amplified \u2014 ${s.actor} is muted`);
|
|
@@ -57655,6 +58119,9 @@ async function amplify(intake, noteId, { approved = false, activity = null } = {
|
|
|
57655
58119
|
await intake.publisher.recordOutbox(act);
|
|
57656
58120
|
intake.store.setPending(intake.store.getPending().filter((p) => p.noteId !== noteId));
|
|
57657
58121
|
intake.log(`amplified ${noteId} \u2192 ${inboxes.length} inbox(es)`);
|
|
58122
|
+
if (intake.onCarried) {
|
|
58123
|
+
await intake.onCarried({ noteId, actor: s.actor, activity: act, sent: activity }).catch((e) => intake.log(`after the carry of ${noteId}: ${e.message}`));
|
|
58124
|
+
}
|
|
57658
58125
|
await intake.bskyGroup?.mirrorCarry(s).catch((e) => intake.log(`bluesky mirror of the carry failed: ${e.message}`));
|
|
57659
58126
|
}
|
|
57660
58127
|
async function isCoMember(intake, actor) {
|
|
@@ -57702,6 +58169,7 @@ function announceTargets(intake, author) {
|
|
|
57702
58169
|
}
|
|
57703
58170
|
|
|
57704
58171
|
// lib/core/intake/activities.mjs
|
|
58172
|
+
var idOf = (v) => typeof v === "string" ? v : v?.id;
|
|
57705
58173
|
function onAddRemove(intake, activity, actor) {
|
|
57706
58174
|
const target = typeof activity.target === "string" ? activity.target : activity.target?.id;
|
|
57707
58175
|
const ours = target && [intake.urls.followers, intake.urls.following, intake.urls.featured].filter(Boolean).includes(target);
|
|
@@ -57765,7 +58233,7 @@ async function onFollow(intake, activity, actor, { trusted = false } = {}) {
|
|
|
57765
58233
|
}
|
|
57766
58234
|
async function onUndo(intake, activity, actor, { trusted = false } = {}) {
|
|
57767
58235
|
const inner = activity.object && typeof activity.object === "object" ? activity.object : null;
|
|
57768
|
-
if (inner?.type === "Like" || inner?.type === "Announce") return onUndoReaction(intake, inner, actor, { trusted });
|
|
58236
|
+
if (inner?.type === "Like" || inner?.type === "Announce" || inner?.type === "EmojiReact") return onUndoReaction(intake, inner, actor, { trusted });
|
|
57769
58237
|
if (typeof activity.object === "object" && activity.object?.type && activity.object.type !== "Follow") return;
|
|
57770
58238
|
const named = typeof activity.object === "string" ? activity.object : activity.object?.id;
|
|
57771
58239
|
if (!named && !trusted) return;
|
|
@@ -57861,6 +58329,13 @@ async function onUndoReaction(intake, inner, actor, { trusted = false } = {}) {
|
|
|
57861
58329
|
const objectId = typeof target === "string" ? target : target?.id;
|
|
57862
58330
|
if (!objectId) return;
|
|
57863
58331
|
if (objectId.startsWith(intake.urls.notes)) {
|
|
58332
|
+
const reaction = reactionOf(inner);
|
|
58333
|
+
if (reaction) {
|
|
58334
|
+
const gone2 = intake.store.removeNotifications((n) => n.type === "reaction" && n.actor === actor && n.noteId === objectId && n.emoji === reaction.emoji);
|
|
58335
|
+
if (gone2) intake.log(`reaction ${reaction.emoji} withdrawn by ${actor} on ${objectId}`);
|
|
58336
|
+
return;
|
|
58337
|
+
}
|
|
58338
|
+
if (inner.type === "EmojiReact") return;
|
|
57864
58339
|
const type = inner.type === "Like" ? "favourite" : "reblog";
|
|
57865
58340
|
const gone = intake.store.removeNotifications((n) => n.type === type && n.actor === actor && n.noteId === objectId);
|
|
57866
58341
|
if (gone) intake.log(`${inner.type} withdrawn by ${actor} on ${objectId}`);
|
|
@@ -57921,6 +58396,9 @@ async function onDelete(intake, activity, actor) {
|
|
|
57921
58396
|
}
|
|
57922
58397
|
const s = intake.store.getStatuses().find((x) => x.noteId === objectId);
|
|
57923
58398
|
if (s) await intake.forget(s);
|
|
58399
|
+
if (s && intake.onCarriedGone) {
|
|
58400
|
+
await intake.onCarriedGone({ noteId: objectId, actor }).catch((e) => intake.log(`carried gone ${objectId}: ${e.message}`));
|
|
58401
|
+
}
|
|
57924
58402
|
intake.log(`deleted upstream: ${objectId}`);
|
|
57925
58403
|
}
|
|
57926
58404
|
async function onUpdate(intake, activity, actor) {
|
|
@@ -57945,10 +58423,16 @@ async function onUpdate(intake, activity, actor) {
|
|
|
57945
58423
|
const attachments = attachmentsOf3(note);
|
|
57946
58424
|
const freshPoll = pollOf(note);
|
|
57947
58425
|
const freshEmojis = emojisOf(note);
|
|
58426
|
+
const quote = quoteOf(note);
|
|
58427
|
+
const quoteState = quote ? await intake.quoteStateOf(note, quote) : null;
|
|
58428
|
+
const quotePolicy2 = quotePolicyOf(note, s.actor);
|
|
57948
58429
|
intake.store.updateStatus(objectId, {
|
|
57949
58430
|
content,
|
|
57950
58431
|
...attachments.length ? { attachments } : {},
|
|
57951
58432
|
emojis: freshEmojis.length ? freshEmojis : void 0,
|
|
58433
|
+
quote: quote || void 0,
|
|
58434
|
+
quoteState: quoteState || void 0,
|
|
58435
|
+
quotePolicy: quotePolicy2 || void 0,
|
|
57952
58436
|
// The edit's own stamp when the note carries one; tallies and the
|
|
57953
58437
|
// content warning follow the edit too. A poll refresh keeps our vote.
|
|
57954
58438
|
editedAt: note.updated || (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -57957,9 +58441,14 @@ async function onUpdate(intake, activity, actor) {
|
|
|
57957
58441
|
poll: { ...freshPoll, voted: !!s.poll?.voted, ownVotes: s.poll?.ownVotes || [] }
|
|
57958
58442
|
} : {}
|
|
57959
58443
|
});
|
|
58444
|
+
if (intake.onCarriedEdit) {
|
|
58445
|
+
await intake.onCarriedEdit({ noteId: objectId, actor, note }).catch((e) => intake.log(`carried edit ${objectId}: ${e.message}`));
|
|
58446
|
+
}
|
|
57960
58447
|
intake.log(`edited upstream: ${objectId}`);
|
|
57961
58448
|
}
|
|
57962
58449
|
async function onReject(intake, activity, actor, { trusted = false } = {}) {
|
|
58450
|
+
const asked = quoteAsked(intake, activity);
|
|
58451
|
+
if (asked) return onQuoteAnswered(intake, asked, activity, actor, "Reject");
|
|
57963
58452
|
if (activity.object?.type && activity.object.type !== "Follow") return;
|
|
57964
58453
|
const contacts = intake.store.getContacts();
|
|
57965
58454
|
const rec = contacts.following.find((f) => f.actor === actor);
|
|
@@ -57997,6 +58486,8 @@ async function onMove(intake, activity, actor) {
|
|
|
57997
58486
|
intake.log(`${actor} moved to ${target} \u2014 follow the new account to keep seeing them`);
|
|
57998
58487
|
}
|
|
57999
58488
|
async function onAccept(intake, activity, actor, { trusted = false } = {}) {
|
|
58489
|
+
const asked = quoteAsked(intake, activity);
|
|
58490
|
+
if (asked) return onQuoteAnswered(intake, asked, activity, actor, "Accept");
|
|
58000
58491
|
const contacts = intake.store.getContacts();
|
|
58001
58492
|
const rec = contacts.following.find((f) => f.actor === actor);
|
|
58002
58493
|
const named = typeof activity.object === "string" ? activity.object : activity.object?.id;
|
|
@@ -58012,6 +58503,86 @@ async function onAccept(intake, activity, actor, { trusted = false } = {}) {
|
|
|
58012
58503
|
intake.log(`follow accepted by ${actor}`);
|
|
58013
58504
|
}
|
|
58014
58505
|
}
|
|
58506
|
+
function quoteAsked(intake, activity) {
|
|
58507
|
+
const named = idOf(activity.object);
|
|
58508
|
+
if (!named) return null;
|
|
58509
|
+
return intake.store.getStatuses().find((x) => x.kind === "post" && x.quoteRequest?.id === named) || null;
|
|
58510
|
+
}
|
|
58511
|
+
async function onQuoteAnswered(intake, s, activity, actor, type) {
|
|
58512
|
+
if (actor !== s.quoteRequest?.actor) {
|
|
58513
|
+
intake.log(`${type} of our quote request from ${actor}, who is not ${s.quoteRequest?.actor} \u2014 ignored`);
|
|
58514
|
+
return;
|
|
58515
|
+
}
|
|
58516
|
+
if (type === "Reject") {
|
|
58517
|
+
if (s.quoteState === "rejected") return;
|
|
58518
|
+
await intake.publisher.withdrawQuote(s);
|
|
58519
|
+
return;
|
|
58520
|
+
}
|
|
58521
|
+
if (s.quoteState === "accepted") return;
|
|
58522
|
+
const resultId = idOf(activity.result);
|
|
58523
|
+
if (!resultId || !intake.sameIdentity(resultId, actor)) return `Accept of a quote request carries no authorization at ${actor}`;
|
|
58524
|
+
const auth = await intake.fetchAP(resultId);
|
|
58525
|
+
if (!auth) throw new Error(`cannot fetch quote authorization ${resultId} \u2014 will retry`);
|
|
58526
|
+
const ok = auth.id === resultId && auth.type === "QuoteAuthorization" && [].concat(auth.attributedTo || []).map(idOf).includes(actor) && idOf(auth.interactingObject) === s.noteId && idOf(auth.interactionTarget) === s.quote;
|
|
58527
|
+
if (!ok) return `quote authorization ${resultId} does not say ${s.noteId} may quote ${s.quote}`;
|
|
58528
|
+
await intake.publisher.authorizeQuote(s, resultId);
|
|
58529
|
+
}
|
|
58530
|
+
async function onQuoteRequest(intake, activity, actor) {
|
|
58531
|
+
const objectId = idOf(activity.object);
|
|
58532
|
+
const instrumentId = idOf(activity.instrument);
|
|
58533
|
+
if (!objectId || !instrumentId) return "QuoteRequest without object or instrument";
|
|
58534
|
+
const ours = objectId.startsWith(intake.urls.notes) || intake.urls.privateNotes && objectId.startsWith(intake.urls.privateNotes);
|
|
58535
|
+
if (!ours) {
|
|
58536
|
+
intake.log(`QuoteRequest from ${actor} names ${objectId}, not a post of ours \u2014 ignored`);
|
|
58537
|
+
return;
|
|
58538
|
+
}
|
|
58539
|
+
if (!intake.sameIdentity(instrumentId, actor)) return `QuoteRequest instrument/actor identity mismatch (${instrumentId})`;
|
|
58540
|
+
const s = intake.store.getStatuses().find((x) => x.noteId === objectId && x.kind === "post");
|
|
58541
|
+
if (!s) return `QuoteRequest for a post we do not hold (${objectId})`;
|
|
58542
|
+
const note = await intake.fetchAP(instrumentId);
|
|
58543
|
+
if (!note) return `quote request: instrument fetch failed (${instrumentId})`;
|
|
58544
|
+
if (note.id !== instrumentId || !isContentType(note.type)) return `quote request: instrument is not verifiable content (${instrumentId})`;
|
|
58545
|
+
if (authorOf(note, actor) !== actor) return `quote request: ${instrumentId} is not by ${actor}`;
|
|
58546
|
+
if (quoteOf(note) !== objectId) return `quote request: ${instrumentId} does not quote ${objectId}`;
|
|
58547
|
+
const doc = await intake.fetchAP(actor);
|
|
58548
|
+
const inbox = doc?.endpoints?.sharedInbox || doc?.inbox;
|
|
58549
|
+
if (!inbox) return `quote request: no inbox for ${actor}`;
|
|
58550
|
+
const { quoteAnswerActivity: quoteAnswerActivity2 } = await Promise.resolve().then(() => (init_wire_quotes(), wire_quotes_exports));
|
|
58551
|
+
const open2 = !s.visibility || s.visibility === "public" || s.visibility === "unlisted";
|
|
58552
|
+
if (!open2) {
|
|
58553
|
+
await intake.deliverer.deliver(
|
|
58554
|
+
inbox,
|
|
58555
|
+
quoteAnswerActivity2({ urls: intake.urls, type: "Reject", request: activity, to: actor, serial: intake.serial++ })
|
|
58556
|
+
);
|
|
58557
|
+
intake.log(`quote refused: ${instrumentId} asked to quote ${objectId}, which is not public`);
|
|
58558
|
+
return;
|
|
58559
|
+
}
|
|
58560
|
+
const authorization = await intake.publisher.grantQuote(objectId, instrumentId);
|
|
58561
|
+
await intake.deliverer.deliver(
|
|
58562
|
+
inbox,
|
|
58563
|
+
quoteAnswerActivity2({ urls: intake.urls, type: "Accept", request: activity, result: authorization, to: actor, serial: intake.serial++ })
|
|
58564
|
+
);
|
|
58565
|
+
const held = intake.store.getStatuses().find((x) => x.noteId === instrumentId);
|
|
58566
|
+
if (!held) {
|
|
58567
|
+
const rejected = await intake.ingestNote(instrumentId, actor, { inline: note });
|
|
58568
|
+
if (rejected) intake.log(`quote of ${objectId} by ${actor}: ${rejected}`);
|
|
58569
|
+
} else if (held.quoteState !== "accepted") {
|
|
58570
|
+
intake.store.updateStatus(instrumentId, { quote: objectId, quoteState: "accepted" });
|
|
58571
|
+
}
|
|
58572
|
+
intake.store.addNotification({ type: "quote", actor, noteId: instrumentId });
|
|
58573
|
+
intake.log(`quote allowed: ${instrumentId} quotes ${objectId}`);
|
|
58574
|
+
}
|
|
58575
|
+
async function quoteStateOf(intake, note, quote) {
|
|
58576
|
+
if (quote.startsWith(intake.urls.notes)) {
|
|
58577
|
+
const target = intake.store.getStatuses().find((x) => x.noteId === quote);
|
|
58578
|
+
return (target?.quotedBy || []).some((q) => q.note === note.id) ? "accepted" : "pending";
|
|
58579
|
+
}
|
|
58580
|
+
const authId = idOf(note.quoteAuthorization);
|
|
58581
|
+
if (!authId || !intake.sameIdentity(authId, quote)) return "pending";
|
|
58582
|
+
const auth = await intake.fetchAP(authId).catch(() => null);
|
|
58583
|
+
const ok = auth && auth.id === authId && auth.type === "QuoteAuthorization" && idOf(auth.interactingObject) === note.id && idOf(auth.interactionTarget) === quote;
|
|
58584
|
+
return ok ? "accepted" : "pending";
|
|
58585
|
+
}
|
|
58015
58586
|
|
|
58016
58587
|
// lib/core/intake/notes.mjs
|
|
58017
58588
|
init_wire();
|
|
@@ -58021,6 +58592,8 @@ function concernsUs(intake, doc, actor) {
|
|
|
58021
58592
|
if (audience.includes(intake.urls.actor) || audience.includes(intake.urls.followers)) return true;
|
|
58022
58593
|
const tagged = [].concat(doc?.tag || []).some((t) => t?.type === "Mention" && (t.href === intake.urls.actor || t.name?.includes(intake.urls.actor)));
|
|
58023
58594
|
if (tagged) return true;
|
|
58595
|
+
const quoted = quoteOf(doc);
|
|
58596
|
+
if (quoted && quoted.startsWith(intake.urls.notes)) return true;
|
|
58024
58597
|
const inReplyTo = typeof doc?.inReplyTo === "string" ? doc.inReplyTo : doc?.inReplyTo?.id;
|
|
58025
58598
|
if (!inReplyTo) return false;
|
|
58026
58599
|
if (String(inReplyTo).startsWith(intake.urls.notes)) return true;
|
|
@@ -58081,6 +58654,7 @@ async function ingestNote(intake, objectId, actor, { via, inline = null } = {})
|
|
|
58081
58654
|
const content = titledContent2(note);
|
|
58082
58655
|
const author = authorOf(note, actor);
|
|
58083
58656
|
if (!author) return `object names an author its origin does not vouch for (${objectId})`;
|
|
58657
|
+
intake.recentNotes?.set(note.id, note);
|
|
58084
58658
|
if (intake.store.isBlocked(author)) return `blocked author (${author})`;
|
|
58085
58659
|
if (!via && !intake.concernsUs(note, author)) {
|
|
58086
58660
|
return `the note its own server serves does not address us (${objectId})`;
|
|
@@ -58102,6 +58676,10 @@ async function ingestNote(intake, objectId, actor, { via, inline = null } = {})
|
|
|
58102
58676
|
const hashtags = [].concat(note.tag || []).filter((t) => t?.type === "Hashtag" && t.name).slice(0, MAX_MENTIONS).map((t) => ({ name: String(t.name).replace(/^#/, "").slice(0, 128), url: httpOnly(String(t.href || "").slice(0, MAX_URL_CHARS)) })).filter((t) => t.name);
|
|
58103
58677
|
const emojis = emojisOf(note);
|
|
58104
58678
|
const poll = pollOf(note);
|
|
58679
|
+
const quote = quoteOf(note);
|
|
58680
|
+
const quotePolicy2 = quotePolicyOf(note, author);
|
|
58681
|
+
const quoteState = quote ? await intake.quoteStateOf(note, quote) : null;
|
|
58682
|
+
if (quote) await holdQuoted(intake, quote);
|
|
58105
58683
|
const audience = [].concat(note.to || [], note.cc || []).map(String);
|
|
58106
58684
|
const direct = audience.length > 0 && !audience.includes(PUBLIC) && !audience.some((a) => a.endsWith("/followers"));
|
|
58107
58685
|
const nonPublic = audience.length > 0 && !audience.includes(PUBLIC);
|
|
@@ -58122,11 +58700,16 @@ async function ingestNote(intake, objectId, actor, { via, inline = null } = {})
|
|
|
58122
58700
|
...emojis.length ? { emojis } : {},
|
|
58123
58701
|
...mentions.length ? { mentions } : {},
|
|
58124
58702
|
...attachments.length ? { attachments } : {},
|
|
58703
|
+
...quote ? { quote, quoteState } : {},
|
|
58704
|
+
...quotePolicy2 ? { quotePolicy: quotePolicy2 } : {},
|
|
58125
58705
|
...via ? { via } : {}
|
|
58126
58706
|
});
|
|
58127
58707
|
const namesUs = mentions.some((m) => m.href === intake.urls.actor);
|
|
58128
58708
|
const replyToOurs = !!note.inReplyTo && String(note.inReplyTo).startsWith(intake.urls.notes);
|
|
58129
|
-
|
|
58709
|
+
const quotesUs = !!quote && quote.startsWith(intake.urls.notes);
|
|
58710
|
+
if (quotesUs) {
|
|
58711
|
+
intake.store.addNotification({ type: "quote", actor: author, noteId: note.id });
|
|
58712
|
+
} else if (!followed || replyToOurs || direct || namesUs) {
|
|
58130
58713
|
intake.store.addNotification({ type: "mention", actor: author, noteId: note.id });
|
|
58131
58714
|
}
|
|
58132
58715
|
if (note.inReplyTo && String(note.inReplyTo).startsWith(intake.urls.notes)) {
|
|
@@ -58134,6 +58717,39 @@ async function ingestNote(intake, objectId, actor, { via, inline = null } = {})
|
|
|
58134
58717
|
}
|
|
58135
58718
|
intake.log(`${kind}: ${note.id}${via ? ` (boosted by ${via})` : ""}`);
|
|
58136
58719
|
}
|
|
58720
|
+
async function holdQuoted(intake, quoteId) {
|
|
58721
|
+
if (intake.store.getStatuses().some((s) => s.noteId === quoteId)) return;
|
|
58722
|
+
if (intake.store.isBlocked(quoteId)) return;
|
|
58723
|
+
const q = await intake.fetchAP(quoteId).catch(() => null);
|
|
58724
|
+
if (!q || q.id !== quoteId || !isContentType(q.type)) {
|
|
58725
|
+
intake.log(`quoted post ${quoteId} could not be read \u2014 the quote shows without it`);
|
|
58726
|
+
return;
|
|
58727
|
+
}
|
|
58728
|
+
const by = authorOf(q, null);
|
|
58729
|
+
if (!by || intake.store.isBlocked(by)) return;
|
|
58730
|
+
const { attachmentsOf: attachmentsOf3, titledContent: titledContent2 } = await Promise.resolve().then(() => (init_wire(), wire_exports));
|
|
58731
|
+
const attachments = attachmentsOf3(q);
|
|
58732
|
+
const emojis = emojisOf(q);
|
|
58733
|
+
const poll = pollOf(q);
|
|
58734
|
+
const quotePolicy2 = quotePolicyOf(q, by);
|
|
58735
|
+
const mentions = [].concat(q.tag || []).filter((t) => t?.type === "Mention" && t.href && t.name).slice(0, MAX_MENTIONS).map((t) => ({ href: httpOnly(String(t.href).slice(0, MAX_URL_CHARS)), name: String(t.name).slice(0, 256) })).filter((m) => m.href);
|
|
58736
|
+
intake.store.addStatus({
|
|
58737
|
+
noteId: q.id,
|
|
58738
|
+
actor: by,
|
|
58739
|
+
content: titledContent2(q),
|
|
58740
|
+
published: q.published,
|
|
58741
|
+
inReplyTo: q.inReplyTo,
|
|
58742
|
+
kind: "remote",
|
|
58743
|
+
...q.summary ? { spoiler: String(q.summary).replace(/<[^>]*>/g, "") } : {},
|
|
58744
|
+
...q.sensitive === true ? { sensitive: true } : {},
|
|
58745
|
+
...poll ? { poll } : {},
|
|
58746
|
+
...emojis.length ? { emojis } : {},
|
|
58747
|
+
...mentions.length ? { mentions } : {},
|
|
58748
|
+
...attachments.length ? { attachments } : {},
|
|
58749
|
+
...quotePolicy2 ? { quotePolicy: quotePolicy2 } : {}
|
|
58750
|
+
});
|
|
58751
|
+
intake.log(`quoted post kept: ${q.id}`);
|
|
58752
|
+
}
|
|
58137
58753
|
async function forget(intake, s, { collect = null } = {}) {
|
|
58138
58754
|
if (s.announceActivity) {
|
|
58139
58755
|
await intake.retract(s.noteId, { collect }).catch((e) => intake.log(`retract: ${e.message}`));
|
|
@@ -63145,6 +63761,34 @@ var activitystreams_default2 = `# activitystreams.ttl \u2014 shapes for the Acti
|
|
|
63145
63761
|
sh:minCount 1 ;
|
|
63146
63762
|
sh:maxCount 1 ;
|
|
63147
63763
|
] .
|
|
63764
|
+
|
|
63765
|
+
<#Update>
|
|
63766
|
+
a sh:NodeShape ;
|
|
63767
|
+
sh:targetClass as:Update ;
|
|
63768
|
+
sh:name "Update" ;
|
|
63769
|
+
sh:description "A change to the object it names, carrying the parts that are now different." ;
|
|
63770
|
+
sh:property [
|
|
63771
|
+
sh:path as:actor ;
|
|
63772
|
+
sh:name "actor" ;
|
|
63773
|
+
sh:description "The one that performed the activity." ;
|
|
63774
|
+
sh:nodeKind sh:BlankNodeOrIRI ;
|
|
63775
|
+
sh:minCount 1 ;
|
|
63776
|
+
sh:maxCount 1 ;
|
|
63777
|
+
] ;
|
|
63778
|
+
sh:property [
|
|
63779
|
+
sh:path as:object ;
|
|
63780
|
+
sh:name "object" ;
|
|
63781
|
+
sh:description "The thing the activity is about." ;
|
|
63782
|
+
sh:minCount 1 ;
|
|
63783
|
+
sh:maxCount 1 ;
|
|
63784
|
+
] ;
|
|
63785
|
+
sh:property [
|
|
63786
|
+
sh:path ( as:object as:closed ) ;
|
|
63787
|
+
sh:name "closed" ;
|
|
63788
|
+
sh:description "The time the object stopped taking anything more." ;
|
|
63789
|
+
sh:datatype xsd:dateTime ;
|
|
63790
|
+
sh:maxCount 1 ;
|
|
63791
|
+
] .
|
|
63148
63792
|
`;
|
|
63149
63793
|
|
|
63150
63794
|
// web/app/shims/shapes-text.mjs
|
|
@@ -63173,6 +63817,18 @@ function asTerm(q) {
|
|
|
63173
63817
|
};
|
|
63174
63818
|
return rdf3.quad(term3(q.subject), term3(q.predicate), term3(q.object));
|
|
63175
63819
|
}
|
|
63820
|
+
var SH_NAME = "http://www.w3.org/ns/shacl#name";
|
|
63821
|
+
function pathName(r) {
|
|
63822
|
+
const direct = r.path?.value ?? null;
|
|
63823
|
+
if (direct && /[#/]/u.test(direct)) return direct;
|
|
63824
|
+
const shape = r.sourceShape;
|
|
63825
|
+
if (shape) {
|
|
63826
|
+
for (const q of shacl().$shapes?.dataset ?? []) {
|
|
63827
|
+
if (q.subject.equals(shape) && q.predicate.value === SH_NAME) return q.object.value;
|
|
63828
|
+
}
|
|
63829
|
+
}
|
|
63830
|
+
return direct;
|
|
63831
|
+
}
|
|
63176
63832
|
async function checkShapes(quads) {
|
|
63177
63833
|
if (!Array.isArray(quads) || quads.length === 0) return null;
|
|
63178
63834
|
let report;
|
|
@@ -63190,7 +63846,12 @@ async function checkShapes(quads) {
|
|
|
63190
63846
|
conforms: false,
|
|
63191
63847
|
results: report.results.slice(0, 10).map((r) => ({
|
|
63192
63848
|
focus: r.focusNode?.value ?? null,
|
|
63193
|
-
path:
|
|
63849
|
+
// A SEQUENCE path — `( as:object as:closed )` — is an RDF list, so its
|
|
63850
|
+
// node is blank and `.value` is a generated label like "n3-13". Reported
|
|
63851
|
+
// as that, the record named nothing a reader could act on. The shape's
|
|
63852
|
+
// own `sh:name` is what it is called, so that is used when the path
|
|
63853
|
+
// cannot name itself.
|
|
63854
|
+
path: pathName(r),
|
|
63194
63855
|
message: r.message?.[0]?.value ?? String(r.sourceConstraintComponent?.value ?? "does not fit the shape"),
|
|
63195
63856
|
shape: r.sourceShape?.value ?? null
|
|
63196
63857
|
}))
|
|
@@ -63457,6 +64118,7 @@ var Intake = class {
|
|
|
63457
64118
|
}
|
|
63458
64119
|
this.lastDrain = (/* @__PURE__ */ new Date()).toISOString();
|
|
63459
64120
|
this.lastDrainAtMs = Date.now();
|
|
64121
|
+
if (this.afterDrain) setTimeout(() => this.afterDrain(), 0);
|
|
63460
64122
|
this._pruneAttempts();
|
|
63461
64123
|
let all;
|
|
63462
64124
|
try {
|
|
@@ -63608,8 +64270,11 @@ var Intake = class {
|
|
|
63608
64270
|
return this.onCreate(activity, actor, { trusted });
|
|
63609
64271
|
case "Accept":
|
|
63610
64272
|
return this.onAccept(activity, actor, { trusted });
|
|
64273
|
+
case "QuoteRequest":
|
|
64274
|
+
return this.onQuoteRequest(activity, actor, { trusted });
|
|
63611
64275
|
case "Like":
|
|
63612
|
-
case "Announce":
|
|
64276
|
+
case "Announce":
|
|
64277
|
+
case "EmojiReact": {
|
|
63613
64278
|
const wrapped = activity.object;
|
|
63614
64279
|
if (activity.type === "Announce" && wrapped && typeof wrapped === "object" && wrapped.type === "Delete") {
|
|
63615
64280
|
return this.onAnnouncedDelete(actor, wrapped);
|
|
@@ -63617,6 +64282,27 @@ var Intake = class {
|
|
|
63617
64282
|
const inner = wrapped && typeof wrapped === "object" && (wrapped.type === "Create" || wrapped.type === "Update") ? wrapped.object : wrapped;
|
|
63618
64283
|
const objectId = typeof inner === "string" ? inner : inner?.id;
|
|
63619
64284
|
this.log(`${activity.type} from ${actor} on ${objectId}`);
|
|
64285
|
+
const reaction = activity.type !== "Announce" ? reactionOf(activity) : null;
|
|
64286
|
+
if (reaction) {
|
|
64287
|
+
if (!objectId || !objectId.startsWith(this.urls.notes)) return;
|
|
64288
|
+
this.store.addNotification({
|
|
64289
|
+
type: "reaction",
|
|
64290
|
+
actor,
|
|
64291
|
+
noteId: objectId,
|
|
64292
|
+
emoji: reaction.emoji,
|
|
64293
|
+
...reaction.url ? { url: reaction.url } : {},
|
|
64294
|
+
...this.known(actor) ? {} : { unverified: true }
|
|
64295
|
+
});
|
|
64296
|
+
if (reaction.url) {
|
|
64297
|
+
const s = this.store.getStatuses().find((x) => x.noteId === objectId);
|
|
64298
|
+
const shortcode = reaction.emoji.replace(/^:|:$/g, "");
|
|
64299
|
+
if (s && !(s.emojis || []).some((e) => e.shortcode === shortcode) && (s.emojis || []).length < MAX_EMOJIS) {
|
|
64300
|
+
this.store.updateStatus(objectId, { emojis: [...s.emojis || [], { shortcode, url: reaction.url }] });
|
|
64301
|
+
}
|
|
64302
|
+
}
|
|
64303
|
+
return;
|
|
64304
|
+
}
|
|
64305
|
+
if (activity.type === "EmojiReact") return;
|
|
63620
64306
|
if (objectId && objectId.startsWith(this.urls.notes)) {
|
|
63621
64307
|
this.store.addNotification({
|
|
63622
64308
|
type: activity.type === "Like" ? "favourite" : "reblog",
|
|
@@ -63642,6 +64328,9 @@ var Intake = class {
|
|
|
63642
64328
|
return this.onAddRemove(activity, actor);
|
|
63643
64329
|
case "Block":
|
|
63644
64330
|
return this.onBlock(activity, actor, { trusted });
|
|
64331
|
+
case "Flag":
|
|
64332
|
+
if (this.onReport) return this.onReport(activity, actor, { trusted });
|
|
64333
|
+
return;
|
|
63645
64334
|
default:
|
|
63646
64335
|
this.log(`ignored ${activity.type} from ${actor}`);
|
|
63647
64336
|
}
|
|
@@ -63747,6 +64436,12 @@ var Intake = class {
|
|
|
63747
64436
|
onAnnounce(...a) {
|
|
63748
64437
|
return onAnnounce(this, ...a);
|
|
63749
64438
|
}
|
|
64439
|
+
onQuoteRequest(...a) {
|
|
64440
|
+
return onQuoteRequest(this, ...a);
|
|
64441
|
+
}
|
|
64442
|
+
quoteStateOf(...a) {
|
|
64443
|
+
return quoteStateOf(this, ...a);
|
|
64444
|
+
}
|
|
63750
64445
|
onDelete(...a) {
|
|
63751
64446
|
return onDelete(this, ...a);
|
|
63752
64447
|
}
|
|
@@ -64230,6 +64925,7 @@ async function pinStatus(agent2, s, pinned) {
|
|
|
64230
64925
|
init_wire();
|
|
64231
64926
|
var MAX_BODY = 512 * 1024;
|
|
64232
64927
|
var MAX_INBOX_PAGE = 500;
|
|
64928
|
+
var MAX_OUTBOX_PAGE = 50;
|
|
64233
64929
|
var ACTIVITY_TYPES = /* @__PURE__ */ new Set([
|
|
64234
64930
|
"Create",
|
|
64235
64931
|
"Update",
|
|
@@ -64246,7 +64942,7 @@ var ACTIVITY_TYPES = /* @__PURE__ */ new Set([
|
|
|
64246
64942
|
"Move"
|
|
64247
64943
|
]);
|
|
64248
64944
|
var arr = (v) => v === void 0 || v === null ? [] : Array.isArray(v) ? v : [v];
|
|
64249
|
-
var
|
|
64945
|
+
var idOf2 = (v) => typeof v === "string" ? v : v?.id || null;
|
|
64250
64946
|
function readBody(req) {
|
|
64251
64947
|
return new Promise((resolve2, reject) => {
|
|
64252
64948
|
let size = 0;
|
|
@@ -64265,10 +64961,12 @@ function readBody(req) {
|
|
|
64265
64961
|
});
|
|
64266
64962
|
}
|
|
64267
64963
|
var C2S = class {
|
|
64268
|
-
constructor({ agent: agent2, log: log2 = console.log, auth }) {
|
|
64964
|
+
constructor({ agent: agent2, log: log2 = console.log, auth, scheme = null, mount = "" }) {
|
|
64269
64965
|
this.agent = agent2;
|
|
64270
64966
|
this.log = log2;
|
|
64271
64967
|
this.auth = auth;
|
|
64968
|
+
this.scheme = scheme;
|
|
64969
|
+
this.mount = mount;
|
|
64272
64970
|
}
|
|
64273
64971
|
get store() {
|
|
64274
64972
|
return this.agent.store;
|
|
@@ -64355,6 +65053,79 @@ var C2S = class {
|
|
|
64355
65053
|
orderedItems: kept.map((k) => k.activity)
|
|
64356
65054
|
});
|
|
64357
65055
|
}
|
|
65056
|
+
// The actor a client-to-server client on this machine reads. The same
|
|
65057
|
+
// document the pod holds, but for the fields naming where a write goes —
|
|
65058
|
+
// the outbox and the two OAuth endpoints — and for `id`, which names this
|
|
65059
|
+
// address so the client files its credential here and comes back. The pod's
|
|
65060
|
+
// id stays on as an alias; the two are one identity.
|
|
65061
|
+
// What this agent calls itself, to the client that just asked. The Host
|
|
65062
|
+
// passed the Authorities firewall before any route ran, so whichever alias
|
|
65063
|
+
// the client used is one this agent answers on. The scheme is read off the
|
|
65064
|
+
// connection unless the caller named one: only the Server passes a scheme,
|
|
65065
|
+
// and standalone this listener is its own TLS.
|
|
65066
|
+
localOrigin(req) {
|
|
65067
|
+
const host = req.headers.host;
|
|
65068
|
+
if (!host) return null;
|
|
65069
|
+
const scheme = this.scheme || (req.socket?.encrypted || req.headers["x-forwarded-proto"] === "https" ? "https" : "http");
|
|
65070
|
+
return `${scheme}://${host}${this.mount}/`;
|
|
65071
|
+
}
|
|
65072
|
+
/**
|
|
65073
|
+
* The owner's own outbox, from what this agent recorded. The pod holds the
|
|
65074
|
+
* published copy and it stays the canonical one; this is served only where
|
|
65075
|
+
* that copy cannot be read by the client asking.
|
|
65076
|
+
*/
|
|
65077
|
+
sendLocalOutbox(res, url, origin) {
|
|
65078
|
+
const id = `${origin}ap/outbox`;
|
|
65079
|
+
const outbox = this.store.read("outbox.json", []);
|
|
65080
|
+
const page2 = url?.searchParams?.get("page") || null;
|
|
65081
|
+
const ct = { "content-type": "application/activity+json; charset=utf-8" };
|
|
65082
|
+
if (!page2) {
|
|
65083
|
+
return this.send(res, 200, {
|
|
65084
|
+
"@context": AS_CTX,
|
|
65085
|
+
id,
|
|
65086
|
+
type: "OrderedCollection",
|
|
65087
|
+
totalItems: outbox.length,
|
|
65088
|
+
...outbox.length ? { first: `${id}?page=1` } : { orderedItems: [] }
|
|
65089
|
+
}, ct);
|
|
65090
|
+
}
|
|
65091
|
+
const n = Number(page2);
|
|
65092
|
+
if (!Number.isInteger(n) || n < 1) {
|
|
65093
|
+
return this.send(res, 400, { error: "page is a whole number from 1" });
|
|
65094
|
+
}
|
|
65095
|
+
const start = (n - 1) * MAX_OUTBOX_PAGE;
|
|
65096
|
+
const items = outbox.slice(start, start + MAX_OUTBOX_PAGE);
|
|
65097
|
+
return this.send(res, 200, {
|
|
65098
|
+
"@context": AS_CTX,
|
|
65099
|
+
id: `${id}?page=${n}`,
|
|
65100
|
+
type: "OrderedCollectionPage",
|
|
65101
|
+
partOf: id,
|
|
65102
|
+
...start + items.length < outbox.length ? { next: `${id}?page=${n + 1}` } : {},
|
|
65103
|
+
// A post is recorded by its object id and published as the Create beside
|
|
65104
|
+
// it — the same mapping the pod's copy uses, from the same helper.
|
|
65105
|
+
orderedItems: items.map(outboxWireItem)
|
|
65106
|
+
}, ct);
|
|
65107
|
+
}
|
|
65108
|
+
sendLocalActor(req, res, origin) {
|
|
65109
|
+
const pub = this.agent.publisher;
|
|
65110
|
+
const cfg = pub.config || {};
|
|
65111
|
+
const gw = cfg.gateway;
|
|
65112
|
+
const gwActive = gw && gw.url && gw.mode && gw.mode !== "off";
|
|
65113
|
+
const doc = pub.actorDocFor({
|
|
65114
|
+
inbox: gwActive ? gw.url : cfg.inboxUrl || null,
|
|
65115
|
+
clientOrigin: origin
|
|
65116
|
+
});
|
|
65117
|
+
const canonical2 = doc.id;
|
|
65118
|
+
doc.id = `${origin}ap/actor`;
|
|
65119
|
+
doc.alsoKnownAs = [.../* @__PURE__ */ new Set([...doc.alsoKnownAs || [], canonical2])];
|
|
65120
|
+
const body = JSON.stringify(doc);
|
|
65121
|
+
res.writeHead(200, {
|
|
65122
|
+
"content-type": "application/activity+json; charset=utf-8",
|
|
65123
|
+
"cache-control": "no-store",
|
|
65124
|
+
"content-length": Buffer.byteLength(body)
|
|
65125
|
+
});
|
|
65126
|
+
res.end(req.method === "HEAD" ? void 0 : body);
|
|
65127
|
+
return true;
|
|
65128
|
+
}
|
|
64358
65129
|
async handle(req, res, pathname, url) {
|
|
64359
65130
|
if (pathname !== "/ap/outbox" && pathname !== "/ap/actor" && pathname !== "/ap/inbox") return false;
|
|
64360
65131
|
if (req.method === "OPTIONS") {
|
|
@@ -64374,6 +65145,9 @@ var C2S = class {
|
|
|
64374
65145
|
return this.sendInbox(res, url);
|
|
64375
65146
|
}
|
|
64376
65147
|
if (req.method === "GET" || req.method === "HEAD") {
|
|
65148
|
+
const local = this.agent.publisher?.clientOrigin ? null : this.localOrigin(req);
|
|
65149
|
+
if (local && pathname === "/ap/actor") return this.sendLocalActor(req, res, local);
|
|
65150
|
+
if (local && pathname === "/ap/outbox") return this.sendLocalOutbox(res, url, local);
|
|
64377
65151
|
const target = pathname === "/ap/actor" ? this.urls.actor : this.urls.outbox;
|
|
64378
65152
|
res.writeHead(303, { location: target, "cache-control": "no-store" });
|
|
64379
65153
|
res.end();
|
|
@@ -64414,8 +65188,8 @@ var C2S = class {
|
|
|
64414
65188
|
// every client means by a post with no audience chosen, and what a client
|
|
64415
65189
|
// that never addresses (dokieli's annotations) needs.
|
|
64416
65190
|
visibilityOf(activity, object) {
|
|
64417
|
-
const to = arr(activity.to ?? object?.to).map(
|
|
64418
|
-
const cc = arr(activity.cc ?? object?.cc).map(
|
|
65191
|
+
const to = arr(activity.to ?? object?.to).map(idOf2);
|
|
65192
|
+
const cc = arr(activity.cc ?? object?.cc).map(idOf2);
|
|
64419
65193
|
if (!to.length && !cc.length) return "public";
|
|
64420
65194
|
if (to.includes(PUBLIC)) return "public";
|
|
64421
65195
|
if (cc.includes(PUBLIC)) return "unlisted";
|
|
@@ -64441,13 +65215,13 @@ var C2S = class {
|
|
|
64441
65215
|
// in to/cc are listed and delivered to; the ones in bto/bcc are delivered
|
|
64442
65216
|
// to and never listed (§6).
|
|
64443
65217
|
addressedActors(activity, object) {
|
|
64444
|
-
const pick = (...fields) => [...new Set(fields.flatMap((f) => arr(activity[f] ?? object?.[f]).map(
|
|
65218
|
+
const pick = (...fields) => [...new Set(fields.flatMap((f) => arr(activity[f] ?? object?.[f]).map(idOf2)))].filter((a) => typeof a === "string" && /^https?:\/\//u.test(a) && a !== PUBLIC && a !== this.urls.followers && a !== this.urls.actor);
|
|
64445
65219
|
return { also: pick("to", "cc"), deliverTo: pick("bto", "bcc") };
|
|
64446
65220
|
}
|
|
64447
65221
|
async _dispatch(activity, { slug, raw, reply }) {
|
|
64448
65222
|
const agent2 = this.agent;
|
|
64449
65223
|
const object = typeof activity.object === "object" && activity.object !== null ? activity.object : null;
|
|
64450
|
-
const objectId =
|
|
65224
|
+
const objectId = idOf2(activity.object);
|
|
64451
65225
|
switch (activity.type) {
|
|
64452
65226
|
case "Create": {
|
|
64453
65227
|
const makes = object?.type || "Note";
|
|
@@ -64470,7 +65244,7 @@ var C2S = class {
|
|
|
64470
65244
|
options: titles,
|
|
64471
65245
|
multiple: !one.length && many.length > 0,
|
|
64472
65246
|
expiresAt: object.endTime || null,
|
|
64473
|
-
inReplyTo:
|
|
65247
|
+
inReplyTo: idOf2(object.inReplyTo) || void 0,
|
|
64474
65248
|
visibility,
|
|
64475
65249
|
spoilerText: object.summary || null,
|
|
64476
65250
|
sensitive: object.sensitive === true
|
|
@@ -64490,7 +65264,7 @@ var C2S = class {
|
|
|
64490
65264
|
...a?.name ? { description: a.name } : {}
|
|
64491
65265
|
})).filter((a) => a.url);
|
|
64492
65266
|
const note = await agent2.publisher.publishNote(text, {
|
|
64493
|
-
inReplyTo:
|
|
65267
|
+
inReplyTo: idOf2(object.inReplyTo) || void 0,
|
|
64494
65268
|
attachments,
|
|
64495
65269
|
visibility,
|
|
64496
65270
|
spoilerText: object.summary || null,
|
|
@@ -64583,28 +65357,28 @@ var C2S = class {
|
|
|
64583
65357
|
}
|
|
64584
65358
|
case "Undo": {
|
|
64585
65359
|
const inner = object;
|
|
64586
|
-
const innerId =
|
|
65360
|
+
const innerId = idOf2(activity.object);
|
|
64587
65361
|
if (inner?.type === "Block") {
|
|
64588
|
-
const target =
|
|
65362
|
+
const target = idOf2(inner.object);
|
|
64589
65363
|
if (!target) return reply(400, { error: "unblock whom?" });
|
|
64590
65364
|
await unblockActor(agent2, target);
|
|
64591
65365
|
return reply(200, { ok: true, object: target });
|
|
64592
65366
|
}
|
|
64593
65367
|
const statuses = this.store.getStatuses();
|
|
64594
65368
|
let s = innerId ? statuses.find((x) => x.likeActivity?.id === innerId) : null;
|
|
64595
|
-
if (!s && inner?.type === "Like") s = this.byIri(
|
|
65369
|
+
if (!s && inner?.type === "Like") s = this.byIri(idOf2(inner.object));
|
|
64596
65370
|
if (s?.favourited) {
|
|
64597
65371
|
const updated = await unfavourite(agent2, s);
|
|
64598
65372
|
return reply(200, { ok: true, object: updated.noteId });
|
|
64599
65373
|
}
|
|
64600
65374
|
s = innerId ? statuses.find((x) => x.announceActivity?.id === innerId) : null;
|
|
64601
|
-
if (!s && inner?.type === "Announce") s = this.byIri(
|
|
65375
|
+
if (!s && inner?.type === "Announce") s = this.byIri(idOf2(inner.object));
|
|
64602
65376
|
if (s?.reblogged) {
|
|
64603
65377
|
const updated = await unreblog(agent2, s);
|
|
64604
65378
|
return reply(200, { ok: true, object: updated.noteId });
|
|
64605
65379
|
}
|
|
64606
65380
|
const following = this.store.getContacts().following;
|
|
64607
|
-
const rec = following.find((f) => f.followActivity?.id === innerId) || (inner?.type === "Follow" ? following.find((f) => f.actor ===
|
|
65381
|
+
const rec = following.find((f) => f.followActivity?.id === innerId) || (inner?.type === "Follow" ? following.find((f) => f.actor === idOf2(inner.object)) : null);
|
|
64608
65382
|
if (rec) {
|
|
64609
65383
|
await unfollowActor(agent2, rec.actor);
|
|
64610
65384
|
return reply(200, { ok: true, object: rec.actor });
|
|
@@ -64618,7 +65392,7 @@ var C2S = class {
|
|
|
64618
65392
|
}
|
|
64619
65393
|
case "Add":
|
|
64620
65394
|
case "Remove": {
|
|
64621
|
-
if (
|
|
65395
|
+
if (idOf2(activity.target) !== this.urls.featured) {
|
|
64622
65396
|
return reply(422, { error: "the featured collection is the one Add/Remove edits here" });
|
|
64623
65397
|
}
|
|
64624
65398
|
const s = this.byIri(objectId);
|
|
@@ -64628,7 +65402,7 @@ var C2S = class {
|
|
|
64628
65402
|
}
|
|
64629
65403
|
case "Accept":
|
|
64630
65404
|
case "Reject": {
|
|
64631
|
-
const requester = object?.actor ?
|
|
65405
|
+
const requester = object?.actor ? idOf2(object.actor) : objectId;
|
|
64632
65406
|
if (!requester) return reply(400, { error: "whose request? object must name the Follow or its actor" });
|
|
64633
65407
|
const r = activity.type === "Accept" ? await admitRequest(agent2, requester).catch((e) => ({ error: e.message })) : await refuseRequest(agent2, requester).catch((e) => ({ error: e.message }));
|
|
64634
65408
|
if (r.error) return reply(404, { error: r.error });
|
|
@@ -64649,11 +65423,21 @@ var RENEW_MS = 9e4;
|
|
|
64649
65423
|
var JITTER = () => 0.85 + Math.random() * 0.3;
|
|
64650
65424
|
var UNREADABLE = /* @__PURE__ */ Symbol("lease-unreadable");
|
|
64651
65425
|
var Lease = class {
|
|
64652
|
-
|
|
65426
|
+
// `id` names the HOLDER, and acquire() lets a holder reclaim its own lease
|
|
65427
|
+
// without waiting for the TTL — so whether it is stable decides whether a
|
|
65428
|
+
// restart is the same agent coming back or a second one arriving. A process
|
|
65429
|
+
// that is gone for good wants a new one each time; a browser does not, since
|
|
65430
|
+
// its agent lives in a service worker the browser is free to kill and restart
|
|
65431
|
+
// whenever it likes. Left to the default, every one of those restarts looked
|
|
65432
|
+
// like another device and asked the owner to take over their own account —
|
|
65433
|
+
// which is what switching between the two clients did, because that is a
|
|
65434
|
+
// navigation and a navigation can take the worker with it. The browser build
|
|
65435
|
+
// passes one it keeps.
|
|
65436
|
+
constructor({ url, fetchImpl, log: log2 = console.log, id = null }) {
|
|
64653
65437
|
this.url = url;
|
|
64654
65438
|
this.fetchImpl = fetchImpl;
|
|
64655
65439
|
this.log = log2;
|
|
64656
|
-
this.id = node_crypto_default.randomUUID();
|
|
65440
|
+
this.id = id || node_crypto_default.randomUUID();
|
|
64657
65441
|
this.stopped = false;
|
|
64658
65442
|
this.timer = null;
|
|
64659
65443
|
this.heldUntil = 0;
|
|
@@ -64968,6 +65752,7 @@ function readBody2(req) {
|
|
|
64968
65752
|
const ct = String(req.headers["content-type"] || "");
|
|
64969
65753
|
try {
|
|
64970
65754
|
if (ct.includes("application/json")) return resolve2(data ? JSON.parse(data) : {});
|
|
65755
|
+
if (ct.includes("multipart/form-data")) return resolve2(textFields(data, ct));
|
|
64971
65756
|
const form = new URLSearchParams(data);
|
|
64972
65757
|
const out = {};
|
|
64973
65758
|
for (const key of new Set(form.keys())) {
|
|
@@ -64981,6 +65766,76 @@ function readBody2(req) {
|
|
|
64981
65766
|
req.on("error", reject);
|
|
64982
65767
|
});
|
|
64983
65768
|
}
|
|
65769
|
+
function textFields(data, contentType) {
|
|
65770
|
+
const m = /boundary=(?:"([^"]+)"|([^;]+))/.exec(contentType);
|
|
65771
|
+
if (!m) return {};
|
|
65772
|
+
const out = {};
|
|
65773
|
+
for (const part of data.split("--" + (m[1] || m[2]).trim())) {
|
|
65774
|
+
const sep = part.indexOf("\r\n\r\n");
|
|
65775
|
+
if (sep < 0) continue;
|
|
65776
|
+
const head = part.slice(0, sep);
|
|
65777
|
+
const name = /name="([^"]*)"/.exec(head)?.[1];
|
|
65778
|
+
if (!name || /filename="/.test(head)) continue;
|
|
65779
|
+
const value = part.slice(sep + 4).replace(/\r\n$/, "");
|
|
65780
|
+
if (name.endsWith("[]")) (out[name] ||= []).push(value);
|
|
65781
|
+
else out[name] = value;
|
|
65782
|
+
}
|
|
65783
|
+
return out;
|
|
65784
|
+
}
|
|
65785
|
+
function readMultipart(req, limit = 12e6) {
|
|
65786
|
+
return new Promise((resolve2, reject) => {
|
|
65787
|
+
const chunks = [];
|
|
65788
|
+
let n = 0;
|
|
65789
|
+
req.on("data", (c) => {
|
|
65790
|
+
n += c.length;
|
|
65791
|
+
if (n > limit) {
|
|
65792
|
+
reject(new Error("upload too large"));
|
|
65793
|
+
req.destroy();
|
|
65794
|
+
return;
|
|
65795
|
+
}
|
|
65796
|
+
chunks.push(c);
|
|
65797
|
+
});
|
|
65798
|
+
req.on("error", reject);
|
|
65799
|
+
req.on("end", () => {
|
|
65800
|
+
try {
|
|
65801
|
+
const m = /boundary=(?:"([^"]+)"|([^;]+))/.exec(String(req.headers["content-type"] || ""));
|
|
65802
|
+
if (!m) return resolve2({ fields: {}, file: null });
|
|
65803
|
+
const buf = Buffer.concat(chunks);
|
|
65804
|
+
const boundary = Buffer.from("--" + (m[1] || m[2]).trim());
|
|
65805
|
+
const fields = {};
|
|
65806
|
+
const files = {};
|
|
65807
|
+
let file = null;
|
|
65808
|
+
let i = buf.indexOf(boundary);
|
|
65809
|
+
while (i >= 0) {
|
|
65810
|
+
const start = i + boundary.length;
|
|
65811
|
+
if (buf.slice(start, start + 2).toString() === "--") break;
|
|
65812
|
+
const next = buf.indexOf(boundary, start);
|
|
65813
|
+
if (next < 0) break;
|
|
65814
|
+
const part = buf.slice(start + 2, next - 2);
|
|
65815
|
+
const sep = part.indexOf("\r\n\r\n");
|
|
65816
|
+
if (sep >= 0) {
|
|
65817
|
+
const head = part.slice(0, sep).toString();
|
|
65818
|
+
const body = part.slice(sep + 4);
|
|
65819
|
+
const name = /name="([^"]*)"/.exec(head)?.[1];
|
|
65820
|
+
const filename = /filename="([^"]*)"/.exec(head)?.[1];
|
|
65821
|
+
if (filename !== void 0) {
|
|
65822
|
+
file = {
|
|
65823
|
+
filename,
|
|
65824
|
+
contentType: /content-type:\s*([^\r\n]+)/i.exec(head)?.[1]?.trim() || "application/octet-stream",
|
|
65825
|
+
data: body
|
|
65826
|
+
};
|
|
65827
|
+
if (name) files[name] = file;
|
|
65828
|
+
} else if (name) fields[name] = body.toString();
|
|
65829
|
+
}
|
|
65830
|
+
i = next;
|
|
65831
|
+
}
|
|
65832
|
+
resolve2({ fields, file, files });
|
|
65833
|
+
} catch (e) {
|
|
65834
|
+
reject(e);
|
|
65835
|
+
}
|
|
65836
|
+
});
|
|
65837
|
+
});
|
|
65838
|
+
}
|
|
64984
65839
|
|
|
64985
65840
|
// lib/client/masto/oauth.mjs
|
|
64986
65841
|
var TOKEN_TTL_MS = 90 * 24 * 60 * 60 * 1e3;
|
|
@@ -65008,6 +65863,39 @@ function checkPassword(rec, password) {
|
|
|
65008
65863
|
}
|
|
65009
65864
|
var escapeHtml = (s) => String(s).replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
65010
65865
|
var parseRedirects = (v) => (Array.isArray(v) ? v : String(v || "").split(/\s+/)).map((s) => s.trim()).filter(Boolean);
|
|
65866
|
+
function sendPodSigninPage(res, client, webId, mount = "") {
|
|
65867
|
+
let asking = "";
|
|
65868
|
+
if (client && (client.name || client.redirect)) {
|
|
65869
|
+
let where = "";
|
|
65870
|
+
try {
|
|
65871
|
+
where = client.redirect ? new URL(client.redirect).host : "";
|
|
65872
|
+
} catch {
|
|
65873
|
+
}
|
|
65874
|
+
const who = client.name ? escapeHtml(client.name) : where ? escapeHtml(where) : "A client";
|
|
65875
|
+
asking = `<p><strong>${who}</strong> is asking to use your account${where ? `, and will be sent back to <code>${escapeHtml(where)}</code>` : ""}.</p>`;
|
|
65876
|
+
}
|
|
65877
|
+
res.writeHead(200, { "content-type": "text/html; charset=utf-8", "cache-control": "no-cache" });
|
|
65878
|
+
res.end(`<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
|
|
65879
|
+
<title>Sign in with your pod</title>
|
|
65880
|
+
<style>:root{color-scheme:light dark;--heading:#1a4f8a;--btn:#3d6b35;--line:#9a9a9a}
|
|
65881
|
+
body{font:20px Arial,Helvetica,sans-serif;max-width:28rem;margin:12vh auto;padding:0 1rem}
|
|
65882
|
+
h1{color:var(--heading);font-size:1.5rem}
|
|
65883
|
+
code{word-break:break-all;background:rgba(120,120,120,.12);padding:.05em .35em;border-radius:4px}
|
|
65884
|
+
button{font:inherit;padding:.6rem 1.1rem;background:var(--btn);color:#fff;border:none;border-radius:9px;cursor:pointer}
|
|
65885
|
+
button[hidden]{display:none}
|
|
65886
|
+
#signin-status{min-height:1.5em;padding:.6rem .8rem;border:1px solid var(--line);border-radius:8px;margin-top:1rem}
|
|
65887
|
+
@media (prefers-color-scheme:dark){:root{--heading:#7fb3e8;--btn:#5a8a4f}}</style></head><body>
|
|
65888
|
+
<main>
|
|
65889
|
+
<h1>Sign in with your pod</h1>
|
|
65890
|
+
${asking}
|
|
65891
|
+
<p>This account belongs to <a id="webid" href="${escapeHtml(webId)}">${escapeHtml(webId)}</a>. Sign in at that pod to allow it.</p>
|
|
65892
|
+
<p><button type="button" id="signin" hidden>Sign in with your pod</button></p>
|
|
65893
|
+
<p id="signin-status" role="status" aria-live="polite"></p>
|
|
65894
|
+
</main>
|
|
65895
|
+
<script type="module" src="${escapeHtml(mount)}/oauth/session/signin.mjs"><\/script>
|
|
65896
|
+
</body></html>`);
|
|
65897
|
+
return true;
|
|
65898
|
+
}
|
|
65011
65899
|
function sendLoginForm(res, params, error2 = "", client = null, status2 = null, headers = {}) {
|
|
65012
65900
|
const hidden = [...params.entries()].filter(([k]) => k !== "password").map(([k, v]) => `<input type="hidden" name="${escapeHtml(k)}" value="${escapeHtml(v)}">`).join("\n");
|
|
65013
65901
|
let asking = "<p>Enter the agent password to authorize this client.</p>";
|
|
@@ -65276,6 +66164,16 @@ async function handle(api, ctx) {
|
|
|
65276
66164
|
params = new URLSearchParams(body);
|
|
65277
66165
|
}
|
|
65278
66166
|
const redirect = params.get("redirect_uri") || "";
|
|
66167
|
+
let proved = false;
|
|
66168
|
+
if (req.method === "POST" && req.headers.authorization && req.headers.dpop && api.podAuth) {
|
|
66169
|
+
const proof = await api.podAuth(req, pathname);
|
|
66170
|
+
if (!proof.ok) return send(proof.status || 401, { error: proof.error });
|
|
66171
|
+
proved = true;
|
|
66172
|
+
}
|
|
66173
|
+
const ownerWebId = api.ownerWebId?.() || null;
|
|
66174
|
+
if (req.method === "GET" && !params.get("client_id") && params.get("code") && params.get("state") && ownerWebId) {
|
|
66175
|
+
return sendPodSigninPage(res, null, ownerWebId, api.mount || "");
|
|
66176
|
+
}
|
|
65279
66177
|
const app = api.findApp(params.get("client_id") || "");
|
|
65280
66178
|
const doc = app ? null : await api.resolveClientDocument(params.get("client_id") || "");
|
|
65281
66179
|
const external = !!app || !!doc;
|
|
@@ -65283,13 +66181,14 @@ async function handle(api, ctx) {
|
|
|
65283
66181
|
api.log(`authorize refused: cross-site navigation to the mint from ${req.headers.referer || "nowhere"}`);
|
|
65284
66182
|
return send(403, { error: "a cross-site navigation may not authorize a client" });
|
|
65285
66183
|
}
|
|
65286
|
-
|
|
66184
|
+
const client = { name: app?.name || doc?.name || null, redirect, scope: params.get("scope") || "read" };
|
|
66185
|
+
if (external && !proved && !api.store.getConfig()?.uiPassword && !api.redirectAllowed(redirect)) {
|
|
66186
|
+
if (req.method === "GET" && ownerWebId) return sendPodSigninPage(res, client, ownerWebId, api.mount || "");
|
|
65287
66187
|
api.log(`authorize refused: no UI password, and "${redirect}" is not an address of this agent`);
|
|
65288
66188
|
return send(403, {
|
|
65289
66189
|
error: "this client asks to be sent somewhere other than this agent, and no password is set to approve that with. Run `fedipod passwd` and try again."
|
|
65290
66190
|
});
|
|
65291
66191
|
}
|
|
65292
|
-
const client = { name: app?.name || doc?.name || null, redirect, scope: params.get("scope") || "read" };
|
|
65293
66192
|
if (app) {
|
|
65294
66193
|
if (!app.redirectUris.includes(redirect)) {
|
|
65295
66194
|
api.log(`authorize refused: redirect_uri "${redirect}" not registered for ${app.clientId}`);
|
|
@@ -65308,7 +66207,7 @@ async function handle(api, ctx) {
|
|
|
65308
66207
|
api.log(`authorize refused: redirect_uri "${redirect}" is not this agent`);
|
|
65309
66208
|
return send(400, { error: "redirect_uri must be an address of this agent" });
|
|
65310
66209
|
}
|
|
65311
|
-
if (req.method === "POST") {
|
|
66210
|
+
if (req.method === "POST" && !proved) {
|
|
65312
66211
|
if (api.rateLimited()) {
|
|
65313
66212
|
api.log("authorize rate limited");
|
|
65314
66213
|
return sendLoginForm(
|
|
@@ -65323,12 +66222,13 @@ async function handle(api, ctx) {
|
|
|
65323
66222
|
if (!pw || !checkPassword(pw, body.password || "")) {
|
|
65324
66223
|
return sendLoginForm(res, params, "wrong password \u2014 try again", client);
|
|
65325
66224
|
}
|
|
65326
|
-
} else if (pw) {
|
|
66225
|
+
} else if (pw && !proved) {
|
|
65327
66226
|
return sendLoginForm(res, params, "", client);
|
|
65328
|
-
} else if (api.allowed && !api.allowed.isLocalRequest(req)) {
|
|
66227
|
+
} else if (!proved && api.allowed && !api.allowed.isLocalRequest(req) && !api.throughDoor?.(req)) {
|
|
66228
|
+
if (req.method === "GET" && ownerWebId) return sendPodSigninPage(res, client, ownerWebId, api.mount || "");
|
|
65329
66229
|
api.log(`authorize refused: no UI password, and "${req.headers.host}" is not this machine`);
|
|
65330
66230
|
return send(403, {
|
|
65331
|
-
error: "
|
|
66231
|
+
error: api.embedded ? "Open your account's management page first, then sign in from there." : "Signing in from another machine needs a password. On the machine that runs your account, run: fedipod passwd"
|
|
65332
66232
|
});
|
|
65333
66233
|
}
|
|
65334
66234
|
const code = external ? api.mintCode({
|
|
@@ -65342,6 +66242,7 @@ async function handle(api, ctx) {
|
|
|
65342
66242
|
const target = new URL(redirect);
|
|
65343
66243
|
target.searchParams.set("code", code);
|
|
65344
66244
|
if (params.get("state")) target.searchParams.set("state", params.get("state"));
|
|
66245
|
+
if (proved) return send(200, { redirect: target.href });
|
|
65345
66246
|
res.writeHead(302, { location: target.href });
|
|
65346
66247
|
res.end();
|
|
65347
66248
|
return true;
|
|
@@ -65656,6 +66557,7 @@ var BskyFeed = class {
|
|
|
65656
66557
|
|
|
65657
66558
|
// lib/client/masto/render.mjs
|
|
65658
66559
|
var TRANSPARENT_PNG = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==";
|
|
66560
|
+
var DEFAULT_AVATAR = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAABd0lEQVR42u3ZoW4CQRDG8Xk6NBLbVCJ5gBpMTQ2iBlFR29RXNsFhz1JHXV9hydfkEkoobW5n2F34i0kIHMfN73ZvdwYbTabpmsMAAAAAAAAAAAAAAAAAAACiY3wzS/ePz+n1bZW6zTZ9fH59h17rPX2mYy4OQEktnl5+JP1b6Bgdey6IcIDb2Ty9r7s/Ez8MfUffbRpACfznrp8aDdEIFjnsh9z5YyMhcjqEAWge5ybfh87VFIDuWM7QPzYVokZBCICWM6/k+9A5mwHQmu4NoHM2A+A5/PenQTMA3sn3AQBTgIcgyyAbIbbCFEOUwzREaInRFKUtDgAAFQLcPSxDCqH9Z4V+o0oAXVhU4ofhiWCtJe+NYKV3e6V3idkAHvv9nDqhKIBnxVeqUrRaav5SvQKrqetTols0GGDdbaoB0LWcFUBP31qS72PoimCtD//caWC1tL1Ltc2ttbXfe08wCKC25HP+OgMAAAAAAAAAAAAAAAAArhNgB1PE/x/JOiuNAAAAAElFTkSuQmCC";
|
|
65659
66561
|
var selfIcon = (api, self2, cached) => (self2 ? api.store.getConfig()?.icon : null) || cached.icon;
|
|
65660
66562
|
function selfAccount(api) {
|
|
65661
66563
|
return api.account(api.urls.actor, { selfAcct: publicHandle(api.store.getConfig()) });
|
|
@@ -65693,8 +66595,8 @@ function account(api, actorUrl, { selfAcct } = {}) {
|
|
|
65693
66595
|
note: (self2 ? api.store.getConfig()?.summary : cached.summary) || "",
|
|
65694
66596
|
url: actorUrl,
|
|
65695
66597
|
uri: actorUrl,
|
|
65696
|
-
avatar: selfIcon(api, self2, cached) ||
|
|
65697
|
-
avatar_static: selfIcon(api, self2, cached) ||
|
|
66598
|
+
avatar: selfIcon(api, self2, cached) || DEFAULT_AVATAR,
|
|
66599
|
+
avatar_static: selfIcon(api, self2, cached) || DEFAULT_AVATAR,
|
|
65698
66600
|
header: (self2 ? api.store.getConfig()?.image : null) || TRANSPARENT_PNG,
|
|
65699
66601
|
header_static: (self2 ? api.store.getConfig()?.image : null) || TRANSPARENT_PNG,
|
|
65700
66602
|
// A remote actor's counts are whatever its own collections said when we
|
|
@@ -65709,11 +66611,11 @@ function account(api, actorUrl, { selfAcct } = {}) {
|
|
|
65709
66611
|
fields: (self2 ? api.store.getConfig()?.fields : cached.fields) || []
|
|
65710
66612
|
};
|
|
65711
66613
|
}
|
|
65712
|
-
function page(api, items, url, { limit = 20, max = 40, idOf:
|
|
66614
|
+
function page(api, items, url, { limit = 20, max = 40, idOf: idOf3 = (s) => api.store.idFor(s.noteId) } = {}) {
|
|
65713
66615
|
const n = Math.min(Number(url.searchParams.get("limit")) || limit, max);
|
|
65714
66616
|
const ids = /* @__PURE__ */ new Map();
|
|
65715
66617
|
const idAt = (s) => {
|
|
65716
|
-
if (!ids.has(s)) ids.set(s,
|
|
66618
|
+
if (!ids.has(s)) ids.set(s, idOf3(s));
|
|
65717
66619
|
return ids.get(s);
|
|
65718
66620
|
};
|
|
65719
66621
|
const cut = (param) => {
|
|
@@ -65737,8 +66639,8 @@ function page(api, items, url, { limit = 20, max = 40, idOf: idOf2 = (s) => api.
|
|
|
65737
66639
|
for (const [k, v] of Object.entries(extra)) u.searchParams.set(k, v);
|
|
65738
66640
|
return u.href;
|
|
65739
66641
|
};
|
|
65740
|
-
const links = [`<${q({ max_id:
|
|
65741
|
-
if (pageItems2.length) links.push(`<${q({ min_id:
|
|
66642
|
+
const links = [`<${q({ max_id: idOf3(pageItems2[pageItems2.length - 1]) })}>; rel="next"`];
|
|
66643
|
+
if (pageItems2.length) links.push(`<${q({ min_id: idOf3(pageItems2[0]) })}>; rel="prev"`);
|
|
65742
66644
|
return { items: pageItems2, headers: { link: links.join(", ") } };
|
|
65743
66645
|
}
|
|
65744
66646
|
async function bskyReply(api, send, body, parent, visibility) {
|
|
@@ -65849,7 +66751,26 @@ async function acctReply(api, send, body, parent, visibility) {
|
|
|
65849
66751
|
return send(422, { error: e.message });
|
|
65850
66752
|
}
|
|
65851
66753
|
}
|
|
65852
|
-
function
|
|
66754
|
+
function quoteJson(api, s, { all, depth = 0 } = {}) {
|
|
66755
|
+
if (!s.quote) return null;
|
|
66756
|
+
const state = s.quoteState || "pending";
|
|
66757
|
+
const quoted = state !== "rejected" && (all || api.store.getStatuses()).find((x) => x.noteId === s.quote);
|
|
66758
|
+
if (depth > 0) return { state, quoted_status_id: quoted ? api.store.idFor(quoted.noteId) : null };
|
|
66759
|
+
return { state, quoted_status: quoted ? api.status(quoted, { all, depth: depth + 1 }) : null };
|
|
66760
|
+
}
|
|
66761
|
+
function quoteApproval(api, s) {
|
|
66762
|
+
if (s.actor === api.urls?.actor) {
|
|
66763
|
+
const open2 = !s.visibility || s.visibility === "public" || s.visibility === "unlisted";
|
|
66764
|
+
return { automatic: open2 ? ["public"] : [], manual: [], current_user: open2 ? "automatic" : "denied" };
|
|
66765
|
+
}
|
|
66766
|
+
const p = s.quotePolicy;
|
|
66767
|
+
if (!p) return { automatic: [], manual: [], current_user: "unknown" };
|
|
66768
|
+
const follows = () => api.store.getContacts().following.some((f) => f.actor === s.actor && f.accepted);
|
|
66769
|
+
const may = (list3) => list3.includes("public") || list3.includes("followers") && follows();
|
|
66770
|
+
const current = may(p.automatic) ? "automatic" : may(p.manual) ? "manual" : "denied";
|
|
66771
|
+
return { automatic: p.automatic, manual: p.manual, current_user: current };
|
|
66772
|
+
}
|
|
66773
|
+
function status(api, s, { all, depth = 0 } = {}) {
|
|
65853
66774
|
const replies = (all || api.store.getStatuses()).filter((x) => x.inReplyTo === s.noteId).length;
|
|
65854
66775
|
return {
|
|
65855
66776
|
id: api.store.idFor(s.noteId),
|
|
@@ -65905,6 +66826,8 @@ function status(api, s, { all } = {}) {
|
|
|
65905
66826
|
})),
|
|
65906
66827
|
card: null,
|
|
65907
66828
|
poll: s.poll ? api.pollJson(s) : null,
|
|
66829
|
+
quote: quoteJson(api, s, { all, depth }),
|
|
66830
|
+
quote_approval: quoteApproval(api, s),
|
|
65908
66831
|
// What a filter matched, if any. Mastodon's clients read this and do the
|
|
65909
66832
|
// hiding or warning; they do NOT match keywords themselves — Phanpy does
|
|
65910
66833
|
// not — so filters that were stored and served but never applied were a
|
|
@@ -65991,7 +66914,9 @@ function pushNotify(api, n) {
|
|
|
65991
66914
|
reblog: "boosted your post",
|
|
65992
66915
|
follow: "followed you",
|
|
65993
66916
|
"follow-request": "asked to follow you",
|
|
65994
|
-
move: "moved account"
|
|
66917
|
+
move: "moved account",
|
|
66918
|
+
reaction: `reacted ${n.emoji || ""} to your post`,
|
|
66919
|
+
quote: "quoted your post"
|
|
65995
66920
|
};
|
|
65996
66921
|
const s = n.noteId && api.store.getStatuses().find((x) => x.noteId === n.noteId);
|
|
65997
66922
|
return api.push.notify(n, {
|
|
@@ -66072,10 +66997,14 @@ function relationship(api, actorUrl) {
|
|
|
66072
66997
|
};
|
|
66073
66998
|
}
|
|
66074
66999
|
function notificationType(api, t) {
|
|
66075
|
-
return t === "follow-request" ? "follow_request" : t;
|
|
67000
|
+
return t === "follow-request" ? "follow_request" : t === "reaction" ? "emoji_reaction" : t;
|
|
66076
67001
|
}
|
|
66077
67002
|
function notification(api, n) {
|
|
66078
67003
|
const out = { id: n.id, type: api.notificationType(n.type), created_at: n.at, account: api.account(n.actor) };
|
|
67004
|
+
if (n.type === "reaction") {
|
|
67005
|
+
out.emoji = n.emoji;
|
|
67006
|
+
if (n.url) out.emoji_url = n.url;
|
|
67007
|
+
}
|
|
66079
67008
|
if (n.noteId) {
|
|
66080
67009
|
const s = api.store.getStatuses().find((x) => x.noteId === n.noteId);
|
|
66081
67010
|
if (s) out.status = api.status(s);
|
|
@@ -66120,7 +67049,6 @@ var STUBS = new Map(Object.entries({
|
|
|
66120
67049
|
"/api/v1/custom_emojis": [],
|
|
66121
67050
|
"/api/v1/announcements": [],
|
|
66122
67051
|
"/api/v1/instance/peers": [],
|
|
66123
|
-
"/api/v1/trends/tags": [],
|
|
66124
67052
|
"/api/v1/trends/links": [],
|
|
66125
67053
|
"/api/v2/suggestions": [],
|
|
66126
67054
|
"/api/v1/preferences": {}
|
|
@@ -66157,14 +67085,15 @@ function instanceTitle(api) {
|
|
|
66157
67085
|
const cfg = api.store.getConfig();
|
|
66158
67086
|
return cfg?.handle ? `@${cfg.handle}@${api.host}` : "FediPod";
|
|
66159
67087
|
}
|
|
66160
|
-
function tagObject(api, name, following, req) {
|
|
67088
|
+
function tagObject(api, name, following, req, history = []) {
|
|
66161
67089
|
const host = req?.headers?.host || api.host;
|
|
66162
|
-
return { name, url: `https://${host}/tags/${name}`, history
|
|
67090
|
+
return { name, url: `https://${host}/tags/${name}`, history, following: !!following };
|
|
66163
67091
|
}
|
|
66164
67092
|
function instanceBlurb(api) {
|
|
66165
67093
|
const kind = api.store.getConfig()?.kind === "group" ? "group" : "actor";
|
|
66166
67094
|
return `Solid pod ActivityPub ${kind}`;
|
|
66167
67095
|
}
|
|
67096
|
+
var API_VERSIONS = { mastodon: 7 };
|
|
66168
67097
|
async function handle2(api, ctx) {
|
|
66169
67098
|
const { req, res, pathname, url, send } = ctx;
|
|
66170
67099
|
if (pathname === "/api/v1/instance") {
|
|
@@ -66176,6 +67105,7 @@ async function handle2(api, ctx) {
|
|
|
66176
67105
|
description: api.instanceBlurb(),
|
|
66177
67106
|
email: "",
|
|
66178
67107
|
version: "4.2.0 (compatible; fedipod)",
|
|
67108
|
+
api_versions: API_VERSIONS,
|
|
66179
67109
|
urls: su ? { streaming_api: su } : {},
|
|
66180
67110
|
stats: { user_count: 1, status_count: api.store.countStatuses(), domain_count: 1 },
|
|
66181
67111
|
languages: ["en"],
|
|
@@ -66193,6 +67123,7 @@ async function handle2(api, ctx) {
|
|
|
66193
67123
|
domain: api.host,
|
|
66194
67124
|
title: api.instanceTitle(),
|
|
66195
67125
|
version: "4.2.0 (compatible; fedipod)",
|
|
67126
|
+
api_versions: API_VERSIONS,
|
|
66196
67127
|
source_url: "https://github.com/jeff-zucker/FediPod",
|
|
66197
67128
|
description: api.instanceBlurb(),
|
|
66198
67129
|
usage: { users: { active_month: 1 } },
|
|
@@ -66220,124 +67151,15 @@ async function handle2(api, ctx) {
|
|
|
66220
67151
|
// lib/client/masto/accounts.mjs
|
|
66221
67152
|
var accounts_exports = {};
|
|
66222
67153
|
__export(accounts_exports, {
|
|
66223
|
-
handle: () =>
|
|
67154
|
+
handle: () => handle3
|
|
66224
67155
|
});
|
|
66225
67156
|
init_node_crypto();
|
|
66226
67157
|
|
|
66227
67158
|
// lib/pod/media.mjs
|
|
66228
67159
|
var write6 = (pod, url, bytes, contentType) => pod.put(url, bytes, contentType);
|
|
66229
67160
|
|
|
66230
|
-
// lib/client/masto/media.mjs
|
|
66231
|
-
var media_exports = {};
|
|
66232
|
-
__export(media_exports, {
|
|
66233
|
-
attachmentType: () => attachmentType,
|
|
66234
|
-
extensionFor: () => extensionFor,
|
|
66235
|
-
handle: () => handle3,
|
|
66236
|
-
readMultipart: () => readMultipart
|
|
66237
|
-
});
|
|
66238
|
-
init_node_crypto();
|
|
66239
|
-
var ATTACHMENT_KINDS = /* @__PURE__ */ new Set(["image", "video", "audio"]);
|
|
66240
|
-
var NEVER = /* @__PURE__ */ new Set(["image/svg+xml", "image/svg"]);
|
|
66241
|
-
var OPAQUE = "application/octet-stream";
|
|
66242
|
-
function attachmentType(claimed) {
|
|
66243
|
-
const t = String(claimed || "").split(";")[0].trim().toLowerCase();
|
|
66244
|
-
if (!/^[a-z0-9.+-]+\/[a-z0-9.+-]+$/.test(t) || NEVER.has(t)) return OPAQUE;
|
|
66245
|
-
return ATTACHMENT_KINDS.has(t.split("/")[0]) ? t : OPAQUE;
|
|
66246
|
-
}
|
|
66247
|
-
function extensionFor(mediaType, filename = "") {
|
|
66248
|
-
if (mediaType === OPAQUE) return "bin";
|
|
66249
|
-
const sub = mediaType.split("/")[1].replace(/[^a-z0-9]/g, "");
|
|
66250
|
-
const given = String(filename || "").includes(".") ? filename.split(".").pop().toLowerCase().replace(/[^a-z0-9]/g, "") : "";
|
|
66251
|
-
return given && (given === sub || sub.startsWith(given) || given.startsWith(sub)) ? given : sub || "bin";
|
|
66252
|
-
}
|
|
66253
|
-
function readMultipart(req, limit = 12e6) {
|
|
66254
|
-
return new Promise((resolve2, reject) => {
|
|
66255
|
-
const chunks = [];
|
|
66256
|
-
let n = 0;
|
|
66257
|
-
req.on("data", (c) => {
|
|
66258
|
-
n += c.length;
|
|
66259
|
-
if (n > limit) {
|
|
66260
|
-
reject(new Error("upload too large"));
|
|
66261
|
-
req.destroy();
|
|
66262
|
-
return;
|
|
66263
|
-
}
|
|
66264
|
-
chunks.push(c);
|
|
66265
|
-
});
|
|
66266
|
-
req.on("error", reject);
|
|
66267
|
-
req.on("end", () => {
|
|
66268
|
-
try {
|
|
66269
|
-
const m = /boundary=(?:"([^"]+)"|([^;]+))/.exec(String(req.headers["content-type"] || ""));
|
|
66270
|
-
if (!m) return resolve2({ fields: {}, file: null });
|
|
66271
|
-
const buf = Buffer.concat(chunks);
|
|
66272
|
-
const boundary = Buffer.from("--" + (m[1] || m[2]).trim());
|
|
66273
|
-
const fields = {};
|
|
66274
|
-
const files = {};
|
|
66275
|
-
let file = null;
|
|
66276
|
-
let i = buf.indexOf(boundary);
|
|
66277
|
-
while (i >= 0) {
|
|
66278
|
-
const start = i + boundary.length;
|
|
66279
|
-
if (buf.slice(start, start + 2).toString() === "--") break;
|
|
66280
|
-
const next = buf.indexOf(boundary, start);
|
|
66281
|
-
if (next < 0) break;
|
|
66282
|
-
const part = buf.slice(start + 2, next - 2);
|
|
66283
|
-
const sep = part.indexOf("\r\n\r\n");
|
|
66284
|
-
if (sep >= 0) {
|
|
66285
|
-
const head = part.slice(0, sep).toString();
|
|
66286
|
-
const body = part.slice(sep + 4);
|
|
66287
|
-
const name = /name="([^"]*)"/.exec(head)?.[1];
|
|
66288
|
-
const filename = /filename="([^"]*)"/.exec(head)?.[1];
|
|
66289
|
-
if (filename !== void 0) {
|
|
66290
|
-
file = {
|
|
66291
|
-
filename,
|
|
66292
|
-
contentType: /content-type:\s*([^\r\n]+)/i.exec(head)?.[1]?.trim() || "application/octet-stream",
|
|
66293
|
-
data: body
|
|
66294
|
-
};
|
|
66295
|
-
if (name) files[name] = file;
|
|
66296
|
-
} else if (name) fields[name] = body.toString();
|
|
66297
|
-
}
|
|
66298
|
-
i = next;
|
|
66299
|
-
}
|
|
66300
|
-
resolve2({ fields, file, files });
|
|
66301
|
-
} catch (e) {
|
|
66302
|
-
reject(e);
|
|
66303
|
-
}
|
|
66304
|
-
});
|
|
66305
|
-
});
|
|
66306
|
-
}
|
|
66307
|
-
async function handle3(api, ctx) {
|
|
66308
|
-
const { req, res, pathname, url, send } = ctx;
|
|
66309
|
-
if ((pathname === "/api/v2/media" || pathname === "/api/v1/media") && req.method === "POST") {
|
|
66310
|
-
const { fields, file } = await readMultipart(req);
|
|
66311
|
-
if (!file?.data?.length) return send(422, { error: "file required" });
|
|
66312
|
-
const mediaType = attachmentType(file.contentType);
|
|
66313
|
-
const ext = extensionFor(mediaType, file.filename);
|
|
66314
|
-
const slug = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10) + "-" + node_crypto_default.randomBytes(4).toString("hex") + "." + ext;
|
|
66315
|
-
const mediaUrl = api.urls.media + slug;
|
|
66316
|
-
await api.agent.publisher.ensureMediaContainer();
|
|
66317
|
-
await write6(api.agent.remote, mediaUrl, file.data, mediaType);
|
|
66318
|
-
const entry = { url: mediaUrl, mediaType, description: fields.description || "" };
|
|
66319
|
-
const id = api.store.idFor(mediaUrl);
|
|
66320
|
-
api.store.setMedia(id, entry);
|
|
66321
|
-
return send(200, api.mediaJson({ id, ...entry }));
|
|
66322
|
-
}
|
|
66323
|
-
const mMedia = /^\/api\/v1\/media\/([a-f0-9]+)$/.exec(pathname);
|
|
66324
|
-
if (mMedia) {
|
|
66325
|
-
const entry = api.store.getMedia()[mMedia[1]];
|
|
66326
|
-
if (!entry) return send(404, { error: "Record not found" });
|
|
66327
|
-
if (req.method === "PUT") {
|
|
66328
|
-
const body = await readBody2(req);
|
|
66329
|
-
if (typeof body.description === "string") {
|
|
66330
|
-
entry.description = body.description;
|
|
66331
|
-
api.store.setMedia(mMedia[1], entry);
|
|
66332
|
-
}
|
|
66333
|
-
}
|
|
66334
|
-
return send(200, api.mediaJson({ id: mMedia[1], ...entry }));
|
|
66335
|
-
}
|
|
66336
|
-
return false;
|
|
66337
|
-
}
|
|
66338
|
-
|
|
66339
67161
|
// lib/client/masto/accounts.mjs
|
|
66340
|
-
async function
|
|
67162
|
+
async function handle3(api, ctx) {
|
|
66341
67163
|
const { req, res, pathname, url, send } = ctx;
|
|
66342
67164
|
if (pathname === "/api/v1/accounts/verify_credentials") {
|
|
66343
67165
|
const cfg0 = api.store.getConfig() || {};
|
|
@@ -66516,6 +67338,31 @@ async function handle4(api, ctx) {
|
|
|
66516
67338
|
}
|
|
66517
67339
|
}
|
|
66518
67340
|
if (/^\/api\/v1\/accounts\/[a-f0-9]+\/featured_tags$/.test(pathname)) return send(200, []);
|
|
67341
|
+
if (pathname === "/api/v1/directory") return send(200, api.urls?.actor ? [api.account(api.urls.actor)] : []);
|
|
67342
|
+
if (pathname === "/api/v1/suggestions") return send(200, []);
|
|
67343
|
+
const mAccLists = /^\/api\/v1\/accounts\/([a-f0-9]+)\/lists$/.exec(pathname);
|
|
67344
|
+
if (mAccLists && req.method === "GET") {
|
|
67345
|
+
const actorUrl = api.store.urlFor(mAccLists[1]);
|
|
67346
|
+
if (!actorUrl) return send(404, { error: "Record not found" });
|
|
67347
|
+
return send(200, api.store.getLists().filter((l) => (l.members || []).includes(actorUrl)).map((l) => ({ id: l.id, title: l.title, replies_policy: l.repliesPolicy || "list", exclusive: false })));
|
|
67348
|
+
}
|
|
67349
|
+
if (pathname === "/api/v1/mutes" && req.method === "GET") {
|
|
67350
|
+
return send(200, (api.store.getMuted().actors || []).map((a) => api.account(a)));
|
|
67351
|
+
}
|
|
67352
|
+
if (pathname === "/api/v1/blocks" && req.method === "GET") {
|
|
67353
|
+
return send(200, (api.store.getBlocklist().actors || []).map((a) => api.account(a)));
|
|
67354
|
+
}
|
|
67355
|
+
if (pathname === "/api/v1/domain_blocks") {
|
|
67356
|
+
const b = api.store.getBlocklist();
|
|
67357
|
+
if (req.method === "GET") return send(200, [...b.domains]);
|
|
67358
|
+
const body = await readBody2(req);
|
|
67359
|
+
const domain = String(body.domain || url.searchParams.get("domain") || "").trim().toLowerCase();
|
|
67360
|
+
if (!/^[a-z0-9.-]+\.[a-z]{2,}$/u.test(domain)) return send(422, { error: "a domain is required" });
|
|
67361
|
+
if (req.method === "POST" && !b.domains.includes(domain)) b.domains.push(domain);
|
|
67362
|
+
if (req.method === "DELETE") b.domains = b.domains.filter((d) => d !== domain);
|
|
67363
|
+
api.store.setBlocklist(b);
|
|
67364
|
+
return send(200, {});
|
|
67365
|
+
}
|
|
66519
67366
|
const mAccList = /^\/api\/v1\/accounts\/([a-f0-9]+)\/(following|followers)$/.exec(pathname);
|
|
66520
67367
|
if (mAccList && req.method === "GET") {
|
|
66521
67368
|
const actorUrl = api.store.urlFor(mAccList[1]);
|
|
@@ -66538,9 +67385,13 @@ async function handle4(api, ctx) {
|
|
|
66538
67385
|
if (mAccStatuses) {
|
|
66539
67386
|
const actorUrl = api.store.urlFor(mAccStatuses[1]);
|
|
66540
67387
|
const all = api.store.getStatuses();
|
|
66541
|
-
const
|
|
67388
|
+
const q = url.searchParams;
|
|
67389
|
+
const is = (name) => q.get(name) === "true" || q.get(name) === "1";
|
|
67390
|
+
const pinnedOnly = is("pinned");
|
|
67391
|
+
const tagged = String(q.get("tagged") || "").replace(/^#/, "").toLowerCase();
|
|
67392
|
+
const isBoost = (s) => !!s.via && s.via !== s.actor;
|
|
66542
67393
|
const { items, headers } = api.page(
|
|
66543
|
-
all.filter((s) => s.actor === actorUrl && (!pinnedOnly || s.pinned)),
|
|
67394
|
+
all.filter((s) => s.actor === actorUrl && (!pinnedOnly || s.pinned)).filter((s) => !is("exclude_replies") || !s.inReplyTo).filter((s) => !is("exclude_reblogs") || !isBoost(s)).filter((s) => !is("only_media") || (s.attachments || []).length > 0).filter((s) => !tagged || (s.tags || []).some((t) => String(t.name).toLowerCase() === tagged)),
|
|
66544
67395
|
url
|
|
66545
67396
|
);
|
|
66546
67397
|
return send(200, items.map((s) => api.statusOrBoost(s, { all })), headers);
|
|
@@ -66551,11 +67402,11 @@ async function handle4(api, ctx) {
|
|
|
66551
67402
|
// lib/client/masto/timelines.mjs
|
|
66552
67403
|
var timelines_exports = {};
|
|
66553
67404
|
__export(timelines_exports, {
|
|
66554
|
-
handle: () =>
|
|
67405
|
+
handle: () => handle4
|
|
66555
67406
|
});
|
|
66556
67407
|
init_node_crypto();
|
|
66557
67408
|
init_wire();
|
|
66558
|
-
async function
|
|
67409
|
+
async function handle4(api, ctx) {
|
|
66559
67410
|
const { req, res, pathname, url, send } = ctx;
|
|
66560
67411
|
if (pathname === "/api/v1/timelines/home" || pathname === "/api/v1/timelines/public" || pathname === "/api/v1/trends/statuses") {
|
|
66561
67412
|
const localOnly = pathname === "/api/v1/timelines/public" && url.searchParams.get("local") === "true";
|
|
@@ -66586,6 +67437,41 @@ async function handle5(api, ctx) {
|
|
|
66586
67437
|
const following = (api.agent.tagfeed?.config().tags || []).includes(name);
|
|
66587
67438
|
return send(200, api.tagObject(name, following, req));
|
|
66588
67439
|
}
|
|
67440
|
+
if (pathname === "/api/v1/trends/tags" || pathname === "/api/v1/trends") {
|
|
67441
|
+
const DAY = 864e5;
|
|
67442
|
+
const today = Math.floor(Date.now() / DAY) * DAY;
|
|
67443
|
+
const seen = /* @__PURE__ */ new Map();
|
|
67444
|
+
for (const st2 of api.store.getStatuses()) {
|
|
67445
|
+
if (st2.kind !== "tag" || !st2.tag) continue;
|
|
67446
|
+
const at = Date.parse(st2.published || "");
|
|
67447
|
+
if (!Number.isFinite(at) || at < today - 6 * DAY) continue;
|
|
67448
|
+
const day = Math.floor(at / DAY) * DAY;
|
|
67449
|
+
if (!seen.has(st2.tag)) seen.set(st2.tag, /* @__PURE__ */ new Map());
|
|
67450
|
+
const days = seen.get(st2.tag);
|
|
67451
|
+
if (!days.has(day)) days.set(day, { uses: 0, accounts: /* @__PURE__ */ new Set() });
|
|
67452
|
+
const rec = days.get(day);
|
|
67453
|
+
rec.uses++;
|
|
67454
|
+
if (st2.actor) rec.accounts.add(st2.actor);
|
|
67455
|
+
}
|
|
67456
|
+
const followed = new Set(api.agent.tagfeed?.config().tags || []);
|
|
67457
|
+
const ranked = [...seen.entries()].map(([name, days]) => {
|
|
67458
|
+
const history = [];
|
|
67459
|
+
for (let i = 0; i < 7; i++) {
|
|
67460
|
+
const rec = days.get(today - i * DAY);
|
|
67461
|
+
history.push({
|
|
67462
|
+
day: String(Math.floor((today - i * DAY) / 1e3)),
|
|
67463
|
+
uses: String(rec ? rec.uses : 0),
|
|
67464
|
+
accounts: String(rec ? rec.accounts.size : 0)
|
|
67465
|
+
});
|
|
67466
|
+
}
|
|
67467
|
+
return {
|
|
67468
|
+
total: history.reduce((n, h) => n + Number(h.uses), 0),
|
|
67469
|
+
tag: api.tagObject(name, followed.has(name), req, history)
|
|
67470
|
+
};
|
|
67471
|
+
}).sort((a, b) => b.total - a.total);
|
|
67472
|
+
const limit = Math.min(Math.max(Number(url.searchParams.get("limit")) || 10, 1), 40);
|
|
67473
|
+
return send(200, ranked.slice(0, limit).map((x) => x.tag));
|
|
67474
|
+
}
|
|
66589
67475
|
const mTagTl = pathname.match(/^\/api\/v1\/timelines\/tag\/([^/]+)$/);
|
|
66590
67476
|
if (mTagTl && req.method === "GET") {
|
|
66591
67477
|
const name = decodeURIComponent(mTagTl[1]).replace(/^#/, "").toLowerCase();
|
|
@@ -66722,7 +67608,81 @@ async function handle5(api, ctx) {
|
|
|
66722
67608
|
last_status: api.status(c.last, { all })
|
|
66723
67609
|
})));
|
|
66724
67610
|
}
|
|
67611
|
+
if (pathname === "/api/v1/timelines/direct" && req.method === "GET") {
|
|
67612
|
+
const all = api.store.getStatuses();
|
|
67613
|
+
const items = all.filter((s) => s.direct || s.visibility === "direct");
|
|
67614
|
+
const { items: page2, headers } = api.page(items, url);
|
|
67615
|
+
return send(200, page2.map((s) => api.status(s, { all })), headers);
|
|
67616
|
+
}
|
|
67617
|
+
if (pathname === "/api/v1/notifications/clear" && req.method === "POST") {
|
|
67618
|
+
api.store.removeNotifications(() => true);
|
|
67619
|
+
return send(200, {});
|
|
67620
|
+
}
|
|
67621
|
+
const mNotif = /^\/api\/v1\/notifications\/([A-Za-z0-9_-]+)(\/dismiss)?$/.exec(pathname);
|
|
67622
|
+
if (mNotif) {
|
|
67623
|
+
const n = api.store.getNotifications().find((x) => x.id === mNotif[1]);
|
|
67624
|
+
if (!n) return send(404, { error: "Record not found" });
|
|
67625
|
+
if (mNotif[2] && req.method === "POST") {
|
|
67626
|
+
api.store.removeNotifications((x) => x.id === n.id);
|
|
67627
|
+
return send(200, {});
|
|
67628
|
+
}
|
|
67629
|
+
if (!mNotif[2] && req.method === "GET") return send(200, api.notification(n));
|
|
67630
|
+
}
|
|
67631
|
+
const mGroup = /^\/api\/v2\/notifications\/([A-Za-z0-9_-]+)(\/accounts|\/dismiss)?$/.exec(pathname);
|
|
67632
|
+
if (mGroup) {
|
|
67633
|
+
const n = api.store.getNotifications().find((x) => x.id === mGroup[1]);
|
|
67634
|
+
if (!n) return send(404, { error: "Record not found" });
|
|
67635
|
+
if (mGroup[2] === "/dismiss" && req.method === "POST") {
|
|
67636
|
+
api.store.removeNotifications((x) => x.id === n.id);
|
|
67637
|
+
return send(200, {});
|
|
67638
|
+
}
|
|
67639
|
+
if (mGroup[2] === "/accounts" && req.method === "GET") return send(200, [api.account(n.actor)]);
|
|
67640
|
+
if (!mGroup[2] && req.method === "GET") return send(200, groupedNotifications(api, [n]));
|
|
67641
|
+
}
|
|
67642
|
+
if (pathname === "/api/v2/notifications") {
|
|
67643
|
+
const { page: page2, headers } = notificationPage(api, ctx);
|
|
67644
|
+
return send(200, groupedNotifications(api, page2), headers);
|
|
67645
|
+
}
|
|
66725
67646
|
if (pathname === "/api/v1/notifications") {
|
|
67647
|
+
const { page: page2, headers } = notificationPage(api, ctx);
|
|
67648
|
+
return send(200, page2.map((n) => api.notification(n)), headers);
|
|
67649
|
+
}
|
|
67650
|
+
if (pathname === "/api/v2/search" || pathname === "/api/v1/search") {
|
|
67651
|
+
return searchHandler(api, ctx);
|
|
67652
|
+
}
|
|
67653
|
+
return false;
|
|
67654
|
+
}
|
|
67655
|
+
function groupedNotifications(api, page2) {
|
|
67656
|
+
const accounts = /* @__PURE__ */ new Map();
|
|
67657
|
+
const statuses = /* @__PURE__ */ new Map();
|
|
67658
|
+
const groups = page2.map((n) => {
|
|
67659
|
+
const acct = api.account(n.actor);
|
|
67660
|
+
accounts.set(acct.id, acct);
|
|
67661
|
+
const s = n.noteId && api.store.getStatuses().find((x) => x.noteId === n.noteId);
|
|
67662
|
+
let statusId = null;
|
|
67663
|
+
if (s) {
|
|
67664
|
+
const j = api.status(s);
|
|
67665
|
+
statuses.set(j.id, j);
|
|
67666
|
+
statusId = j.id;
|
|
67667
|
+
}
|
|
67668
|
+
return {
|
|
67669
|
+
group_key: n.id,
|
|
67670
|
+
notifications_count: 1,
|
|
67671
|
+
type: api.notificationType(n.type),
|
|
67672
|
+
most_recent_notification_id: n.id,
|
|
67673
|
+
page_min_id: n.id,
|
|
67674
|
+
page_max_id: n.id,
|
|
67675
|
+
latest_page_notification_at: n.at,
|
|
67676
|
+
sample_account_ids: [acct.id],
|
|
67677
|
+
...statusId ? { status_id: statusId } : {},
|
|
67678
|
+
...n.type === "reaction" ? { emoji: n.emoji, ...n.url ? { emoji_url: n.url } : {} } : {}
|
|
67679
|
+
};
|
|
67680
|
+
});
|
|
67681
|
+
return { accounts: [...accounts.values()], statuses: [...statuses.values()], notification_groups: groups };
|
|
67682
|
+
}
|
|
67683
|
+
function notificationPage(api, ctx) {
|
|
67684
|
+
const { req, pathname, url } = ctx;
|
|
67685
|
+
{
|
|
66726
67686
|
const limit = Math.min(Number(url.searchParams.get("limit")) || 30, 60);
|
|
66727
67687
|
const q = url.searchParams;
|
|
66728
67688
|
let items = api.store.getNotifications();
|
|
@@ -66753,13 +67713,16 @@ async function handle5(api, ctx) {
|
|
|
66753
67713
|
for (const [k, v] of Object.entries(params)) u.searchParams.set(k, v);
|
|
66754
67714
|
return u.href;
|
|
66755
67715
|
};
|
|
66756
|
-
return
|
|
67716
|
+
return { page: page2, headers: {
|
|
66757
67717
|
link: `<${link({ max_id: page2[page2.length - 1].id })}>; rel="next", <${link({ min_id: page2[0].id })}>; rel="prev"`
|
|
66758
|
-
}
|
|
67718
|
+
} };
|
|
66759
67719
|
}
|
|
66760
|
-
return
|
|
67720
|
+
return { page: [], headers: void 0 };
|
|
66761
67721
|
}
|
|
66762
|
-
|
|
67722
|
+
}
|
|
67723
|
+
async function searchHandler(api, ctx) {
|
|
67724
|
+
const { req, res, pathname, url, send } = ctx;
|
|
67725
|
+
{
|
|
66763
67726
|
const q = String(url.searchParams.get("q") || "").trim();
|
|
66764
67727
|
const type = url.searchParams.get("type");
|
|
66765
67728
|
const out = { accounts: [], statuses: [], hashtags: [] };
|
|
@@ -66806,10 +67769,10 @@ async function handle5(api, ctx) {
|
|
|
66806
67769
|
// lib/client/masto/statuses.mjs
|
|
66807
67770
|
var statuses_exports = {};
|
|
66808
67771
|
__export(statuses_exports, {
|
|
66809
|
-
handle: () =>
|
|
67772
|
+
handle: () => handle5
|
|
66810
67773
|
});
|
|
66811
67774
|
init_node_crypto();
|
|
66812
|
-
async function
|
|
67775
|
+
async function handle5(api, ctx) {
|
|
66813
67776
|
const { req, res, pathname, url, send } = ctx;
|
|
66814
67777
|
if (pathname === "/api/v1/statuses" && req.method === "POST") {
|
|
66815
67778
|
const body = await readBody2(req);
|
|
@@ -66832,8 +67795,22 @@ async function handle6(api, ctx) {
|
|
|
66832
67795
|
const mediaIds = [].concat(body.media_ids || body["media_ids[]"] || []).filter(Boolean);
|
|
66833
67796
|
const media = api.store.getMedia();
|
|
66834
67797
|
const attachments = mediaIds.map((id) => media[id] && { id, ...media[id] }).filter(Boolean);
|
|
67798
|
+
let quote = null;
|
|
67799
|
+
if (body.quoted_status_id) {
|
|
67800
|
+
const quotedUrl = api.store.urlFor(String(body.quoted_status_id));
|
|
67801
|
+
const quoted = quotedUrl && api.store.getStatuses().find((x) => x.noteId === quotedUrl);
|
|
67802
|
+
if (!quoted) return send(404, { error: "Record not found" });
|
|
67803
|
+
if (["bsky", "acct"].includes(quoted.kind)) return send(422, { error: "that post cannot be quoted from this account" });
|
|
67804
|
+
if (visibility === "private" || visibility === "direct") return send(422, { error: "a quote post is public or unlisted" });
|
|
67805
|
+
const may = api.quoteApproval(quoted).current_user;
|
|
67806
|
+
if (may === "denied") return send(422, { error: "the author of that post does not allow quotes" });
|
|
67807
|
+
if (may === "unknown") return send(422, { error: "the author of that post has not said whether it may be quoted" });
|
|
67808
|
+
if (body.scheduled_at) return send(422, { error: "a quote cannot be scheduled" });
|
|
67809
|
+
quote = { id: quoted.noteId, actor: quoted.actor };
|
|
67810
|
+
}
|
|
66835
67811
|
const asking = pollParams(body);
|
|
66836
67812
|
if (asking) {
|
|
67813
|
+
if (quote) return send(422, { error: "a poll cannot quote a post" });
|
|
66837
67814
|
if (attachments.length) return send(422, { error: "a poll cannot carry media" });
|
|
66838
67815
|
if (body.scheduled_at) return send(422, { error: "a poll cannot be scheduled" });
|
|
66839
67816
|
if (asking.options.length < 2) return send(422, { error: "a poll needs at least two options" });
|
|
@@ -66889,7 +67866,7 @@ async function handle6(api, ctx) {
|
|
|
66889
67866
|
try {
|
|
66890
67867
|
note = await api.agent.publisher.publishNote(
|
|
66891
67868
|
body.status,
|
|
66892
|
-
{ inReplyTo, attachments, visibility, spoilerText, sensitive }
|
|
67869
|
+
{ inReplyTo, attachments, visibility, spoilerText, sensitive, quote }
|
|
66893
67870
|
);
|
|
66894
67871
|
} catch (e) {
|
|
66895
67872
|
if (e.code === "unaddressed") return send(422, { error: e.message });
|
|
@@ -66898,6 +67875,9 @@ async function handle6(api, ctx) {
|
|
|
66898
67875
|
const s = api.store.getStatuses().find((x) => x.noteId === note.id);
|
|
66899
67876
|
return send(200, api.status(s));
|
|
66900
67877
|
}
|
|
67878
|
+
if (/^\/api\/v1\/statuses\/[a-f0-9]+\/interaction_policy$/.test(pathname) && req.method === "PUT") {
|
|
67879
|
+
return send(422, { error: "the quote policy is the same for every post here: anyone may quote a public or unlisted post, nobody a followers-only or direct one" });
|
|
67880
|
+
}
|
|
66901
67881
|
const mSource = /^\/api\/v1\/statuses\/([a-f0-9]+)\/source$/.exec(pathname);
|
|
66902
67882
|
if (mSource && req.method === "GET") {
|
|
66903
67883
|
const noteUrl = api.store.urlFor(mSource[1]);
|
|
@@ -67106,6 +68086,60 @@ async function handle6(api, ctx) {
|
|
|
67106
68086
|
return false;
|
|
67107
68087
|
}
|
|
67108
68088
|
|
|
68089
|
+
// lib/client/masto/media.mjs
|
|
68090
|
+
var media_exports2 = {};
|
|
68091
|
+
__export(media_exports2, {
|
|
68092
|
+
attachmentType: () => attachmentType,
|
|
68093
|
+
extensionFor: () => extensionFor,
|
|
68094
|
+
handle: () => handle6
|
|
68095
|
+
});
|
|
68096
|
+
init_node_crypto();
|
|
68097
|
+
var ATTACHMENT_KINDS = /* @__PURE__ */ new Set(["image", "video", "audio"]);
|
|
68098
|
+
var NEVER = /* @__PURE__ */ new Set(["image/svg+xml", "image/svg"]);
|
|
68099
|
+
var OPAQUE = "application/octet-stream";
|
|
68100
|
+
function attachmentType(claimed) {
|
|
68101
|
+
const t = String(claimed || "").split(";")[0].trim().toLowerCase();
|
|
68102
|
+
if (!/^[a-z0-9.+-]+\/[a-z0-9.+-]+$/.test(t) || NEVER.has(t)) return OPAQUE;
|
|
68103
|
+
return ATTACHMENT_KINDS.has(t.split("/")[0]) ? t : OPAQUE;
|
|
68104
|
+
}
|
|
68105
|
+
function extensionFor(mediaType, filename = "") {
|
|
68106
|
+
if (mediaType === OPAQUE) return "bin";
|
|
68107
|
+
const sub = mediaType.split("/")[1].replace(/[^a-z0-9]/g, "");
|
|
68108
|
+
const given = String(filename || "").includes(".") ? filename.split(".").pop().toLowerCase().replace(/[^a-z0-9]/g, "") : "";
|
|
68109
|
+
return given && (given === sub || sub.startsWith(given) || given.startsWith(sub)) ? given : sub || "bin";
|
|
68110
|
+
}
|
|
68111
|
+
async function handle6(api, ctx) {
|
|
68112
|
+
const { req, res, pathname, url, send } = ctx;
|
|
68113
|
+
if ((pathname === "/api/v2/media" || pathname === "/api/v1/media") && req.method === "POST") {
|
|
68114
|
+
const { fields, file } = await readMultipart(req);
|
|
68115
|
+
if (!file?.data?.length) return send(422, { error: "file required" });
|
|
68116
|
+
const mediaType = attachmentType(file.contentType);
|
|
68117
|
+
const ext = extensionFor(mediaType, file.filename);
|
|
68118
|
+
const slug = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10) + "-" + node_crypto_default.randomBytes(4).toString("hex") + "." + ext;
|
|
68119
|
+
const mediaUrl = api.urls.media + slug;
|
|
68120
|
+
await api.agent.publisher.ensureMediaContainer();
|
|
68121
|
+
await write6(api.agent.remote, mediaUrl, file.data, mediaType);
|
|
68122
|
+
const entry = { url: mediaUrl, mediaType, description: fields.description || "" };
|
|
68123
|
+
const id = api.store.idFor(mediaUrl);
|
|
68124
|
+
api.store.setMedia(id, entry);
|
|
68125
|
+
return send(200, api.mediaJson({ id, ...entry }));
|
|
68126
|
+
}
|
|
68127
|
+
const mMedia = /^\/api\/v1\/media\/([a-f0-9]+)$/.exec(pathname);
|
|
68128
|
+
if (mMedia) {
|
|
68129
|
+
const entry = api.store.getMedia()[mMedia[1]];
|
|
68130
|
+
if (!entry) return send(404, { error: "Record not found" });
|
|
68131
|
+
if (req.method === "PUT") {
|
|
68132
|
+
const body = await readBody2(req);
|
|
68133
|
+
if (typeof body.description === "string") {
|
|
68134
|
+
entry.description = body.description;
|
|
68135
|
+
api.store.setMedia(mMedia[1], entry);
|
|
68136
|
+
}
|
|
68137
|
+
}
|
|
68138
|
+
return send(200, api.mediaJson({ id: mMedia[1], ...entry }));
|
|
68139
|
+
}
|
|
68140
|
+
return false;
|
|
68141
|
+
}
|
|
68142
|
+
|
|
67109
68143
|
// lib/client/masto/index.mjs
|
|
67110
68144
|
var MastoApi = class _MastoApi {
|
|
67111
68145
|
constructor({
|
|
@@ -67114,6 +68148,7 @@ var MastoApi = class _MastoApi {
|
|
|
67114
68148
|
allowed = null,
|
|
67115
68149
|
scheme = null,
|
|
67116
68150
|
embedded = false,
|
|
68151
|
+
throughDoor = null,
|
|
67117
68152
|
mount = "",
|
|
67118
68153
|
streaming = true,
|
|
67119
68154
|
webPush = true,
|
|
@@ -67124,6 +68159,7 @@ var MastoApi = class _MastoApi {
|
|
|
67124
68159
|
this.embedded = embedded;
|
|
67125
68160
|
this.log = log2;
|
|
67126
68161
|
this.allowed = allowed;
|
|
68162
|
+
this.throughDoor = throughDoor;
|
|
67127
68163
|
this.scheme = scheme;
|
|
67128
68164
|
this.streaming = streaming;
|
|
67129
68165
|
this.webPush = webPush;
|
|
@@ -67202,7 +68238,7 @@ var MastoApi = class _MastoApi {
|
|
|
67202
68238
|
const took = await this.agent.requestTakeover?.();
|
|
67203
68239
|
if (!took) return send(503, { error: "another agent is active for this pod \u2014 takeover failed, try again" });
|
|
67204
68240
|
}
|
|
67205
|
-
for (const area of [accounts_exports, timelines_exports, statuses_exports,
|
|
68241
|
+
for (const area of [accounts_exports, timelines_exports, statuses_exports, media_exports2]) {
|
|
67206
68242
|
if (await area.handle(this, ctx)) return true;
|
|
67207
68243
|
}
|
|
67208
68244
|
this.log(`mastoapi: unhandled ${req.method} ${pathname} \u2014 punch list`);
|
|
@@ -67273,6 +68309,9 @@ var MastoApi = class _MastoApi {
|
|
|
67273
68309
|
status(...a) {
|
|
67274
68310
|
return status(this, ...a);
|
|
67275
68311
|
}
|
|
68312
|
+
quoteApproval(...a) {
|
|
68313
|
+
return quoteApproval(this, ...a);
|
|
68314
|
+
}
|
|
67276
68315
|
filtersFor(...a) {
|
|
67277
68316
|
return filtersFor(this, ...a);
|
|
67278
68317
|
}
|
|
@@ -67322,11 +68361,32 @@ var MastoApi = class _MastoApi {
|
|
|
67322
68361
|
var MAX_TIMELINE_BYTES = 2 * 1024 * 1024;
|
|
67323
68362
|
var DEFAULTS2 = {
|
|
67324
68363
|
instance: "https://mastodon.social",
|
|
67325
|
-
tags: [
|
|
68364
|
+
tags: [
|
|
68365
|
+
"fediverse",
|
|
68366
|
+
"activitypub",
|
|
68367
|
+
// the network this account is on
|
|
68368
|
+
"opensource",
|
|
68369
|
+
"foss",
|
|
68370
|
+
// how the software gets made
|
|
68371
|
+
"solidproject",
|
|
68372
|
+
"linkeddata",
|
|
68373
|
+
// where the data lives
|
|
68374
|
+
"privacy",
|
|
68375
|
+
"digitalrights",
|
|
68376
|
+
// what is at stake
|
|
68377
|
+
"indieweb",
|
|
68378
|
+
"smallweb",
|
|
68379
|
+
// publishing for yourself
|
|
68380
|
+
"commons",
|
|
68381
|
+
"platformcooperativism"
|
|
68382
|
+
// owning it together
|
|
68383
|
+
],
|
|
67326
68384
|
intervalMin: 15
|
|
67327
68385
|
};
|
|
67328
68386
|
var PER_TAG = 20;
|
|
67329
|
-
var MAX_NEW_PER_SWEEP =
|
|
68387
|
+
var MAX_NEW_PER_SWEEP = 8;
|
|
68388
|
+
var MAX_NEW_PER_TAG = 2;
|
|
68389
|
+
var TAGS_PER_SWEEP = 4;
|
|
67330
68390
|
var MAX_TAG_ENTRIES = 200;
|
|
67331
68391
|
var BACKOFF_MIN_MS2 = 15 * 6e4;
|
|
67332
68392
|
var BACKOFF_MAX_MS2 = 6 * 60 * 6e4;
|
|
@@ -67336,6 +68396,7 @@ var TagFeed = class {
|
|
|
67336
68396
|
Object.assign(this, { store, intake, log: log2, fetcher: fetcher2 });
|
|
67337
68397
|
this.lastSweep = null;
|
|
67338
68398
|
this.lastAdded = 0;
|
|
68399
|
+
this.cursor = 0;
|
|
67339
68400
|
}
|
|
67340
68401
|
config() {
|
|
67341
68402
|
return { ...DEFAULTS2, ...this.store.read("tagfeed.json", {}) };
|
|
@@ -67393,9 +68454,13 @@ var TagFeed = class {
|
|
|
67393
68454
|
const known2 = new Set(this.store.getStatuses().map((s) => s.noteId));
|
|
67394
68455
|
let budget = MAX_NEW_PER_SWEEP;
|
|
67395
68456
|
let added = 0;
|
|
68457
|
+
const start = this.cursor % tags.length;
|
|
68458
|
+
this.cursor = (start + TAGS_PER_SWEEP) % tags.length;
|
|
68459
|
+
const order = [...tags, ...tags].slice(start, start + Math.min(TAGS_PER_SWEEP, tags.length));
|
|
67396
68460
|
this.store.hold?.();
|
|
67397
68461
|
try {
|
|
67398
|
-
for (const tag of
|
|
68462
|
+
for (const tag of order) {
|
|
68463
|
+
let perTag = MAX_NEW_PER_TAG;
|
|
67399
68464
|
let list3;
|
|
67400
68465
|
try {
|
|
67401
68466
|
const { safeFetch: safeFetch2, retryAfterMs: retryAfterMs3, readCapped: readCapped3 } = await Promise.resolve().then(() => (init_safefetch(), safefetch_exports));
|
|
@@ -67415,7 +68480,9 @@ var TagFeed = class {
|
|
|
67415
68480
|
for (const st2 of Array.isArray(list3) ? list3 : []) {
|
|
67416
68481
|
const noteId = st2?.uri;
|
|
67417
68482
|
if (!noteId || known2.has(noteId) || this.store.isBlocked(noteId)) continue;
|
|
67418
|
-
if (budget
|
|
68483
|
+
if (perTag <= 0 || budget <= 0) break;
|
|
68484
|
+
perTag--;
|
|
68485
|
+
budget--;
|
|
67419
68486
|
const note = await this.intake.fetchAP(noteId).catch(() => null);
|
|
67420
68487
|
if (!note || note.id !== noteId || !isContentType(note.type)) continue;
|
|
67421
68488
|
const author = authorOf(note);
|
|
@@ -67439,6 +68506,7 @@ var TagFeed = class {
|
|
|
67439
68506
|
known2.add(noteId);
|
|
67440
68507
|
added++;
|
|
67441
68508
|
}
|
|
68509
|
+
if (budget <= 0) break;
|
|
67442
68510
|
}
|
|
67443
68511
|
const all = this.store.getStatuses();
|
|
67444
68512
|
const tagged = all.filter((s) => s.kind === "tag");
|
|
@@ -67450,7 +68518,7 @@ var TagFeed = class {
|
|
|
67450
68518
|
this.store.release?.();
|
|
67451
68519
|
}
|
|
67452
68520
|
this.lastAdded = added;
|
|
67453
|
-
if (added) this.log(`tagfeed: +${added} from #${
|
|
68521
|
+
if (added) this.log(`tagfeed: +${added} from #${order.join(" #")}`);
|
|
67454
68522
|
}
|
|
67455
68523
|
};
|
|
67456
68524
|
|
|
@@ -67743,7 +68811,7 @@ var PodTransport = class {
|
|
|
67743
68811
|
// leaves the private trees world-readable. $rdf.sym() also throws on an
|
|
67744
68812
|
// illegal IRI, so a pod URL with something odd in it fails here rather than
|
|
67745
68813
|
// silently producing a document that means something else.
|
|
67746
|
-
aclDoc(targetUrl, publicModes, { appendAgents = [], aclUrl = null } = {}) {
|
|
68814
|
+
aclDoc(targetUrl, publicModes, { appendAgents = [], readAgents = [], aclUrl = null } = {}) {
|
|
67747
68815
|
const url = aclUrl || targetUrl + ".acl";
|
|
67748
68816
|
const doc = namedNode2(url);
|
|
67749
68817
|
const target = namedNode2(targetUrl);
|
|
@@ -67759,6 +68827,7 @@ var PodTransport = class {
|
|
|
67759
68827
|
authorize(namedNode2(url + "#public"), ACL("agentClass"), FOAF("Agent"), publicModes);
|
|
67760
68828
|
}
|
|
67761
68829
|
appendAgents.forEach((webId, i) => authorize(namedNode2(url + `#gw${i}`), ACL("agent"), namedNode2(webId), ["Append"]));
|
|
68830
|
+
readAgents.forEach((webId, i) => authorize(namedNode2(url + `#r${i}`), ACL("agent"), namedNode2(webId), ["Read"]));
|
|
67762
68831
|
authorize(
|
|
67763
68832
|
namedNode2(url + "#owner"),
|
|
67764
68833
|
ACL("agent"),
|
|
@@ -67948,61 +69017,6 @@ var BrowserRemotePod = class extends PodTransport {
|
|
|
67948
69017
|
}
|
|
67949
69018
|
};
|
|
67950
69019
|
|
|
67951
|
-
// web/app/idb-kv.mjs
|
|
67952
|
-
var DB = "fedipod-accounts";
|
|
67953
|
-
function open() {
|
|
67954
|
-
return new Promise((res, rej) => {
|
|
67955
|
-
const r = indexedDB.open(DB, 1);
|
|
67956
|
-
r.onupgradeneeded = () => {
|
|
67957
|
-
const db = r.result;
|
|
67958
|
-
if (!db.objectStoreNames.contains("kv")) db.createObjectStore("kv");
|
|
67959
|
-
};
|
|
67960
|
-
r.onsuccess = () => res(r.result);
|
|
67961
|
-
r.onerror = () => rej(r.error);
|
|
67962
|
-
});
|
|
67963
|
-
}
|
|
67964
|
-
async function kvGet(key) {
|
|
67965
|
-
const db = await open();
|
|
67966
|
-
return new Promise((res, rej) => {
|
|
67967
|
-
const rq = db.transaction("kv", "readonly").objectStore("kv").get(key);
|
|
67968
|
-
rq.onsuccess = () => res(rq.result ?? null);
|
|
67969
|
-
rq.onerror = () => rej(rq.error);
|
|
67970
|
-
});
|
|
67971
|
-
}
|
|
67972
|
-
async function kvAll() {
|
|
67973
|
-
const db = await open();
|
|
67974
|
-
return new Promise((res, rej) => {
|
|
67975
|
-
const out = {};
|
|
67976
|
-
const cur = db.transaction("kv", "readonly").objectStore("kv").openCursor();
|
|
67977
|
-
cur.onsuccess = () => {
|
|
67978
|
-
const c = cur.result;
|
|
67979
|
-
if (c) {
|
|
67980
|
-
out[c.key] = c.value;
|
|
67981
|
-
c.continue();
|
|
67982
|
-
} else res(out);
|
|
67983
|
-
};
|
|
67984
|
-
cur.onerror = () => rej(cur.error);
|
|
67985
|
-
});
|
|
67986
|
-
}
|
|
67987
|
-
async function kvPut(key, val) {
|
|
67988
|
-
const db = await open();
|
|
67989
|
-
return new Promise((res, rej) => {
|
|
67990
|
-
const tx = db.transaction("kv", "readwrite");
|
|
67991
|
-
tx.objectStore("kv").put(val, key);
|
|
67992
|
-
tx.oncomplete = () => res();
|
|
67993
|
-
tx.onerror = () => rej(tx.error);
|
|
67994
|
-
});
|
|
67995
|
-
}
|
|
67996
|
-
async function kvDel(key) {
|
|
67997
|
-
const db = await open();
|
|
67998
|
-
return new Promise((res, rej) => {
|
|
67999
|
-
const tx = db.transaction("kv", "readwrite");
|
|
68000
|
-
tx.objectStore("kv").delete(key);
|
|
68001
|
-
tx.oncomplete = () => res();
|
|
68002
|
-
tx.onerror = () => rej(tx.error);
|
|
68003
|
-
});
|
|
68004
|
-
}
|
|
68005
|
-
|
|
68006
69020
|
// web/app/keystore.mjs
|
|
68007
69021
|
var PEM = (der, label) => {
|
|
68008
69022
|
const b642 = btoa(String.fromCharCode(...new Uint8Array(der)));
|
|
@@ -68034,18 +69048,6 @@ async function generateKeys() {
|
|
|
68034
69048
|
}
|
|
68035
69049
|
return rec;
|
|
68036
69050
|
}
|
|
68037
|
-
var PBKDF2_ITERATIONS = 31e4;
|
|
68038
|
-
async function deriveAesKey(password, salt, iterations) {
|
|
68039
|
-
const base = await crypto.subtle.importKey("raw", new TextEncoder().encode(password), "PBKDF2", false, ["deriveKey"]);
|
|
68040
|
-
return crypto.subtle.deriveKey(
|
|
68041
|
-
{ name: "PBKDF2", salt, iterations, hash: "SHA-256" },
|
|
68042
|
-
base,
|
|
68043
|
-
{ name: "AES-GCM", length: 256 },
|
|
68044
|
-
false,
|
|
68045
|
-
["encrypt", "decrypt"]
|
|
68046
|
-
);
|
|
68047
|
-
}
|
|
68048
|
-
var toB64 = (buf) => btoa(String.fromCharCode(...new Uint8Array(buf)));
|
|
68049
69051
|
function isKeyEnvelope(doc) {
|
|
68050
69052
|
return !!doc && doc.v === 1 && typeof doc.ct === "string" && typeof doc.salt === "string";
|
|
68051
69053
|
}
|
|
@@ -68055,21 +69057,6 @@ var KeyPasswordNeeded = class extends Error {
|
|
|
68055
69057
|
}
|
|
68056
69058
|
code = "key-password-needed";
|
|
68057
69059
|
};
|
|
68058
|
-
async function wrapKeys(keysRecord, password) {
|
|
68059
|
-
const salt = crypto.getRandomValues(new Uint8Array(16));
|
|
68060
|
-
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
68061
|
-
const aes = await deriveAesKey(password, salt, PBKDF2_ITERATIONS);
|
|
68062
|
-
const plaintext = new TextEncoder().encode(JSON.stringify(keysRecord));
|
|
68063
|
-
const ct = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, aes, plaintext);
|
|
68064
|
-
return {
|
|
68065
|
-
v: 1,
|
|
68066
|
-
kdf: "PBKDF2-SHA256",
|
|
68067
|
-
iterations: PBKDF2_ITERATIONS,
|
|
68068
|
-
salt: toB64(salt),
|
|
68069
|
-
iv: toB64(iv),
|
|
68070
|
-
ct: toB64(ct)
|
|
68071
|
-
};
|
|
68072
|
-
}
|
|
68073
69060
|
|
|
68074
69061
|
// web/app/keys-browser.mjs
|
|
68075
69062
|
var pemToDer = (pem) => Uint8Array.from(
|
|
@@ -68101,7 +69088,7 @@ async function loadKeysFromPod(remote, urls) {
|
|
|
68101
69088
|
const cached = await kvGet(keyCacheKey(podActor)).catch(() => null);
|
|
68102
69089
|
if (isOpenedKey(cached)) return fromCache(cached);
|
|
68103
69090
|
if (cached?.rsa?.privatePem) return cacheOpenedKeys(podActor, cached);
|
|
68104
|
-
const doc = await
|
|
69091
|
+
const doc = await readKeys(remote, urls);
|
|
68105
69092
|
if (isKeyEnvelope(doc)) throw new KeyPasswordNeeded();
|
|
68106
69093
|
if (!doc || !doc.rsa) throw new Error("no signing key on the pod \u2014 sign up did not finish");
|
|
68107
69094
|
return cacheOpenedKeys(podActor, doc);
|
|
@@ -68921,6 +69908,7 @@ var ADMIN_PATHS = /* @__PURE__ */ new Set([
|
|
|
68921
69908
|
"/log",
|
|
68922
69909
|
"/fediacct",
|
|
68923
69910
|
"/describe",
|
|
69911
|
+
"/follow",
|
|
68924
69912
|
"/atproto",
|
|
68925
69913
|
"/atproto/connect",
|
|
68926
69914
|
"/atproto/disconnect",
|
|
@@ -68930,6 +69918,9 @@ var ADMIN_PATHS = /* @__PURE__ */ new Set([
|
|
|
68930
69918
|
"/inbox/prune",
|
|
68931
69919
|
"/park",
|
|
68932
69920
|
"/revive",
|
|
69921
|
+
"/takeover",
|
|
69922
|
+
"/gateway/pause",
|
|
69923
|
+
"/gateway/close",
|
|
68933
69924
|
"/fediacct/connect",
|
|
68934
69925
|
"/fediacct/disconnect",
|
|
68935
69926
|
"/fediacct/callback"
|
|
@@ -69034,7 +70025,11 @@ var AdminFacade = class {
|
|
|
69034
70025
|
frontActor: g?.frontActor || null,
|
|
69035
70026
|
mode: g?.mode || "off",
|
|
69036
70027
|
hasSecret: !!g?.hmacSecret,
|
|
69037
|
-
stats: a.store.read("gateway-stats.json", { verified: 0, unverified: 0, lastAt: null })
|
|
70028
|
+
stats: a.store.read("gateway-stats.json", { verified: 0, unverified: 0, lastAt: null }),
|
|
70029
|
+
// The account's standing at the gateway — paused, closed — as the
|
|
70030
|
+
// gateway last said it (agent.mjs tellGateway). The record page
|
|
70031
|
+
// shows its pause and close controls only when this is here.
|
|
70032
|
+
standing: a.gatewayStanding || null
|
|
69038
70033
|
});
|
|
69039
70034
|
}
|
|
69040
70035
|
case "/deadletter":
|
|
@@ -69155,6 +70150,47 @@ var AdminFacade = class {
|
|
|
69155
70150
|
await a.publisher.publishProfile();
|
|
69156
70151
|
return json2(200, { ok: true, summary: cfg.summary || null, icon: cfg.icon || null });
|
|
69157
70152
|
}
|
|
70153
|
+
// Following somebody, the same call the Node agent's admin surface takes
|
|
70154
|
+
// (lib/device/admin/routes/social.mjs): by handle normally, by actor url
|
|
70155
|
+
// when WebFinger cannot answer for them. The remote-follow page
|
|
70156
|
+
// (/authorize_interaction) is what asks for it here — another server
|
|
70157
|
+
// sends its reader to that page when they press Follow and say they are
|
|
70158
|
+
// at this host.
|
|
70159
|
+
case "/follow": {
|
|
70160
|
+
await a.requestTakeover?.();
|
|
70161
|
+
if (body.actor) {
|
|
70162
|
+
await followActor(a, String(body.actor));
|
|
70163
|
+
return json2(200, { ok: true, actor: String(body.actor) });
|
|
70164
|
+
}
|
|
70165
|
+
if (!body.handle) return json2(400, { error: "handle or actor required" });
|
|
70166
|
+
return json2(200, await followHandle(a, String(body.handle)));
|
|
70167
|
+
}
|
|
70168
|
+
// Taking the account back. A viewer publishes nothing and drains
|
|
70169
|
+
// nothing, so an account whose lease is held by something that is not
|
|
70170
|
+
// running any more stays silent with no way to say so. The owner acting
|
|
70171
|
+
// HERE outranks whatever holds it.
|
|
70172
|
+
case "/takeover": {
|
|
70173
|
+
if (!await a.requestTakeover?.()) return json2(503, { error: "the lease could not be taken \u2014 the pod refused the write" });
|
|
70174
|
+
return json2(200, { ok: true, mode: a.status().mode });
|
|
70175
|
+
}
|
|
70176
|
+
// The account's standing at its gateway: paused by its owner, or closed
|
|
70177
|
+
// for good (front-core: accounts that go quiet). Both go to the gateway
|
|
70178
|
+
// with the pod session as proof, and its answer is the new standing.
|
|
70179
|
+
case "/gateway/pause": {
|
|
70180
|
+
if (typeof body.paused !== "boolean") return json2(400, { error: "paused must be true or false" });
|
|
70181
|
+
if (!a.pauseAtGateway || !a.gatewayApi) return json2(501, { error: "this account is not at a gateway" });
|
|
70182
|
+
const r = await a.pauseAtGateway(body.paused);
|
|
70183
|
+
if (!r) return json2(502, { error: "the gateway could not be reached" });
|
|
70184
|
+
return json2(r.status === 200 ? 200 : r.status, r);
|
|
70185
|
+
}
|
|
70186
|
+
case "/gateway/close": {
|
|
70187
|
+
const handle7 = String(a.store.getConfig()?.handle || "").toLowerCase();
|
|
70188
|
+
if (!handle7 || String(body.confirm || "").toLowerCase() !== handle7) return json2(400, { error: "type the handle to confirm" });
|
|
70189
|
+
if (!a.closeAtGateway || !a.gatewayApi) return json2(501, { error: "this account is not at a gateway" });
|
|
70190
|
+
const r = await a.closeAtGateway();
|
|
70191
|
+
if (!r) return json2(502, { error: "the gateway could not be reached" });
|
|
70192
|
+
return json2(r.status === 200 ? 200 : r.status, r);
|
|
70193
|
+
}
|
|
69158
70194
|
// Going quiet, and coming back. The record page's active/parked select.
|
|
69159
70195
|
case "/park": {
|
|
69160
70196
|
if (!await a.requestTakeover?.()) return json2(503, { error: "another device is active for this pod \u2014 park from there" });
|
|
@@ -69166,13 +70202,8 @@ var AdminFacade = class {
|
|
|
69166
70202
|
}
|
|
69167
70203
|
case "/rotate-key": {
|
|
69168
70204
|
await a.requestTakeover?.();
|
|
69169
|
-
|
|
69170
|
-
|
|
69171
|
-
return json2(200, { ok: true, changed: !!r?.changed });
|
|
69172
|
-
} catch (e) {
|
|
69173
|
-
if (e.code !== "key-password-needed") throw e;
|
|
69174
|
-
return json2(428, { error: e.message, needsPassword: true });
|
|
69175
|
-
}
|
|
70205
|
+
const r = await a.rotateKey();
|
|
70206
|
+
return json2(200, { ok: true, changed: !!r?.changed });
|
|
69176
70207
|
}
|
|
69177
70208
|
// Recover posts this browser lost, from what the pod still holds.
|
|
69178
70209
|
case "/rebuild": {
|
|
@@ -69471,6 +70502,60 @@ var AdminFacade = class {
|
|
|
69471
70502
|
}
|
|
69472
70503
|
};
|
|
69473
70504
|
|
|
70505
|
+
// web/app/gateway-move.mjs
|
|
70506
|
+
init_wire();
|
|
70507
|
+
async function completeGatewayMove(agent2) {
|
|
70508
|
+
const cfg = agent2.store.getConfig();
|
|
70509
|
+
const mv = cfg?.movedFrom;
|
|
70510
|
+
if (!mv || mv.completedAt) return null;
|
|
70511
|
+
const log2 = agent2.log;
|
|
70512
|
+
const newActor = agent2.urls.actor;
|
|
70513
|
+
let told;
|
|
70514
|
+
try {
|
|
70515
|
+
told = await agent2.sessionFetch(`${mv.gateway}/api/move`, {
|
|
70516
|
+
method: "POST",
|
|
70517
|
+
headers: { "content-type": "application/json" },
|
|
70518
|
+
body: JSON.stringify({ handle: mv.handle, movedTo: newActor })
|
|
70519
|
+
});
|
|
70520
|
+
} catch (e) {
|
|
70521
|
+
told = { status: 0, statusText: e.message };
|
|
70522
|
+
}
|
|
70523
|
+
if (told.status !== 200) {
|
|
70524
|
+
log2(`gateway move: ${mv.gateway} did not mark @${mv.handle} as moved (${told.status || told.statusText}) \u2014 will retry`);
|
|
70525
|
+
return { told: false };
|
|
70526
|
+
}
|
|
70527
|
+
const contacts = agent2.store.getContacts();
|
|
70528
|
+
const inboxes = [...new Set(contacts.followers.map((f) => f.sharedInbox || f.inbox).filter(Boolean))];
|
|
70529
|
+
const activity = moveActivity({ actor: mv.actor }, newActor, Date.now());
|
|
70530
|
+
const sender = new RelayDeliverer({
|
|
70531
|
+
passive: true,
|
|
70532
|
+
store: agent2.store,
|
|
70533
|
+
rsaPrivate: agent2.deliverer.rsaPrivate,
|
|
70534
|
+
keyId: `${mv.actor}#main-key`,
|
|
70535
|
+
actorId: mv.actor,
|
|
70536
|
+
log: log2,
|
|
70537
|
+
relayUrl: `${mv.gateway}/api/relay`,
|
|
70538
|
+
handle: mv.handle,
|
|
70539
|
+
sessionFetch: agent2.sessionFetch
|
|
70540
|
+
});
|
|
70541
|
+
let sent = 0;
|
|
70542
|
+
let failed = 0;
|
|
70543
|
+
for (const inbox of inboxes) {
|
|
70544
|
+
try {
|
|
70545
|
+
await sender.deliverNow(inbox, activity);
|
|
70546
|
+
sent++;
|
|
70547
|
+
} catch (e) {
|
|
70548
|
+
failed++;
|
|
70549
|
+
log2(`gateway move: Move to ${inbox} failed: ${e.message}`);
|
|
70550
|
+
}
|
|
70551
|
+
}
|
|
70552
|
+
const done = { ...mv, completedAt: (/* @__PURE__ */ new Date()).toISOString(), moveSent: sent, moveFailed: failed };
|
|
70553
|
+
agent2.store.setConfig({ ...agent2.store.getConfig(), movedFrom: done });
|
|
70554
|
+
await agent2.store.flush?.();
|
|
70555
|
+
log2(`moved from ${mv.actor} to ${newActor}: Move sent to ${sent} inbox(es)${failed ? `, ${failed} failed` : ""}`);
|
|
70556
|
+
return done;
|
|
70557
|
+
}
|
|
70558
|
+
|
|
69474
70559
|
// stub:node:os
|
|
69475
70560
|
var fail2 = () => {
|
|
69476
70561
|
throw new Error("node:os is not available in the browser agent");
|
|
@@ -70582,6 +71667,48 @@ var originAuthorities = (host) => ({
|
|
|
70582
71667
|
}
|
|
70583
71668
|
// fetch-only: this build serves no socket
|
|
70584
71669
|
});
|
|
71670
|
+
async function accountNotRead({ unread, podBase, state, webId, actorUrl = null, fetchImpl = globalThis.fetch }) {
|
|
71671
|
+
const host = (() => {
|
|
71672
|
+
try {
|
|
71673
|
+
return new URL(podBase).host;
|
|
71674
|
+
} catch {
|
|
71675
|
+
return podBase;
|
|
71676
|
+
}
|
|
71677
|
+
})();
|
|
71678
|
+
const of = (code, message) => {
|
|
71679
|
+
const e = new Error(message);
|
|
71680
|
+
e.code = code;
|
|
71681
|
+
return e;
|
|
71682
|
+
};
|
|
71683
|
+
if (!unread) {
|
|
71684
|
+
const actor = actorUrl ? await fetchImpl(actorUrl, { headers: { accept: "application/activity+json" } }).then((r) => r.ok ? r.json() : null).catch(() => null) : null;
|
|
71685
|
+
if (actor?.preferredUsername) {
|
|
71686
|
+
const at = (() => {
|
|
71687
|
+
try {
|
|
71688
|
+
return `@${actor.preferredUsername}@${new URL(actor.id || actorUrl).host}`;
|
|
71689
|
+
} catch {
|
|
71690
|
+
return actor.preferredUsername;
|
|
71691
|
+
}
|
|
71692
|
+
})();
|
|
71693
|
+
return of("device-account", `${host} holds the account ${at}, but not its record: this account is run by a DeviceAgent, and its record lives on that device. Open it from that device's admin page, not here. The sign-in used here was ${webId}.`);
|
|
71694
|
+
}
|
|
71695
|
+
return of("no-account", `${host} answered, and there is no FediPod account in it. Sign in with the pod that holds your account, or make an account in this one \u2014 both start from the sign-in page. The sign-in used here was ${webId}.`);
|
|
71696
|
+
}
|
|
71697
|
+
const status2 = Number((/HTTP (\d{3})/u.exec(unread.message) || [])[1]) || 0;
|
|
71698
|
+
if (status2 === 401 || status2 === 403) {
|
|
71699
|
+
return of("sign-in-refused", `${host} would not let this sign-in read your account \u2014 it answered "not allowed" (HTTP ${status2}) \u2014 and renewing the sign-in did not change its mind. Your account and everything in it are untouched. Signing in at ${host} again is the one thing that helps.`);
|
|
71700
|
+
}
|
|
71701
|
+
if (status2 === 429) {
|
|
71702
|
+
return of("pod-busy", `${host} is telling this browser it has asked for too much too quickly (HTTP 429), so your account could not be read. Nothing is wrong with your account, and nothing here needs doing: this page is waiting the throttle out and will ask again by itself. While a pod is rate-limiting you, only time helps.`);
|
|
71703
|
+
}
|
|
71704
|
+
if (status2 >= 500) {
|
|
71705
|
+
return of("pod-error", `${host} broke while handing your account over (HTTP ${status2}). That is the pod's own failure, not your account's. This page asks again by itself in a moment; if it keeps saying this, the pod is down and there is nothing to do here.`);
|
|
71706
|
+
}
|
|
71707
|
+
if (status2 === 404) {
|
|
71708
|
+
return of("no-account-here", `${host} says there is nothing at ${state} (HTTP 404). Either no FediPod account has ever been set up in this pod, or you signed in with a different pod than the one holding yours. Sign in with that one, or make an account in this one \u2014 both start from the sign-in page.`);
|
|
71709
|
+
}
|
|
71710
|
+
return of("pod-unreachable", `This browser could not reach ${host} to read your account (${unread.message}). Your account is still there. This page tries again by itself; if it keeps saying this, check that you are online and that the pod is up.`);
|
|
71711
|
+
}
|
|
70585
71712
|
var BrowserAgent = class _BrowserAgent {
|
|
70586
71713
|
constructor({ log: log2 = console.log } = {}) {
|
|
70587
71714
|
this.log = log2;
|
|
@@ -70605,10 +71732,46 @@ var BrowserAgent = class _BrowserAgent {
|
|
|
70605
71732
|
this.log("took over from the other device (action here)");
|
|
70606
71733
|
return true;
|
|
70607
71734
|
}
|
|
71735
|
+
// One call to the gateway's owner API, proved with the pod session. What
|
|
71736
|
+
// comes back on 200 or 410 is the account's standing there, kept for the
|
|
71737
|
+
// manage page; anything else is logged and forgotten.
|
|
71738
|
+
async tellGateway(what, body) {
|
|
71739
|
+
if (!this.gatewayApi) return null;
|
|
71740
|
+
let res;
|
|
71741
|
+
try {
|
|
71742
|
+
res = await this.sessionFetch(`${this.gatewayApi}/${what}`, {
|
|
71743
|
+
method: "POST",
|
|
71744
|
+
headers: { "content-type": "application/json" },
|
|
71745
|
+
body: JSON.stringify(body)
|
|
71746
|
+
});
|
|
71747
|
+
} catch (e) {
|
|
71748
|
+
this.log(`gateway ${what}: ${e.message}`);
|
|
71749
|
+
return null;
|
|
71750
|
+
}
|
|
71751
|
+
const json2 = await res.json().catch(() => ({}));
|
|
71752
|
+
const out = { status: res.status, ...json2 };
|
|
71753
|
+
if (res.status === 200 || res.status === 410) this.gatewayStanding = out;
|
|
71754
|
+
else this.log(`gateway ${what}: ${res.status} ${json2.error || ""}`);
|
|
71755
|
+
return out;
|
|
71756
|
+
}
|
|
71757
|
+
openAtGateway() {
|
|
71758
|
+
return this.tellGateway("open", { handle: this.doorKey });
|
|
71759
|
+
}
|
|
71760
|
+
pauseAtGateway(paused) {
|
|
71761
|
+
return this.tellGateway("pause", { handle: this.doorKey, paused: !!paused });
|
|
71762
|
+
}
|
|
71763
|
+
closeAtGateway() {
|
|
71764
|
+
return this.tellGateway("close", { handle: this.doorKey, confirm: true });
|
|
71765
|
+
}
|
|
71766
|
+
static OPEN_EVERY_MS = 60 * 6e4;
|
|
70608
71767
|
// Become the active agent: renew the lease, then start what a viewer skips —
|
|
70609
71768
|
// publish the face, drain the inbox, run the mirrors.
|
|
70610
71769
|
async goActive() {
|
|
70611
71770
|
this.viewer = false;
|
|
71771
|
+
clearInterval(this._openTimer);
|
|
71772
|
+
this._openTimer = setInterval(() => {
|
|
71773
|
+
this.openAtGateway();
|
|
71774
|
+
}, _BrowserAgent.OPEN_EVERY_MS);
|
|
70612
71775
|
this.lease.onLost = () => this.demote();
|
|
70613
71776
|
this.lease.startRenewal();
|
|
70614
71777
|
try {
|
|
@@ -70618,6 +71781,7 @@ var BrowserAgent = class _BrowserAgent {
|
|
|
70618
71781
|
this.deliverer?.startQueue?.();
|
|
70619
71782
|
await this.publisher.publishProfile();
|
|
70620
71783
|
await this.store.flush?.();
|
|
71784
|
+
await completeGatewayMove(this).catch((e) => this.log(`gateway move: ${e.message}`));
|
|
70621
71785
|
await this.intake.start();
|
|
70622
71786
|
this.startBsky();
|
|
70623
71787
|
this.startAccts();
|
|
@@ -70634,6 +71798,8 @@ var BrowserAgent = class _BrowserAgent {
|
|
|
70634
71798
|
this.viewer = true;
|
|
70635
71799
|
this.log("another device took over \u2014 read-only here");
|
|
70636
71800
|
this.lease.stopRenewal();
|
|
71801
|
+
clearInterval(this._openTimer);
|
|
71802
|
+
this._openTimer = null;
|
|
70637
71803
|
this.intake?.stop?.();
|
|
70638
71804
|
this.deliverer?.stop?.();
|
|
70639
71805
|
this.importer?.stop?.();
|
|
@@ -70685,6 +71851,21 @@ var BrowserAgent = class _BrowserAgent {
|
|
|
70685
71851
|
* - { credential, keysRecord, config }: a client-credential session with the
|
|
70686
71852
|
* material in hand (the offline test path).
|
|
70687
71853
|
*/
|
|
71854
|
+
// This browser's name for itself, minted once and kept. Not an identity and
|
|
71855
|
+
// not a secret — it says only "the same browser as last time" to the lease.
|
|
71856
|
+
// If storage cannot be reached, a fresh one is honest: this browser cannot
|
|
71857
|
+
// prove it is the one that held the lease, so it should not claim to be.
|
|
71858
|
+
async deviceId() {
|
|
71859
|
+
try {
|
|
71860
|
+
const had = await kvGet("device-id");
|
|
71861
|
+
if (had) return had;
|
|
71862
|
+
const made = crypto.randomUUID();
|
|
71863
|
+
await kvPut("device-id", made);
|
|
71864
|
+
return made;
|
|
71865
|
+
} catch {
|
|
71866
|
+
return null;
|
|
71867
|
+
}
|
|
71868
|
+
}
|
|
70688
71869
|
async boot({ oidc, credential, keysRecord, config, frontOrigin }) {
|
|
70689
71870
|
let session;
|
|
70690
71871
|
let webId;
|
|
@@ -70700,15 +71881,18 @@ var BrowserAgent = class _BrowserAgent {
|
|
|
70700
71881
|
remotePod = credential.remotePod;
|
|
70701
71882
|
}
|
|
70702
71883
|
this.webId = webId;
|
|
71884
|
+
this.sessionFetch = session.fetch;
|
|
70703
71885
|
const root = config && config.root || "fedipod/";
|
|
70704
71886
|
this.remote = new BrowserRemotePod(session, { webId, log: this.log });
|
|
70705
71887
|
this.urls = apUrls2(remotePod, root);
|
|
70706
71888
|
const podFetch = (u, i) => this.remote.fetch(u, i);
|
|
70707
71889
|
this.store = new PodStore({ storage: new HttpStorage(this.urls.state, podFetch), log: this.log });
|
|
70708
|
-
|
|
71890
|
+
let unread = null;
|
|
71891
|
+
await this.store.load().catch((e) => {
|
|
71892
|
+
unread = e;
|
|
70709
71893
|
});
|
|
70710
71894
|
const cfg = config || this.store.getConfig();
|
|
70711
|
-
if (!cfg) throw
|
|
71895
|
+
if (!cfg) throw await accountNotRead({ unread, podBase: remotePod, state: this.urls.state, webId, actorUrl: this.urls.actor });
|
|
70712
71896
|
this.store.setConfig({ ...this.store.getConfig() || {}, ...cfg, root });
|
|
70713
71897
|
config = this.store.getConfig();
|
|
70714
71898
|
const publicBase = config.gateway?.frontActor ? config.gateway.frontActor.replace(/ap\/actor\/?$/, "") : null;
|
|
@@ -70729,6 +71913,29 @@ var BrowserAgent = class _BrowserAgent {
|
|
|
70729
71913
|
sessionFetch: session.fetch,
|
|
70730
71914
|
onGone: () => this.publisher.publishCollections({ followers: true })
|
|
70731
71915
|
});
|
|
71916
|
+
this.gatewayApi = null;
|
|
71917
|
+
try {
|
|
71918
|
+
if (config.gateway?.url) this.gatewayApi = `${new URL(config.gateway.url).origin}/api`;
|
|
71919
|
+
} catch {
|
|
71920
|
+
this.gatewayApi = null;
|
|
71921
|
+
}
|
|
71922
|
+
this.doorKey = doorKeyOf(config.gateway?.url) || config.handle;
|
|
71923
|
+
this.gatewayStanding = null;
|
|
71924
|
+
const standing = await this.openAtGateway();
|
|
71925
|
+
if (standing?.status === 410) {
|
|
71926
|
+
const host = (() => {
|
|
71927
|
+
try {
|
|
71928
|
+
return new URL(this.gatewayApi).host;
|
|
71929
|
+
} catch {
|
|
71930
|
+
return "the gateway";
|
|
71931
|
+
}
|
|
71932
|
+
})();
|
|
71933
|
+
const address = this.doorKey.includes("@") ? `@${this.doorKey}` : `@${this.doorKey}@${host}`;
|
|
71934
|
+
const why = standing.closedBy === "owner" ? "you closed it" : "nothing opened it for six months";
|
|
71935
|
+
const e = new Error(`${address} is closed at ${host}: ${why}, and a closed address does not come back. Everything on your pod is untouched. The sign-in used here was ${webId}.`);
|
|
71936
|
+
e.code = "address-closed";
|
|
71937
|
+
throw e;
|
|
71938
|
+
}
|
|
70732
71939
|
this.publisher = new Publisher({
|
|
70733
71940
|
config: this.store.getConfig(),
|
|
70734
71941
|
remote: this.remote,
|
|
@@ -70746,7 +71953,17 @@ var BrowserAgent = class _BrowserAgent {
|
|
|
70746
71953
|
this.atproto = new BrowserAtproto({ store: this.store, actorId: this.urls.actor, log: this.log });
|
|
70747
71954
|
this.publisher.atproto = this.atproto;
|
|
70748
71955
|
this.fediaccts = new BrowserFediAccounts({ store: this.store, actorId: this.urls.actor, log: this.log });
|
|
70749
|
-
this.lease = new Lease({
|
|
71956
|
+
this.lease = new Lease({
|
|
71957
|
+
url: this.urls.state + "lease.json",
|
|
71958
|
+
fetchImpl: podFetch,
|
|
71959
|
+
log: this.log,
|
|
71960
|
+
// Kept on this origin, so THIS browser is one holder however many times
|
|
71961
|
+
// its worker is killed and restarted. Without it every restart was a new
|
|
71962
|
+
// holder: the old lease still had minutes to run, so the browser found
|
|
71963
|
+
// its own account "active on another device" and asked to take it over.
|
|
71964
|
+
// Switching between the clients did it every time, being a navigation.
|
|
71965
|
+
id: await this.deviceId()
|
|
71966
|
+
});
|
|
70750
71967
|
this.c2s = new C2S({ agent: this, log: this.log });
|
|
70751
71968
|
this.intake = new Intake({
|
|
70752
71969
|
config: this.store.getConfig(),
|
|
@@ -70827,23 +72044,11 @@ var BrowserAgent = class _BrowserAgent {
|
|
|
70827
72044
|
// actor so the new public key is on the wire. The old key stops signing the
|
|
70828
72045
|
// moment this returns — same one-way change as the Node agent's rotateKey
|
|
70829
72046
|
// (run-agent.mjs).
|
|
70830
|
-
|
|
70831
|
-
// The pod's copy is wrapped, so this needs the account password. There is
|
|
70832
|
-
// nowhere to get it from without asking: the worker boots from a stored
|
|
70833
|
-
// session and holds no password, and caching one to save a prompt on a
|
|
70834
|
-
// once-in-a-while action would put the account password in storage to avoid
|
|
70835
|
-
// typing it. So the caller supplies it, and rotating without one is refused
|
|
70836
|
-
// rather than quietly writing a bare key back where a wrapped one was.
|
|
70837
|
-
async rotateKey({ password } = {}) {
|
|
70838
|
-
if (!password) {
|
|
70839
|
-
const e = new Error("rotating the signing key needs your account password \u2014 it is what the new key is locked under on the pod");
|
|
70840
|
-
e.code = "key-password-needed";
|
|
70841
|
-
throw e;
|
|
70842
|
-
}
|
|
72047
|
+
async rotateKey() {
|
|
70843
72048
|
const before = this.publisher.publicKeyPem;
|
|
70844
72049
|
const rec = await generateKeys();
|
|
70845
72050
|
rec.mintedFor = this.urls.actor;
|
|
70846
|
-
await
|
|
72051
|
+
await writeKeys(this.remote, this.urls, rec);
|
|
70847
72052
|
const keys = await cacheOpenedKeys(podActorOf(this.urls), rec);
|
|
70848
72053
|
this.publisher.publicKeyPem = keys.rsaPublicPem;
|
|
70849
72054
|
this.deliverer.rsaPrivate = keys.rsaPrivate;
|
|
@@ -70978,43 +72183,14 @@ var BrowserAgent = class _BrowserAgent {
|
|
|
70978
72183
|
}
|
|
70979
72184
|
};
|
|
70980
72185
|
|
|
70981
|
-
//
|
|
70982
|
-
var DB2 = "fedipod-oidc";
|
|
72186
|
+
// lib/session/oidc-session.mjs
|
|
70983
72187
|
var STORE = "session";
|
|
70984
72188
|
var b64u3 = (buf) => btoa(String.fromCharCode(...new Uint8Array(buf))).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
70985
72189
|
var enc3 = (o) => b64u3(new TextEncoder().encode(JSON.stringify(o)));
|
|
70986
72190
|
var sha2564 = (s) => crypto.subtle.digest("SHA-256", new TextEncoder().encode(s));
|
|
70987
|
-
|
|
70988
|
-
|
|
70989
|
-
|
|
70990
|
-
r.onupgradeneeded = () => r.result.createObjectStore(STORE);
|
|
70991
|
-
r.onsuccess = () => res(r.result);
|
|
70992
|
-
r.onerror = () => rej(r.error);
|
|
70993
|
-
});
|
|
70994
|
-
}
|
|
70995
|
-
async function idbGet(key) {
|
|
70996
|
-
const db = await idb();
|
|
70997
|
-
return new Promise((res, rej) => {
|
|
70998
|
-
const t = db.transaction(STORE).objectStore(STORE).get(key);
|
|
70999
|
-
t.onsuccess = () => res(t.result);
|
|
71000
|
-
t.onerror = () => rej(t.error);
|
|
71001
|
-
});
|
|
71002
|
-
}
|
|
71003
|
-
async function idbPut(key, val) {
|
|
71004
|
-
const db = await idb();
|
|
71005
|
-
return new Promise((res, rej) => {
|
|
71006
|
-
const t = db.transaction(STORE, "readwrite").objectStore(STORE).put(val, key);
|
|
71007
|
-
t.onsuccess = () => res();
|
|
71008
|
-
t.onerror = () => rej(t.error);
|
|
71009
|
-
});
|
|
71010
|
-
}
|
|
71011
|
-
async function idbDel(key) {
|
|
71012
|
-
const db = await idb();
|
|
71013
|
-
return new Promise((res, rej) => {
|
|
71014
|
-
const t = db.transaction(STORE, "readwrite").objectStore(STORE).delete(key);
|
|
71015
|
-
t.onsuccess = () => res();
|
|
71016
|
-
t.onerror = () => rej(t.error);
|
|
71017
|
-
});
|
|
72191
|
+
var rand = (n = 32) => b64u3(crypto.getRandomValues(new Uint8Array(n)));
|
|
72192
|
+
async function dpopKey() {
|
|
72193
|
+
return crypto.subtle.generateKey({ name: "ECDSA", namedCurve: "P-256" }, false, ["sign"]);
|
|
71018
72194
|
}
|
|
71019
72195
|
async function publicJwk(pair) {
|
|
71020
72196
|
const j = await crypto.subtle.exportKey("jwk", pair.publicKey);
|
|
@@ -71027,64 +72203,217 @@ async function dpopProof(pair, jwk, htm, htu, ath) {
|
|
|
71027
72203
|
const sig = await crypto.subtle.sign({ name: "ECDSA", hash: "SHA-256" }, pair.privateKey, new TextEncoder().encode(data));
|
|
71028
72204
|
return `${data}.${b64u3(sig)}`;
|
|
71029
72205
|
}
|
|
71030
|
-
|
|
71031
|
-
|
|
71032
|
-
|
|
71033
|
-
}
|
|
71034
|
-
|
|
71035
|
-
const s = await idbGet("session").catch(() => null);
|
|
71036
|
-
if (s?.revocationEndpoint && s.refreshToken) {
|
|
71037
|
-
try {
|
|
71038
|
-
await fetch(s.revocationEndpoint, {
|
|
71039
|
-
method: "POST",
|
|
71040
|
-
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
71041
|
-
body: new URLSearchParams({
|
|
71042
|
-
token: s.refreshToken,
|
|
71043
|
-
token_type_hint: "refresh_token",
|
|
71044
|
-
client_id: s.client_id
|
|
71045
|
-
})
|
|
71046
|
-
});
|
|
71047
|
-
} catch {
|
|
71048
|
-
}
|
|
72206
|
+
var jwtPayload = (jwt) => {
|
|
72207
|
+
try {
|
|
72208
|
+
return JSON.parse(new TextDecoder().decode(Uint8Array.from(atob(jwt.split(".")[1].replace(/-/g, "+").replace(/_/g, "/")), (c) => c.charCodeAt(0))));
|
|
72209
|
+
} catch {
|
|
72210
|
+
return {};
|
|
71049
72211
|
}
|
|
71050
|
-
|
|
71051
|
-
|
|
72212
|
+
};
|
|
72213
|
+
async function discover(issuer) {
|
|
72214
|
+
const res = await fetch(`${issuer.replace(/\/+$/, "")}/.well-known/openid-configuration`, { headers: { accept: "application/json" } });
|
|
72215
|
+
if (!res.ok) throw new Error(`OIDC discovery failed at ${issuer} (HTTP ${res.status})`);
|
|
72216
|
+
return res.json();
|
|
71052
72217
|
}
|
|
71053
|
-
function
|
|
71054
|
-
|
|
71055
|
-
|
|
71056
|
-
|
|
71057
|
-
|
|
71058
|
-
|
|
71059
|
-
|
|
72218
|
+
async function registerClient(cfg, redirectUri, clientName) {
|
|
72219
|
+
const res = await fetch(cfg.registration_endpoint, {
|
|
72220
|
+
method: "POST",
|
|
72221
|
+
headers: { "content-type": "application/json" },
|
|
72222
|
+
body: JSON.stringify({
|
|
72223
|
+
client_name: clientName,
|
|
72224
|
+
redirect_uris: [redirectUri],
|
|
72225
|
+
token_endpoint_auth_method: "none",
|
|
72226
|
+
grant_types: ["authorization_code", "refresh_token"],
|
|
72227
|
+
response_types: ["code"],
|
|
72228
|
+
application_type: "web",
|
|
72229
|
+
scope: "openid webid offline_access"
|
|
72230
|
+
})
|
|
72231
|
+
});
|
|
72232
|
+
if (!res.ok) throw new Error(`client registration failed (HTTP ${res.status})`);
|
|
72233
|
+
return (await res.json()).client_id;
|
|
72234
|
+
}
|
|
72235
|
+
function solidOidcSession({ dbName = "solid-oidc-session", clientName = "Solid app" } = {}) {
|
|
72236
|
+
function idb() {
|
|
72237
|
+
return new Promise((res, rej) => {
|
|
72238
|
+
const r = indexedDB.open(dbName, 1);
|
|
72239
|
+
r.onupgradeneeded = () => r.result.createObjectStore(STORE);
|
|
72240
|
+
r.onsuccess = () => res(r.result);
|
|
72241
|
+
r.onerror = () => rej(r.error);
|
|
72242
|
+
});
|
|
72243
|
+
}
|
|
72244
|
+
async function idbGet(key) {
|
|
72245
|
+
const db = await idb();
|
|
72246
|
+
return new Promise((res, rej) => {
|
|
72247
|
+
const t = db.transaction(STORE).objectStore(STORE).get(key);
|
|
72248
|
+
t.onsuccess = () => res(t.result);
|
|
72249
|
+
t.onerror = () => rej(t.error);
|
|
72250
|
+
});
|
|
72251
|
+
}
|
|
72252
|
+
async function idbPut(key, val) {
|
|
72253
|
+
const db = await idb();
|
|
72254
|
+
return new Promise((res, rej) => {
|
|
72255
|
+
const t = db.transaction(STORE, "readwrite").objectStore(STORE).put(val, key);
|
|
72256
|
+
t.onsuccess = () => res();
|
|
72257
|
+
t.onerror = () => rej(t.error);
|
|
72258
|
+
});
|
|
72259
|
+
}
|
|
72260
|
+
async function idbDel(key) {
|
|
72261
|
+
const db = await idb();
|
|
72262
|
+
return new Promise((res, rej) => {
|
|
72263
|
+
const t = db.transaction(STORE, "readwrite").objectStore(STORE).delete(key);
|
|
72264
|
+
t.onsuccess = () => res();
|
|
72265
|
+
t.onerror = () => rej(t.error);
|
|
72266
|
+
});
|
|
72267
|
+
}
|
|
72268
|
+
async function beginLogin2({ issuer, redirectUri, clientName: name = clientName, returnTo = null }) {
|
|
72269
|
+
const cfg = await discover(issuer);
|
|
72270
|
+
const client_id = await registerClient(cfg, redirectUri, name);
|
|
72271
|
+
const pair = await dpopKey();
|
|
72272
|
+
const verifier = rand(48);
|
|
72273
|
+
const challenge = b64u3(await sha2564(verifier));
|
|
72274
|
+
const state = rand(16);
|
|
72275
|
+
await idbPut("pending", {
|
|
72276
|
+
issuer,
|
|
72277
|
+
client_id,
|
|
72278
|
+
redirectUri,
|
|
72279
|
+
verifier,
|
|
72280
|
+
state,
|
|
72281
|
+
pair,
|
|
72282
|
+
returnTo,
|
|
72283
|
+
tokenEndpoint: cfg.token_endpoint,
|
|
72284
|
+
authorizationEndpoint: cfg.authorization_endpoint,
|
|
72285
|
+
revocationEndpoint: cfg.revocation_endpoint || null
|
|
72286
|
+
});
|
|
72287
|
+
const url = new URL(cfg.authorization_endpoint);
|
|
72288
|
+
for (const [k, v] of Object.entries({
|
|
72289
|
+
client_id,
|
|
72290
|
+
redirect_uri: redirectUri,
|
|
72291
|
+
response_type: "code",
|
|
72292
|
+
scope: "openid webid offline_access",
|
|
72293
|
+
code_challenge: challenge,
|
|
72294
|
+
code_challenge_method: "S256",
|
|
72295
|
+
state,
|
|
72296
|
+
prompt: "consent"
|
|
72297
|
+
})) url.searchParams.set(k, v);
|
|
72298
|
+
return { authorizationUrl: url.href };
|
|
72299
|
+
}
|
|
72300
|
+
async function completeLogin2({ currentUrl }) {
|
|
72301
|
+
const u = new URL(currentUrl);
|
|
72302
|
+
const code = u.searchParams.get("code");
|
|
72303
|
+
const state = u.searchParams.get("state");
|
|
72304
|
+
if (!code) return null;
|
|
72305
|
+
const p = await idbGet("pending");
|
|
72306
|
+
if (!p || p.state !== state) throw new Error("login state mismatch");
|
|
72307
|
+
const jwk = await publicJwk(p.pair);
|
|
72308
|
+
const res = await fetch(p.tokenEndpoint, {
|
|
71060
72309
|
method: "POST",
|
|
71061
|
-
headers: { "content-type": "application/x-www-form-urlencoded", dpop: await dpopProof(
|
|
71062
|
-
body: new URLSearchParams({ grant_type: "
|
|
72310
|
+
headers: { "content-type": "application/x-www-form-urlencoded", dpop: await dpopProof(p.pair, jwk, "POST", p.tokenEndpoint) },
|
|
72311
|
+
body: new URLSearchParams({ grant_type: "authorization_code", code, redirect_uri: p.redirectUri, client_id: p.client_id, code_verifier: p.verifier })
|
|
71063
72312
|
});
|
|
71064
72313
|
const tok = await res.json().catch(() => ({}));
|
|
71065
|
-
if (!res.ok || !tok.access_token) {
|
|
71066
|
-
|
|
71067
|
-
|
|
71068
|
-
|
|
71069
|
-
|
|
71070
|
-
|
|
71071
|
-
|
|
71072
|
-
|
|
71073
|
-
|
|
71074
|
-
|
|
71075
|
-
|
|
71076
|
-
|
|
71077
|
-
|
|
71078
|
-
|
|
71079
|
-
|
|
71080
|
-
|
|
71081
|
-
|
|
71082
|
-
|
|
71083
|
-
return
|
|
71084
|
-
}
|
|
71085
|
-
|
|
72314
|
+
if (!res.ok || !tok.access_token) throw new Error(`token exchange failed (HTTP ${res.status}): ${tok.error || ""}`);
|
|
72315
|
+
const webId = jwtPayload(tok.access_token).webid || jwtPayload(tok.id_token || "").webid || null;
|
|
72316
|
+
const session = {
|
|
72317
|
+
issuer: p.issuer,
|
|
72318
|
+
client_id: p.client_id,
|
|
72319
|
+
tokenEndpoint: p.tokenEndpoint,
|
|
72320
|
+
pair: p.pair,
|
|
72321
|
+
// Where to hand the refresh token back at sign-out. Kept on the session
|
|
72322
|
+
// because discovery is a network round trip and sign-out should not need
|
|
72323
|
+
// one — see signOut().
|
|
72324
|
+
revocationEndpoint: p.revocationEndpoint || null,
|
|
72325
|
+
refreshToken: tok.refresh_token || null,
|
|
72326
|
+
accessToken: tok.access_token,
|
|
72327
|
+
expiresAt: Date.now() + Math.max(30, tok.expires_in || 300) * 1e3,
|
|
72328
|
+
webId
|
|
72329
|
+
};
|
|
72330
|
+
await idbPut("session", session);
|
|
72331
|
+
await idbDel("pending");
|
|
72332
|
+
return Object.assign(sessionHandle(session), p.returnTo ? { returnTo: p.returnTo } : {});
|
|
72333
|
+
}
|
|
72334
|
+
async function getSession2() {
|
|
72335
|
+
const s = await idbGet("session");
|
|
72336
|
+
return s ? sessionHandle(s) : null;
|
|
72337
|
+
}
|
|
72338
|
+
async function signOut2() {
|
|
72339
|
+
const s = await idbGet("session").catch(() => null);
|
|
72340
|
+
if (s?.revocationEndpoint && s.refreshToken) {
|
|
72341
|
+
try {
|
|
72342
|
+
await fetch(s.revocationEndpoint, {
|
|
72343
|
+
method: "POST",
|
|
72344
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
72345
|
+
body: new URLSearchParams({
|
|
72346
|
+
token: s.refreshToken,
|
|
72347
|
+
token_type_hint: "refresh_token",
|
|
72348
|
+
client_id: s.client_id
|
|
72349
|
+
})
|
|
72350
|
+
});
|
|
72351
|
+
} catch {
|
|
72352
|
+
}
|
|
72353
|
+
}
|
|
72354
|
+
await idbDel("session");
|
|
72355
|
+
await idbDel("pending");
|
|
72356
|
+
}
|
|
72357
|
+
function sessionHandle(s) {
|
|
72358
|
+
let { accessToken, expiresAt, refreshToken } = s;
|
|
72359
|
+
const jwkP = publicJwk(s.pair);
|
|
72360
|
+
const refresh = async () => {
|
|
72361
|
+
if (!refreshToken) throw new Error("session expired and there is no refresh token \u2014 sign in again");
|
|
72362
|
+
const jwk = await jwkP;
|
|
72363
|
+
let res;
|
|
72364
|
+
try {
|
|
72365
|
+
res = await fetch(s.tokenEndpoint, {
|
|
72366
|
+
method: "POST",
|
|
72367
|
+
headers: { "content-type": "application/x-www-form-urlencoded", dpop: await dpopProof(s.pair, jwk, "POST", s.tokenEndpoint) },
|
|
72368
|
+
body: new URLSearchParams({ grant_type: "refresh_token", refresh_token: refreshToken, client_id: s.client_id, scope: "openid webid offline_access" })
|
|
72369
|
+
});
|
|
72370
|
+
} catch (e) {
|
|
72371
|
+
throw new Error(`${e.message} \u2014 renewing your sign-in at ${new URL(s.tokenEndpoint).host}`);
|
|
72372
|
+
}
|
|
72373
|
+
const tok = await res.json().catch(() => ({}));
|
|
72374
|
+
if (res.status === 429) {
|
|
72375
|
+
throw new Error(`${new URL(s.tokenEndpoint).host} is asking us to slow down \u2014 your sign-in could not be renewed just now`);
|
|
72376
|
+
}
|
|
72377
|
+
if (!res.ok || !tok.access_token) {
|
|
72378
|
+
await idbDel("session");
|
|
72379
|
+
throw new Error("refresh failed \u2014 sign in again");
|
|
72380
|
+
}
|
|
72381
|
+
accessToken = tok.access_token;
|
|
72382
|
+
expiresAt = Date.now() + Math.max(30, tok.expires_in || 300) * 1e3;
|
|
72383
|
+
if (tok.refresh_token) refreshToken = tok.refresh_token;
|
|
72384
|
+
const now = await idbGet("session");
|
|
72385
|
+
if (now && now.webId !== s.webId) {
|
|
72386
|
+
throw new Error("signed in as somebody else while this session was refreshing");
|
|
72387
|
+
}
|
|
72388
|
+
await idbPut("session", { ...s, accessToken, expiresAt, refreshToken });
|
|
72389
|
+
};
|
|
72390
|
+
const send = async (url, init) => {
|
|
72391
|
+
const jwk = await jwkP;
|
|
72392
|
+
const ath = b64u3(await sha2564(accessToken));
|
|
72393
|
+
const headers = { ...init.headers || {}, authorization: `DPoP ${accessToken}`, dpop: await dpopProof(s.pair, jwk, init.method || "GET", url, ath) };
|
|
72394
|
+
return fetch(url, { ...init, headers });
|
|
72395
|
+
};
|
|
72396
|
+
const authFetch = async (url, init = {}) => {
|
|
72397
|
+
if (Date.now() > expiresAt - 3e4) await refresh();
|
|
72398
|
+
const res = await send(url, init);
|
|
72399
|
+
if (res.status === 401 && refreshToken) {
|
|
72400
|
+
try {
|
|
72401
|
+
await refresh();
|
|
72402
|
+
} catch {
|
|
72403
|
+
return res;
|
|
72404
|
+
}
|
|
72405
|
+
return send(url, init);
|
|
72406
|
+
}
|
|
72407
|
+
return res;
|
|
72408
|
+
};
|
|
72409
|
+
return { webId: s.webId, issuer: s.issuer, fetch: authFetch, refresh, signOut: signOut2 };
|
|
72410
|
+
}
|
|
72411
|
+
return { beginLogin: beginLogin2, completeLogin: completeLogin2, getSession: getSession2, signOut: signOut2 };
|
|
71086
72412
|
}
|
|
71087
72413
|
|
|
72414
|
+
// web/app/oidc-session.mjs
|
|
72415
|
+
var { beginLogin, completeLogin, getSession, signOut } = solidOidcSession({ dbName: "fedipod-oidc", clientName: "FediPod" });
|
|
72416
|
+
|
|
71088
72417
|
// web/app/sw-src.mjs
|
|
71089
72418
|
var agent = null;
|
|
71090
72419
|
var booting = null;
|
|
@@ -71172,7 +72501,7 @@ async function serve(request, url) {
|
|
|
71172
72501
|
} catch {
|
|
71173
72502
|
}
|
|
71174
72503
|
}
|
|
71175
|
-
if (!agent) return json(503, { error: "
|
|
72504
|
+
if (!agent) return json(503, { error: "not signed in on this browser \u2014 sign in at the front page" });
|
|
71176
72505
|
const bodyBytes = request.method === "GET" || request.method === "HEAD" ? null : Buffer.from(new Uint8Array(await request.arrayBuffer()));
|
|
71177
72506
|
const bodyText = bodyBytes ? new TextDecoder().decode(bodyBytes) : "";
|
|
71178
72507
|
const reqHeaders = {};
|