@fedify/fedify
Version:
An ActivityPub server framework
1,247 lines (1,243 loc) • 36.9 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 "../assert_instance_of-CF09JHYM.js";
import { MemoryKvStore } from "../kv-BMY6Qf_A.js";
import { acceptsJsonLd, createFederation, handleActor, handleCollection, handleInbox, handleObject, respondWithObject, respondWithObjectIfAcceptable } from "../middleware-DlU2_Djj.js";
import "../docloader-C8mmLN-Y.js";
import "../url-kTAI6_KP.js";
import "../semver-DWClQt_5.js";
import "../client-rv6n0nKR.js";
import "../router-D_aVZZUc.js";
import "../types-C7C_l-jz.js";
import "../multibase-DeCHcK8L.js";
import { Create, Note, Person } from "../vocab-dDpPQ0fF.js";
import "../langstr-DbWheeIS.js";
import "../lookup-Ozuw-rxN.js";
import "../type-D2s5lmbZ.js";
import "../actor-BP-NTJ2m.js";
import "../key-CPSlDkCj.js";
import { signRequest } from "../http-BmfI6U39.js";
import "../authdocloader-Banw__l6.js";
import "../ld-D9kk1Uy2.js";
import "../owner-DF6Fg4-s.js";
import "../proof-13QPysOU.js";
import "../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 "../std__assert-vp0TKMS1.js";
import { assertFalse } from "../assert_rejects-C-sxEMM5.js";
import "../assert_is_error-nrwA1GeT.js";
import "../assert_not_equals-Dc7y-V5Q.js";
import "../assert_throws-Cn9C6Jur.js";
import { mockDocumentLoader } from "../docloader-09nVWLAZ.js";
import { createInboxContext, createRequestContext } from "../context-Buzj1YKD.js";
import { rsaPrivateKey3, rsaPublicKey2, rsaPublicKey3 } from "../keys-CtFc865v.js";
//#region federation/handler.test.ts
test("acceptsJsonLd()", () => {
assert(acceptsJsonLd(new Request("https://example.com/", { headers: { Accept: "application/activity+json" } })));
assert(acceptsJsonLd(new Request("https://example.com/", { headers: { Accept: "application/ld+json" } })));
assert(acceptsJsonLd(new Request("https://example.com/", { headers: { Accept: "application/json" } })));
assertFalse(acceptsJsonLd(new Request("https://example.com/", { headers: { Accept: "application/ld+json; q=0.5, text/html; q=0.8" } })));
assertFalse(acceptsJsonLd(new Request("https://example.com/", { headers: { Accept: "application/ld+json; q=0.4, application/xhtml+xml; q=0.9" } })));
});
test("handleActor()", async () => {
const federation = createFederation({ kv: new MemoryKvStore() });
let context = createRequestContext({
federation,
data: void 0,
url: new URL("https://example.com/"),
getActorUri(identifier) {
return new URL(`https://example.com/users/${identifier}`);
}
});
const actorDispatcher = (ctx, handle) => {
if (handle !== "someone") return null;
return new Person({
id: ctx.getActorUri(handle),
name: "Someone"
});
};
let onNotFoundCalled = null;
const onNotFound = (request) => {
onNotFoundCalled = request;
return new Response("Not found", { status: 404 });
};
let onNotAcceptableCalled = null;
const onNotAcceptable = (request) => {
onNotAcceptableCalled = request;
return new Response("Not acceptable", { status: 406 });
};
let onUnauthorizedCalled = null;
const onUnauthorized = (request) => {
onUnauthorizedCalled = request;
return new Response("Unauthorized", { status: 401 });
};
let response = await handleActor(context.request, {
context,
identifier: "someone",
onNotFound,
onNotAcceptable,
onUnauthorized
});
assertEquals(response.status, 404);
assertEquals(onNotFoundCalled, context.request);
assertEquals(onNotAcceptableCalled, null);
assertEquals(onUnauthorizedCalled, null);
onNotFoundCalled = null;
context = createRequestContext({
...context,
getActor(handle) {
return Promise.resolve(actorDispatcher(context, handle));
}
});
response = await handleActor(context.request, {
context,
identifier: "someone",
actorDispatcher,
onNotFound,
onNotAcceptable,
onUnauthorized
});
assertEquals(response.status, 406);
assertEquals(onNotFoundCalled, null);
assertEquals(onNotAcceptableCalled, context.request);
assertEquals(onUnauthorizedCalled, null);
onNotAcceptableCalled = null;
response = await handleActor(context.request, {
context,
identifier: "no-one",
actorDispatcher,
onNotFound,
onNotAcceptable,
onUnauthorized
});
assertEquals(response.status, 404);
assertEquals(onNotFoundCalled, context.request);
assertEquals(onNotAcceptableCalled, null);
assertEquals(onUnauthorizedCalled, null);
onNotFoundCalled = null;
context = createRequestContext({
...context,
request: new Request(context.url, { headers: { Accept: "application/activity+json" } })
});
response = await handleActor(context.request, {
context,
identifier: "someone",
actorDispatcher,
onNotFound,
onNotAcceptable,
onUnauthorized
});
assertEquals(response.status, 200);
assertEquals(response.headers.get("Content-Type"), "application/activity+json");
assertEquals(await response.json(), {
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
"https://w3id.org/security/data-integrity/v1",
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/multikey/v1",
{
alsoKnownAs: {
"@id": "as:alsoKnownAs",
"@type": "@id"
},
manuallyApprovesFollowers: "as:manuallyApprovesFollowers",
movedTo: {
"@id": "as:movedTo",
"@type": "@id"
},
featured: {
"@id": "toot:featured",
"@type": "@id"
},
featuredTags: {
"@id": "toot:featuredTags",
"@type": "@id"
},
discoverable: "toot:discoverable",
indexable: "toot:indexable",
memorial: "toot:memorial",
suspended: "toot:suspended",
toot: "http://joinmastodon.org/ns#",
schema: "http://schema.org#",
PropertyValue: "schema:PropertyValue",
value: "schema:value",
misskey: "https://misskey-hub.net/ns#",
_misskey_followedMessage: "misskey:_misskey_followedMessage",
isCat: "misskey:isCat",
Emoji: "toot:Emoji"
}
],
id: "https://example.com/users/someone",
type: "Person",
name: "Someone"
});
assertEquals(onNotFoundCalled, null);
assertEquals(onNotAcceptableCalled, null);
assertEquals(onUnauthorizedCalled, null);
response = await handleActor(context.request, {
context,
identifier: "no-one",
actorDispatcher,
onNotFound,
onNotAcceptable,
onUnauthorized
});
assertEquals(response.status, 404);
assertEquals(onNotFoundCalled, context.request);
assertEquals(onNotAcceptableCalled, null);
assertEquals(onUnauthorizedCalled, null);
onNotFoundCalled = null;
response = await handleActor(context.request, {
context,
identifier: "someone",
actorDispatcher,
authorizePredicate: (_ctx, _handle, signedKey, signedKeyOwner) => signedKey != null && signedKeyOwner != null,
onNotFound,
onNotAcceptable,
onUnauthorized
});
assertEquals(response.status, 401);
assertEquals(onNotFoundCalled, null);
assertEquals(onNotAcceptableCalled, null);
assertEquals(onUnauthorizedCalled, context.request);
onUnauthorizedCalled = null;
context = createRequestContext({
...context,
getSignedKey: () => Promise.resolve(rsaPublicKey2),
getSignedKeyOwner: () => Promise.resolve(new Person({}))
});
response = await handleActor(context.request, {
context,
identifier: "someone",
actorDispatcher,
authorizePredicate: (_ctx, _handle, signedKey, signedKeyOwner) => signedKey != null && signedKeyOwner != null,
onNotFound,
onNotAcceptable,
onUnauthorized
});
assertEquals(response.status, 200);
assertEquals(response.headers.get("Content-Type"), "application/activity+json");
assertEquals(await response.json(), {
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
"https://w3id.org/security/data-integrity/v1",
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/multikey/v1",
{
alsoKnownAs: {
"@id": "as:alsoKnownAs",
"@type": "@id"
},
manuallyApprovesFollowers: "as:manuallyApprovesFollowers",
movedTo: {
"@id": "as:movedTo",
"@type": "@id"
},
featured: {
"@id": "toot:featured",
"@type": "@id"
},
featuredTags: {
"@id": "toot:featuredTags",
"@type": "@id"
},
discoverable: "toot:discoverable",
indexable: "toot:indexable",
memorial: "toot:memorial",
suspended: "toot:suspended",
toot: "http://joinmastodon.org/ns#",
schema: "http://schema.org#",
PropertyValue: "schema:PropertyValue",
value: "schema:value",
misskey: "https://misskey-hub.net/ns#",
_misskey_followedMessage: "misskey:_misskey_followedMessage",
isCat: "misskey:isCat",
Emoji: "toot:Emoji"
}
],
id: "https://example.com/users/someone",
type: "Person",
name: "Someone"
});
assertEquals(onNotFoundCalled, null);
assertEquals(onNotAcceptableCalled, null);
assertEquals(onUnauthorizedCalled, null);
});
test("handleObject()", async () => {
const federation = createFederation({ kv: new MemoryKvStore() });
let context = createRequestContext({
federation,
data: void 0,
url: new URL("https://example.com/"),
getObjectUri(_cls, values) {
return new URL(`https://example.com/users/${values.handle}/notes/${values.id}`);
}
});
const objectDispatcher = (ctx, values) => {
if (values.handle !== "someone" || values.id !== "123") return null;
return new Note({
id: ctx.getObjectUri(Note, values),
summary: "Hello, world!"
});
};
let onNotFoundCalled = null;
const onNotFound = (request) => {
onNotFoundCalled = request;
return new Response("Not found", { status: 404 });
};
let onNotAcceptableCalled = null;
const onNotAcceptable = (request) => {
onNotAcceptableCalled = request;
return new Response("Not acceptable", { status: 406 });
};
let onUnauthorizedCalled = null;
const onUnauthorized = (request) => {
onUnauthorizedCalled = request;
return new Response("Unauthorized", { status: 401 });
};
let response = await handleObject(context.request, {
context,
values: {
handle: "someone",
id: "123"
},
onNotFound,
onNotAcceptable,
onUnauthorized
});
assertEquals(response.status, 404);
assertEquals(onNotFoundCalled, context.request);
assertEquals(onNotAcceptableCalled, null);
assertEquals(onUnauthorizedCalled, null);
onNotFoundCalled = null;
response = await handleObject(context.request, {
context,
values: {
handle: "someone",
id: "123"
},
objectDispatcher,
onNotFound,
onNotAcceptable,
onUnauthorized
});
assertEquals(response.status, 406);
assertEquals(onNotFoundCalled, null);
assertEquals(onNotAcceptableCalled, context.request);
assertEquals(onUnauthorizedCalled, null);
onNotAcceptableCalled = null;
response = await handleObject(context.request, {
context,
values: {
handle: "no-one",
id: "123"
},
objectDispatcher,
onNotFound,
onNotAcceptable,
onUnauthorized
});
assertEquals(response.status, 404);
assertEquals(onNotFoundCalled, context.request);
assertEquals(onNotAcceptableCalled, null);
assertEquals(onUnauthorizedCalled, null);
onNotFoundCalled = null;
response = await handleObject(context.request, {
context,
values: {
handle: "someone",
id: "not-exist"
},
objectDispatcher,
onNotFound,
onNotAcceptable,
onUnauthorized
});
assertEquals(response.status, 404);
assertEquals(onNotFoundCalled, context.request);
assertEquals(onNotAcceptableCalled, null);
assertEquals(onUnauthorizedCalled, null);
onNotFoundCalled = null;
context = createRequestContext({
...context,
request: new Request(context.url, { headers: { Accept: "application/activity+json" } })
});
response = await handleObject(context.request, {
context,
values: {
handle: "someone",
id: "123"
},
objectDispatcher,
onNotFound,
onNotAcceptable,
onUnauthorized
});
assertEquals(response.status, 200);
assertEquals(response.headers.get("Content-Type"), "application/activity+json");
assertEquals(await response.json(), {
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/data-integrity/v1",
{
Emoji: "toot:Emoji",
Hashtag: "as:Hashtag",
sensitive: "as:sensitive",
toot: "http://joinmastodon.org/ns#",
_misskey_quote: "misskey:_misskey_quote",
fedibird: "http://fedibird.com/ns#",
misskey: "https://misskey-hub.net/ns#",
quoteUri: "fedibird:quoteUri",
quoteUrl: "as:quoteUrl",
emojiReactions: {
"@id": "fedibird:emojiReactions",
"@type": "@id"
}
}
],
id: "https://example.com/users/someone/notes/123",
summary: "Hello, world!",
type: "Note"
});
assertEquals(onNotFoundCalled, null);
assertEquals(onNotAcceptableCalled, null);
assertEquals(onUnauthorizedCalled, null);
response = await handleObject(context.request, {
context,
values: {
handle: "no-one",
id: "123"
},
objectDispatcher,
onNotFound,
onNotAcceptable,
onUnauthorized
});
assertEquals(response.status, 404);
assertEquals(onNotFoundCalled, context.request);
assertEquals(onNotAcceptableCalled, null);
assertEquals(onUnauthorizedCalled, null);
onNotFoundCalled = null;
response = await handleObject(context.request, {
context,
values: {
handle: "someone",
id: "not-exist"
},
objectDispatcher,
onNotFound,
onNotAcceptable,
onUnauthorized
});
assertEquals(response.status, 404);
assertEquals(onNotFoundCalled, context.request);
assertEquals(onNotAcceptableCalled, null);
assertEquals(onUnauthorizedCalled, null);
onNotFoundCalled = null;
response = await handleObject(context.request, {
context,
values: {
handle: "someone",
id: "123"
},
objectDispatcher,
authorizePredicate: (_ctx, _values, signedKey, signedKeyOwner) => signedKey != null && signedKeyOwner != null,
onNotFound,
onNotAcceptable,
onUnauthorized
});
assertEquals(response.status, 401);
assertEquals(onNotFoundCalled, null);
assertEquals(onNotAcceptableCalled, null);
assertEquals(onUnauthorizedCalled, context.request);
onUnauthorizedCalled = null;
context = createRequestContext({
...context,
getSignedKey: () => Promise.resolve(rsaPublicKey2),
getSignedKeyOwner: () => Promise.resolve(new Person({}))
});
response = await handleObject(context.request, {
context,
values: {
handle: "someone",
id: "123"
},
objectDispatcher,
authorizePredicate: (_ctx, _values, signedKey, signedKeyOwner) => signedKey != null && signedKeyOwner != null,
onNotFound,
onNotAcceptable,
onUnauthorized
});
assertEquals(response.status, 200);
assertEquals(response.headers.get("Content-Type"), "application/activity+json");
assertEquals(await response.json(), {
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/data-integrity/v1",
{
Emoji: "toot:Emoji",
Hashtag: "as:Hashtag",
sensitive: "as:sensitive",
toot: "http://joinmastodon.org/ns#",
_misskey_quote: "misskey:_misskey_quote",
fedibird: "http://fedibird.com/ns#",
misskey: "https://misskey-hub.net/ns#",
quoteUri: "fedibird:quoteUri",
quoteUrl: "as:quoteUrl",
emojiReactions: {
"@id": "fedibird:emojiReactions",
"@type": "@id"
}
}
],
id: "https://example.com/users/someone/notes/123",
summary: "Hello, world!",
type: "Note"
});
assertEquals(onNotFoundCalled, null);
assertEquals(onNotAcceptableCalled, null);
assertEquals(onUnauthorizedCalled, null);
});
test("handleCollection()", async () => {
const federation = createFederation({ kv: new MemoryKvStore() });
let context = createRequestContext({
federation,
data: void 0,
url: new URL("https://example.com/"),
getActorUri(identifier) {
return new URL(`https://example.com/users/${identifier}`);
}
});
const dispatcher = (_ctx, handle, cursor) => {
if (handle !== "someone") return null;
const items = [
new Create({ id: new URL("https://example.com/activities/1") }),
new Create({ id: new URL("https://example.com/activities/2") }),
new Create({ id: new URL("https://example.com/activities/3") })
];
if (cursor != null) {
const idx = parseInt(cursor);
return {
items: [items[idx]],
nextCursor: idx < items.length - 1 ? (idx + 1).toString() : null,
prevCursor: idx > 0 ? (idx - 1).toString() : null
};
}
return { items };
};
const counter = (_ctx, handle) => handle === "someone" ? 3 : null;
const firstCursor = (_ctx, handle) => handle === "someone" ? "0" : null;
const lastCursor = (_ctx, handle) => handle === "someone" ? "2" : null;
let onNotFoundCalled = null;
const onNotFound = (request) => {
onNotFoundCalled = request;
return new Response("Not found", { status: 404 });
};
let onNotAcceptableCalled = null;
const onNotAcceptable = (request) => {
onNotAcceptableCalled = request;
return new Response("Not acceptable", { status: 406 });
};
let onUnauthorizedCalled = null;
const onUnauthorized = (request) => {
onUnauthorizedCalled = request;
return new Response("Unauthorized", { status: 401 });
};
let response = await handleCollection(context.request, {
context,
name: "collection",
identifier: "someone",
uriGetter(identifier) {
return new URL(`https://example.com/users/${identifier}`);
},
onNotFound,
onNotAcceptable,
onUnauthorized
});
assertEquals(response.status, 404);
assertEquals(onNotFoundCalled, context.request);
assertEquals(onNotAcceptableCalled, null);
assertEquals(onUnauthorizedCalled, null);
onNotFoundCalled = null;
response = await handleCollection(context.request, {
context,
name: "collection",
identifier: "someone",
uriGetter(identifier) {
return new URL(`https://example.com/users/${identifier}`);
},
collectionCallbacks: { dispatcher },
onNotFound,
onNotAcceptable,
onUnauthorized
});
assertEquals(response.status, 406);
assertEquals(onNotFoundCalled, null);
assertEquals(onNotAcceptableCalled, context.request);
assertEquals(onUnauthorizedCalled, null);
onNotAcceptableCalled = null;
response = await handleCollection(context.request, {
context,
name: "collection",
identifier: "no-one",
uriGetter(identifier) {
return new URL(`https://example.com/users/${identifier}`);
},
collectionCallbacks: { dispatcher },
onNotFound,
onNotAcceptable,
onUnauthorized
});
assertEquals(response.status, 404);
assertEquals(onNotFoundCalled, context.request);
assertEquals(onNotAcceptableCalled, null);
assertEquals(onUnauthorizedCalled, null);
onNotFoundCalled = null;
context = createRequestContext({
...context,
request: new Request(context.url, { headers: { Accept: "application/activity+json" } })
});
response = await handleCollection(context.request, {
context,
name: "collection",
identifier: "no-one",
uriGetter(identifier) {
return new URL(`https://example.com/users/${identifier}`);
},
collectionCallbacks: { dispatcher },
onNotFound,
onNotAcceptable,
onUnauthorized
});
assertEquals(response.status, 404);
assertEquals(onNotFoundCalled, context.request);
assertEquals(onNotAcceptableCalled, null);
assertEquals(onUnauthorizedCalled, null);
onNotFoundCalled = null;
response = await handleCollection(context.request, {
context,
name: "collection",
identifier: "someone",
uriGetter(identifier) {
return new URL(`https://example.com/users/${identifier}`);
},
collectionCallbacks: { dispatcher },
onNotFound,
onNotAcceptable,
onUnauthorized
});
assertEquals(response.status, 200);
assertEquals(response.headers.get("Content-Type"), "application/activity+json");
const createCtx = [
"https://w3id.org/identity/v1",
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/data-integrity/v1",
{
toot: "http://joinmastodon.org/ns#",
misskey: "https://misskey-hub.net/ns#",
fedibird: "http://fedibird.com/ns#",
ChatMessage: "http://litepub.social/ns#ChatMessage",
Emoji: "toot:Emoji",
Hashtag: "as:Hashtag",
sensitive: "as:sensitive",
votersCount: {
"@id": "toot:votersCount",
"@type": "http://www.w3.org/2001/XMLSchema#nonNegativeInteger"
},
_misskey_quote: "misskey:_misskey_quote",
quoteUri: "fedibird:quoteUri",
quoteUrl: "as:quoteUrl",
emojiReactions: {
"@id": "fedibird:emojiReactions",
"@type": "@id"
}
}
];
assertEquals(await response.json(), {
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/data-integrity/v1",
{
toot: "http://joinmastodon.org/ns#",
misskey: "https://misskey-hub.net/ns#",
fedibird: "http://fedibird.com/ns#",
ChatMessage: "http://litepub.social/ns#ChatMessage",
Emoji: "toot:Emoji",
Hashtag: "as:Hashtag",
sensitive: "as:sensitive",
votersCount: "toot:votersCount",
_misskey_quote: "misskey:_misskey_quote",
quoteUri: "fedibird:quoteUri",
quoteUrl: "as:quoteUrl",
emojiReactions: {
"@id": "fedibird:emojiReactions",
"@type": "@id"
}
}
],
id: "https://example.com/users/someone",
type: "OrderedCollection",
orderedItems: [
{
"@context": createCtx,
type: "Create",
id: "https://example.com/activities/1"
},
{
"@context": createCtx,
type: "Create",
id: "https://example.com/activities/2"
},
{
"@context": createCtx,
type: "Create",
id: "https://example.com/activities/3"
}
]
});
assertEquals(onNotFoundCalled, null);
assertEquals(onNotAcceptableCalled, null);
assertEquals(onUnauthorizedCalled, null);
response = await handleCollection(context.request, {
context,
name: "collection",
identifier: "someone",
uriGetter(identifier) {
return new URL(`https://example.com/users/${identifier}`);
},
collectionCallbacks: {
dispatcher,
authorizePredicate: (_ctx, _handle, key, keyOwner) => key != null && keyOwner != null
},
onNotFound,
onNotAcceptable,
onUnauthorized
});
assertEquals(response.status, 401);
assertEquals(onNotFoundCalled, null);
assertEquals(onNotAcceptableCalled, null);
assertEquals(onUnauthorizedCalled, context.request);
onUnauthorizedCalled = null;
context = createRequestContext({
...context,
getSignedKey: () => Promise.resolve(rsaPublicKey2),
getSignedKeyOwner: () => Promise.resolve(new Person({}))
});
response = await handleCollection(context.request, {
context,
name: "collection",
identifier: "someone",
uriGetter(identifier) {
return new URL(`https://example.com/users/${identifier}`);
},
collectionCallbacks: {
dispatcher,
authorizePredicate: (_ctx, _handle, key, keyOwner) => key != null && keyOwner != null
},
onNotFound,
onNotAcceptable,
onUnauthorized
});
assertEquals(response.status, 200);
assertEquals(response.headers.get("Content-Type"), "application/activity+json");
assertEquals(await response.json(), {
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/data-integrity/v1",
{
toot: "http://joinmastodon.org/ns#",
misskey: "https://misskey-hub.net/ns#",
fedibird: "http://fedibird.com/ns#",
ChatMessage: "http://litepub.social/ns#ChatMessage",
Emoji: "toot:Emoji",
Hashtag: "as:Hashtag",
sensitive: "as:sensitive",
votersCount: "toot:votersCount",
_misskey_quote: "misskey:_misskey_quote",
quoteUri: "fedibird:quoteUri",
quoteUrl: "as:quoteUrl",
emojiReactions: {
"@id": "fedibird:emojiReactions",
"@type": "@id"
}
}
],
id: "https://example.com/users/someone",
type: "OrderedCollection",
orderedItems: [
{
"@context": createCtx,
type: "Create",
id: "https://example.com/activities/1"
},
{
"@context": createCtx,
type: "Create",
id: "https://example.com/activities/2"
},
{
"@context": createCtx,
type: "Create",
id: "https://example.com/activities/3"
}
]
});
assertEquals(onNotFoundCalled, null);
assertEquals(onNotAcceptableCalled, null);
assertEquals(onUnauthorizedCalled, null);
response = await handleCollection(context.request, {
context,
name: "collection",
identifier: "someone",
uriGetter(identifier) {
return new URL(`https://example.com/users/${identifier}`);
},
collectionCallbacks: {
dispatcher,
counter,
firstCursor,
lastCursor
},
onNotFound,
onNotAcceptable,
onUnauthorized
});
assertEquals(response.status, 200);
assertEquals(response.headers.get("Content-Type"), "application/activity+json");
assertEquals(await response.json(), {
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/data-integrity/v1",
{
toot: "http://joinmastodon.org/ns#",
misskey: "https://misskey-hub.net/ns#",
fedibird: "http://fedibird.com/ns#",
ChatMessage: "http://litepub.social/ns#ChatMessage",
Emoji: "toot:Emoji",
Hashtag: "as:Hashtag",
sensitive: "as:sensitive",
votersCount: "toot:votersCount",
_misskey_quote: "misskey:_misskey_quote",
quoteUri: "fedibird:quoteUri",
quoteUrl: "as:quoteUrl",
emojiReactions: {
"@id": "fedibird:emojiReactions",
"@type": "@id"
}
}
],
id: "https://example.com/users/someone",
type: "OrderedCollection",
totalItems: 3,
first: "https://example.com/?cursor=0",
last: "https://example.com/?cursor=2"
});
assertEquals(onNotFoundCalled, null);
assertEquals(onNotAcceptableCalled, null);
assertEquals(onUnauthorizedCalled, null);
let url = new URL("https://example.com/?cursor=0");
context = createRequestContext({
...context,
url,
request: new Request(url, { headers: { Accept: "application/activity+json" } })
});
response = await handleCollection(context.request, {
context,
name: "collection",
identifier: "someone",
uriGetter(identifier) {
return new URL(`https://example.com/users/${identifier}`);
},
collectionCallbacks: {
dispatcher,
counter,
firstCursor,
lastCursor
},
onNotFound,
onNotAcceptable,
onUnauthorized
});
assertEquals(response.status, 200);
assertEquals(response.headers.get("Content-Type"), "application/activity+json");
assertEquals(await response.json(), {
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/data-integrity/v1",
{
toot: "http://joinmastodon.org/ns#",
misskey: "https://misskey-hub.net/ns#",
fedibird: "http://fedibird.com/ns#",
ChatMessage: "http://litepub.social/ns#ChatMessage",
Emoji: "toot:Emoji",
Hashtag: "as:Hashtag",
sensitive: "as:sensitive",
votersCount: "toot:votersCount",
_misskey_quote: "misskey:_misskey_quote",
quoteUri: "fedibird:quoteUri",
quoteUrl: "as:quoteUrl",
emojiReactions: {
"@id": "fedibird:emojiReactions",
"@type": "@id"
}
}
],
id: "https://example.com/users/someone?cursor=0",
type: "OrderedCollectionPage",
partOf: "https://example.com/",
next: "https://example.com/?cursor=1",
orderedItems: [{
"@context": createCtx,
id: "https://example.com/activities/1",
type: "Create"
}]
});
assertEquals(onNotFoundCalled, null);
assertEquals(onNotAcceptableCalled, null);
assertEquals(onUnauthorizedCalled, null);
url = new URL("https://example.com/?cursor=2");
context = createRequestContext({
...context,
url,
request: new Request(url, { headers: { Accept: "application/activity+json" } })
});
response = await handleCollection(context.request, {
context,
name: "collection",
identifier: "someone",
uriGetter(identifier) {
return new URL(`https://example.com/users/${identifier}`);
},
collectionCallbacks: {
dispatcher,
counter,
firstCursor,
lastCursor
},
onNotFound,
onNotAcceptable,
onUnauthorized
});
assertEquals(response.status, 200);
assertEquals(response.headers.get("Content-Type"), "application/activity+json");
assertEquals(await response.json(), {
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/data-integrity/v1",
{
toot: "http://joinmastodon.org/ns#",
misskey: "https://misskey-hub.net/ns#",
fedibird: "http://fedibird.com/ns#",
ChatMessage: "http://litepub.social/ns#ChatMessage",
Emoji: "toot:Emoji",
Hashtag: "as:Hashtag",
sensitive: "as:sensitive",
votersCount: "toot:votersCount",
_misskey_quote: "misskey:_misskey_quote",
quoteUri: "fedibird:quoteUri",
quoteUrl: "as:quoteUrl",
emojiReactions: {
"@id": "fedibird:emojiReactions",
"@type": "@id"
}
}
],
id: "https://example.com/users/someone?cursor=2",
type: "OrderedCollectionPage",
partOf: "https://example.com/",
prev: "https://example.com/?cursor=1",
orderedItems: [{
"@context": createCtx,
id: "https://example.com/activities/3",
type: "Create"
}]
});
assertEquals(onNotFoundCalled, null);
assertEquals(onNotAcceptableCalled, null);
assertEquals(onUnauthorizedCalled, null);
});
test("handleInbox()", async () => {
const activity = new Create({
id: new URL("https://example.com/activities/1"),
actor: new URL("https://example.com/person2"),
object: new Note({
id: new URL("https://example.com/notes/1"),
attribution: new URL("https://example.com/person2"),
content: "Hello, world!"
})
});
const unsignedRequest = new Request("https://example.com/", {
method: "POST",
body: JSON.stringify(await activity.toJsonLd())
});
const federation = createFederation({ kv: new MemoryKvStore() });
const unsignedContext = createRequestContext({
federation,
request: unsignedRequest,
url: new URL(unsignedRequest.url),
data: void 0
});
let onNotFoundCalled = null;
const onNotFound = (request) => {
onNotFoundCalled = request;
return new Response("Not found", { status: 404 });
};
const actorDispatcher = (_ctx, identifier) => {
if (identifier !== "someone") return null;
return new Person({ name: "Someone" });
};
const inboxOptions = {
kv: new MemoryKvStore(),
kvPrefixes: {
activityIdempotence: ["_fedify", "activityIdempotence"],
publicKey: ["_fedify", "publicKey"]
},
actorDispatcher,
onNotFound,
signatureTimeWindow: { minutes: 5 },
skipSignatureVerification: false
};
let response = await handleInbox(unsignedRequest, {
recipient: null,
context: unsignedContext,
inboxContextFactory(_activity) {
return createInboxContext({
...unsignedContext,
clone: void 0
});
},
...inboxOptions,
actorDispatcher: void 0
});
assertEquals(onNotFoundCalled, unsignedRequest);
assertEquals(response.status, 404);
onNotFoundCalled = null;
response = await handleInbox(unsignedRequest, {
recipient: "nobody",
context: unsignedContext,
inboxContextFactory(_activity) {
return createInboxContext({
...unsignedContext,
clone: void 0,
recipient: "nobody"
});
},
...inboxOptions
});
assertEquals(onNotFoundCalled, unsignedRequest);
assertEquals(response.status, 404);
onNotFoundCalled = null;
response = await handleInbox(unsignedRequest, {
recipient: null,
context: unsignedContext,
inboxContextFactory(_activity) {
return createInboxContext({
...unsignedContext,
clone: void 0
});
},
...inboxOptions
});
assertEquals(onNotFoundCalled, null);
assertEquals(response.status, 401);
response = await handleInbox(unsignedRequest, {
recipient: "someone",
context: unsignedContext,
inboxContextFactory(_activity) {
return createInboxContext({
...unsignedContext,
clone: void 0,
recipient: "someone"
});
},
...inboxOptions
});
assertEquals(onNotFoundCalled, null);
assertEquals(response.status, 401);
onNotFoundCalled = null;
const signedRequest = await signRequest(unsignedRequest.clone(), rsaPrivateKey3, rsaPublicKey3.id);
const signedContext = createRequestContext({
federation,
request: signedRequest,
url: new URL(signedRequest.url),
data: void 0,
documentLoader: mockDocumentLoader
});
response = await handleInbox(signedRequest, {
recipient: null,
context: signedContext,
inboxContextFactory(_activity) {
return createInboxContext({
...unsignedContext,
clone: void 0
});
},
...inboxOptions
});
assertEquals(onNotFoundCalled, null);
assertEquals([response.status, await response.text()], [202, ""]);
response = await handleInbox(signedRequest, {
recipient: "someone",
context: signedContext,
inboxContextFactory(_activity) {
return createInboxContext({
...unsignedContext,
clone: void 0,
recipient: "someone"
});
},
...inboxOptions
});
assertEquals(onNotFoundCalled, null);
assertEquals([response.status, await response.text()], [202, ""]);
response = await handleInbox(unsignedRequest, {
recipient: null,
context: unsignedContext,
inboxContextFactory(_activity) {
return createInboxContext({
...unsignedContext,
clone: void 0
});
},
...inboxOptions,
skipSignatureVerification: true
});
assertEquals(onNotFoundCalled, null);
assertEquals(response.status, 202);
response = await handleInbox(unsignedRequest, {
recipient: "someone",
context: unsignedContext,
inboxContextFactory(_activity) {
return createInboxContext({
...unsignedContext,
clone: void 0,
recipient: "someone"
});
},
...inboxOptions,
skipSignatureVerification: true
});
assertEquals(onNotFoundCalled, null);
assertEquals(response.status, 202);
const invalidRequest = new Request("https://example.com/", {
method: "POST",
body: JSON.stringify({
"@context": [
"https://www.w3.org/ns/activitystreams",
true,
23
],
type: "Create",
object: {
type: "Note",
content: "Hello, world!"
},
actor: "https://example.com/users/alice"
})
});
const signedInvalidRequest = await signRequest(invalidRequest, rsaPrivateKey3, rsaPublicKey3.id);
const signedInvalidContext = createRequestContext({
federation,
request: signedInvalidRequest,
url: new URL(signedInvalidRequest.url),
data: void 0,
documentLoader: mockDocumentLoader
});
response = await handleInbox(signedInvalidRequest, {
recipient: null,
context: signedContext,
inboxContextFactory(_activity) {
return createInboxContext({
...signedInvalidContext,
clone: void 0
});
},
...inboxOptions
});
assertEquals(onNotFoundCalled, null);
assertEquals(response.status, 400);
});
test("respondWithObject()", async () => {
const response = await respondWithObject(new Note({
id: new URL("https://example.com/notes/1"),
content: "Hello, world!"
}), { contextLoader: mockDocumentLoader });
assert(response.ok);
assertEquals(response.headers.get("Content-Type"), "application/activity+json");
assertEquals(await response.json(), {
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/data-integrity/v1",
{
Emoji: "toot:Emoji",
Hashtag: "as:Hashtag",
sensitive: "as:sensitive",
toot: "http://joinmastodon.org/ns#",
_misskey_quote: "misskey:_misskey_quote",
fedibird: "http://fedibird.com/ns#",
misskey: "https://misskey-hub.net/ns#",
quoteUri: "fedibird:quoteUri",
quoteUrl: "as:quoteUrl",
emojiReactions: {
"@id": "fedibird:emojiReactions",
"@type": "@id"
}
}
],
id: "https://example.com/notes/1",
type: "Note",
content: "Hello, world!"
});
});
test("respondWithObjectIfAcceptable", async () => {
let request = new Request("https://example.com/", { headers: { Accept: "application/activity+json" } });
let response = await respondWithObjectIfAcceptable(new Note({
id: new URL("https://example.com/notes/1"),
content: "Hello, world!"
}), request, { contextLoader: mockDocumentLoader });
assert(response != null);
assert(response.ok);
assertEquals(response.headers.get("Content-Type"), "application/activity+json");
assertEquals(await response.json(), {
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/data-integrity/v1",
{
Emoji: "toot:Emoji",
Hashtag: "as:Hashtag",
sensitive: "as:sensitive",
toot: "http://joinmastodon.org/ns#",
_misskey_quote: "misskey:_misskey_quote",
fedibird: "http://fedibird.com/ns#",
misskey: "https://misskey-hub.net/ns#",
quoteUri: "fedibird:quoteUri",
quoteUrl: "as:quoteUrl",
emojiReactions: {
"@id": "fedibird:emojiReactions",
"@type": "@id"
}
}
],
id: "https://example.com/notes/1",
type: "Note",
content: "Hello, world!"
});
request = new Request("https://example.com/", { headers: { Accept: "text/html" } });
response = await respondWithObjectIfAcceptable(new Note({
id: new URL("https://example.com/notes/1"),
content: "Hello, world!"
}), request, { contextLoader: mockDocumentLoader });
assertEquals(response, null);
});
//#endregion