fedipod-server 0.29.0 → 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 +11 -6
- 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 +36 -101
- package/dist/handler.js +85 -645
- package/dist/handler.jsonld +2 -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/package.json +10 -18
- package/lib/client/c2s.mjs +0 -636
- package/lib/client/localapi.mjs +0 -87
- package/lib/client/masto/accounts.mjs +0 -310
- 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 -387
- package/lib/core/graphview.mjs +0 -327
- package/lib/core/intake/activities.mjs +0 -649
- package/lib/core/intake/activity.mjs +0 -318
- package/lib/core/intake/channel.mjs +0 -144
- package/lib/core/intake/group.mjs +0 -258
- package/lib/core/intake/index.mjs +0 -732
- package/lib/core/intake/notes.mjs +0 -367
- 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/place.mjs +0 -82
- 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 -272
- package/lib/core/publisher/index.mjs +0 -522
- package/lib/core/publisher/notes.mjs +0 -519
- package/lib/core/publisher/own.mjs +0 -143
- package/lib/core/publisher/questions.mjs +0 -241
- 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 -561
- package/lib/core/storage.mjs +0 -176
- package/lib/core/store.mjs +0 -578
- package/lib/core/wire-quotes.mjs +0 -71
- package/lib/core/wire.mjs +0 -925
- 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 -428
- 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 -489
- 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 -496
- 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 -966
- package/lib/gateway/gateway-core.mjs +0 -244
- 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/gateway/relay-extras.mjs +0 -89
- package/lib/gateway/token-claims.mjs +0 -16
- 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/location.mjs +0 -52
- package/lib/pod/media.mjs +0 -27
- package/lib/pod/notes.mjs +0 -78
- 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 -674
- package/lib/pod/type-index.mjs +0 -101
- package/lib/pod/urls.mjs +0 -91
- package/lib/server/embed.mjs +0 -424
- package/lib/session/README.md +0 -137
- package/lib/session/demo.html +0 -94
- package/lib/session/fedi-account.mjs +0 -417
- 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 -899
- 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 -147
- 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 -612
- package/web/admin/notices-bar.js +0 -90
- package/web/admin/oauth-signin.mjs +0 -37
- package/web/admin/record.js +0 -393
- package/web/admin/setup/index.html +0 -257
- package/web/admin/setup/setup.js +0 -339
- 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 -672
- 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 -616
- package/web/app/deliver-relay.mjs +0 -129
- package/web/app/dist/boot.js +0 -34562
- package/web/app/dist/boot.js.map +0 -7
- package/web/app/dist/sw.js +0 -73456
- 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 -258
- 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 -357
- 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 -64
- package/web/front/run.js +0 -221
- 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,636 +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
|
-
import * as podNotes from '../pod/notes.mjs';
|
|
32
|
-
|
|
33
|
-
const MAX_BODY = 512 * 1024; // same ceiling the inbox drain enforces
|
|
34
|
-
|
|
35
|
-
// How many archived items one page of the inbox will read. A page is one
|
|
36
|
-
// month, and a month with more than this is served short rather than costing
|
|
37
|
-
// the pod an unbounded read; the log says when that happened.
|
|
38
|
-
const MAX_INBOX_PAGE = 500;
|
|
39
|
-
|
|
40
|
-
// How many recorded activities one page of the locally-served outbox carries.
|
|
41
|
-
const MAX_OUTBOX_PAGE = 50;
|
|
42
|
-
|
|
43
|
-
// §6 names activities; anything else with a type is an object to wrap.
|
|
44
|
-
const ACTIVITY_TYPES = new Set([
|
|
45
|
-
'Create', 'Update', 'Delete', 'Follow', 'Like', 'Announce', 'Undo',
|
|
46
|
-
'Block', 'Add', 'Remove', 'Accept', 'Reject', 'Move',
|
|
47
|
-
]);
|
|
48
|
-
|
|
49
|
-
const arr = (v) => (v === undefined || v === null ? [] : Array.isArray(v) ? v : [v]);
|
|
50
|
-
const idOf = (v) => (typeof v === 'string' ? v : v?.id || null);
|
|
51
|
-
|
|
52
|
-
function readBody(req) {
|
|
53
|
-
return new Promise((resolve, reject) => {
|
|
54
|
-
let size = 0;
|
|
55
|
-
const chunks = [];
|
|
56
|
-
req.on('data', (c) => {
|
|
57
|
-
size += c.length;
|
|
58
|
-
if (size > MAX_BODY) { reject(new Error('body too large')); req.destroy(); return; }
|
|
59
|
-
chunks.push(c);
|
|
60
|
-
});
|
|
61
|
-
req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
|
|
62
|
-
req.on('error', reject);
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export class C2S {
|
|
67
|
-
constructor({ agent, log = console.log, auth, scheme = null, mount = '' }) {
|
|
68
|
-
this.agent = agent;
|
|
69
|
-
this.log = log;
|
|
70
|
-
this.auth = auth;
|
|
71
|
-
// What this agent calls itself, for the actor it serves a local client.
|
|
72
|
-
// The same two values oidc-auth rebuilds the DPoP `htu` from, so the two
|
|
73
|
-
// surfaces agree on the address a client just used.
|
|
74
|
-
this.scheme = scheme;
|
|
75
|
-
this.mount = mount;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
get store() { return this.agent.store; }
|
|
79
|
-
get urls() { return this.agent.publisher?.urls; }
|
|
80
|
-
|
|
81
|
-
send(res, status, obj, headers = {}) {
|
|
82
|
-
res.writeHead(status, {
|
|
83
|
-
'content-type': 'application/json; charset=utf-8',
|
|
84
|
-
'cache-control': 'no-store',
|
|
85
|
-
...headers,
|
|
86
|
-
});
|
|
87
|
-
res.end(JSON.stringify(obj));
|
|
88
|
-
return true;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
byIri(iri) {
|
|
92
|
-
return iri ? this.store.getStatuses().find((s) => s.noteId === iri) : null;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/** The months the archive holds, newest first. One container listing. */
|
|
96
|
-
async archiveMonths() {
|
|
97
|
-
const archive = this.agent.intake?.archive;
|
|
98
|
-
if (!archive) return [];
|
|
99
|
-
const { names } = await archive.list('');
|
|
100
|
-
return (names || [])
|
|
101
|
-
.map((n) => n.replace(/\/$/u, ''))
|
|
102
|
-
.filter((n) => /^\d{4}-\d{2}$/u.test(n))
|
|
103
|
-
.sort()
|
|
104
|
-
.reverse();
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* The owner's own inbox, §5.2. Paged by month because that is how the
|
|
109
|
-
* archive is stored, so a page costs one listing and a read per item and no
|
|
110
|
-
* page is dearer for another month being large.
|
|
111
|
-
*/
|
|
112
|
-
async sendInbox(res, url) {
|
|
113
|
-
const id = `${this.urls.base}ap/inbox`;
|
|
114
|
-
const page = url?.searchParams?.get('page') || null;
|
|
115
|
-
let months;
|
|
116
|
-
try {
|
|
117
|
-
months = await this.archiveMonths();
|
|
118
|
-
} catch (e) {
|
|
119
|
-
return this.send(res, 502, { error: `the archive could not be read: ${e.message}` });
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
if (!page) {
|
|
123
|
-
if (!months.length && this.store.getConfig()?.archiveInbox === false) {
|
|
124
|
-
this.log('inbox read: nothing to show — this identity does not keep what it receives');
|
|
125
|
-
}
|
|
126
|
-
return this.send(res, 200, {
|
|
127
|
-
'@context': wire.AS_CTX, id, type: 'OrderedCollection',
|
|
128
|
-
...(months.length ? { first: `${id}?page=${months[0]}` } : { orderedItems: [] }),
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
if (!/^\d{4}-\d{2}$/u.test(page)) {
|
|
132
|
-
return this.send(res, 400, { error: 'page names a month, written 2026-09' });
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const archive = this.agent.intake?.archive;
|
|
136
|
-
let names = [];
|
|
137
|
-
try {
|
|
138
|
-
// The trailing slash matters: without it this names a document, not the
|
|
139
|
-
// container, and a pod answers about the wrong thing.
|
|
140
|
-
({ names } = await archive.list(`${page}/`));
|
|
141
|
-
} catch (e) {
|
|
142
|
-
return this.send(res, 502, { error: `the archive could not be read: ${e.message}` });
|
|
143
|
-
}
|
|
144
|
-
const files = (names || []).filter((n) => n.endsWith('.json')).sort();
|
|
145
|
-
if (files.length > MAX_INBOX_PAGE) {
|
|
146
|
-
this.log(`inbox read: ${page} holds ${files.length} items; serving the first ${MAX_INBOX_PAGE}`);
|
|
147
|
-
}
|
|
148
|
-
const kept = [];
|
|
149
|
-
for (const file of files.slice(0, MAX_INBOX_PAGE)) {
|
|
150
|
-
// Read as written: these records are JSON-LD, and the default read asks
|
|
151
|
-
// turtle-first, which a server is free to answer with turtle.
|
|
152
|
-
const read = await archive.read(`${page}/${file}`, { accept: '*/*' });
|
|
153
|
-
if (!read?.ok || !read.body) continue;
|
|
154
|
-
try {
|
|
155
|
-
const record = JSON.parse(read.body);
|
|
156
|
-
// The record wraps the bytes as they arrived; the activity is those
|
|
157
|
-
// bytes, not a retelling of them.
|
|
158
|
-
kept.push({ at: record.receivedAt || '', activity: JSON.parse(record.raw) });
|
|
159
|
-
} catch { /* a record that will not parse is not one that can be served */ }
|
|
160
|
-
}
|
|
161
|
-
kept.sort((a, b) => String(b.at).localeCompare(String(a.at)));
|
|
162
|
-
const older = months.filter((m) => m < page)[0] || null;
|
|
163
|
-
return this.send(res, 200, {
|
|
164
|
-
'@context': wire.AS_CTX,
|
|
165
|
-
id: `${id}?page=${page}`,
|
|
166
|
-
type: 'OrderedCollectionPage',
|
|
167
|
-
partOf: id,
|
|
168
|
-
...(older ? { next: `${id}?page=${older}` } : {}),
|
|
169
|
-
orderedItems: kept.map((k) => k.activity),
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// The actor a client-to-server client on this machine reads. The same
|
|
174
|
-
// document the pod holds, but for the fields naming where a write goes —
|
|
175
|
-
// the outbox and the two OAuth endpoints — and for `id`, which names this
|
|
176
|
-
// address so the client files its credential here and comes back. The pod's
|
|
177
|
-
// id stays on as an alias; the two are one identity.
|
|
178
|
-
// What this agent calls itself, to the client that just asked. The Host
|
|
179
|
-
// passed the Authorities firewall before any route ran, so whichever alias
|
|
180
|
-
// the client used is one this agent answers on. The scheme is read off the
|
|
181
|
-
// connection unless the caller named one: only the Server passes a scheme,
|
|
182
|
-
// and standalone this listener is its own TLS.
|
|
183
|
-
localOrigin(req) {
|
|
184
|
-
const host = req.headers.host;
|
|
185
|
-
if (!host) return null;
|
|
186
|
-
const scheme = this.scheme
|
|
187
|
-
|| (req.socket?.encrypted || req.headers['x-forwarded-proto'] === 'https' ? 'https' : 'http');
|
|
188
|
-
return `${scheme}://${host}${this.mount}/`;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* The owner's own outbox, from what this agent recorded. The pod holds the
|
|
193
|
-
* published copy and it stays the canonical one; this is served only where
|
|
194
|
-
* that copy cannot be read by the client asking.
|
|
195
|
-
*/
|
|
196
|
-
sendLocalOutbox(res, url, origin, { owner = false } = {}) {
|
|
197
|
-
const id = `${origin}ap/outbox`;
|
|
198
|
-
// newest first; the owner's view is every message, the public one what a
|
|
199
|
-
// stranger may see
|
|
200
|
-
const outbox = this.store.read(owner ? 'outbox-own.json' : 'outbox.json', []);
|
|
201
|
-
const page = url?.searchParams?.get('page') || null;
|
|
202
|
-
const ct = { 'content-type': 'application/activity+json; charset=utf-8' };
|
|
203
|
-
if (!page) {
|
|
204
|
-
return this.send(res, 200, {
|
|
205
|
-
'@context': wire.AS_CTX, id, type: 'OrderedCollection', totalItems: outbox.length,
|
|
206
|
-
...(outbox.length ? { first: `${id}?page=1` } : { orderedItems: [] }),
|
|
207
|
-
}, ct);
|
|
208
|
-
}
|
|
209
|
-
const n = Number(page);
|
|
210
|
-
if (!Number.isInteger(n) || n < 1) {
|
|
211
|
-
return this.send(res, 400, { error: 'page is a whole number from 1' });
|
|
212
|
-
}
|
|
213
|
-
const start = (n - 1) * MAX_OUTBOX_PAGE;
|
|
214
|
-
const items = outbox.slice(start, start + MAX_OUTBOX_PAGE);
|
|
215
|
-
return this.send(res, 200, {
|
|
216
|
-
'@context': wire.AS_CTX,
|
|
217
|
-
id: `${id}?page=${n}`,
|
|
218
|
-
type: 'OrderedCollectionPage',
|
|
219
|
-
partOf: id,
|
|
220
|
-
...(start + items.length < outbox.length ? { next: `${id}?page=${n + 1}` } : {}),
|
|
221
|
-
// A post is recorded by its object id and published as the Create beside
|
|
222
|
-
// it — the same mapping the pod's copy uses, from the same helper.
|
|
223
|
-
orderedItems: items.map(wire.outboxWireItem),
|
|
224
|
-
}, ct);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
sendLocalActor(req, res, origin) {
|
|
228
|
-
const pub = this.agent.publisher;
|
|
229
|
-
const cfg = pub.config || {};
|
|
230
|
-
const gw = cfg.gateway;
|
|
231
|
-
const gwActive = gw && gw.url && gw.mode && gw.mode !== 'off';
|
|
232
|
-
const doc = pub.actorDocFor({
|
|
233
|
-
inbox: gwActive ? gw.url : (cfg.inboxUrl || null),
|
|
234
|
-
clientOrigin: origin,
|
|
235
|
-
});
|
|
236
|
-
// A client files its credential under the id it reads here and comes back
|
|
237
|
-
// to that address for everything after, so this document has to name
|
|
238
|
-
// itself. The pod's id stays on as an alias rather than being dropped —
|
|
239
|
-
// the two are one identity. Nothing published ever carries this id: every
|
|
240
|
-
// activity the dispatcher builds names the pod's actor.
|
|
241
|
-
const canonical = doc.id;
|
|
242
|
-
doc.id = `${origin}ap/actor`;
|
|
243
|
-
doc.alsoKnownAs = [...new Set([...(doc.alsoKnownAs || []), canonical])];
|
|
244
|
-
const body = JSON.stringify(doc);
|
|
245
|
-
res.writeHead(200, {
|
|
246
|
-
'content-type': 'application/activity+json; charset=utf-8',
|
|
247
|
-
'cache-control': 'no-store',
|
|
248
|
-
'content-length': Buffer.byteLength(body),
|
|
249
|
-
});
|
|
250
|
-
res.end(req.method === 'HEAD' ? undefined : body);
|
|
251
|
-
return true;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
async handle(req, res, pathname, url) { // eslint-disable-line no-unused-vars
|
|
255
|
-
if (pathname !== '/ap/outbox' && pathname !== '/ap/actor' && pathname !== '/ap/inbox') return false;
|
|
256
|
-
if (req.method === 'OPTIONS') {
|
|
257
|
-
// Accept-Post is what a client such as dokieli reads to choose a format;
|
|
258
|
-
// naming JSON only is what makes it send JSON-LD rather than HTML.
|
|
259
|
-
res.writeHead(204, pathname === '/ap/inbox' ? { allow: 'GET, OPTIONS' }
|
|
260
|
-
: { allow: 'GET, POST, OPTIONS', 'accept-post': 'application/ld+json, application/activity+json' });
|
|
261
|
-
res.end(); return true;
|
|
262
|
-
}
|
|
263
|
-
if (!this.agent.configured() || !this.urls) {
|
|
264
|
-
return this.send(res, 409, { error: 'agent not configured' });
|
|
265
|
-
}
|
|
266
|
-
if (pathname === '/ap/inbox') {
|
|
267
|
-
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
268
|
-
return this.send(res, 405, { error: "deliveries go to this actor's inbox on the pod, which the "
|
|
269
|
-
+ 'actor document names; this address is the owner reading their own' });
|
|
270
|
-
}
|
|
271
|
-
const reader = await this.auth(req, pathname);
|
|
272
|
-
if (!reader.ok) return this.send(res, reader.status, { error: reader.error }, reader.headers || {});
|
|
273
|
-
return this.sendInbox(res, url);
|
|
274
|
-
}
|
|
275
|
-
if (req.method === 'GET' || req.method === 'HEAD') {
|
|
276
|
-
// The owner, signed in, reads every message the actor produced (§5.1:
|
|
277
|
-
// the outbox is filtered by who asks). Anyone else reads the public one.
|
|
278
|
-
const owner = pathname === '/ap/outbox' && req.headers.authorization
|
|
279
|
-
? (await this.auth(req, pathname)).ok : false;
|
|
280
|
-
// Where the client surface answers on an address a stranger can reach,
|
|
281
|
-
// the pod's copies already name it and are the documents. Standalone
|
|
282
|
-
// they are not reachable by the client asking, so it is answered here.
|
|
283
|
-
const local = this.agent.publisher?.clientOrigin ? null : this.localOrigin(req);
|
|
284
|
-
if (local && pathname === '/ap/actor') return this.sendLocalActor(req, res, local);
|
|
285
|
-
if (local && pathname === '/ap/outbox') return this.sendLocalOutbox(res, url, local, { owner });
|
|
286
|
-
// The owner is answered here, not sent on: a redirect loses the
|
|
287
|
-
// credential (a browser drops it across origins, and a DPoP proof names
|
|
288
|
-
// the address it was made for), so the pod would refuse them.
|
|
289
|
-
if (owner && pathname === '/ap/outbox') {
|
|
290
|
-
return this.sendLocalOutbox(res, url, this.agent.publisher.clientOrigin, { owner });
|
|
291
|
-
}
|
|
292
|
-
// The pod's copy is the document; send the reader there.
|
|
293
|
-
const target = pathname === '/ap/actor' ? this.urls.actor : this.urls.outbox;
|
|
294
|
-
res.writeHead(303, { location: target, 'cache-control': 'no-store' });
|
|
295
|
-
res.end();
|
|
296
|
-
return true;
|
|
297
|
-
}
|
|
298
|
-
if (pathname !== '/ap/outbox' || req.method !== 'POST') {
|
|
299
|
-
return this.send(res, 405, { error: 'POST the outbox; GET redirects to the pod' });
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
const who = await this.auth(req, pathname);
|
|
303
|
-
if (!who.ok) return this.send(res, who.status, { error: who.error }, who.headers || {});
|
|
304
|
-
|
|
305
|
-
// A viewer (another agent holds the lease) may not act — but the user
|
|
306
|
-
// acting HERE outranks an idle active agent elsewhere, exactly as on the
|
|
307
|
-
// facade: a write attempt claims the lease, and only a failed claim 503s.
|
|
308
|
-
if (this.agent.viewer) {
|
|
309
|
-
const took = await this.agent.requestTakeover?.();
|
|
310
|
-
if (!took) return this.send(res, 503, { error: 'another agent is active for this pod — takeover failed, try again' });
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
// Read as JSON-LD, so a client may send its activity with whatever context
|
|
314
|
-
// it likes and still be understood. What is read is the GRAPH: `dispatch`
|
|
315
|
-
// takes decisions from it and the publisher builds the document that is
|
|
316
|
-
// actually posted, so nothing a client sent is republished verbatim and a
|
|
317
|
-
// term it aliased still means what it says.
|
|
318
|
-
// `raw` is the document as the client wrote it. The graph is what
|
|
319
|
-
// decisions are taken from; the bytes are what an object that is stored
|
|
320
|
-
// as sent (publishObject) is stored from — a read against our contexts
|
|
321
|
-
// rewrites a stranger's terms to full IRIs, which is right for reading and
|
|
322
|
-
// wrong for keeping.
|
|
323
|
-
let activity; let raw = null;
|
|
324
|
-
try {
|
|
325
|
-
const body = await readBody(req);
|
|
326
|
-
try { raw = JSON.parse(body); } catch { raw = null; }
|
|
327
|
-
const read = await readLenient(body);
|
|
328
|
-
activity = read.view ?? read.doc;
|
|
329
|
-
} catch (e) {
|
|
330
|
-
return this.send(res, 400, { error: `unreadable body: ${e.message}` });
|
|
331
|
-
}
|
|
332
|
-
if (!activity || typeof activity !== 'object' || Array.isArray(activity) || !activity.type) {
|
|
333
|
-
return this.send(res, 400, { error: 'a typed ActivityStreams object is required' });
|
|
334
|
-
}
|
|
335
|
-
// The name the client asks for its new document (LDP's Slug), taken when
|
|
336
|
-
// it is plain and free — it is what lets the client know the address of
|
|
337
|
-
// what it made before anything answers.
|
|
338
|
-
const slug = safeSlug(req.headers.slug) || null;
|
|
339
|
-
const r = await this.dispatch(activity, { slug, raw });
|
|
340
|
-
return this.send(res, r.status, r.body, r.headers);
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// Addressing → the facade's four visibilities, inverting the table the
|
|
344
|
-
// composer writes (wire.addressing). Nothing stated is a public post — what
|
|
345
|
-
// every client means by a post with no audience chosen, and what a client
|
|
346
|
-
// that never addresses (dokieli's annotations) needs.
|
|
347
|
-
visibilityOf(activity, object) {
|
|
348
|
-
const to = arr(activity.to ?? object?.to).map(idOf);
|
|
349
|
-
const cc = arr(activity.cc ?? object?.cc).map(idOf);
|
|
350
|
-
// Blind copies alone are still an audience: a post for them and nobody
|
|
351
|
-
// else is direct, never public.
|
|
352
|
-
const blind = arr(activity.bto ?? object?.bto).length + arr(activity.bcc ?? object?.bcc).length;
|
|
353
|
-
if (!to.length && !cc.length) return blind ? 'direct' : 'public';
|
|
354
|
-
if (to.includes(wire.PUBLIC)) return 'public';
|
|
355
|
-
if (cc.includes(wire.PUBLIC)) return 'unlisted';
|
|
356
|
-
// Followers named anywhere — "to alice, cc my followers" included — is a
|
|
357
|
-
// followers-only post that also reaches whoever else is named.
|
|
358
|
-
if (to.includes(this.urls.followers) || cc.includes(this.urls.followers)) return 'private';
|
|
359
|
-
return 'direct';
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
// `serial` and `at` name what this activity will make: the outbox door
|
|
363
|
-
// chose them and told the client, so they are used as given. A client
|
|
364
|
-
// posting here directly gets fresh ones.
|
|
365
|
-
async dispatch(activity, { slug = null, raw = null, serial = Date.now(), at = new Date().toISOString() } = {}) {
|
|
366
|
-
const reply = (status, body, headers = {}) => ({ status, body, headers });
|
|
367
|
-
if (!activity || typeof activity !== 'object' || Array.isArray(activity) || !activity.type) {
|
|
368
|
-
return reply(400, { error: 'a typed ActivityStreams object is required' });
|
|
369
|
-
}
|
|
370
|
-
// A bare object arrives without an activity around it; the server supplies
|
|
371
|
-
// the Create (§6.2.1), carrying the object's own addressing up onto it.
|
|
372
|
-
if (!ACTIVITY_TYPES.has(activity.type)) {
|
|
373
|
-
// Every audience field is copied up onto the Create (§6.2.1).
|
|
374
|
-
const { to, cc, bto, bcc, audience } = activity;
|
|
375
|
-
activity = { type: 'Create', object: activity, to, cc, bto, bcc, audience };
|
|
376
|
-
}
|
|
377
|
-
try {
|
|
378
|
-
return await this._dispatch(activity, { slug, raw, reply, serial, at });
|
|
379
|
-
} catch (e) {
|
|
380
|
-
this.log(`c2s ${activity?.type}: ${e.message}`);
|
|
381
|
-
return reply(422, { error: e.message || String(e) });
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
// The actors a client addressed by id, beyond the audience words: the ones
|
|
386
|
-
// in to/cc are listed and delivered to; the ones in bto/bcc are delivered
|
|
387
|
-
// to and never listed (§6).
|
|
388
|
-
addressedActors(activity, object) {
|
|
389
|
-
const pick = (...fields) => [...new Set(fields.flatMap((f) => arr(activity[f] ?? object?.[f]).map(idOf)))]
|
|
390
|
-
.filter((a) => typeof a === 'string' && /^https?:\/\//u.test(a)
|
|
391
|
-
&& a !== wire.PUBLIC && a !== this.urls.followers && a !== this.urls.actor);
|
|
392
|
-
// `audience` is who the post is for, a group most often: delivered to and
|
|
393
|
-
// named like to and cc (§7.1).
|
|
394
|
-
return { also: pick('to', 'cc', 'audience'), deliverTo: pick('bto', 'bcc') };
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
async _dispatch(activity, { slug, raw, reply, serial, at }) {
|
|
398
|
-
const agent = this.agent;
|
|
399
|
-
// §6: every post to the outbox answers 201 with the new activity's id.
|
|
400
|
-
const made = (id, body = {}) => reply(201, { id, ...body }, { location: id });
|
|
401
|
-
// What is never sent anywhere — a person's block, a pin — is still a
|
|
402
|
-
// message this actor produced, so the owner's outbox records it here.
|
|
403
|
-
const kept = (type, extra) => {
|
|
404
|
-
const id = type === 'Undo' ? wire.undoActivityId(this.urls, serial) : `${this.urls.actor}#${type.toLowerCase()}-${serial}`;
|
|
405
|
-
const act = { id, type, actor: this.urls.actor, published: at, ...extra };
|
|
406
|
-
agent.publisher.recordOwn?.(act);
|
|
407
|
-
return act.id;
|
|
408
|
-
};
|
|
409
|
-
const object = typeof activity.object === 'object' && activity.object !== null
|
|
410
|
-
? activity.object : null;
|
|
411
|
-
const objectId = idOf(activity.object);
|
|
412
|
-
|
|
413
|
-
switch (activity.type) {
|
|
414
|
-
case 'Create': {
|
|
415
|
-
const makes = object?.type || 'Note';
|
|
416
|
-
if (!object) return reply(422, { error: 'a Create carries the object it creates' });
|
|
417
|
-
const visibility = this.visibilityOf(activity, object);
|
|
418
|
-
const { also, deliverTo } = this.addressedActors(activity, object);
|
|
419
|
-
// Not a Note and not a poll: stored as sent, under this actor, and
|
|
420
|
-
// the Create around it delivered — a Web Annotation, for one.
|
|
421
|
-
if (makes !== 'Note' && makes !== 'Question') {
|
|
422
|
-
const asSent = raw && typeof raw === 'object' && !Array.isArray(raw)
|
|
423
|
-
? (ACTIVITY_TYPES.has(raw.type) ? (raw.object && typeof raw.object === 'object' ? raw.object : null) : raw)
|
|
424
|
-
: null;
|
|
425
|
-
const made = await agent.publisher.publishObject(asSent || object, { visibility, slug, also, deliverTo });
|
|
426
|
-
return reply(201, { id: made.createId, object: made.id }, { location: made.createId });
|
|
427
|
-
}
|
|
428
|
-
// `source.content` is the client's plain text when it sends one; bare
|
|
429
|
-
// `content` is TREATED as plain text and escaped — markup survives as
|
|
430
|
-
// visible characters rather than as markup. Documented v1 limit.
|
|
431
|
-
const text = String(object.source?.content ?? object.content ?? '');
|
|
432
|
-
if (!text.trim()) return reply(422, { error: 'the note has no content' });
|
|
433
|
-
|
|
434
|
-
// A Question is a poll: the choices are in oneOf (pick one) or anyOf
|
|
435
|
-
// (pick several), each naming itself, and endTime is when it shuts.
|
|
436
|
-
if (makes === 'Question') {
|
|
437
|
-
const one = arr(object.oneOf);
|
|
438
|
-
const many = arr(object.anyOf);
|
|
439
|
-
const titles = (one.length ? one : many).map((c) => String(c?.name ?? '').trim()).filter(Boolean);
|
|
440
|
-
try {
|
|
441
|
-
const question = await agent.publisher.publishQuestion(text, {
|
|
442
|
-
options: titles,
|
|
443
|
-
multiple: !one.length && many.length > 0,
|
|
444
|
-
expiresAt: object.endTime || null,
|
|
445
|
-
inReplyTo: idOf(object.inReplyTo) || undefined,
|
|
446
|
-
visibility,
|
|
447
|
-
spoilerText: object.summary || null,
|
|
448
|
-
sensitive: object.sensitive === true,
|
|
449
|
-
slug,
|
|
450
|
-
});
|
|
451
|
-
return reply(201,
|
|
452
|
-
{ id: wire.createActivityId(question.id), object: question.id },
|
|
453
|
-
{ location: wire.createActivityId(question.id) });
|
|
454
|
-
} catch (e) {
|
|
455
|
-
return reply(422, { error: e.message });
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
const attachments = arr(object.attachment).map((a) => ({
|
|
460
|
-
url: a?.url, mediaType: a?.mediaType,
|
|
461
|
-
...(a?.name ? { description: a.name } : {}),
|
|
462
|
-
})).filter((a) => a.url);
|
|
463
|
-
const note = await agent.publisher.publishNote(text, {
|
|
464
|
-
inReplyTo: idOf(object.inReplyTo) || undefined,
|
|
465
|
-
attachments,
|
|
466
|
-
visibility,
|
|
467
|
-
spoilerText: object.summary || null,
|
|
468
|
-
sensitive: object.sensitive === true,
|
|
469
|
-
slug, also, deliverTo,
|
|
470
|
-
});
|
|
471
|
-
return reply(201, { id: wire.createActivityId(note.id), object: note.id },
|
|
472
|
-
{ location: wire.createActivityId(note.id) });
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
case 'Update': {
|
|
476
|
-
if (objectId === this.urls.actor) {
|
|
477
|
-
return reply(422, { error: 'edit the profile on the admin surface; actor updates are not taken here' });
|
|
478
|
-
}
|
|
479
|
-
const s = this.byIri(objectId);
|
|
480
|
-
if (!s) return reply(404, { error: 'no such note here' });
|
|
481
|
-
if (s.actor !== this.urls.actor || s.kind !== 'post') {
|
|
482
|
-
return reply(403, { error: 'not your note' });
|
|
483
|
-
}
|
|
484
|
-
// §6.3: a client's Update changes only what it sends, and a field sent
|
|
485
|
-
// as null is removed. What was sent is read from the bytes, because a
|
|
486
|
-
// JSON-LD reading drops the nulls that mean "remove this".
|
|
487
|
-
const sent = raw?.type === 'Update' && raw.object && typeof raw.object === 'object' ? raw.object : (object || {});
|
|
488
|
-
const has = (k) => Object.prototype.hasOwnProperty.call(sent, k);
|
|
489
|
-
const stored = await Promise.resolve().then(() => podNotes.read(agent.remote, s.noteId)).catch(() => null);
|
|
490
|
-
if (stored?.type && stored.type !== 'Note') {
|
|
491
|
-
await agent.publisher.updateObject(s, sent, { updated: at });
|
|
492
|
-
return made(wire.updateActivityId(s.noteId, at), { object: s.noteId });
|
|
493
|
-
}
|
|
494
|
-
const text = has('content') || sent.source?.content !== undefined
|
|
495
|
-
? String(object?.source?.content ?? object?.content ?? '') : String(s.text ?? '');
|
|
496
|
-
if (!text.trim()) return reply(422, { error: 'the edit leaves the note with no content' });
|
|
497
|
-
const attachments = has('attachment')
|
|
498
|
-
? arr(object.attachment).map((a) => ({
|
|
499
|
-
url: a?.url, mediaType: a?.mediaType,
|
|
500
|
-
...(a?.name ? { description: a.name } : {}),
|
|
501
|
-
})).filter((a) => a.url)
|
|
502
|
-
: null;
|
|
503
|
-
await agent.publisher.updateNote(s, {
|
|
504
|
-
content: text,
|
|
505
|
-
spoilerText: has('summary') ? (object?.summary || null) : (s.spoiler || null),
|
|
506
|
-
sensitive: has('sensitive') ? object?.sensitive === true : null, attachments,
|
|
507
|
-
updated: at,
|
|
508
|
-
});
|
|
509
|
-
return made(wire.updateActivityId(s.noteId, at), { object: s.noteId });
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
case 'Delete': {
|
|
513
|
-
if (objectId === this.urls.actor) {
|
|
514
|
-
return reply(422, { error: 'retiring the actor is done on the admin surface, where it asks twice' });
|
|
515
|
-
}
|
|
516
|
-
const s = this.byIri(objectId);
|
|
517
|
-
if (!s) return reply(404, { error: 'no such note here' });
|
|
518
|
-
if (s.actor !== this.urls.actor || s.kind !== 'post') {
|
|
519
|
-
return reply(403, { error: 'not your note' });
|
|
520
|
-
}
|
|
521
|
-
const r = await social.deleteNote(agent, s);
|
|
522
|
-
if (!r.ok) return reply(502, { error: r.error, stillPublished: r.stillPublished });
|
|
523
|
-
return made(wire.deleteActivityId(s.noteId), { object: s.noteId });
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
case 'Follow': {
|
|
527
|
-
if (!objectId) return reply(400, { error: 'whom? object must name an actor' });
|
|
528
|
-
// An acct: form or bare handle resolves through WebFinger; an https
|
|
529
|
-
// IRI is fetched directly.
|
|
530
|
-
if (/^acct:|^@|^[^/@]+@[^/@]+$/.test(objectId) && !/^https?:/.test(objectId)) {
|
|
531
|
-
const r = await social.followHandle(agent, objectId.replace(/^acct:/, ''), { serial });
|
|
532
|
-
const rec = this.store.getContacts().following.find((f) => f.actor === r.actor);
|
|
533
|
-
return made(rec?.followActivity?.id, { object: r.actor });
|
|
534
|
-
}
|
|
535
|
-
const doc = await social.followActor(agent, objectId, { serial });
|
|
536
|
-
const rec = this.store.getContacts().following.find((f) => f.actor === doc.id);
|
|
537
|
-
return made(rec?.followActivity?.id, { object: doc.id });
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
case 'Like': {
|
|
541
|
-
const s = this.byIri(objectId);
|
|
542
|
-
if (!s) return reply(422, { error: 'that note is not held here — like what the timeline holds' });
|
|
543
|
-
const updated = await social.favourite(agent, s, { serial });
|
|
544
|
-
return made(updated.likeActivity?.id, { object: s.noteId });
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
case 'Announce': {
|
|
548
|
-
const s = this.byIri(objectId);
|
|
549
|
-
if (!s) return reply(422, { error: 'that note is not held here — boost what the timeline holds' });
|
|
550
|
-
const updated = await social.reblog(agent, s, { serial });
|
|
551
|
-
return made(updated.announceActivity?.id, { object: s.noteId });
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
case 'Undo': {
|
|
555
|
-
// What is being unsaid — by the inner activity's id when the client
|
|
556
|
-
// sends one, by its type+object when it re-states it instead.
|
|
557
|
-
const inner = object;
|
|
558
|
-
const innerId = idOf(activity.object);
|
|
559
|
-
const undone = wire.undoActivityId(this.urls, serial);
|
|
560
|
-
if (inner?.type === 'Block') {
|
|
561
|
-
const target = idOf(inner.object);
|
|
562
|
-
if (!target) return reply(400, { error: 'unblock whom?' });
|
|
563
|
-
await social.unblockActor(agent, target, { serial });
|
|
564
|
-
// A group's unban was sent, and recorded as it went; a person's is not.
|
|
565
|
-
if (this.store.getConfig()?.kind !== 'group') {
|
|
566
|
-
kept('Undo', { object: { type: 'Block', actor: this.urls.actor, object: target } });
|
|
567
|
-
}
|
|
568
|
-
return made(undone, { object: target });
|
|
569
|
-
}
|
|
570
|
-
const statuses = this.store.getStatuses();
|
|
571
|
-
let s = innerId ? statuses.find((x) => x.likeActivity?.id === innerId) : null;
|
|
572
|
-
if (!s && inner?.type === 'Like') s = this.byIri(idOf(inner.object));
|
|
573
|
-
if (s?.favourited) {
|
|
574
|
-
const updated = await social.unfavourite(agent, s, { serial });
|
|
575
|
-
return made(undone, { object: updated.noteId });
|
|
576
|
-
}
|
|
577
|
-
s = innerId ? statuses.find((x) => x.announceActivity?.id === innerId) : null;
|
|
578
|
-
if (!s && inner?.type === 'Announce') s = this.byIri(idOf(inner.object));
|
|
579
|
-
if (s?.reblogged) {
|
|
580
|
-
const updated = await social.unreblog(agent, s, { serial });
|
|
581
|
-
return made(undone, { object: updated.noteId });
|
|
582
|
-
}
|
|
583
|
-
const following = this.store.getContacts().following;
|
|
584
|
-
const rec = following.find((f) => f.followActivity?.id === innerId)
|
|
585
|
-
|| (inner?.type === 'Follow' ? following.find((f) => f.actor === idOf(inner.object)) : null);
|
|
586
|
-
if (rec) {
|
|
587
|
-
await social.unfollowActor(agent, rec.actor, { serial });
|
|
588
|
-
return made(undone, { object: rec.actor });
|
|
589
|
-
}
|
|
590
|
-
return reply(422, { error: 'nothing here matches what that Undo names' });
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
case 'Block': {
|
|
594
|
-
if (!objectId) return reply(400, { error: 'block whom? object must name an actor' });
|
|
595
|
-
await social.blockActor(agent, objectId, { serial });
|
|
596
|
-
// A group's ban was sent to its members; a person's never goes anywhere.
|
|
597
|
-
const id = this.store.getConfig()?.kind === 'group'
|
|
598
|
-
? `${this.urls.actor}#block-${serial}` : kept('Block', { object: objectId });
|
|
599
|
-
return made(id, { object: objectId });
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
case 'Add':
|
|
603
|
-
case 'Remove': {
|
|
604
|
-
// The one collection a client may edit is the pins (§7.6/§7.9 in the
|
|
605
|
-
// other direction): target must be the featured collection.
|
|
606
|
-
if (idOf(activity.target) !== this.urls.featured) {
|
|
607
|
-
return reply(422, { error: 'the featured collection is the one Add/Remove edits here' });
|
|
608
|
-
}
|
|
609
|
-
const s = this.byIri(objectId);
|
|
610
|
-
if (!s) return reply(404, { error: 'no such note here' });
|
|
611
|
-
const updated = await social.pinStatus(agent, s, activity.type === 'Add');
|
|
612
|
-
return made(kept(activity.type, { object: updated.noteId, target: this.urls.featured }),
|
|
613
|
-
{ object: updated.noteId, pinned: !!updated.pinned });
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
case 'Accept':
|
|
617
|
-
case 'Reject': {
|
|
618
|
-
// Answering a held follow request: the object is the Follow (or the
|
|
619
|
-
// requester). Which request is meant comes from the Follow's actor.
|
|
620
|
-
const requester = object?.actor ? idOf(object.actor) : objectId;
|
|
621
|
-
if (!requester) return reply(400, { error: 'whose request? object must name the Follow or its actor' });
|
|
622
|
-
const r = activity.type === 'Accept'
|
|
623
|
-
? await social.admitRequest(agent, requester, { serial }).catch((e) => ({ error: e.message }))
|
|
624
|
-
: await social.refuseRequest(agent, requester, { serial }).catch((e) => ({ error: e.message }));
|
|
625
|
-
if (r.error) return reply(404, { error: r.error });
|
|
626
|
-
return made(`${this.urls.actor}#${activity.type.toLowerCase()}-${serial}`, { object: requester });
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
case 'Move':
|
|
630
|
-
return reply(422, { error: 'moving the account is done on the admin surface, where it asks twice' });
|
|
631
|
-
|
|
632
|
-
default:
|
|
633
|
-
return reply(422, { error: `no handler for ${activity.type}` });
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
}
|