fedipod-server 0.10.0 → 0.11.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/dist/claims.js +1 -1
- package/dist/handler.d.ts +1 -1
- package/dist/handler.js +35 -2
- package/dist/handler.jsonld +1 -1
- package/lib/admin.mjs +3 -3
- package/lib/deliver.mjs +6 -0
- package/lib/export-collections.mjs +1 -1
- package/lib/front-core.mjs +272 -49
- package/lib/gateway-core.mjs +3 -7
- package/lib/httpsig.mjs +33 -2
- package/lib/import.mjs +1 -1
- package/lib/intake.mjs +335 -118
- package/lib/links.mjs +6 -32
- package/lib/mastoapi.mjs +372 -35
- package/lib/migrate.mjs +4 -18
- package/lib/pod/README.md +61 -0
- package/lib/pod/actor.mjs +96 -0
- package/lib/pod/collection.mjs +77 -0
- package/lib/pod/containers.mjs +136 -0
- package/lib/pod/discovery.mjs +46 -0
- package/lib/pod/featured.mjs +18 -0
- package/lib/pod/followers.mjs +24 -0
- package/lib/pod/following.mjs +10 -0
- package/lib/pod/http.mjs +74 -0
- package/lib/pod/inbox.mjs +144 -0
- package/lib/pod/links.mjs +35 -0
- package/lib/pod/media.mjs +27 -0
- package/lib/pod/notes.mjs +80 -0
- package/lib/pod/notifications.mjs +89 -0
- package/lib/pod/outbox.mjs +18 -0
- package/lib/pod/package.json +19 -0
- package/lib/pod/policy.mjs +58 -0
- package/lib/pod/private.mjs +19 -0
- package/lib/pod/root.mjs +63 -0
- package/lib/pod/state.mjs +39 -0
- package/lib/pod/transport.mjs +477 -0
- package/lib/pod/urls.mjs +71 -0
- package/lib/publisher.mjs +112 -183
- package/lib/remote.mjs +30 -395
- package/lib/social.mjs +18 -9
- package/lib/storage.mjs +2 -2
- package/lib/store.mjs +8 -1
- package/lib/tagfeed.mjs +19 -2
- package/lib/wire.mjs +48 -67
- package/package.json +1 -1
- package/run-agent.mjs +14 -39
- package/web/admin/admin.js +141 -74
- package/web/admin/bar.css +9 -13
- package/web/admin/bar.js +2 -2
- package/web/admin/client/client.js +1 -1
- package/web/admin/client/index.html +5 -6
- package/web/admin/index.html +53 -65
- package/web/admin/setup/index.html +2 -1
- package/web/admin/setup/setup.js +1 -1
- package/web/admin/tokens.css +56 -0
- package/web/app/README.md +77 -0
- package/web/app/admin-facade.mjs +549 -0
- package/web/app/agent.mjs +490 -0
- package/web/app/atproto-browser.mjs +86 -0
- package/web/app/boot.mjs +340 -0
- package/web/app/deliver-relay.mjs +72 -0
- package/web/app/dist/boot.js +34067 -0
- package/web/app/dist/boot.js.map +7 -0
- package/web/app/dist/sw.js +53745 -0
- package/web/app/dist/sw.js.map +7 -0
- package/web/app/fediacct-browser.mjs +146 -0
- package/web/app/idb-kv.mjs +35 -0
- package/web/app/index.html +203 -0
- package/web/app/index.html~ +152 -0
- package/web/app/keys-browser.mjs +46 -0
- package/web/app/keystore.mjs +99 -0
- package/web/app/oidc-session.mjs +189 -0
- package/web/app/pod-auth.mjs +248 -0
- package/web/app/pod-remote.mjs +84 -0
- package/web/app/shims/_globals.mjs +2 -0
- package/web/app/shims/fedify-sig.mjs +77 -0
- package/web/app/shims/node-crypto.mjs +113 -0
- package/web/app/shims/node-fs.mjs +17 -0
- package/web/app/shims/node-path.mjs +19 -0
- package/web/app/shims/node-url.mjs +7 -0
- package/web/app/shims/prelude.js +58 -0
- package/web/app/shims/safefetch.mjs +58 -0
- package/web/app/shims/web-push.mjs +14 -0
- package/web/app/signup.mjs +248 -0
- package/web/app/site/_headers +9 -0
- package/web/app/site/_redirects +21 -0
- package/web/app/site/admin/admin.js +1181 -0
- package/web/app/site/admin/bar.css +60 -0
- package/web/app/site/admin/bar.js +61 -0
- package/web/app/site/admin/client/client.js +66 -0
- package/web/app/site/admin/client/index.html +43 -0
- package/web/app/site/admin/index.html +556 -0
- package/web/app/site/admin/setup/index.html +191 -0
- package/web/app/site/admin/setup/setup.js +308 -0
- package/web/app/site/admin/tokens.css +56 -0
- package/web/app/site/admin/window.css +55 -0
- package/web/app/site/admin/window.js +136 -0
- package/web/app/site/app/404.html +40 -0
- package/web/app/site/app/_headers +2 -0
- package/web/app/site/app/apple-touch-icon.png +0 -0
- package/web/app/site/app/assets/ICONS-DIPJeU0C.js +2 -0
- package/web/app/site/app/assets/ICONS-DIPJeU0C.js.map +1 -0
- package/web/app/site/app/assets/ScheduledAtField-D-Abfo4J.js +183 -0
- package/web/app/site/app/assets/ScheduledAtField-D-Abfo4J.js.map +1 -0
- package/web/app/site/app/assets/Temml-CCjoacWt.woff2 +0 -0
- package/web/app/site/app/assets/__vite-browser-external-C7iut881.js +2 -0
- package/web/app/site/app/assets/__vite-browser-external-C7iut881.js.map +1 -0
- package/web/app/site/app/assets/boosts-carousel-BiOaNdMT.jpg +0 -0
- package/web/app/site/app/assets/catch-up-O5q5C75Z.png +0 -0
- package/web/app/site/app/assets/chunk-aKtaBQYM.js +1 -0
- package/web/app/site/app/assets/compose-CFEx7wb4.js +2 -0
- package/web/app/site/app/assets/compose-CFEx7wb4.js.map +1 -0
- package/web/app/site/app/assets/compose-ZJN5xSb4.js +35 -0
- package/web/app/site/app/assets/compose-ZJN5xSb4.js.map +1 -0
- package/web/app/site/app/assets/debug.module-YFQgwnIr.js +52 -0
- package/web/app/site/app/assets/debug.module-YFQgwnIr.js.map +1 -0
- package/web/app/site/app/assets/dom-p1YcJ5Rw.js +2 -0
- package/web/app/site/app/assets/dom-p1YcJ5Rw.js.map +1 -0
- package/web/app/site/app/assets/home-mobile-dark@2x-Dc-J8-SM.png +0 -0
- package/web/app/site/app/assets/home-mobile-light@2x-Bd3fod8m.png +0 -0
- package/web/app/site/app/assets/home-tablet-dark@2x-BgcNI8le.png +0 -0
- package/web/app/site/app/assets/home-tablet-light@2x-BW2RV9T6.png +0 -0
- package/web/app/site/app/assets/icons/add-circle-line-WD0mwORD.js +1 -0
- package/web/app/site/app/assets/icons/alert-line-CpFUMf9s.js +1 -0
- package/web/app/site/app/assets/icons/android-2-line-Bzxnrd3S.js +1 -0
- package/web/app/site/app/assets/icons/announcement-line-5nwaJFJm.js +1 -0
- package/web/app/site/app/assets/icons/arrow-down-circle-line-a7Id9-Iv.js +1 -0
- package/web/app/site/app/assets/icons/arrow-down-line-B8uzXzq6.js +1 -0
- package/web/app/site/app/assets/icons/arrow-left-line-CGrGT9C-.js +1 -0
- package/web/app/site/app/assets/icons/arrow-right-line-CE-HERfX.js +1 -0
- package/web/app/site/app/assets/icons/arrow-to-up-line-CvdQjmcy.js +1 -0
- package/web/app/site/app/assets/icons/arrow-up-circle-line-DEparSNF.js +1 -0
- package/web/app/site/app/assets/icons/arrow-up-line-C7yBeugc.js +1 -0
- package/web/app/site/app/assets/icons/arrows-right-line-BtbXmJGP.js +1 -0
- package/web/app/site/app/assets/icons/at-line-CF66Bznk.js +1 -0
- package/web/app/site/app/assets/icons/attachment-line-BY8It-l9.js +1 -0
- package/web/app/site/app/assets/icons/blockquote-line-BS6pU_hm.js +1 -0
- package/web/app/site/app/assets/icons/board-line-C0UWXyA6.js +1 -0
- package/web/app/site/app/assets/icons/bookmark-line-1CtX5JLF.js +1 -0
- package/web/app/site/app/assets/icons/building-5-line-Dd59MzbO.js +1 -0
- package/web/app/site/app/assets/icons/bus-2-line-6_dvA0GG.js +1 -0
- package/web/app/site/app/assets/icons/calendar-day-line-Crpl_fmu.js +1 -0
- package/web/app/site/app/assets/icons/calendar-month-line-vommts2C.js +1 -0
- package/web/app/site/app/assets/icons/calendar-time-add-line-BU_DTlQk.js +1 -0
- package/web/app/site/app/assets/icons/camera-line-Cv69MEei.js +1 -0
- package/web/app/site/app/assets/icons/celebrate-line-B8DvIoi0.js +1 -0
- package/web/app/site/app/assets/icons/chart-bar-line-FK-QOmtF.js +1 -0
- package/web/app/site/app/assets/icons/chart-line-line-BGcJAFWw.js +1 -0
- package/web/app/site/app/assets/icons/chat-3-line-YFK2-hwz.js +1 -0
- package/web/app/site/app/assets/icons/check-circle-line-DsAxFr5-.js +1 -0
- package/web/app/site/app/assets/icons/clipboard-line-BBp364gJ.js +1 -0
- package/web/app/site/app/assets/icons/close-circle-line-CICdiuGz.js +1 -0
- package/web/app/site/app/assets/icons/close-line-DYrFWcjv.js +1 -0
- package/web/app/site/app/assets/icons/cloud-line-tiQAbah8.js +1 -0
- package/web/app/site/app/assets/icons/code-line-fAl8iiV3.js +1 -0
- package/web/app/site/app/assets/icons/comment-2-line-CR3g7WeB.js +1 -0
- package/web/app/site/app/assets/icons/copy-2-line-CLW69yF5.js +1 -0
- package/web/app/site/app/assets/icons/delete-2-line-B8xmbr4n.js +1 -0
- package/web/app/site/app/assets/icons/document-3-line-DBGNhAg2.js +1 -0
- package/web/app/site/app/assets/icons/document-line-C7mb9sb7.js +1 -0
- package/web/app/site/app/assets/icons/down-line-BGqSSThd.js +1 -0
- package/web/app/site/app/assets/icons/edit-4-line-DcgGaEhx.js +1 -0
- package/web/app/site/app/assets/icons/emoji-2-line-D8YgnM-y.js +1 -0
- package/web/app/site/app/assets/icons/exit-line-Dc07BS-x.js +1 -0
- package/web/app/site/app/assets/icons/external-link-line-BvpSkmjO.js +1 -0
- package/web/app/site/app/assets/icons/eye-2-line-CiWXx-f_.js +1 -0
- package/web/app/site/app/assets/icons/eye-close-line-BgTaSJ4P.js +1 -0
- package/web/app/site/app/assets/icons/filter-2-line-B_mR2usy.js +1 -0
- package/web/app/site/app/assets/icons/filter-line-C_nm-3cU.js +1 -0
- package/web/app/site/app/assets/icons/flag-1-line-C2HB05I0.js +1 -0
- package/web/app/site/app/assets/icons/forbid-circle-line-D3LTT9yd.js +1 -0
- package/web/app/site/app/assets/icons/formula-line-DLrt2d6j.js +1 -0
- package/web/app/site/app/assets/icons/grid-line-CgUmEikf.js +1 -0
- package/web/app/site/app/assets/icons/group-line-C5fnX0wU.js +1 -0
- package/web/app/site/app/assets/icons/hand-finger-2-line-BGgxbuL9.js +1 -0
- package/web/app/site/app/assets/icons/hashtag-line-CyTEEXBb.js +1 -0
- package/web/app/site/app/assets/icons/heart-crack-line-BZddbO2C.js +1 -0
- package/web/app/site/app/assets/icons/heart-line-CJ7K47J4.js +1 -0
- package/web/app/site/app/assets/icons/history-2-line-kWWvFzqv.js +1 -0
- package/web/app/site/app/assets/icons/history-line-bGQiwbTH.js +1 -0
- package/web/app/site/app/assets/icons/home-3-line-fRU1zeLG.js +1 -0
- package/web/app/site/app/assets/icons/information-line-iNk6-sOY.js +1 -0
- package/web/app/site/app/assets/icons/keyboard-line-DsCyuQNz.js +1 -0
- package/web/app/site/app/assets/icons/layout-4-line-CZkOX52Z.js +1 -0
- package/web/app/site/app/assets/icons/layout-5-line-B_znt5wO.js +1 -0
- package/web/app/site/app/assets/icons/left-line-eUw0tya4.js +1 -0
- package/web/app/site/app/assets/icons/lightning-line-DaKoDdix.js +1 -0
- package/web/app/site/app/assets/icons/link-2-line-DCqLmMA7.js +1 -0
- package/web/app/site/app/assets/icons/list-check-line-BAxosBh9.js +1 -0
- package/web/app/site/app/assets/icons/lock-line-CPoaXeUW.js +1 -0
- package/web/app/site/app/assets/icons/mail-line-B0P8aa1L.js +1 -0
- package/web/app/site/app/assets/icons/moon-line-CEqNAToP.js +1 -0
- package/web/app/site/app/assets/icons/more-1-fill-Dw2e5xab.js +1 -0
- package/web/app/site/app/assets/icons/more-3-line-BIiVjYTz.js +1 -0
- package/web/app/site/app/assets/icons/notification-line-BZZq2Gtu.js +1 -0
- package/web/app/site/app/assets/icons/pencil-line-CKh8-A1v.js +1 -0
- package/web/app/site/app/assets/icons/photo-album-line-Bnfg2u40.js +1 -0
- package/web/app/site/app/assets/icons/pin-line-DpTolyhs.js +1 -0
- package/web/app/site/app/assets/icons/play-fill-B0-G_8lX.js +1 -0
- package/web/app/site/app/assets/icons/qrcode-2-line-B6SPCaHn.js +1 -0
- package/web/app/site/app/assets/icons/quill-pen-line-B6LUrnCP.js +1 -0
- package/web/app/site/app/assets/icons/quote-left-fill-hQZ1cj_6.js +1 -0
- package/web/app/site/app/assets/icons/radar-line-CKyroTth.js +1 -0
- package/web/app/site/app/assets/icons/react-line-CoIKhhtK.js +1 -0
- package/web/app/site/app/assets/icons/refresh-2-line-D0mTF97S.js +1 -0
- package/web/app/site/app/assets/icons/right-line-B3KWJaqc.js +1 -0
- package/web/app/site/app/assets/icons/rocket-line-4KZl537D.js +1 -0
- package/web/app/site/app/assets/icons/round-fill-C69NTrZ0.js +1 -0
- package/web/app/site/app/assets/icons/round-line-B9k_1uo-.js +1 -0
- package/web/app/site/app/assets/icons/route-line-B28ReHcE.js +1 -0
- package/web/app/site/app/assets/icons/rows-4-line-CrBniagM.js +1 -0
- package/web/app/site/app/assets/icons/scan-line-DSf74YdP.js +1 -0
- package/web/app/site/app/assets/icons/search-2-line-B6geIU1z.js +1 -0
- package/web/app/site/app/assets/icons/settings-3-line-azJwK47w.js +1 -0
- package/web/app/site/app/assets/icons/settings-6-line-C_5zc5YR.js +1 -0
- package/web/app/site/app/assets/icons/share-2-line-D5MxWjrm.js +1 -0
- package/web/app/site/app/assets/icons/share-forward-line-Cp61fZzZ.js +1 -0
- package/web/app/site/app/assets/icons/sparkles-2-line-DYYyXTaW.js +1 -0
- package/web/app/site/app/assets/icons/sparkles-line-C2hY1s6I.js +1 -0
- package/web/app/site/app/assets/icons/time-line-BF3eet1v.js +1 -0
- package/web/app/site/app/assets/icons/transfer-4-line-oisfvTBw.js +1 -0
- package/web/app/site/app/assets/icons/translate-line-BFWol2Ce.js +1 -0
- package/web/app/site/app/assets/icons/unlock-line-X_x5vwv3.js +1 -0
- package/web/app/site/app/assets/icons/upload-3-line-DgzwUJeW.js +1 -0
- package/web/app/site/app/assets/icons/user-4-line-BVSynZCp.js +1 -0
- package/web/app/site/app/assets/icons/user-add-2-line-BlGS5Q8q.js +1 -0
- package/web/app/site/app/assets/icons/user-add-line-BbXdHG4z.js +1 -0
- package/web/app/site/app/assets/icons/user-edit-line-BFAHOV0X.js +1 -0
- package/web/app/site/app/assets/icons/user-follow-line-BXux0wSd.js +1 -0
- package/web/app/site/app/assets/icons/user-star-line-DdYlpHLU.js +1 -0
- package/web/app/site/app/assets/icons/user-warning-line-Cj43j0_E.js +1 -0
- package/web/app/site/app/assets/icons/user-x-line-DIDgNlPH.js +1 -0
- package/web/app/site/app/assets/icons/volume-line-Bs390KQt.js +1 -0
- package/web/app/site/app/assets/icons/volume-mute-line-DzlzIqFZ.js +1 -0
- package/web/app/site/app/assets/icons/walk-line-yNXfB-md.js +1 -0
- package/web/app/site/app/assets/icons/world-2-line-CenkGIwj.js +1 -0
- package/web/app/site/app/assets/icons/zoom-in-line-CaENUphf.js +1 -0
- package/web/app/site/app/assets/icons/zoom-out-line-BDNk0EWo.js +1 -0
- package/web/app/site/app/assets/instances-BNPptnn-.json +333 -0
- package/web/app/site/app/assets/locales/ar-SA-CfheRbCu.js +1 -0
- package/web/app/site/app/assets/locales/ca-ES-DHRW86Tz.js +1 -0
- package/web/app/site/app/assets/locales/cs-CZ-CKoqEuA1.js +1 -0
- package/web/app/site/app/assets/locales/de-DE-ByRPMRFy.js +1 -0
- package/web/app/site/app/assets/locales/eo-UY-CPC7wLCS.js +1 -0
- package/web/app/site/app/assets/locales/es-ES-dJa0Fd9t.js +1 -0
- package/web/app/site/app/assets/locales/eu-ES-C3djlPR2.js +1 -0
- package/web/app/site/app/assets/locales/fa-IR-DTT490Ck.js +1 -0
- package/web/app/site/app/assets/locales/fi-FI-B23MCPuz.js +1 -0
- package/web/app/site/app/assets/locales/fr-FR-BcaQsO5C.js +1 -0
- package/web/app/site/app/assets/locales/gl-ES-BR-CVKBN.js +1 -0
- package/web/app/site/app/assets/locales/he-IL-CfheRbCu.js +1 -0
- package/web/app/site/app/assets/locales/hu-HU-CfheRbCu.js +1 -0
- package/web/app/site/app/assets/locales/it-IT-C1136UFV.js +1 -0
- package/web/app/site/app/assets/locales/ja-JP-CRZmRAhD.js +1 -0
- package/web/app/site/app/assets/locales/kab-Cz8xH_TT.js +1 -0
- package/web/app/site/app/assets/locales/ko-KR-CG9UiY4o.js +1 -0
- package/web/app/site/app/assets/locales/lt-LT-CJcI6sHZ.js +1 -0
- package/web/app/site/app/assets/locales/nb-NO-CfheRbCu.js +1 -0
- package/web/app/site/app/assets/locales/nl-NL-DxN4Yc1t.js +1 -0
- package/web/app/site/app/assets/locales/oc-FR-CfheRbCu.js +1 -0
- package/web/app/site/app/assets/locales/pl-PL-DkwGXf6P.js +1 -0
- package/web/app/site/app/assets/locales/pseudo-LOCALE-CQ4XxkQ0.js +1 -0
- package/web/app/site/app/assets/locales/pt-BR-nGL0HdFS.js +1 -0
- package/web/app/site/app/assets/locales/pt-PT-Cp17aj2z.js +1 -0
- package/web/app/site/app/assets/locales/ru-RU-0Y8cIoaK.js +1 -0
- package/web/app/site/app/assets/locales/th-TH-CfheRbCu.js +1 -0
- package/web/app/site/app/assets/locales/tok-CfheRbCu.js +1 -0
- package/web/app/site/app/assets/locales/tr-TR-CfheRbCu.js +1 -0
- package/web/app/site/app/assets/locales/uk-UA-C5E3dFBf.js +1 -0
- package/web/app/site/app/assets/locales/zh-CN-BHJi7_et.js +1 -0
- package/web/app/site/app/assets/locales/zh-TW-CfheRbCu.js +1 -0
- package/web/app/site/app/assets/main-BdqNbG-a.js +26 -0
- package/web/app/site/app/assets/main-BdqNbG-a.js.map +1 -0
- package/web/app/site/app/assets/mock-home-DGhR__He.js +2 -0
- package/web/app/site/app/assets/mock-home-DGhR__He.js.map +1 -0
- package/web/app/site/app/assets/multi-column-ROck0NVt.jpg +0 -0
- package/web/app/site/app/assets/multi-hashtag-timeline-DH5INVHi.jpg +0 -0
- package/web/app/site/app/assets/nested-comments-thread-hv59kZLc.jpg +0 -0
- package/web/app/site/app/assets/polyfill-force-H8sUkLGp.js +2 -0
- package/web/app/site/app/assets/polyfill-force-H8sUkLGp.js.map +1 -0
- package/web/app/site/app/assets/pwa-viewport-ogKbF51K.js +20 -0
- package/web/app/site/app/assets/pwa-viewport-ogKbF51K.js.map +1 -0
- package/web/app/site/app/assets/style-B1iLPTPR.css +2 -0
- package/web/app/site/app/assets/temml-DuyIQMei.js +97 -0
- package/web/app/site/app/assets/temml-DuyIQMei.js.map +1 -0
- package/web/app/site/app/assets/tinyld.light.node-DtbBhLYC.js +2 -0
- package/web/app/site/app/assets/tinyld.light.node-DtbBhLYC.js.map +1 -0
- package/web/app/site/app/assets/year-in-posts-B2lywqdx.png +0 -0
- package/web/app/site/app/assets/year-in-posts-DJv_star.js +3 -0
- package/web/app/site/app/assets/year-in-posts-DJv_star.js.map +1 -0
- package/web/app/site/app/compose/index.html +20 -0
- package/web/app/site/app/favicon.ico +0 -0
- package/web/app/site/app/index.html +73 -0
- package/web/app/site/app/logo-192.png +0 -0
- package/web/app/site/app/logo-512.png +0 -0
- package/web/app/site/app/logo-badge-72.png +0 -0
- package/web/app/site/app/logo-maskable-512.png +0 -0
- package/web/app/site/app/logo-monochrome-512.png +0 -0
- package/web/app/site/app/logo-monochrome-maskable-512.png +0 -0
- package/web/app/site/app/manifest.webmanifest +1 -0
- package/web/app/site/app/og-image-2.jpg +0 -0
- package/web/app/site/app/og-image.png +0 -0
- package/web/app/site/app/robots.txt +2 -0
- package/web/app/site/app/version.json +1 -0
- package/web/app/site/boot.js +34067 -0
- package/web/app/site/index.html +203 -0
- package/web/app/site/sw.js +53745 -0
- package/web/app/sw-src.mjs +205 -0
- package/web/front/#new-account.html# +43 -0
- package/web/front/admin.html +1 -81
- package/web/front/admin.js +85 -0
- package/web/front/new-account.html +1 -9
- package/web/front/new-account.js +13 -0
- package/web/front/run.html +1 -92
- package/web/front/run.js +107 -0
- package/lib/podrdf.mjs +0 -132
package/lib/publisher.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
// publisher.mjs — builds/maintains the actor's public face on the remote pod
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// publisher.mjs — builds/maintains the actor's public face on the remote pod:
|
|
2
|
+
// webfinger, actor doc, collections, notes. The /ap/ tree is disposable:
|
|
3
|
+
// publishProfile() rebuilds it.
|
|
4
4
|
|
|
5
5
|
import crypto from 'node:crypto';
|
|
6
6
|
import fs from 'node:fs';
|
|
@@ -10,6 +10,18 @@ import * as wire from './wire.mjs';
|
|
|
10
10
|
import * as polls from './polls.mjs';
|
|
11
11
|
import { USER_AGENT } from './ua.mjs';
|
|
12
12
|
import { HTTP_TIMEOUT_MS } from './safefetch.mjs';
|
|
13
|
+
import * as containers from './pod/containers.mjs';
|
|
14
|
+
import * as discovery from './pod/discovery.mjs';
|
|
15
|
+
import * as podActor from './pod/actor.mjs';
|
|
16
|
+
import * as podInbox from './pod/inbox.mjs';
|
|
17
|
+
import * as podOutbox from './pod/outbox.mjs';
|
|
18
|
+
import * as podFollowers from './pod/followers.mjs';
|
|
19
|
+
import * as podFollowing from './pod/following.mjs';
|
|
20
|
+
import * as podFeatured from './pod/featured.mjs';
|
|
21
|
+
import * as podPrivate from './pod/private.mjs';
|
|
22
|
+
import * as podPolicy from './pod/policy.mjs';
|
|
23
|
+
import * as podNotes from './pod/notes.mjs';
|
|
24
|
+
import * as podMedia from './pod/media.mjs';
|
|
13
25
|
|
|
14
26
|
const ACCEPT_AP = 'application/activity+json, application/ld+json; profile="https://www.w3.org/ns/activitystreams"';
|
|
15
27
|
// The default for publishCollections: the whole public surface, ACLs included.
|
|
@@ -28,12 +40,11 @@ const AGENT_VERSION = JSON.parse(fs.readFileSync(
|
|
|
28
40
|
path.join(path.dirname(fileURLToPath(import.meta.url)), '../package.json'), 'utf8')).version;
|
|
29
41
|
|
|
30
42
|
export class Publisher {
|
|
31
|
-
constructor({ config, remote,
|
|
32
|
-
probeFetch = null, resolveMention = null,
|
|
43
|
+
constructor({ config, remote, store, deliverer, publicKeyPem, assertionKey = null, log = console.log,
|
|
44
|
+
probeFetch = null, resolveMention = null, clientOrigin = null,
|
|
33
45
|
}) {
|
|
34
46
|
this.config = config;
|
|
35
47
|
this.remote = remote;
|
|
36
|
-
this.local = local;
|
|
37
48
|
this.store = store;
|
|
38
49
|
this.deliverer = deliverer;
|
|
39
50
|
this.publicKeyPem = publicKeyPem;
|
|
@@ -53,7 +64,6 @@ export class Publisher {
|
|
|
53
64
|
// socket opened to that pod. Tests inject their own.
|
|
54
65
|
this.probeFetch = probeFetch || ((u, i) => this.remote.probe(u, i));
|
|
55
66
|
this.resolveMention = resolveMention;
|
|
56
|
-
this.privateOnPod = privateOnPod;
|
|
57
67
|
// Per-poll rewrite windows, keyed by question id. See POLL_REWRITE_MS.
|
|
58
68
|
this.pollTimers = new Map();
|
|
59
69
|
this.log = log;
|
|
@@ -118,34 +128,25 @@ export class Publisher {
|
|
|
118
128
|
return { unreachable: [], updated: 0, skipped: true };
|
|
119
129
|
}
|
|
120
130
|
|
|
121
|
-
await this.remote
|
|
122
|
-
wire.jrd({ handle: this.config.handle, host, actor: urls.actor })
|
|
123
|
-
await this.remote.
|
|
131
|
+
await discovery.writeWebfinger(this.remote, urls,
|
|
132
|
+
wire.jrd({ handle: this.config.handle, host, actor: urls.actor }));
|
|
133
|
+
await discovery.writeHostMeta(this.remote, urls, wire.hostMeta(urls.base));
|
|
124
134
|
|
|
125
|
-
const hostMetaUrl = urls.base + '.well-known/host-meta';
|
|
126
|
-
await this.remote.put(hostMetaUrl, wire.hostMeta(urls.base), 'application/xrd+xml');
|
|
127
|
-
await this.remote.setAcl(hostMetaUrl, ['Read']);
|
|
128
|
-
|
|
129
|
-
// NodeInfo: pointer at the protocol-required root, document in our tree.
|
|
130
135
|
const nodeinfoDocUrl = urls.home + 'ap/nodeinfo-2.0';
|
|
131
136
|
const localPosts = this.store.getStatuses().filter(s => s.kind === 'post').length;
|
|
132
|
-
await this.remote
|
|
133
|
-
wire.nodeinfoPointer(nodeinfoDocUrl),
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
wire.nodeinfoDoc({ version: AGENT_VERSION, localPosts }), 'application/json');
|
|
137
|
-
await this.remote.setAcl(nodeinfoDocUrl, ['Read']);
|
|
137
|
+
await discovery.writeNodeinfo(this.remote, urls, {
|
|
138
|
+
pointer: wire.nodeinfoPointer(nodeinfoDocUrl),
|
|
139
|
+
doc: wire.nodeinfoDoc({ version: AGENT_VERSION, localPosts }),
|
|
140
|
+
});
|
|
138
141
|
|
|
139
142
|
const actor = actorDoc; // built above, for the digest
|
|
140
|
-
await this.remote
|
|
141
|
-
await this.remote.setAcl(urls.actor, ['Read']);
|
|
143
|
+
await podActor.write(this.remote, urls, actor);
|
|
142
144
|
|
|
143
145
|
// FEP-1b12: the moderator roster, public — it is what a recipient
|
|
144
146
|
// validates a group's announced moderation against.
|
|
145
147
|
if (moderators) {
|
|
146
|
-
await this.remote
|
|
148
|
+
await podFeatured.writeModerators(this.remote, urls,
|
|
147
149
|
wire.orderedCollection(urls.moderators, this.config.moderators));
|
|
148
|
-
await this.remote.setAcl(urls.moderators, ['Read']);
|
|
149
150
|
}
|
|
150
151
|
// The gateway policy doc: the PUBLIC data a keyless gateway reads to decide
|
|
151
152
|
// what concerns this identity. Written only while a gateway is advertised.
|
|
@@ -153,20 +154,19 @@ export class Publisher {
|
|
|
153
154
|
|
|
154
155
|
// The human half: a page a browser can open and follow from. The actor
|
|
155
156
|
// document is for servers; this is the address you hand to a person.
|
|
156
|
-
await this.remote
|
|
157
|
+
await podActor.writeProfilePage(this.remote, urls, wire.profilePageHtml({
|
|
157
158
|
name: this.config.name || this.config.handle,
|
|
158
159
|
address: wire.webfingerHost(urls.base) ? `@${this.config.handle}@${host}` : urls.actor,
|
|
159
160
|
summary: this.config.summary ? wire.contentHtml(this.config.summary) : null,
|
|
160
161
|
icon: this.config.icon || null,
|
|
161
162
|
kind: this.config.kind,
|
|
162
|
-
})
|
|
163
|
-
await this.remote.setAcl(urls.profileHtml, ['Read']);
|
|
163
|
+
}));
|
|
164
164
|
|
|
165
165
|
// WebID → actor: the profile card lists the actor as a foaf:account.
|
|
166
166
|
// Best-effort — a profile that cannot be read or edited does not stop the
|
|
167
167
|
// publish, it is logged and the rest of the surface still goes up.
|
|
168
168
|
try {
|
|
169
|
-
const wrote = await this.remote
|
|
169
|
+
const wrote = await podActor.linkInWebIdProfile(this.remote, {
|
|
170
170
|
actorUrl: urls.actor,
|
|
171
171
|
accountName: `@${this.config.handle}@${host}`,
|
|
172
172
|
kind: this.config.kind,
|
|
@@ -179,21 +179,15 @@ export class Publisher {
|
|
|
179
179
|
// inbox: public may only Append; owner (the agent) reads + drains. A
|
|
180
180
|
// quiesced actor keeps its name resolving but takes no more mail, so a
|
|
181
181
|
// republish must not re-open the door.
|
|
182
|
-
await this.remote
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
this.log('inbox left closed — this actor is quiesced');
|
|
186
|
-
} else {
|
|
187
|
-
await this.remote.setAcl(urls.inbox, ['Append']);
|
|
188
|
-
}
|
|
182
|
+
await podInbox.writeKeep(this.remote, urls);
|
|
183
|
+
await podInbox.setPosture(this.remote, urls, this.config.quiescedAt ? 'closed' : 'open');
|
|
184
|
+
if (this.config.quiescedAt) this.log('inbox left closed — this actor is quiesced');
|
|
189
185
|
|
|
190
186
|
// notes live under a public-Read container (acl:default covers new notes).
|
|
191
|
-
await this.remote
|
|
192
|
-
await this.remote.setAcl(urls.notes, ['Read']);
|
|
187
|
+
await podNotes.provisionContainer(this.remote, urls);
|
|
193
188
|
|
|
194
189
|
await this.publishCollections({ ...ALL_COLLECTIONS, force });
|
|
195
190
|
const updated = await this.announceProfileChange(actor, { force });
|
|
196
|
-
await this.local.writeSettings({ handle: this.config.handle, actorUrl: urls.actor });
|
|
197
191
|
await this.ensurePrivateAcls();
|
|
198
192
|
const unreachable = await this.verifyPublicSurface();
|
|
199
193
|
// Last, and merged: announceProfileChange writes this document too.
|
|
@@ -280,23 +274,14 @@ export class Publisher {
|
|
|
280
274
|
// this runs on every connect: probe UNauthenticated, rewrite whatever
|
|
281
275
|
// answers, and say so loudly if the rewrite does not take.
|
|
282
276
|
async ensurePrivateAcls() {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
if (
|
|
290
|
-
this.log(
|
|
291
|
-
try {
|
|
292
|
-
await this.remote.setAcl(url, []);
|
|
293
|
-
} catch (e) {
|
|
294
|
-
this.log(`SECURITY: ${url} is public and its ACL could not be rewritten: ${e.message}`);
|
|
295
|
-
continue;
|
|
296
|
-
}
|
|
297
|
-
if (await this.publiclyReadable(url)) {
|
|
298
|
-
this.log(`SECURITY: ${url} is STILL readable without credentials — check the pod's ACLs`);
|
|
299
|
-
}
|
|
277
|
+
const findings = await containers.repairPrivateAcls(this.remote,
|
|
278
|
+
[this.urls.home, this.urls.state],
|
|
279
|
+
{ isPublic: (u) => this.publiclyReadable(u) });
|
|
280
|
+
// What a finding MEANS is ours to say; the library just reports.
|
|
281
|
+
for (const f of findings) {
|
|
282
|
+
this.log(`${f.url} was readable without credentials — rewriting its ACL`);
|
|
283
|
+
if (f.error) { this.log(`SECURITY: ${f.url} is public and its ACL could not be rewritten: ${f.error}`); continue; }
|
|
284
|
+
if (f.stillPublic) this.log(`SECURITY: ${f.url} is STILL readable without credentials — check the pod's ACLs`);
|
|
300
285
|
}
|
|
301
286
|
}
|
|
302
287
|
|
|
@@ -304,14 +289,9 @@ export class Publisher {
|
|
|
304
289
|
// accept: */* matters — asking for turtle makes the server answer 501 on the
|
|
305
290
|
// JSON documents (webfinger, actor), which reads as "unreachable" when the
|
|
306
291
|
// world can in fact see them perfectly well.
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
headers: { accept: '*/*', 'user-agent': USER_AGENT },
|
|
311
|
-
signal: AbortSignal.timeout(HTTP_TIMEOUT_MS),
|
|
312
|
-
});
|
|
313
|
-
return res.status < 400;
|
|
314
|
-
} catch { return false; } // unreachable is not a finding here
|
|
292
|
+
publiclyReadable(url) {
|
|
293
|
+
return containers.probePublicReadability(this.probeFetch, url,
|
|
294
|
+
{ headers: { 'user-agent': USER_AGENT }, timeoutMs: HTTP_TIMEOUT_MS });
|
|
315
295
|
}
|
|
316
296
|
|
|
317
297
|
// Retire this identity for good: tell everyone who follows us to drop the
|
|
@@ -326,8 +306,7 @@ export class Publisher {
|
|
|
326
306
|
const inboxes = [...new Set(contacts.followers.map(f => f.sharedInbox || f.inbox).filter(Boolean))];
|
|
327
307
|
const deletedAt = new Date().toISOString();
|
|
328
308
|
await this.deliverer.deliverToAll(inboxes, wire.deleteActorActivity(urls, Date.parse(deletedAt)));
|
|
329
|
-
await this.remote
|
|
330
|
-
await this.remote.setAcl(urls.actor, ['Read']); // a Tombstone must stay fetchable
|
|
309
|
+
await podActor.writeTombstone(this.remote, urls, wire.tombstoneDoc(urls, deletedAt, this.config.kind));
|
|
331
310
|
this.store.setConfig({ ...this.store.getConfig(), retiredAt: deletedAt });
|
|
332
311
|
await this.store.flush();
|
|
333
312
|
this.log(`retired: Delete sent to ${inboxes.length} inbox(es), actor replaced with a Tombstone`);
|
|
@@ -338,7 +317,7 @@ export class Publisher {
|
|
|
338
317
|
// 401 rather than a 201 into storage nobody will ever drain. WebFinger,
|
|
339
318
|
// host-meta and the actor stay published, so the handle still resolves.
|
|
340
319
|
async closeInbox() {
|
|
341
|
-
await this.remote
|
|
320
|
+
await podInbox.setPosture(this.remote, this.urls, 'closed');
|
|
342
321
|
const at = new Date().toISOString();
|
|
343
322
|
this.store.setConfig({ ...this.store.getConfig(), quiescedAt: at });
|
|
344
323
|
await this.store.flush();
|
|
@@ -348,7 +327,7 @@ export class Publisher {
|
|
|
348
327
|
|
|
349
328
|
// Undo closeInbox: mail flows again and the actor is no longer quiesced.
|
|
350
329
|
async openInbox() {
|
|
351
|
-
await this.remote
|
|
330
|
+
await podInbox.setPosture(this.remote, this.urls, 'open');
|
|
352
331
|
const { quiescedAt, ...rest } = this.store.getConfig() || {};
|
|
353
332
|
this.store.setConfig(rest);
|
|
354
333
|
this.config.quiescedAt = undefined;
|
|
@@ -360,7 +339,7 @@ export class Publisher {
|
|
|
360
339
|
// the pod except through the gateway's verify-at-the-door. Reversible with
|
|
361
340
|
// openInbox (public-Append) — the one-call rollback.
|
|
362
341
|
async lockInboxToGateway(gatewayWebId) {
|
|
363
|
-
await this.remote
|
|
342
|
+
await podInbox.setPosture(this.remote, this.urls, { gatewayWebId });
|
|
364
343
|
this.log(`inbox locked to gateway ${gatewayWebId} — public delivery is refused`);
|
|
365
344
|
}
|
|
366
345
|
|
|
@@ -374,7 +353,7 @@ export class Publisher {
|
|
|
374
353
|
await this.deliverer.deliverToAll(inboxes, wire.moveActivity(urls, target, Date.parse(at)));
|
|
375
354
|
this.config.movedTo = target; // so the republish below carries it
|
|
376
355
|
this.store.setConfig({ ...this.store.getConfig(), movedTo: target, movedAt: at });
|
|
377
|
-
await this.remote
|
|
356
|
+
await podActor.writeMoved(this.remote, urls, wire.actorDoc({
|
|
378
357
|
urls, handle: wire.publicHandle(this.config), name: this.config.name,
|
|
379
358
|
publicKeyPem: this.publicKeyPem, movedTo: target, kind: this.config.kind,
|
|
380
359
|
approveJoins: wire.followsNeedApproval(this.config),
|
|
@@ -384,7 +363,6 @@ export class Publisher {
|
|
|
384
363
|
webId: this.remote.webId || null,
|
|
385
364
|
aliases: this.config.aliases || [],
|
|
386
365
|
}));
|
|
387
|
-
await this.remote.setAcl(urls.actor, ['Read']);
|
|
388
366
|
await this.store.flush();
|
|
389
367
|
this.log(`moved to ${target}: Move sent to ${inboxes.length} inbox(es), actor now advertises movedTo`);
|
|
390
368
|
return { inboxes: inboxes.length, target, movedAt: at };
|
|
@@ -508,8 +486,7 @@ export class Publisher {
|
|
|
508
486
|
// `-replies` — plus a `.keep`. Which is which is settled by reading the
|
|
509
487
|
// document below, not by its name: a slug is a date and eight hex
|
|
510
488
|
// characters and says nothing about what it holds.
|
|
511
|
-
for (const child of await this.remote
|
|
512
|
-
if (/(-create|-replies|\/\.keep)$/.test(child.url)) continue;
|
|
489
|
+
for (const child of await podNotes.list(this.remote, urls).catch(() => [])) {
|
|
513
490
|
ids.add(child.url);
|
|
514
491
|
}
|
|
515
492
|
indexed = true;
|
|
@@ -528,7 +505,7 @@ export class Publisher {
|
|
|
528
505
|
if (budget <= 0) { this.log(`rebuild: stopping at ${REBUILD_MAX_PER_RUN} this run — run it again for the rest`); break; }
|
|
529
506
|
if (have.has(id) || removed.has(id)) continue;
|
|
530
507
|
budget--;
|
|
531
|
-
const note = await this.remote
|
|
508
|
+
const note = await podNotes.read(this.remote, id).catch(() => null);
|
|
532
509
|
if (note?.type !== 'Note' || note.id !== id || note.attributedTo !== urls.actor) continue;
|
|
533
510
|
const attachments = wire.attachmentsOf(note);
|
|
534
511
|
const mentions = (Array.isArray(note.tag) ? note.tag : [])
|
|
@@ -560,31 +537,18 @@ export class Publisher {
|
|
|
560
537
|
}
|
|
561
538
|
if (!recovered.length && !reblogs) return { indexed: ids.size, recovered: 0, reblogs: 0, landed: true };
|
|
562
539
|
|
|
563
|
-
// Written whole and sorted
|
|
564
|
-
//
|
|
565
|
-
//
|
|
540
|
+
// Written whole and sorted: addStatus unshifts and fires the streaming
|
|
541
|
+
// event, so a loop of it would arrive backwards and push every recovered
|
|
542
|
+
// post at connected clients as new.
|
|
566
543
|
merged.sort((a, b) => String(b.published || '').localeCompare(String(a.published || '')));
|
|
567
544
|
const kept = merged.slice(0, 1000);
|
|
568
545
|
this.store.write('statuses.json', kept);
|
|
569
546
|
const landed = await this.store.commit();
|
|
570
547
|
|
|
571
|
-
// The RDF mirror is the other thing a lost machine took with it, and it is
|
|
572
|
-
// what backfillStatuses reads on a fresh state. Best effort: a post in
|
|
573
|
-
// statuses.json is already the recovery that matters.
|
|
574
|
-
let rdf = 0;
|
|
575
|
-
for (const s of recovered) {
|
|
576
|
-
try {
|
|
577
|
-
await this.local.writeNote('posts', s.slug, {
|
|
578
|
-
noteId: s.noteId, actor: s.actor, published: s.published,
|
|
579
|
-
content: s.content, inReplyTo: s.inReplyTo, attachments: s.attachments,
|
|
580
|
-
});
|
|
581
|
-
rdf++;
|
|
582
|
-
} catch (e) { this.log(`rebuild: RDF for ${s.slug} not written (${e.message})`); }
|
|
583
|
-
}
|
|
584
548
|
this.log(`rebuilt ${recovered.length} post(s) and ${reblogs} boost(s) from the pod`
|
|
585
549
|
+ `${landed ? '' : ' — THE STATE WRITE DID NOT LAND'}`);
|
|
586
550
|
return {
|
|
587
|
-
indexed: ids.size, recovered: recovered.length, reblogs,
|
|
551
|
+
indexed: ids.size, recovered: recovered.length, reblogs, landed,
|
|
588
552
|
dropped: Math.max(0, merged.length - kept.length),
|
|
589
553
|
};
|
|
590
554
|
}
|
|
@@ -632,8 +596,8 @@ export class Publisher {
|
|
|
632
596
|
const digest = crypto.createHash('sha256').update(JSON.stringify(doc)).digest('hex').slice(0, 16);
|
|
633
597
|
after[n] = digest;
|
|
634
598
|
if (before[n] === digest) continue; // sealed and unchanged
|
|
635
|
-
await this.remote
|
|
636
|
-
|
|
599
|
+
await podOutbox.writePage(this.remote, wire.outboxPageId(urls.outbox, n), doc,
|
|
600
|
+
{ publicRead: !before[n] || acls });
|
|
637
601
|
wrote++;
|
|
638
602
|
}
|
|
639
603
|
// Pages above the new count are orphans: the outbox shrank past them, and
|
|
@@ -643,13 +607,12 @@ export class Publisher {
|
|
|
643
607
|
const stale = Object.keys(seen.outboxPages || {}).map(Number)
|
|
644
608
|
.filter(n => Number.isFinite(n) && n > pages.length);
|
|
645
609
|
for (const n of stale) {
|
|
646
|
-
await this.remote
|
|
610
|
+
await podOutbox.dropPage(this.remote, wire.outboxPageId(urls.outbox, n));
|
|
647
611
|
}
|
|
648
612
|
// The head carries totalItems, so it moves whenever the outbox does. Four
|
|
649
613
|
// lines, and constant however much you have posted.
|
|
650
|
-
await this.remote
|
|
651
|
-
wire.outboxHead(urls.outbox, outbox.length, pages.length));
|
|
652
|
-
if (acls) await this.remote.setAcl(urls.outbox, ['Read']);
|
|
614
|
+
await podOutbox.writeHead(this.remote, urls,
|
|
615
|
+
wire.outboxHead(urls.outbox, outbox.length, pages.length), { publicRead: acls });
|
|
653
616
|
this.store.write('published.json',
|
|
654
617
|
{ ...this.store.read('published.json', {}), outboxPages: after, outboxIndex: index });
|
|
655
618
|
return wrote;
|
|
@@ -659,22 +622,7 @@ export class Publisher {
|
|
|
659
622
|
// the flat collection this used to write, so an actor published before paging
|
|
660
623
|
// is still readable — which matters because rebuild reads this to recover
|
|
661
624
|
// posts a lost machine no longer has.
|
|
662
|
-
|
|
663
|
-
const head = await this.remote.getJson(this.urls.outbox).catch(() => null);
|
|
664
|
-
if (!head) return null;
|
|
665
|
-
if (Array.isArray(head.orderedItems) && head.orderedItems.length) return head.orderedItems;
|
|
666
|
-
const items = [];
|
|
667
|
-
let next = head.first;
|
|
668
|
-
const seen = new Set();
|
|
669
|
-
while (next && !seen.has(next) && items.length < 10_000) {
|
|
670
|
-
seen.add(next);
|
|
671
|
-
const page = await this.remote.getJson(next).catch(() => null);
|
|
672
|
-
if (!page) break;
|
|
673
|
-
items.push(...(page.orderedItems || []));
|
|
674
|
-
next = page.next;
|
|
675
|
-
}
|
|
676
|
-
return items;
|
|
677
|
-
}
|
|
625
|
+
readPublishedOutbox() { return podOutbox.readPublished(this.remote, this.urls); }
|
|
678
626
|
|
|
679
627
|
// The followers collection, paged like the outbox: a head that carries only
|
|
680
628
|
// the count and the page bounds, and page documents holding the actor IRIs —
|
|
@@ -695,19 +643,18 @@ export class Publisher {
|
|
|
695
643
|
const digest = crypto.createHash('sha256').update(JSON.stringify(doc)).digest('hex').slice(0, 16);
|
|
696
644
|
after[n] = digest;
|
|
697
645
|
if (before[n] === digest) continue; // unchanged page
|
|
698
|
-
await this.remote
|
|
699
|
-
|
|
646
|
+
await podFollowers.writePage(this.remote, wire.followersPageId(urls.followers, n), doc,
|
|
647
|
+
{ publicRead: !before[n] || acls });
|
|
700
648
|
}
|
|
701
649
|
// Pages the collection shrank past would keep serving names that no longer
|
|
702
650
|
// follow; the head stops pointing at them but the URL is guessable.
|
|
703
651
|
const stale = Object.keys(seen.followersPages || {}).map(Number)
|
|
704
652
|
.filter(n => Number.isFinite(n) && n > pages.length);
|
|
705
653
|
for (const n of stale) {
|
|
706
|
-
await this.remote
|
|
654
|
+
await podFollowers.dropPage(this.remote, wire.followersPageId(urls.followers, n));
|
|
707
655
|
}
|
|
708
|
-
await this.remote
|
|
709
|
-
wire.followersHead(urls.followers, actors.length, pages.length));
|
|
710
|
-
if (acls) await this.remote.setAcl(urls.followers, ['Read']);
|
|
656
|
+
await podFollowers.writeHead(this.remote, urls,
|
|
657
|
+
wire.followersHead(urls.followers, actors.length, pages.length), { publicRead: acls });
|
|
711
658
|
this.store.write('published.json',
|
|
712
659
|
{ ...this.store.read('published.json', {}), followersPages: after, followersIndex: index });
|
|
713
660
|
}
|
|
@@ -715,23 +662,7 @@ export class Publisher {
|
|
|
715
662
|
// Every actor in the published followers collection, walking pages. Also reads
|
|
716
663
|
// the flat collection this used to write, so an actor published before paging
|
|
717
664
|
// still reconciles.
|
|
718
|
-
|
|
719
|
-
const head = await this.remote.getJson(this.urls.followers).catch(() => null);
|
|
720
|
-
if (!head) return null;
|
|
721
|
-
if (Array.isArray(head.orderedItems) && head.orderedItems.length) return head.orderedItems;
|
|
722
|
-
if (Array.isArray(head.items) && head.items.length) return head.items;
|
|
723
|
-
const items = [];
|
|
724
|
-
let next = head.first;
|
|
725
|
-
const seen = new Set();
|
|
726
|
-
while (next && !seen.has(next) && items.length < 100_000) {
|
|
727
|
-
seen.add(next);
|
|
728
|
-
const page = await this.remote.getJson(next).catch(() => null);
|
|
729
|
-
if (!page) break;
|
|
730
|
-
items.push(...(page.orderedItems || []));
|
|
731
|
-
next = page.next;
|
|
732
|
-
}
|
|
733
|
-
return items;
|
|
734
|
-
}
|
|
665
|
+
readPublishedFollowers() { return podFollowers.readPublished(this.remote, this.urls); }
|
|
735
666
|
|
|
736
667
|
async publishCollections(which = ALL_COLLECTIONS) {
|
|
737
668
|
const { urls } = this;
|
|
@@ -748,9 +679,9 @@ export class Publisher {
|
|
|
748
679
|
await this.publishFollowers(actors, { acls: which.acls, force: which.force });
|
|
749
680
|
}
|
|
750
681
|
if (which.following) {
|
|
751
|
-
await this.remote
|
|
752
|
-
wire.orderedCollection(urls.following, contacts.following.filter(f => f.accepted).map(f => f.actor))
|
|
753
|
-
|
|
682
|
+
await podFollowing.write(this.remote, urls,
|
|
683
|
+
wire.orderedCollection(urls.following, contacts.following.filter(f => f.accepted).map(f => f.actor)),
|
|
684
|
+
{ publicRead: which.acls });
|
|
754
685
|
}
|
|
755
686
|
if (which.pending) await this.publishPending();
|
|
756
687
|
if (which.blocked) await this.publishBlocked();
|
|
@@ -766,7 +697,6 @@ export class Publisher {
|
|
|
766
697
|
if (which.force || !Array.isArray(known)) await this.reconcileOutbox(outbox);
|
|
767
698
|
await this.publishOutbox(outbox, { acls: which.acls, force: which.force });
|
|
768
699
|
}
|
|
769
|
-
if (which.followers || which.following) await this.local.writeContacts(contacts);
|
|
770
700
|
}
|
|
771
701
|
|
|
772
702
|
// FEP-4ccd: the follows in limbo, as owner-only collections of the Follow
|
|
@@ -778,13 +708,13 @@ export class Publisher {
|
|
|
778
708
|
if (await this.privateReady() !== true) return;
|
|
779
709
|
const { urls } = this;
|
|
780
710
|
const contacts = this.store.getContacts();
|
|
781
|
-
await this.remote
|
|
782
|
-
urls.pendingFollowers,
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
711
|
+
await podPrivate.writePending(this.remote, urls, {
|
|
712
|
+
followers: wire.orderedCollection(urls.pendingFollowers,
|
|
713
|
+
this.store.getRequests().filter(r => r.activity && !r.bsky).map(r => r.activity)),
|
|
714
|
+
following: wire.orderedCollection(urls.pendingFollowing,
|
|
715
|
+
contacts.following.filter(f => !f.accepted && f.followActivity)
|
|
716
|
+
.map(f => f.followActivity).reverse()),
|
|
717
|
+
});
|
|
788
718
|
}
|
|
789
719
|
|
|
790
720
|
// The gateway policy: a small PUBLIC document a keyless inbox gateway reads
|
|
@@ -797,7 +727,7 @@ export class Publisher {
|
|
|
797
727
|
const { urls } = this;
|
|
798
728
|
const contacts = this.store.getContacts();
|
|
799
729
|
const bl = this.store.getBlocklist();
|
|
800
|
-
await this.remote
|
|
730
|
+
await podPolicy.write(this.remote, urls, {
|
|
801
731
|
v: 1,
|
|
802
732
|
actorUrl: urls.actor,
|
|
803
733
|
followersUrl: urls.followers,
|
|
@@ -807,8 +737,7 @@ export class Publisher {
|
|
|
807
737
|
kind: this.config.kind || 'person',
|
|
808
738
|
following: contacts.following.filter(f => f.accepted && !f.bsky).map(f => f.actor),
|
|
809
739
|
blocklist: { domains: bl.domains || [], actors: bl.actors || [] },
|
|
810
|
-
}
|
|
811
|
-
await this.remote.setAcl(urls.home + 'ap/gateway-policy.json', ['Read']);
|
|
740
|
+
});
|
|
812
741
|
}
|
|
813
742
|
|
|
814
743
|
// FEP-c648: the blocked actors, as an owner-only collection. Actors only —
|
|
@@ -817,7 +746,7 @@ export class Publisher {
|
|
|
817
746
|
if (await this.privateReady() !== true) return;
|
|
818
747
|
const { urls } = this;
|
|
819
748
|
const actors = [...(this.store.getBlocklist().actors || [])].reverse();
|
|
820
|
-
await this.remote
|
|
749
|
+
await podPrivate.writeBlocked(this.remote, urls, wire.orderedCollection(urls.blocked, actors));
|
|
821
750
|
}
|
|
822
751
|
|
|
823
752
|
// The outbox is the public record of everything this actor has said, boosts
|
|
@@ -852,19 +781,32 @@ export class Publisher {
|
|
|
852
781
|
|
|
853
782
|
// Media container on the remote pod — public-Read like notes, created lazily
|
|
854
783
|
// at first upload (idempotent; the flag only saves round-trips).
|
|
784
|
+
// Ask before writing. `_mediaReady` is per PROCESS, and a service worker is
|
|
785
|
+
// restarted whenever the browser feels like it — so on the browser build this
|
|
786
|
+
// re-created a container that has existed since sign-up, and rewrote its ACL,
|
|
787
|
+
// on every restart. A HEAD is one request and usually the only one.
|
|
855
788
|
async ensureMediaContainer() {
|
|
856
789
|
if (this._mediaReady) return;
|
|
857
|
-
await this.remote
|
|
858
|
-
await this.remote
|
|
790
|
+
if (await containers.exists(this.remote, this.urls.media)) { this._mediaReady = true; return; }
|
|
791
|
+
await containers.provisionPublic(this.remote, this.urls.media);
|
|
859
792
|
this._mediaReady = true;
|
|
860
793
|
}
|
|
861
794
|
|
|
795
|
+
// Whether the canary this class writes is already there. Only a definite
|
|
796
|
+
// "yes" counts: anything else falls through to the write, which is
|
|
797
|
+
// idempotent anyway, so a failed probe costs a request and never correctness.
|
|
798
|
+
_containerExists(base) { return containers.exists(this.remote, base); }
|
|
799
|
+
|
|
862
800
|
// The owner-only container that followers-only and direct posts live in.
|
|
863
801
|
// Its ACL is set once; every note under it inherits.
|
|
864
802
|
async ensurePrivateContainer() {
|
|
865
803
|
if (this._privateContainer) return;
|
|
866
|
-
|
|
867
|
-
|
|
804
|
+
// Same reasoning as ensureMediaContainer: probe before provisioning, so a
|
|
805
|
+
// worker restart is not a re-provision. The ACL is NOT skipped on the
|
|
806
|
+
// strength of the container existing alone — ensurePrivateAcls re-checks
|
|
807
|
+
// that separately on every connect, which is the control that matters here.
|
|
808
|
+
if (await containers.exists(this.remote, this.urls.privateNotes)) { this._privateContainer = true; return; }
|
|
809
|
+
await containers.provisionOwnerOnly(this.remote, this.urls.privateNotes);
|
|
868
810
|
this._privateContainer = true;
|
|
869
811
|
}
|
|
870
812
|
|
|
@@ -881,9 +823,8 @@ export class Publisher {
|
|
|
881
823
|
// fronted identity must map the advertised private container back to the
|
|
882
824
|
// pod itself — the ACL we are testing is the pod's.
|
|
883
825
|
const pn = this.urls.toPod ? this.urls.toPod(this.urls.privateNotes) : this.urls.privateNotes;
|
|
884
|
-
const
|
|
885
|
-
this._privateVerdict =
|
|
886
|
-
: `this pod serves private documents to strangers (HTTP ${r.status}) — private posts stay off it`;
|
|
826
|
+
const verdict = await containers.probePrivateEnforcement(this.probeFetch || fetch, pn + '.keep');
|
|
827
|
+
this._privateVerdict = verdict === true ? true : `${verdict} — private posts stay off it`;
|
|
887
828
|
return this._privateVerdict;
|
|
888
829
|
} catch (e) {
|
|
889
830
|
return `could not verify that the pod protects private posts (${e.message})`;
|
|
@@ -927,15 +868,13 @@ export class Publisher {
|
|
|
927
868
|
const note = wire.noteDoc({ urls, slug, content, published, inReplyTo, attachments, mentions,
|
|
928
869
|
visibility, summary: spoilerText, container: priv ? urls.privateNotes : urls.notes });
|
|
929
870
|
|
|
930
|
-
await this.remote
|
|
871
|
+
await podNotes.write(this.remote, note.id, note);
|
|
931
872
|
// Empty, but present: a dangling `replies` that 404s is worse than none.
|
|
932
|
-
await this.remote
|
|
873
|
+
await podNotes.writeEmptyReplies(this.remote, wire.repliesId(note.id),
|
|
874
|
+
wire.collection(wire.repliesId(note.id), []));
|
|
933
875
|
// The outbox is the PUBLIC index; a private or direct post is not in it.
|
|
934
876
|
if (!priv) await this.recordOutbox(note.id);
|
|
935
877
|
|
|
936
|
-
await this.local.writeNote('posts', slug, {
|
|
937
|
-
noteId: note.id, actor: urls.actor, published, content: note.content, inReplyTo, attachments,
|
|
938
|
-
});
|
|
939
878
|
this.store.addStatus({
|
|
940
879
|
noteId: note.id, actor: urls.actor, content: note.content, published, inReplyTo,
|
|
941
880
|
kind: 'post', slug, text: content, visibility,
|
|
@@ -948,7 +887,7 @@ export class Publisher {
|
|
|
948
887
|
// Published as its own document: a group that carries this post wraps the
|
|
949
888
|
// whole activity, and the receiving server resolves it by fetching this id.
|
|
950
889
|
// It inherits the notes container's public-Read acl:default.
|
|
951
|
-
await this.remote
|
|
890
|
+
await podNotes.writeCreate(this.remote, create.id, create);
|
|
952
891
|
const contacts = this.store.getContacts();
|
|
953
892
|
// A direct post goes to the people it names and to nobody else.
|
|
954
893
|
const inboxes = [...new Set([
|
|
@@ -1036,14 +975,11 @@ export class Publisher {
|
|
|
1036
975
|
options: poll.options, multiple: poll.multiple, endTime: poll.expiresAt, votersCount: 0,
|
|
1037
976
|
});
|
|
1038
977
|
|
|
1039
|
-
await this.remote
|
|
978
|
+
await podNotes.write(this.remote, question.id, question);
|
|
1040
979
|
// Empty, but present: a dangling `replies` that 404s is worse than none.
|
|
1041
|
-
await this.remote
|
|
980
|
+
await podNotes.writeEmptyReplies(this.remote, wire.repliesId(question.id),
|
|
981
|
+
wire.collection(wire.repliesId(question.id), []));
|
|
1042
982
|
if (!priv) await this.recordOutbox(question.id);
|
|
1043
|
-
await this.local.writeNote('posts', slug, {
|
|
1044
|
-
noteId: question.id, actor: urls.actor, published, content: question.content,
|
|
1045
|
-
inReplyTo, attachments: [],
|
|
1046
|
-
});
|
|
1047
983
|
this.store.addStatus({
|
|
1048
984
|
noteId: question.id, actor: urls.actor, content: question.content, published,
|
|
1049
985
|
kind: 'post', slug, text: content, visibility, poll, inReplyTo,
|
|
@@ -1052,7 +988,7 @@ export class Publisher {
|
|
|
1052
988
|
});
|
|
1053
989
|
|
|
1054
990
|
const create = this._pollActivity('Create', question, wire.createActivityId(question.id));
|
|
1055
|
-
await this.remote
|
|
991
|
+
await podNotes.writeCreate(this.remote, create.id, create);
|
|
1056
992
|
const contacts = this.store.getContacts();
|
|
1057
993
|
const inboxes = [...new Set([
|
|
1058
994
|
...(visibility === 'direct' ? [] : contacts.followers.map(f => f.sharedInbox || f.inbox)),
|
|
@@ -1136,10 +1072,10 @@ export class Publisher {
|
|
|
1136
1072
|
options: poll.options, multiple: !!poll.multiple, endTime: poll.expiresAt,
|
|
1137
1073
|
closed: poll.closed, votersCount: poll.votersCount || 0,
|
|
1138
1074
|
});
|
|
1139
|
-
await this.remote
|
|
1075
|
+
await podNotes.write(this.remote, question.id, question);
|
|
1140
1076
|
// The Create is overwritten too, so a group's Announce resolves to the
|
|
1141
1077
|
// current count rather than to the one the poll opened with.
|
|
1142
|
-
await this.remote
|
|
1078
|
+
await podNotes.writeCreate(this.remote, wire.createActivityId(question.id),
|
|
1143
1079
|
this._pollActivity('Create', question, wire.createActivityId(question.id)));
|
|
1144
1080
|
this.store.updateStatus(questionId, { poll });
|
|
1145
1081
|
|
|
@@ -1206,8 +1142,7 @@ export class Publisher {
|
|
|
1206
1142
|
// remote server reads when it shows this profile's pins.
|
|
1207
1143
|
async publishFeatured() {
|
|
1208
1144
|
const ids = this.store.getStatuses().filter(s => s.kind === 'post' && s.pinned).map(s => s.noteId);
|
|
1209
|
-
await this.remote
|
|
1210
|
-
await this.remote.setAcl(this.urls.featured, ['Read']);
|
|
1145
|
+
await podFeatured.write(this.remote, this.urls, wire.orderedCollection(this.urls.featured, ids));
|
|
1211
1146
|
return ids.length;
|
|
1212
1147
|
}
|
|
1213
1148
|
|
|
@@ -1236,14 +1171,8 @@ export class Publisher {
|
|
|
1236
1171
|
attachments: atts, mentions, visibility: s.visibility || 'public',
|
|
1237
1172
|
summary: spoilerText, updated, container,
|
|
1238
1173
|
});
|
|
1239
|
-
await this.remote
|
|
1240
|
-
await this.remote
|
|
1241
|
-
if (s.slug) {
|
|
1242
|
-
await this.local.writeNote('posts', s.slug, {
|
|
1243
|
-
noteId: note.id, actor: urls.actor, published: s.published, content: note.content,
|
|
1244
|
-
inReplyTo: s.inReplyTo, attachments: atts,
|
|
1245
|
-
});
|
|
1246
|
-
}
|
|
1174
|
+
await podNotes.write(this.remote, note.id, note);
|
|
1175
|
+
await podNotes.writeCreate(this.remote, wire.createActivityId(note.id), wire.createActivity(note, urls));
|
|
1247
1176
|
const patched = this.store.updateStatus(s.noteId, {
|
|
1248
1177
|
content: note.content, text: content, editedAt: updated,
|
|
1249
1178
|
spoiler: spoilerText || undefined,
|