fedipod-server 0.10.0 → 0.12.1
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 +15 -6
- package/dist/claims.js +1 -1
- package/dist/handler.d.ts +7 -1
- package/dist/handler.js +58 -12
- package/dist/handler.jsonld +5 -1
- package/dist/store-pod.js +18 -4
- package/lib/{c2s.mjs → client/c2s.mjs} +8 -3
- package/lib/{localapi.mjs → client/localapi.mjs} +2 -2
- package/lib/client/masto/accounts.mjs +264 -0
- package/lib/client/masto/body.mjs +69 -0
- package/lib/client/masto/index.mjs +183 -0
- package/lib/client/masto/instance.mjs +104 -0
- package/lib/client/masto/media.mjs +133 -0
- package/lib/client/masto/oauth.mjs +599 -0
- package/lib/client/masto/render.mjs +459 -0
- package/lib/client/masto/statuses.mjs +331 -0
- package/lib/client/masto/timelines.mjs +316 -0
- package/lib/{streaming.mjs → client/streaming.mjs} +1 -1
- package/lib/{acctfeed.mjs → connections/acctfeed.mjs} +1 -1
- package/lib/{atproto.mjs → connections/atproto.mjs} +15 -16
- package/lib/{bskygroup.mjs → connections/bskygroup.mjs} +1 -1
- package/lib/{fediacct.mjs → connections/fediacct.mjs} +31 -35
- package/lib/{import.mjs → connections/import.mjs} +2 -2
- package/lib/{tagfeed.mjs → connections/tagfeed.mjs} +21 -4
- package/lib/connections/vault.mjs +114 -0
- package/lib/core/as2.mjs +124 -0
- package/lib/core/contexts/activitystreams.json +379 -0
- package/lib/core/contexts/did-v1.json +57 -0
- package/lib/core/contexts/fep-5711.json +36 -0
- package/lib/core/contexts/gotosocial.json +86 -0
- package/lib/core/contexts/identity-v1.json +152 -0
- package/lib/core/contexts/index.mjs +45 -0
- package/lib/core/contexts/join-lemmy.json +33 -0
- package/lib/core/contexts/joinmastodon.json +28 -0
- package/lib/core/contexts/map.json +16 -0
- package/lib/core/contexts/miscellany.json +19 -0
- package/lib/core/contexts/schemaorg.json +8845 -0
- package/lib/core/contexts/security-data-integrity-v1.json +78 -0
- package/lib/core/contexts/security-data-integrity-v2.json +81 -0
- package/lib/core/contexts/security-multikey-v1.json +35 -0
- package/lib/core/contexts/security-v1.json +74 -0
- package/lib/core/contexts/webfinger.json +10 -0
- package/lib/{deliver.mjs → core/deliver.mjs} +8 -2
- package/lib/core/intake/activities.mjs +437 -0
- package/lib/core/intake/activity.mjs +240 -0
- package/lib/core/intake/channel.mjs +144 -0
- package/lib/core/intake/group.mjs +222 -0
- package/lib/core/intake/index.mjs +629 -0
- package/lib/core/intake/notes.mjs +288 -0
- package/lib/core/intake/verify.mjs +141 -0
- package/lib/{keys.mjs → core/keys.mjs} +1 -1
- package/lib/core/publisher/collections.mjs +229 -0
- package/lib/core/publisher/index.mjs +421 -0
- package/lib/core/publisher/notes.mjs +188 -0
- package/lib/core/publisher/questions.mjs +233 -0
- package/lib/core/publisher/restore.mjs +196 -0
- package/lib/core/shapes/activitystreams.ttl +129 -0
- package/lib/core/shapes/index.mjs +107 -0
- package/lib/core/shapes/shapes-text.mjs +13 -0
- package/lib/{social.mjs → core/social.mjs} +19 -10
- package/lib/{storage.mjs → core/storage.mjs} +2 -2
- package/lib/{store.mjs → core/store.mjs} +12 -1
- package/lib/{wire.mjs → core/wire.mjs} +48 -67
- package/lib/device/admin/index.mjs +13 -0
- package/lib/device/admin/origins.mjs +35 -0
- package/lib/device/admin/routes/connections.mjs +144 -0
- package/lib/device/admin/routes/gateway.mjs +199 -0
- package/lib/device/admin/routes/lifecycle.mjs +191 -0
- package/lib/device/admin/routes/owner.mjs +322 -0
- package/lib/device/admin/routes/setup.mjs +393 -0
- package/lib/device/admin/routes/social.mjs +188 -0
- package/lib/device/admin/server.mjs +95 -0
- package/lib/device/admin/static.mjs +244 -0
- package/lib/device/admin/surface.mjs +274 -0
- package/lib/device/cli/commands/account.mjs +586 -0
- package/lib/device/cli/commands/run.mjs +278 -0
- package/lib/device/cli/commands/service.mjs +221 -0
- package/lib/device/cli/commands/setup.mjs +410 -0
- package/lib/device/cli/commands/state.mjs +559 -0
- package/lib/device/cli/context.mjs +288 -0
- package/lib/{export-collections.mjs → device/export-collections.mjs} +1 -1
- package/lib/{migrate.mjs → device/migrate.mjs} +5 -19
- package/lib/device/remote.mjs +54 -0
- package/lib/{setup.mjs → device/setup.mjs} +3 -3
- package/lib/{update.mjs → device/update.mjs} +1 -1
- package/lib/{directory.mjs → gateway/directory.mjs} +1 -1
- package/lib/{front-core.mjs → gateway/front-core.mjs} +272 -49
- package/lib/{gateway-core.mjs → gateway/gateway-core.mjs} +3 -7
- package/lib/{httpsig.mjs → gateway/httpsig.mjs} +34 -3
- 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/{links.mjs → pod/links.mjs} +1 -1
- 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/{remote.mjs → pod/transport.mjs} +217 -159
- package/lib/pod/urls.mjs +71 -0
- package/lib/{embed.mjs → server/embed.mjs} +149 -22
- package/lib/shared/links.mjs +9 -0
- package/lib/{ua.mjs → shared/ua.mjs} +1 -1
- package/package.json +1 -1
- package/run-agent.mjs +47 -64
- package/web/admin/actors.js +145 -0
- 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/common.js +23 -0
- package/web/admin/connections.js +112 -0
- package/web/admin/gateway.js +111 -0
- package/web/admin/group.js +258 -0
- package/web/admin/index.html +60 -66
- package/web/admin/record.js +378 -0
- package/web/admin/setup/index.html +3 -1
- package/web/admin/setup/setup.js +2 -13
- package/web/admin/tokens.css +56 -0
- package/web/admin/upkeep.js +170 -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 +70014 -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/shapes-text.mjs +8 -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/actors.js +145 -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/common.js +23 -0
- package/web/app/site/admin/connections.js +112 -0
- package/web/app/site/admin/gateway.js +111 -0
- package/web/app/site/admin/group.js +258 -0
- package/web/app/site/admin/index.html +562 -0
- package/web/app/site/admin/record.js +378 -0
- package/web/app/site/admin/setup/index.html +192 -0
- package/web/app/site/admin/setup/setup.js +297 -0
- package/web/app/site/admin/tokens.css +56 -0
- package/web/app/site/admin/upkeep.js +170 -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 +70014 -0
- package/web/app/sw-src.mjs +220 -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/admin.mjs +0 -1913
- package/lib/intake.mjs +0 -1764
- package/lib/mastoapi.mjs +0 -1947
- package/lib/podrdf.mjs +0 -132
- package/lib/publisher.mjs +0 -1263
- package/web/admin/admin.js +0 -1114
- /package/lib/{oidc-auth.mjs → client/oidc-auth.mjs} +0 -0
- /package/lib/{webpush.mjs → client/webpush.mjs} +0 -0
- /package/lib/{bskyfeed.mjs → connections/bskyfeed.mjs} +0 -0
- /package/lib/{lease.mjs → core/lease.mjs} +0 -0
- /package/lib/{polls.mjs → core/polls.mjs} +0 -0
- /package/lib/{proof.mjs → core/proof.mjs} +0 -0
- /package/lib/{account.mjs → device/account.mjs} +0 -0
- /package/lib/{certs.mjs → device/certs.mjs} +0 -0
- /package/lib/{home.mjs → device/home.mjs} +0 -0
- /package/lib/{ports.mjs → device/ports.mjs} +0 -0
- /package/lib/{guard.mjs → shared/guard.mjs} +0 -0
- /package/lib/{safefetch.mjs → shared/safefetch.mjs} +0 -0
|
@@ -16,15 +16,16 @@ import path from 'node:path';
|
|
|
16
16
|
|
|
17
17
|
import { createRequire } from 'node:module';
|
|
18
18
|
|
|
19
|
-
import { Agent } from '
|
|
20
|
-
import { RemotePod } from '
|
|
21
|
-
import { apUrls } from '
|
|
22
|
-
import { writeJsonAtomic } from '
|
|
23
|
-
import { buildAdminSurface } from '
|
|
24
|
-
import { FixedAuthorities } from '
|
|
19
|
+
import { Agent } from '../../run-agent.mjs';
|
|
20
|
+
import { RemotePod } from '../device/remote.mjs';
|
|
21
|
+
import { apUrls, DEFAULT_ROOT } from '../core/wire.mjs';
|
|
22
|
+
import { writeJsonAtomic } from '../device/home.mjs';
|
|
23
|
+
import { buildAdminSurface } from '../device/admin/index.mjs';
|
|
24
|
+
import { FixedAuthorities } from '../shared/guard.mjs';
|
|
25
|
+
import { CONNECTION_PREFIX, fileVault, moveVault, podVault } from '../connections/vault.mjs';
|
|
25
26
|
|
|
26
27
|
const require = createRequire(import.meta.url);
|
|
27
|
-
const { makeGate } = require('
|
|
28
|
+
const { makeGate } = require('../../vendor/gate.cjs');
|
|
28
29
|
|
|
29
30
|
// How long to gather store events before draining, so a delivery of several
|
|
30
31
|
// items costs one sweep rather than one each.
|
|
@@ -34,23 +35,75 @@ const DRAIN_COALESCE_MS = 250;
|
|
|
34
35
|
const mintSecret = () => crypto.randomBytes(32).toString('base64');
|
|
35
36
|
|
|
36
37
|
/**
|
|
37
|
-
* The secret guarding one identity's owner door, kept
|
|
38
|
-
* Minted when absent; `rotate` re-mints over an
|
|
39
|
-
* lost secret is recovered, by proving pod
|
|
38
|
+
* The secret guarding one identity's owner door, kept in its pod's state
|
|
39
|
+
* beside its signing key. Minted when absent; `rotate` re-mints over an
|
|
40
|
+
* existing one — which is how a lost secret is recovered, by proving pod
|
|
41
|
+
* control again. Never logged.
|
|
42
|
+
*
|
|
43
|
+
* `dataDir`, when given, is where an identity set up before this kept it: the
|
|
44
|
+
* secret is moved into the pod and the host's copy removed, so the owner's
|
|
45
|
+
* existing door link goes on working.
|
|
40
46
|
*/
|
|
41
|
-
export function ensureDoorSecret(
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
const
|
|
47
|
+
export async function ensureDoorSecret(session, podBase, { rotate = false, dataDir = null, handle = null, log = () => {} } = {}) {
|
|
48
|
+
const base = podBase.endsWith('/') ? podBase : podBase + '/';
|
|
49
|
+
const url = apUrls(base, DEFAULT_ROOT).state + 'door-secret.json';
|
|
50
|
+
const onHost = dataDir && handle ? path.join(dataDir, handle, 'door-secret.json') : null;
|
|
51
|
+
|
|
45
52
|
if (!rotate) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
53
|
+
const res = await session.fetch(url, { headers: { accept: 'application/json' } })
|
|
54
|
+
.catch(() => ({ ok: false }));
|
|
55
|
+
if (res.ok) {
|
|
56
|
+
try {
|
|
57
|
+
const rec = JSON.parse(await res.text());
|
|
58
|
+
if (rec?.secret) {
|
|
59
|
+
if (onHost) fs.rmSync(onHost, { force: true });
|
|
60
|
+
return { secret: rec.secret, url, rotated: false };
|
|
61
|
+
}
|
|
62
|
+
} catch { /* unreadable: mint below */ }
|
|
63
|
+
}
|
|
64
|
+
// Set up before the secret lived on the pod: keep the one the owner has.
|
|
65
|
+
if (onHost && fs.existsSync(onHost)) {
|
|
66
|
+
try {
|
|
67
|
+
const rec = JSON.parse(fs.readFileSync(onHost, 'utf8'));
|
|
68
|
+
if (rec?.secret && await putSecret(session, url, rec)) {
|
|
69
|
+
fs.rmSync(onHost, { force: true });
|
|
70
|
+
log('door secret moved into the pod');
|
|
71
|
+
return { secret: rec.secret, url, rotated: false };
|
|
72
|
+
}
|
|
73
|
+
} catch { /* unreadable: mint below */ }
|
|
74
|
+
}
|
|
50
75
|
}
|
|
76
|
+
|
|
51
77
|
const rec = { secret: mintSecret(), mintedAt: new Date().toISOString() };
|
|
52
|
-
|
|
53
|
-
|
|
78
|
+
if (await putSecret(session, url, rec)) {
|
|
79
|
+
if (onHost) fs.rmSync(onHost, { force: true });
|
|
80
|
+
return { secret: rec.secret, url, rotated: rotate };
|
|
81
|
+
}
|
|
82
|
+
// The pod would not take it. Opting in must not fail for that: a pod that
|
|
83
|
+
// cannot be written to right now is a pod whose owner still asked for an
|
|
84
|
+
// identity, and the secret has somewhere else to live — where it lived
|
|
85
|
+
// before. The next start moves it onto the pod.
|
|
86
|
+
if (onHost) {
|
|
87
|
+
writeJsonAtomic(onHost, rec, { mode: 0o600 });
|
|
88
|
+
log(`the pod would not take the door secret; it is on this host at ${onHost} for now`);
|
|
89
|
+
return { secret: rec.secret, url: onHost, rotated: rotate };
|
|
90
|
+
}
|
|
91
|
+
throw new Error(`could not write the door secret to ${url}, and there is no directory to keep it in`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function putSecret(session, url, rec) {
|
|
95
|
+
try {
|
|
96
|
+
const res = await session.fetch(url, {
|
|
97
|
+
method: 'PUT',
|
|
98
|
+
headers: { 'content-type': 'application/json' },
|
|
99
|
+
body: JSON.stringify(rec, null, 2) + '\n',
|
|
100
|
+
});
|
|
101
|
+
return res.ok;
|
|
102
|
+
} catch {
|
|
103
|
+
// A store that throws rather than answering is a store that did not take
|
|
104
|
+
// it, which is the same answer.
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
54
107
|
}
|
|
55
108
|
|
|
56
109
|
/** The identity's name: a subdomain pod is its label, a path pod its last segment. */
|
|
@@ -64,20 +117,90 @@ export function handleFor(podBase) {
|
|
|
64
117
|
* The credential file an embedded identity runs on. It names the pod and
|
|
65
118
|
* nothing else: there is no client id and no secret, because there is nobody
|
|
66
119
|
* to authenticate to. Absent `privateRoot`, so the state tree stays on the pod.
|
|
120
|
+
*
|
|
121
|
+
* `keysMode: 'pod'` because here the pod's server and the agent are the same
|
|
122
|
+
* process: keeping the signing key on the host's disk puts it somewhere the
|
|
123
|
+
* pod does not travel to, so handing the pod over hands over an identity that
|
|
124
|
+
* cannot sign. An identity set up before this gets the mode added.
|
|
67
125
|
*/
|
|
68
126
|
function ensureCredential(home, { podBase, webId }) {
|
|
69
127
|
const file = path.join(home, 'credential.json');
|
|
70
|
-
try {
|
|
71
|
-
|
|
128
|
+
try {
|
|
129
|
+
const rec = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
130
|
+
if (rec.keysMode === 'pod') return rec;
|
|
131
|
+
const updated = { ...rec, keysMode: 'pod' };
|
|
132
|
+
writeJsonAtomic(file, updated, { mode: 0o600 });
|
|
133
|
+
return updated;
|
|
134
|
+
} catch { /* first run for this identity */ }
|
|
72
135
|
const rec = {
|
|
73
136
|
webId,
|
|
74
137
|
remotePod: podBase.endsWith('/') ? podBase : podBase + '/',
|
|
75
138
|
root: 'activitypods-js/',
|
|
139
|
+
keysMode: 'pod',
|
|
76
140
|
};
|
|
77
141
|
writeJsonAtomic(file, rec, { mode: 0o600 });
|
|
78
142
|
return rec;
|
|
79
143
|
}
|
|
80
144
|
|
|
145
|
+
/**
|
|
146
|
+
* Move an identity's signing key off the host's disk and into its pod.
|
|
147
|
+
*
|
|
148
|
+
* Only after the pod has it: the private key is the one thing here that
|
|
149
|
+
* nothing can rebuild, and remote servers have its public half cached, so a
|
|
150
|
+
* copy lost between the two places is an identity that can never sign again
|
|
151
|
+
* under the name it already published.
|
|
152
|
+
*/
|
|
153
|
+
async function keyIntoPod(agent, home, log) {
|
|
154
|
+
const local = path.join(home, 'keys.json');
|
|
155
|
+
if (!fs.existsSync(local)) return;
|
|
156
|
+
if (agent.store.read('keys.json', null)) {
|
|
157
|
+
fs.rmSync(local, { force: true });
|
|
158
|
+
log('this identity signs with the key in its pod; removed the copy on this host');
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
let rec;
|
|
162
|
+
try {
|
|
163
|
+
rec = JSON.parse(fs.readFileSync(local, 'utf8'));
|
|
164
|
+
} catch (e) {
|
|
165
|
+
log(`the local signing key could not be read (${e.message}) — leaving it where it is`);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
agent.store.write('keys.json', rec);
|
|
169
|
+
if (!await agent.store.commit()) {
|
|
170
|
+
log('could not write the signing key to the pod — it stays on this host for now');
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
fs.rmSync(local, { force: true });
|
|
174
|
+
log('signing key moved into the pod');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Move an identity's credentials for accounts it holds on OTHER servers off
|
|
179
|
+
* the host's disk and into its pod. A token here is full access to somebody's
|
|
180
|
+
* account on a server this project does not run, so the host's copy goes only
|
|
181
|
+
* after the pod has taken it.
|
|
182
|
+
*/
|
|
183
|
+
async function connectionsIntoPod(agent, home, log) {
|
|
184
|
+
const moves = [
|
|
185
|
+
[fileVault(path.join(home, 'fediaccts')), podVault(agent.store, `${CONNECTION_PREFIX}fedi-`)],
|
|
186
|
+
[fileVault(path.join(home, 'fediaccts', '_apps')), podVault(agent.store, `${CONNECTION_PREFIX}fediapp-`)],
|
|
187
|
+
[fileVault(home), podVault(agent.store, CONNECTION_PREFIX)],
|
|
188
|
+
];
|
|
189
|
+
// The last pair is the whole home directory, where only the Bluesky
|
|
190
|
+
// credential is ours to move.
|
|
191
|
+
for (const [from, to] of moves.slice(0, 2)) await moveVault(from, to, log);
|
|
192
|
+
const bluesky = moves[2][0].read('atproto');
|
|
193
|
+
if (bluesky && !moves[2][1].read('bluesky')) {
|
|
194
|
+
moves[2][1].write('bluesky', bluesky);
|
|
195
|
+
if (await moves[2][1].commit()) {
|
|
196
|
+
await moves[2][0].remove('atproto');
|
|
197
|
+
log('the Bluesky connection moved into the pod');
|
|
198
|
+
} else {
|
|
199
|
+
log('could not write the Bluesky connection to the pod — it stays on this host for now');
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
81
204
|
/**
|
|
82
205
|
* Bring one identity up inside the server.
|
|
83
206
|
*
|
|
@@ -151,6 +274,10 @@ export async function startEmbeddedAgent({
|
|
|
151
274
|
agent.stateLoaded = true;
|
|
152
275
|
}
|
|
153
276
|
|
|
277
|
+
// Before connect(), which is what looks the key and the connections up.
|
|
278
|
+
await keyIntoPod(agent, home, log);
|
|
279
|
+
await connectionsIntoPod(agent, home, log);
|
|
280
|
+
|
|
154
281
|
await agent.connect();
|
|
155
282
|
|
|
156
283
|
// The client surfaces, on the pod's own origin: the Mastodon API a phone app
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// links.mjs — reading RFC 8288 Link headers.
|
|
2
|
+
//
|
|
3
|
+
// The implementation moved into the pod library (lib/pod/links.mjs), which has
|
|
4
|
+
// to carry it: that library runs unmodified in a service worker and may not
|
|
5
|
+
// import anything above its own directory. This path stays so the dozen
|
|
6
|
+
// importers around the project do not move, and so there is still one obvious
|
|
7
|
+
// place to look for "how does this read a Link header".
|
|
8
|
+
|
|
9
|
+
export { linkTargets, REL } from '../pod/links.mjs';
|
|
@@ -16,7 +16,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
16
16
|
let version = '0';
|
|
17
17
|
try {
|
|
18
18
|
({ version } = JSON.parse(fs.readFileSync(
|
|
19
|
-
path.join(path.dirname(fileURLToPath(import.meta.url)), '
|
|
19
|
+
path.join(path.dirname(fileURLToPath(import.meta.url)), '../../package.json'), 'utf8')));
|
|
20
20
|
} catch {
|
|
21
21
|
try {
|
|
22
22
|
({ version } = JSON.parse(fs.readFileSync(
|
package/package.json
CHANGED
package/run-agent.mjs
CHANGED
|
@@ -28,29 +28,29 @@ import os from 'node:os';
|
|
|
28
28
|
import path from 'node:path';
|
|
29
29
|
import { pathToFileURL } from 'node:url';
|
|
30
30
|
|
|
31
|
-
import { PodStore } from './lib/store.mjs';
|
|
32
|
-
import { apRoot, rootOf, writeJsonAtomic } from './lib/home.mjs';
|
|
33
|
-
import { ensureTrustedTls } from './lib/certs.mjs';
|
|
34
|
-
import { storageFor } from './lib/storage.mjs';
|
|
35
|
-
import { resolveKeys } from './lib/keys.mjs';
|
|
36
|
-
import { RemotePod } from './lib/remote.mjs';
|
|
37
|
-
import {
|
|
38
|
-
import {
|
|
39
|
-
import {
|
|
40
|
-
import {
|
|
41
|
-
import {
|
|
42
|
-
import {
|
|
43
|
-
import {
|
|
44
|
-
import {
|
|
45
|
-
import { AcctFeed } from './lib/acctfeed.mjs';
|
|
46
|
-
import { BskyFeed } from './lib/bskyfeed.mjs';
|
|
47
|
-
import { BskyGroup } from './lib/bskygroup.mjs';
|
|
48
|
-
import { Lease } from './lib/lease.mjs';
|
|
49
|
-
import { startAdmin } from './lib/admin.mjs';
|
|
50
|
-
import { exposureProblem, hostLabel } from './lib/guard.mjs';
|
|
51
|
-
import { pendingSteps } from './lib/migrate.mjs';
|
|
52
|
-
import { apUrls, assertionKeyId , publicHandle } from './lib/wire.mjs';
|
|
53
|
-
import { followActor, unfollowActor, resolveHandle } from './lib/social.mjs';
|
|
31
|
+
import { PodStore } from './lib/core/store.mjs';
|
|
32
|
+
import { apRoot, rootOf, writeJsonAtomic } from './lib/device/home.mjs';
|
|
33
|
+
import { ensureTrustedTls } from './lib/device/certs.mjs';
|
|
34
|
+
import { storageFor } from './lib/core/storage.mjs';
|
|
35
|
+
import { resolveKeys } from './lib/core/keys.mjs';
|
|
36
|
+
import { RemotePod } from './lib/device/remote.mjs';
|
|
37
|
+
import { Deliverer } from './lib/core/deliver.mjs';
|
|
38
|
+
import { Publisher } from './lib/core/publisher/index.mjs';
|
|
39
|
+
import { Intake } from './lib/core/intake/index.mjs';
|
|
40
|
+
import { TagFeed } from './lib/connections/tagfeed.mjs';
|
|
41
|
+
import { ImportWorker } from './lib/connections/import.mjs';
|
|
42
|
+
import { Atproto } from './lib/connections/atproto.mjs';
|
|
43
|
+
import { FediAccounts } from './lib/connections/fediacct.mjs';
|
|
44
|
+
import { vaultsFor } from './lib/connections/vault.mjs';
|
|
45
|
+
import { AcctFeed } from './lib/connections/acctfeed.mjs';
|
|
46
|
+
import { BskyFeed } from './lib/connections/bskyfeed.mjs';
|
|
47
|
+
import { BskyGroup } from './lib/connections/bskygroup.mjs';
|
|
48
|
+
import { Lease } from './lib/core/lease.mjs';
|
|
49
|
+
import { startAdmin } from './lib/device/admin/index.mjs';
|
|
50
|
+
import { exposureProblem, hostLabel } from './lib/shared/guard.mjs';
|
|
51
|
+
import { pendingSteps } from './lib/device/migrate.mjs';
|
|
52
|
+
import { apUrls, assertionKeyId , publicHandle } from './lib/core/wire.mjs';
|
|
53
|
+
import { followActor, unfollowActor, resolveHandle } from './lib/core/social.mjs';
|
|
54
54
|
|
|
55
55
|
export class Agent {
|
|
56
56
|
constructor({ home, log }) {
|
|
@@ -69,15 +69,14 @@ export class Agent {
|
|
|
69
69
|
|
|
70
70
|
// Where the private half lives. `privateRoot` in the credential file names a
|
|
71
71
|
// container — by default a plain directory beside the credential, and a pod
|
|
72
|
-
// on this machine if you move it there
|
|
73
|
-
// laid out as on the pod. Absent means on the pod, exactly as before, so an
|
|
72
|
+
// on this machine if you move it there. Absent means on the pod, so an
|
|
74
73
|
// existing install is untouched.
|
|
75
74
|
privateUrls(cred, urls = this.urls) {
|
|
76
75
|
if (!cred.privateRoot) {
|
|
77
|
-
return { state: urls.state,
|
|
76
|
+
return { state: urls.state, archive: urls.home + 'inbox-archive/', elsewhere: false };
|
|
78
77
|
}
|
|
79
78
|
const base = cred.privateRoot.endsWith('/') ? cred.privateRoot : cred.privateRoot + '/';
|
|
80
|
-
return { state: base + 'ap-state/',
|
|
79
|
+
return { state: base + 'ap-state/', archive: base + 'inbox-archive/', elsewhere: true };
|
|
81
80
|
}
|
|
82
81
|
|
|
83
82
|
// A container to keep the private half in. On the pod it is reached with the
|
|
@@ -146,14 +145,9 @@ export class Agent {
|
|
|
146
145
|
await this.remote.setAcl(this.urls.home, []);
|
|
147
146
|
await this.remote.setAcl(this.urls.state, []);
|
|
148
147
|
if (priv.elsewhere) {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
if (!r.ok) throw new Error(`private store ${s.base}.keep → ${r.why}`);
|
|
152
|
-
}
|
|
148
|
+
const r = await stateStore.write('.keep', '{"keep":true}\n', 'application/json');
|
|
149
|
+
if (!r.ok) throw new Error(`private store ${stateStore.base}.keep → ${r.why}`);
|
|
153
150
|
this.log(`private state lives at ${cred.privateRoot} (${stateStore.kind})`);
|
|
154
|
-
} else {
|
|
155
|
-
await this.remote.putJson(this.urls.fediverse + '.keep', { keep: true }, 'application/json');
|
|
156
|
-
await this.remote.setAcl(this.urls.fediverse, []);
|
|
157
151
|
}
|
|
158
152
|
this.store.attach(stateStore);
|
|
159
153
|
// Re-running setup must not destroy state set afterwards (the UI
|
|
@@ -314,7 +308,6 @@ export class Agent {
|
|
|
314
308
|
const { rotateKeyOnce, ...rest } = cred;
|
|
315
309
|
writeJsonAtomic(path.join(this.home, 'credential.json'), rest);
|
|
316
310
|
}
|
|
317
|
-
this.local = new PodRdf({ storage: this.privateStorage(cred, 'fediverse') });
|
|
318
311
|
// connect() can run more than once — connectWithRetry retries after a throw,
|
|
319
312
|
// and the CLI connects for real after its confirmation. Deliverer arms a
|
|
320
313
|
// 60s queue timer in its constructor and Intake owns a poll timer and a
|
|
@@ -332,13 +325,10 @@ export class Agent {
|
|
|
332
325
|
log: this.log, passive: this.viewer,
|
|
333
326
|
});
|
|
334
327
|
this.publisher = new Publisher({
|
|
335
|
-
config, remote: this.remote,
|
|
328
|
+
config, remote: this.remote, store: this.store,
|
|
336
329
|
deliverer: this.deliverer, publicKeyPem: keys.rsaPublicPem,
|
|
337
330
|
assertionKey: keys.edPublicMultibase, log: this.log,
|
|
338
331
|
resolveMention: (h) => resolveHandle(this, h),
|
|
339
|
-
// Whether the fediverse tree is on the pod at all, so the ACL check does
|
|
340
|
-
// not probe for something the default layout keeps on local disk.
|
|
341
|
-
privateOnPod: !cred.privateRoot,
|
|
342
332
|
// Inside a pod server the client surface answers on the pod's own
|
|
343
333
|
// origin, so it can be advertised. Standalone it is on loopback, and
|
|
344
334
|
// naming it in a world-readable actor would send clients nowhere.
|
|
@@ -347,7 +337,7 @@ export class Agent {
|
|
|
347
337
|
// Intake is constructed even for viewers — its signed fetchAP powers
|
|
348
338
|
// search/deref; start() (draining) is active-only.
|
|
349
339
|
this.intake = new Intake({
|
|
350
|
-
config, urls: this.urls, remote: this.remote,
|
|
340
|
+
config, urls: this.urls, remote: this.remote, store: this.store,
|
|
351
341
|
deliverer: this.deliverer, publisher: this.publisher, log: this.log, lease: this.lease,
|
|
352
342
|
archive: this.privateStorage(cred, 'archive'),
|
|
353
343
|
push: !this.embedded, pollSeconds: this.pollSeconds || null,
|
|
@@ -355,13 +345,20 @@ export class Agent {
|
|
|
355
345
|
// The CSV-import worker: paced, resumable, armed only while active.
|
|
356
346
|
this.importer?.stop();
|
|
357
347
|
this.importer = new ImportWorker({ agent: this, log: this.log });
|
|
348
|
+
// Where the credentials for accounts held on OTHER servers are kept.
|
|
349
|
+
const vaults = vaultsFor({ embedded: this.embedded, home: this.home, store: this.store });
|
|
358
350
|
// The Bluesky connection, when one exists. Stamped to this actor; a
|
|
359
351
|
// credential connected for another identity is treated as absent.
|
|
360
|
-
this.atproto = new Atproto({
|
|
352
|
+
this.atproto = new Atproto({
|
|
353
|
+
localDir: this.home, actorId: this.urls.actor, log: this.log, vault: vaults.bluesky,
|
|
354
|
+
});
|
|
361
355
|
this.publisher.atproto = this.atproto;
|
|
362
356
|
// Fediverse accounts the owner holds elsewhere. Same custody rules as the
|
|
363
357
|
// Bluesky credential, and the same absence when stamped for someone else.
|
|
364
|
-
this.fediaccts = new FediAccounts({
|
|
358
|
+
this.fediaccts = new FediAccounts({
|
|
359
|
+
localDir: this.home, actorId: this.urls.actor, log: this.log,
|
|
360
|
+
vault: vaults.fedi, apps: vaults.apps,
|
|
361
|
+
});
|
|
365
362
|
this.acctfeed?.stop();
|
|
366
363
|
this.acctfeed = null;
|
|
367
364
|
this.bskyfeed?.stop();
|
|
@@ -581,7 +578,7 @@ export class Agent {
|
|
|
581
578
|
this.ensureActorPublished()
|
|
582
579
|
.catch(e => this.log(`actor check failed: ${e.message}`));
|
|
583
580
|
}
|
|
584
|
-
this.
|
|
581
|
+
this.seedFollowNotifications().catch(e => this.log(`notification seeding failed: ${e.message}`));
|
|
585
582
|
}
|
|
586
583
|
|
|
587
584
|
// Re-read state before acting on what we hold. A viewer's cache is kept
|
|
@@ -695,27 +692,13 @@ export class Agent {
|
|
|
695
692
|
return true;
|
|
696
693
|
}
|
|
697
694
|
|
|
698
|
-
//
|
|
699
|
-
//
|
|
700
|
-
async
|
|
701
|
-
if (
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
if (this.store.has('statuses.json')) return;
|
|
707
|
-
const entries = [];
|
|
708
|
-
for (const [container, kind] of [['timeline', 'timeline'], ['posts', 'post']]) {
|
|
709
|
-
for (const url of await this.local.listNotes(container)) {
|
|
710
|
-
try {
|
|
711
|
-
const n = await this.local.readNote(url);
|
|
712
|
-
if (n.noteId) entries.push({ ...n, kind, slug: url.split('/').pop() });
|
|
713
|
-
} catch (e) { this.log(`backfill: skipped ${url}: ${e.message}`); }
|
|
714
|
-
}
|
|
695
|
+
// A first run inherits followers without ever having seen them arrive, so
|
|
696
|
+
// the notifications list starts empty where it should start full.
|
|
697
|
+
async seedFollowNotifications() {
|
|
698
|
+
if (this.store.has('notifications.json')) return;
|
|
699
|
+
for (const f of this.store.getContacts().followers) {
|
|
700
|
+
this.store.addNotification({ type: 'follow', actor: f.actor });
|
|
715
701
|
}
|
|
716
|
-
entries.sort((a, b) => String(b.published || '').localeCompare(String(a.published || '')));
|
|
717
|
-
this.store.write('statuses.json', entries.slice(0, 1000));
|
|
718
|
-
this.log(`backfilled ${entries.length} statuses from pod RDF`);
|
|
719
702
|
}
|
|
720
703
|
}
|
|
721
704
|
|
|
@@ -798,7 +781,7 @@ export async function startAgent({
|
|
|
798
781
|
// Is a newer FediPod published? Once at boot and daily after; the answer
|
|
799
782
|
// rides /status and the record page offers the update.
|
|
800
783
|
const updateTick = async () => {
|
|
801
|
-
const { checkLatest } = await import('./lib/update.mjs');
|
|
784
|
+
const { checkLatest } = await import('./lib/device/update.mjs');
|
|
802
785
|
const u = await checkLatest();
|
|
803
786
|
if (u) {
|
|
804
787
|
agent.updateInfo = u;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// actors.js — the other actors on this machine: the picker, going to one,
|
|
2
|
+
// starting a stopped one, and the form for a new one.
|
|
3
|
+
|
|
4
|
+
// Somewhere to go, and nothing else. A stopped actor is left out — the link
|
|
5
|
+
// would land on nothing. The one you are on keeps its place in the row but
|
|
6
|
+
// offers only `app`, because its admin page is the page you are reading.
|
|
7
|
+
|
|
8
|
+
let actors = [];
|
|
9
|
+
|
|
10
|
+
async function renderOthers() {
|
|
11
|
+
const { json } = await api('/profiles');
|
|
12
|
+
actors = json?.identities || [];
|
|
13
|
+
const sel = $('actor-pick');
|
|
14
|
+
sel.textContent = '';
|
|
15
|
+
// The fediverse address, which is what the actor IS to everyone else, and the
|
|
16
|
+
// only form that stays distinct: two identities can share a local handle, but
|
|
17
|
+
// never a handle AND a pod. A stopped one has no address to report — nothing
|
|
18
|
+
// answered — so it falls back to the name it is filed under.
|
|
19
|
+
const label = (r) => (r.address || r.handle || r.name)
|
|
20
|
+
+ (r.mode && r.mode !== 'active' ? ` (${r.mode})` : '') + (r.mode ? '' : ' (stopped)');
|
|
21
|
+
const seen = {};
|
|
22
|
+
for (const r of actors) seen[label(r)] = (seen[label(r)] || 0) + 1;
|
|
23
|
+
|
|
24
|
+
for (const [i, r] of actors.entries()) {
|
|
25
|
+
const o = document.createElement('option');
|
|
26
|
+
o.value = String(i);
|
|
27
|
+
o.selected = !!r.current; // it opens showing where you already are
|
|
28
|
+
o.textContent = label(r) + (seen[label(r)] > 1 && r.port ? ` :${r.port}` : '');
|
|
29
|
+
sel.appendChild(o);
|
|
30
|
+
}
|
|
31
|
+
// The way to a NEW actor rides the same dropdown as the existing ones.
|
|
32
|
+
const add = document.createElement('option');
|
|
33
|
+
add.value = '__add';
|
|
34
|
+
add.textContent = '+ add a new account…';
|
|
35
|
+
sel.appendChild(add);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Going to an actor commits a navigation and, for a stopped one, a POST that
|
|
39
|
+
// boots its pod — so it must run once per choice. Pressing Enter on the select
|
|
40
|
+
// fires BOTH a keydown (handled below) and a native `change`, so without a
|
|
41
|
+
// guard a keyboard commit would start the actor twice.
|
|
42
|
+
let goingToActor = false;
|
|
43
|
+
const goToActor = async () => {
|
|
44
|
+
if (goingToActor) return;
|
|
45
|
+
if ($('actor-pick').value === '__add') { renderOthers(); openNewActor(); return; }
|
|
46
|
+
const r = actors[Number($('actor-pick').value)];
|
|
47
|
+
if (!r || r.current) return;
|
|
48
|
+
goingToActor = true;
|
|
49
|
+
try {
|
|
50
|
+
if (r.mode) { location.href = r.admin; return; }
|
|
51
|
+
$('actor-pick').disabled = true;
|
|
52
|
+
say(`starting ${r.name}`);
|
|
53
|
+
const started = await write('/start-actor', { name: r.name }, `${r.name} is up`);
|
|
54
|
+
$('actor-pick').disabled = false;
|
|
55
|
+
if (started?.url) location.href = `${started.url}admin/`;
|
|
56
|
+
else renderOthers(); // put the picker back on the current actor
|
|
57
|
+
} finally {
|
|
58
|
+
goingToActor = false;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
// Choosing one goes to its record, as it always has.
|
|
62
|
+
//
|
|
63
|
+
// Arrowing is the one case that must not: on a closed select every Arrow
|
|
64
|
+
// keypress fires `change`, so a keyboard user browsing the list would be
|
|
65
|
+
// carried off to the first actor they passed — and for a stopped one that
|
|
66
|
+
// boots its pod. An arrow key arms a flag and the change it causes is ignored;
|
|
67
|
+
// Enter commits. A mouse never sets the flag, so clicking behaves as before.
|
|
68
|
+
let arrowing = false;
|
|
69
|
+
$('actor-pick').addEventListener('keydown', (ev) => {
|
|
70
|
+
if (['ArrowUp', 'ArrowDown', 'Home', 'End', 'PageUp', 'PageDown'].includes(ev.key)) arrowing = true;
|
|
71
|
+
if (ev.key === 'Enter') { arrowing = false; goToActor(); }
|
|
72
|
+
});
|
|
73
|
+
$('actor-pick').addEventListener('pointerdown', () => { arrowing = false; });
|
|
74
|
+
$('actor-pick').addEventListener('change', () => {
|
|
75
|
+
if (!arrowing) goToActor();
|
|
76
|
+
arrowing = false;
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Setting up a new actor asks for exactly what this page cannot already tell
|
|
80
|
+
// it: the pod and the account behind it, and its permanent name. Display name,
|
|
81
|
+
// bio and pictures are the client's job now — Phanpy's profile editor writes
|
|
82
|
+
// them through /api/v1/accounts/update_credentials. Where the private data goes
|
|
83
|
+
// is not a question either: it lands beside the credential, and `state --to`
|
|
84
|
+
// moves it afterwards.
|
|
85
|
+
|
|
86
|
+
const picked = (name) => document.querySelector(`input[name=${name}]:checked`)?.value;
|
|
87
|
+
|
|
88
|
+
function newActorRows() {
|
|
89
|
+
const mode = picked('newMode');
|
|
90
|
+
$('new-row-podname').hidden = mode !== 'new';
|
|
91
|
+
$('new-row-pod').hidden = mode !== 'existing';
|
|
92
|
+
}
|
|
93
|
+
for (const el of document.querySelectorAll('input[name=newMode]')) {
|
|
94
|
+
el.addEventListener('change', newActorRows);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// While the form is up the page IS the new actor's setup — so the record of
|
|
98
|
+
// the actor you came from goes away, list included. Which panes were showing
|
|
99
|
+
// is remembered rather than recomputed: whether Group and Inbox belong is a
|
|
100
|
+
// decision render() already made.
|
|
101
|
+
const RECORD_PANES = ['pane-identity', 'pane-group', 'pane-inbox', 'rail'];
|
|
102
|
+
let putBack = [];
|
|
103
|
+
|
|
104
|
+
function showNewActor(on) {
|
|
105
|
+
if (on) closePanels(); // nothing of the old actor left open behind it
|
|
106
|
+
document.body.classList.toggle('adding', on); // see body.adding in the CSS
|
|
107
|
+
$('new-actor-form').hidden = !on;
|
|
108
|
+
if (on) {
|
|
109
|
+
putBack = RECORD_PANES.filter(id => !$(id).hidden);
|
|
110
|
+
for (const id of putBack) $(id).hidden = true;
|
|
111
|
+
} else {
|
|
112
|
+
for (const id of putBack) $(id).hidden = false;
|
|
113
|
+
putBack = [];
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// `add a new account` is the last item of the Local Actors dropdown; choosing
|
|
118
|
+
// it opens the form in place. Arriving with ?new does the same.
|
|
119
|
+
function openNewActor() {
|
|
120
|
+
showNewActor(true);
|
|
121
|
+
newActorRows();
|
|
122
|
+
$('new-handle').focus();
|
|
123
|
+
}
|
|
124
|
+
$('new-actor-cancel').addEventListener('click', () => { showNewActor(false); say('nothing changed'); });
|
|
125
|
+
|
|
126
|
+
$('new-actor-form').addEventListener('submit', async (ev) => {
|
|
127
|
+
ev.preventDefault();
|
|
128
|
+
const handle = $('new-handle').value.trim();
|
|
129
|
+
const answers = {
|
|
130
|
+
handle,
|
|
131
|
+
kind: picked('newKind'),
|
|
132
|
+
mode: picked('newMode'),
|
|
133
|
+
issuer: $('new-issuer').value.trim(),
|
|
134
|
+
email: $('new-email').value.trim(),
|
|
135
|
+
password: $('new-password').value,
|
|
136
|
+
};
|
|
137
|
+
if (answers.mode === 'new') answers.podName = $('new-podname').value.trim() || handle;
|
|
138
|
+
else answers.pod = $('new-pod').value.trim();
|
|
139
|
+
$('new-actor-go').disabled = true;
|
|
140
|
+
say(`setting up ${handle || 'the new actor'} — this takes a while`);
|
|
141
|
+
const r = await write('/new-actor', answers, `setting up ${handle}`);
|
|
142
|
+
$('new-actor-go').disabled = false;
|
|
143
|
+
if (r?.url) location.href = r.url; // its own page, where the progress is
|
|
144
|
+
});
|
|
145
|
+
|
package/web/admin/bar.css
CHANGED
|
@@ -4,18 +4,9 @@
|
|
|
4
4
|
page's left edge and the buttons over its right rather than out at the window
|
|
5
5
|
borders. `width: 100%` with `box-sizing: border-box` keeps the padding inside
|
|
6
6
|
that 960 instead of adding to it, which is what makes the two margins equal. */
|
|
7
|
-
/*
|
|
8
|
-
|
|
9
|
-
Phanpy
|
|
10
|
-
because bar.css is on all three pages, so they cannot drift. */
|
|
11
|
-
:root { --surface: #f0f2f5; --raised: #fff;
|
|
12
|
-
--ink: #1b1c1e; --field-bg: #ffffff; --field-bg-hover: #f7f4ee; --field-edge: #767676;
|
|
13
|
-
--chevron: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='6'><path d='M1 1l4 4 4-4' fill='none' stroke='%23444444' stroke-width='1.6' stroke-linecap='round'/></svg>"); }
|
|
14
|
-
@media (prefers-color-scheme: dark) {
|
|
15
|
-
:root { --surface: #18191a; --raised: #242526;
|
|
16
|
-
--ink: #e8eaed; --field-bg: #383d44; --field-bg-hover: #40464e; --field-edge: #9aa0a6;
|
|
17
|
-
--chevron: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='6'><path d='M1 1l4 4 4-4' fill='none' stroke='%23dddddd' stroke-width='1.6' stroke-linecap='round'/></svg>"); }
|
|
18
|
-
}
|
|
7
|
+
/* Colours come from tokens.css (the front's palette), linked by every page
|
|
8
|
+
before this file, so the bar matches the front and the record rather than
|
|
9
|
+
Phanpy: --surface is the page ground, --raised the bar band on top of it. */
|
|
19
10
|
body { background: var(--surface); }
|
|
20
11
|
/* The BAND spans the window; its CONTENTS stay in the 960 column. Constraining
|
|
21
12
|
the element itself left the colour as a stripe with bare page either side.
|
|
@@ -51,8 +42,13 @@ body { background: var(--surface); }
|
|
|
51
42
|
#bar select:hover { background-color: var(--field-bg-hover); }
|
|
52
43
|
/* The app title, in px rather than rem: it is chrome, the same on every page,
|
|
53
44
|
not something that should move when a page changes its base size. */
|
|
54
|
-
#bar .name { flex: 1 1 auto; font-weight: 600; font-size:
|
|
45
|
+
#bar .name { flex: 1 1 auto; font-weight: 600; font-size: 40px; color: var(--heading);
|
|
55
46
|
margin: 0; line-height: 1.1; }
|
|
47
|
+
/* The browser build wraps the brand text in a link (to /?add) — the general
|
|
48
|
+
#bar a rule below would otherwise pin it back down to nav-link size and
|
|
49
|
+
colour, since a rule on the anchor itself always wins over inheriting the
|
|
50
|
+
heading's size. */
|
|
51
|
+
#bar .name a { font: inherit; color: inherit; text-decoration: none; }
|
|
56
52
|
/* Plain text, not chrome: three destinations in a row read better without a
|
|
57
53
|
box round each. */
|
|
58
54
|
#bar button, #bar a { font: inherit; font-size: .95rem; padding: .15rem .2rem;
|
package/web/admin/bar.js
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
// open windows apart without putting the handle in the page twice.
|
|
18
18
|
// /status, not /config: an agent that is not set up yet answers this one and
|
|
19
19
|
// 409s the other, which logged a failed request on every setup page.
|
|
20
|
-
fetch(location.pathname.replace(/\/admin\/.*$/u, '') + '/status').then(r => (r.ok ? r.json() : null)).then((s) => {
|
|
20
|
+
fetch(location.pathname.replace(/\/admin\/.*$/u, '') + '/status', { headers: { 'x-fedipod-page': '1' } }).then(r => (r.ok ? r.json() : null)).then((s) => {
|
|
21
21
|
if (s?.handle) document.title = `FediPod — ${s.handle}`;
|
|
22
22
|
// The full fediverse handle, centred in the bar on every page.
|
|
23
23
|
const el = document.getElementById('bar-handle');
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
const pick = document.getElementById('actor-pick');
|
|
33
33
|
if (pick && !document.getElementById('new-actor-form')) {
|
|
34
34
|
const base = location.pathname.replace(/\/admin\/.*$/u, '');
|
|
35
|
-
fetch(base + '/profiles').then(r => (r.ok ? r.json() : null)).then((j) => {
|
|
35
|
+
fetch(base + '/profiles', { headers: { 'x-fedipod-page': '1' } }).then(r => (r.ok ? r.json() : null)).then((j) => {
|
|
36
36
|
const actors = j?.identities || [];
|
|
37
37
|
pick.textContent = '';
|
|
38
38
|
const label = (r) => (r.address || r.handle || r.name)
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
// "which account logged in here" is a different question, and it is the one
|
|
28
28
|
// that goes wrong when the two origins get mixed.
|
|
29
29
|
const base = location.pathname.replace(/\/admin\/.*$/u, '');
|
|
30
|
-
const status = await fetch(base + '/status').then(r => (r.ok ? r.json() : null)).catch(() => null);
|
|
30
|
+
const status = await fetch(base + '/status', { headers: { 'x-fedipod-page': '1' } }).then(r => (r.ok ? r.json() : null)).catch(() => null);
|
|
31
31
|
if (!status?.actor) return; // not set up yet; setup owns that
|
|
32
32
|
|
|
33
33
|
// A deep link into the client — the record links an actor's own page this way
|