UNPKG

@fedify/fedify

Version:

An ActivityPub server framework

1,259 lines (1,254 loc) • 69.2 kB
import { Temporal } from "@js-temporal/polyfill"; import { URLPattern } from "urlpattern-polyfill"; globalThis.addEventListener = () => {}; import { assertEquals } from "../assert_equals-CTYbeopb.js"; import { assert } from "../assert-DmFG7ppO.js"; import { assertInstanceOf } from "../assert_instance_of-CF09JHYM.js"; import { MemoryKvStore } from "../kv-BMY6Qf_A.js"; import { ContextImpl, FederationImpl, InboxContextImpl, KvSpecDeterminer, createFederation } from "../middleware-DlU2_Djj.js"; import { FetchError, fetchDocumentLoader } from "../docloader-C8mmLN-Y.js"; import "../url-kTAI6_KP.js"; import "../semver-DWClQt_5.js"; import "../client-rv6n0nKR.js"; import { RouterError } from "../router-D_aVZZUc.js"; import "../types-C7C_l-jz.js"; import "../multibase-DeCHcK8L.js"; import { Activity, Announce, Create, Invite, Multikey, Note, Object as Object$1, Offer, Person } from "../vocab-dDpPQ0fF.js"; import "../langstr-DbWheeIS.js"; import "../lookup-Ozuw-rxN.js"; import { getTypeId } from "../type-D2s5lmbZ.js"; import "../actor-BP-NTJ2m.js"; import "../key-CPSlDkCj.js"; import { signRequest, verifyRequest } from "../http-BmfI6U39.js"; import { getAuthenticatedDocumentLoader } from "../authdocloader-Banw__l6.js"; import { detachSignature, signJsonLd, verifyJsonLd } from "../ld-D9kk1Uy2.js"; import { doesActorOwnKey } from "../owner-DF6Fg4-s.js"; import { signObject, verifyObject } from "../proof-13QPysOU.js"; import { lookupObject } from "../lookup-DNXR408t.js"; import "../inbox-AwdJVoMZ.js"; import "../builder-EdzpUb9N.js"; import "../collection-Dfb0TPno.js"; import "../keycache-BPwY_Up0.js"; import "../retry-BiIhZWgD.js"; import "../send-CaL5Xv1F.js"; import { test } from "../testing-BZ0dJ4qn.js"; import { assertStrictEquals } from "../std__assert-vp0TKMS1.js"; import { assertFalse, assertRejects } from "../assert_rejects-C-sxEMM5.js"; import "../assert_is_error-nrwA1GeT.js"; import { assertNotEquals } from "../assert_not_equals-Dc7y-V5Q.js"; import { assertThrows } from "../assert_throws-Cn9C6Jur.js"; import { mockDocumentLoader } from "../docloader-09nVWLAZ.js"; import { ed25519Multikey, ed25519PrivateKey, ed25519PublicKey, rsaPrivateKey2, rsaPrivateKey3, rsaPublicKey2, rsaPublicKey3 } from "../keys-CtFc865v.js"; import { esm_default } from "../esm-Db4De7AS.js"; //#region testing/fixtures/example.com/person.json var __context$1 = ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1"]; var id$1 = "https://example.com/person"; var type$1 = "Person"; var name$1 = "John Doe"; var publicKey$1 = [{ "id": "https://example.com/key", "owner": "https://example.com/person", "type": "CryptographicKey", "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyIB9rotX8G6r6/6toT+x\n24BUiQ/HaPH1Em9dOt4c94s+OPFoEdH7DY7Iym9A8LlH4JaGF8KD38bLHWe1S4x0\njV3gHJKhK7veJfGZCKUENcQecBZ+YWUs5HWvUIX1vVB//0luHrg6BQKGOrSOE+WI\nAxyr0qsWCFfZzQrvSnUD2yvg1arJX2xhms14uxoRd5Kg9efKSCmmQaNEapicARUm\nFWrIEpGFa/nUUnqimssAGw1eZFqf3wA4TjhsuARBhGaJtv/3KEa016eMZxy3kDlO\njZnXZTaTgWkXdodwUvy8563fes3Al6BlcS2iJ9qbtha8rSm0FHqoUKH73JsLPKQI\nwQIDAQAB\n-----END PUBLIC KEY-----" }, { "id": "https://example.com/key2", "type": "CryptographicKey", "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoRmBtnxbdFutoRd1GLGw\nwGTrsqlRRWUe11hHQaoRLGf5LwQ0tIc6I9q+dynliw+2kxYsLn9SH2je6HcTYOol\ngW7F/cOWXZQN04b+OiYcU1ConAhLjmn4k1uKawJ614y0ScPNd8PQ+CljsnlPxbq9\nofaCMe2BV3B6y09aCuGFJ0nxn1/ubjmIBIWWFTAznoz1J9BhJDGyt3IO3ABy3f9z\nDVlR32L/n5VIkXnxkjUKdzMAOzYb62kuKOp1iznRTPrV71SNtivJMwSh/LVgBrmZ\njtIn/oim+KyX/fdLU3tQ7VClyqmJzyAjccOH6Qj6nFTPh+vX07gqN8IlLT2uye4w\nawIDAQAB\n-----END PUBLIC KEY-----" }]; var person_default = { "@context": __context$1, id: id$1, type: type$1, name: name$1, publicKey: publicKey$1 }; //#endregion //#region testing/fixtures/example.com/person2.json var __context = [ "https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1", "https://w3id.org/security/multikey/v1", "https://w3id.org/security/data-integrity/v1", "https://www.w3.org/ns/did/v1" ]; var id = "https://example.com/person2"; var type = "Person"; var name = "Jane Doe"; var publicKey = [{ "id": "https://example.com/person2#key3", "type": "CryptographicKey", "owner": "https://example.com/person2", "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA4GUqWgdiYlN3Su5Gr4l6\ni+xRS8gDDVKZ718vpGk6eIpvqs33q430nRbHIzbHRXRaAhc/1++rUBcK0V4/kjZl\nCSzVtRgGU6HMkmjcD+uE56a8XbTczfltbEDj7afoEuB2F3UhQEWrSz+QJ29DPXaL\nMIa1Yv61NR2vxGqNbdtoMjDORMBYtg77CYbcFkiJHw65PDa7+f/yjLxuCRPye5L7\nhncN0UZuuFoRJmHNRLSg5omBad9WTvQXmSyXEhEdk9fHwlI022AqAzlWbT79hldc\nDSKGGLLbQIs1c3JZIG8G5i6Uh5Vy0Z7tSNBcxbhqoI9i9je4f/x/OPIVc19f04BE\n1LgWuHsftZzRgW9Sdqz53W83XxVdxlyHeywXOnstSWT11f8dkLyQUcHKTH+E6urb\nH+aiPLiRpYK8W7D9KTQA9kZ5JXaEuveBd5vJX7wakhbzAn8pWJU7GYIHNY38Ycok\nmivkU5pY8S2cKFMwY0b7ade3MComlir5P3ZYSjF+n6gRVsT96P+9mNfCu9gXt/f8\nXCyjKlH89kGwuJ7HhR8CuVdm0l+jYozVt6GsDy0hHYyn79NCCAEzP7ZbhBMR0T5V\nrkl+TIGXoJH9WFiz4VxO+NnglF6dNQjDS5IzYLoFRXIK1f3cmQiEB4FZmL70l9HL\nrgwR+Xys83xia79OqFDRezMCAwEAAQ==\n-----END PUBLIC KEY-----\n" }, { "id": "https://example.com/person2#key4", "type": "CryptographicKey", "owner": "https://example.com/person2", "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEALR8epAGDe+cVq5p2Tx49CCfphpk1rNhkNoY9i+XEUfg=\n-----END PUBLIC KEY-----\n" }]; var assertionMethod = [{ "id": "https://example.com/peson2#key3", "type": "Multikey", "controller": "https://example.com/person2", "publicKeyMultibase": "zgghBUVkqmWS8e1j4aN2yowLAEkJC6wowB9wWmLRACYCok7UzstWcTBp3waKiDUM7wqL9bbBD9W9FvNaXEK2KPCZ9ffhvd5dxChJL9bdPQSrMwa28FEYMGDtcF1uocrYNmZm2dBBMaWrCu8U3s4PpVVhn4hsWDL8GLuE466pkJs9Hy8xmECoaaVgAZLiYDw2gwrjHDiX2i7aDHKfE7aSZWUWmC8nAGNZ7DX5pXoyXK3pxuaCWxNxXwPmaFwgKDyy9uhtBJ8znp9NZXkXHBTQe5uAi8GFwHY5asvqCmYPrAGWxcT6pdbZaJHdWkM7nw6apBHfakKs42oMqdBoJ2WkkresoT1qHrX2GW7gNP9PLtveF4vfEd6cwgHKQCdYgayG3muGfZiPvML75cyfkNrjkctvuQUfMxY9umbd2TG3V3mPnLrvQnqHpuRMZYtCn3nX1qfZaqFhTwT4NFPqVNLqvgR6k9vcuGXn6Ndaumhd5xtTK64jk3e2gPBit9iq6MrFUSoxNsbTty4kqcHAodtkK8CMSxUxbFP1kK3nyy8ZfeMgDCts1KboBcT2m5FMpQpYxKtNBfvhTuyeDDC34uhbY8itmTAnDwSr5mKrniwwDUGPZFejda51TYs1N9D9Ejzaw5Mvr8qN6wahHmsDBWTbWwV6YKVMD1MjAhJBUopWJWB5x6mEBAX25MssKfAEhJyDtqYWjq63uQHUJCsPJp" }, { "id": "https://example.com/person2#key4", "type": "Multikey", "controller": "https://example.com/person2", "publicKeyMultibase": "z6MkhVPuyvgG1RkMv67azDqDCDERPXVrUg1i3qchXY5EACE3" }]; var person2_default = { "@context": __context, id, type, name, publicKey, assertionMethod }; //#endregion //#region federation/middleware.test.ts test("createFederation()", async (t) => { const kv = new MemoryKvStore(); await t.step("allowPrivateAddress", () => { assertThrows(() => createFederation({ kv, documentLoader: mockDocumentLoader, allowPrivateAddress: true }), TypeError); assertThrows(() => createFederation({ kv, contextLoader: mockDocumentLoader, allowPrivateAddress: true }), TypeError); assertThrows(() => createFederation({ kv, authenticatedDocumentLoaderFactory: () => mockDocumentLoader, allowPrivateAddress: true }), TypeError); }); await t.step("origin", () => { const f = createFederation({ kv, origin: "http://example.com:8080" }); assertInstanceOf(f, FederationImpl); assertEquals(f.origin, { handleHost: "example.com:8080", webOrigin: "http://example.com:8080" }); assertThrows(() => createFederation({ kv, origin: "example.com" }), TypeError); assertThrows(() => createFederation({ kv, origin: "ftp://example.com" }), TypeError); assertThrows(() => createFederation({ kv, origin: "https://example.com/foo" }), TypeError); assertThrows(() => createFederation({ kv, origin: "https://example.com/?foo" }), TypeError); assertThrows(() => createFederation({ kv, origin: "https://example.com/#foo" }), TypeError); const f2 = createFederation({ kv, origin: { handleHost: "example.com:8080", webOrigin: "https://ap.example.com" } }); assertInstanceOf(f2, FederationImpl); assertEquals(f2.origin, { handleHost: "example.com:8080", webOrigin: "https://ap.example.com" }); assertThrows(() => createFederation({ kv, origin: { handleHost: "https://example.com", webOrigin: "https://example.com" } }), TypeError); assertThrows(() => createFederation({ kv, origin: { handleHost: "example.com/", webOrigin: "https://example.com" } }), TypeError); assertThrows(() => createFederation({ kv, origin: { handleHost: "example.com", webOrigin: "example.com" } }), TypeError); assertThrows(() => createFederation({ kv, origin: { handleHost: "example.com", webOrigin: "ftp://example.com" } }), TypeError); assertThrows(() => createFederation({ kv, origin: { handleHost: "example.com", webOrigin: "https://example.com/foo" } }), TypeError); assertThrows(() => createFederation({ kv, origin: { handleHost: "example.com", webOrigin: "https://example.com/?foo" } }), TypeError); assertThrows(() => createFederation({ kv, origin: { handleHost: "example.com", webOrigin: "https://example.com/#foo" } }), TypeError); }); }); test("Federation.createContext()", async (t) => { const kv = new MemoryKvStore(); const documentLoader = (url) => { throw new FetchError(new URL(url), "Not found"); }; esm_default.spyGlobal(); esm_default.get("https://example.com/object", async (cl) => { const v = await verifyRequest(cl.request, { contextLoader: mockDocumentLoader, documentLoader: mockDocumentLoader, currentTime: Temporal.Now.instant() }); return new Response(JSON.stringify(v != null), { headers: { "Content-Type": "application/json" } }); }); await t.step("Context", async () => { const federation = createFederation({ kv, documentLoader, contextLoader: mockDocumentLoader }); let ctx = federation.createContext(new URL("https://example.com:1234/"), 123); assertEquals(ctx.data, 123); assertEquals(ctx.origin, "https://example.com:1234"); assertEquals(ctx.canonicalOrigin, "https://example.com:1234"); assertEquals(ctx.host, "example.com:1234"); assertEquals(ctx.hostname, "example.com"); assertStrictEquals(ctx.documentLoader, documentLoader); assertStrictEquals(ctx.contextLoader, mockDocumentLoader); assertStrictEquals(ctx.federation, federation); assertThrows(() => ctx.getNodeInfoUri(), RouterError); assertThrows(() => ctx.getActorUri("handle"), RouterError); assertThrows(() => ctx.getObjectUri(Note, { handle: "handle", id: "id" }), RouterError); assertThrows(() => ctx.getInboxUri(), RouterError); assertThrows(() => ctx.getInboxUri("handle"), RouterError); assertThrows(() => ctx.getOutboxUri("handle"), RouterError); assertThrows(() => ctx.getFollowingUri("handle"), RouterError); assertThrows(() => ctx.getFollowersUri("handle"), RouterError); assertThrows(() => ctx.getLikedUri("handle"), RouterError); assertThrows(() => ctx.getFeaturedUri("handle"), RouterError); assertThrows(() => ctx.getFeaturedTagsUri("handle"), RouterError); assertEquals(ctx.parseUri(new URL("https://example.com/")), null); assertEquals(ctx.parseUri(null), null); assertEquals(await ctx.getActorKeyPairs("handle"), []); await assertRejects(() => ctx.getDocumentLoader({ identifier: "handle" }), Error, "No actor key pairs dispatcher registered"); await assertRejects(() => ctx.sendActivity({ identifier: "handle" }, [], new Create({})), Error, "No actor key pairs dispatcher registered"); federation.setNodeInfoDispatcher("/nodeinfo/2.1", () => ({ software: { name: "Example", version: { major: 1, minor: 2, patch: 3 } }, protocols: ["activitypub"], usage: { users: {}, localPosts: 123, localComments: 456 } })); ctx = federation.createContext(new URL("https://example.com/"), 123); assertEquals(ctx.getNodeInfoUri(), new URL("https://example.com/nodeinfo/2.1")); federation.setActorDispatcher("/users/{identifier}", () => new Person({})).setKeyPairsDispatcher(() => [{ privateKey: rsaPrivateKey2, publicKey: rsaPublicKey2.publicKey }, { privateKey: ed25519PrivateKey, publicKey: ed25519PublicKey.publicKey }]).mapHandle((_, username) => username === "HANDLE" ? "handle" : null); ctx = federation.createContext(new URL("https://example.com/"), 123); assertEquals(ctx.getActorUri("handle"), new URL("https://example.com/users/handle")); assertEquals(ctx.parseUri(new URL("https://example.com/")), null); assertEquals(ctx.parseUri(new URL("https://example.com/users/handle")), { type: "actor", identifier: "handle", handle: "handle" }); assertEquals(ctx.parseUri(null), null); assertEquals(await ctx.getActorKeyPairs("handle"), [{ keyId: new URL("https://example.com/users/handle#main-key"), privateKey: rsaPrivateKey2, publicKey: rsaPublicKey2.publicKey, cryptographicKey: rsaPublicKey2.clone({ id: new URL("https://example.com/users/handle#main-key"), owner: new URL("https://example.com/users/handle") }), multikey: new Multikey({ id: new URL("https://example.com/users/handle#main-key"), controller: new URL("https://example.com/users/handle"), publicKey: rsaPublicKey2.publicKey }) }, { keyId: new URL("https://example.com/users/handle#key-2"), privateKey: ed25519PrivateKey, publicKey: ed25519PublicKey.publicKey, cryptographicKey: ed25519PublicKey.clone({ id: new URL("https://example.com/users/handle#key-2"), owner: new URL("https://example.com/users/handle") }), multikey: new Multikey({ id: new URL("https://example.com/users/handle#key-2"), controller: new URL("https://example.com/users/handle"), publicKey: ed25519PublicKey.publicKey }) }]); const loader = await ctx.getDocumentLoader({ identifier: "handle" }); assertEquals(await loader("https://example.com/object"), { contextUrl: null, documentUrl: "https://example.com/object", document: true }); const loader2 = await ctx.getDocumentLoader({ username: "HANDLE" }); assertEquals(await loader2("https://example.com/object"), { contextUrl: null, documentUrl: "https://example.com/object", document: true }); const loader3 = ctx.getDocumentLoader({ keyId: new URL("https://example.com/key2"), privateKey: rsaPrivateKey2 }); assertEquals(await loader3("https://example.com/object"), { contextUrl: null, documentUrl: "https://example.com/object", document: true }); assertEquals(await ctx.lookupObject("https://example.com/object"), null); await assertRejects(() => ctx.sendActivity({ identifier: "handle" }, [], new Create({})), TypeError, "The activity to send must have at least one actor property."); await ctx.sendActivity({ identifier: "handle" }, [], new Create({ actor: new URL("https://example.com/users/handle") })); const federation2 = createFederation({ kv, documentLoader: mockDocumentLoader, contextLoader: mockDocumentLoader }); const ctx2 = federation2.createContext(new URL("https://example.com/"), 123); assertEquals(await ctx2.lookupObject("https://example.com/object"), new Object$1({ id: new URL("https://example.com/object"), name: "Fetched object" })); federation.setObjectDispatcher(Note, "/users/{identifier}/notes/{id}", (_ctx, values) => { return new Note({ summary: `Note ${values.id} by ${values.identifier}` }); }); ctx = federation.createContext(new URL("https://example.com/"), 123); assertEquals(ctx.getObjectUri(Note, { identifier: "john", id: "123" }), new URL("https://example.com/users/john/notes/123")); assertEquals(ctx.parseUri(new URL("https://example.com/users/john/notes/123")), { type: "object", class: Note, typeId: new URL("https://www.w3.org/ns/activitystreams#Note"), values: { identifier: "john", id: "123" } }); assertEquals(ctx.parseUri(null), null); federation.setInboxListeners("/users/{identifier}/inbox", "/inbox"); ctx = federation.createContext(new URL("https://example.com/"), 123); assertEquals(ctx.getInboxUri(), new URL("https://example.com/inbox")); assertEquals(ctx.getInboxUri("handle"), new URL("https://example.com/users/handle/inbox")); assertEquals(ctx.parseUri(new URL("https://example.com/inbox")), { type: "inbox", identifier: void 0, handle: void 0 }); assertEquals(ctx.parseUri(new URL("https://example.com/users/handle/inbox")), { type: "inbox", identifier: "handle", handle: "handle" }); assertEquals(ctx.parseUri(null), null); federation.setOutboxDispatcher("/users/{identifier}/outbox", () => ({ items: [] })); ctx = federation.createContext(new URL("https://example.com/"), 123); assertEquals(ctx.getOutboxUri("handle"), new URL("https://example.com/users/handle/outbox")); assertEquals(ctx.parseUri(new URL("https://example.com/users/handle/outbox")), { type: "outbox", identifier: "handle", handle: "handle" }); assertEquals(ctx.parseUri(null), null); federation.setFollowingDispatcher("/users/{identifier}/following", () => ({ items: [] })); ctx = federation.createContext(new URL("https://example.com/"), 123); assertEquals(ctx.getFollowingUri("handle"), new URL("https://example.com/users/handle/following")); assertEquals(ctx.parseUri(new URL("https://example.com/users/handle/following")), { type: "following", identifier: "handle", handle: "handle" }); assertEquals(ctx.parseUri(null), null); federation.setFollowersDispatcher("/users/{identifier}/followers", () => ({ items: [] })); ctx = federation.createContext(new URL("https://example.com/"), 123); assertEquals(ctx.getFollowersUri("handle"), new URL("https://example.com/users/handle/followers")); assertEquals(ctx.parseUri(new URL("https://example.com/users/handle/followers")), { type: "followers", identifier: "handle", handle: "handle" }); assertEquals(ctx.parseUri(null), null); federation.setLikedDispatcher("/users/{identifier}/liked", () => ({ items: [] })); ctx = federation.createContext(new URL("https://example.com/"), 123); assertEquals(ctx.getLikedUri("handle"), new URL("https://example.com/users/handle/liked")); assertEquals(ctx.parseUri(new URL("https://example.com/users/handle/liked")), { type: "liked", identifier: "handle", handle: "handle" }); assertEquals(ctx.parseUri(null), null); federation.setFeaturedDispatcher("/users/{identifier}/featured", () => ({ items: [] })); ctx = federation.createContext(new URL("https://example.com/"), 123); assertEquals(ctx.getFeaturedUri("handle"), new URL("https://example.com/users/handle/featured")); assertEquals(ctx.parseUri(new URL("https://example.com/users/handle/featured")), { type: "featured", identifier: "handle", handle: "handle" }); assertEquals(ctx.parseUri(null), null); federation.setFeaturedTagsDispatcher("/users/{identifier}/tags", () => ({ items: [] })); ctx = federation.createContext(new URL("https://example.com/"), 123); assertEquals(ctx.getFeaturedTagsUri("handle"), new URL("https://example.com/users/handle/tags")); assertEquals(ctx.parseUri(new URL("https://example.com/users/handle/tags")), { type: "featuredTags", identifier: "handle", handle: "handle" }); assertEquals(ctx.parseUri(null), null); }); await t.step("Context with origin", () => { const federation = createFederation({ kv, origin: "https://ap.example.com", documentLoader, contextLoader: mockDocumentLoader }); const ctx = federation.createContext(new URL("https://example.com:1234/")); assertEquals(ctx.origin, "https://example.com:1234"); assertEquals(ctx.canonicalOrigin, "https://ap.example.com"); assertEquals(ctx.host, "example.com:1234"); assertEquals(ctx.hostname, "example.com"); federation.setNodeInfoDispatcher("/nodeinfo/2.1", () => ({ software: { name: "Example", version: { major: 1, minor: 2, patch: 3 } }, protocols: ["activitypub"], usage: { users: {}, localPosts: 123, localComments: 456 } })); assertEquals(ctx.getNodeInfoUri(), new URL("https://ap.example.com/nodeinfo/2.1")); federation.setActorDispatcher("/users/{identifier}", () => new Person({})); assertEquals(ctx.getActorUri("handle"), new URL("https://ap.example.com/users/handle")); assertEquals(ctx.parseUri(new URL("https://ap.example.com/users/handle")), { type: "actor", handle: "handle", identifier: "handle" }); assertEquals(ctx.parseUri(new URL("https://example.com:1234/users/handle")), { type: "actor", handle: "handle", identifier: "handle" }); federation.setObjectDispatcher(Note, "/users/{identifier}/notes/{id}", (_ctx, values) => { return new Note({ summary: `Note ${values.id} by ${values.identifier}` }); }); assertEquals(ctx.getObjectUri(Note, { identifier: "john", id: "123" }), new URL("https://ap.example.com/users/john/notes/123")); assertEquals(ctx.parseUri(new URL("https://ap.example.com/users/john/notes/123")), { type: "object", class: Note, typeId: new URL("https://www.w3.org/ns/activitystreams#Note"), values: { identifier: "john", id: "123" } }); assertEquals(ctx.parseUri(new URL("https://example.com:1234/users/john/notes/123")), { type: "object", class: Note, typeId: new URL("https://www.w3.org/ns/activitystreams#Note"), values: { identifier: "john", id: "123" } }); federation.setInboxListeners("/users/{identifier}/inbox", "/inbox"); assertEquals(ctx.getInboxUri(), new URL("https://ap.example.com/inbox")); assertEquals(ctx.getInboxUri("handle"), new URL("https://ap.example.com/users/handle/inbox")); assertEquals(ctx.parseUri(new URL("https://ap.example.com/inbox")), { type: "inbox", handle: void 0, identifier: void 0 }); assertEquals(ctx.parseUri(new URL("https://example.com:1234/inbox")), { type: "inbox", handle: void 0, identifier: void 0 }); assertEquals(ctx.parseUri(new URL("https://ap.example.com/users/handle/inbox")), { type: "inbox", handle: "handle", identifier: "handle" }); assertEquals(ctx.parseUri(new URL("https://example.com:1234/users/handle/inbox")), { type: "inbox", handle: "handle", identifier: "handle" }); federation.setOutboxDispatcher("/users/{identifier}/outbox", () => ({ items: [] })); assertEquals(ctx.getOutboxUri("handle"), new URL("https://ap.example.com/users/handle/outbox")); assertEquals(ctx.parseUri(new URL("https://ap.example.com/users/handle/outbox")), { type: "outbox", handle: "handle", identifier: "handle" }); assertEquals(ctx.parseUri(new URL("https://example.com:1234/users/handle/outbox")), { type: "outbox", handle: "handle", identifier: "handle" }); federation.setFollowingDispatcher("/users/{identifier}/following", () => ({ items: [] })); assertEquals(ctx.getFollowingUri("handle"), new URL("https://ap.example.com/users/handle/following")); assertEquals(ctx.parseUri(new URL("https://ap.example.com/users/handle/following")), { type: "following", handle: "handle", identifier: "handle" }); assertEquals(ctx.parseUri(new URL("https://example.com:1234/users/handle/following")), { type: "following", handle: "handle", identifier: "handle" }); federation.setFollowersDispatcher("/users/{identifier}/followers", () => ({ items: [] })); assertEquals(ctx.getFollowersUri("handle"), new URL("https://ap.example.com/users/handle/followers")); assertEquals(ctx.parseUri(new URL("https://ap.example.com/users/handle/followers")), { type: "followers", handle: "handle", identifier: "handle" }); assertEquals(ctx.parseUri(new URL("https://example.com:1234/users/handle/followers")), { type: "followers", handle: "handle", identifier: "handle" }); federation.setLikedDispatcher("/users/{identifier}/liked", () => ({ items: [] })); assertEquals(ctx.getLikedUri("handle"), new URL("https://ap.example.com/users/handle/liked")); assertEquals(ctx.parseUri(new URL("https://ap.example.com/users/handle/liked")), { type: "liked", handle: "handle", identifier: "handle" }); assertEquals(ctx.parseUri(new URL("https://example.com:1234/users/handle/liked")), { type: "liked", handle: "handle", identifier: "handle" }); federation.setFeaturedDispatcher("/users/{identifier}/featured", () => ({ items: [] })); assertEquals(ctx.getFeaturedUri("handle"), new URL("https://ap.example.com/users/handle/featured")); assertEquals(ctx.parseUri(new URL("https://ap.example.com/users/handle/featured")), { type: "featured", handle: "handle", identifier: "handle" }); assertEquals(ctx.parseUri(new URL("https://example.com:1234/users/handle/featured")), { type: "featured", handle: "handle", identifier: "handle" }); federation.setFeaturedTagsDispatcher("/users/{identifier}/tags", () => ({ items: [] })); assertEquals(ctx.getFeaturedTagsUri("handle"), new URL("https://ap.example.com/users/handle/tags")); assertEquals(ctx.parseUri(new URL("https://ap.example.com/users/handle/tags")), { type: "featuredTags", handle: "handle", identifier: "handle" }); assertEquals(ctx.parseUri(new URL("https://example.com:1234/users/handle/tags")), { type: "featuredTags", handle: "handle", identifier: "handle" }); }); await t.step("Context.clone()", () => { const federation = createFederation({ kv }); const ctx = federation.createContext(new URL("https://example.com/"), 123); const clone = ctx.clone(456); assertStrictEquals(clone.canonicalOrigin, ctx.canonicalOrigin); assertStrictEquals(clone.origin, ctx.origin); assertEquals(clone.data, 456); assertEquals(clone.host, ctx.host); assertEquals(clone.hostname, ctx.hostname); assertStrictEquals(clone.documentLoader, ctx.documentLoader); assertStrictEquals(clone.contextLoader, ctx.contextLoader); assertStrictEquals(clone.federation, ctx.federation); }); esm_default.get("https://example.com/.well-known/nodeinfo", (cl) => { const headers = cl.options.headers ?? {}; assertEquals(new Headers(headers).get("User-Agent"), "CustomUserAgent/1.2.3"); return new Response(JSON.stringify({ links: [{ rel: "http://nodeinfo.diaspora.software/ns/schema/2.1", href: "https://example.com/nodeinfo/2.1" }] })); }); esm_default.get("https://example.com/nodeinfo/2.1", (cl) => { const headers = cl.options.headers ?? {}; assertEquals(new Headers(headers).get("User-Agent"), "CustomUserAgent/1.2.3"); return new Response(JSON.stringify({ software: { name: "foo", version: "1.2.3" }, protocols: ["activitypub", "diaspora"], usage: { users: {}, localPosts: 123, localComments: 456 } })); }); await t.step("Context.lookupNodeInfo()", async () => { const federation = createFederation({ kv, userAgent: "CustomUserAgent/1.2.3" }); const ctx = federation.createContext(new URL("https://example.com/"), 123); const nodeInfo = await ctx.lookupNodeInfo("https://example.com/"); assertEquals(nodeInfo, { software: { name: "foo", version: { major: 1, minor: 2, patch: 3, build: [], prerelease: [] } }, protocols: ["activitypub", "diaspora"], usage: { users: {}, localPosts: 123, localComments: 456 } }); const rawNodeInfo = await ctx.lookupNodeInfo("https://example.com/", { parse: "none" }); assertEquals(rawNodeInfo, { software: { name: "foo", version: "1.2.3" }, protocols: ["activitypub", "diaspora"], usage: { users: {}, localPosts: 123, localComments: 456 } }); }); await t.step("RequestContext", async () => { const federation = createFederation({ kv, documentLoader: mockDocumentLoader }); const req = new Request("https://example.com/"); const ctx = federation.createContext(req, 123); assertEquals(ctx.request, req); assertEquals(ctx.url, new URL("https://example.com/")); assertEquals(ctx.origin, "https://example.com"); assertEquals(ctx.host, "example.com"); assertEquals(ctx.hostname, "example.com"); assertEquals(ctx.data, 123); await assertRejects(() => ctx.getActor("someone"), Error); await assertRejects(() => ctx.getObject(Note, { handle: "someone", id: "123" }), Error); assertEquals(await ctx.getSignedKey(), null); assertEquals(await ctx.getSignedKeyOwner(), null); assertEquals(await ctx.getSignedKey(), null); assertEquals(await ctx.getSignedKeyOwner(), null); await assertRejects(() => ctx.getActor("someone"), Error, "No actor dispatcher registered"); const signedReq = await signRequest(new Request("https://example.com/"), rsaPrivateKey2, rsaPublicKey2.id); const signedCtx = federation.createContext(signedReq, 456); assertEquals(signedCtx.request, signedReq); assertEquals(signedCtx.url, new URL("https://example.com/")); assertEquals(signedCtx.data, 456); assertEquals(await signedCtx.getSignedKey(), rsaPublicKey2); assertEquals(await signedCtx.getSignedKeyOwner(), null); assertEquals(await signedCtx.getSignedKey(), rsaPublicKey2); assertEquals(await signedCtx.getSignedKeyOwner(), null); const signedReq2 = await signRequest(new Request("https://example.com/"), rsaPrivateKey3, rsaPublicKey3.id); const signedCtx2 = federation.createContext(signedReq2, 456); assertEquals(signedCtx2.request, signedReq2); assertEquals(signedCtx2.url, new URL("https://example.com/")); assertEquals(signedCtx2.data, 456); assertEquals(await signedCtx2.getSignedKey(), rsaPublicKey3); const expectedOwner = await lookupObject("https://example.com/person2", { documentLoader: mockDocumentLoader, contextLoader: mockDocumentLoader }); assertEquals(await signedCtx2.getSignedKeyOwner(), expectedOwner); assertEquals(await signedCtx2.getSignedKey(), rsaPublicKey3); assertEquals(await signedCtx2.getSignedKeyOwner(), expectedOwner); federation.setActorDispatcher("/users/{identifier}", (_ctx, identifier) => new Person({ preferredUsername: identifier })); const ctx2 = federation.createContext(req, 789); assertEquals(ctx2.request, req); assertEquals(ctx2.url, new URL("https://example.com/")); assertEquals(ctx2.data, 789); assertEquals(await ctx2.getActor("john"), new Person({ preferredUsername: "john" })); federation.setObjectDispatcher(Note, "/users/{identifier}/notes/{id}", (_ctx, values) => { return new Note({ summary: `Note ${values.id} by ${values.identifier}` }); }); const ctx3 = federation.createContext(req, 123); assertEquals(ctx3.request, req); assertEquals(ctx3.url, new URL("https://example.com/")); assertEquals(ctx3.data, 123); assertEquals(await ctx2.getObject(Note, { identifier: "john", id: "123" }), new Note({ summary: "Note 123 by john" })); }); await t.step("RequestContext.clone()", () => { const federation = createFederation({ kv }); const req = new Request("https://example.com/"); const ctx = federation.createContext(req, 123); const clone = ctx.clone(456); assertStrictEquals(clone.request, ctx.request); assertEquals(clone.url, ctx.url); assertEquals(clone.data, 456); assertEquals(clone.origin, ctx.origin); assertEquals(clone.host, ctx.host); assertEquals(clone.hostname, ctx.hostname); assertStrictEquals(clone.documentLoader, ctx.documentLoader); assertStrictEquals(clone.contextLoader, ctx.contextLoader); assertStrictEquals(clone.federation, ctx.federation); }); esm_default.hardReset(); }); test("Federation.setInboxListeners()", async (t) => { const kv = new MemoryKvStore(); esm_default.spyGlobal(); esm_default.get("https://example.com/key2", { headers: { "Content-Type": "application/activity+json" }, body: await rsaPublicKey2.toJsonLd({ contextLoader: mockDocumentLoader }) }); esm_default.get("begin:https://example.com/person2", { headers: { "Content-Type": "application/activity+json" }, body: person2_default }); esm_default.get("begin:https://example.com/person", { headers: { "Content-Type": "application/activity+json" }, body: person_default }); await t.step("path match", () => { const federation = createFederation({ kv, documentLoader: mockDocumentLoader }); federation.setInboxDispatcher("/users/{identifier}/inbox", () => ({ items: [] })); assertThrows(() => federation.setInboxListeners("/users/{identifier}/inbox2"), RouterError); }); await t.step("wrong variables in path", () => { const federation = createFederation({ kv, documentLoader: mockDocumentLoader }); assertThrows(() => federation.setInboxListeners("/users/inbox"), RouterError); assertThrows(() => federation.setInboxListeners("/users/{identifier}/inbox/{id2}"), RouterError); assertThrows(() => federation.setInboxListeners("/users/{identifier}/inbox/{handle}"), RouterError); assertThrows(() => federation.setInboxListeners("/users/{identifier2}/inbox"), RouterError); }); await t.step("on()", async () => { const authenticatedRequests = []; const federation = createFederation({ kv, documentLoader: mockDocumentLoader, authenticatedDocumentLoaderFactory(identity) { const docLoader = getAuthenticatedDocumentLoader(identity); return (url) => { const urlObj = new URL(url); authenticatedRequests.push([url, identity.keyId.href]); if (urlObj.host === "example.com") return docLoader(url); return mockDocumentLoader(url); }; } }); const inbox = []; federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, (ctx, create) => { inbox.push([ctx, create]); }); let response = await federation.fetch(new Request("https://example.com/inbox", { method: "POST" }), { contextData: void 0 }); assertEquals(inbox, []); assertEquals(response.status, 404); federation.setActorDispatcher("/users/{identifier}", (_, identifier) => identifier === "john" ? new Person({}) : null).setKeyPairsDispatcher(() => [{ privateKey: rsaPrivateKey2, publicKey: rsaPublicKey2.publicKey }]); const options = { documentLoader: mockDocumentLoader, contextLoader: mockDocumentLoader }; const activity = () => new Create({ id: new URL("https://example.com/activities/" + crypto.randomUUID()), actor: new URL("https://example.com/person2") }); response = await federation.fetch(new Request("https://example.com/inbox", { method: "POST", body: JSON.stringify(await activity().toJsonLd(options)) }), { contextData: void 0 }); assertEquals(inbox, []); assertEquals(response.status, 401); response = await federation.fetch(new Request("https://example.com/users/no-one/inbox", { method: "POST" }), { contextData: void 0 }); assertEquals(inbox, []); assertEquals(response.status, 404); response = await federation.fetch(new Request("https://example.com/users/john/inbox", { method: "POST", body: JSON.stringify(await activity().toJsonLd(options)) }), { contextData: void 0 }); assertEquals(inbox, []); assertEquals(response.status, 401); const activityPayload = await activity().toJsonLd(options); let request = new Request("https://example.com/users/john/inbox", { method: "POST", headers: { "Content-Type": "application/activity+json" }, body: JSON.stringify(activityPayload) }); request = await signRequest(request, rsaPrivateKey3, new URL("https://example.com/person2#key3")); response = await federation.fetch(request, { contextData: void 0 }); assertEquals(inbox.length, 1); assertEquals(inbox[0][1].actorId, new URL("https://example.com/person2")); assertEquals(response.status, 202); while (authenticatedRequests.length > 0) authenticatedRequests.shift(); assertEquals(authenticatedRequests, []); await inbox[0][0].documentLoader("https://example.com/person"); assertEquals(authenticatedRequests, [["https://example.com/person", "https://example.com/users/john#main-key"]]); response = await federation.fetch(request, { contextData: void 0 }); assertEquals(inbox.length, 1); inbox.shift(); request = new Request("https://another.host/users/john/inbox", { method: "POST", headers: { "Content-Type": "application/activity+json" }, body: JSON.stringify(activityPayload) }); request = await signRequest(request, rsaPrivateKey3, new URL("https://example.com/person2#key3")); response = await federation.fetch(request, { contextData: void 0 }); assertEquals(inbox.length, 1); assertEquals(inbox[0][1].actorId, new URL("https://example.com/person2")); assertEquals(response.status, 202); while (authenticatedRequests.length > 0) authenticatedRequests.shift(); assertEquals(authenticatedRequests, []); await inbox[0][0].documentLoader("https://example.com/person"); assertEquals(authenticatedRequests, [["https://example.com/person", "https://another.host/users/john#main-key"]]); inbox.shift(); request = new Request("https://example.com/inbox", { method: "POST", headers: { "Content-Type": "application/activity+json" }, body: JSON.stringify(await activity().toJsonLd(options)) }); request = await signRequest(request, rsaPrivateKey3, new URL("https://example.com/person2#key3")); response = await federation.fetch(request, { contextData: void 0 }); assertEquals(inbox.length, 1); assertEquals(inbox[0][1].actorId, new URL("https://example.com/person2")); assertEquals(response.status, 202); while (authenticatedRequests.length > 0) authenticatedRequests.shift(); assertEquals(authenticatedRequests, []); await inbox[0][0].documentLoader("https://example.com/person"); assertEquals(authenticatedRequests, []); inbox.shift(); request = new Request("https://example.com/users/john/inbox", { method: "POST", headers: { "Content-Type": "application/activity+json" }, body: JSON.stringify(await (await signObject(activity(), ed25519PrivateKey, ed25519Multikey.id, options)).toJsonLd(options)) }); response = await federation.fetch(request, { contextData: void 0 }); assertEquals(inbox.length, 1); assertEquals(inbox[0][1].actorId, new URL("https://example.com/person2")); assertEquals(response.status, 202); while (authenticatedRequests.length > 0) authenticatedRequests.shift(); assertEquals(authenticatedRequests, []); await inbox[0][0].documentLoader("https://example.com/person"); assertEquals(authenticatedRequests, [["https://example.com/person", "https://example.com/users/john#main-key"]]); }); await t.step("onError()", async () => { const federation = createFederation({ kv, documentLoader: mockDocumentLoader, authenticatedDocumentLoaderFactory(identity) { const docLoader = getAuthenticatedDocumentLoader(identity); return (url) => { const urlObj = new URL(url); if (urlObj.host === "example.com") return docLoader(url); return mockDocumentLoader(url); }; } }); federation.setActorDispatcher("/users/{identifier}", (_, identifier) => identifier === "john" ? new Person({}) : null).setKeyPairsDispatcher(() => [{ privateKey: rsaPrivateKey2, publicKey: rsaPublicKey2.publicKey }]); const error = /* @__PURE__ */ new Error("test"); const errors = []; federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => { throw error; }).onError((_, e) => { errors.push(e); }); const activity = new Create({ actor: new URL("https://example.com/person") }); let request = new Request("https://example.com/users/john/inbox", { method: "POST", headers: { "Content-Type": "application/activity+json" }, body: JSON.stringify(await activity.toJsonLd({ contextLoader: mockDocumentLoader })) }); request = await signRequest(request, rsaPrivateKey2, new URL("https://example.com/key2")); const response = await federation.fetch(request, { contextData: void 0 }); assertEquals(errors.length, 1); assertEquals(errors[0], error); assertEquals(response.status, 500); }); esm_default.hardReset(); }); test("Federation.setInboxDispatcher()", async (t) => { const kv = new MemoryKvStore(); await t.step("path match", () => { const federation = createFederation({ kv, documentLoader: mockDocumentLoader }); federation.setInboxListeners("/users/{identifier}/inbox"); assertThrows(() => federation.setInboxDispatcher("/users/{identifier}/inbox2", () => ({ items: [] })), RouterError); }); await t.step("path match", () => { const federation = createFederation({ kv, documentLoader: mockDocumentLoader }); federation.setInboxListeners("/users/{identifier}/inbox"); federation.setInboxDispatcher("/users/{identifier}/inbox", () => ({ items: [] })); }); await t.step("wrong variables in path", () => { const federation = createFederation({ kv, documentLoader: mockDocumentLoader }); assertThrows(() => federation.setInboxDispatcher("/users/inbox", () => ({ items: [] })), RouterError); assertThrows(() => federation.setInboxDispatcher("/users/{identifier}/inbox/{identifier2}", () => ({ items: [] })), RouterError); assertThrows(() => federation.setInboxDispatcher("/users/{identifier2}/inbox", () => ({ items: [] })), RouterError); }); }); test("FederationImpl.sendActivity()", async (t) => { esm_default.spyGlobal(); let verified = null; let request = null; esm_default.post("https://example.com/inbox", async (cl) => { verified = []; request = cl.request.clone(); const options = { documentLoader: mockDocumentLoader, contextLoader: mockDocumentLoader }; let json = await cl.request.json(); if (await verifyJsonLd(json, options)) verified.push("ld"); json = detachSignature(json); let activity = await verifyObject(Activity, json, options); if (activity == null) activity = await Activity.fromJsonLd(json, options); else verified.push("proof"); const key = await verifyRequest(request, options); if (key != null && await doesActorOwnKey(activity, key, options)) verified.push("http"); if (verified.length > 0) return new Response(null, { status: 202 }); return new Response(null, { status: 401 }); }); const kv = new MemoryKvStore(); const federation = new FederationImpl({ kv, contextLoader: mockDocumentLoader }); const context = federation.createContext(new URL("https://example.com/")); await t.step("success", async () => { const activity = new Create({ id: new URL("https://example.com/activity/1"), actor: new URL("https://example.com/person") }); const inboxes = { "https://example.com/inbox": { actorIds: ["https://example.com/recipient"], sharedInbox: false } }; await federation.sendActivity([{ privateKey: rsaPrivateKey2, keyId: rsaPublicKey2.id }], inboxes, activity, { context }); assertEquals(verified, ["http"]); assertInstanceOf(request, Request); assertEquals(request?.method, "POST"); assertEquals(request?.url, "https://example.com/inbox"); assertEquals(request?.headers.get("Content-Type"), "application/activity+json"); verified = null; await federation.sendActivity([{ privateKey: rsaPrivateKey3, keyId: rsaPublicKey3.id }], inboxes, activity.clone({ actor: new URL("https://example.com/person2") }), { context }); assertEquals(verified, ["ld", "http"]); assertInstanceOf(request, Request); assertEquals(request?.method, "POST"); assertEquals(request?.url, "https://example.com/inbox"); assertEquals(request?.headers.get("Content-Type"), "application/activity+json"); verified = null; await federation.sendActivity([{ privateKey: ed25519PrivateKey, keyId: ed25519Multikey.id }], inboxes, activity.clone({ actor: new URL("https://example.com/person2") }), { context }); assertEquals(verified, ["proof"]); assertInstanceOf(request, Request); assertEquals(request?.method, "POST"); assertEquals(request?.url, "https://example.com/inbox"); assertEquals(request?.headers.get("Content-Type"), "application/activity+json"); verified = null; await federation.sendActivity([{ privateKey: rsaPrivateKey3, keyId: rsaPublicKey3.id }, { privateKey: ed25519PrivateKey, keyId: ed25519Multikey.id }], inboxes, activity.clone({ actor: new URL("https://example.com/person2") }), { context }); assertEquals(verified, [ "ld", "proof", "http" ]); assertInstanceOf(request, Request); assertEquals(request?.method, "POST"); assertEquals(request?.url, "https://example.com/inbox"); assertEquals(request?.headers.get("Content-Type"), "application/activity+json"); }); esm_default.hardReset(); }); test("FederationImpl.processQueuedTask()", async (t) => { await t.step("with MessageQueue having nativeRetrial", async () => { const kv = new MemoryKvStore(); const queuedMessages = []; const queue = { nativeRetrial: true, enqueue(message, _options) { queuedMessages.push(message); return Promise.resolve(); }, listen(_handler, _options) { return Promise.resolve(); } }; const federation = new FederationImpl({ kv, queue }); federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => { throw new Error("Intended error for testing"); }); await assertRejects(() => federation.processQueuedTask(void 0, { type: "outbox", id: crypto.randomUUID(), baseUrl: "https://example.com", keys: [], activity: { "@context": "https://www.w3.org/ns/activitystreams", type: "Create", actor: "https://example.com/users/alice", object: { type: "Note", content: "test" } }, activityType: "https://www.w3.org/ns/activitystreams#Create", inbox: "https://invalid-domain-that-does-not-exist.example/inbox", sharedInbox: false, started: (/* @__PURE__ */ new Date()).toISOString(), attempt: 0, headers: {}, traceContext: {} }), Error); assertEquals(queuedMessages, []); await assertRejects(() => federation.processQueuedTask(void 0, { type: "inbox", id: crypto.randomUUID(), baseUrl: "https://example.com", activity: { "@context": "https://www.w3.org/ns/activitystreams", type: "Create", actor: "https://remote.example/users/alice", object: { type: "Note", content: "Hello world" } }, started: (/* @__PURE__ */ new Date()).toISOString(), attempt: 0, identifier: null, traceContext: {} }), Error); assertEquals(queuedMessages, []); }); await t.step("with MessageQueue having no nativeRetrial", async () => { const kv = new MemoryKvStore(); let queuedMessages = []; const queue = { enqueue(message, _options) { queuedMessages.push(message); return Promise.resolve(); }, listen(_handler, _options) { return Promise.resolve(); } }; const federation = new FederationImpl({ kv, queue }); federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => { throw new Error("Intended error for testing"); }); const outboxMessage = { type: "outbox", id: crypto.randomUUID(), baseUrl: "https://example.com", keys: [], activity: { "@context": "https://www.w3.org/ns/activitystreams", type: "Create", actor: "https://example.com/users/alice", object: { type: "Note", content: "test" } }, activityType: "https://www.w3.org/ns/activitystreams#Create", inbox: "https://invalid-domain-that-does-not-exist.example/inbox", sharedInbox: false, started: (/* @__PURE__ */ new Date()).toISOString(), attempt: 0, headers: {}, traceContext: {} }; await federation.processQueuedTask(void 0, outboxMessage); assertEquals(queuedMessages, [{ ...outboxMessage, attempt: 1 }]); queuedMessages = []; const inboxMessage = { type: "inbox", id: crypto.randomUUID(), baseUrl: "https://example.com", activity: { "@context": "https://www.w3.org/ns/activitystreams", type: "Create", actor: "https://remote.example/users/alice", object: { type: "Note", content: "Hello world" } }, started: (/* @__PURE__ */ new Date()).toISOString(), attempt: 0, identifier: null, traceContext: {} }; await federation.processQueuedTask(void 0, inboxMessage); assertEquals(queuedMessages, [{ ...inboxMessage, attempt: 1 }]); }); }); test("ContextImpl.lookupObject()", async (t) => { esm_default.spyGlobal(); esm_default.get("begin:https://localhost/.well-known/webfinger", { headers: { "Content-Type": "application/jrd+json" }, body: { subject: "acct:test@localhost", links: [{ rel: "self", type: "application/activity+json", href: "https://localhost/actor" }] } }); esm_default.get("https://localhost/actor", { headers: { "Content-Type": "application/activity+json" }, body: { "@context": "https://www.w3.org/ns/activitystreams", "type": "Person", "id": "https://localhost/actor", "preferredUsername": "test" } }); await t.step("allowPrivateAddress: true", async () => { const federation = createFederation({ kv: new MemoryKvStore(), allowPrivateAddress: true }); const ctx = federation.createContext(new URL("https://example.com/")); const result = await ctx.lookupObject("@test@localhost"); assertInstanceOf(result, Person); assertEquals(result.id, new URL("https://localhost/actor")); assertEquals(result.preferredUsername, "test"); }); await t.step("allowPrivateAddress: false", async () => { const federation = createFederation({ kv: new MemoryKvStore(), allowPrivateAddress: false }); const ctx = federation.createContext(new URL("https://example.com/")); const result = await ctx.lookupObject("@test@localhost"); assertEquals(result, null); }); esm_default.hardReset(); }); test("ContextImpl.sendActivity()", async (t) => { esm_default.spyGlobal(); let verified = null; let request = null; let collectionSyncHeader = null; esm_default.post("https://example.com/inbox", async (cl) => { verified = []; request = cl.request.clone(); collectionSyncHeader = cl.request.headers.get("Collec