fedipod-server 0.28.1 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -34
- package/config/server.json +22 -0
- package/dist/claims.js +3 -2
- package/dist/directory.d.ts +2 -0
- package/dist/door.d.ts +4 -0
- package/dist/door.js +31 -0
- package/dist/fedipod.d.ts +16 -0
- package/dist/fedipod.js +13 -0
- package/dist/front.d.ts +3 -0
- package/dist/front.js +94 -0
- package/dist/handler.d.ts +39 -101
- package/dist/handler.js +86 -617
- package/dist/handler.jsonld +6 -30
- package/dist/identities.d.ts +62 -0
- package/dist/identities.js +236 -0
- package/dist/mounts.d.ts +43 -0
- package/dist/mounts.js +123 -0
- package/dist/opt-in.d.ts +46 -0
- package/dist/opt-in.js +176 -0
- package/dist/store-pod.js +37 -0
- package/package.json +10 -18
- package/lib/client/c2s.mjs +0 -582
- package/lib/client/localapi.mjs +0 -87
- package/lib/client/masto/accounts.mjs +0 -309
- package/lib/client/masto/body.mjs +0 -156
- package/lib/client/masto/index.mjs +0 -206
- package/lib/client/masto/instance.mjs +0 -112
- package/lib/client/masto/media.mjs +0 -79
- package/lib/client/masto/oauth.mjs +0 -656
- package/lib/client/masto/render.mjs +0 -518
- package/lib/client/masto/statuses.mjs +0 -372
- package/lib/client/masto/timelines.mjs +0 -438
- package/lib/client/oidc-auth.mjs +0 -62
- package/lib/client/streaming.mjs +0 -154
- package/lib/client/webpush.mjs +0 -98
- package/lib/connections/acctfeed.mjs +0 -257
- package/lib/connections/atproto.mjs +0 -309
- package/lib/connections/bskyfeed.mjs +0 -243
- package/lib/connections/bskygroup.mjs +0 -162
- package/lib/connections/fediacct.mjs +0 -253
- package/lib/connections/import.mjs +0 -357
- package/lib/connections/tagfeed.mjs +0 -212
- package/lib/connections/vault.mjs +0 -114
- package/lib/core/as2.mjs +0 -206
- package/lib/core/contexts/activitystreams.json +0 -379
- package/lib/core/contexts/anno.json +0 -126
- package/lib/core/contexts/did-v1.json +0 -57
- package/lib/core/contexts/fep-5711.json +0 -36
- package/lib/core/contexts/gotosocial.json +0 -86
- package/lib/core/contexts/identity-v1.json +0 -152
- package/lib/core/contexts/index.mjs +0 -63
- package/lib/core/contexts/join-lemmy.json +0 -33
- package/lib/core/contexts/joinmastodon.json +0 -28
- package/lib/core/contexts/litepub-0.1.json +0 -60
- package/lib/core/contexts/map.json +0 -19
- package/lib/core/contexts/miscellany.json +0 -19
- package/lib/core/contexts/quotes.json +0 -18
- package/lib/core/contexts/schemaorg.json +0 -8845
- package/lib/core/contexts/security-data-integrity-v1.json +0 -78
- package/lib/core/contexts/security-data-integrity-v2.json +0 -81
- package/lib/core/contexts/security-multikey-v1.json +0 -35
- package/lib/core/contexts/security-v1.json +0 -74
- package/lib/core/contexts/webfinger.json +0 -10
- package/lib/core/deliver.mjs +0 -371
- package/lib/core/graphview.mjs +0 -327
- package/lib/core/intake/activities.mjs +0 -633
- package/lib/core/intake/activity.mjs +0 -318
- package/lib/core/intake/channel.mjs +0 -144
- package/lib/core/intake/group.mjs +0 -256
- package/lib/core/intake/index.mjs +0 -701
- package/lib/core/intake/notes.mjs +0 -360
- package/lib/core/intake/verify.mjs +0 -142
- package/lib/core/keys.mjs +0 -153
- package/lib/core/lease.mjs +0 -206
- package/lib/core/polls.mjs +0 -105
- package/lib/core/profile-page.mjs +0 -104
- package/lib/core/proof.mjs +0 -79
- package/lib/core/publisher/collections.mjs +0 -237
- package/lib/core/publisher/index.mjs +0 -500
- package/lib/core/publisher/notes.mjs +0 -453
- package/lib/core/publisher/questions.mjs +0 -239
- package/lib/core/publisher/restore.mjs +0 -224
- package/lib/core/shapes/activitystreams.ttl +0 -157
- package/lib/core/shapes/index.mjs +0 -132
- package/lib/core/shapes/shapes-text.mjs +0 -13
- package/lib/core/social.mjs +0 -531
- package/lib/core/storage.mjs +0 -176
- package/lib/core/store.mjs +0 -568
- package/lib/core/wire-quotes.mjs +0 -71
- package/lib/core/wire.mjs +0 -889
- package/lib/device/account.mjs +0 -87
- package/lib/device/admin/index.mjs +0 -13
- package/lib/device/admin/origins.mjs +0 -35
- package/lib/device/admin/routes/connections.mjs +0 -144
- package/lib/device/admin/routes/gateway.mjs +0 -199
- package/lib/device/admin/routes/lifecycle.mjs +0 -200
- package/lib/device/admin/routes/owner.mjs +0 -322
- package/lib/device/admin/routes/setup.mjs +0 -412
- package/lib/device/admin/routes/social.mjs +0 -188
- package/lib/device/admin/server.mjs +0 -95
- package/lib/device/admin/static.mjs +0 -247
- package/lib/device/admin/surface.mjs +0 -315
- package/lib/device/certs.mjs +0 -281
- package/lib/device/cli/commands/account.mjs +0 -586
- package/lib/device/cli/commands/run.mjs +0 -278
- package/lib/device/cli/commands/service.mjs +0 -221
- package/lib/device/cli/commands/setup.mjs +0 -447
- package/lib/device/cli/commands/state.mjs +0 -561
- package/lib/device/cli/context.mjs +0 -288
- package/lib/device/export-collections.mjs +0 -161
- package/lib/device/gateway-move.mjs +0 -58
- package/lib/device/home.mjs +0 -161
- package/lib/device/migrate.mjs +0 -115
- package/lib/device/ports.mjs +0 -27
- package/lib/device/remote.mjs +0 -54
- package/lib/device/setup.mjs +0 -459
- package/lib/device/update.mjs +0 -96
- package/lib/gateway/caches.mjs +0 -36
- package/lib/gateway/directory.mjs +0 -90
- package/lib/gateway/front-core.mjs +0 -970
- package/lib/gateway/gateway-core.mjs +0 -165
- package/lib/gateway/headers.mjs +0 -49
- package/lib/gateway/httpsig.mjs +0 -159
- package/lib/gateway/notices.mjs +0 -84
- package/lib/gateway/quiet.mjs +0 -189
- package/lib/pod/README.md +0 -61
- package/lib/pod/actor.mjs +0 -96
- package/lib/pod/collection.mjs +0 -77
- package/lib/pod/containers.mjs +0 -140
- package/lib/pod/discovery.mjs +0 -46
- package/lib/pod/featured.mjs +0 -18
- package/lib/pod/followers.mjs +0 -24
- package/lib/pod/following.mjs +0 -10
- package/lib/pod/http.mjs +0 -74
- package/lib/pod/inbox.mjs +0 -166
- package/lib/pod/links.mjs +0 -35
- package/lib/pod/media.mjs +0 -27
- package/lib/pod/notes.mjs +0 -80
- package/lib/pod/notifications.mjs +0 -89
- package/lib/pod/outbox.mjs +0 -18
- package/lib/pod/package.json +0 -19
- package/lib/pod/policy.mjs +0 -58
- package/lib/pod/private.mjs +0 -19
- package/lib/pod/root.mjs +0 -104
- package/lib/pod/state.mjs +0 -40
- package/lib/pod/transport.mjs +0 -525
- package/lib/pod/urls.mjs +0 -85
- package/lib/server/embed.mjs +0 -423
- package/lib/session/README.md +0 -137
- package/lib/session/demo.html +0 -94
- package/lib/session/fedi-account.mjs +0 -408
- package/lib/session/fedi-login.mjs +0 -123
- package/lib/session/oidc-session.mjs +0 -233
- package/lib/session/package.json +0 -49
- package/lib/shared/guard.mjs +0 -213
- package/lib/shared/links.mjs +0 -9
- package/lib/shared/safefetch.mjs +0 -235
- package/lib/shared/ua.mjs +0 -29
- package/phanpy/dist/404.html +0 -40
- package/phanpy/dist/_headers +0 -2
- package/phanpy/dist/apple-touch-icon.png +0 -0
- package/phanpy/dist/assets/ICONS-DIPJeU0C.js +0 -2
- package/phanpy/dist/assets/ICONS-DIPJeU0C.js.map +0 -1
- package/phanpy/dist/assets/ScheduledAtField-D-Abfo4J.js +0 -183
- package/phanpy/dist/assets/ScheduledAtField-D-Abfo4J.js.map +0 -1
- package/phanpy/dist/assets/Temml-CCjoacWt.woff2 +0 -0
- package/phanpy/dist/assets/__vite-browser-external-C7iut881.js +0 -2
- package/phanpy/dist/assets/__vite-browser-external-C7iut881.js.map +0 -1
- package/phanpy/dist/assets/boosts-carousel-BiOaNdMT.jpg +0 -0
- package/phanpy/dist/assets/catch-up-O5q5C75Z.png +0 -0
- package/phanpy/dist/assets/chunk-aKtaBQYM.js +0 -1
- package/phanpy/dist/assets/compose-CFEx7wb4.js +0 -2
- package/phanpy/dist/assets/compose-CFEx7wb4.js.map +0 -1
- package/phanpy/dist/assets/compose-ZJN5xSb4.js +0 -35
- package/phanpy/dist/assets/compose-ZJN5xSb4.js.map +0 -1
- package/phanpy/dist/assets/debug.module-YFQgwnIr.js +0 -52
- package/phanpy/dist/assets/debug.module-YFQgwnIr.js.map +0 -1
- package/phanpy/dist/assets/dom-p1YcJ5Rw.js +0 -2
- package/phanpy/dist/assets/dom-p1YcJ5Rw.js.map +0 -1
- package/phanpy/dist/assets/home-mobile-dark@2x-Dc-J8-SM.png +0 -0
- package/phanpy/dist/assets/home-mobile-light@2x-Bd3fod8m.png +0 -0
- package/phanpy/dist/assets/home-tablet-dark@2x-BgcNI8le.png +0 -0
- package/phanpy/dist/assets/home-tablet-light@2x-BW2RV9T6.png +0 -0
- package/phanpy/dist/assets/icons/add-circle-line-WD0mwORD.js +0 -1
- package/phanpy/dist/assets/icons/alert-line-CpFUMf9s.js +0 -1
- package/phanpy/dist/assets/icons/android-2-line-Bzxnrd3S.js +0 -1
- package/phanpy/dist/assets/icons/announcement-line-5nwaJFJm.js +0 -1
- package/phanpy/dist/assets/icons/arrow-down-circle-line-a7Id9-Iv.js +0 -1
- package/phanpy/dist/assets/icons/arrow-down-line-B8uzXzq6.js +0 -1
- package/phanpy/dist/assets/icons/arrow-left-line-CGrGT9C-.js +0 -1
- package/phanpy/dist/assets/icons/arrow-right-line-CE-HERfX.js +0 -1
- package/phanpy/dist/assets/icons/arrow-to-up-line-CvdQjmcy.js +0 -1
- package/phanpy/dist/assets/icons/arrow-up-circle-line-DEparSNF.js +0 -1
- package/phanpy/dist/assets/icons/arrow-up-line-C7yBeugc.js +0 -1
- package/phanpy/dist/assets/icons/arrows-right-line-BtbXmJGP.js +0 -1
- package/phanpy/dist/assets/icons/at-line-CF66Bznk.js +0 -1
- package/phanpy/dist/assets/icons/attachment-line-BY8It-l9.js +0 -1
- package/phanpy/dist/assets/icons/blockquote-line-BS6pU_hm.js +0 -1
- package/phanpy/dist/assets/icons/board-line-C0UWXyA6.js +0 -1
- package/phanpy/dist/assets/icons/bookmark-line-1CtX5JLF.js +0 -1
- package/phanpy/dist/assets/icons/building-5-line-Dd59MzbO.js +0 -1
- package/phanpy/dist/assets/icons/bus-2-line-6_dvA0GG.js +0 -1
- package/phanpy/dist/assets/icons/calendar-day-line-Crpl_fmu.js +0 -1
- package/phanpy/dist/assets/icons/calendar-month-line-vommts2C.js +0 -1
- package/phanpy/dist/assets/icons/calendar-time-add-line-BU_DTlQk.js +0 -1
- package/phanpy/dist/assets/icons/camera-line-Cv69MEei.js +0 -1
- package/phanpy/dist/assets/icons/celebrate-line-B8DvIoi0.js +0 -1
- package/phanpy/dist/assets/icons/chart-bar-line-FK-QOmtF.js +0 -1
- package/phanpy/dist/assets/icons/chart-line-line-BGcJAFWw.js +0 -1
- package/phanpy/dist/assets/icons/chat-3-line-YFK2-hwz.js +0 -1
- package/phanpy/dist/assets/icons/check-circle-line-DsAxFr5-.js +0 -1
- package/phanpy/dist/assets/icons/clipboard-line-BBp364gJ.js +0 -1
- package/phanpy/dist/assets/icons/close-circle-line-CICdiuGz.js +0 -1
- package/phanpy/dist/assets/icons/close-line-DYrFWcjv.js +0 -1
- package/phanpy/dist/assets/icons/cloud-line-tiQAbah8.js +0 -1
- package/phanpy/dist/assets/icons/code-line-fAl8iiV3.js +0 -1
- package/phanpy/dist/assets/icons/comment-2-line-CR3g7WeB.js +0 -1
- package/phanpy/dist/assets/icons/copy-2-line-CLW69yF5.js +0 -1
- package/phanpy/dist/assets/icons/delete-2-line-B8xmbr4n.js +0 -1
- package/phanpy/dist/assets/icons/document-3-line-DBGNhAg2.js +0 -1
- package/phanpy/dist/assets/icons/document-line-C7mb9sb7.js +0 -1
- package/phanpy/dist/assets/icons/down-line-BGqSSThd.js +0 -1
- package/phanpy/dist/assets/icons/edit-4-line-DcgGaEhx.js +0 -1
- package/phanpy/dist/assets/icons/emoji-2-line-D8YgnM-y.js +0 -1
- package/phanpy/dist/assets/icons/exit-line-Dc07BS-x.js +0 -1
- package/phanpy/dist/assets/icons/external-link-line-BvpSkmjO.js +0 -1
- package/phanpy/dist/assets/icons/eye-2-line-CiWXx-f_.js +0 -1
- package/phanpy/dist/assets/icons/eye-close-line-BgTaSJ4P.js +0 -1
- package/phanpy/dist/assets/icons/filter-2-line-B_mR2usy.js +0 -1
- package/phanpy/dist/assets/icons/filter-line-C_nm-3cU.js +0 -1
- package/phanpy/dist/assets/icons/flag-1-line-C2HB05I0.js +0 -1
- package/phanpy/dist/assets/icons/forbid-circle-line-D3LTT9yd.js +0 -1
- package/phanpy/dist/assets/icons/formula-line-DLrt2d6j.js +0 -1
- package/phanpy/dist/assets/icons/grid-line-CgUmEikf.js +0 -1
- package/phanpy/dist/assets/icons/group-line-C5fnX0wU.js +0 -1
- package/phanpy/dist/assets/icons/hand-finger-2-line-BGgxbuL9.js +0 -1
- package/phanpy/dist/assets/icons/hashtag-line-CyTEEXBb.js +0 -1
- package/phanpy/dist/assets/icons/heart-crack-line-BZddbO2C.js +0 -1
- package/phanpy/dist/assets/icons/heart-line-CJ7K47J4.js +0 -1
- package/phanpy/dist/assets/icons/history-2-line-kWWvFzqv.js +0 -1
- package/phanpy/dist/assets/icons/history-line-bGQiwbTH.js +0 -1
- package/phanpy/dist/assets/icons/home-3-line-fRU1zeLG.js +0 -1
- package/phanpy/dist/assets/icons/information-line-iNk6-sOY.js +0 -1
- package/phanpy/dist/assets/icons/keyboard-line-DsCyuQNz.js +0 -1
- package/phanpy/dist/assets/icons/layout-4-line-CZkOX52Z.js +0 -1
- package/phanpy/dist/assets/icons/layout-5-line-B_znt5wO.js +0 -1
- package/phanpy/dist/assets/icons/left-line-eUw0tya4.js +0 -1
- package/phanpy/dist/assets/icons/lightning-line-DaKoDdix.js +0 -1
- package/phanpy/dist/assets/icons/link-2-line-DCqLmMA7.js +0 -1
- package/phanpy/dist/assets/icons/list-check-line-BAxosBh9.js +0 -1
- package/phanpy/dist/assets/icons/lock-line-CPoaXeUW.js +0 -1
- package/phanpy/dist/assets/icons/mail-line-B0P8aa1L.js +0 -1
- package/phanpy/dist/assets/icons/moon-line-CEqNAToP.js +0 -1
- package/phanpy/dist/assets/icons/more-1-fill-Dw2e5xab.js +0 -1
- package/phanpy/dist/assets/icons/more-3-line-BIiVjYTz.js +0 -1
- package/phanpy/dist/assets/icons/notification-line-BZZq2Gtu.js +0 -1
- package/phanpy/dist/assets/icons/pencil-line-CKh8-A1v.js +0 -1
- package/phanpy/dist/assets/icons/photo-album-line-Bnfg2u40.js +0 -1
- package/phanpy/dist/assets/icons/pin-line-DpTolyhs.js +0 -1
- package/phanpy/dist/assets/icons/play-fill-B0-G_8lX.js +0 -1
- package/phanpy/dist/assets/icons/qrcode-2-line-B6SPCaHn.js +0 -1
- package/phanpy/dist/assets/icons/quill-pen-line-B6LUrnCP.js +0 -1
- package/phanpy/dist/assets/icons/quote-left-fill-hQZ1cj_6.js +0 -1
- package/phanpy/dist/assets/icons/radar-line-CKyroTth.js +0 -1
- package/phanpy/dist/assets/icons/react-line-CoIKhhtK.js +0 -1
- package/phanpy/dist/assets/icons/refresh-2-line-D0mTF97S.js +0 -1
- package/phanpy/dist/assets/icons/right-line-B3KWJaqc.js +0 -1
- package/phanpy/dist/assets/icons/rocket-line-4KZl537D.js +0 -1
- package/phanpy/dist/assets/icons/round-fill-C69NTrZ0.js +0 -1
- package/phanpy/dist/assets/icons/round-line-B9k_1uo-.js +0 -1
- package/phanpy/dist/assets/icons/route-line-B28ReHcE.js +0 -1
- package/phanpy/dist/assets/icons/rows-4-line-CrBniagM.js +0 -1
- package/phanpy/dist/assets/icons/scan-line-DSf74YdP.js +0 -1
- package/phanpy/dist/assets/icons/search-2-line-B6geIU1z.js +0 -1
- package/phanpy/dist/assets/icons/settings-3-line-azJwK47w.js +0 -1
- package/phanpy/dist/assets/icons/settings-6-line-C_5zc5YR.js +0 -1
- package/phanpy/dist/assets/icons/share-2-line-D5MxWjrm.js +0 -1
- package/phanpy/dist/assets/icons/share-forward-line-Cp61fZzZ.js +0 -1
- package/phanpy/dist/assets/icons/sparkles-2-line-DYYyXTaW.js +0 -1
- package/phanpy/dist/assets/icons/sparkles-line-C2hY1s6I.js +0 -1
- package/phanpy/dist/assets/icons/time-line-BF3eet1v.js +0 -1
- package/phanpy/dist/assets/icons/transfer-4-line-oisfvTBw.js +0 -1
- package/phanpy/dist/assets/icons/translate-line-BFWol2Ce.js +0 -1
- package/phanpy/dist/assets/icons/unlock-line-X_x5vwv3.js +0 -1
- package/phanpy/dist/assets/icons/upload-3-line-DgzwUJeW.js +0 -1
- package/phanpy/dist/assets/icons/user-4-line-BVSynZCp.js +0 -1
- package/phanpy/dist/assets/icons/user-add-2-line-BlGS5Q8q.js +0 -1
- package/phanpy/dist/assets/icons/user-add-line-BbXdHG4z.js +0 -1
- package/phanpy/dist/assets/icons/user-edit-line-BFAHOV0X.js +0 -1
- package/phanpy/dist/assets/icons/user-follow-line-BXux0wSd.js +0 -1
- package/phanpy/dist/assets/icons/user-star-line-DdYlpHLU.js +0 -1
- package/phanpy/dist/assets/icons/user-warning-line-Cj43j0_E.js +0 -1
- package/phanpy/dist/assets/icons/user-x-line-DIDgNlPH.js +0 -1
- package/phanpy/dist/assets/icons/volume-line-Bs390KQt.js +0 -1
- package/phanpy/dist/assets/icons/volume-mute-line-DzlzIqFZ.js +0 -1
- package/phanpy/dist/assets/icons/walk-line-yNXfB-md.js +0 -1
- package/phanpy/dist/assets/icons/world-2-line-CenkGIwj.js +0 -1
- package/phanpy/dist/assets/icons/zoom-in-line-CaENUphf.js +0 -1
- package/phanpy/dist/assets/icons/zoom-out-line-BDNk0EWo.js +0 -1
- package/phanpy/dist/assets/instances-BNPptnn-.json +0 -333
- package/phanpy/dist/assets/locales/ar-SA-CfheRbCu.js +0 -1
- package/phanpy/dist/assets/locales/ca-ES-DHRW86Tz.js +0 -1
- package/phanpy/dist/assets/locales/cs-CZ-CKoqEuA1.js +0 -1
- package/phanpy/dist/assets/locales/de-DE-ByRPMRFy.js +0 -1
- package/phanpy/dist/assets/locales/eo-UY-CPC7wLCS.js +0 -1
- package/phanpy/dist/assets/locales/es-ES-dJa0Fd9t.js +0 -1
- package/phanpy/dist/assets/locales/eu-ES-C3djlPR2.js +0 -1
- package/phanpy/dist/assets/locales/fa-IR-DTT490Ck.js +0 -1
- package/phanpy/dist/assets/locales/fi-FI-B23MCPuz.js +0 -1
- package/phanpy/dist/assets/locales/fr-FR-BcaQsO5C.js +0 -1
- package/phanpy/dist/assets/locales/gl-ES-BR-CVKBN.js +0 -1
- package/phanpy/dist/assets/locales/he-IL-CfheRbCu.js +0 -1
- package/phanpy/dist/assets/locales/hu-HU-CfheRbCu.js +0 -1
- package/phanpy/dist/assets/locales/it-IT-C1136UFV.js +0 -1
- package/phanpy/dist/assets/locales/ja-JP-CRZmRAhD.js +0 -1
- package/phanpy/dist/assets/locales/kab-Cz8xH_TT.js +0 -1
- package/phanpy/dist/assets/locales/ko-KR-CG9UiY4o.js +0 -1
- package/phanpy/dist/assets/locales/lt-LT-CJcI6sHZ.js +0 -1
- package/phanpy/dist/assets/locales/nb-NO-CfheRbCu.js +0 -1
- package/phanpy/dist/assets/locales/nl-NL-DxN4Yc1t.js +0 -1
- package/phanpy/dist/assets/locales/oc-FR-CfheRbCu.js +0 -1
- package/phanpy/dist/assets/locales/pl-PL-DkwGXf6P.js +0 -1
- package/phanpy/dist/assets/locales/pseudo-LOCALE-CQ4XxkQ0.js +0 -1
- package/phanpy/dist/assets/locales/pt-BR-nGL0HdFS.js +0 -1
- package/phanpy/dist/assets/locales/pt-PT-Cp17aj2z.js +0 -1
- package/phanpy/dist/assets/locales/ru-RU-0Y8cIoaK.js +0 -1
- package/phanpy/dist/assets/locales/th-TH-CfheRbCu.js +0 -1
- package/phanpy/dist/assets/locales/tok-CfheRbCu.js +0 -1
- package/phanpy/dist/assets/locales/tr-TR-CfheRbCu.js +0 -1
- package/phanpy/dist/assets/locales/uk-UA-C5E3dFBf.js +0 -1
- package/phanpy/dist/assets/locales/zh-CN-BHJi7_et.js +0 -1
- package/phanpy/dist/assets/locales/zh-TW-CfheRbCu.js +0 -1
- package/phanpy/dist/assets/main-BdqNbG-a.js +0 -26
- package/phanpy/dist/assets/main-BdqNbG-a.js.map +0 -1
- package/phanpy/dist/assets/mock-home-DGhR__He.js +0 -2
- package/phanpy/dist/assets/mock-home-DGhR__He.js.map +0 -1
- package/phanpy/dist/assets/multi-column-ROck0NVt.jpg +0 -0
- package/phanpy/dist/assets/multi-hashtag-timeline-DH5INVHi.jpg +0 -0
- package/phanpy/dist/assets/nested-comments-thread-hv59kZLc.jpg +0 -0
- package/phanpy/dist/assets/polyfill-force-H8sUkLGp.js +0 -2
- package/phanpy/dist/assets/polyfill-force-H8sUkLGp.js.map +0 -1
- package/phanpy/dist/assets/pwa-viewport-ogKbF51K.js +0 -20
- package/phanpy/dist/assets/pwa-viewport-ogKbF51K.js.map +0 -1
- package/phanpy/dist/assets/style-B1iLPTPR.css +0 -2
- package/phanpy/dist/assets/temml-DuyIQMei.js +0 -97
- package/phanpy/dist/assets/temml-DuyIQMei.js.map +0 -1
- package/phanpy/dist/assets/tinyld.light.node-DtbBhLYC.js +0 -2
- package/phanpy/dist/assets/tinyld.light.node-DtbBhLYC.js.map +0 -1
- package/phanpy/dist/assets/year-in-posts-B2lywqdx.png +0 -0
- package/phanpy/dist/assets/year-in-posts-DJv_star.js +0 -3
- package/phanpy/dist/assets/year-in-posts-DJv_star.js.map +0 -1
- package/phanpy/dist/compose/index.html +0 -20
- package/phanpy/dist/favicon.ico +0 -0
- package/phanpy/dist/index.html +0 -73
- package/phanpy/dist/logo-192.png +0 -0
- package/phanpy/dist/logo-512.png +0 -0
- package/phanpy/dist/logo-badge-72.png +0 -0
- package/phanpy/dist/logo-maskable-512.png +0 -0
- package/phanpy/dist/logo-monochrome-512.png +0 -0
- package/phanpy/dist/logo-monochrome-maskable-512.png +0 -0
- package/phanpy/dist/manifest.webmanifest +0 -1
- package/phanpy/dist/og-image-2.jpg +0 -0
- package/phanpy/dist/og-image.png +0 -0
- package/phanpy/dist/robots.txt +0 -2
- package/phanpy/dist/sw.js +0 -2
- package/phanpy/dist/sw.js.map +0 -1
- package/phanpy/dist/version.json +0 -1
- package/run-agent.mjs +0 -897
- package/vendor/gate.cjs +0 -138
- package/vendor/idp-grant.cjs +0 -394
- package/vendor/jfetch.cjs +0 -16
- package/web/admin/actors.js +0 -145
- package/web/admin/bar.css +0 -126
- package/web/admin/bar.js +0 -99
- package/web/admin/client/client.js +0 -66
- package/web/admin/client/index.html +0 -69
- package/web/admin/client-pick.js +0 -79
- package/web/admin/common.js +0 -23
- package/web/admin/connections.js +0 -112
- package/web/admin/gateway.js +0 -133
- package/web/admin/group.js +0 -258
- package/web/admin/index.html +0 -603
- package/web/admin/notices-bar.js +0 -90
- package/web/admin/oauth-signin.mjs +0 -37
- package/web/admin/record.js +0 -390
- package/web/admin/setup/index.html +0 -241
- package/web/admin/setup/setup.js +0 -326
- package/web/admin/tokens.css +0 -68
- package/web/admin/upkeep.js +0 -177
- package/web/admin/window.css +0 -55
- package/web/admin/window.js +0 -136
- package/web/app/README.md +0 -81
- package/web/app/admin-facade.mjs +0 -588
- package/web/app/agent.mjs +0 -663
- package/web/app/atproto-browser.mjs +0 -86
- package/web/app/authorize_interaction.html +0 -56
- package/web/app/authorize_interaction.js +0 -69
- package/web/app/boot.mjs +0 -576
- package/web/app/deliver-relay.mjs +0 -129
- package/web/app/dist/boot.js +0 -34218
- package/web/app/dist/boot.js.map +0 -7
- package/web/app/dist/sw.js +0 -72810
- package/web/app/dist/sw.js.map +0 -7
- package/web/app/fediacct-browser.mjs +0 -146
- package/web/app/gateway-move.mjs +0 -60
- package/web/app/idb-kv.mjs +0 -35
- package/web/app/index.html +0 -242
- package/web/app/keys-browser.mjs +0 -71
- package/web/app/keystore.mjs +0 -82
- package/web/app/oidc-session.mjs +0 -6
- package/web/app/pod-auth.mjs +0 -248
- package/web/app/pod-remote.mjs +0 -84
- package/web/app/shims/_globals.mjs +0 -2
- package/web/app/shims/fedify-sig.mjs +0 -77
- package/web/app/shims/node-crypto.mjs +0 -113
- package/web/app/shims/node-fs.mjs +0 -17
- package/web/app/shims/node-path.mjs +0 -19
- package/web/app/shims/node-url.mjs +0 -7
- package/web/app/shims/prelude.js +0 -58
- package/web/app/shims/safefetch.mjs +0 -58
- package/web/app/shims/shapes-text.mjs +0 -8
- package/web/app/shims/web-push.mjs +0 -14
- package/web/app/signup.mjs +0 -318
- package/web/app/sw-src.mjs +0 -220
- package/web/app/update.js +0 -70
- package/web/front/admin.html +0 -55
- package/web/front/admin.js +0 -89
- package/web/front/install.sh +0 -69
- package/web/front/new-account.html +0 -35
- package/web/front/new-account.js +0 -13
- package/web/front/notices.html +0 -69
- package/web/front/notices.js +0 -107
- package/web/front/run.html +0 -58
- package/web/front/run.js +0 -195
- package/web/front/solid-oidc-client.js +0 -6
- package/web/icons/forum.svg +0 -11
- package/web/icons/gateway.svg +0 -14
- package/web/icons/local.svg +0 -11
package/lib/client/c2s.mjs
DELETED
|
@@ -1,582 +0,0 @@
|
|
|
1
|
-
// c2s.mjs — ActivityPub client-to-server (§6): the spec's own write API,
|
|
2
|
-
// beside the Mastodon facade. A client POSTs an activity — or a bare object,
|
|
3
|
-
// which is wrapped in a Create (§6.2.1) — to this actor's outbox; the agent
|
|
4
|
-
// assigns ids, applies the side-effects and delivers. Every verb lands on the
|
|
5
|
-
// SAME helper the facade and admin surfaces use — this module re-implements
|
|
6
|
-
// no persistence and no delivery, so one write path stays one.
|
|
7
|
-
//
|
|
8
|
-
// GETs on the actor and outbox are redirects to the pod's documents wherever
|
|
9
|
-
// those are reachable, which is the Server. Standalone they are not: the actor
|
|
10
|
-
// published there names no address a local client could write to, and on a
|
|
11
|
-
// fronted account the published outbox address is the Gateway's write door,
|
|
12
|
-
// which does not answer a read. So on that build both are served here, to the
|
|
13
|
-
// client already talking to this agent.
|
|
14
|
-
//
|
|
15
|
-
// `dispatch` takes an activity and answers with { status, body, headers }; it
|
|
16
|
-
// touches no request or response. The HTTP handler below is one caller. The
|
|
17
|
-
// other is the inbox drain, handing over an activity the Gateway took at the
|
|
18
|
-
// outbox door on the owner's behalf and stamped as theirs.
|
|
19
|
-
//
|
|
20
|
-
// The inbox is the exception, and has to be. Deliveries land in a container on
|
|
21
|
-
// the pod which the drain empties as it handles each item, so reading that
|
|
22
|
-
// container tells the owner only what has not been dealt with yet. What was
|
|
23
|
-
// actually received is whole only in the archive, so §5.2's "the owner can
|
|
24
|
-
// read their own inbox" is served from there, by this agent, to the owner
|
|
25
|
-
// alone.
|
|
26
|
-
|
|
27
|
-
import * as social from '../core/social.mjs';
|
|
28
|
-
import * as wire from '../core/wire.mjs';
|
|
29
|
-
import { readLenient } from '../core/as2.mjs';
|
|
30
|
-
import { safeSlug } from '../core/publisher/notes.mjs';
|
|
31
|
-
|
|
32
|
-
const MAX_BODY = 512 * 1024; // same ceiling the inbox drain enforces
|
|
33
|
-
|
|
34
|
-
// How many archived items one page of the inbox will read. A page is one
|
|
35
|
-
// month, and a month with more than this is served short rather than costing
|
|
36
|
-
// the pod an unbounded read; the log says when that happened.
|
|
37
|
-
const MAX_INBOX_PAGE = 500;
|
|
38
|
-
|
|
39
|
-
// How many recorded activities one page of the locally-served outbox carries.
|
|
40
|
-
const MAX_OUTBOX_PAGE = 50;
|
|
41
|
-
|
|
42
|
-
// §6 names activities; anything else with a type is an object to wrap.
|
|
43
|
-
const ACTIVITY_TYPES = new Set([
|
|
44
|
-
'Create', 'Update', 'Delete', 'Follow', 'Like', 'Announce', 'Undo',
|
|
45
|
-
'Block', 'Add', 'Remove', 'Accept', 'Reject', 'Move',
|
|
46
|
-
]);
|
|
47
|
-
|
|
48
|
-
const arr = (v) => (v === undefined || v === null ? [] : Array.isArray(v) ? v : [v]);
|
|
49
|
-
const idOf = (v) => (typeof v === 'string' ? v : v?.id || null);
|
|
50
|
-
|
|
51
|
-
function readBody(req) {
|
|
52
|
-
return new Promise((resolve, reject) => {
|
|
53
|
-
let size = 0;
|
|
54
|
-
const chunks = [];
|
|
55
|
-
req.on('data', (c) => {
|
|
56
|
-
size += c.length;
|
|
57
|
-
if (size > MAX_BODY) { reject(new Error('body too large')); req.destroy(); return; }
|
|
58
|
-
chunks.push(c);
|
|
59
|
-
});
|
|
60
|
-
req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
|
|
61
|
-
req.on('error', reject);
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export class C2S {
|
|
66
|
-
constructor({ agent, log = console.log, auth, scheme = null, mount = '' }) {
|
|
67
|
-
this.agent = agent;
|
|
68
|
-
this.log = log;
|
|
69
|
-
this.auth = auth;
|
|
70
|
-
// What this agent calls itself, for the actor it serves a local client.
|
|
71
|
-
// The same two values oidc-auth rebuilds the DPoP `htu` from, so the two
|
|
72
|
-
// surfaces agree on the address a client just used.
|
|
73
|
-
this.scheme = scheme;
|
|
74
|
-
this.mount = mount;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
get store() { return this.agent.store; }
|
|
78
|
-
get urls() { return this.agent.publisher?.urls; }
|
|
79
|
-
|
|
80
|
-
send(res, status, obj, headers = {}) {
|
|
81
|
-
res.writeHead(status, {
|
|
82
|
-
'content-type': 'application/json; charset=utf-8',
|
|
83
|
-
'cache-control': 'no-store',
|
|
84
|
-
...headers,
|
|
85
|
-
});
|
|
86
|
-
res.end(JSON.stringify(obj));
|
|
87
|
-
return true;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
byIri(iri) {
|
|
91
|
-
return iri ? this.store.getStatuses().find((s) => s.noteId === iri) : null;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/** The months the archive holds, newest first. One container listing. */
|
|
95
|
-
async archiveMonths() {
|
|
96
|
-
const archive = this.agent.intake?.archive;
|
|
97
|
-
if (!archive) return [];
|
|
98
|
-
const { names } = await archive.list('');
|
|
99
|
-
return (names || [])
|
|
100
|
-
.map((n) => n.replace(/\/$/u, ''))
|
|
101
|
-
.filter((n) => /^\d{4}-\d{2}$/u.test(n))
|
|
102
|
-
.sort()
|
|
103
|
-
.reverse();
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* The owner's own inbox, §5.2. Paged by month because that is how the
|
|
108
|
-
* archive is stored, so a page costs one listing and a read per item and no
|
|
109
|
-
* page is dearer for another month being large.
|
|
110
|
-
*/
|
|
111
|
-
async sendInbox(res, url) {
|
|
112
|
-
const id = `${this.urls.base}ap/inbox`;
|
|
113
|
-
const page = url?.searchParams?.get('page') || null;
|
|
114
|
-
let months;
|
|
115
|
-
try {
|
|
116
|
-
months = await this.archiveMonths();
|
|
117
|
-
} catch (e) {
|
|
118
|
-
return this.send(res, 502, { error: `the archive could not be read: ${e.message}` });
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if (!page) {
|
|
122
|
-
if (!months.length && this.store.getConfig()?.archiveInbox === false) {
|
|
123
|
-
this.log('inbox read: nothing to show — this identity does not keep what it receives');
|
|
124
|
-
}
|
|
125
|
-
return this.send(res, 200, {
|
|
126
|
-
'@context': wire.AS_CTX, id, type: 'OrderedCollection',
|
|
127
|
-
...(months.length ? { first: `${id}?page=${months[0]}` } : { orderedItems: [] }),
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
if (!/^\d{4}-\d{2}$/u.test(page)) {
|
|
131
|
-
return this.send(res, 400, { error: 'page names a month, written 2026-09' });
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const archive = this.agent.intake?.archive;
|
|
135
|
-
let names = [];
|
|
136
|
-
try {
|
|
137
|
-
// The trailing slash matters: without it this names a document, not the
|
|
138
|
-
// container, and a pod answers about the wrong thing.
|
|
139
|
-
({ names } = await archive.list(`${page}/`));
|
|
140
|
-
} catch (e) {
|
|
141
|
-
return this.send(res, 502, { error: `the archive could not be read: ${e.message}` });
|
|
142
|
-
}
|
|
143
|
-
const files = (names || []).filter((n) => n.endsWith('.json')).sort();
|
|
144
|
-
if (files.length > MAX_INBOX_PAGE) {
|
|
145
|
-
this.log(`inbox read: ${page} holds ${files.length} items; serving the first ${MAX_INBOX_PAGE}`);
|
|
146
|
-
}
|
|
147
|
-
const kept = [];
|
|
148
|
-
for (const file of files.slice(0, MAX_INBOX_PAGE)) {
|
|
149
|
-
// Read as written: these records are JSON-LD, and the default read asks
|
|
150
|
-
// turtle-first, which a server is free to answer with turtle.
|
|
151
|
-
const read = await archive.read(`${page}/${file}`, { accept: '*/*' });
|
|
152
|
-
if (!read?.ok || !read.body) continue;
|
|
153
|
-
try {
|
|
154
|
-
const record = JSON.parse(read.body);
|
|
155
|
-
// The record wraps the bytes as they arrived; the activity is those
|
|
156
|
-
// bytes, not a retelling of them.
|
|
157
|
-
kept.push({ at: record.receivedAt || '', activity: JSON.parse(record.raw) });
|
|
158
|
-
} catch { /* a record that will not parse is not one that can be served */ }
|
|
159
|
-
}
|
|
160
|
-
kept.sort((a, b) => String(b.at).localeCompare(String(a.at)));
|
|
161
|
-
const older = months.filter((m) => m < page)[0] || null;
|
|
162
|
-
return this.send(res, 200, {
|
|
163
|
-
'@context': wire.AS_CTX,
|
|
164
|
-
id: `${id}?page=${page}`,
|
|
165
|
-
type: 'OrderedCollectionPage',
|
|
166
|
-
partOf: id,
|
|
167
|
-
...(older ? { next: `${id}?page=${older}` } : {}),
|
|
168
|
-
orderedItems: kept.map((k) => k.activity),
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// The actor a client-to-server client on this machine reads. The same
|
|
173
|
-
// document the pod holds, but for the fields naming where a write goes —
|
|
174
|
-
// the outbox and the two OAuth endpoints — and for `id`, which names this
|
|
175
|
-
// address so the client files its credential here and comes back. The pod's
|
|
176
|
-
// id stays on as an alias; the two are one identity.
|
|
177
|
-
// What this agent calls itself, to the client that just asked. The Host
|
|
178
|
-
// passed the Authorities firewall before any route ran, so whichever alias
|
|
179
|
-
// the client used is one this agent answers on. The scheme is read off the
|
|
180
|
-
// connection unless the caller named one: only the Server passes a scheme,
|
|
181
|
-
// and standalone this listener is its own TLS.
|
|
182
|
-
localOrigin(req) {
|
|
183
|
-
const host = req.headers.host;
|
|
184
|
-
if (!host) return null;
|
|
185
|
-
const scheme = this.scheme
|
|
186
|
-
|| (req.socket?.encrypted || req.headers['x-forwarded-proto'] === 'https' ? 'https' : 'http');
|
|
187
|
-
return `${scheme}://${host}${this.mount}/`;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* The owner's own outbox, from what this agent recorded. The pod holds the
|
|
192
|
-
* published copy and it stays the canonical one; this is served only where
|
|
193
|
-
* that copy cannot be read by the client asking.
|
|
194
|
-
*/
|
|
195
|
-
sendLocalOutbox(res, url, origin) {
|
|
196
|
-
const id = `${origin}ap/outbox`;
|
|
197
|
-
const outbox = this.store.read('outbox.json', []); // newest first
|
|
198
|
-
const page = url?.searchParams?.get('page') || null;
|
|
199
|
-
const ct = { 'content-type': 'application/activity+json; charset=utf-8' };
|
|
200
|
-
if (!page) {
|
|
201
|
-
return this.send(res, 200, {
|
|
202
|
-
'@context': wire.AS_CTX, id, type: 'OrderedCollection', totalItems: outbox.length,
|
|
203
|
-
...(outbox.length ? { first: `${id}?page=1` } : { orderedItems: [] }),
|
|
204
|
-
}, ct);
|
|
205
|
-
}
|
|
206
|
-
const n = Number(page);
|
|
207
|
-
if (!Number.isInteger(n) || n < 1) {
|
|
208
|
-
return this.send(res, 400, { error: 'page is a whole number from 1' });
|
|
209
|
-
}
|
|
210
|
-
const start = (n - 1) * MAX_OUTBOX_PAGE;
|
|
211
|
-
const items = outbox.slice(start, start + MAX_OUTBOX_PAGE);
|
|
212
|
-
return this.send(res, 200, {
|
|
213
|
-
'@context': wire.AS_CTX,
|
|
214
|
-
id: `${id}?page=${n}`,
|
|
215
|
-
type: 'OrderedCollectionPage',
|
|
216
|
-
partOf: id,
|
|
217
|
-
...(start + items.length < outbox.length ? { next: `${id}?page=${n + 1}` } : {}),
|
|
218
|
-
// A post is recorded by its object id and published as the Create beside
|
|
219
|
-
// it — the same mapping the pod's copy uses, from the same helper.
|
|
220
|
-
orderedItems: items.map(wire.outboxWireItem),
|
|
221
|
-
}, ct);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
sendLocalActor(req, res, origin) {
|
|
225
|
-
const pub = this.agent.publisher;
|
|
226
|
-
const cfg = pub.config || {};
|
|
227
|
-
const gw = cfg.gateway;
|
|
228
|
-
const gwActive = gw && gw.url && gw.mode && gw.mode !== 'off';
|
|
229
|
-
const doc = pub.actorDocFor({
|
|
230
|
-
inbox: gwActive ? gw.url : (cfg.inboxUrl || null),
|
|
231
|
-
clientOrigin: origin,
|
|
232
|
-
});
|
|
233
|
-
// A client files its credential under the id it reads here and comes back
|
|
234
|
-
// to that address for everything after, so this document has to name
|
|
235
|
-
// itself. The pod's id stays on as an alias rather than being dropped —
|
|
236
|
-
// the two are one identity. Nothing published ever carries this id: every
|
|
237
|
-
// activity the dispatcher builds names the pod's actor.
|
|
238
|
-
const canonical = doc.id;
|
|
239
|
-
doc.id = `${origin}ap/actor`;
|
|
240
|
-
doc.alsoKnownAs = [...new Set([...(doc.alsoKnownAs || []), canonical])];
|
|
241
|
-
const body = JSON.stringify(doc);
|
|
242
|
-
res.writeHead(200, {
|
|
243
|
-
'content-type': 'application/activity+json; charset=utf-8',
|
|
244
|
-
'cache-control': 'no-store',
|
|
245
|
-
'content-length': Buffer.byteLength(body),
|
|
246
|
-
});
|
|
247
|
-
res.end(req.method === 'HEAD' ? undefined : body);
|
|
248
|
-
return true;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
async handle(req, res, pathname, url) { // eslint-disable-line no-unused-vars
|
|
252
|
-
if (pathname !== '/ap/outbox' && pathname !== '/ap/actor' && pathname !== '/ap/inbox') return false;
|
|
253
|
-
if (req.method === 'OPTIONS') {
|
|
254
|
-
// Accept-Post is what a client such as dokieli reads to choose a format;
|
|
255
|
-
// naming JSON only is what makes it send JSON-LD rather than HTML.
|
|
256
|
-
res.writeHead(204, pathname === '/ap/inbox' ? { allow: 'GET, OPTIONS' }
|
|
257
|
-
: { allow: 'GET, POST, OPTIONS', 'accept-post': 'application/ld+json, application/activity+json' });
|
|
258
|
-
res.end(); return true;
|
|
259
|
-
}
|
|
260
|
-
if (!this.agent.configured() || !this.urls) {
|
|
261
|
-
return this.send(res, 409, { error: 'agent not configured' });
|
|
262
|
-
}
|
|
263
|
-
if (pathname === '/ap/inbox') {
|
|
264
|
-
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
265
|
-
return this.send(res, 405, { error: "deliveries go to this actor's inbox on the pod, which the "
|
|
266
|
-
+ 'actor document names; this address is the owner reading their own' });
|
|
267
|
-
}
|
|
268
|
-
const reader = await this.auth(req, pathname);
|
|
269
|
-
if (!reader.ok) return this.send(res, reader.status, { error: reader.error }, reader.headers || {});
|
|
270
|
-
return this.sendInbox(res, url);
|
|
271
|
-
}
|
|
272
|
-
if (req.method === 'GET' || req.method === 'HEAD') {
|
|
273
|
-
// Where the client surface answers on an address a stranger can reach,
|
|
274
|
-
// the pod's copies already name it and are the documents. Standalone
|
|
275
|
-
// they are not reachable by the client asking, so it is answered here.
|
|
276
|
-
const local = this.agent.publisher?.clientOrigin ? null : this.localOrigin(req);
|
|
277
|
-
if (local && pathname === '/ap/actor') return this.sendLocalActor(req, res, local);
|
|
278
|
-
if (local && pathname === '/ap/outbox') return this.sendLocalOutbox(res, url, local);
|
|
279
|
-
// The pod's copy is the document; send the reader there.
|
|
280
|
-
const target = pathname === '/ap/actor' ? this.urls.actor : this.urls.outbox;
|
|
281
|
-
res.writeHead(303, { location: target, 'cache-control': 'no-store' });
|
|
282
|
-
res.end();
|
|
283
|
-
return true;
|
|
284
|
-
}
|
|
285
|
-
if (pathname !== '/ap/outbox' || req.method !== 'POST') {
|
|
286
|
-
return this.send(res, 405, { error: 'POST the outbox; GET redirects to the pod' });
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
const who = await this.auth(req, pathname);
|
|
290
|
-
if (!who.ok) return this.send(res, who.status, { error: who.error }, who.headers || {});
|
|
291
|
-
|
|
292
|
-
// A viewer (another agent holds the lease) may not act — but the user
|
|
293
|
-
// acting HERE outranks an idle active agent elsewhere, exactly as on the
|
|
294
|
-
// facade: a write attempt claims the lease, and only a failed claim 503s.
|
|
295
|
-
if (this.agent.viewer) {
|
|
296
|
-
const took = await this.agent.requestTakeover?.();
|
|
297
|
-
if (!took) return this.send(res, 503, { error: 'another agent is active for this pod — takeover failed, try again' });
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
// Read as JSON-LD, so a client may send its activity with whatever context
|
|
301
|
-
// it likes and still be understood. What is read is the GRAPH: `dispatch`
|
|
302
|
-
// takes decisions from it and the publisher builds the document that is
|
|
303
|
-
// actually posted, so nothing a client sent is republished verbatim and a
|
|
304
|
-
// term it aliased still means what it says.
|
|
305
|
-
// `raw` is the document as the client wrote it. The graph is what
|
|
306
|
-
// decisions are taken from; the bytes are what an object that is stored
|
|
307
|
-
// as sent (publishObject) is stored from — a read against our contexts
|
|
308
|
-
// rewrites a stranger's terms to full IRIs, which is right for reading and
|
|
309
|
-
// wrong for keeping.
|
|
310
|
-
let activity; let raw = null;
|
|
311
|
-
try {
|
|
312
|
-
const body = await readBody(req);
|
|
313
|
-
try { raw = JSON.parse(body); } catch { raw = null; }
|
|
314
|
-
const read = await readLenient(body);
|
|
315
|
-
activity = read.view ?? read.doc;
|
|
316
|
-
} catch (e) {
|
|
317
|
-
return this.send(res, 400, { error: `unreadable body: ${e.message}` });
|
|
318
|
-
}
|
|
319
|
-
if (!activity || typeof activity !== 'object' || Array.isArray(activity) || !activity.type) {
|
|
320
|
-
return this.send(res, 400, { error: 'a typed ActivityStreams object is required' });
|
|
321
|
-
}
|
|
322
|
-
// The name the client asks for its new document (LDP's Slug), taken when
|
|
323
|
-
// it is plain and free — it is what lets the client know the address of
|
|
324
|
-
// what it made before anything answers.
|
|
325
|
-
const slug = safeSlug(req.headers.slug) || null;
|
|
326
|
-
const r = await this.dispatch(activity, { slug, raw });
|
|
327
|
-
return this.send(res, r.status, r.body, r.headers);
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// Addressing → the facade's four visibilities, inverting the table the
|
|
331
|
-
// composer writes (wire.addressing). Nothing stated is a public post — what
|
|
332
|
-
// every client means by a post with no audience chosen, and what a client
|
|
333
|
-
// that never addresses (dokieli's annotations) needs.
|
|
334
|
-
visibilityOf(activity, object) {
|
|
335
|
-
const to = arr(activity.to ?? object?.to).map(idOf);
|
|
336
|
-
const cc = arr(activity.cc ?? object?.cc).map(idOf);
|
|
337
|
-
if (!to.length && !cc.length) return 'public';
|
|
338
|
-
if (to.includes(wire.PUBLIC)) return 'public';
|
|
339
|
-
if (cc.includes(wire.PUBLIC)) return 'unlisted';
|
|
340
|
-
if (to.includes(this.urls.followers)) return 'private';
|
|
341
|
-
return 'direct';
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
async dispatch(activity, { slug = null, raw = null } = {}) {
|
|
345
|
-
const reply = (status, body, headers = {}) => ({ status, body, headers });
|
|
346
|
-
if (!activity || typeof activity !== 'object' || Array.isArray(activity) || !activity.type) {
|
|
347
|
-
return reply(400, { error: 'a typed ActivityStreams object is required' });
|
|
348
|
-
}
|
|
349
|
-
// A bare object arrives without an activity around it; the server supplies
|
|
350
|
-
// the Create (§6.2.1), carrying the object's own addressing up onto it.
|
|
351
|
-
if (!ACTIVITY_TYPES.has(activity.type)) {
|
|
352
|
-
activity = { type: 'Create', object: activity, to: activity.to, cc: activity.cc };
|
|
353
|
-
}
|
|
354
|
-
try {
|
|
355
|
-
return await this._dispatch(activity, { slug, raw, reply });
|
|
356
|
-
} catch (e) {
|
|
357
|
-
this.log(`c2s ${activity?.type}: ${e.message}`);
|
|
358
|
-
return reply(422, { error: e.message || String(e) });
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
// The actors a client addressed by id, beyond the audience words: the ones
|
|
363
|
-
// in to/cc are listed and delivered to; the ones in bto/bcc are delivered
|
|
364
|
-
// to and never listed (§6).
|
|
365
|
-
addressedActors(activity, object) {
|
|
366
|
-
const pick = (...fields) => [...new Set(fields.flatMap((f) => arr(activity[f] ?? object?.[f]).map(idOf)))]
|
|
367
|
-
.filter((a) => typeof a === 'string' && /^https?:\/\//u.test(a)
|
|
368
|
-
&& a !== wire.PUBLIC && a !== this.urls.followers && a !== this.urls.actor);
|
|
369
|
-
return { also: pick('to', 'cc'), deliverTo: pick('bto', 'bcc') };
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
async _dispatch(activity, { slug, raw, reply }) {
|
|
373
|
-
const agent = this.agent;
|
|
374
|
-
const object = typeof activity.object === 'object' && activity.object !== null
|
|
375
|
-
? activity.object : null;
|
|
376
|
-
const objectId = idOf(activity.object);
|
|
377
|
-
|
|
378
|
-
switch (activity.type) {
|
|
379
|
-
case 'Create': {
|
|
380
|
-
const makes = object?.type || 'Note';
|
|
381
|
-
if (!object) return reply(422, { error: 'a Create carries the object it creates' });
|
|
382
|
-
const visibility = this.visibilityOf(activity, object);
|
|
383
|
-
const { also, deliverTo } = this.addressedActors(activity, object);
|
|
384
|
-
// Not a Note and not a poll: stored as sent, under this actor, and
|
|
385
|
-
// the Create around it delivered — a Web Annotation, for one.
|
|
386
|
-
if (makes !== 'Note' && makes !== 'Question') {
|
|
387
|
-
const asSent = raw && typeof raw === 'object' && !Array.isArray(raw)
|
|
388
|
-
? (ACTIVITY_TYPES.has(raw.type) ? (raw.object && typeof raw.object === 'object' ? raw.object : null) : raw)
|
|
389
|
-
: null;
|
|
390
|
-
const made = await agent.publisher.publishObject(asSent || object, { visibility, slug, also, deliverTo });
|
|
391
|
-
return reply(201, { id: made.createId, object: made.id }, { location: made.createId });
|
|
392
|
-
}
|
|
393
|
-
// `source.content` is the client's plain text when it sends one; bare
|
|
394
|
-
// `content` is TREATED as plain text and escaped — markup survives as
|
|
395
|
-
// visible characters rather than as markup. Documented v1 limit.
|
|
396
|
-
const text = String(object.source?.content ?? object.content ?? '');
|
|
397
|
-
if (!text.trim()) return reply(422, { error: 'the note has no content' });
|
|
398
|
-
|
|
399
|
-
// A Question is a poll: the choices are in oneOf (pick one) or anyOf
|
|
400
|
-
// (pick several), each naming itself, and endTime is when it shuts.
|
|
401
|
-
if (makes === 'Question') {
|
|
402
|
-
const one = arr(object.oneOf);
|
|
403
|
-
const many = arr(object.anyOf);
|
|
404
|
-
const titles = (one.length ? one : many).map((c) => String(c?.name ?? '').trim()).filter(Boolean);
|
|
405
|
-
try {
|
|
406
|
-
const question = await agent.publisher.publishQuestion(text, {
|
|
407
|
-
options: titles,
|
|
408
|
-
multiple: !one.length && many.length > 0,
|
|
409
|
-
expiresAt: object.endTime || null,
|
|
410
|
-
inReplyTo: idOf(object.inReplyTo) || undefined,
|
|
411
|
-
visibility,
|
|
412
|
-
spoilerText: object.summary || null,
|
|
413
|
-
sensitive: object.sensitive === true,
|
|
414
|
-
});
|
|
415
|
-
return reply(201,
|
|
416
|
-
{ id: wire.createActivityId(question.id), object: question.id },
|
|
417
|
-
{ location: wire.createActivityId(question.id) });
|
|
418
|
-
} catch (e) {
|
|
419
|
-
return reply(422, { error: e.message });
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
const attachments = arr(object.attachment).map((a) => ({
|
|
424
|
-
url: a?.url, mediaType: a?.mediaType,
|
|
425
|
-
...(a?.name ? { description: a.name } : {}),
|
|
426
|
-
})).filter((a) => a.url);
|
|
427
|
-
const note = await agent.publisher.publishNote(text, {
|
|
428
|
-
inReplyTo: idOf(object.inReplyTo) || undefined,
|
|
429
|
-
attachments,
|
|
430
|
-
visibility,
|
|
431
|
-
spoilerText: object.summary || null,
|
|
432
|
-
sensitive: object.sensitive === true,
|
|
433
|
-
slug, also, deliverTo,
|
|
434
|
-
});
|
|
435
|
-
return reply(201, { id: wire.createActivityId(note.id), object: note.id },
|
|
436
|
-
{ location: wire.createActivityId(note.id) });
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
case 'Update': {
|
|
440
|
-
if (objectId === this.urls.actor) {
|
|
441
|
-
return reply(422, { error: 'edit the profile on the admin surface; actor updates are not taken here' });
|
|
442
|
-
}
|
|
443
|
-
const s = this.byIri(objectId);
|
|
444
|
-
if (!s) return reply(404, { error: 'no such note here' });
|
|
445
|
-
if (s.actor !== this.urls.actor || s.kind !== 'post') {
|
|
446
|
-
return reply(403, { error: 'not your note' });
|
|
447
|
-
}
|
|
448
|
-
const text = String(object?.source?.content ?? object?.content ?? '');
|
|
449
|
-
if (!text.trim()) return reply(422, { error: 'the edit has no content' });
|
|
450
|
-
const attachments = object?.attachment !== undefined
|
|
451
|
-
? arr(object.attachment).map((a) => ({
|
|
452
|
-
url: a?.url, mediaType: a?.mediaType,
|
|
453
|
-
...(a?.name ? { description: a.name } : {}),
|
|
454
|
-
})).filter((a) => a.url)
|
|
455
|
-
: null;
|
|
456
|
-
await agent.publisher.updateNote(s, {
|
|
457
|
-
content: text, spoilerText: object?.summary || null,
|
|
458
|
-
sensitive: object?.sensitive === undefined ? null : object.sensitive === true, attachments,
|
|
459
|
-
});
|
|
460
|
-
return reply(200, { ok: true, object: s.noteId });
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
case 'Delete': {
|
|
464
|
-
if (objectId === this.urls.actor) {
|
|
465
|
-
return reply(422, { error: 'retiring the actor is done on the admin surface, where it asks twice' });
|
|
466
|
-
}
|
|
467
|
-
const s = this.byIri(objectId);
|
|
468
|
-
if (!s) return reply(404, { error: 'no such note here' });
|
|
469
|
-
if (s.actor !== this.urls.actor || s.kind !== 'post') {
|
|
470
|
-
return reply(403, { error: 'not your note' });
|
|
471
|
-
}
|
|
472
|
-
const r = await social.deleteNote(agent, s);
|
|
473
|
-
if (!r.ok) return reply(502, { error: r.error, stillPublished: r.stillPublished });
|
|
474
|
-
return reply(200, { ok: true });
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
case 'Follow': {
|
|
478
|
-
if (!objectId) return reply(400, { error: 'whom? object must name an actor' });
|
|
479
|
-
// An acct: form or bare handle resolves through WebFinger; an https
|
|
480
|
-
// IRI is fetched directly.
|
|
481
|
-
if (/^acct:|^@|^[^/@]+@[^/@]+$/.test(objectId) && !/^https?:/.test(objectId)) {
|
|
482
|
-
const r = await social.followHandle(agent, objectId.replace(/^acct:/, ''));
|
|
483
|
-
const rec = this.store.getContacts().following.find((f) => f.actor === r.actor);
|
|
484
|
-
return reply(201, { id: rec?.followActivity?.id, object: r.actor },
|
|
485
|
-
rec?.followActivity?.id ? { location: rec.followActivity.id } : {});
|
|
486
|
-
}
|
|
487
|
-
const doc = await social.followActor(agent, objectId);
|
|
488
|
-
const rec = this.store.getContacts().following.find((f) => f.actor === doc.id);
|
|
489
|
-
return reply(201, { id: rec?.followActivity?.id, object: doc.id },
|
|
490
|
-
rec?.followActivity?.id ? { location: rec.followActivity.id } : {});
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
case 'Like': {
|
|
494
|
-
const s = this.byIri(objectId);
|
|
495
|
-
if (!s) return reply(422, { error: 'that note is not held here — like what the timeline holds' });
|
|
496
|
-
const updated = await social.favourite(agent, s);
|
|
497
|
-
return reply(201, { id: updated.likeActivity?.id, object: s.noteId },
|
|
498
|
-
updated.likeActivity?.id ? { location: updated.likeActivity.id } : {});
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
case 'Announce': {
|
|
502
|
-
const s = this.byIri(objectId);
|
|
503
|
-
if (!s) return reply(422, { error: 'that note is not held here — boost what the timeline holds' });
|
|
504
|
-
const updated = await social.reblog(agent, s);
|
|
505
|
-
return reply(201, { id: updated.announceActivity?.id, object: s.noteId },
|
|
506
|
-
updated.announceActivity?.id ? { location: updated.announceActivity.id } : {});
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
case 'Undo': {
|
|
510
|
-
// What is being unsaid — by the inner activity's id when the client
|
|
511
|
-
// sends one, by its type+object when it re-states it instead.
|
|
512
|
-
const inner = object;
|
|
513
|
-
const innerId = idOf(activity.object);
|
|
514
|
-
if (inner?.type === 'Block') {
|
|
515
|
-
const target = idOf(inner.object);
|
|
516
|
-
if (!target) return reply(400, { error: 'unblock whom?' });
|
|
517
|
-
await social.unblockActor(agent, target);
|
|
518
|
-
return reply(200, { ok: true, object: target });
|
|
519
|
-
}
|
|
520
|
-
const statuses = this.store.getStatuses();
|
|
521
|
-
let s = innerId ? statuses.find((x) => x.likeActivity?.id === innerId) : null;
|
|
522
|
-
if (!s && inner?.type === 'Like') s = this.byIri(idOf(inner.object));
|
|
523
|
-
if (s?.favourited) {
|
|
524
|
-
const updated = await social.unfavourite(agent, s);
|
|
525
|
-
return reply(200, { ok: true, object: updated.noteId });
|
|
526
|
-
}
|
|
527
|
-
s = innerId ? statuses.find((x) => x.announceActivity?.id === innerId) : null;
|
|
528
|
-
if (!s && inner?.type === 'Announce') s = this.byIri(idOf(inner.object));
|
|
529
|
-
if (s?.reblogged) {
|
|
530
|
-
const updated = await social.unreblog(agent, s);
|
|
531
|
-
return reply(200, { ok: true, object: updated.noteId });
|
|
532
|
-
}
|
|
533
|
-
const following = this.store.getContacts().following;
|
|
534
|
-
const rec = following.find((f) => f.followActivity?.id === innerId)
|
|
535
|
-
|| (inner?.type === 'Follow' ? following.find((f) => f.actor === idOf(inner.object)) : null);
|
|
536
|
-
if (rec) {
|
|
537
|
-
await social.unfollowActor(agent, rec.actor);
|
|
538
|
-
return reply(200, { ok: true, object: rec.actor });
|
|
539
|
-
}
|
|
540
|
-
return reply(422, { error: 'nothing here matches what that Undo names' });
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
case 'Block': {
|
|
544
|
-
if (!objectId) return reply(400, { error: 'block whom? object must name an actor' });
|
|
545
|
-
await social.blockActor(agent, objectId);
|
|
546
|
-
return reply(200, { ok: true, object: objectId });
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
case 'Add':
|
|
550
|
-
case 'Remove': {
|
|
551
|
-
// The one collection a client may edit is the pins (§7.6/§7.9 in the
|
|
552
|
-
// other direction): target must be the featured collection.
|
|
553
|
-
if (idOf(activity.target) !== this.urls.featured) {
|
|
554
|
-
return reply(422, { error: 'the featured collection is the one Add/Remove edits here' });
|
|
555
|
-
}
|
|
556
|
-
const s = this.byIri(objectId);
|
|
557
|
-
if (!s) return reply(404, { error: 'no such note here' });
|
|
558
|
-
const updated = await social.pinStatus(agent, s, activity.type === 'Add');
|
|
559
|
-
return reply(200, { ok: true, object: updated.noteId, pinned: !!updated.pinned });
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
case 'Accept':
|
|
563
|
-
case 'Reject': {
|
|
564
|
-
// Answering a held follow request: the object is the Follow (or the
|
|
565
|
-
// requester). Which request is meant comes from the Follow's actor.
|
|
566
|
-
const requester = object?.actor ? idOf(object.actor) : objectId;
|
|
567
|
-
if (!requester) return reply(400, { error: 'whose request? object must name the Follow or its actor' });
|
|
568
|
-
const r = activity.type === 'Accept'
|
|
569
|
-
? await social.admitRequest(agent, requester).catch((e) => ({ error: e.message }))
|
|
570
|
-
: await social.refuseRequest(agent, requester).catch((e) => ({ error: e.message }));
|
|
571
|
-
if (r.error) return reply(404, { error: r.error });
|
|
572
|
-
return reply(200, { ok: true, object: requester });
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
case 'Move':
|
|
576
|
-
return reply(422, { error: 'moving the account is done on the admin surface, where it asks twice' });
|
|
577
|
-
|
|
578
|
-
default:
|
|
579
|
-
return reply(422, { error: `no handler for ${activity.type}` });
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
}
|
package/lib/client/localapi.mjs
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
// localapi.mjs — talking to an agent on this machine.
|
|
2
|
-
//
|
|
3
|
-
// The agent serves https and nothing else, on the one port it was given. Its
|
|
4
|
-
// certificate is signed by this install's own certificate authority, which no
|
|
5
|
-
// public trust store knows about, so a plain fetch() would refuse it. Every
|
|
6
|
-
// caller here is on loopback reaching a listener this install started, so the
|
|
7
|
-
// answer is to trust that one authority — never to skip verification.
|
|
8
|
-
//
|
|
9
|
-
// Returns a small fetch-shaped result ({ status, ok, text(), json() }) so call
|
|
10
|
-
// sites read the way they did when this was cleartext http.
|
|
11
|
-
|
|
12
|
-
import https from 'node:https';
|
|
13
|
-
import fs from 'node:fs';
|
|
14
|
-
import path from 'node:path';
|
|
15
|
-
import { rootOf, apRoot } from '../device/home.mjs';
|
|
16
|
-
import { certPaths } from '../device/certs.mjs';
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* The authorities worth offering for a loopback call: this install's, and the
|
|
20
|
-
* machine's default install's. The well-known door on 8030 is held by
|
|
21
|
-
* whichever agent got there first, which may belong to a different root than
|
|
22
|
-
* the one this command is acting in — so both are candidates. An empty list
|
|
23
|
-
* means no certificate has been minted anywhere, and the call will fail the
|
|
24
|
-
* way any unreachable agent does.
|
|
25
|
-
*/
|
|
26
|
-
export function localCa(home) {
|
|
27
|
-
const read = (root) => {
|
|
28
|
-
try { return fs.readFileSync(certPaths(path.join(root, 'certs')).caCert); } catch { return null; }
|
|
29
|
-
};
|
|
30
|
-
// This install's own authority decides, and alone: every install names its
|
|
31
|
-
// authority the same thing, so offering two lets the verifier match by name
|
|
32
|
-
// and then fail on the signature. Only an install with no authority of its
|
|
33
|
-
// own falls back to the machine's default — that is the case of a command
|
|
34
|
-
// run outside any install reaching the well-known door.
|
|
35
|
-
const mine = home ? read(rootOf(home)) : null;
|
|
36
|
-
if (mine) return [mine];
|
|
37
|
-
try { const fallback = read(apRoot()); if (fallback) return [fallback]; } catch { /* no home dir */ }
|
|
38
|
-
return null;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* One request to an agent on this machine. `home` names any identity under the
|
|
43
|
-
* install, which is how the CA is found; `port` is the agent's own port.
|
|
44
|
-
*/
|
|
45
|
-
export async function localFetch(home, port, pathname, opts = {}) {
|
|
46
|
-
const res = await once(home, port, pathname, opts);
|
|
47
|
-
// The well-known door answers with a redirect to whichever agent holds it,
|
|
48
|
-
// so a probe that lands there has to follow it the way fetch() would.
|
|
49
|
-
if (opts.redirect === 'manual' || res.status < 300 || res.status >= 400) return res;
|
|
50
|
-
const to = res.headers?.location;
|
|
51
|
-
if (!to) return res;
|
|
52
|
-
const target = new URL(to, `https://localhost:${port}`);
|
|
53
|
-
return once(home, Number(target.port) || 443, target.pathname + target.search, opts);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function once(home, port, pathname, { method = 'GET', headers = {}, body, timeout = 0 } = {}) {
|
|
57
|
-
const ca = localCa(home);
|
|
58
|
-
return new Promise((resolve, reject) => {
|
|
59
|
-
const req = https.request({
|
|
60
|
-
host: 'localhost', port, path: pathname, method,
|
|
61
|
-
// One-shot calls: a kept-alive socket holds the agent's listener open, so
|
|
62
|
-
// a shutdown asked for over this channel would never finish.
|
|
63
|
-
headers: { connection: 'close', ...headers },
|
|
64
|
-
...(ca ? { ca } : {}),
|
|
65
|
-
// The certificate carries localhost and *.localhost; on some systems
|
|
66
|
-
// "localhost" resolves to ::1 first, which the name check still covers.
|
|
67
|
-
servername: 'localhost',
|
|
68
|
-
}, (res) => {
|
|
69
|
-
const chunks = [];
|
|
70
|
-
res.on('data', (c) => chunks.push(c));
|
|
71
|
-
res.on('end', () => {
|
|
72
|
-
const text = Buffer.concat(chunks).toString('utf8');
|
|
73
|
-
resolve({
|
|
74
|
-
status: res.statusCode,
|
|
75
|
-
ok: res.statusCode >= 200 && res.statusCode < 300,
|
|
76
|
-
headers: res.headers,
|
|
77
|
-
text: async () => text,
|
|
78
|
-
json: async () => JSON.parse(text),
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
});
|
|
82
|
-
req.on('error', reject);
|
|
83
|
-
if (timeout) req.setTimeout(timeout, () => req.destroy(new Error('timed out')));
|
|
84
|
-
if (body !== undefined && body !== null) req.write(body);
|
|
85
|
-
req.end();
|
|
86
|
-
});
|
|
87
|
-
}
|