fedipod-server 0.29.0 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -6
- package/dist/door.d.ts +4 -0
- package/dist/door.js +31 -0
- package/dist/fedipod.d.ts +16 -0
- package/dist/fedipod.js +13 -0
- package/dist/front.d.ts +3 -0
- package/dist/front.js +94 -0
- package/dist/handler.d.ts +36 -101
- package/dist/handler.js +85 -645
- package/dist/handler.jsonld +2 -30
- package/dist/identities.d.ts +62 -0
- package/dist/identities.js +236 -0
- package/dist/mounts.d.ts +43 -0
- package/dist/mounts.js +123 -0
- package/dist/opt-in.d.ts +46 -0
- package/dist/opt-in.js +176 -0
- package/package.json +10 -18
- package/lib/client/c2s.mjs +0 -636
- package/lib/client/localapi.mjs +0 -87
- package/lib/client/masto/accounts.mjs +0 -310
- package/lib/client/masto/body.mjs +0 -156
- package/lib/client/masto/index.mjs +0 -206
- package/lib/client/masto/instance.mjs +0 -112
- package/lib/client/masto/media.mjs +0 -79
- package/lib/client/masto/oauth.mjs +0 -656
- package/lib/client/masto/render.mjs +0 -518
- package/lib/client/masto/statuses.mjs +0 -372
- package/lib/client/masto/timelines.mjs +0 -438
- package/lib/client/oidc-auth.mjs +0 -62
- package/lib/client/streaming.mjs +0 -154
- package/lib/client/webpush.mjs +0 -98
- package/lib/connections/acctfeed.mjs +0 -257
- package/lib/connections/atproto.mjs +0 -309
- package/lib/connections/bskyfeed.mjs +0 -243
- package/lib/connections/bskygroup.mjs +0 -162
- package/lib/connections/fediacct.mjs +0 -253
- package/lib/connections/import.mjs +0 -357
- package/lib/connections/tagfeed.mjs +0 -212
- package/lib/connections/vault.mjs +0 -114
- package/lib/core/as2.mjs +0 -206
- package/lib/core/contexts/activitystreams.json +0 -379
- package/lib/core/contexts/anno.json +0 -126
- package/lib/core/contexts/did-v1.json +0 -57
- package/lib/core/contexts/fep-5711.json +0 -36
- package/lib/core/contexts/gotosocial.json +0 -86
- package/lib/core/contexts/identity-v1.json +0 -152
- package/lib/core/contexts/index.mjs +0 -63
- package/lib/core/contexts/join-lemmy.json +0 -33
- package/lib/core/contexts/joinmastodon.json +0 -28
- package/lib/core/contexts/litepub-0.1.json +0 -60
- package/lib/core/contexts/map.json +0 -19
- package/lib/core/contexts/miscellany.json +0 -19
- package/lib/core/contexts/quotes.json +0 -18
- package/lib/core/contexts/schemaorg.json +0 -8845
- package/lib/core/contexts/security-data-integrity-v1.json +0 -78
- package/lib/core/contexts/security-data-integrity-v2.json +0 -81
- package/lib/core/contexts/security-multikey-v1.json +0 -35
- package/lib/core/contexts/security-v1.json +0 -74
- package/lib/core/contexts/webfinger.json +0 -10
- package/lib/core/deliver.mjs +0 -387
- package/lib/core/graphview.mjs +0 -327
- package/lib/core/intake/activities.mjs +0 -649
- package/lib/core/intake/activity.mjs +0 -318
- package/lib/core/intake/channel.mjs +0 -144
- package/lib/core/intake/group.mjs +0 -258
- package/lib/core/intake/index.mjs +0 -732
- package/lib/core/intake/notes.mjs +0 -367
- package/lib/core/intake/verify.mjs +0 -142
- package/lib/core/keys.mjs +0 -153
- package/lib/core/lease.mjs +0 -206
- package/lib/core/place.mjs +0 -82
- package/lib/core/polls.mjs +0 -105
- package/lib/core/profile-page.mjs +0 -104
- package/lib/core/proof.mjs +0 -79
- package/lib/core/publisher/collections.mjs +0 -272
- package/lib/core/publisher/index.mjs +0 -522
- package/lib/core/publisher/notes.mjs +0 -519
- package/lib/core/publisher/own.mjs +0 -143
- package/lib/core/publisher/questions.mjs +0 -241
- package/lib/core/publisher/restore.mjs +0 -224
- package/lib/core/shapes/activitystreams.ttl +0 -157
- package/lib/core/shapes/index.mjs +0 -132
- package/lib/core/shapes/shapes-text.mjs +0 -13
- package/lib/core/social.mjs +0 -561
- package/lib/core/storage.mjs +0 -176
- package/lib/core/store.mjs +0 -578
- package/lib/core/wire-quotes.mjs +0 -71
- package/lib/core/wire.mjs +0 -925
- package/lib/device/account.mjs +0 -87
- package/lib/device/admin/index.mjs +0 -13
- package/lib/device/admin/origins.mjs +0 -35
- package/lib/device/admin/routes/connections.mjs +0 -144
- package/lib/device/admin/routes/gateway.mjs +0 -199
- package/lib/device/admin/routes/lifecycle.mjs +0 -200
- package/lib/device/admin/routes/owner.mjs +0 -322
- package/lib/device/admin/routes/setup.mjs +0 -428
- package/lib/device/admin/routes/social.mjs +0 -188
- package/lib/device/admin/server.mjs +0 -95
- package/lib/device/admin/static.mjs +0 -247
- package/lib/device/admin/surface.mjs +0 -315
- package/lib/device/certs.mjs +0 -281
- package/lib/device/cli/commands/account.mjs +0 -586
- package/lib/device/cli/commands/run.mjs +0 -278
- package/lib/device/cli/commands/service.mjs +0 -221
- package/lib/device/cli/commands/setup.mjs +0 -489
- package/lib/device/cli/commands/state.mjs +0 -561
- package/lib/device/cli/context.mjs +0 -288
- package/lib/device/export-collections.mjs +0 -161
- package/lib/device/gateway-move.mjs +0 -58
- package/lib/device/home.mjs +0 -161
- package/lib/device/migrate.mjs +0 -115
- package/lib/device/ports.mjs +0 -27
- package/lib/device/remote.mjs +0 -54
- package/lib/device/setup.mjs +0 -496
- package/lib/device/update.mjs +0 -96
- package/lib/gateway/caches.mjs +0 -36
- package/lib/gateway/directory.mjs +0 -90
- package/lib/gateway/front-core.mjs +0 -966
- package/lib/gateway/gateway-core.mjs +0 -244
- package/lib/gateway/headers.mjs +0 -49
- package/lib/gateway/httpsig.mjs +0 -159
- package/lib/gateway/notices.mjs +0 -84
- package/lib/gateway/quiet.mjs +0 -189
- package/lib/gateway/relay-extras.mjs +0 -89
- package/lib/gateway/token-claims.mjs +0 -16
- package/lib/pod/README.md +0 -61
- package/lib/pod/actor.mjs +0 -96
- package/lib/pod/collection.mjs +0 -77
- package/lib/pod/containers.mjs +0 -140
- package/lib/pod/discovery.mjs +0 -46
- package/lib/pod/featured.mjs +0 -18
- package/lib/pod/followers.mjs +0 -24
- package/lib/pod/following.mjs +0 -10
- package/lib/pod/http.mjs +0 -74
- package/lib/pod/inbox.mjs +0 -166
- package/lib/pod/links.mjs +0 -35
- package/lib/pod/location.mjs +0 -52
- package/lib/pod/media.mjs +0 -27
- package/lib/pod/notes.mjs +0 -78
- package/lib/pod/notifications.mjs +0 -89
- package/lib/pod/outbox.mjs +0 -18
- package/lib/pod/package.json +0 -19
- package/lib/pod/policy.mjs +0 -58
- package/lib/pod/private.mjs +0 -19
- package/lib/pod/root.mjs +0 -104
- package/lib/pod/state.mjs +0 -40
- package/lib/pod/transport.mjs +0 -674
- package/lib/pod/type-index.mjs +0 -101
- package/lib/pod/urls.mjs +0 -91
- package/lib/server/embed.mjs +0 -424
- package/lib/session/README.md +0 -137
- package/lib/session/demo.html +0 -94
- package/lib/session/fedi-account.mjs +0 -417
- package/lib/session/fedi-login.mjs +0 -123
- package/lib/session/oidc-session.mjs +0 -233
- package/lib/session/package.json +0 -49
- package/lib/shared/guard.mjs +0 -213
- package/lib/shared/links.mjs +0 -9
- package/lib/shared/safefetch.mjs +0 -235
- package/lib/shared/ua.mjs +0 -29
- package/phanpy/dist/404.html +0 -40
- package/phanpy/dist/_headers +0 -2
- package/phanpy/dist/apple-touch-icon.png +0 -0
- package/phanpy/dist/assets/ICONS-DIPJeU0C.js +0 -2
- package/phanpy/dist/assets/ICONS-DIPJeU0C.js.map +0 -1
- package/phanpy/dist/assets/ScheduledAtField-D-Abfo4J.js +0 -183
- package/phanpy/dist/assets/ScheduledAtField-D-Abfo4J.js.map +0 -1
- package/phanpy/dist/assets/Temml-CCjoacWt.woff2 +0 -0
- package/phanpy/dist/assets/__vite-browser-external-C7iut881.js +0 -2
- package/phanpy/dist/assets/__vite-browser-external-C7iut881.js.map +0 -1
- package/phanpy/dist/assets/boosts-carousel-BiOaNdMT.jpg +0 -0
- package/phanpy/dist/assets/catch-up-O5q5C75Z.png +0 -0
- package/phanpy/dist/assets/chunk-aKtaBQYM.js +0 -1
- package/phanpy/dist/assets/compose-CFEx7wb4.js +0 -2
- package/phanpy/dist/assets/compose-CFEx7wb4.js.map +0 -1
- package/phanpy/dist/assets/compose-ZJN5xSb4.js +0 -35
- package/phanpy/dist/assets/compose-ZJN5xSb4.js.map +0 -1
- package/phanpy/dist/assets/debug.module-YFQgwnIr.js +0 -52
- package/phanpy/dist/assets/debug.module-YFQgwnIr.js.map +0 -1
- package/phanpy/dist/assets/dom-p1YcJ5Rw.js +0 -2
- package/phanpy/dist/assets/dom-p1YcJ5Rw.js.map +0 -1
- package/phanpy/dist/assets/home-mobile-dark@2x-Dc-J8-SM.png +0 -0
- package/phanpy/dist/assets/home-mobile-light@2x-Bd3fod8m.png +0 -0
- package/phanpy/dist/assets/home-tablet-dark@2x-BgcNI8le.png +0 -0
- package/phanpy/dist/assets/home-tablet-light@2x-BW2RV9T6.png +0 -0
- package/phanpy/dist/assets/icons/add-circle-line-WD0mwORD.js +0 -1
- package/phanpy/dist/assets/icons/alert-line-CpFUMf9s.js +0 -1
- package/phanpy/dist/assets/icons/android-2-line-Bzxnrd3S.js +0 -1
- package/phanpy/dist/assets/icons/announcement-line-5nwaJFJm.js +0 -1
- package/phanpy/dist/assets/icons/arrow-down-circle-line-a7Id9-Iv.js +0 -1
- package/phanpy/dist/assets/icons/arrow-down-line-B8uzXzq6.js +0 -1
- package/phanpy/dist/assets/icons/arrow-left-line-CGrGT9C-.js +0 -1
- package/phanpy/dist/assets/icons/arrow-right-line-CE-HERfX.js +0 -1
- package/phanpy/dist/assets/icons/arrow-to-up-line-CvdQjmcy.js +0 -1
- package/phanpy/dist/assets/icons/arrow-up-circle-line-DEparSNF.js +0 -1
- package/phanpy/dist/assets/icons/arrow-up-line-C7yBeugc.js +0 -1
- package/phanpy/dist/assets/icons/arrows-right-line-BtbXmJGP.js +0 -1
- package/phanpy/dist/assets/icons/at-line-CF66Bznk.js +0 -1
- package/phanpy/dist/assets/icons/attachment-line-BY8It-l9.js +0 -1
- package/phanpy/dist/assets/icons/blockquote-line-BS6pU_hm.js +0 -1
- package/phanpy/dist/assets/icons/board-line-C0UWXyA6.js +0 -1
- package/phanpy/dist/assets/icons/bookmark-line-1CtX5JLF.js +0 -1
- package/phanpy/dist/assets/icons/building-5-line-Dd59MzbO.js +0 -1
- package/phanpy/dist/assets/icons/bus-2-line-6_dvA0GG.js +0 -1
- package/phanpy/dist/assets/icons/calendar-day-line-Crpl_fmu.js +0 -1
- package/phanpy/dist/assets/icons/calendar-month-line-vommts2C.js +0 -1
- package/phanpy/dist/assets/icons/calendar-time-add-line-BU_DTlQk.js +0 -1
- package/phanpy/dist/assets/icons/camera-line-Cv69MEei.js +0 -1
- package/phanpy/dist/assets/icons/celebrate-line-B8DvIoi0.js +0 -1
- package/phanpy/dist/assets/icons/chart-bar-line-FK-QOmtF.js +0 -1
- package/phanpy/dist/assets/icons/chart-line-line-BGcJAFWw.js +0 -1
- package/phanpy/dist/assets/icons/chat-3-line-YFK2-hwz.js +0 -1
- package/phanpy/dist/assets/icons/check-circle-line-DsAxFr5-.js +0 -1
- package/phanpy/dist/assets/icons/clipboard-line-BBp364gJ.js +0 -1
- package/phanpy/dist/assets/icons/close-circle-line-CICdiuGz.js +0 -1
- package/phanpy/dist/assets/icons/close-line-DYrFWcjv.js +0 -1
- package/phanpy/dist/assets/icons/cloud-line-tiQAbah8.js +0 -1
- package/phanpy/dist/assets/icons/code-line-fAl8iiV3.js +0 -1
- package/phanpy/dist/assets/icons/comment-2-line-CR3g7WeB.js +0 -1
- package/phanpy/dist/assets/icons/copy-2-line-CLW69yF5.js +0 -1
- package/phanpy/dist/assets/icons/delete-2-line-B8xmbr4n.js +0 -1
- package/phanpy/dist/assets/icons/document-3-line-DBGNhAg2.js +0 -1
- package/phanpy/dist/assets/icons/document-line-C7mb9sb7.js +0 -1
- package/phanpy/dist/assets/icons/down-line-BGqSSThd.js +0 -1
- package/phanpy/dist/assets/icons/edit-4-line-DcgGaEhx.js +0 -1
- package/phanpy/dist/assets/icons/emoji-2-line-D8YgnM-y.js +0 -1
- package/phanpy/dist/assets/icons/exit-line-Dc07BS-x.js +0 -1
- package/phanpy/dist/assets/icons/external-link-line-BvpSkmjO.js +0 -1
- package/phanpy/dist/assets/icons/eye-2-line-CiWXx-f_.js +0 -1
- package/phanpy/dist/assets/icons/eye-close-line-BgTaSJ4P.js +0 -1
- package/phanpy/dist/assets/icons/filter-2-line-B_mR2usy.js +0 -1
- package/phanpy/dist/assets/icons/filter-line-C_nm-3cU.js +0 -1
- package/phanpy/dist/assets/icons/flag-1-line-C2HB05I0.js +0 -1
- package/phanpy/dist/assets/icons/forbid-circle-line-D3LTT9yd.js +0 -1
- package/phanpy/dist/assets/icons/formula-line-DLrt2d6j.js +0 -1
- package/phanpy/dist/assets/icons/grid-line-CgUmEikf.js +0 -1
- package/phanpy/dist/assets/icons/group-line-C5fnX0wU.js +0 -1
- package/phanpy/dist/assets/icons/hand-finger-2-line-BGgxbuL9.js +0 -1
- package/phanpy/dist/assets/icons/hashtag-line-CyTEEXBb.js +0 -1
- package/phanpy/dist/assets/icons/heart-crack-line-BZddbO2C.js +0 -1
- package/phanpy/dist/assets/icons/heart-line-CJ7K47J4.js +0 -1
- package/phanpy/dist/assets/icons/history-2-line-kWWvFzqv.js +0 -1
- package/phanpy/dist/assets/icons/history-line-bGQiwbTH.js +0 -1
- package/phanpy/dist/assets/icons/home-3-line-fRU1zeLG.js +0 -1
- package/phanpy/dist/assets/icons/information-line-iNk6-sOY.js +0 -1
- package/phanpy/dist/assets/icons/keyboard-line-DsCyuQNz.js +0 -1
- package/phanpy/dist/assets/icons/layout-4-line-CZkOX52Z.js +0 -1
- package/phanpy/dist/assets/icons/layout-5-line-B_znt5wO.js +0 -1
- package/phanpy/dist/assets/icons/left-line-eUw0tya4.js +0 -1
- package/phanpy/dist/assets/icons/lightning-line-DaKoDdix.js +0 -1
- package/phanpy/dist/assets/icons/link-2-line-DCqLmMA7.js +0 -1
- package/phanpy/dist/assets/icons/list-check-line-BAxosBh9.js +0 -1
- package/phanpy/dist/assets/icons/lock-line-CPoaXeUW.js +0 -1
- package/phanpy/dist/assets/icons/mail-line-B0P8aa1L.js +0 -1
- package/phanpy/dist/assets/icons/moon-line-CEqNAToP.js +0 -1
- package/phanpy/dist/assets/icons/more-1-fill-Dw2e5xab.js +0 -1
- package/phanpy/dist/assets/icons/more-3-line-BIiVjYTz.js +0 -1
- package/phanpy/dist/assets/icons/notification-line-BZZq2Gtu.js +0 -1
- package/phanpy/dist/assets/icons/pencil-line-CKh8-A1v.js +0 -1
- package/phanpy/dist/assets/icons/photo-album-line-Bnfg2u40.js +0 -1
- package/phanpy/dist/assets/icons/pin-line-DpTolyhs.js +0 -1
- package/phanpy/dist/assets/icons/play-fill-B0-G_8lX.js +0 -1
- package/phanpy/dist/assets/icons/qrcode-2-line-B6SPCaHn.js +0 -1
- package/phanpy/dist/assets/icons/quill-pen-line-B6LUrnCP.js +0 -1
- package/phanpy/dist/assets/icons/quote-left-fill-hQZ1cj_6.js +0 -1
- package/phanpy/dist/assets/icons/radar-line-CKyroTth.js +0 -1
- package/phanpy/dist/assets/icons/react-line-CoIKhhtK.js +0 -1
- package/phanpy/dist/assets/icons/refresh-2-line-D0mTF97S.js +0 -1
- package/phanpy/dist/assets/icons/right-line-B3KWJaqc.js +0 -1
- package/phanpy/dist/assets/icons/rocket-line-4KZl537D.js +0 -1
- package/phanpy/dist/assets/icons/round-fill-C69NTrZ0.js +0 -1
- package/phanpy/dist/assets/icons/round-line-B9k_1uo-.js +0 -1
- package/phanpy/dist/assets/icons/route-line-B28ReHcE.js +0 -1
- package/phanpy/dist/assets/icons/rows-4-line-CrBniagM.js +0 -1
- package/phanpy/dist/assets/icons/scan-line-DSf74YdP.js +0 -1
- package/phanpy/dist/assets/icons/search-2-line-B6geIU1z.js +0 -1
- package/phanpy/dist/assets/icons/settings-3-line-azJwK47w.js +0 -1
- package/phanpy/dist/assets/icons/settings-6-line-C_5zc5YR.js +0 -1
- package/phanpy/dist/assets/icons/share-2-line-D5MxWjrm.js +0 -1
- package/phanpy/dist/assets/icons/share-forward-line-Cp61fZzZ.js +0 -1
- package/phanpy/dist/assets/icons/sparkles-2-line-DYYyXTaW.js +0 -1
- package/phanpy/dist/assets/icons/sparkles-line-C2hY1s6I.js +0 -1
- package/phanpy/dist/assets/icons/time-line-BF3eet1v.js +0 -1
- package/phanpy/dist/assets/icons/transfer-4-line-oisfvTBw.js +0 -1
- package/phanpy/dist/assets/icons/translate-line-BFWol2Ce.js +0 -1
- package/phanpy/dist/assets/icons/unlock-line-X_x5vwv3.js +0 -1
- package/phanpy/dist/assets/icons/upload-3-line-DgzwUJeW.js +0 -1
- package/phanpy/dist/assets/icons/user-4-line-BVSynZCp.js +0 -1
- package/phanpy/dist/assets/icons/user-add-2-line-BlGS5Q8q.js +0 -1
- package/phanpy/dist/assets/icons/user-add-line-BbXdHG4z.js +0 -1
- package/phanpy/dist/assets/icons/user-edit-line-BFAHOV0X.js +0 -1
- package/phanpy/dist/assets/icons/user-follow-line-BXux0wSd.js +0 -1
- package/phanpy/dist/assets/icons/user-star-line-DdYlpHLU.js +0 -1
- package/phanpy/dist/assets/icons/user-warning-line-Cj43j0_E.js +0 -1
- package/phanpy/dist/assets/icons/user-x-line-DIDgNlPH.js +0 -1
- package/phanpy/dist/assets/icons/volume-line-Bs390KQt.js +0 -1
- package/phanpy/dist/assets/icons/volume-mute-line-DzlzIqFZ.js +0 -1
- package/phanpy/dist/assets/icons/walk-line-yNXfB-md.js +0 -1
- package/phanpy/dist/assets/icons/world-2-line-CenkGIwj.js +0 -1
- package/phanpy/dist/assets/icons/zoom-in-line-CaENUphf.js +0 -1
- package/phanpy/dist/assets/icons/zoom-out-line-BDNk0EWo.js +0 -1
- package/phanpy/dist/assets/instances-BNPptnn-.json +0 -333
- package/phanpy/dist/assets/locales/ar-SA-CfheRbCu.js +0 -1
- package/phanpy/dist/assets/locales/ca-ES-DHRW86Tz.js +0 -1
- package/phanpy/dist/assets/locales/cs-CZ-CKoqEuA1.js +0 -1
- package/phanpy/dist/assets/locales/de-DE-ByRPMRFy.js +0 -1
- package/phanpy/dist/assets/locales/eo-UY-CPC7wLCS.js +0 -1
- package/phanpy/dist/assets/locales/es-ES-dJa0Fd9t.js +0 -1
- package/phanpy/dist/assets/locales/eu-ES-C3djlPR2.js +0 -1
- package/phanpy/dist/assets/locales/fa-IR-DTT490Ck.js +0 -1
- package/phanpy/dist/assets/locales/fi-FI-B23MCPuz.js +0 -1
- package/phanpy/dist/assets/locales/fr-FR-BcaQsO5C.js +0 -1
- package/phanpy/dist/assets/locales/gl-ES-BR-CVKBN.js +0 -1
- package/phanpy/dist/assets/locales/he-IL-CfheRbCu.js +0 -1
- package/phanpy/dist/assets/locales/hu-HU-CfheRbCu.js +0 -1
- package/phanpy/dist/assets/locales/it-IT-C1136UFV.js +0 -1
- package/phanpy/dist/assets/locales/ja-JP-CRZmRAhD.js +0 -1
- package/phanpy/dist/assets/locales/kab-Cz8xH_TT.js +0 -1
- package/phanpy/dist/assets/locales/ko-KR-CG9UiY4o.js +0 -1
- package/phanpy/dist/assets/locales/lt-LT-CJcI6sHZ.js +0 -1
- package/phanpy/dist/assets/locales/nb-NO-CfheRbCu.js +0 -1
- package/phanpy/dist/assets/locales/nl-NL-DxN4Yc1t.js +0 -1
- package/phanpy/dist/assets/locales/oc-FR-CfheRbCu.js +0 -1
- package/phanpy/dist/assets/locales/pl-PL-DkwGXf6P.js +0 -1
- package/phanpy/dist/assets/locales/pseudo-LOCALE-CQ4XxkQ0.js +0 -1
- package/phanpy/dist/assets/locales/pt-BR-nGL0HdFS.js +0 -1
- package/phanpy/dist/assets/locales/pt-PT-Cp17aj2z.js +0 -1
- package/phanpy/dist/assets/locales/ru-RU-0Y8cIoaK.js +0 -1
- package/phanpy/dist/assets/locales/th-TH-CfheRbCu.js +0 -1
- package/phanpy/dist/assets/locales/tok-CfheRbCu.js +0 -1
- package/phanpy/dist/assets/locales/tr-TR-CfheRbCu.js +0 -1
- package/phanpy/dist/assets/locales/uk-UA-C5E3dFBf.js +0 -1
- package/phanpy/dist/assets/locales/zh-CN-BHJi7_et.js +0 -1
- package/phanpy/dist/assets/locales/zh-TW-CfheRbCu.js +0 -1
- package/phanpy/dist/assets/main-BdqNbG-a.js +0 -26
- package/phanpy/dist/assets/main-BdqNbG-a.js.map +0 -1
- package/phanpy/dist/assets/mock-home-DGhR__He.js +0 -2
- package/phanpy/dist/assets/mock-home-DGhR__He.js.map +0 -1
- package/phanpy/dist/assets/multi-column-ROck0NVt.jpg +0 -0
- package/phanpy/dist/assets/multi-hashtag-timeline-DH5INVHi.jpg +0 -0
- package/phanpy/dist/assets/nested-comments-thread-hv59kZLc.jpg +0 -0
- package/phanpy/dist/assets/polyfill-force-H8sUkLGp.js +0 -2
- package/phanpy/dist/assets/polyfill-force-H8sUkLGp.js.map +0 -1
- package/phanpy/dist/assets/pwa-viewport-ogKbF51K.js +0 -20
- package/phanpy/dist/assets/pwa-viewport-ogKbF51K.js.map +0 -1
- package/phanpy/dist/assets/style-B1iLPTPR.css +0 -2
- package/phanpy/dist/assets/temml-DuyIQMei.js +0 -97
- package/phanpy/dist/assets/temml-DuyIQMei.js.map +0 -1
- package/phanpy/dist/assets/tinyld.light.node-DtbBhLYC.js +0 -2
- package/phanpy/dist/assets/tinyld.light.node-DtbBhLYC.js.map +0 -1
- package/phanpy/dist/assets/year-in-posts-B2lywqdx.png +0 -0
- package/phanpy/dist/assets/year-in-posts-DJv_star.js +0 -3
- package/phanpy/dist/assets/year-in-posts-DJv_star.js.map +0 -1
- package/phanpy/dist/compose/index.html +0 -20
- package/phanpy/dist/favicon.ico +0 -0
- package/phanpy/dist/index.html +0 -73
- package/phanpy/dist/logo-192.png +0 -0
- package/phanpy/dist/logo-512.png +0 -0
- package/phanpy/dist/logo-badge-72.png +0 -0
- package/phanpy/dist/logo-maskable-512.png +0 -0
- package/phanpy/dist/logo-monochrome-512.png +0 -0
- package/phanpy/dist/logo-monochrome-maskable-512.png +0 -0
- package/phanpy/dist/manifest.webmanifest +0 -1
- package/phanpy/dist/og-image-2.jpg +0 -0
- package/phanpy/dist/og-image.png +0 -0
- package/phanpy/dist/robots.txt +0 -2
- package/phanpy/dist/sw.js +0 -2
- package/phanpy/dist/sw.js.map +0 -1
- package/phanpy/dist/version.json +0 -1
- package/run-agent.mjs +0 -899
- package/vendor/gate.cjs +0 -138
- package/vendor/idp-grant.cjs +0 -394
- package/vendor/jfetch.cjs +0 -16
- package/web/admin/actors.js +0 -147
- package/web/admin/bar.css +0 -126
- package/web/admin/bar.js +0 -99
- package/web/admin/client/client.js +0 -66
- package/web/admin/client/index.html +0 -69
- package/web/admin/client-pick.js +0 -79
- package/web/admin/common.js +0 -23
- package/web/admin/connections.js +0 -112
- package/web/admin/gateway.js +0 -133
- package/web/admin/group.js +0 -258
- package/web/admin/index.html +0 -612
- package/web/admin/notices-bar.js +0 -90
- package/web/admin/oauth-signin.mjs +0 -37
- package/web/admin/record.js +0 -393
- package/web/admin/setup/index.html +0 -257
- package/web/admin/setup/setup.js +0 -339
- package/web/admin/tokens.css +0 -68
- package/web/admin/upkeep.js +0 -177
- package/web/admin/window.css +0 -55
- package/web/admin/window.js +0 -136
- package/web/app/README.md +0 -81
- package/web/app/admin-facade.mjs +0 -588
- package/web/app/agent.mjs +0 -672
- package/web/app/atproto-browser.mjs +0 -86
- package/web/app/authorize_interaction.html +0 -56
- package/web/app/authorize_interaction.js +0 -69
- package/web/app/boot.mjs +0 -616
- package/web/app/deliver-relay.mjs +0 -129
- package/web/app/dist/boot.js +0 -34562
- package/web/app/dist/boot.js.map +0 -7
- package/web/app/dist/sw.js +0 -73456
- package/web/app/dist/sw.js.map +0 -7
- package/web/app/fediacct-browser.mjs +0 -146
- package/web/app/gateway-move.mjs +0 -60
- package/web/app/idb-kv.mjs +0 -35
- package/web/app/index.html +0 -258
- package/web/app/keys-browser.mjs +0 -71
- package/web/app/keystore.mjs +0 -82
- package/web/app/oidc-session.mjs +0 -6
- package/web/app/pod-auth.mjs +0 -248
- package/web/app/pod-remote.mjs +0 -84
- package/web/app/shims/_globals.mjs +0 -2
- package/web/app/shims/fedify-sig.mjs +0 -77
- package/web/app/shims/node-crypto.mjs +0 -113
- package/web/app/shims/node-fs.mjs +0 -17
- package/web/app/shims/node-path.mjs +0 -19
- package/web/app/shims/node-url.mjs +0 -7
- package/web/app/shims/prelude.js +0 -58
- package/web/app/shims/safefetch.mjs +0 -58
- package/web/app/shims/shapes-text.mjs +0 -8
- package/web/app/shims/web-push.mjs +0 -14
- package/web/app/signup.mjs +0 -357
- package/web/app/sw-src.mjs +0 -220
- package/web/app/update.js +0 -70
- package/web/front/admin.html +0 -55
- package/web/front/admin.js +0 -89
- package/web/front/install.sh +0 -69
- package/web/front/new-account.html +0 -35
- package/web/front/new-account.js +0 -13
- package/web/front/notices.html +0 -69
- package/web/front/notices.js +0 -107
- package/web/front/run.html +0 -64
- package/web/front/run.js +0 -221
- package/web/front/solid-oidc-client.js +0 -6
- package/web/icons/forum.svg +0 -11
- package/web/icons/gateway.svg +0 -14
- package/web/icons/local.svg +0 -11
package/web/app/agent.mjs
DELETED
|
@@ -1,672 +0,0 @@
|
|
|
1
|
-
// agent.mjs — FediPod's agent, running in the browser.
|
|
2
|
-
//
|
|
3
|
-
// The Node agent (run-agent.mjs) wires the same pieces; this is that wiring with
|
|
4
|
-
// the browser's edges: the pod is reached over the DPoP session (pod-remote),
|
|
5
|
-
// the signing key comes from WebCrypto (keys-browser), and delivery goes through
|
|
6
|
-
// the relay (deliver-relay). Everything between — wire, the store, the
|
|
7
|
-
// publisher, intake, the Mastodon facade — is lib/, unchanged.
|
|
8
|
-
import { kvGet, kvPut } from './idb-kv.mjs';
|
|
9
|
-
import { apUrls, DEFAULT_ROOT } from '../../lib/core/wire.mjs';
|
|
10
|
-
import { findAccount } from '../../lib/core/place.mjs';
|
|
11
|
-
import * as containers from '../../lib/pod/containers.mjs';
|
|
12
|
-
import * as podState from '../../lib/pod/state.mjs';
|
|
13
|
-
import { PodStore } from '../../lib/core/store.mjs';
|
|
14
|
-
import { HttpStorage } from '../../lib/core/storage.mjs';
|
|
15
|
-
import { Publisher } from '../../lib/core/publisher/index.mjs';
|
|
16
|
-
import { Intake } from '../../lib/core/intake/index.mjs';
|
|
17
|
-
import { C2S } from '../../lib/client/c2s.mjs';
|
|
18
|
-
import { Lease } from '../../lib/core/lease.mjs';
|
|
19
|
-
import { MastoApi } from '../../lib/client/masto/index.mjs';
|
|
20
|
-
import { TagFeed } from '../../lib/connections/tagfeed.mjs';
|
|
21
|
-
import { makeDpopSession } from './pod-auth.mjs';
|
|
22
|
-
import { BrowserRemotePod } from './pod-remote.mjs';
|
|
23
|
-
import { importSigningKey, loadKeysFromPod, cacheOpenedKeys, podActorOf } from './keys-browser.mjs';
|
|
24
|
-
import { generateKeys } from './keystore.mjs';
|
|
25
|
-
import { RelayDeliverer, doorKeyOf } from './deliver-relay.mjs';
|
|
26
|
-
import { AdminFacade } from './admin-facade.mjs';
|
|
27
|
-
import { completeGatewayMove } from './gateway-move.mjs';
|
|
28
|
-
import { BrowserAtproto } from './atproto-browser.mjs';
|
|
29
|
-
import { BskyFeed } from '../../lib/connections/bskyfeed.mjs';
|
|
30
|
-
import { BrowserFediAccounts } from './fediacct-browser.mjs';
|
|
31
|
-
import { AcctFeed } from '../../lib/connections/acctfeed.mjs';
|
|
32
|
-
import { followActor, unfollowActor, resolveHandle } from '../../lib/core/social.mjs';
|
|
33
|
-
import { podBaseOfWebId } from '../../lib/pod/urls.mjs';
|
|
34
|
-
import { ImportWorker } from '../../lib/connections/import.mjs';
|
|
35
|
-
|
|
36
|
-
// The authorities this identity answers on: exactly one, this origin. The Node
|
|
37
|
-
// agent gets this from lib/guard.mjs, which is not in the browser bundle and
|
|
38
|
-
// would not fit if it were — its whole subject is loopback and named local
|
|
39
|
-
// origins, neither of which means anything here.
|
|
40
|
-
//
|
|
41
|
-
// Two questions are asked of it. `has()` decides whether an OAuth redirect_uri
|
|
42
|
-
// is somewhere this agent actually answers. `isLocalRequest()` decides whether
|
|
43
|
-
// a caller is the owner rather than a stranger, and in a browser that question
|
|
44
|
-
// has exactly one honest answer: whether the request came from this origin —
|
|
45
|
-
// which the service worker has already established before anything reaches the
|
|
46
|
-
// facade, and stamps on the request it builds (see sw-src.mjs).
|
|
47
|
-
const originAuthorities = (host) => ({
|
|
48
|
-
set: new Set([String(host || '').toLowerCase()]),
|
|
49
|
-
has(authority) { return this.set.has(String(authority || '').toLowerCase()); },
|
|
50
|
-
isLocalRequest(req) { return req?.sameOrigin === true; },
|
|
51
|
-
isLocal(h) { return this.has(h); },
|
|
52
|
-
wsAuthorities() { return []; }, // fetch-only: this build serves no socket
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
// Why an account did not open, in words for the person it happened to, and a
|
|
56
|
-
// `code` the sign-in page turns into a title and the one button that leads
|
|
57
|
-
// somewhere (boot.mjs). Every one of these used to be the same sentence —
|
|
58
|
-
// "no account config on this pod — sign up first" — which named the wrong
|
|
59
|
-
// cause, offered the wrong cure, and left the reader with nothing to press.
|
|
60
|
-
//
|
|
61
|
-
// Each message says three things and stops: what the pod did, what that means
|
|
62
|
-
// for the account, what to do about it. The pod's own wording is carried only
|
|
63
|
-
// where it adds something the status code does not.
|
|
64
|
-
async function accountNotRead({ unread, podBase, state, webId, actorUrl = null, fetchImpl = globalThis.fetch }) {
|
|
65
|
-
const host = (() => { try { return new URL(podBase).host; } catch { return podBase; } })();
|
|
66
|
-
const of = (code, message) => { const e = new Error(message); e.code = code; return e; };
|
|
67
|
-
if (!unread) {
|
|
68
|
-
// A pod that holds the account's actor and no record is an account a
|
|
69
|
-
// DeviceAgent runs: the record lives on the device, and only the device
|
|
70
|
-
// can open it. Said so, rather than "there is no account".
|
|
71
|
-
const actor = actorUrl
|
|
72
|
-
? await fetchImpl(actorUrl, { headers: { accept: 'application/activity+json' } }).then((r) => (r.ok ? r.json() : null)).catch(() => null)
|
|
73
|
-
: null;
|
|
74
|
-
if (actor?.preferredUsername) {
|
|
75
|
-
const at = (() => { try { return `@${actor.preferredUsername}@${new URL(actor.id || actorUrl).host}`; } catch { return actor.preferredUsername; } })();
|
|
76
|
-
return of('device-account', `${host} holds the account ${at}, but not its record: this account is run by a`
|
|
77
|
-
+ ` DeviceAgent, and its record lives on that device. Open it from that device's admin page, not here.`
|
|
78
|
-
+ ` The sign-in used here was ${webId}.`);
|
|
79
|
-
}
|
|
80
|
-
return of('no-account', `${host} answered, and there is no FediPod account in it.`
|
|
81
|
-
+ ` Sign in with the pod that holds your account, or make an account in this one —`
|
|
82
|
-
+ ` both start from the sign-in page. The sign-in used here was ${webId}.`);
|
|
83
|
-
}
|
|
84
|
-
const status = Number((/HTTP (\d{3})/u.exec(unread.message) || [])[1]) || 0;
|
|
85
|
-
if (status === 401 || status === 403) {
|
|
86
|
-
// The page renews a refused sign-in by itself, and takes the person to
|
|
87
|
-
// their pod's login when the renewal is refused too (boot.mjs). By the
|
|
88
|
-
// time anybody reads this, both have already been tried.
|
|
89
|
-
return of('sign-in-refused', `${host} would not let this sign-in read your account — it answered`
|
|
90
|
-
+ ` "not allowed" (HTTP ${status}) — and renewing the sign-in did not change its mind.`
|
|
91
|
-
+ ` Your account and everything in it are untouched. Signing in at ${host} again is the`
|
|
92
|
-
+ ` one thing that helps.`);
|
|
93
|
-
}
|
|
94
|
-
if (status === 429) {
|
|
95
|
-
return of('pod-busy', `${host} is telling this browser it has asked for too much too quickly`
|
|
96
|
-
+ ` (HTTP 429), so your account could not be read. Nothing is wrong with your account, and`
|
|
97
|
-
+ ` nothing here needs doing: this page is waiting the throttle out and will ask again by`
|
|
98
|
-
+ ` itself. While a pod is rate-limiting you, only time helps.`);
|
|
99
|
-
}
|
|
100
|
-
if (status >= 500) {
|
|
101
|
-
return of('pod-error', `${host} broke while handing your account over (HTTP ${status}).`
|
|
102
|
-
+ ` That is the pod's own failure, not your account's. This page asks again by itself in`
|
|
103
|
-
+ ` a moment; if it keeps saying this, the pod is down and there is nothing to do here.`);
|
|
104
|
-
}
|
|
105
|
-
if (status === 404) {
|
|
106
|
-
return of('no-account-here', `${host} says there is nothing at ${state} (HTTP 404).`
|
|
107
|
-
+ ` Either no FediPod account has ever been set up in this pod, or you signed in with a`
|
|
108
|
-
+ ` different pod than the one holding yours. Sign in with that one, or make an account`
|
|
109
|
-
+ ` in this one — both start from the sign-in page.`);
|
|
110
|
-
}
|
|
111
|
-
// No status at all: the request never got an answer. The pod's own words are
|
|
112
|
-
// the only thing that says anything here, so they come along.
|
|
113
|
-
return of('pod-unreachable', `This browser could not reach ${host} to read your account`
|
|
114
|
-
+ ` (${unread.message}). Your account is still there. This page tries again by itself;`
|
|
115
|
-
+ ` if it keeps saying this, check that you are online and that the pod is up.`);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
export class BrowserAgent {
|
|
119
|
-
constructor({ log = console.log } = {}) {
|
|
120
|
-
this.log = log;
|
|
121
|
-
this.viewer = false;
|
|
122
|
-
this.fediaccts = null;
|
|
123
|
-
this.atproto = null;
|
|
124
|
-
this.intake = null;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
configured() { return !!this.store?.getConfig(); }
|
|
128
|
-
|
|
129
|
-
// Asked by every write / destructive path before it acts. If we already hold
|
|
130
|
-
// the lease, proceed. If we are a viewer, the owner acting HERE outranks the
|
|
131
|
-
// idle active device: claim the lease outright and become active.
|
|
132
|
-
async requestTakeover() {
|
|
133
|
-
if (!this.viewer) return true;
|
|
134
|
-
if (!(await this.lease.takeover())) return false;
|
|
135
|
-
clearTimeout(this._viewerTimer); this._viewerTimer = null;
|
|
136
|
-
await this.goActive();
|
|
137
|
-
this.log('took over from the other device (action here)');
|
|
138
|
-
return true;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// One call to the gateway's owner API, proved with the pod session. What
|
|
142
|
-
// comes back on 200 or 410 is the account's standing there, kept for the
|
|
143
|
-
// manage page; anything else is logged and forgotten.
|
|
144
|
-
async tellGateway(what, body) {
|
|
145
|
-
if (!this.gatewayApi) return null;
|
|
146
|
-
let res;
|
|
147
|
-
try {
|
|
148
|
-
res = await this.sessionFetch(`${this.gatewayApi}/${what}`, {
|
|
149
|
-
method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body),
|
|
150
|
-
});
|
|
151
|
-
} catch (e) { this.log(`gateway ${what}: ${e.message}`); return null; }
|
|
152
|
-
const json = await res.json().catch(() => ({}));
|
|
153
|
-
const out = { status: res.status, ...json };
|
|
154
|
-
if (res.status === 200 || res.status === 410) this.gatewayStanding = out;
|
|
155
|
-
else this.log(`gateway ${what}: ${res.status} ${json.error || ''}`);
|
|
156
|
-
return out;
|
|
157
|
-
}
|
|
158
|
-
openAtGateway() { return this.tellGateway('open', { handle: this.doorKey }); }
|
|
159
|
-
pauseAtGateway(paused) { return this.tellGateway('pause', { handle: this.doorKey, paused: !!paused }); }
|
|
160
|
-
closeAtGateway() { return this.tellGateway('close', { handle: this.doorKey, confirm: true }); }
|
|
161
|
-
static OPEN_EVERY_MS = 60 * 60_000;
|
|
162
|
-
|
|
163
|
-
// Become the active agent: renew the lease, then start what a viewer skips —
|
|
164
|
-
// publish the face, drain the inbox, run the mirrors.
|
|
165
|
-
async goActive() {
|
|
166
|
-
this.viewer = false;
|
|
167
|
-
// Reading here is being here: the gateway hears so once an hour.
|
|
168
|
-
clearInterval(this._openTimer);
|
|
169
|
-
this._openTimer = setInterval(() => { this.openAtGateway(); }, BrowserAgent.OPEN_EVERY_MS);
|
|
170
|
-
this.lease.onLost = () => this.demote();
|
|
171
|
-
this.lease.startRenewal();
|
|
172
|
-
try {
|
|
173
|
-
// Forced, not revalidated, when this device WATCHED first: the active
|
|
174
|
-
// one was writing, our cache is however stale the last viewer poll left
|
|
175
|
-
// it, and the store is write-through — so the first write from here
|
|
176
|
-
// would push a whole document back over newer state. A device that
|
|
177
|
-
// booted straight into acting read everything a moment ago; asking
|
|
178
|
-
// again is one revalidation, not a second download of every document.
|
|
179
|
-
await this.store.load({ force: !!this._watched }).catch((e) => this.log(`re-reading state: ${e.message}`));
|
|
180
|
-
this._watched = false;
|
|
181
|
-
// Own posts the outbox names and the timeline index lacks come back
|
|
182
|
-
// here, before anything acts on the index.
|
|
183
|
-
await this.publisher.healStatuses().catch((e) => this.log(`healing the timeline index: ${e.message}`));
|
|
184
|
-
// Likes made before the liked list existed become its first entries.
|
|
185
|
-
try { this.publisher.backfillLiked(); } catch (e) { this.log(`liked list: ${e.message}`); }
|
|
186
|
-
await this.publisher.publishProfilePage().catch((e) => this.log(`profile page: ${e.message}`));
|
|
187
|
-
// And start delivering again, since demote() stopped it. startQueue() is
|
|
188
|
-
// idempotent, so a goActive() that was already active costs nothing.
|
|
189
|
-
this.deliverer?.startQueue?.();
|
|
190
|
-
await this.publisher.publishProfile();
|
|
191
|
-
await this.store.flush?.();
|
|
192
|
-
// An address that just moved here from another gateway: the new
|
|
193
|
-
// actor is published, so the old gateway and the followers can be told.
|
|
194
|
-
await completeGatewayMove(this).catch((e) => this.log(`gateway move: ${e.message}`));
|
|
195
|
-
await this.intake.start();
|
|
196
|
-
this.startBsky();
|
|
197
|
-
this.startAccts();
|
|
198
|
-
this.tagfeed?.start();
|
|
199
|
-
// Resumes a run that was staged on another device, or here before a
|
|
200
|
-
// restart — the state lives on the pod, not in this process.
|
|
201
|
-
this.importer?.start();
|
|
202
|
-
} catch (e) { this.log(`going active: ${e.message}`); }
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Another device took the lease: stop acting (the drain also self-checks the
|
|
206
|
-
// lease), keep serving the feed read-only, and poll to take it back if freed.
|
|
207
|
-
demote() {
|
|
208
|
-
if (this.viewer) return;
|
|
209
|
-
this.viewer = true;
|
|
210
|
-
this._watched = true;
|
|
211
|
-
this.log('another device took over — read-only here');
|
|
212
|
-
this.lease.stopRenewal();
|
|
213
|
-
clearInterval(this._openTimer); this._openTimer = null;
|
|
214
|
-
this.intake?.stop?.();
|
|
215
|
-
// The delivery queue as well. Its timer starts in the Deliverer's
|
|
216
|
-
// constructor and nothing here ever switched it off, so a demoted device
|
|
217
|
-
// went on sending from a queue the active device is also sending from —
|
|
218
|
-
// the same activity delivered twice, from two browsers, over one key.
|
|
219
|
-
this.deliverer?.stop?.();
|
|
220
|
-
this.importer?.stop?.();
|
|
221
|
-
this.stopBsky(); this.stopAccts(); this.tagfeed?.stop?.();
|
|
222
|
-
this.startViewerPoll();
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// A viewer refreshes the feed from the pod (the active device updates it), and
|
|
226
|
-
// promotes the moment the lease is free.
|
|
227
|
-
// Five minutes with jitter, matching the Node agent (run-agent.mjs). Two
|
|
228
|
-
// minutes flat was ~60 pod requests an hour from a device that is not acting,
|
|
229
|
-
// and unjittered meant several idle devices on one pod knocking in lockstep.
|
|
230
|
-
static VIEWER_POLL_MS = 5 * 60_000;
|
|
231
|
-
|
|
232
|
-
startViewerPoll() {
|
|
233
|
-
clearTimeout(this._viewerTimer);
|
|
234
|
-
const every = () => BrowserAgent.VIEWER_POLL_MS * (0.85 + Math.random() * 0.3);
|
|
235
|
-
const tick = () => {
|
|
236
|
-
this._viewerTimer = setTimeout(async () => {
|
|
237
|
-
try {
|
|
238
|
-
if (!this.viewer) return;
|
|
239
|
-
await this.store.load().catch(() => {}); // revalidating is enough while watching
|
|
240
|
-
if (await this.lease.acquire()) { await this.goActive(); this.log('lease freed — now active'); return; }
|
|
241
|
-
} catch (e) { this.log(`viewer poll: ${e.message}`); }
|
|
242
|
-
if (this.viewer) tick();
|
|
243
|
-
}, every());
|
|
244
|
-
};
|
|
245
|
-
tick();
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
* Boot from what sign-up (or sign-in) produced.
|
|
250
|
-
* @param credential the pod credential { clientId, secret, tokenEndpoint, webId, remotePod, root }
|
|
251
|
-
* @param keysRecord the plaintext signing keys { rsa:{privatePem,publicPem} }
|
|
252
|
-
* @param config { remotePod, root, handle, name, issuer, gateway? }
|
|
253
|
-
* @param opts.frontOrigin where the relay lives (for delivery)
|
|
254
|
-
*/
|
|
255
|
-
/**
|
|
256
|
-
* Boot the agent. Two ways in:
|
|
257
|
-
* - { oidc }: a Solid-OIDC session from oidc-session.getSession() (the real,
|
|
258
|
-
* redirect-login path). Config and keys are read from the pod, which the
|
|
259
|
-
* session is authenticated to; a fresh sign-up writes them first.
|
|
260
|
-
* - { credential, keysRecord, config }: a client-credential session with the
|
|
261
|
-
* material in hand (the offline test path).
|
|
262
|
-
*/
|
|
263
|
-
// This browser's name for itself, minted once and kept. Not an identity and
|
|
264
|
-
// not a secret — it says only "the same browser as last time" to the lease.
|
|
265
|
-
// If storage cannot be reached, a fresh one is honest: this browser cannot
|
|
266
|
-
// prove it is the one that held the lease, so it should not claim to be.
|
|
267
|
-
async deviceId() {
|
|
268
|
-
try {
|
|
269
|
-
const had = await kvGet('device-id');
|
|
270
|
-
if (had) return had;
|
|
271
|
-
const made = crypto.randomUUID();
|
|
272
|
-
await kvPut('device-id', made);
|
|
273
|
-
return made;
|
|
274
|
-
} catch {
|
|
275
|
-
return null;
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
async boot({ oidc, credential, keysRecord, config, frontOrigin }) {
|
|
280
|
-
let session; let webId; let remotePod;
|
|
281
|
-
if (oidc) {
|
|
282
|
-
session = { fetch: (u, i) => oidc.fetch(u, i) };
|
|
283
|
-
webId = oidc.webId;
|
|
284
|
-
remotePod = podBaseOfWebId(webId); // the pod, which on a suffix-based host is a path
|
|
285
|
-
} else {
|
|
286
|
-
const dpop = await makeDpopSession(credential);
|
|
287
|
-
session = { fetch: (u, i) => dpop.fetch(u, i) };
|
|
288
|
-
webId = credential.webId;
|
|
289
|
-
remotePod = credential.remotePod;
|
|
290
|
-
}
|
|
291
|
-
this.webId = webId;
|
|
292
|
-
// The pod session's own fetch, for the gateway APIs that take it as
|
|
293
|
-
// proof of the pod (attach, move) — the transport below is for the pod.
|
|
294
|
-
this.sessionFetch = session.fetch;
|
|
295
|
-
this.remote = new BrowserRemotePod(session, { webId, log: this.log });
|
|
296
|
-
// Where the account lives: handed in on sign-up, else what its owner's type
|
|
297
|
-
// index records, else `fedipod/` for an account older than the choice.
|
|
298
|
-
const root = (config && config.root)
|
|
299
|
-
|| (await findAccount(this.remote, remotePod,
|
|
300
|
-
(r) => podState.readConfig(this.remote, { state: `${remotePod}${r}ap-state/` }),
|
|
301
|
-
(c) => (c.kind || 'person') === 'person').catch(() => null))?.root
|
|
302
|
-
|| DEFAULT_ROOT;
|
|
303
|
-
// Pod-native for now: the state store below is read with these, and only
|
|
304
|
-
// the config it holds says whether this identity is fronted.
|
|
305
|
-
this.urls = apUrls(remotePod, root);
|
|
306
|
-
|
|
307
|
-
// State store, on the pod.
|
|
308
|
-
// Through the transport, not the raw session: state writes are pod writes,
|
|
309
|
-
// and going round it skipped the Retry-After cooldown, the retry ladder and
|
|
310
|
-
// the deletion deny-list that every other write on this agent observes.
|
|
311
|
-
// The Node agent has always passed remote.fetch here.
|
|
312
|
-
const podFetch = (u, i) => this.remote.fetch(u, i);
|
|
313
|
-
this.store = new PodStore({ storage: new HttpStorage(this.urls.state, podFetch), log: this.log });
|
|
314
|
-
// A read that FAILED is not an account that is not there. load() already
|
|
315
|
-
// tells those apart — a missing container, a refused token, a pod saying
|
|
316
|
-
// "too many requests" each come back with their own reason — and throwing
|
|
317
|
-
// the reason away turned every one of them into "sign up first", said to
|
|
318
|
-
// somebody who had just signed in and whose account was sitting on the pod
|
|
319
|
-
// unread. Keep it: below is the only place that can tell whether it
|
|
320
|
-
// mattered, because a sign-up hands its config in and needs no read at all.
|
|
321
|
-
let unread = null;
|
|
322
|
-
await this.store.load().catch((e) => { unread = e; });
|
|
323
|
-
// Config: handed in on sign-up, or read from the pod on a returning sign-in.
|
|
324
|
-
const cfg = config || this.store.getConfig();
|
|
325
|
-
if (!cfg) throw await accountNotRead({ unread, podBase: remotePod, state: this.urls.state, webId, actorUrl: this.urls.actor });
|
|
326
|
-
// `root` is written INTO the config, not just used above. The Publisher
|
|
327
|
-
// builds its own urls from `config.root` (publisher.mjs), and a config
|
|
328
|
-
// without one falls to the Node default — so an account set up elsewhere
|
|
329
|
-
// and signed into here would keep its state under `fedipod/` while every
|
|
330
|
-
// document it published landed under a different root. One root, decided
|
|
331
|
-
// once, carried by the config everything downstream reads.
|
|
332
|
-
this.store.setConfig({ ...(this.store.getConfig() || {}), ...cfg, root });
|
|
333
|
-
config = this.store.getConfig();
|
|
334
|
-
// A fronted identity (config.gateway.frontActor) advertises its ids at the
|
|
335
|
-
// gateway; the documents stay on the pod. Same three lines as the Node
|
|
336
|
-
// agent (run-agent.mjs connect): the advertised urls, and the map that
|
|
337
|
-
// turns an advertised url back into the pod one at the transport's single
|
|
338
|
-
// write and read choke point. State and media stay pod-native either way.
|
|
339
|
-
const publicBase = config.gateway?.frontActor
|
|
340
|
-
? config.gateway.frontActor.replace(/ap\/actor\/?$/, '') : null;
|
|
341
|
-
this.urls = apUrls(remotePod, root, { publicBase });
|
|
342
|
-
if (this.urls.toPod) this.remote.setUrlMap(this.urls.toPod);
|
|
343
|
-
// Keys: handed in (offline), or the owner-only keys.json read from the pod.
|
|
344
|
-
const keys = keysRecord ? await importSigningKey(keysRecord) : await loadKeysFromPod(this.remote, this.urls);
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
// `passive`: no queue-drain timer until this device is the active one.
|
|
348
|
-
// Whether it IS the active one is not known here — the lease is acquired
|
|
349
|
-
// further down, after everything is constructed — and the timer starts in
|
|
350
|
-
// the Deliverer's constructor, so a viewer used to boot delivering. Start
|
|
351
|
-
// it in goActive() instead, which is the one place that means "act".
|
|
352
|
-
this.deliverer = new RelayDeliverer({
|
|
353
|
-
passive: true,
|
|
354
|
-
store: this.store, rsaPrivate: keys.rsaPrivate, keyId: this.urls.actor + '#main-key',
|
|
355
|
-
actorId: this.urls.actor, log: this.log,
|
|
356
|
-
relayUrl: `${frontOrigin.replace(/\/$/, '')}/api/relay`,
|
|
357
|
-
// The relay finds the account by the front's own key for it, which for a
|
|
358
|
-
// mail-door account is the full address, not the bare handle.
|
|
359
|
-
handle: doorKeyOf(config.gateway?.url) || config.handle, sessionFetch: session.fetch,
|
|
360
|
-
onGone: () => this.publisher.publishCollections({ followers: true }),
|
|
361
|
-
});
|
|
362
|
-
|
|
363
|
-
// The gateway this account's mail comes through, and the account's own
|
|
364
|
-
// standing there — paused, closed, how much arrived unread (front-core:
|
|
365
|
-
// accounts that go quiet). The gateway is told on every sign-in and
|
|
366
|
-
// hourly while this device is active: that is what keeps an account
|
|
367
|
-
// somebody reads from counting as a quiet one. Only a closed address
|
|
368
|
-
// stops the boot; a gateway that cannot be reached is no reason to
|
|
369
|
-
// refuse a sign-in.
|
|
370
|
-
// At the page's own origin, as the relay is: a page on the test alias
|
|
371
|
-
// asked the real site instead and could not get past its preflight.
|
|
372
|
-
this.gatewayApi = config.gateway?.url ? `${frontOrigin.replace(/\/$/, '')}/api` : null;
|
|
373
|
-
this.doorKey = doorKeyOf(config.gateway?.url) || config.handle;
|
|
374
|
-
this.gatewayStanding = null;
|
|
375
|
-
const standing = await this.openAtGateway();
|
|
376
|
-
if (standing?.status === 410) {
|
|
377
|
-
const host = (() => { try { return new URL(this.gatewayApi).host; } catch { return 'the gateway'; } })();
|
|
378
|
-
const address = this.doorKey.includes('@') ? `@${this.doorKey}` : `@${this.doorKey}@${host}`;
|
|
379
|
-
const why = standing.closedBy === 'owner' ? 'you closed it' : 'nothing opened it for six months';
|
|
380
|
-
const e = new Error(`${address} is closed at ${host}: ${why}, and a closed address does not come back.`
|
|
381
|
-
+ ` Everything on your pod is untouched. The sign-in used here was ${webId}.`);
|
|
382
|
-
e.code = 'address-closed';
|
|
383
|
-
throw e;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
this.publisher = new Publisher({
|
|
387
|
-
config: this.store.getConfig(), remote: this.remote, store: this.store,
|
|
388
|
-
deliverer: this.deliverer, publicKeyPem: keys.rsaPublicPem, assertionKey: null, log: this.log,
|
|
389
|
-
// Who a post names, resolved — the same lookup the DeviceAgent
|
|
390
|
-
// gives its publisher. Without it no mention from the browser ever
|
|
391
|
-
// resolved: a direct message went to nobody, a mention notified no one.
|
|
392
|
-
resolveMention: (h) => resolveHandle(this, h),
|
|
393
|
-
resolveActor: (u) => this.intake.fetchAP(u),
|
|
394
|
-
});
|
|
395
|
-
|
|
396
|
-
// The Bluesky connection, stamped to this actor. The same client the Node
|
|
397
|
-
// agent uses; here the credential lives in the owner-only pod state (see
|
|
398
|
-
// atproto-browser.mjs). The publisher cross-posts public notes through it
|
|
399
|
-
// when one is connected and crossPost is on; BskyFeed mirrors Bluesky back.
|
|
400
|
-
this.atproto = new BrowserAtproto({ store: this.store, actorId: this.urls.actor, log: this.log });
|
|
401
|
-
this.publisher.atproto = this.atproto;
|
|
402
|
-
|
|
403
|
-
// Fediverse accounts the owner holds on OTHER servers, mirrored into the
|
|
404
|
-
// feed read here. Their full-access tokens live in this browser (IndexedDB),
|
|
405
|
-
// never on the pod — see fediacct-browser.mjs. Records are loaded, and the
|
|
406
|
-
// poll started, in the background provisioning below.
|
|
407
|
-
this.fediaccts = new BrowserFediAccounts({ store: this.store, actorId: this.urls.actor, log: this.log });
|
|
408
|
-
|
|
409
|
-
// The facade and the intake object exist synchronously, so the client can be
|
|
410
|
-
// served the instant boot returns. The heavy pod I/O — provisioning the
|
|
411
|
-
// owner-only containers, publishing the public face, and starting the inbox
|
|
412
|
-
// drain — is a burst of ~20 writes that a pod behind an edge (Cloudflare on
|
|
413
|
-
// solidcommunity.net) throttles. Reaching the feed must NOT wait on it, or a
|
|
414
|
-
// throttled request hangs the whole sign-in. So it runs in the background,
|
|
415
|
-
// where per-request retry rides out the throttling; `this.provisioning`
|
|
416
|
-
// resolves when it is done (a test, or a later call, can await it).
|
|
417
|
-
// Single-active-agent lease (lib/lease.mjs): the inbox drain is a
|
|
418
|
-
// destructive read and the state store is write-through-cached, so exactly
|
|
419
|
-
// one browser/device may ACT on a pod at a time — a later arrival runs
|
|
420
|
-
// read-only until the owner acts on it and it takes over. Written with fresh
|
|
421
|
-
// fetches, never the cached store. Passed into Intake so the drain checks it.
|
|
422
|
-
this.lease = new Lease({ url: this.urls.state + 'lease.json', fetchImpl: podFetch, log: this.log,
|
|
423
|
-
// Kept on this origin, so THIS browser is one holder however many times
|
|
424
|
-
// its worker is killed and restarted. Without it every restart was a new
|
|
425
|
-
// holder: the old lease still had minutes to run, so the browser found
|
|
426
|
-
// its own account "active on another device" and asked to take it over.
|
|
427
|
-
// Switching between the clients did it every time, being a navigation.
|
|
428
|
-
id: await this.deviceId() });
|
|
429
|
-
// The client-to-server dispatcher, here only for what the Gateway's
|
|
430
|
-
// outbox door takes on the owner's behalf: the browser answers no
|
|
431
|
-
// /ap/outbox of its own.
|
|
432
|
-
this.c2s = new C2S({ agent: this, log: this.log });
|
|
433
|
-
this.intake = new Intake({
|
|
434
|
-
config: this.store.getConfig(), urls: this.urls, remote: this.remote,
|
|
435
|
-
store: this.store, deliverer: this.deliverer, publisher: this.publisher, log: this.log, push: true, lease: this.lease,
|
|
436
|
-
ownerPost: (a, o) => this.c2s.dispatch(a, o),
|
|
437
|
-
});
|
|
438
|
-
// The Mastodon facade the service worker serves.
|
|
439
|
-
//
|
|
440
|
-
// It used to be handed `allowed: null`, which reads as "no policy" — and
|
|
441
|
-
// `redirectAllowed()` waves through EVERY redirect_uri when there is no
|
|
442
|
-
// policy (lib/mastoapi.mjs:315). So any page could have a freshly minted
|
|
443
|
-
// 90-day bearer delivered to an address of its own. One origin, ours, is
|
|
444
|
-
// the whole of the policy here.
|
|
445
|
-
// Three capabilities this build does not have, declared rather than
|
|
446
|
-
// pretended: no streaming socket (a worker is fetch-only), no web push
|
|
447
|
-
// (shims/web-push.mjs is a no-op), and no scheduling (nothing here runs
|
|
448
|
-
// between now and the scheduled time to publish). Each is omitted from the
|
|
449
|
-
// instance document, so the client hides the control instead of offering
|
|
450
|
-
// one that quietly does nothing.
|
|
451
|
-
this.masto = new MastoApi({
|
|
452
|
-
agent: this, log: this.log, scheme: 'https',
|
|
453
|
-
streaming: false, webPush: false, scheduling: false,
|
|
454
|
-
allowed: originAuthorities(self.location.host),
|
|
455
|
-
});
|
|
456
|
-
|
|
457
|
-
// The owner's record/manage surface (the same web/admin page the Node agent
|
|
458
|
-
// serves), answered by this agent over the worker. Personal only — the
|
|
459
|
-
// group and multi-actor endpoints do not exist here. See admin-facade.mjs.
|
|
460
|
-
this.admin = new AdminFacade({ agent: this, log: this.log });
|
|
461
|
-
|
|
462
|
-
// The CSV follow/block/mute importer — the same worker the Node agent runs.
|
|
463
|
-
// It needs only `agent.store` and social.mjs, both of which are in this
|
|
464
|
-
// bundle, so the browser answering 501 was a gap rather than a limitation.
|
|
465
|
-
// Started only by goActive(): it follows people over minutes, which is
|
|
466
|
-
// exactly the kind of writing a viewer must not do.
|
|
467
|
-
this.importer = new ImportWorker({ agent: this, log: this.log });
|
|
468
|
-
|
|
469
|
-
// The topical (hashtag) feed, so a fresh account is not a blank wall. It
|
|
470
|
-
// polls public tag timelines and mirrors NEW notes as view-only statuses —
|
|
471
|
-
// nothing is written to the pod. A browser cannot fetch arbitrary fediverse
|
|
472
|
-
// servers cross-origin, so every fetch it makes (the tag timeline and each
|
|
473
|
-
// note's verification via intake.fetchAP) goes through the same relay the
|
|
474
|
-
// rest of the agent's remote reads use. The user controls it from the
|
|
475
|
-
// client's Followed Hashtags surface (see MastoApi's tag endpoints).
|
|
476
|
-
const relayGet = (u, i = {}) => this.deliverer.signedFetch(u, { ...i, method: 'GET' });
|
|
477
|
-
this.tagfeed = new TagFeed({ store: this.store, intake: this.intake, log: this.log, fetcher: relayGet });
|
|
478
|
-
// Started only when this browser is the ACTIVE agent (goActive), so two
|
|
479
|
-
// devices do not double-mirror the same hashtags into the pod state.
|
|
480
|
-
|
|
481
|
-
this.provisioning = (async () => {
|
|
482
|
-
// Owner-only containers first; idempotent, so a second device provisioning
|
|
483
|
-
// them too is harmless. Each public document sets its own Read ACL inside
|
|
484
|
-
// publishProfile. Mirrors run-agent bootstrap.
|
|
485
|
-
await containers.provisionPrivate(this.remote, this.urls);
|
|
486
|
-
// Connected-account records are reads — safe whether we act or view.
|
|
487
|
-
await this.atproto.load(); // the Bluesky credential, if it is browser-stored
|
|
488
|
-
await this.fediaccts.load(); // connected fediverse accounts, from both backends
|
|
489
|
-
// The lease decides: this device ACTS on the pod, or reads it read-only.
|
|
490
|
-
this.viewer = !(await this.lease.acquire());
|
|
491
|
-
if (this.viewer) {
|
|
492
|
-
this._watched = true;
|
|
493
|
-
this.log(`read-only viewer: another device is active on @${config.handle}`);
|
|
494
|
-
this.startViewerPoll(); // reload the feed, and promote if the lease frees
|
|
495
|
-
return;
|
|
496
|
-
}
|
|
497
|
-
await this.goActive();
|
|
498
|
-
this.log(`browser agent fully provisioned (active): @${config.handle}`);
|
|
499
|
-
})().catch((e) => { this.log(`background provisioning: ${e.message}`); throw e; });
|
|
500
|
-
// Never let an unhandled rejection escape when nobody awaits it.
|
|
501
|
-
this.provisioning.catch(() => {});
|
|
502
|
-
|
|
503
|
-
this.log(`browser agent up: @${config.handle} on ${remotePod}`);
|
|
504
|
-
return this;
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
// What the record page and the bar read (GET /status). The same shape the
|
|
508
|
-
// Node agent returns (run-agent.mjs), minus what a browser cannot have — no
|
|
509
|
-
// process, no local checkout, no atproto yet, so those are null.
|
|
510
|
-
status() {
|
|
511
|
-
const cfg = this.store?.getConfig();
|
|
512
|
-
const contacts = this.store?.getContacts() || { followers: [], following: [] };
|
|
513
|
-
return {
|
|
514
|
-
configured: this.configured(),
|
|
515
|
-
mode: !this.configured() ? 'unconfigured' : this.viewer ? 'viewer' : 'active',
|
|
516
|
-
kind: cfg?.kind || 'person',
|
|
517
|
-
handle: cfg?.handle || null,
|
|
518
|
-
actor: this.urls?.actor || null,
|
|
519
|
-
followers: contacts.followers.length,
|
|
520
|
-
following: contacts.following.length,
|
|
521
|
-
queue: this.store?.getQueue().length || 0,
|
|
522
|
-
deadLetters: this.store?.getDeadLetters().length || 0,
|
|
523
|
-
blockedDomains: this.store?.getBlocklist().domains.length || 0,
|
|
524
|
-
push: this.intake?.wsState || 'n/a',
|
|
525
|
-
inbox: this.intake?.inboxStats || null,
|
|
526
|
-
lastDrain: this.intake?.lastDrain || null,
|
|
527
|
-
tagfeed: this.tagfeed
|
|
528
|
-
? { ...this.tagfeed.config(), lastSweep: this.tagfeed.lastSweep, lastAdded: this.tagfeed.lastAdded }
|
|
529
|
-
: null,
|
|
530
|
-
atproto: this.atproto?.status() || null,
|
|
531
|
-
podRequests: this.remote?.stats?.() || null,
|
|
532
|
-
update: null,
|
|
533
|
-
inboxCooldownFor: 0,
|
|
534
|
-
stateSkipped: this.store?.lastSkipped || [],
|
|
535
|
-
};
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
// Rotate the signing key (POST /rotate-key). Mint a fresh keypair, replace the
|
|
539
|
-
// pod's copy, swap it into the live publisher/deliverer, and republish the
|
|
540
|
-
// actor so the new public key is on the wire. The old key stops signing the
|
|
541
|
-
// moment this returns — same one-way change as the Node agent's rotateKey
|
|
542
|
-
// (run-agent.mjs).
|
|
543
|
-
async rotateKey() {
|
|
544
|
-
const before = this.publisher.publicKeyPem;
|
|
545
|
-
const rec = await generateKeys();
|
|
546
|
-
rec.mintedFor = this.urls.actor; // one key, one actor (lib/keys.mjs)
|
|
547
|
-
await podState.writeKeys(this.remote, this.urls, rec);
|
|
548
|
-
const keys = await cacheOpenedKeys(podActorOf(this.urls), rec);
|
|
549
|
-
this.publisher.publicKeyPem = keys.rsaPublicPem;
|
|
550
|
-
this.deliverer.rsaPrivate = keys.rsaPrivate;
|
|
551
|
-
await this.publisher.publishProfile();
|
|
552
|
-
return { changed: before !== keys.rsaPublicPem, publicKeyPem: keys.rsaPublicPem };
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
// The Bluesky mirror poll — Bluesky notifications and replies into the pod —
|
|
556
|
-
// running only while an account is connected. Reused across calls, not
|
|
557
|
-
// replaced, so a reconnect never leaves an orphaned timer (as in run-agent).
|
|
558
|
-
// A person's mirror only; a group's Bluesky presence is not built here.
|
|
559
|
-
startBsky() {
|
|
560
|
-
if (!this.atproto?.feedActive()) return; // connected AND not paused
|
|
561
|
-
if (this.store.getConfig()?.quiescedAt) return;
|
|
562
|
-
this.bskyfeed ||= new BskyFeed({ store: this.store, atproto: this.atproto, log: this.log });
|
|
563
|
-
this.bskyfeed.start();
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
stopBsky() { this.bskyfeed?.stop(); }
|
|
567
|
-
|
|
568
|
-
// Pausing/resuming or moving the Bluesky connection changes whether the mirror
|
|
569
|
-
// should run — re-evaluate it.
|
|
570
|
-
restartBsky() { this.stopBsky(); this.bskyfeed = null; this.startBsky(); }
|
|
571
|
-
|
|
572
|
-
// The connected-fediverse-accounts mirror — their home timelines and
|
|
573
|
-
// notifications polled into the feed read here — running only while at least
|
|
574
|
-
// one is connected. Reused, not replaced, for the same orphaned-timer reason
|
|
575
|
-
// as the others (run-agent.mjs).
|
|
576
|
-
startAccts() {
|
|
577
|
-
if (!this.fediaccts?.connected()) return;
|
|
578
|
-
if (this.store.getConfig()?.quiescedAt) return;
|
|
579
|
-
this.acctfeed ||= new AcctFeed({ store: this.store, accounts: this.fediaccts, log: this.log });
|
|
580
|
-
this.acctfeed.start();
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
stopAccts() { this.acctfeed?.stop(); }
|
|
584
|
-
|
|
585
|
-
// Connecting or disconnecting one changes what there is to poll.
|
|
586
|
-
restartAccts() { this.stopAccts(); this.acctfeed = null; this.startAccts(); }
|
|
587
|
-
|
|
588
|
-
// The follow graph, written down before it is torn down.
|
|
589
|
-
//
|
|
590
|
-
// Both parking and moving away unfollow everyone, and the record page offers
|
|
591
|
-
// "active" again after either — so both have to leave something to come back
|
|
592
|
-
// from. moveTo used to report `snapshot: following.length` without writing
|
|
593
|
-
// one, so a browser account set back to active re-followed nobody and said so
|
|
594
|
-
// only in a count of zero. Same file, same name, same shape as the Node
|
|
595
|
-
// agent's `_snapshotFollowing` (run-agent.mjs).
|
|
596
|
-
async _snapshotFollowing() {
|
|
597
|
-
const following = this.store.getContacts().following;
|
|
598
|
-
this.store.write('parked.json', {
|
|
599
|
-
parkedAt: new Date().toISOString(),
|
|
600
|
-
following: following.map(f => ({ actor: f.actor, handle: f.handle || null })),
|
|
601
|
-
});
|
|
602
|
-
await this.store.flush();
|
|
603
|
-
return following;
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
// Keep the handle, take no more mail.
|
|
607
|
-
//
|
|
608
|
-
// Unfollowing is what actually stops the volume — every account you follow
|
|
609
|
-
// pushes its posts into your inbox — and closing the inbox handles what no
|
|
610
|
-
// follow graph can gate: stranger mentions, new follow requests, spam.
|
|
611
|
-
//
|
|
612
|
-
// This matters more here than it does on Node. A Node agent runs as a service
|
|
613
|
-
// and keeps draining; close the tab on a browser agent and nothing drains,
|
|
614
|
-
// while the gateway goes on delivering into the pod inbox regardless (it
|
|
615
|
-
// writes straight to the pod — see lib/gateway-core.mjs). An unattended
|
|
616
|
-
// browser account is a pod quietly filling up with nobody collecting, and
|
|
617
|
-
// this is the only control that stops it at the source.
|
|
618
|
-
async quiesce() {
|
|
619
|
-
const following = this.store.getContacts().following.map(f => f.actor).filter(Boolean);
|
|
620
|
-
let unfollowed = 0;
|
|
621
|
-
for (const actor of following) {
|
|
622
|
-
try { await unfollowActor(this, actor); unfollowed++; }
|
|
623
|
-
catch (e) { this.log(`unfollow ${actor} failed: ${e.message}`); }
|
|
624
|
-
}
|
|
625
|
-
const quiescedAt = await this.publisher.closeInbox();
|
|
626
|
-
this.log(`quiesced: unfollowed ${unfollowed}/${following.length}, inbox closed`);
|
|
627
|
-
return { unfollowed, following: following.length, quiescedAt };
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
// Park (POST /park). The snapshot is taken FIRST: unfollowing is what stops
|
|
631
|
-
// the traffic, but it also destroys the only record of who was being
|
|
632
|
-
// followed, and "until I want this back" needs that record.
|
|
633
|
-
async park() {
|
|
634
|
-
const following = await this._snapshotFollowing();
|
|
635
|
-
const r = await this.quiesce();
|
|
636
|
-
this.log(`parked: ${r.unfollowed} unfollow(s) recorded for revival, inbox closed`);
|
|
637
|
-
return { ...r, snapshot: following.length };
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
// Undo a park (POST /revive). Re-open the inbox, then re-follow everyone in
|
|
641
|
-
// the snapshot. Each Follow needs the far end to Accept, so this is a request
|
|
642
|
-
// rather than a restoration — some will not come back, which is the nature of
|
|
643
|
-
// the thing, and why the result counts both numbers.
|
|
644
|
-
async revive() {
|
|
645
|
-
const parked = this.store.read('parked.json', null);
|
|
646
|
-
await this.publisher.openInbox();
|
|
647
|
-
let refollowed = 0;
|
|
648
|
-
for (const f of parked?.following || []) {
|
|
649
|
-
try { await followActor(this, f.actor); refollowed++; }
|
|
650
|
-
catch (e) { this.log(`re-follow ${f.actor} failed: ${e.message}`); }
|
|
651
|
-
}
|
|
652
|
-
if (parked) this.store.remove('parked.json').catch(() => {});
|
|
653
|
-
await this.store.flush();
|
|
654
|
-
this.log(`revived: inbox open, ${refollowed}/${parked?.following?.length || 0} follow(s) re-sent`);
|
|
655
|
-
return { refollowed, of: parked?.following?.length || 0, parkedAt: parked?.parkedAt || null };
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
// Hand the identity to another account (POST /move). The federated act is the
|
|
659
|
-
// Move to every follower's server; then this account quiesces — unfollows
|
|
660
|
-
// everyone and closes its inbox — since it is being left behind. Same as the
|
|
661
|
-
// Node agent's moveTo + quiesce (run-agent.mjs). `target` is an actor URL,
|
|
662
|
-
// already resolved by the caller.
|
|
663
|
-
async moveTo(target) {
|
|
664
|
-
const moved = await this.publisher.publishMove(target);
|
|
665
|
-
const following = await this._snapshotFollowing();
|
|
666
|
-
const r = await this.quiesce();
|
|
667
|
-
this.log(`moved to ${target}: unfollowed ${r.unfollowed}/${r.following}, inbox closed`);
|
|
668
|
-
return { ...moved, ...r, snapshot: following.length };
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
export { accountNotRead };
|