fedipod-server 0.29.0 → 0.31.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 +16 -6
- package/dist/claims.d.ts +2 -0
- package/dist/claims.js +13 -4
- package/dist/door.d.ts +4 -0
- package/dist/door.js +31 -0
- package/dist/fedipod.d.ts +15 -0
- package/dist/fedipod.js +13 -0
- package/dist/front.d.ts +3 -0
- package/dist/front.js +92 -0
- package/dist/handler.d.ts +36 -101
- package/dist/handler.js +93 -644
- package/dist/handler.jsonld +2 -30
- package/dist/identities.d.ts +71 -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/private-read.d.ts +4 -0
- package/dist/private-read.js +34 -0
- package/package.json +12 -20
- 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/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
|
-
}
|
|
@@ -1,310 +0,0 @@
|
|
|
1
|
-
// accounts.mjs — the account endpoints: the owner's credentials and profile
|
|
2
|
-
// editor, follow requests, markers, relationships, search and lookup, block
|
|
3
|
-
// and mute, follow and unfollow, an account and its lists and statuses, and
|
|
4
|
-
// the web-push subscription a client login holds.
|
|
5
|
-
|
|
6
|
-
import crypto from 'node:crypto';
|
|
7
|
-
import * as podMedia from '../../pod/media.mjs';
|
|
8
|
-
import * as social from '../../core/social.mjs';
|
|
9
|
-
import { readBody, readMultipart } from './body.mjs';
|
|
10
|
-
|
|
11
|
-
export async function handle(api, ctx) {
|
|
12
|
-
const { req, res, pathname, url, send } = ctx; // eslint-disable-line no-unused-vars
|
|
13
|
-
|
|
14
|
-
if (pathname === '/api/v1/accounts/verify_credentials') {
|
|
15
|
-
const cfg0 = api.store.getConfig() || {};
|
|
16
|
-
return send(200, {
|
|
17
|
-
...api.selfAccount(),
|
|
18
|
-
// `source` is what the editor fills its inputs from: the raw text it
|
|
19
|
-
// will send back, not the HTML the profile renders.
|
|
20
|
-
source: {
|
|
21
|
-
privacy: 'public', sensitive: false, language: 'en',
|
|
22
|
-
note: cfg0.summary || '',
|
|
23
|
-
fields: (cfg0.fields || []).map(f => ({ name: f.name, value: f.value })),
|
|
24
|
-
},
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// The profile editor. Everything it can send is carried: the name and bio,
|
|
29
|
-
// both pictures, and the extra fields. An avatar or header arrives as file
|
|
30
|
-
// bytes, so it goes to the pod's media container first and the actor gets
|
|
31
|
-
// the URL — the same path a posted attachment takes.
|
|
32
|
-
if (pathname === '/api/v1/accounts/update_credentials') {
|
|
33
|
-
if (req.method !== 'PATCH' && req.method !== 'POST') return send(405, { error: 'PATCH expected' });
|
|
34
|
-
const ct = String(req.headers['content-type'] || '');
|
|
35
|
-
// readBody already covers JSON and urlencoded; only the file case differs.
|
|
36
|
-
let form = {}, files = {};
|
|
37
|
-
if (ct.includes('multipart/form-data')) ({ fields: form, files } = await readMultipart(req));
|
|
38
|
-
else form = await readBody(req);
|
|
39
|
-
|
|
40
|
-
const cfg = { ...api.store.getConfig() };
|
|
41
|
-
const putImage = async (f) => {
|
|
42
|
-
const ext = (f.filename || '').includes('.')
|
|
43
|
-
? f.filename.split('.').pop().replace(/[^\w]/g, '') : 'bin';
|
|
44
|
-
const slug = new Date().toISOString().slice(0, 10) + '-' + crypto.randomBytes(4).toString('hex') + '.' + ext;
|
|
45
|
-
const url = api.urls.media + slug;
|
|
46
|
-
await api.agent.publisher.ensureMediaContainer();
|
|
47
|
-
await podMedia.write(api.agent.remote, url, f.data, f.contentType);
|
|
48
|
-
return url;
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
if ('display_name' in form) cfg.name = String(form.display_name).trim() || cfg.handle;
|
|
52
|
-
if ('note' in form) cfg.summary = String(form.note) || undefined;
|
|
53
|
-
if ('locked' in form) cfg.approveJoins = form.locked === 'true' || form.locked === true;
|
|
54
|
-
if (files.avatar?.data?.length) cfg.icon = await putImage(files.avatar);
|
|
55
|
-
if (files.header?.data?.length) cfg.image = await putImage(files.header);
|
|
56
|
-
|
|
57
|
-
// fields_attributes arrives as fields_attributes[0][name] etc. A row with
|
|
58
|
-
// no name is how the editor says "delete this one", so it is dropped.
|
|
59
|
-
const rows = [];
|
|
60
|
-
for (const [k, v] of Object.entries(form)) {
|
|
61
|
-
const m = /^fields_attributes\[(\d+)\]\[(name|value)\]$/.exec(k);
|
|
62
|
-
if (!m) continue;
|
|
63
|
-
(rows[Number(m[1])] ||= {})[m[2]] = String(v);
|
|
64
|
-
}
|
|
65
|
-
if (rows.length) cfg.fields = rows.filter(r => r && r.name?.trim())
|
|
66
|
-
.map(r => ({ name: r.name.trim(), value: (r.value || '').trim() }));
|
|
67
|
-
|
|
68
|
-
api.store.setConfig(cfg);
|
|
69
|
-
Object.assign(api.agent.publisher.config, {
|
|
70
|
-
name: cfg.name, summary: cfg.summary, icon: cfg.icon, image: cfg.image,
|
|
71
|
-
fields: cfg.fields, approveJoins: !!cfg.approveJoins,
|
|
72
|
-
});
|
|
73
|
-
await api.store.flush();
|
|
74
|
-
// publishProfile says whether the world can actually read the actor it
|
|
75
|
-
// just wrote. Discarding that reported success for a save that left the
|
|
76
|
-
// account undiscoverable — the one outcome the caller needed to hear.
|
|
77
|
-
const published = await api.agent.publisher.publishProfile();
|
|
78
|
-
const unreachable = published?.unreachable;
|
|
79
|
-
if (unreachable?.length) {
|
|
80
|
-
api.log(`profile saved but NOT publicly readable: ${unreachable.join(', ')}`);
|
|
81
|
-
}
|
|
82
|
-
api.log(`profile updated from a client: ${Object.keys(form).join(', ') || '(files only)'}`);
|
|
83
|
-
return send(200, api.selfAccount());
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Follow requests. The queue, and the two answers to it, have existed since
|
|
87
|
-
// groups did — `agent.store.getRequests()`, `admitRequest`, `refuseRequest`,
|
|
88
|
-
// all driven from the record page — but the facade stubbed the list to `[]`
|
|
89
|
-
// and offered no authorize/reject. So a locked account could see and answer
|
|
90
|
-
// its requests in FediPod's own page and in NO Mastodon client: Phanpy,
|
|
91
|
-
// Tuba and Whalebird all showed nothing waiting.
|
|
92
|
-
if (pathname === '/api/v1/follow_requests' && req.method === 'GET') {
|
|
93
|
-
const limit = Math.min(Number(url.searchParams.get('limit')) || 40, 80);
|
|
94
|
-
return send(200, api.store.getRequests().slice(0, limit)
|
|
95
|
-
.map((r) => api.account(r.actor)));
|
|
96
|
-
}
|
|
97
|
-
const mReq = /^\/api\/v1\/follow_requests\/([a-f0-9]+)\/(authorize|reject)$/.exec(pathname);
|
|
98
|
-
if (mReq && req.method === 'POST') {
|
|
99
|
-
// The client addresses an account by the id it was given for it, which is
|
|
100
|
-
// this store's own hash of the actor URL — the same one every other
|
|
101
|
-
// account route here uses.
|
|
102
|
-
const actorUrl = api.store.urlFor(mReq[1]);
|
|
103
|
-
if (!actorUrl) return send(404, { error: 'Record not found' });
|
|
104
|
-
if (!api.store.getRequests().some((r) => r.actor === actorUrl)) {
|
|
105
|
-
return send(404, { error: 'Record not found' });
|
|
106
|
-
}
|
|
107
|
-
try {
|
|
108
|
-
if (mReq[2] === 'authorize') await social.admitRequest(api.agent, actorUrl);
|
|
109
|
-
else await social.refuseRequest(api.agent, actorUrl);
|
|
110
|
-
} catch (e) { return send(422, { error: e.message }); }
|
|
111
|
-
await api.store.flush();
|
|
112
|
-
return send(200, api.relationship(actorUrl));
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (pathname === '/api/v1/markers') {
|
|
116
|
-
if (req.method === 'POST') {
|
|
117
|
-
const body = await readBody(req);
|
|
118
|
-
const markers = api.store.read('masto-markers.json', {});
|
|
119
|
-
for (const [k, v] of Object.entries(body)) {
|
|
120
|
-
const lastId = v?.last_read_id || v;
|
|
121
|
-
if (typeof lastId === 'string') {
|
|
122
|
-
markers[k] = { last_read_id: lastId, version: (markers[k]?.version || 0) + 1, updated_at: new Date().toISOString() };
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
api.store.write('masto-markers.json', markers);
|
|
126
|
-
return send(200, markers);
|
|
127
|
-
}
|
|
128
|
-
return send(200, api.store.read('masto-markers.json', {}));
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
if (pathname === '/api/v1/accounts/relationships') {
|
|
132
|
-
const ids = [...url.searchParams.getAll('id[]'), ...url.searchParams.getAll('id')];
|
|
133
|
-
const rels = ids.map(id => api.store.urlFor(id)).filter(Boolean).map(u => api.relationship(u));
|
|
134
|
-
return send(200, rels);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
if (pathname === '/api/v1/accounts/search') {
|
|
138
|
-
return send(200, await api.accountSearch(url.searchParams.get('q')));
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
if (pathname === '/api/v1/accounts/lookup') {
|
|
142
|
-
const acct = String(url.searchParams.get('acct') || '').replace(/^@/, '');
|
|
143
|
-
const cfg = api.store.getConfig();
|
|
144
|
-
if (acct === cfg?.handle || acct === `${cfg?.handle}@${api.host}`) {
|
|
145
|
-
return send(200, api.selfAccount());
|
|
146
|
-
}
|
|
147
|
-
// By host with its port, as the account is shown; and by hostname alone,
|
|
148
|
-
// for a client that drops the port from an address it was given.
|
|
149
|
-
const hit = Object.entries(api.store.getActors()).find(([u, a]) => {
|
|
150
|
-
try {
|
|
151
|
-
const at = new URL(u);
|
|
152
|
-
return `${a.preferredUsername}@${at.host}` === acct || `${a.preferredUsername}@${at.hostname}` === acct;
|
|
153
|
-
} catch { return false; }
|
|
154
|
-
});
|
|
155
|
-
return hit ? send(200, api.account(hit[0])) : send(404, { error: 'Record not found' });
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Block and mute, from where the trouble is seen. A block also unfollows —
|
|
159
|
-
// intake refuses a blocked author already — and a mute is view-only: their
|
|
160
|
-
// posts stay out of the timelines, nothing federates.
|
|
161
|
-
const mRel = /^\/api\/v1\/accounts\/([a-f0-9]+)\/(block|unblock|mute|unmute)$/.exec(pathname);
|
|
162
|
-
if (mRel && req.method === 'POST') {
|
|
163
|
-
const actorUrl = api.store.urlFor(mRel[1]);
|
|
164
|
-
if (!actorUrl) return send(404, { error: 'Record not found' });
|
|
165
|
-
if (mRel[2] === 'block' || mRel[2] === 'unblock') {
|
|
166
|
-
if (mRel[2] === 'block') await social.blockActor(api.agent, actorUrl);
|
|
167
|
-
else await social.unblockActor(api.agent, actorUrl);
|
|
168
|
-
} else {
|
|
169
|
-
const m = api.store.getMuted();
|
|
170
|
-
if (mRel[2] === 'mute' && !m.actors.includes(actorUrl)) m.actors.push(actorUrl);
|
|
171
|
-
if (mRel[2] === 'unmute') m.actors = m.actors.filter(a => a !== actorUrl);
|
|
172
|
-
api.store.setMuted(m);
|
|
173
|
-
}
|
|
174
|
-
return send(200, api.relationship(actorUrl));
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
const mFollow = /^\/api\/v1\/accounts\/([a-f0-9]+)\/(follow|unfollow)$/.exec(pathname);
|
|
178
|
-
if (mFollow && req.method === 'POST') {
|
|
179
|
-
const actorUrl = api.store.urlFor(mFollow[1]);
|
|
180
|
-
if (!actorUrl) return send(404, { error: 'Record not found' });
|
|
181
|
-
if (mFollow[2] === 'follow') await social.followActor(api.agent, actorUrl);
|
|
182
|
-
else await social.unfollowActor(api.agent, actorUrl).catch(() => {}); // already-gone is fine
|
|
183
|
-
return send(200, api.relationship(actorUrl));
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
const mAccount = /^\/api\/v1\/accounts\/([a-f0-9]+)$/.exec(pathname);
|
|
187
|
-
if (mAccount && req.method === 'GET') {
|
|
188
|
-
const actorUrl = api.store.urlFor(mAccount[1]);
|
|
189
|
-
return actorUrl ? send(200, api.account(actorUrl)) : send(404, { error: 'Record not found' });
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// Web push: one subscription per client login. The agent pushes payloads
|
|
193
|
-
// to the browser's push service itself, so a closed client still hears.
|
|
194
|
-
if (pathname === '/api/v1/push/subscription') {
|
|
195
|
-
const token = (/^Bearer (.+)$/.exec(req.headers.authorization || '') || [])[1];
|
|
196
|
-
if (!token) return send(401, { error: 'The access token is invalid' });
|
|
197
|
-
// A client that ignores the missing `vapid` and subscribes anyway must
|
|
198
|
-
// not be told it worked — a stored subscription nothing ever pushes to is
|
|
199
|
-
// the same silent nothing the toggle was.
|
|
200
|
-
if (!api.webPush) return send(422, { error: 'this instance does not send web push' });
|
|
201
|
-
if (req.method === 'GET') {
|
|
202
|
-
const sub = api.push.get(token);
|
|
203
|
-
return sub ? send(200, api.push.json(token, sub)) : send(404, { error: 'Record not found' });
|
|
204
|
-
}
|
|
205
|
-
if (req.method === 'POST') {
|
|
206
|
-
const body = await readBody(req);
|
|
207
|
-
const sub = api.push.set(token, {
|
|
208
|
-
endpoint: body.subscription?.endpoint,
|
|
209
|
-
keys: body.subscription?.keys,
|
|
210
|
-
alerts: body.data?.alerts,
|
|
211
|
-
});
|
|
212
|
-
if (!sub) return send(422, { error: 'a https endpoint and p256dh/auth keys are required' });
|
|
213
|
-
return send(200, api.push.json(token, sub));
|
|
214
|
-
}
|
|
215
|
-
if (req.method === 'PUT') {
|
|
216
|
-
const body = await readBody(req);
|
|
217
|
-
const sub = api.push.setAlerts(token, body.data?.alerts);
|
|
218
|
-
return sub ? send(200, api.push.json(token, sub)) : send(404, { error: 'Record not found' });
|
|
219
|
-
}
|
|
220
|
-
if (req.method === 'DELETE') {
|
|
221
|
-
api.push.drop(token);
|
|
222
|
-
return send(200, {});
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
if (/^\/api\/v1\/accounts\/[a-f0-9]+\/featured_tags$/.test(pathname)) return send(200, []);
|
|
227
|
-
|
|
228
|
-
// The instance's own directory is its one account; there is nothing to
|
|
229
|
-
// suggest and nothing trending, so those screens are empty rather than 404.
|
|
230
|
-
if (pathname === '/api/v1/directory') return send(200, api.urls?.actor ? [api.account(api.urls.actor)] : []);
|
|
231
|
-
if (pathname === '/api/v1/suggestions') return send(200, []);
|
|
232
|
-
|
|
233
|
-
// The lists this account is on — what "add to list" from a profile reads.
|
|
234
|
-
const mAccLists = /^\/api\/v1\/accounts\/([a-f0-9]+)\/lists$/.exec(pathname);
|
|
235
|
-
if (mAccLists && req.method === 'GET') {
|
|
236
|
-
const actorUrl = api.store.urlFor(mAccLists[1]);
|
|
237
|
-
if (!actorUrl) return send(404, { error: 'Record not found' });
|
|
238
|
-
return send(200, api.store.getLists().filter(l => (l.members || []).includes(actorUrl))
|
|
239
|
-
.map(l => ({ id: l.id, title: l.title, replies_policy: l.repliesPolicy || 'list', exclusive: false })));
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// The people muted and blocked, as accounts; the domains blocked, as names.
|
|
243
|
-
if (pathname === '/api/v1/mutes' && req.method === 'GET') {
|
|
244
|
-
return send(200, (api.store.getMuted().actors || []).map(a => api.account(a)));
|
|
245
|
-
}
|
|
246
|
-
if (pathname === '/api/v1/blocks' && req.method === 'GET') {
|
|
247
|
-
return send(200, (api.store.getBlocklist().actors || []).map(a => api.account(a)));
|
|
248
|
-
}
|
|
249
|
-
if (pathname === '/api/v1/domain_blocks') {
|
|
250
|
-
const b = api.store.getBlocklist();
|
|
251
|
-
if (req.method === 'GET') return send(200, [...b.domains]);
|
|
252
|
-
const body = await readBody(req);
|
|
253
|
-
const domain = String(body.domain || url.searchParams.get('domain') || '').trim().toLowerCase();
|
|
254
|
-
if (!/^[a-z0-9.-]+\.[a-z]{2,}$/u.test(domain)) return send(422, { error: 'a domain is required' });
|
|
255
|
-
if (req.method === 'POST' && !b.domains.includes(domain)) b.domains.push(domain);
|
|
256
|
-
if (req.method === 'DELETE') b.domains = b.domains.filter(d => d !== domain);
|
|
257
|
-
api.store.setBlocklist(b);
|
|
258
|
-
Promise.resolve(api.agent?.publisher?.publishCollections?.({ blocked: true })).catch(() => {});
|
|
259
|
-
return send(200, {});
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
// The counts these back are rendered from the same two arrays (see
|
|
263
|
-
// `account`), so a client that shows a number here can always open it.
|
|
264
|
-
const mAccList = /^\/api\/v1\/accounts\/([a-f0-9]+)\/(following|followers)$/.exec(pathname);
|
|
265
|
-
if (mAccList && req.method === 'GET') {
|
|
266
|
-
const actorUrl = api.store.urlFor(mAccList[1]);
|
|
267
|
-
if (!actorUrl) return send(404, { error: 'Record not found' });
|
|
268
|
-
// Only our own lists are known. A remote actor's collections live on its
|
|
269
|
-
// own server, and opening a profile is not worth a fetch of a stranger's
|
|
270
|
-
// pod — an empty list, not an error, is what the API can honestly say.
|
|
271
|
-
const mine = actorUrl === api.urls?.actor;
|
|
272
|
-
const c = api.store.getContacts();
|
|
273
|
-
// Reversed: the contact arrays append, and page()'s cursors read
|
|
274
|
-
// newest-first — fed as stored, since_id answered with its complement.
|
|
275
|
-
const recs = (!mine ? []
|
|
276
|
-
: mAccList[2] === 'followers' ? c.followers
|
|
277
|
-
: c.following.filter(f => f.accepted)) // pending is not following
|
|
278
|
-
.slice().reverse();
|
|
279
|
-
const { items, headers } = api.page(recs, url,
|
|
280
|
-
{ limit: 40, max: 80, idOf: (r) => api.store.idFor(r.actor) });
|
|
281
|
-
return send(200, items.map(r => api.account(r.actor)), headers);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// Who among the people I follow also follows THEM — a graph we do not hold,
|
|
285
|
-
// and the shape is an entry per requested account, not a bare list.
|
|
286
|
-
if (pathname === '/api/v1/accounts/familiar_followers') {
|
|
287
|
-
const ids = [...url.searchParams.getAll('id[]'), ...url.searchParams.getAll('id')];
|
|
288
|
-
return send(200, ids.map(id => ({ id, accounts: [] })));
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
const mAccStatuses = /^\/api\/v1\/accounts\/([a-f0-9]+)\/statuses$/.exec(pathname);
|
|
292
|
-
if (mAccStatuses) {
|
|
293
|
-
const actorUrl = api.store.urlFor(mAccStatuses[1]);
|
|
294
|
-
const all = api.store.getStatuses();
|
|
295
|
-
const q = url.searchParams;
|
|
296
|
-
const is = (name) => q.get(name) === 'true' || q.get(name) === '1';
|
|
297
|
-
const pinnedOnly = is('pinned');
|
|
298
|
-
const tagged = String(q.get('tagged') || '').replace(/^#/, '').toLowerCase();
|
|
299
|
-
const isBoost = (s) => !!s.via && s.via !== s.actor;
|
|
300
|
-
const { items, headers } = api.page(
|
|
301
|
-
all.filter(s => s.actor === actorUrl && (!pinnedOnly || s.pinned))
|
|
302
|
-
.filter(s => !is('exclude_replies') || !s.inReplyTo)
|
|
303
|
-
.filter(s => !is('exclude_reblogs') || !isBoost(s))
|
|
304
|
-
.filter(s => !is('only_media') || (s.attachments || []).length > 0)
|
|
305
|
-
.filter(s => !tagged || (s.tags || []).some(t => String(t.name).toLowerCase() === tagged)), url);
|
|
306
|
-
return send(200, items.map(s => api.statusOrBoost(s, { all })), headers);
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
return false;
|
|
310
|
-
}
|
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
// body.mjs — reading what a client sent: JSON, form-encoded or multipart
|
|
2
|
-
// bodies, the poll a compose request carries, and the text of a post back out
|
|
3
|
-
// of its HTML.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* A poll out of a compose request, or null when there is none. A JSON client
|
|
9
|
-
* sends a `poll` object; a form-encoded one spells the same thing out in
|
|
10
|
-
* Rails's bracket notation, which is the shape the option list arrives in.
|
|
11
|
-
*/
|
|
12
|
-
export function pollParams(body) {
|
|
13
|
-
const nested = body?.poll && typeof body.poll === 'object' ? body.poll : null;
|
|
14
|
-
const options = [].concat(nested?.options ?? body?.['poll[options][]'] ?? [])
|
|
15
|
-
.map(o => String(o ?? '').trim()).filter(Boolean);
|
|
16
|
-
const rawExpiry = nested?.expires_in ?? body?.['poll[expires_in]'];
|
|
17
|
-
const rawMultiple = nested?.multiple ?? body?.['poll[multiple]'];
|
|
18
|
-
if (!options.length && rawExpiry === undefined) return null;
|
|
19
|
-
return {
|
|
20
|
-
options,
|
|
21
|
-
expiresIn: rawExpiry === undefined || rawExpiry === null || rawExpiry === ''
|
|
22
|
-
? null : Number(rawExpiry),
|
|
23
|
-
multiple: rawMultiple === true || rawMultiple === 'true' || rawMultiple === '1',
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Accepts JSON or form-encoded bodies (OAuth posts are often form-encoded).
|
|
28
|
-
// The source of a post that predates raw-text storage: its HTML back to
|
|
29
|
-
// typed text, near enough to edit.
|
|
30
|
-
export function htmlToText(html) {
|
|
31
|
-
return String(html)
|
|
32
|
-
.replace(/<br\s*\/?>/gi, '\n')
|
|
33
|
-
.replace(/<\/p>\s*<p[^>]*>/gi, '\n\n')
|
|
34
|
-
.replace(/<[^>]+>/g, '')
|
|
35
|
-
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
36
|
-
.replace(/"/g, '"').replace(/'/g, "'")
|
|
37
|
-
.trim();
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export function readBody(req) {
|
|
41
|
-
return new Promise((resolve, reject) => {
|
|
42
|
-
let data = '';
|
|
43
|
-
req.on('data', c => {
|
|
44
|
-
data += c;
|
|
45
|
-
// Destroying the socket without settling left the awaiting handler
|
|
46
|
-
// pending for the life of the process and the client waiting on a
|
|
47
|
-
// response that would never come.
|
|
48
|
-
if (data.length > 1e6) { req.destroy(); reject(new Error('request body too large')); }
|
|
49
|
-
});
|
|
50
|
-
req.on('end', () => {
|
|
51
|
-
const ct = String(req.headers['content-type'] || '');
|
|
52
|
-
try {
|
|
53
|
-
if (ct.includes('application/json')) return resolve(data ? JSON.parse(data) : {});
|
|
54
|
-
// A multipart body is not a query string: read as one it becomes a
|
|
55
|
-
// single nonsense key, so every field a client sent that way arrived
|
|
56
|
-
// undefined. Sengi registers its OAuth client with a FormData, which
|
|
57
|
-
// is multipart, so /api/v1/apps saw no redirect_uris and handed back
|
|
58
|
-
// the out-of-band URN, leaving the client nowhere to come back to.
|
|
59
|
-
if (ct.includes('multipart/form-data')) return resolve(textFields(data, ct));
|
|
60
|
-
// A form-encoded list is the same key repeated, spelled with a
|
|
61
|
-
// trailing `[]`. Reading it as a plain object kept only the last one,
|
|
62
|
-
// so a client sending its poll or its media that way lost all but the
|
|
63
|
-
// final value. Only the `[]` keys become lists: everything else keeps
|
|
64
|
-
// the single value the rest of this file reads.
|
|
65
|
-
const form = new URLSearchParams(data);
|
|
66
|
-
const out = {};
|
|
67
|
-
for (const key of new Set(form.keys())) {
|
|
68
|
-
out[key] = key.endsWith('[]') ? form.getAll(key) : form.get(key);
|
|
69
|
-
}
|
|
70
|
-
resolve(out);
|
|
71
|
-
} catch (e) { reject(e); }
|
|
72
|
-
});
|
|
73
|
-
req.on('error', reject);
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* The text fields of a multipart body that arrived as text. readBody
|
|
79
|
-
* accumulates the body as a string, so this reads the fields out of that
|
|
80
|
-
* string rather than out of bytes — which is all a client sending a plain
|
|
81
|
-
* form this way has in it. A body with a FILE in it is not this reader's:
|
|
82
|
-
* a text round trip corrupts binary, and the upload endpoints take their
|
|
83
|
-
* bytes from readMultipart below.
|
|
84
|
-
*/
|
|
85
|
-
function textFields(data, contentType) {
|
|
86
|
-
const m = /boundary=(?:"([^"]+)"|([^;]+))/.exec(contentType);
|
|
87
|
-
if (!m) return {};
|
|
88
|
-
const out = {};
|
|
89
|
-
for (const part of data.split('--' + (m[1] || m[2]).trim())) {
|
|
90
|
-
const sep = part.indexOf('\r\n\r\n');
|
|
91
|
-
if (sep < 0) continue;
|
|
92
|
-
const head = part.slice(0, sep);
|
|
93
|
-
const name = /name="([^"]*)"/.exec(head)?.[1];
|
|
94
|
-
// A file part is skipped rather than kept as mangled text.
|
|
95
|
-
if (!name || /filename="/.test(head)) continue;
|
|
96
|
-
const value = part.slice(sep + 4).replace(/\r\n$/, '');
|
|
97
|
-
if (name.endsWith('[]')) (out[name] ||= []).push(value);
|
|
98
|
-
else out[name] = value;
|
|
99
|
-
}
|
|
100
|
-
return out;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Minimal multipart/form-data reader for media uploads: string fields plus
|
|
104
|
-
// at most one file part (Mastodon's media endpoints send exactly one).
|
|
105
|
-
export function readMultipart(req, limit = 12e6) {
|
|
106
|
-
return new Promise((resolve, reject) => {
|
|
107
|
-
const chunks = [];
|
|
108
|
-
let n = 0;
|
|
109
|
-
req.on('data', c => {
|
|
110
|
-
n += c.length;
|
|
111
|
-
if (n > limit) { reject(new Error('upload too large')); req.destroy(); return; }
|
|
112
|
-
chunks.push(c);
|
|
113
|
-
});
|
|
114
|
-
req.on('error', reject);
|
|
115
|
-
req.on('end', () => {
|
|
116
|
-
try {
|
|
117
|
-
const m = /boundary=(?:"([^"]+)"|([^;]+))/.exec(String(req.headers['content-type'] || ''));
|
|
118
|
-
if (!m) return resolve({ fields: {}, file: null });
|
|
119
|
-
const buf = Buffer.concat(chunks);
|
|
120
|
-
const boundary = Buffer.from('--' + (m[1] || m[2]).trim());
|
|
121
|
-
const fields = {};
|
|
122
|
-
// `file` is the last one seen, which is what the single-file media
|
|
123
|
-
// upload wants. `files` keys them by field name, because the profile
|
|
124
|
-
// editor submits an avatar and a header in one request.
|
|
125
|
-
const files = {};
|
|
126
|
-
let file = null;
|
|
127
|
-
let i = buf.indexOf(boundary);
|
|
128
|
-
while (i >= 0) {
|
|
129
|
-
const start = i + boundary.length;
|
|
130
|
-
if (buf.slice(start, start + 2).toString() === '--') break;
|
|
131
|
-
const next = buf.indexOf(boundary, start);
|
|
132
|
-
if (next < 0) break;
|
|
133
|
-
const part = buf.slice(start + 2, next - 2); // strip the CRLFs framing the part
|
|
134
|
-
const sep = part.indexOf('\r\n\r\n');
|
|
135
|
-
if (sep >= 0) {
|
|
136
|
-
const head = part.slice(0, sep).toString();
|
|
137
|
-
const body = part.slice(sep + 4);
|
|
138
|
-
const name = /name="([^"]*)"/.exec(head)?.[1];
|
|
139
|
-
const filename = /filename="([^"]*)"/.exec(head)?.[1];
|
|
140
|
-
if (filename !== undefined) {
|
|
141
|
-
file = {
|
|
142
|
-
filename,
|
|
143
|
-
contentType: /content-type:\s*([^\r\n]+)/i.exec(head)?.[1]?.trim() || 'application/octet-stream',
|
|
144
|
-
data: body,
|
|
145
|
-
};
|
|
146
|
-
if (name) files[name] = file;
|
|
147
|
-
} else if (name) fields[name] = body.toString();
|
|
148
|
-
}
|
|
149
|
-
i = next;
|
|
150
|
-
}
|
|
151
|
-
resolve({ fields, file, files });
|
|
152
|
-
} catch (e) { reject(e); }
|
|
153
|
-
});
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
|