@fedify/fedify
Version:
An ActivityPub server framework
1,259 lines (1,254 loc) • 69.2 kB
JavaScript
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