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/core/lease.mjs
DELETED
|
@@ -1,206 +0,0 @@
|
|
|
1
|
-
// lease.mjs — single-active-agent coordination. The inbox drain is a
|
|
2
|
-
// destructive read and the state store is write-through-cached, so exactly
|
|
3
|
-
// one agent may ACT on a pod at a time; later arrivals run as read-only
|
|
4
|
-
// viewers. The lease is one JSON doc in ap-state, always read and written
|
|
5
|
-
// with FRESH fetches (never through the cached store), renewed while held.
|
|
6
|
-
// Best-effort by design: a lost renewal just means the other agent takes
|
|
7
|
-
// over at the next expiry.
|
|
8
|
-
|
|
9
|
-
import crypto from 'node:crypto';
|
|
10
|
-
|
|
11
|
-
// Renewal is the one thing that writes even when nothing is happening: at 30s
|
|
12
|
-
// it was 120 PUTs an hour per agent, each taking a write lock on the pod; at
|
|
13
|
-
// 90s, 40. Five minutes against a 15-minute TTL leaves two missed renewals of
|
|
14
|
-
// headroom and makes it 12. The cost is automatic promotion after a crash
|
|
15
|
-
// waiting up to the TTL — a user acting on a second device does not wait,
|
|
16
|
-
// since takeover() claims the lease outright.
|
|
17
|
-
const TTL_MS = 900_000;
|
|
18
|
-
const RENEW_MS = 300_000;
|
|
19
|
-
const JITTER = () => 0.85 + Math.random() * 0.3;
|
|
20
|
-
// Distinct from null: null is "nobody holds it", this is "we could not ask".
|
|
21
|
-
const UNREADABLE = Symbol('lease-unreadable');
|
|
22
|
-
|
|
23
|
-
export class Lease {
|
|
24
|
-
// `id` names the HOLDER, and acquire() lets a holder reclaim its own lease
|
|
25
|
-
// without waiting for the TTL — so whether it is stable decides whether a
|
|
26
|
-
// restart is the same agent coming back or a second one arriving. A process
|
|
27
|
-
// that is gone for good wants a new one each time; a browser does not, since
|
|
28
|
-
// its agent lives in a service worker the browser is free to kill and restart
|
|
29
|
-
// whenever it likes. Left to the default, every one of those restarts looked
|
|
30
|
-
// like another device and asked the owner to take over their own account —
|
|
31
|
-
// which is what switching between the two clients did, because that is a
|
|
32
|
-
// navigation and a navigation can take the worker with it. The browser build
|
|
33
|
-
// passes one it keeps.
|
|
34
|
-
constructor({ url, fetchImpl, log = console.log, id = null }) {
|
|
35
|
-
this.url = url;
|
|
36
|
-
this.fetchImpl = fetchImpl;
|
|
37
|
-
this.log = log;
|
|
38
|
-
this.id = id || crypto.randomUUID();
|
|
39
|
-
this.stopped = false;
|
|
40
|
-
this.timer = null;
|
|
41
|
-
this.heldUntil = 0; // wall-clock end of the lease we last wrote
|
|
42
|
-
this.denied = null; // why the last acquire() said no: 'unreadable' | 'held'
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// null means the lease document is NOT THERE — nobody holds it. UNREADABLE
|
|
46
|
-
// means we could not ask, which is a different answer and must not be read as
|
|
47
|
-
// "free": acquire() treated both as free, so a pod hiccup during acquire
|
|
48
|
-
// could hand a second agent a lease the first still holds, and both would
|
|
49
|
-
// drain the same inbox destructively.
|
|
50
|
-
async readFresh() {
|
|
51
|
-
let res, body;
|
|
52
|
-
try {
|
|
53
|
-
res = await this.fetchImpl(this.url, { headers: { accept: 'application/json' } });
|
|
54
|
-
if (res.status === 404 || res.status === 410) return null;
|
|
55
|
-
if (res.status >= 400) return UNREADABLE;
|
|
56
|
-
body = await res.text();
|
|
57
|
-
} catch { return UNREADABLE; }
|
|
58
|
-
this.etag = res.headers?.get?.('etag') || null;
|
|
59
|
-
// A 200 carrying garbage — a half-written PUT, a proxy error page served
|
|
60
|
-
// as 200 — is not a lease anybody holds, and overwriting it is the repair.
|
|
61
|
-
// Calling that UNREADABLE would leave the agent a viewer forever, because
|
|
62
|
-
// acquire() refuses to write over what it could not read.
|
|
63
|
-
try { return JSON.parse(body); }
|
|
64
|
-
catch { this.log('lease document is unparsable — treating it as unheld'); return null; }
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Returns false only for a refused If-Match — that is not an error, it is the
|
|
68
|
-
// answer. Anything else throws.
|
|
69
|
-
async write(doc, { ifMatch = null } = {}) {
|
|
70
|
-
const res = await this.fetchImpl(this.url, {
|
|
71
|
-
method: 'PUT',
|
|
72
|
-
headers: { 'content-type': 'application/json', ...(ifMatch ? { 'if-match': ifMatch } : {}) },
|
|
73
|
-
body: JSON.stringify(doc),
|
|
74
|
-
});
|
|
75
|
-
if (ifMatch && res.status === 412) return false;
|
|
76
|
-
if (res.status >= 400) throw new Error(`lease PUT → ${res.status}`);
|
|
77
|
-
this.etag = res.headers?.get?.('etag') || null;
|
|
78
|
-
return true;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// True when this agent now holds the lease. Write-then-confirm narrows the
|
|
82
|
-
// last-writer-wins race to a small window — acceptable for a human-scale
|
|
83
|
-
// "laptop vs phone" situation.
|
|
84
|
-
async acquire() {
|
|
85
|
-
const cur = await this.readFresh();
|
|
86
|
-
// Cannot read it: stay a viewer. A viewer that should have been active is
|
|
87
|
-
// an inconvenience; two agents both draining is the destructive one.
|
|
88
|
-
if (cur === UNREADABLE) {
|
|
89
|
-
this.denied = 'unreadable';
|
|
90
|
-
this.log('lease unreadable — staying a viewer'); return false;
|
|
91
|
-
}
|
|
92
|
-
if (cur && cur.holder !== this.id && Date.now() < cur.expiresAt) { this.denied = 'held'; return false; }
|
|
93
|
-
const doc = { holder: this.id, expiresAt: Date.now() + TTL_MS };
|
|
94
|
-
if (!await this.write(doc)) { this.denied = 'held'; return false; }
|
|
95
|
-
const confirm = await this.readFresh();
|
|
96
|
-
if (confirm === UNREADABLE) { this.denied = 'unreadable'; return false; }
|
|
97
|
-
if (confirm?.holder !== this.id) { this.denied = 'held'; return false; }
|
|
98
|
-
// From the document we WROTE, and only once the confirm agrees. Recomputing
|
|
99
|
-
// it here would claim a lease that outlives what the pod's copy grants, by
|
|
100
|
-
// however long the PUT and the confirming GET took.
|
|
101
|
-
this.heldUntil = doc.expiresAt;
|
|
102
|
-
this.denied = null;
|
|
103
|
-
return true;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Renewal checks ownership first: a takeover by another agent must win,
|
|
107
|
-
// not be clobbered by a blind rewrite. Losing the lease fires onLost once
|
|
108
|
-
// and stops renewing.
|
|
109
|
-
async renewOnce() {
|
|
110
|
-
// The lease we hold has a wall-clock end. If renewals keep failing — the
|
|
111
|
-
// pod unreachable, refusing, or slow — the TTL passes, another agent is
|
|
112
|
-
// entitled to take over, and this one went on draining as though nothing
|
|
113
|
-
// had happened. Two agents on one destructive inbox read is exactly what
|
|
114
|
-
// the lease exists to prevent, so an expiry we could not extend is a loss.
|
|
115
|
-
if (this.heldUntil && Date.now() > this.heldUntil) {
|
|
116
|
-
this.stopRenewal();
|
|
117
|
-
this.etag = null;
|
|
118
|
-
this.log('lease expired and could not be renewed — standing down');
|
|
119
|
-
this.onLost?.();
|
|
120
|
-
return false;
|
|
121
|
-
}
|
|
122
|
-
const doc = { holder: this.id, expiresAt: Date.now() + TTL_MS };
|
|
123
|
-
// The steady path is a single conditional PUT. A 412 means somebody else
|
|
124
|
-
// wrote the lease — the only thing the preceding GET was ever there to
|
|
125
|
-
// detect — so the read is paid for exactly when it matters instead of every
|
|
126
|
-
// 90 seconds forever. No etag (a server that does not return one) falls
|
|
127
|
-
// back to read-then-write.
|
|
128
|
-
if (this.etag && await this.write(doc, { ifMatch: this.etag })) { this.heldUntil = doc.expiresAt; return true; }
|
|
129
|
-
const cur = await this.readFresh();
|
|
130
|
-
// Before the ownership test, or the sentinel answers it: a Symbol is truthy
|
|
131
|
-
// and its .holder is undefined, so "could not ask" read as "somebody else
|
|
132
|
-
// holds it" and stood a perfectly healthy agent down on one 500.
|
|
133
|
-
if (cur === UNREADABLE) return true; // keep it; the next tick retries
|
|
134
|
-
if (cur && cur.holder !== this.id) {
|
|
135
|
-
// clearTimeout alone was a no-op here — the timer being cleared is the one
|
|
136
|
-
// whose own callback is running, and the chain re-armed in `finally`
|
|
137
|
-
// regardless. Stopping has to outlive this call, so it is a flag.
|
|
138
|
-
this.stopRenewal();
|
|
139
|
-
// readFresh just cached the NEW holder's ETag. Keeping it would let the
|
|
140
|
-
// next conditional PUT succeed and take the lease back, silently, from an
|
|
141
|
-
// agent that has already stood down.
|
|
142
|
-
this.etag = null;
|
|
143
|
-
this.log('lease taken over by another agent');
|
|
144
|
-
this.onLost?.();
|
|
145
|
-
return false;
|
|
146
|
-
}
|
|
147
|
-
await this.write(doc);
|
|
148
|
-
this.heldUntil = doc.expiresAt;
|
|
149
|
-
return true;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Self-scheduling rather than setInterval, so each agent's renewals drift
|
|
153
|
-
// apart instead of several beating in lockstep against one pod.
|
|
154
|
-
// Idempotent, like Deliverer.startQueue: startActive reaches this twice on the
|
|
155
|
-
// parked path, and two chains against one pod is the doubling this whole file
|
|
156
|
-
// is written to avoid.
|
|
157
|
-
startRenewal() {
|
|
158
|
-
if (this.timer) return;
|
|
159
|
-
this.stopped = false; // restartable across demote/takeover
|
|
160
|
-
const tick = () => {
|
|
161
|
-
if (this.stopped) return;
|
|
162
|
-
this.timer = setTimeout(() => {
|
|
163
|
-
this.renewOnce()
|
|
164
|
-
.catch(e => this.log(`lease renewal: ${e.message}`))
|
|
165
|
-
.finally(() => { if (!this.stopped) tick(); });
|
|
166
|
-
}, Math.round(RENEW_MS * JITTER()));
|
|
167
|
-
this.timer.unref?.();
|
|
168
|
-
};
|
|
169
|
-
tick();
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// Stop renewing WITHOUT giving the lease up — a demoted agent stands down, it
|
|
173
|
-
// does not release. Unstopped, the chain outlives demotion for the life of the
|
|
174
|
-
// process: ~80 requests an hour from an agent that is no longer acting.
|
|
175
|
-
stopRenewal() {
|
|
176
|
-
this.stopped = true;
|
|
177
|
-
clearTimeout(this.timer);
|
|
178
|
-
this.timer = null;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// Forcibly claim the lease (a user acting on a viewer device outranks the
|
|
182
|
-
// idle active agent). Write-then-confirm with one retry against the small
|
|
183
|
-
// window where the current holder's renewal lands in between.
|
|
184
|
-
async takeover() {
|
|
185
|
-
for (let attempt = 0; attempt < 2; attempt++) {
|
|
186
|
-
const doc = { holder: this.id, expiresAt: Date.now() + TTL_MS };
|
|
187
|
-
await this.write(doc);
|
|
188
|
-
const confirm = await this.readFresh();
|
|
189
|
-
if (confirm !== UNREADABLE && confirm?.holder === this.id) {
|
|
190
|
-
this.heldUntil = doc.expiresAt;
|
|
191
|
-
return true;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
return false;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// Asked by the destructive path before it acts. The stand-down otherwise
|
|
198
|
-
// waits for the next renewal tick, which is up to ~117s away, and the whole
|
|
199
|
-
// point of the TTL is that after it another agent may start draining.
|
|
200
|
-
stillHeld() { return !this.stopped && (!this.heldUntil || Date.now() < this.heldUntil); }
|
|
201
|
-
|
|
202
|
-
async release() {
|
|
203
|
-
this.stopRenewal();
|
|
204
|
-
await this.write({ holder: this.id, expiresAt: 0 }).catch(() => {});
|
|
205
|
-
}
|
|
206
|
-
}
|
package/lib/core/place.mjs
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
// place.mjs — where on a pod a FediPod account lives, and recording it.
|
|
2
|
-
//
|
|
3
|
-
// The account's container is always named `fedipod`, inside a container its
|
|
4
|
-
// owner chose. The choice is recorded in their public type index as an
|
|
5
|
-
// instance of as:Actor (lib/pod/type-index.mjs), and that record is how the
|
|
6
|
-
// account is found again. An account made before the choice existed sits at
|
|
7
|
-
// `<pod>fedipod/` with no record, and is found there.
|
|
8
|
-
|
|
9
|
-
import * as typeIndex from '../pod/type-index.mjs';
|
|
10
|
-
import { PodTransport } from '../pod/transport.mjs';
|
|
11
|
-
import { rootFromContainer, rootOfActor } from '../pod/location.mjs';
|
|
12
|
-
import { DEFAULT_ROOT } from './wire.mjs';
|
|
13
|
-
|
|
14
|
-
export { podRootPath } from '../pod/location.mjs';
|
|
15
|
-
|
|
16
|
-
/** The container someone typed, as the account's root — or why it cannot be. */
|
|
17
|
-
export const chosenRoot = (podBase, typed) => rootFromContainer(podBase, typed, DEFAULT_ROOT);
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* The roots of the accounts this pod's owner has recorded, then the place an
|
|
21
|
-
* older account sits without a record. `pod` is a transport acting as the owner.
|
|
22
|
-
*/
|
|
23
|
-
export async function candidateRoots(pod, podBase) {
|
|
24
|
-
const recorded = await typeIndex.registeredActors(pod, podBase).catch(() => []);
|
|
25
|
-
const roots = recorded.map(a => rootOfActor(podBase, a)).filter(Boolean);
|
|
26
|
-
return [...new Set([...roots, DEFAULT_ROOT])];
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* The account on this pod: the first candidate whose config `read(root)` finds
|
|
31
|
-
* and `accept(config)` takes. Null when there is none.
|
|
32
|
-
*/
|
|
33
|
-
export async function findAccount(pod, podBase, read, accept = () => true) {
|
|
34
|
-
for (const root of await candidateRoots(pod, podBase)) {
|
|
35
|
-
const config = await read(root).catch(() => null);
|
|
36
|
-
if (config && accept(config)) return { root, config };
|
|
37
|
-
}
|
|
38
|
-
return null;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Record the account's place in the owner's public type index. With no index,
|
|
43
|
-
* one is made only when `create` is true — the person's own yes. Returns what
|
|
44
|
-
* was done: 'registered', 'already', or 'no-index'.
|
|
45
|
-
*/
|
|
46
|
-
export async function recordPlace(pod, podBase, actorAtPod, { create = false } = {}) {
|
|
47
|
-
let index = await typeIndex.findPublicIndex(pod, podBase);
|
|
48
|
-
if (!index) {
|
|
49
|
-
if (!create) return 'no-index';
|
|
50
|
-
index = await typeIndex.createPublicIndex(pod, podBase);
|
|
51
|
-
}
|
|
52
|
-
return (await typeIndex.register(pod, index, actorAtPod)) ? 'registered' : 'already';
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/** Whether this person's profile names a public type index. */
|
|
56
|
-
export async function hasPublicIndex(pod, podBase) {
|
|
57
|
-
return !!(await typeIndex.findPublicIndex(pod, podBase).catch(() => null));
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Whether a pod's owner has a public type index, asked as a stranger before any
|
|
62
|
-
* credential exists: true, false, or null when the profile could not be read.
|
|
63
|
-
*/
|
|
64
|
-
export async function publicIndexKnown(fetchImpl, podBase, webId = `${podBase}profile/card#me`) {
|
|
65
|
-
const pod = new PodTransport({ fetch: fetchImpl }, { webId });
|
|
66
|
-
try { return !!(await typeIndex.findPublicIndex(pod, podBase)); } catch { return null; }
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* The root for a container typed before the pod's own path was known — a new
|
|
71
|
-
* pod: the answer is taken as inside the pod's root, whatever it turns out to be.
|
|
72
|
-
*/
|
|
73
|
-
export function chosenRootInNewPod(podBase, typed) {
|
|
74
|
-
const inner = String(typed ?? '').trim().replace(/^\/+/, '');
|
|
75
|
-
return chosenRoot(podBase, new URL(podBase).pathname + inner);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/** The error that stops a setup until its person has said yes to a new type index. */
|
|
79
|
-
export function needsIndex(detail) {
|
|
80
|
-
return Object.assign(new Error(`${detail} FediPod records where your account lives in your public type index, `
|
|
81
|
-
+ 'so it needs one. Say yes to creating it, or nothing is set up.'), { code: 'needs-index' });
|
|
82
|
-
}
|
package/lib/core/polls.mjs
DELETED
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
// polls.mjs — the count behind a Question.
|
|
2
|
-
//
|
|
3
|
-
// A poll travels as a Question whose options live in `oneOf` (pick one) or
|
|
4
|
-
// `anyOf` (pick several). A vote is not a special activity: it arrives as an
|
|
5
|
-
// ordinary reply carrying the option's `name` and no content. So the fediverse
|
|
6
|
-
// carries the votes and the author's server keeps the count — which means when
|
|
7
|
-
// the poll is ours, the roster of who chose what is ours to hold.
|
|
8
|
-
//
|
|
9
|
-
// Everything here is pure. The roster is a plain object the caller stores and
|
|
10
|
-
// hands back; the tallies are always DERIVED from it rather than kept beside
|
|
11
|
-
// it, so a count cannot drift away from the votes it is meant to summarize.
|
|
12
|
-
|
|
13
|
-
import crypto from 'node:crypto';
|
|
14
|
-
|
|
15
|
-
/** Where the roster lives in agent state. */
|
|
16
|
-
export const VOTES_DOC = 'poll-votes.json';
|
|
17
|
-
|
|
18
|
-
// What a poll may be. These are Mastodon's own limits, and they are the ones
|
|
19
|
-
// clients read out of the instance document to draw their poll composer, so a
|
|
20
|
-
// number we chose differently would be a number the composer then lets someone
|
|
21
|
-
// exceed. They live here rather than beside the client API because every way
|
|
22
|
-
// in has to honour them: an option is matched BY NAME when the vote comes
|
|
23
|
-
// back, so a long title is one something else in the network may truncate and
|
|
24
|
-
// hand back unrecognizable.
|
|
25
|
-
export const MAX_OPTIONS = 4;
|
|
26
|
-
export const MAX_OPTION_CHARS = 50;
|
|
27
|
-
export const MIN_SECONDS = 5 * 60;
|
|
28
|
-
export const MAX_SECONDS = 2629746; // a month, as Mastodon counts one
|
|
29
|
-
|
|
30
|
-
// A voter is recorded as a hash of their actor id rather than the id itself.
|
|
31
|
-
// The roster is rewritten on every vote inside a document we serialize whole,
|
|
32
|
-
// and a poll that travels would otherwise grow a list of everyone who took
|
|
33
|
-
// part. The hash is stable, which is all a duplicate check needs.
|
|
34
|
-
export function voterKey(actor) {
|
|
35
|
-
return crypto.createHash('sha256').update(String(actor)).digest('hex').slice(0, 16);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Does this note have the shape of a vote? A name, and no content: that is the
|
|
40
|
-
* whole convention. Anything carrying prose is a reply somebody wrote, and
|
|
41
|
-
* swallowing it into a tally would lose it.
|
|
42
|
-
*/
|
|
43
|
-
export function isVoteShape(note) {
|
|
44
|
-
if (!note?.name || typeof note.name !== 'string') return false;
|
|
45
|
-
const text = String(note.content ?? '').replace(/<[^>]*>/gu, '').trim();
|
|
46
|
-
return text === '';
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/** Which option is this the name of? -1 when it names none of them. */
|
|
50
|
-
export function optionIndex(poll, name) {
|
|
51
|
-
const opts = poll?.options || [];
|
|
52
|
-
return opts.findIndex(o => o.title === String(name));
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/** Closed outright, or past its end time. */
|
|
56
|
-
export function pollClosed(poll, now = Date.now()) {
|
|
57
|
-
if (!poll) return false;
|
|
58
|
-
if (poll.closed) return true;
|
|
59
|
-
return !!poll.expiresAt && Date.parse(poll.expiresAt) <= now;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Record one choice. Returns the roster to store and whether anything changed
|
|
64
|
-
* — an unchanged roster must not cost a rewrite of the Question, which is the
|
|
65
|
-
* one expense a busy poll can run up on the pod.
|
|
66
|
-
*
|
|
67
|
-
* A single-choice poll takes the first answer and ignores the rest, which is
|
|
68
|
-
* how a voter changing their mind is refused rather than counted twice. A
|
|
69
|
-
* multiple-choice poll unions the answers, because each choice arrives as its
|
|
70
|
-
* own reply.
|
|
71
|
-
*/
|
|
72
|
-
export function addVote(roster, key, index, { multiple = false } = {}) {
|
|
73
|
-
const had = roster[key] || [];
|
|
74
|
-
if (had.length && !multiple) return { roster, changed: false };
|
|
75
|
-
if (had.includes(index)) return { roster, changed: false };
|
|
76
|
-
return { roster: { ...roster, [key]: [...had, index] }, changed: true };
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* The counts, derived. `votes` is answers given, `voters` is people who gave
|
|
81
|
-
* them — the same number until a poll takes several answers each.
|
|
82
|
-
*/
|
|
83
|
-
export function tallyOf(roster, optionCount) {
|
|
84
|
-
const counts = new Array(optionCount).fill(0);
|
|
85
|
-
let voters = 0;
|
|
86
|
-
for (const picks of Object.values(roster || {})) {
|
|
87
|
-
let counted = false;
|
|
88
|
-
for (const i of picks) {
|
|
89
|
-
if (i >= 0 && i < optionCount) { counts[i]++; counted = true; }
|
|
90
|
-
}
|
|
91
|
-
if (counted) voters++;
|
|
92
|
-
}
|
|
93
|
-
return { counts, voters, votes: counts.reduce((n, c) => n + c, 0) };
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/** The poll record with its counts brought up to date from the roster. */
|
|
97
|
-
export function withTally(poll, roster) {
|
|
98
|
-
const opts = poll?.options || [];
|
|
99
|
-
const { counts, voters } = tallyOf(roster, opts.length);
|
|
100
|
-
return {
|
|
101
|
-
...poll,
|
|
102
|
-
options: opts.map((o, i) => ({ ...o, votes: counts[i] })),
|
|
103
|
-
votersCount: voters,
|
|
104
|
-
};
|
|
105
|
-
}
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
// profile-page.mjs — the human page a browser opens for an actor: who the
|
|
2
|
-
// account is, its fields, when it joined, what it pinned, and a remote-follow
|
|
3
|
-
// control. The actor document is for servers; this is the address you hand
|
|
4
|
-
// to a person. The agent writes it beside the actor (publisher
|
|
5
|
-
// publishProfilePage) only when its content changed.
|
|
6
|
-
|
|
7
|
-
const HTML_ESCAPES = { '&': '&', '<': '<', '>': '>' };
|
|
8
|
-
|
|
9
|
-
// `fields` are the profile's name/value rows, `joined` an ISO date, `pinned`
|
|
10
|
-
// the pinned posts as { content (sanitized HTML), published, url }.
|
|
11
|
-
export function profilePageHtml({ name, address, summary = null, icon = null, image = null,
|
|
12
|
-
fields = [], joined = null, pinned = [], kind = 'person' }) {
|
|
13
|
-
const esc = (s) => String(s ?? '').replace(/[&<>"]/g, c => ({ ...HTML_ESCAPES, '"': '"' }[c]));
|
|
14
|
-
const what = kind === 'group' ? 'a group' : 'an account';
|
|
15
|
-
const month = (iso) => {
|
|
16
|
-
const d = new Date(iso);
|
|
17
|
-
return Number.isNaN(d.getTime()) ? null
|
|
18
|
-
: d.toLocaleDateString('en', { month: 'long', year: 'numeric', timeZone: 'UTC' });
|
|
19
|
-
};
|
|
20
|
-
const day = (iso) => {
|
|
21
|
-
const d = new Date(iso);
|
|
22
|
-
return Number.isNaN(d.getTime()) ? '' : d.toISOString().slice(0, 10);
|
|
23
|
-
};
|
|
24
|
-
const fieldRows = fields.filter(f => f?.name && f?.value !== undefined && f?.value !== null && String(f.value) !== '');
|
|
25
|
-
const joinedText = joined ? month(joined) : null;
|
|
26
|
-
const pins = pinned.filter(x => x && typeof x.content === 'string');
|
|
27
|
-
return `<!doctype html>
|
|
28
|
-
<html lang="en">
|
|
29
|
-
<head>
|
|
30
|
-
<meta charset="utf-8">
|
|
31
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
32
|
-
<title>${esc(name)} (${esc(address)})</title>
|
|
33
|
-
<style>
|
|
34
|
-
:root { color-scheme: light dark; }
|
|
35
|
-
body { font: 112.5%/1.5 system-ui, sans-serif; max-width: 34rem; margin: 3rem auto; padding: 0 1rem; }
|
|
36
|
-
img.header { width: 100%; max-height: 12rem; object-fit: cover; border-radius: 1rem; }
|
|
37
|
-
img.avatar { width: 6rem; height: 6rem; border-radius: 1rem; object-fit: cover; margin-top: 1rem; }
|
|
38
|
-
h1 { margin: .5rem 0 0; }
|
|
39
|
-
.address { font-size: 1.1rem; user-select: all; }
|
|
40
|
-
dl.fields { display: grid; grid-template-columns: max-content 1fr; gap: .4rem 1rem; margin: 1.5rem 0; }
|
|
41
|
-
dl.fields dt { font-weight: 600; }
|
|
42
|
-
dl.fields dd { margin: 0; overflow-wrap: anywhere; }
|
|
43
|
-
.joined { margin: 1rem 0; }
|
|
44
|
-
section.pinned h2 { font-size: 1.1rem; margin: 2rem 0 .5rem; }
|
|
45
|
-
article.pin { border: 1px solid #8884; border-radius: 1rem; padding: 1rem 1.2rem; margin: 1rem 0; }
|
|
46
|
-
article.pin p:last-child { margin-bottom: 0; }
|
|
47
|
-
article.pin .when { font-size: 1rem; }
|
|
48
|
-
form { margin-top: 2rem; }
|
|
49
|
-
label { display: block; margin-bottom: .3rem; }
|
|
50
|
-
input { font: inherit; padding: .5rem; width: 14rem; max-width: 100%; }
|
|
51
|
-
button { font: inherit; padding: .5rem 1rem; }
|
|
52
|
-
.hint { color: #666; font-size: 1rem; }
|
|
53
|
-
.err { color: #b00020; }
|
|
54
|
-
.err:empty { display: none; }
|
|
55
|
-
@media (prefers-color-scheme: dark) {
|
|
56
|
-
.hint { color: #aaa; }
|
|
57
|
-
.err { color: #ff8a80; }
|
|
58
|
-
}
|
|
59
|
-
</style>
|
|
60
|
-
</head>
|
|
61
|
-
<body>
|
|
62
|
-
<main>
|
|
63
|
-
${image ? `<img class="header" src="${esc(image)}" alt="">` : ''}
|
|
64
|
-
${icon ? `<img class="avatar" src="${esc(icon)}" alt="">` : ''}
|
|
65
|
-
<h1>${esc(name)}</h1>
|
|
66
|
-
<p class="address">${esc(address)}</p>
|
|
67
|
-
${summary ? `<div>${summary}</div>` : ''}
|
|
68
|
-
${fieldRows.length ? `<dl class="fields">
|
|
69
|
-
${fieldRows.map(f => `<dt>${esc(f.name)}</dt><dd>${esc(f.value)}</dd>`).join('\n')}
|
|
70
|
-
</dl>` : ''}
|
|
71
|
-
${joinedText ? `<p class="joined">Joined ${esc(joinedText)}</p>` : ''}
|
|
72
|
-
${pins.length ? `<section class="pinned">
|
|
73
|
-
<h2>Pinned</h2>
|
|
74
|
-
${pins.map(x => `<article class="pin">
|
|
75
|
-
${x.content}
|
|
76
|
-
<p class="when">${x.url ? `<a href="${esc(x.url)}">${esc(day(x.published))}</a>` : esc(day(x.published))}</p>
|
|
77
|
-
</article>`).join('\n')}
|
|
78
|
-
</section>` : ''}
|
|
79
|
-
<p>This is ${what} on the Fediverse. To follow it, paste the address above
|
|
80
|
-
into the search box of Mastodon or any Fediverse app — or use the form.</p>
|
|
81
|
-
<form id="follow">
|
|
82
|
-
<label for="server">your server</label>
|
|
83
|
-
<input id="server" type="text" placeholder="mastodon.social" autocomplete="off"
|
|
84
|
-
required aria-describedby="follow-err">
|
|
85
|
-
<button type="submit">Follow</button>
|
|
86
|
-
<p class="err" id="follow-err" role="alert"></p>
|
|
87
|
-
</form>
|
|
88
|
-
<p class="hint">The form sends you to your own server's follow screen.</p>
|
|
89
|
-
</main>
|
|
90
|
-
<script>
|
|
91
|
-
document.getElementById('follow').addEventListener('submit', (ev) => {
|
|
92
|
-
ev.preventDefault();
|
|
93
|
-
const s = document.getElementById('server').value.trim().replace(/^https?:\\/\\//, '').replace(/\\/.*$/, '');
|
|
94
|
-
const err = document.getElementById('follow-err');
|
|
95
|
-
const addr = document.querySelector('.address').textContent.trim();
|
|
96
|
-
if (!s) { err.textContent = 'Enter your server, e.g. mastodon.social'; return; }
|
|
97
|
-
err.textContent = '';
|
|
98
|
-
location.href = 'https://' + s + '/authorize_interaction?uri=' + encodeURIComponent(addr);
|
|
99
|
-
});
|
|
100
|
-
</script>
|
|
101
|
-
</body>
|
|
102
|
-
</html>
|
|
103
|
-
`;
|
|
104
|
-
}
|
package/lib/core/proof.mjs
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
// proof.mjs — FEP-8b32 object integrity proofs (Data Integrity, eddsa-jcs-2022).
|
|
2
|
-
//
|
|
3
|
-
// An HTTP signature covers one delivery and says nothing about who wrote what
|
|
4
|
-
// was inside it. A proof travels with the activity, so a server that receives
|
|
5
|
-
// one of ours second-hand — carried by a group, or forwarded by a follower's
|
|
6
|
-
// server — can tell it is ours without asking us.
|
|
7
|
-
//
|
|
8
|
-
// The RSA `#main-key` and its HTTP signatures are untouched; this is a second
|
|
9
|
-
// key alongside, which is what the Data Integrity suites require.
|
|
10
|
-
|
|
11
|
-
import crypto from 'node:crypto';
|
|
12
|
-
import serialize from 'json-canon';
|
|
13
|
-
|
|
14
|
-
// multicodec: an Ed25519 public key is its 32 raw bytes behind 0xed 0x01.
|
|
15
|
-
const ED25519_PREFIX = Buffer.from([0xed, 0x01]);
|
|
16
|
-
const B58 = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
|
|
17
|
-
|
|
18
|
-
export function base58btc(bytes) {
|
|
19
|
-
const b = Buffer.from(bytes);
|
|
20
|
-
let n = 0n;
|
|
21
|
-
for (const byte of b) n = (n << 8n) + BigInt(byte);
|
|
22
|
-
let out = '';
|
|
23
|
-
while (n > 0n) { out = B58[Number(n % 58n)] + out; n /= 58n; }
|
|
24
|
-
for (const byte of b) { if (byte !== 0) break; out = B58[0] + out; }
|
|
25
|
-
return out;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/** The `publicKeyMultibase` form of an Ed25519 public key in PEM. */
|
|
29
|
-
export function multibaseEd25519(publicPem) {
|
|
30
|
-
const raw = Buffer.from(crypto.createPublicKey(publicPem).export({ format: 'jwk' }).x, 'base64url');
|
|
31
|
-
return 'z' + base58btc(Buffer.concat([ED25519_PREFIX, raw]));
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/** The signing key, as WebCrypto wants it. */
|
|
35
|
-
export function edPrivateKey(privatePem) {
|
|
36
|
-
const jwk = crypto.createPrivateKey(privatePem).export({ format: 'jwk' });
|
|
37
|
-
return crypto.subtle.importKey('jwk', { ...jwk, key_ops: ['sign'] }, { name: 'Ed25519' }, true, ['sign']);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const sha256 = (s) => crypto.createHash('sha256').update(s).digest();
|
|
41
|
-
|
|
42
|
-
// The proof's own terms have to be declared in the document that carries it,
|
|
43
|
-
// or a receiver that processes JSON-LD expands the proof away before it ever
|
|
44
|
-
// looks at it. Declared BEFORE signing, because the proof covers the document
|
|
45
|
-
// exactly as sent, `@context` included.
|
|
46
|
-
export const DI_CTX = 'https://w3id.org/security/data-integrity/v1';
|
|
47
|
-
|
|
48
|
-
function withProofContext(doc) {
|
|
49
|
-
const ctx = doc['@context'];
|
|
50
|
-
const list = Array.isArray(ctx) ? ctx : ctx ? [ctx] : [];
|
|
51
|
-
if (list.includes(DI_CTX)) return doc;
|
|
52
|
-
return { ...doc, '@context': [...list, DI_CTX] };
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Return the activity with a `proof` attached. The proof covers the activity
|
|
57
|
-
* exactly as it goes on the wire, minus the proof itself — so it must be the
|
|
58
|
-
* last thing added, and nothing may edit the activity afterwards.
|
|
59
|
-
*
|
|
60
|
-
* Whole seconds on `created`: a verifier re-serializes the timestamp it was
|
|
61
|
-
* given, and a fractional one has more than one spelling.
|
|
62
|
-
*/
|
|
63
|
-
export async function attachProof(activity, { privateKey, verificationMethod, created = new Date() } = {}) {
|
|
64
|
-
if (!privateKey || !verificationMethod || !activity || typeof activity !== 'object') return activity;
|
|
65
|
-
const { proof: _existing, ...bare } = activity;
|
|
66
|
-
const doc = withProofContext(bare);
|
|
67
|
-
const config = {
|
|
68
|
-
'@context': doc['@context'],
|
|
69
|
-
type: 'DataIntegrityProof',
|
|
70
|
-
cryptosuite: 'eddsa-jcs-2022',
|
|
71
|
-
verificationMethod,
|
|
72
|
-
proofPurpose: 'assertionMethod',
|
|
73
|
-
created: new Date(created).toISOString().replace(/\.\d+Z$/, 'Z'),
|
|
74
|
-
};
|
|
75
|
-
const digest = Buffer.concat([sha256(serialize(config)), sha256(serialize(doc))]);
|
|
76
|
-
const sig = await crypto.subtle.sign('Ed25519', privateKey, digest);
|
|
77
|
-
const { '@context': _ctx, ...emitted } = config;
|
|
78
|
-
return { ...doc, proof: { ...emitted, proofValue: 'z' + base58btc(Buffer.from(sig)) } };
|
|
79
|
-
}
|