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
|
@@ -1,241 +0,0 @@
|
|
|
1
|
-
// questions.mjs — polls.
|
|
2
|
-
//
|
|
3
|
-
// A poll is published as a Question and answered by ordinary replies naming
|
|
4
|
-
// an option, so the count is ours to keep and ours to republish. The roster
|
|
5
|
-
// of who chose what lives in agent state (polls.VOTES_DOC); the tallies on
|
|
6
|
-
// the status row and in the pod document are always derived from it.
|
|
7
|
-
|
|
8
|
-
import crypto from 'node:crypto';
|
|
9
|
-
import * as wire from '../wire.mjs';
|
|
10
|
-
import * as polls from '../polls.mjs';
|
|
11
|
-
import * as podNotes from '../../pod/notes.mjs';
|
|
12
|
-
import { assertDirectAddressed, replyTarget, rowTags, slugFor } from './notes.mjs';
|
|
13
|
-
|
|
14
|
-
// How long a poll gathers votes before its Question is rewritten. Every vote
|
|
15
|
-
// changes a number other servers re-read, and rewriting per vote would make a
|
|
16
|
-
// busy poll a steady write stream against the pod. A burst costs one rewrite
|
|
17
|
-
// and one Update instead.
|
|
18
|
-
const POLL_REWRITE_MS = 10_000;
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Publish a poll. `options` is a list of choice titles, `multiple` lets a
|
|
22
|
-
* voter pick more than one, and `expiresAt` is when voting stops.
|
|
23
|
-
*/
|
|
24
|
-
export async function publishQuestion(publisher, content, { options = [], multiple = false, expiresAt = null,
|
|
25
|
-
inReplyTo = undefined, visibility = 'public', spoilerText = null, sensitive = false, slug: wanted = null } = {}) {
|
|
26
|
-
const { urls } = publisher;
|
|
27
|
-
const priv = visibility === 'private' || visibility === 'direct';
|
|
28
|
-
if (priv) {
|
|
29
|
-
const ready = await publisher.privateReady();
|
|
30
|
-
if (ready !== true) throw new Error(ready);
|
|
31
|
-
}
|
|
32
|
-
const titles = [].concat(options).map(o => String(o ?? '').trim()).filter(Boolean);
|
|
33
|
-
if (titles.length < 2) throw new Error('a poll needs at least two options');
|
|
34
|
-
// Options are matched BY NAME when a vote arrives — that is the whole of
|
|
35
|
-
// the convention — so two options reading the same are one option that
|
|
36
|
-
// cannot be told apart.
|
|
37
|
-
if (new Set(titles).size !== titles.length) throw new Error('a poll’s options must differ from one another');
|
|
38
|
-
// Bounded here as well as at the client API, because the outbox is a
|
|
39
|
-
// second way in and an unbounded poll is a document a stranger sizes.
|
|
40
|
-
if (titles.length > polls.MAX_OPTIONS) throw new Error(`a poll takes at most ${polls.MAX_OPTIONS} options`);
|
|
41
|
-
if (titles.some(t => t.length > polls.MAX_OPTION_CHARS)) {
|
|
42
|
-
throw new Error(`a poll option is at most ${polls.MAX_OPTION_CHARS} characters`);
|
|
43
|
-
}
|
|
44
|
-
if (expiresAt) {
|
|
45
|
-
const ends = Date.parse(expiresAt);
|
|
46
|
-
if (!Number.isFinite(ends)) throw new Error('the closing time is not a date');
|
|
47
|
-
const seconds = (ends - Date.now()) / 1000;
|
|
48
|
-
if (seconds < polls.MIN_SECONDS || seconds > polls.MAX_SECONDS) {
|
|
49
|
-
throw new Error(`a poll runs between ${polls.MIN_SECONDS} and ${polls.MAX_SECONDS} seconds`);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const published = new Date().toISOString();
|
|
54
|
-
// The name the client asked for, when it is plain — the outbox door has
|
|
55
|
-
// already told the client the address it names.
|
|
56
|
-
const slug = await slugFor(publisher, priv ? urls.privateNotes : urls.notes, wanted, published);
|
|
57
|
-
const mentions = await publisher._mentionsFor(content, inReplyTo);
|
|
58
|
-
if (visibility === 'direct') assertDirectAddressed(content, mentions);
|
|
59
|
-
const reply = await replyTarget(publisher, inReplyTo, mentions, visibility);
|
|
60
|
-
const poll = {
|
|
61
|
-
multiple: !!multiple,
|
|
62
|
-
expiresAt: expiresAt || null,
|
|
63
|
-
closed: null,
|
|
64
|
-
options: titles.map(title => ({ title, votes: 0 })),
|
|
65
|
-
votersCount: 0,
|
|
66
|
-
// Resolved once, here: a tally rewrite must not cost a webfinger lookup
|
|
67
|
-
// per vote for people the poll named.
|
|
68
|
-
mentionInboxes: [...new Set([...mentions.map(m => m.inbox), reply?.inbox].filter(Boolean))],
|
|
69
|
-
};
|
|
70
|
-
const question = wire.questionDoc({
|
|
71
|
-
urls, slug, content, published, inReplyTo, attachments: [], mentions,
|
|
72
|
-
visibility, summary: spoilerText, sensitive, also: reply ? [reply.actor] : [],
|
|
73
|
-
container: priv ? urls.privateNotes : urls.notes,
|
|
74
|
-
options: poll.options, multiple: poll.multiple, endTime: poll.expiresAt, votersCount: 0,
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
await podNotes.write(publisher.remote, question.id, question);
|
|
78
|
-
// Empty, but present: a dangling `replies` that 404s is worse than none.
|
|
79
|
-
await podNotes.writeEmptyReplies(publisher.remote, wire.repliesId(question.id),
|
|
80
|
-
wire.collection(wire.repliesId(question.id), []));
|
|
81
|
-
if (!priv) await publisher.recordOutbox(question.id);
|
|
82
|
-
publisher.store.addStatus({
|
|
83
|
-
noteId: question.id, actor: urls.actor, content: question.content, published,
|
|
84
|
-
kind: 'post', slug, text: content, visibility, poll, inReplyTo,
|
|
85
|
-
...(reply ? { replyActor: reply.actor } : {}),
|
|
86
|
-
...(spoilerText ? { spoiler: spoilerText } : {}),
|
|
87
|
-
...(question.sensitive ? { sensitive: true } : {}),
|
|
88
|
-
...rowTags(question),
|
|
89
|
-
});
|
|
90
|
-
if (await publisher.store.commit?.() === false) publisher.log(`poll published but its timeline row was refused: ${question.id}`);
|
|
91
|
-
|
|
92
|
-
const create = publisher._pollActivity('Create', question, wire.createActivityId(question.id));
|
|
93
|
-
await podNotes.writeCreate(publisher.remote, create.id, create);
|
|
94
|
-
const contacts = publisher.store.getContacts();
|
|
95
|
-
const inboxes = [...new Set([
|
|
96
|
-
...(visibility === 'direct' ? [] : contacts.followers.map(f => f.sharedInbox || f.inbox)),
|
|
97
|
-
...poll.mentionInboxes,
|
|
98
|
-
].filter(Boolean))];
|
|
99
|
-
await publisher.deliverer.deliverToAll(inboxes, create);
|
|
100
|
-
publisher.log(`poll published: ${question.id} (${titles.length} options) → ${inboxes.length} inbox(es)`);
|
|
101
|
-
return question;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// The Create or Update carrying a Question. The context is hoisted onto the
|
|
105
|
-
// activity and the embedded object keeps none: a nested @context is legal
|
|
106
|
-
// JSON-LD but not every server reads one, and votersCount is declared there.
|
|
107
|
-
export function pollActivity(publisher, type, question, id) {
|
|
108
|
-
const { '@context': ctx, ...object } = question;
|
|
109
|
-
return {
|
|
110
|
-
'@context': ctx, id, type,
|
|
111
|
-
actor: publisher.urls.actor,
|
|
112
|
-
published: question.published,
|
|
113
|
-
to: question.to, cc: question.cc,
|
|
114
|
-
object,
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* One vote on one of OUR polls, named by the option's title. Returns true
|
|
120
|
-
* when it counted — a second answer to a single-choice poll, an option we do
|
|
121
|
-
* not offer, or a poll that has closed all count for nothing.
|
|
122
|
-
*/
|
|
123
|
-
export async function recordVote(publisher, questionId, actor, optionName) {
|
|
124
|
-
const s = publisher.store.getStatuses().find(x => x.noteId === questionId);
|
|
125
|
-
if (!s?.poll || s.kind !== 'post' || s.actor !== publisher.urls.actor) return false;
|
|
126
|
-
if (polls.pollClosed(s.poll)) return false;
|
|
127
|
-
const index = polls.optionIndex(s.poll, optionName);
|
|
128
|
-
if (index < 0) return false;
|
|
129
|
-
const all = publisher.store.read(polls.VOTES_DOC, {});
|
|
130
|
-
const { roster, changed } = polls.addVote(all[questionId] || {}, polls.voterKey(actor), index,
|
|
131
|
-
{ multiple: !!s.poll.multiple });
|
|
132
|
-
if (!changed) return false;
|
|
133
|
-
publisher.store.write(polls.VOTES_DOC, { ...all, [questionId]: roster });
|
|
134
|
-
publisher.store.updateStatus(questionId, { poll: polls.withTally(s.poll, roster) });
|
|
135
|
-
publisher._pollDirty(questionId);
|
|
136
|
-
return true;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// Open the rewrite window for a poll whose count moved. Already open is
|
|
140
|
-
// already enough: the whole point is that a burst costs one rewrite.
|
|
141
|
-
export function pollDirty(publisher, questionId) {
|
|
142
|
-
if (publisher.pollTimers.has(questionId)) return;
|
|
143
|
-
const t = setTimeout(() => {
|
|
144
|
-
publisher.pollTimers.delete(questionId);
|
|
145
|
-
publisher.republishPoll(questionId).catch(e => publisher.log(`poll rewrite: ${e.message}`));
|
|
146
|
-
}, POLL_REWRITE_MS);
|
|
147
|
-
t.unref?.();
|
|
148
|
-
publisher.pollTimers.set(questionId, t);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Write the poll's current count back to the pod and tell everyone who has
|
|
153
|
-
* it. `closing` stamps it shut, which is a one-way door.
|
|
154
|
-
*/
|
|
155
|
-
export async function republishPoll(publisher, questionId, { closing = null } = {}) {
|
|
156
|
-
const s = publisher.store.getStatuses().find(x => x.noteId === questionId);
|
|
157
|
-
if (!s?.poll) return null;
|
|
158
|
-
const { urls } = publisher;
|
|
159
|
-
const roster = publisher.store.read(polls.VOTES_DOC, {})[questionId] || {};
|
|
160
|
-
// A shut poll whose roster has been retired keeps the counts on its row:
|
|
161
|
-
// deriving them from an empty roster would publish a poll nobody voted in.
|
|
162
|
-
const shut = closing || s.poll.closed;
|
|
163
|
-
const counted = shut && !Object.keys(roster).length ? s.poll : polls.withTally(s.poll, roster);
|
|
164
|
-
const poll = { ...counted, ...(closing ? { closed: closing } : {}) };
|
|
165
|
-
const container = String(s.noteId).startsWith(urls.privateNotes) ? urls.privateNotes : urls.notes;
|
|
166
|
-
const slug = s.slug || String(s.noteId).slice(container.length);
|
|
167
|
-
const mentions = (s.mentions || []).map(m => ({
|
|
168
|
-
handle: String(m.name || '').replace(/^@/, ''), actor: m.href, page: null, inbox: null,
|
|
169
|
-
}));
|
|
170
|
-
const question = wire.questionDoc({
|
|
171
|
-
urls, slug, content: s.text ?? '', published: s.published, inReplyTo: s.inReplyTo,
|
|
172
|
-
attachments: [], mentions, also: s.replyActor ? [s.replyActor] : [],
|
|
173
|
-
visibility: s.visibility || 'public', summary: s.spoiler || null, sensitive: !!s.sensitive, container,
|
|
174
|
-
options: poll.options, multiple: !!poll.multiple, endTime: poll.expiresAt,
|
|
175
|
-
closed: poll.closed, votersCount: poll.votersCount || 0,
|
|
176
|
-
});
|
|
177
|
-
await podNotes.write(publisher.remote, question.id, question);
|
|
178
|
-
// The Create is overwritten too, so a group's Announce resolves to the
|
|
179
|
-
// current count rather than to the one the poll opened with.
|
|
180
|
-
await podNotes.writeCreate(publisher.remote, wire.createActivityId(question.id),
|
|
181
|
-
publisher._pollActivity('Create', question, wire.createActivityId(question.id)));
|
|
182
|
-
publisher.store.updateStatus(questionId, { poll });
|
|
183
|
-
|
|
184
|
-
// Not `updated`: a changed count is not an edit, and stamping one would
|
|
185
|
-
// have every client show the poll as edited each time somebody voted.
|
|
186
|
-
//
|
|
187
|
-
// The Update is named after the STATE it carries rather than the moment it
|
|
188
|
-
// was sent. A clock only tells two rewrites apart when they fall in
|
|
189
|
-
// different milliseconds, and a receiving server that has seen an activity
|
|
190
|
-
// id drops the next one wearing it — which would quietly freeze the count.
|
|
191
|
-
// Naming the state means an id changes exactly when there is something new
|
|
192
|
-
// to say, and two sends of the same numbers are the duplicate they look
|
|
193
|
-
// like.
|
|
194
|
-
const stamp = crypto.createHash('sha256').update(JSON.stringify([
|
|
195
|
-
poll.options.map(o => o.votes || 0), poll.votersCount || 0, poll.closed || '',
|
|
196
|
-
])).digest('hex').slice(0, 12);
|
|
197
|
-
const update = publisher._pollActivity('Update', question, `${question.id}#poll-${stamp}`);
|
|
198
|
-
const contacts = publisher.store.getContacts();
|
|
199
|
-
const inboxes = [...new Set([
|
|
200
|
-
...(s.visibility === 'direct' ? [] : contacts.followers.map(f => f.sharedInbox || f.inbox)),
|
|
201
|
-
...(s.poll.mentionInboxes || []),
|
|
202
|
-
].filter(Boolean))];
|
|
203
|
-
await publisher.deliverer.deliverToAll(inboxes, update);
|
|
204
|
-
publisher.log(`poll ${closing ? 'closed' : 'count published'}: ${question.id} → ${inboxes.length} inbox(es)`);
|
|
205
|
-
return poll;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Shut any poll whose time is up. Called from the agent's sweep. Closing is
|
|
210
|
-
* recorded BEFORE the republish, so a failed republish cannot leave a poll
|
|
211
|
-
* open and collecting votes it has already refused.
|
|
212
|
-
*/
|
|
213
|
-
export async function closeDuePolls(publisher, now = Date.now()) {
|
|
214
|
-
const due = publisher.store.getStatuses().filter(s => s.kind === 'post' && s.poll
|
|
215
|
-
&& !s.poll.closed && s.poll.expiresAt && Date.parse(s.poll.expiresAt) <= now);
|
|
216
|
-
for (const s of due) {
|
|
217
|
-
const closed = new Date(Math.min(now, Date.parse(s.poll.expiresAt) || now)).toISOString();
|
|
218
|
-
clearTimeout(publisher.pollTimers.get(s.noteId));
|
|
219
|
-
publisher.pollTimers.delete(s.noteId);
|
|
220
|
-
publisher.store.updateStatus(s.noteId, { poll: { ...s.poll, closed } });
|
|
221
|
-
const done = await publisher.republishPoll(s.noteId, { closing: closed })
|
|
222
|
-
.catch(e => { publisher.log(`poll close ${s.noteId}: ${e.message}`); return null; });
|
|
223
|
-
// The roster only ever answered one question — has this person already
|
|
224
|
-
// voted — and a shut poll has stopped asking it. Dropping it keeps a
|
|
225
|
-
// document we serialize whole from carrying every poll's voters forever.
|
|
226
|
-
if (done) {
|
|
227
|
-
const all = publisher.store.read(polls.VOTES_DOC, {});
|
|
228
|
-
if (all[s.noteId]) {
|
|
229
|
-
delete all[s.noteId];
|
|
230
|
-
publisher.store.write(polls.VOTES_DOC, all);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
return due.length;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/** Stop the pending rewrite windows. Called at shutdown. */
|
|
238
|
-
export function stopPolls(publisher) {
|
|
239
|
-
for (const t of publisher.pollTimers.values()) clearTimeout(t);
|
|
240
|
-
publisher.pollTimers.clear();
|
|
241
|
-
}
|
|
@@ -1,224 +0,0 @@
|
|
|
1
|
-
// restore.mjs — a machine that is behind the pod catching up: followers and
|
|
2
|
-
// outbox entries the pod carries and this machine never heard of, and the
|
|
3
|
-
// actor's own posts rebuilt from the published outbox.
|
|
4
|
-
|
|
5
|
-
import * as wire from '../wire.mjs';
|
|
6
|
-
import * as podNotes from '../../pod/notes.mjs';
|
|
7
|
-
import { readLenient } from '../as2.mjs';
|
|
8
|
-
import { rowContent } from './notes.mjs';
|
|
9
|
-
|
|
10
|
-
const ACCEPT_AP = 'application/activity+json, application/ld+json; profile="https://www.w3.org/ns/activitystreams"';
|
|
11
|
-
const REBUILD_MAX_PER_RUN = 200;
|
|
12
|
-
|
|
13
|
-
// Anyone the pod says follows us that we have no record of, and never
|
|
14
|
-
// deliberately removed. In the steady state there is nobody: the pod's list is
|
|
15
|
-
// written from this one. They appear when the local half is BEHIND the pod —
|
|
16
|
-
// a restored backup, a home copied off a dead machine — and republishing
|
|
17
|
-
// blindly would delete them from the wire and, worse, stop delivering to them.
|
|
18
|
-
//
|
|
19
|
-
// Removals are what makes this safe to do unconditionally: `dropFollower`
|
|
20
|
-
// records every unfollow, ejection and account deletion, so a returning name
|
|
21
|
-
// is either genuinely still a follower or genuinely a mistake we made.
|
|
22
|
-
export async function reconcileFollowers(publisher, contacts) {
|
|
23
|
-
let published = [];
|
|
24
|
-
try {
|
|
25
|
-
published = (await publisher.readPublishedFollowers()) || []; // walks pages
|
|
26
|
-
} catch { return 0; } // no list to reconcile against
|
|
27
|
-
|
|
28
|
-
const known = new Set(contacts.followers.map(f => f.actor));
|
|
29
|
-
const removed = new Set((contacts.removedFollowers || []).map(r => r.actor));
|
|
30
|
-
const missing = published.filter(a => typeof a === 'string' && !known.has(a) && !removed.has(a));
|
|
31
|
-
if (!missing.length) return 0;
|
|
32
|
-
|
|
33
|
-
// An inbox is what delivery needs, and only the actor document has it — so
|
|
34
|
-
// recovering a follower costs one fetch each. Capped: a list this wrong is
|
|
35
|
-
// a restore, and the rest catch up on the next publish.
|
|
36
|
-
let recovered = 0;
|
|
37
|
-
for (const actor of missing.slice(0, 200)) {
|
|
38
|
-
try {
|
|
39
|
-
const res = await publisher.deliverer.signedFetch(actor, { headers: { accept: ACCEPT_AP } });
|
|
40
|
-
if (!res.ok) continue;
|
|
41
|
-
// The graph, not the compacted copy: `inbox` is `ldp:inbox` rather than
|
|
42
|
-
// an `as:` term, and what is wanted here is the statement, not a key.
|
|
43
|
-
const { doc, view, degraded } = await readLenient(await res.json());
|
|
44
|
-
const actorDoc = view ?? doc;
|
|
45
|
-
if (degraded) publisher.log?.(`actor ${actor} grounded to read: ${degraded}`);
|
|
46
|
-
if (!actorDoc?.inbox) continue;
|
|
47
|
-
contacts.followers.push({
|
|
48
|
-
actor, inbox: actorDoc.inbox, sharedInbox: actorDoc.endpoints?.sharedInbox || null, recovered: true,
|
|
49
|
-
// Said explicitly, because onUndo reads it: the pod publishes WHO
|
|
50
|
-
// follows, never the id of the Follow that did it, so a recovered
|
|
51
|
-
// record has nothing an Undo can be matched against and must not be
|
|
52
|
-
// evictable by one naming anything at all.
|
|
53
|
-
followId: null,
|
|
54
|
-
});
|
|
55
|
-
recovered++;
|
|
56
|
-
} catch { /* unreachable now; it will be there next time */ }
|
|
57
|
-
}
|
|
58
|
-
if (recovered) {
|
|
59
|
-
publisher.store.setContacts(contacts);
|
|
60
|
-
publisher.log(`reconciled ${recovered} follower(s) the pod knew about and this machine did not `
|
|
61
|
-
+ '— a restored or copied state was behind');
|
|
62
|
-
}
|
|
63
|
-
return recovered;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// The same argument as reconcileFollowers, for the other published list. It
|
|
67
|
-
// matters more: the outbox is the INDEX a statuses rebuild reads, so a
|
|
68
|
-
// republish from a restored-and-behind machine would destroy the record of
|
|
69
|
-
// everything this actor ever posted — and destroy it before anyone noticed
|
|
70
|
-
// there was anything to recover.
|
|
71
|
-
//
|
|
72
|
-
// Safe for the same reason: `unrecordOutbox` leaves a tombstone, so an entry
|
|
73
|
-
// the pod still carries is one this machine has not heard of, never one it
|
|
74
|
-
// deliberately took back.
|
|
75
|
-
export async function reconcileOutbox(publisher, outbox) {
|
|
76
|
-
let published = [];
|
|
77
|
-
try {
|
|
78
|
-
published = await publisher.readPublishedOutbox() || [];
|
|
79
|
-
} catch { return 0; }
|
|
80
|
-
if (!Array.isArray(published) || !published.length) return 0;
|
|
81
|
-
|
|
82
|
-
const idOf = (i) => (typeof i === 'string' ? i : i?.id || null);
|
|
83
|
-
const known = new Set(outbox.map(idOf).filter(Boolean));
|
|
84
|
-
const removed = new Set((publisher.store.read('outbox-removed.json', [])).map(r => r.id));
|
|
85
|
-
const missing = published.filter((i) => {
|
|
86
|
-
const id = idOf(i);
|
|
87
|
-
return id && !known.has(id) && !removed.has(id);
|
|
88
|
-
});
|
|
89
|
-
if (!missing.length) return 0;
|
|
90
|
-
// Newest first, like recordOutbox leaves it; the pod's copy is already in
|
|
91
|
-
// that order, so appending the tail is enough to keep both sorted.
|
|
92
|
-
outbox.push(...missing);
|
|
93
|
-
publisher.store.write('outbox.json', outbox);
|
|
94
|
-
publisher.log(`reconciled ${missing.length} outbox entr(ies) the pod carried and this machine did not`);
|
|
95
|
-
return missing.length;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Recover this actor's own posts from the pod's public face. The private half
|
|
99
|
-
// lives on this machine now, so a restored backup or a replaced machine loses
|
|
100
|
-
// statuses.json while the pod still serves every note. Followers already come
|
|
101
|
-
// back; this is the other half of the same gap.
|
|
102
|
-
//
|
|
103
|
-
// The INDEX is ap/outbox, not the ap/notes/ listing, and that difference is
|
|
104
|
-
// the safety argument. Deleting a post rewrites the outbox in one PUT, so an
|
|
105
|
-
// entry still there is a post that still stands. The note DOCUMENT can outlive
|
|
106
|
-
// its own deletion — deleteNote's `remote.delete` is a request that can fail —
|
|
107
|
-
// so walking the container can bring back something its author took down.
|
|
108
|
-
// `fromNotes` is for when you would rather have that than lose the post; it is
|
|
109
|
-
// not the default, and it says so where it is offered.
|
|
110
|
-
//
|
|
111
|
-
// MERGE ONLY. A status this machine already holds is left exactly as it is:
|
|
112
|
-
// it carries local facts — favourited, reblogged, the activities an Undo has
|
|
113
|
-
// to name — that the pod knows nothing about. That is also what makes the
|
|
114
|
-
// failure modes harmless: a listing that fails returns nothing, and nothing
|
|
115
|
-
// is what an empty listing recovers.
|
|
116
|
-
export async function rebuildStatuses(publisher, { fromNotes = false } = {}) {
|
|
117
|
-
const { urls } = publisher;
|
|
118
|
-
const ids = new Set();
|
|
119
|
-
const boosts = [];
|
|
120
|
-
let indexed = false;
|
|
121
|
-
|
|
122
|
-
const published = await publisher.readPublishedOutbox().catch(() => null);
|
|
123
|
-
if (published) {
|
|
124
|
-
indexed = true;
|
|
125
|
-
for (const item of published) {
|
|
126
|
-
if (typeof item === 'string') { if (item.startsWith(urls.notes)) ids.add(item); }
|
|
127
|
-
else if (item?.type === 'Announce') boosts.push(item);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
if (fromNotes) {
|
|
131
|
-
// Three documents are published per post — the note, `-create` and
|
|
132
|
-
// `-replies` — plus a `.keep`. Which is which is settled by reading the
|
|
133
|
-
// document below, not by its name: a slug is a date and eight hex
|
|
134
|
-
// characters and says nothing about what it holds.
|
|
135
|
-
for (const child of await podNotes.list(publisher.remote, urls).catch(() => [])) {
|
|
136
|
-
ids.add(child.url);
|
|
137
|
-
}
|
|
138
|
-
indexed = true;
|
|
139
|
-
}
|
|
140
|
-
if (!indexed) return { indexed: 0, recovered: 0, reblogs: 0, landed: false, why: 'the pod would not answer for its outbox' };
|
|
141
|
-
|
|
142
|
-
const statuses = publisher.store.getStatuses();
|
|
143
|
-
const have = new Set(statuses.map(s => s.noteId));
|
|
144
|
-
const removed = new Set(publisher.store.read('outbox-removed.json', []).map(r => r.id));
|
|
145
|
-
const recovered = [];
|
|
146
|
-
// Capped per run: a long-lived actor's rebuild is otherwise one pod request
|
|
147
|
-
// per post it has ever made, in one burst. What is left is picked up by
|
|
148
|
-
// running it again — the merge is idempotent, so that is safe to repeat.
|
|
149
|
-
let budget = REBUILD_MAX_PER_RUN;
|
|
150
|
-
for (const id of ids) {
|
|
151
|
-
if (budget <= 0) { publisher.log(`rebuild: stopping at ${REBUILD_MAX_PER_RUN} this run — run it again for the rest`); break; }
|
|
152
|
-
if (have.has(id) || removed.has(id)) continue;
|
|
153
|
-
budget--;
|
|
154
|
-
const note = await podNotes.read(publisher.remote, id).catch(() => null);
|
|
155
|
-
// Any object of ours: a Note, a poll, or whatever a client posted.
|
|
156
|
-
if (!note?.type || note.id !== id || note.attributedTo !== urls.actor) continue;
|
|
157
|
-
const attachments = wire.attachmentsOf(note);
|
|
158
|
-
const mentions = (Array.isArray(note.tag) ? note.tag : [])
|
|
159
|
-
.filter(t => t?.type === 'Mention' && t.href)
|
|
160
|
-
.map(t => ({ href: t.href, name: t.name }));
|
|
161
|
-
recovered.push({
|
|
162
|
-
noteId: note.id, actor: urls.actor, content: rowContent(note),
|
|
163
|
-
published: note.published || null,
|
|
164
|
-
...(note.inReplyTo ? { inReplyTo: note.inReplyTo } : {}),
|
|
165
|
-
kind: 'post', slug: note.id.slice(urls.notes.length),
|
|
166
|
-
...(attachments.length ? { attachments } : {}),
|
|
167
|
-
...(mentions.length ? { mentions } : {}),
|
|
168
|
-
recovered: true,
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// A boost is only recoverable for a post we can name — the Announce carries
|
|
173
|
-
// the activity a later Undo needs, but not the boosted post's text, which
|
|
174
|
-
// belongs to whoever wrote it.
|
|
175
|
-
const merged = [...statuses, ...recovered];
|
|
176
|
-
let reblogs = 0;
|
|
177
|
-
for (const act of boosts) {
|
|
178
|
-
const object = typeof act.object === 'string' ? act.object : act.object?.id;
|
|
179
|
-
const s = object && merged.find(x => x.noteId === object);
|
|
180
|
-
if (!s || s.reblogged) continue;
|
|
181
|
-
s.reblogged = true;
|
|
182
|
-
s.announceActivity = act;
|
|
183
|
-
reblogs++;
|
|
184
|
-
}
|
|
185
|
-
if (!recovered.length && !reblogs) return { indexed: ids.size, recovered: 0, reblogs: 0, landed: true };
|
|
186
|
-
|
|
187
|
-
// Written whole and sorted: addStatus unshifts and fires the streaming
|
|
188
|
-
// event, so a loop of it would arrive backwards and push every recovered
|
|
189
|
-
// post at connected clients as new.
|
|
190
|
-
merged.sort((a, b) => String(b.published || '').localeCompare(String(a.published || '')));
|
|
191
|
-
const kept = merged.slice(0, 1000);
|
|
192
|
-
publisher.store.write('statuses.json', kept);
|
|
193
|
-
const landed = await publisher.store.commit();
|
|
194
|
-
|
|
195
|
-
publisher.log(`rebuilt ${recovered.length} post(s) and ${reblogs} boost(s) from the pod`
|
|
196
|
-
+ `${landed ? '' : ' — THE STATE WRITE DID NOT LAND'}`);
|
|
197
|
-
return {
|
|
198
|
-
indexed: ids.size, recovered: recovered.length, reblogs, landed,
|
|
199
|
-
dropped: Math.max(0, merged.length - kept.length),
|
|
200
|
-
};
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// Own posts the outbox records and the statuses index does not.
|
|
204
|
-
//
|
|
205
|
-
// The index row is the last thing a post writes, and it goes by a debounced
|
|
206
|
-
// PUT: a worker killed or a write refused after the post was answered leaves a
|
|
207
|
-
// post that stands on the pod and reached followers but never shows on its
|
|
208
|
-
// author's own timeline. Run when an agent becomes active. Cheap when nothing
|
|
209
|
-
// is wrong — two cached documents compared, no request — and the capped,
|
|
210
|
-
// merge-only rebuild when something is.
|
|
211
|
-
export async function healStatuses(publisher) {
|
|
212
|
-
const { urls, store } = publisher;
|
|
213
|
-
const own = store.read('outbox.json', [])
|
|
214
|
-
.map(i => (typeof i === 'string' ? i : null))
|
|
215
|
-
.filter(id => id && id.startsWith(urls.notes));
|
|
216
|
-
if (!own.length) return { missing: 0, recovered: 0 };
|
|
217
|
-
const have = new Set(store.getStatuses().map(s => s.noteId));
|
|
218
|
-
const removed = new Set(store.read('outbox-removed.json', []).map(r => r.id));
|
|
219
|
-
const missing = own.filter(id => !have.has(id) && !removed.has(id));
|
|
220
|
-
if (!missing.length) return { missing: 0, recovered: 0 };
|
|
221
|
-
const r = await rebuildStatuses(publisher);
|
|
222
|
-
publisher.log(`timeline index healed: ${missing.length} own post(s) were missing, ${r.recovered} recovered`);
|
|
223
|
-
return { missing: missing.length, recovered: r.recovered };
|
|
224
|
-
}
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
# activitystreams.ttl — shapes for the ActivityStreams documents an identity
|
|
2
|
-
# reads from strangers.
|
|
3
|
-
#
|
|
4
|
-
# Shapes are identified by fragments of this document, so nothing here claims a
|
|
5
|
-
# namespace of its own.
|
|
6
|
-
#
|
|
7
|
-
# These are deliberately thin. They state what the ActivityPub specification
|
|
8
|
-
# requires and nothing more, because every fediverse implementation writes
|
|
9
|
-
# slightly different documents and a constraint beyond the spec is a constraint
|
|
10
|
-
# that fires on somebody's real traffic. Nothing here rejects anything: a
|
|
11
|
-
# failure is recorded and the activity is handled. A shape earns its way to
|
|
12
|
-
# being enforced by not firing.
|
|
13
|
-
|
|
14
|
-
@prefix sh: <http://www.w3.org/ns/shacl#> .
|
|
15
|
-
@prefix as: <https://www.w3.org/ns/activitystreams#> .
|
|
16
|
-
@prefix ldp: <http://www.w3.org/ns/ldp#> .
|
|
17
|
-
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
|
18
|
-
|
|
19
|
-
# --- the actor document ------------------------------------------------------
|
|
20
|
-
|
|
21
|
-
<#Actor>
|
|
22
|
-
a sh:NodeShape ;
|
|
23
|
-
sh:targetClass as:Person, as:Service, as:Group, as:Organization, as:Application ;
|
|
24
|
-
sh:name "Actor" ;
|
|
25
|
-
sh:description "Someone or something that acts: it has an address activities are sent to, and a name it is addressed by." ;
|
|
26
|
-
sh:property [
|
|
27
|
-
sh:path ldp:inbox ;
|
|
28
|
-
sh:name "inbox" ;
|
|
29
|
-
sh:description "The collection an actor receives activities in." ;
|
|
30
|
-
sh:nodeKind sh:IRI ;
|
|
31
|
-
sh:minCount 1 ;
|
|
32
|
-
sh:maxCount 1 ;
|
|
33
|
-
] ;
|
|
34
|
-
sh:property [
|
|
35
|
-
sh:path as:preferredUsername ;
|
|
36
|
-
sh:name "preferred username" ;
|
|
37
|
-
sh:description "The short name an actor is addressed by, the local part of its @handle@host address." ;
|
|
38
|
-
sh:datatype xsd:string ;
|
|
39
|
-
sh:maxCount 1 ;
|
|
40
|
-
] .
|
|
41
|
-
|
|
42
|
-
# --- the activities an identity acts on --------------------------------------
|
|
43
|
-
#
|
|
44
|
-
# Each states the two things ActivityPub requires of an activity: who performed
|
|
45
|
-
# it, and what it is about.
|
|
46
|
-
|
|
47
|
-
<#Create>
|
|
48
|
-
a sh:NodeShape ;
|
|
49
|
-
sh:targetClass as:Create ;
|
|
50
|
-
sh:name "Create" ;
|
|
51
|
-
sh:description "The bringing into existence of the object it names." ;
|
|
52
|
-
sh:property [
|
|
53
|
-
sh:path as:actor ;
|
|
54
|
-
sh:name "actor" ;
|
|
55
|
-
sh:description "The one that performed the activity." ;
|
|
56
|
-
sh:nodeKind sh:BlankNodeOrIRI ;
|
|
57
|
-
sh:minCount 1 ;
|
|
58
|
-
sh:maxCount 1 ;
|
|
59
|
-
] ;
|
|
60
|
-
sh:property [
|
|
61
|
-
sh:path as:object ;
|
|
62
|
-
sh:name "object" ;
|
|
63
|
-
sh:description "The thing the activity is about." ;
|
|
64
|
-
sh:minCount 1 ;
|
|
65
|
-
sh:maxCount 1 ;
|
|
66
|
-
] .
|
|
67
|
-
|
|
68
|
-
<#Follow>
|
|
69
|
-
a sh:NodeShape ;
|
|
70
|
-
sh:targetClass as:Follow ;
|
|
71
|
-
sh:name "Follow" ;
|
|
72
|
-
sh:description "A request to receive what the object it names publishes." ;
|
|
73
|
-
sh:property [
|
|
74
|
-
sh:path as:actor ;
|
|
75
|
-
sh:name "actor" ;
|
|
76
|
-
sh:description "The one that performed the activity." ;
|
|
77
|
-
sh:nodeKind sh:BlankNodeOrIRI ;
|
|
78
|
-
sh:minCount 1 ;
|
|
79
|
-
sh:maxCount 1 ;
|
|
80
|
-
] ;
|
|
81
|
-
sh:property [
|
|
82
|
-
sh:path as:object ;
|
|
83
|
-
sh:name "object" ;
|
|
84
|
-
sh:description "The thing the activity is about." ;
|
|
85
|
-
sh:minCount 1 ;
|
|
86
|
-
sh:maxCount 1 ;
|
|
87
|
-
] .
|
|
88
|
-
|
|
89
|
-
<#Accept>
|
|
90
|
-
a sh:NodeShape ;
|
|
91
|
-
sh:targetClass as:Accept ;
|
|
92
|
-
sh:name "Accept" ;
|
|
93
|
-
sh:description "Assent to the activity it names." ;
|
|
94
|
-
sh:property [
|
|
95
|
-
sh:path as:actor ;
|
|
96
|
-
sh:name "actor" ;
|
|
97
|
-
sh:description "The one that performed the activity." ;
|
|
98
|
-
sh:nodeKind sh:BlankNodeOrIRI ;
|
|
99
|
-
sh:minCount 1 ;
|
|
100
|
-
sh:maxCount 1 ;
|
|
101
|
-
] ;
|
|
102
|
-
sh:property [
|
|
103
|
-
sh:path as:object ;
|
|
104
|
-
sh:name "object" ;
|
|
105
|
-
sh:description "The thing the activity is about." ;
|
|
106
|
-
sh:minCount 1 ;
|
|
107
|
-
sh:maxCount 1 ;
|
|
108
|
-
] .
|
|
109
|
-
|
|
110
|
-
<#Undo>
|
|
111
|
-
a sh:NodeShape ;
|
|
112
|
-
sh:targetClass as:Undo ;
|
|
113
|
-
sh:name "Undo" ;
|
|
114
|
-
sh:description "The retraction of the activity it names, by the one that performed it." ;
|
|
115
|
-
sh:property [
|
|
116
|
-
sh:path as:actor ;
|
|
117
|
-
sh:name "actor" ;
|
|
118
|
-
sh:description "The one that performed the activity." ;
|
|
119
|
-
sh:nodeKind sh:BlankNodeOrIRI ;
|
|
120
|
-
sh:minCount 1 ;
|
|
121
|
-
sh:maxCount 1 ;
|
|
122
|
-
] ;
|
|
123
|
-
sh:property [
|
|
124
|
-
sh:path as:object ;
|
|
125
|
-
sh:name "object" ;
|
|
126
|
-
sh:description "The thing the activity is about." ;
|
|
127
|
-
sh:minCount 1 ;
|
|
128
|
-
sh:maxCount 1 ;
|
|
129
|
-
] .
|
|
130
|
-
|
|
131
|
-
<#Update>
|
|
132
|
-
a sh:NodeShape ;
|
|
133
|
-
sh:targetClass as:Update ;
|
|
134
|
-
sh:name "Update" ;
|
|
135
|
-
sh:description "A change to the object it names, carrying the parts that are now different." ;
|
|
136
|
-
sh:property [
|
|
137
|
-
sh:path as:actor ;
|
|
138
|
-
sh:name "actor" ;
|
|
139
|
-
sh:description "The one that performed the activity." ;
|
|
140
|
-
sh:nodeKind sh:BlankNodeOrIRI ;
|
|
141
|
-
sh:minCount 1 ;
|
|
142
|
-
sh:maxCount 1 ;
|
|
143
|
-
] ;
|
|
144
|
-
sh:property [
|
|
145
|
-
sh:path as:object ;
|
|
146
|
-
sh:name "object" ;
|
|
147
|
-
sh:description "The thing the activity is about." ;
|
|
148
|
-
sh:minCount 1 ;
|
|
149
|
-
sh:maxCount 1 ;
|
|
150
|
-
] ;
|
|
151
|
-
sh:property [
|
|
152
|
-
sh:path ( as:object as:closed ) ;
|
|
153
|
-
sh:name "closed" ;
|
|
154
|
-
sh:description "The time the object stopped taking anything more." ;
|
|
155
|
-
sh:datatype xsd:dateTime ;
|
|
156
|
-
sh:maxCount 1 ;
|
|
157
|
-
] .
|