@fedify/fedify
Version:
An ActivityPub server framework
208 lines (207 loc) • 9.44 kB
JavaScript
import "@js-temporal/polyfill";
import "urlpattern-polyfill";
globalThis.addEventListener = () => {};
import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
import "../std__assert-CRDpx_HF.mjs";
import { n as ed25519PrivateKey, r as ed25519PublicKey, t as ed25519Multikey } from "../keys-DGu1NFwu.mjs";
import { r as signObject } from "../proof-DLhLRv3m.mjs";
import { t as MemoryKvStore } from "../kv-rV3vodCc.mjs";
import { o as createFederation } from "../middleware-D9k0Knum.mjs";
import { mockDocumentLoader, test } from "@fedify/fixture";
import { Create, Follow, Person } from "@fedify/vocab";
//#region src/federation/idempotency.test.ts
const kv = new MemoryKvStore();
const federationOptions = {
kv,
documentLoaderFactory: () => mockDocumentLoader,
authenticatedDocumentLoaderFactory: () => mockDocumentLoader
};
function createTestFederation() {
const federation = createFederation(federationOptions);
federation.setActorDispatcher("/users/{identifier}", (_, identifier) => identifier === "john" ? new Person({}) : null).setKeyPairsDispatcher(() => [{
privateKey: ed25519PrivateKey,
publicKey: ed25519PublicKey.publicKey
}]);
return federation;
}
test("Federation.setInboxListeners().withIdempotency() - per-origin strategy", async () => {
const federation = createTestFederation();
const processedActivities = [];
federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").withIdempotency("per-origin").on(Create, (ctx, activity) => {
processedActivities.push([ctx.recipient, activity]);
});
const signedActivity = await signObject(new Create({
id: new URL("https://example.com/activities/1"),
actor: new URL("https://example.com/person2")
}), ed25519PrivateKey, ed25519Multikey.id);
const body = JSON.stringify(await signedActivity.toJsonLd({ contextLoader: mockDocumentLoader }));
let response = await federation.fetch(new Request("https://example.com/users/john/inbox", {
method: "POST",
headers: { "Content-Type": "application/activity+json" },
body
}), { contextData: void 0 });
assertEquals(response.status, 202);
assertEquals(processedActivities.length, 1);
assertEquals(processedActivities[0][0], "john");
response = await federation.fetch(new Request("https://example.com/inbox", {
method: "POST",
headers: { "Content-Type": "application/activity+json" },
body
}), { contextData: void 0 });
assertEquals(response.status, 202);
assertEquals(processedActivities.length, 1);
await kv.delete([
"_fedify",
"activityIdempotence",
"https://example.com:https://example.com/activities/1"
]);
});
test("Federation.setInboxListeners().withIdempotency() - per-inbox strategy", async () => {
const federation = createTestFederation();
const processedActivities = [];
federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").withIdempotency("per-inbox").on(Create, (ctx, activity) => {
processedActivities.push([ctx.recipient, activity]);
});
const signedActivity = await signObject(new Create({
id: new URL("https://example.com/activities/2"),
actor: new URL("https://example.com/person2")
}), ed25519PrivateKey, ed25519Multikey.id);
const body = JSON.stringify(await signedActivity.toJsonLd({ contextLoader: mockDocumentLoader }));
let response = await federation.fetch(new Request("https://example.com/users/john/inbox", {
method: "POST",
headers: { "Content-Type": "application/activity+json" },
body
}), { contextData: void 0 });
assertEquals(response.status, 202);
assertEquals(processedActivities.length, 1);
assertEquals(processedActivities[0][0], "john");
response = await federation.fetch(new Request("https://example.com/inbox", {
method: "POST",
headers: { "Content-Type": "application/activity+json" },
body
}), { contextData: void 0 });
assertEquals(response.status, 202);
assertEquals(processedActivities.length, 2);
assertEquals(processedActivities[1][0], null);
response = await federation.fetch(new Request("https://example.com/users/john/inbox", {
method: "POST",
headers: { "Content-Type": "application/activity+json" },
body
}), { contextData: void 0 });
assertEquals(response.status, 202);
assertEquals(processedActivities.length, 2);
});
test("Federation.setInboxListeners().withIdempotency() - global strategy", async () => {
const federation = createTestFederation();
const processedActivities = [];
federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").withIdempotency("global").on(Create, (ctx, activity) => {
processedActivities.push([ctx.recipient, activity]);
});
const signedActivity = await signObject(new Create({
id: new URL("https://example.com/activities/3"),
actor: new URL("https://example.com/person2")
}), ed25519PrivateKey, ed25519Multikey.id);
const body = JSON.stringify(await signedActivity.toJsonLd({ contextLoader: mockDocumentLoader }));
let response = await federation.fetch(new Request("https://example.com/users/john/inbox", {
method: "POST",
headers: { "Content-Type": "application/activity+json" },
body
}), { contextData: void 0 });
assertEquals(response.status, 202);
assertEquals(processedActivities.length, 1);
response = await federation.fetch(new Request("https://example.com/inbox", {
method: "POST",
headers: { "Content-Type": "application/activity+json" },
body
}), { contextData: void 0 });
assertEquals(response.status, 202);
assertEquals(processedActivities.length, 1);
});
test("Federation.setInboxListeners().withIdempotency() - custom callback", async () => {
const federation = createTestFederation();
const processedActivities = [];
federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").withIdempotency((ctx, activity) => {
if (activity instanceof Follow) return null;
const inboxId = ctx.recipient ?? "shared";
return `${ctx.origin}:${activity.id?.href}:${inboxId}`;
}).on(Create, (ctx, activity) => {
processedActivities.push([ctx.recipient, activity]);
}).on(Follow, (ctx, activity) => {
processedActivities.push([ctx.recipient, activity]);
});
const signedCreate = await signObject(new Create({
id: new URL("https://example.com/activities/4"),
actor: new URL("https://example.com/person2")
}), ed25519PrivateKey, ed25519Multikey.id);
const createBody = JSON.stringify(await signedCreate.toJsonLd({ contextLoader: mockDocumentLoader }));
let response = await federation.fetch(new Request("https://example.com/users/john/inbox", {
method: "POST",
headers: { "Content-Type": "application/activity+json" },
body: createBody
}), { contextData: void 0 });
assertEquals(response.status, 202);
assertEquals(processedActivities.length, 1);
response = await federation.fetch(new Request("https://example.com/users/john/inbox", {
method: "POST",
headers: { "Content-Type": "application/activity+json" },
body: createBody
}), { contextData: void 0 });
assertEquals(response.status, 202);
assertEquals(processedActivities.length, 1);
const signedFollow = await signObject(new Follow({
id: new URL("https://example.com/activities/5"),
actor: new URL("https://example.com/person2")
}), ed25519PrivateKey, ed25519Multikey.id);
const followBody = JSON.stringify(await signedFollow.toJsonLd({ contextLoader: mockDocumentLoader }));
response = await federation.fetch(new Request("https://example.com/users/john/inbox", {
method: "POST",
headers: { "Content-Type": "application/activity+json" },
body: followBody
}), { contextData: void 0 });
assertEquals(response.status, 202);
assertEquals(processedActivities.length, 2);
response = await federation.fetch(new Request("https://example.com/users/john/inbox", {
method: "POST",
headers: { "Content-Type": "application/activity+json" },
body: followBody
}), { contextData: void 0 });
assertEquals(response.status, 202);
assertEquals(processedActivities.length, 3);
});
test("Federation.setInboxListeners() - default strategy is per-inbox", async () => {
const federation = createTestFederation();
const processedActivities = [];
federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, (ctx, activity) => {
processedActivities.push([ctx.recipient, activity]);
});
const signedActivity = await signObject(new Create({
id: new URL("https://example.com/activities/6"),
actor: new URL("https://example.com/person2")
}), ed25519PrivateKey, ed25519Multikey.id);
const body = JSON.stringify(await signedActivity.toJsonLd({ contextLoader: mockDocumentLoader }));
let response = await federation.fetch(new Request("https://example.com/users/john/inbox", {
method: "POST",
headers: { "Content-Type": "application/activity+json" },
body
}), { contextData: void 0 });
assertEquals(response.status, 202);
assertEquals(processedActivities.length, 1);
assertEquals(processedActivities[0][0], "john");
response = await federation.fetch(new Request("https://example.com/inbox", {
method: "POST",
headers: { "Content-Type": "application/activity+json" },
body
}), { contextData: void 0 });
assertEquals(response.status, 202);
assertEquals(processedActivities.length, 2);
assertEquals(processedActivities[1][0], null);
response = await federation.fetch(new Request("https://example.com/users/john/inbox", {
method: "POST",
headers: { "Content-Type": "application/activity+json" },
body
}), { contextData: void 0 });
assertEquals(response.status, 202);
assertEquals(processedActivities.length, 2);
});
//#endregion
export {};