@fedify/fedify
Version:
An ActivityPub server framework
1,613 lines • 132 kB
JavaScript
import { Temporal } from "@js-temporal/polyfill";
import "urlpattern-polyfill";
globalThis.addEventListener = () => {};
import { n as createOutboxContext, r as createRequestContext, t as createInboxContext } from "../context-Dk_tacqz.mjs";
import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
import "../std__assert-CRDpx_HF.mjs";
import { t as assertRejects } from "../assert_rejects-B-qJtC9Z.mjs";
import { t as assertInstanceOf } from "../assert_instance_of-C4Ri6VuN.mjs";
import { t as assert } from "../assert-DikXweDx.mjs";
import { r as parseAcceptSignature } from "../accept-CPkZzmGN.mjs";
import { s as signRequest } from "../http-C_edJspG.mjs";
import { a as rsaPrivateKey3, c as rsaPublicKey3, s as rsaPublicKey2 } from "../keys-DGu1NFwu.mjs";
import { a as compactJsonLd, p as signJsonLd } from "../ld-tusP_XxG.mjs";
import { t as MemoryKvStore } from "../kv-rV3vodCc.mjs";
import { c as handleActor, d as handleInbox, f as handleObject, h as respondWithObjectIfAcceptable, l as handleCollection, m as respondWithObject, o as createFederation, p as handleOutbox, u as handleCustomCollection } from "../middleware-D9k0Knum.mjs";
import { t as ActivityListenerSet } from "../activity-listener-ell7W1s9.mjs";
import { createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
import { Activity, Create, Note, Person, Tombstone } from "@fedify/vocab";
import { FetchError } from "@fedify/vocab-runtime";
//#region src/federation/handler.test.ts
const QUOTE_CONTEXT_TERMS = {
QuoteAuthorization: "https://w3id.org/fep/044f#QuoteAuthorization",
quote: {
"@id": "https://w3id.org/fep/044f#quote",
"@type": "@id"
},
quoteAuthorization: {
"@id": "https://w3id.org/fep/044f#quoteAuthorization",
"@type": "@id"
}
};
const WRAPPER_QUOTE_CONTEXT_TERMS = {
...QUOTE_CONTEXT_TERMS,
QuoteRequest: "https://w3id.org/fep/044f#QuoteRequest"
};
test("handleActor()", async () => {
const federation = createFederation({ kv: new MemoryKvStore() });
const deletedAt = Temporal.Instant.from("2024-01-15T00:00:00Z");
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, identifier) => {
if (identifier !== "someone") return null;
return new Person({
id: ctx.getActorUri(identifier),
name: "Someone"
});
};
const tombstoneDispatcher = (ctx, identifier) => {
if (identifier !== "gone") return null;
return new Tombstone({
id: ctx.getActorUri(identifier),
formerType: Person,
deleted: deletedAt
});
};
let onNotFoundCalled = null;
const onNotFound = (request) => {
onNotFoundCalled = request;
return new Response("Not found", { status: 404 });
};
let onUnauthorizedCalled = null;
const onUnauthorized = (request) => {
onUnauthorizedCalled = request;
return new Response("Unauthorized", { status: 401 });
};
let response = await handleActor(context.request, {
context,
identifier: "someone",
onNotFound,
onUnauthorized
});
assertEquals(response.status, 404);
assertEquals(onNotFoundCalled, context.request);
assertEquals(onUnauthorizedCalled, null);
onNotFoundCalled = null;
response = await handleActor(context.request, {
context,
identifier: "no-one",
actorDispatcher,
onNotFound,
onUnauthorized
});
assertEquals(response.status, 404);
assertEquals(onNotFoundCalled, context.request);
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,
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",
"https://gotosocial.org/ns",
{
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(onUnauthorizedCalled, null);
response = await handleActor(context.request, {
context,
identifier: "no-one",
actorDispatcher,
onNotFound,
onUnauthorized
});
assertEquals(response.status, 404);
assertEquals(onNotFoundCalled, context.request);
assertEquals(onUnauthorizedCalled, null);
onNotFoundCalled = null;
response = await handleActor(context.request, {
context,
identifier: "someone",
actorDispatcher,
authorizePredicate: async (ctx, _handle) => await ctx.getSignedKey() != null && await ctx.getSignedKeyOwner() != null,
onNotFound,
onUnauthorized
});
assertEquals(response.status, 401);
assertEquals(onNotFoundCalled, 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: async (ctx, _handle) => await ctx.getSignedKey() != null && await ctx.getSignedKeyOwner() != null,
onNotFound,
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",
"https://gotosocial.org/ns",
{
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(onUnauthorizedCalled, null);
onNotFoundCalled = null;
response = await handleActor(context.request, {
context,
identifier: "gone",
actorDispatcher: tombstoneDispatcher,
authorizePredicate: () => false,
onNotFound,
onUnauthorized
});
assertEquals(response.status, 401);
assertEquals(onNotFoundCalled, null);
assertEquals(onUnauthorizedCalled, context.request);
onUnauthorizedCalled = null;
response = await handleActor(context.request, {
context,
identifier: "gone",
actorDispatcher: tombstoneDispatcher,
authorizePredicate: () => true,
onNotFound,
onUnauthorized
});
assertEquals(response.status, 410);
assertEquals(response.headers.get("Content-Type"), "application/activity+json");
assertEquals(response.headers.get("Vary"), "Accept");
assertEquals(await response.json(), {
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/data-integrity/v1",
"https://gotosocial.org/ns"
],
id: "https://example.com/users/gone",
type: "Tombstone",
formerType: "as:Person",
deleted: "2024-01-15T00:00:00Z"
});
assertEquals(onNotFoundCalled, null);
assertEquals(onUnauthorizedCalled, null);
});
test("handleObject()", async () => {
let context = createRequestContext({
federation: createFederation({ kv: new MemoryKvStore() }),
data: void 0,
url: new URL("https://example.com/"),
getObjectUri(_cls, values) {
return new URL(`https://example.com/users/${values.identifier}/notes/${values.id}`);
}
});
const objectDispatcher = (ctx, values) => {
if (values.identifier !== "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 onUnauthorizedCalled = null;
const onUnauthorized = (request) => {
onUnauthorizedCalled = request;
return new Response("Unauthorized", { status: 401 });
};
let response = await handleObject(context.request, {
context,
values: {
identifier: "someone",
id: "123"
},
onNotFound,
onUnauthorized
});
assertEquals(response.status, 404);
assertEquals(onNotFoundCalled, context.request);
assertEquals(onUnauthorizedCalled, null);
onNotFoundCalled = null;
response = await handleObject(context.request, {
context,
values: {
identifier: "someone",
id: "123"
},
objectDispatcher,
onNotFound,
onUnauthorized
});
assertEquals(response.status, 200);
assertEquals(onNotFoundCalled, null);
assertEquals(onUnauthorizedCalled, null);
response = await handleObject(context.request, {
context,
values: {
identifier: "no-one",
id: "123"
},
objectDispatcher,
onNotFound,
onUnauthorized
});
assertEquals(response.status, 404);
assertEquals(onNotFoundCalled, context.request);
assertEquals(onUnauthorizedCalled, null);
onNotFoundCalled = null;
response = await handleObject(context.request, {
context,
values: {
identifier: "someone",
id: "not-exist"
},
objectDispatcher,
onNotFound,
onUnauthorized
});
assertEquals(response.status, 404);
assertEquals(onNotFoundCalled, context.request);
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: {
identifier: "someone",
id: "123"
},
objectDispatcher,
onNotFound,
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",
"https://gotosocial.org/ns",
{
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#",
...QUOTE_CONTEXT_TERMS,
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(onUnauthorizedCalled, null);
response = await handleObject(context.request, {
context,
values: {
identifier: "no-one",
id: "123"
},
objectDispatcher,
onNotFound,
onUnauthorized
});
assertEquals(response.status, 404);
assertEquals(onNotFoundCalled, context.request);
assertEquals(onUnauthorizedCalled, null);
onNotFoundCalled = null;
response = await handleObject(context.request, {
context,
values: {
identifier: "someone",
id: "not-exist"
},
objectDispatcher,
onNotFound,
onUnauthorized
});
assertEquals(response.status, 404);
assertEquals(onNotFoundCalled, context.request);
assertEquals(onUnauthorizedCalled, null);
onNotFoundCalled = null;
response = await handleObject(context.request, {
context,
values: {
identifier: "someone",
id: "123"
},
objectDispatcher,
authorizePredicate: async (ctx, _values) => await ctx.getSignedKey() != null && await ctx.getSignedKeyOwner() != null,
onNotFound,
onUnauthorized
});
assertEquals(response.status, 401);
assertEquals(onNotFoundCalled, 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: {
identifier: "someone",
id: "123"
},
objectDispatcher,
authorizePredicate: async (ctx, _values) => await ctx.getSignedKey() != null && await ctx.getSignedKeyOwner() != null,
onNotFound,
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",
"https://gotosocial.org/ns",
{
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#",
...QUOTE_CONTEXT_TERMS,
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(onUnauthorizedCalled, null);
});
test("handleCollection()", async () => {
let context = createRequestContext({
federation: createFederation({ kv: new MemoryKvStore() }),
data: void 0,
url: new URL("https://example.com/"),
getActorUri(identifier) {
return new URL(`https://example.com/users/${identifier}`);
}
});
const dispatcher = (_ctx, identifier, cursor) => {
if (identifier !== "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, identifier) => identifier === "someone" ? 3 : null;
const firstCursor = (_ctx, identifier) => identifier === "someone" ? "0" : null;
const lastCursor = (_ctx, identifier) => identifier === "someone" ? "2" : null;
let onNotFoundCalled = null;
const onNotFound = (request) => {
onNotFoundCalled = request;
return new Response("Not found", { status: 404 });
};
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,
onUnauthorized
});
assertEquals(response.status, 404);
assertEquals(onNotFoundCalled, context.request);
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,
onUnauthorized
});
assertEquals(response.status, 200);
assertEquals(onNotFoundCalled, null);
assertEquals(onUnauthorizedCalled, 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,
onUnauthorized
});
assertEquals(response.status, 404);
assertEquals(onNotFoundCalled, context.request);
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,
onUnauthorized
});
assertEquals(response.status, 404);
assertEquals(onNotFoundCalled, context.request);
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,
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",
"https://gotosocial.org/ns",
{
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",
...WRAPPER_QUOTE_CONTEXT_TERMS,
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",
"https://gotosocial.org/ns",
{
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",
...WRAPPER_QUOTE_CONTEXT_TERMS,
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(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: async (ctx, _handle) => await ctx.getSignedKey() != null && await ctx.getSignedKeyOwner() != null
},
onNotFound,
onUnauthorized
});
assertEquals(response.status, 401);
assertEquals(onNotFoundCalled, 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: async (ctx, _handle) => await ctx.getSignedKey() != null && await ctx.getSignedKeyOwner() != null
},
onNotFound,
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",
"https://gotosocial.org/ns",
{
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",
...WRAPPER_QUOTE_CONTEXT_TERMS,
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(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,
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",
"https://gotosocial.org/ns",
{
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",
...WRAPPER_QUOTE_CONTEXT_TERMS,
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(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,
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",
"https://gotosocial.org/ns",
{
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",
...WRAPPER_QUOTE_CONTEXT_TERMS,
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(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,
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",
"https://gotosocial.org/ns",
{
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",
...WRAPPER_QUOTE_CONTEXT_TERMS,
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(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 restrictiveContextLoader = async (resource) => {
const url = new URL(resource).href;
if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
throw new Error(`Unexpected context: ${url}`);
};
const inboxOptions = {
kv: new MemoryKvStore(),
kvPrefixes: {
activityIdempotence: ["_fedify", "activityIdempotence"],
publicKey: ["_fedify", "publicKey"],
acceptSignatureNonce: ["_fedify", "acceptSignatureNonce"]
},
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);
const malformedProofCreatedRequest = new Request("https://example.com/", {
method: "POST",
body: JSON.stringify({
"@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/data-integrity/v1"],
id: "https://example.com/activities/invalid-proof-created",
type: "Create",
actor: "https://example.com/person2",
object: {
id: "https://example.com/notes/invalid-proof-created",
type: "Note",
attributedTo: "https://example.com/person2",
content: "Hello, world!"
},
proof: {
type: "DataIntegrityProof",
cryptosuite: "eddsa-jcs-2022",
verificationMethod: "https://example.com/person2#main-key",
proofPurpose: "assertionMethod",
created: { "@value": "not-a-date" },
proofValue: "zLaewdp4H9kqtwyrLatK4cjY5oRHwVcw4gibPSUDYDMhi4M49v8pcYk3ZB6D69dNpAPbUmY8ocuJ3m9KhKJEEg7z"
}
})
});
const malformedProofCreatedContext = createRequestContext({
federation,
request: malformedProofCreatedRequest,
url: new URL(malformedProofCreatedRequest.url),
data: void 0,
documentLoader: mockDocumentLoader,
contextLoader: mockDocumentLoader
});
response = await handleInbox(malformedProofCreatedRequest, {
recipient: null,
context: malformedProofCreatedContext,
inboxContextFactory(_activity) {
return createInboxContext({
...malformedProofCreatedContext,
clone: void 0
});
},
...inboxOptions
});
assertEquals([response.status, await response.text()], [400, "Invalid activity."]);
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, ""]);
const ldSignedRequest = new Request("https://example.com/", {
method: "POST",
body: JSON.stringify(await signJsonLd({
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/identity/v1",
"https://w3id.org/security/v1",
"https://w3id.org/security/data-integrity/v1"
],
id: "https://example.com/activities/ld-signed",
type: "Create",
actor: "https://example.com/person2",
object: {
id: "https://example.com/notes/ld-signed",
type: "Note",
attributedTo: "https://example.com/person2",
content: "Hello, world!"
}
}, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: mockDocumentLoader }))
});
const ldSignedContext = createRequestContext({
federation,
request: ldSignedRequest,
url: new URL(ldSignedRequest.url),
data: void 0,
documentLoader: mockDocumentLoader,
contextLoader: restrictiveContextLoader
});
response = await handleInbox(ldSignedRequest, {
recipient: null,
context: ldSignedContext,
inboxContextFactory(_activity) {
return createInboxContext({
...ldSignedContext,
clone: void 0
});
},
...inboxOptions
});
assertEquals(onNotFoundCalled, null);
assertEquals([response.status, await response.text()], [202, ""]);
const remoteContextUrl = "https://remote.example/contexts/ext";
let failRemoteContextOnce = true;
const flakyContextLoader = async (resource) => {
const url = new URL(resource).href;
if (url === remoteContextUrl) {
if (failRemoteContextOnce) {
failRemoteContextOnce = false;
throw new Error(`Unexpected context: ${url}`);
}
return {
contextUrl: null,
documentUrl: url,
document: { "@context": { ext: "https://example.com/ext" } }
};
}
return await mockDocumentLoader(url);
};
const httpSignedLdBody = {
"@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
id: "https://example.com/activities/http-signed-ld",
type: "Create",
actor: "https://example.com/person2",
ext: "preserve-me",
object: {
id: "https://example.com/notes/http-signed-ld",
type: "Note",
attributedTo: "https://example.com/person2",
content: "Hello, world!"
},
signature: {
type: "RsaSignature2017",
creator: rsaPublicKey3.id.href,
created: "2024-01-01T00:00:00Z",
signatureValue: "bogus"
}
};
const httpSignedLdRequest = await signRequest(new Request("https://example.com/", {
method: "POST",
body: JSON.stringify(httpSignedLdBody)
}), rsaPrivateKey3, rsaPublicKey3.id);
const httpSignedLdContext = createRequestContext({
federation,
request: httpSignedLdRequest,
url: new URL(httpSignedLdRequest.url),
data: void 0,
documentLoader: mockDocumentLoader,
contextLoader: flakyContextLoader
});
response = await handleInbox(httpSignedLdRequest, {
recipient: null,
context: httpSignedLdContext,
inboxContextFactory(_activity) {
return createInboxContext({
...httpSignedLdContext,
clone: void 0
});
},
...inboxOptions
});
assertEquals(onNotFoundCalled, null);
assertEquals([response.status, await response.text()], [202, ""]);
const ldSignedOnlyBody = await signJsonLd({
"@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
id: "https://example.com/activities/ld-only-transient",
type: "Create",
actor: "https://example.com/person2",
ext: "preserve-me",
object: {
id: "https://example.com/notes/ld-only-transient",
type: "Note",
attributedTo: "https://example.com/person2",
content: "Hello, world!"
}
}, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: async (resource) => {
const url = new URL(resource).href;
if (url === remoteContextUrl) return {
contextUrl: null,
documentUrl: url,
document: { "@context": { ext: "https://example.com/ext" } }
};
return await mockDocumentLoader(url);
} });
const malformedTemporalLdSignedBody = await signJsonLd({
"@context": "https://www.w3.org/ns/activitystreams",
id: "https://example.com/activities/ld-only-invalid-published",
type: "Create",
actor: "https://example.com/person2",
published: { "@value": "not-a-date" },
object: {
id: "https://example.com/notes/ld-only-invalid-published",
type: "Note",
attributedTo: "https://example.com/person2",
content: "Hello, world!"
}
}, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: mockDocumentLoader });
const malformedTemporalLdSignedRequest = new Request("https://example.com/", {
method: "POST",
body: JSON.stringify(malformedTemporalLdSignedBody)
});
const malformedTemporalLdSignedContext = createRequestContext({
federation,
request: malformedTemporalLdSignedRequest,
url: new URL(malformedTemporalLdSignedRequest.url),
data: void 0,
documentLoader: mockDocumentLoader,
contextLoader: mockDocumentLoader
});
response = await handleInbox(malformedTemporalLdSignedRequest, {
recipient: null,
context: malformedTemporalLdSignedContext,
inboxContextFactory(_activity) {
return createInboxContext({
...malformedTemporalLdSignedContext,
clone: void 0
});
},
...inboxOptions
});
assertEquals([response.status, await response.text()], [400, "Invalid activity."]);
const malformedClosedLdSignedBody = await signJsonLd({
"@context": "https://www.w3.org/ns/activitystreams",
id: "https://example.com/questions/ld-only-invalid-closed",
type: "Question",
closed: "2024-02-31T00:00:00Z"
}, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: mockDocumentLoader });
const malformedClosedLdSignedRequest = new Request("https://example.com/", {
method: "POST",
body: JSON.stringify(malformedClosedLdSignedBody)
});
const malformedClosedLdSignedContext = createRequestContext({
federation,
request: malformedClosedLdSignedRequest,
url: new URL(malformedClosedLdSignedRequest.url),
data: void 0,
documentLoader: mockDocumentLoader,
contextLoader: mockDocumentLoader
});
response = await handleInbox(malformedClosedLdSignedRequest, {
recipient: null,
context: malformedClosedLdSignedContext,
inboxContextFactory(_activity) {
return createInboxContext({
...malformedClosedLdSignedContext,
clone: void 0
});
},
...inboxOptions
});
assertEquals([response.status, await response.text()], [400, "Invalid activity."]);
const malformedIriHttpSignedRequest = await signRequest(new Request("https://example.com/", {
method: "POST",
body: JSON.stringify({
"@context": "https://www.w3.org/ns/activitystreams",
id: "http://[",
type: "Create",
actor: "https://example.com/person2",
object: {
id: "https://example.com/notes/http-signed-invalid-iri",
type: "Note",
attributedTo: "https://example.com/person2",
content: "Hello, world!"
}
})
}), rsaPrivateKey3, rsaPublicKey3.id);
const malformedIriHttpSignedContext = createRequestContext({
federation,
request: malformedIriHttpSignedRequest,
url: new URL(malformedIriHttpSignedRequest.url),
data: void 0,
documentLoader: mockDocumentLoader,
contextLoader: mockDocumentLoader
});
response = await handleInbox(malformedIriHttpSignedRequest, {
recipient: null,
context: malformedIriHttpSignedContext,
inboxContextFactory(_activity) {
return createInboxContext({
...malformedIriHttpSignedContext,
clone: void 0
});
},
...inboxOptions
});
assertEquals([response.status, await response.text()], [400, "Invalid activity."]);
const ldSignedOnlyRequest = new Request("https://example.com/", {
method: "POST",
body: JSON.stringify(ldSignedOnlyBody)
});
const ldSignedOnlyContext = createRequestContext({
federation,
request: ldSignedOnlyRequest,
url: new URL(ldSignedOnlyRequest.url),
data: void 0,
documentLoader: mockDocumentLoader,
contextLoader: async (resource) => {
const url = new URL(resource).href;
if (url === remoteContextUrl) throw new Error(`Unexpected context: ${url}`);
return await mockDocumentLoader(url);
}
});
await assertRejects(() => handleInbox(ldSignedOnlyRequest, {
recipient: null,
context: ldSignedOnlyContext,
inboxContextFactory(_activity) {
return createInboxContext({
...ldSignedOnlyContext,
clone: void 0
});
},
...inboxOptions
}), Error);
failRemoteContextOnce = true;
const invalidHttpFallbackRequest = new Request("https://example.com/", {
method: "POST",
body: JSON.stringify(ldSignedOnlyBody),
headers: { Signature: "bogus" }
});
const invalidHttpFallbackContext = createRequestContext({
federation,
request: invalidHttpFallbackRequest,
url: new URL(invalidHttpFallbackRequest.url),
data: void 0,
documentLoader: mockDocumentLoader,
contextLoader: flakyContextLoader
});
await assertRejects(() => handleInbox(invalidHttpFallbackRequest, {
recipient: null,
context: invalidHttpFallbackContext,
inboxContextFactory(_activity) {
return createInboxContext({
...invalidHttpFallbackContext,
clone: void 0
});
},
...inboxOptions
}), Error);
const transientKeyContextUrl = "https://remote.example/contexts/key";
const transientCreatorUrl = "https://remote.example/keys/transient#main-key";
const verificationFailureLdSignedBody = await signJsonLd({
"@context": "https://www.w3.org/ns/activitystreams",
id: "https://example.com/activities/ld-key-fetch-transient",
type: "Create",
actor: "https://example.com/person2",
object: {
id: "https://example.com/notes/ld-key-fetch-transient",
type: "Note",
attributedTo: "https://example.com/person2",
content: "Hello, world!"
}
}, rsaPrivateKey3, new URL(transientCreatorUrl), { contextLoader: mockDocumentLoader });
const verificationFailureLdSignedRequest = new Request("https://example.com/", {
method: "POST",
body: JSON.stringify(verificationFailureLdSignedBody),
headers: { Signature: "bogus" }
});
const verificationFailureLdSignedContext = createRequestContext({
federation,
request: verificationFailureLdSignedRequest,
url: new URL(verificationFailureLdSignedRequest.url),
data: void 0,
documentLoader: async (resource) => {
if (resource === transientCreatorUrl) return {
contextUrl: null,
documentUrl: resource,
document: {
"@context": [transientKeyContextUrl],
id: resource
}
};
return await mockDocumentLoader(new URL(resource).href);
},
contextLoader: async (resource) => {
if (resource === transientKeyContextUrl) throw new Error(`Transient key context failure: ${resource}`);
return await mockDocumentLoader(new URL(resource).href);
}
});
response = await handleInbox(verificationFailureLdSignedRequest, {
recipient: null,
context: verificationFailureLdSignedContext,
inboxContextFactory(_activity) {
return createInboxContext({
...verificationFailureLdSignedContext,
clone: void 0
});
},
...inboxOptions
});
assertEquals([response.status, await response.text()], [401, "Failed to verify the request signature."]);
failRemoteContextOnce = true;
const deferredMalformedTemporalLdSignedBody = await signJsonLd({
"@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
id: "https://example.com/activities/deferred-invalid-published",
type: "Create",
actor: "https://example.com/person2",
ext: "preserve-me",
published: { "@value": "not-a-date" },
object: {
id: "https://example.com/notes/deferred-invalid-published",
type: "Note",
attributedTo: "https://example.com/person2",
content: "Hello, world!"
}
}, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: async (resource) => {
const url = new URL(resource).href;
if (url === remoteContextUrl) return {
contextUrl: null,
documentUrl: url,
document: { "@context": { ext: "https://example.com/ext" } }
};
return await mockDocumentLoader(url);
} });
const deferredMalformedTemporalLdSignedRequest = new Request("https://example.com/", {
method: "POST",
body: JSON.stringify(deferredMalformedTemporalLdSignedBody),
headers: { Signature: "bogus" }
});
const deferredMalformedTemporalLdSignedContext = createRequestContext({
federation,
request: deferredMalformedTemporalLdSignedRequest,
url: new URL(deferredMalformedTemporalLdSignedRequest.url),
data: void 0,
documentLoader: mockDocumentLoader,
contextLoader: flakyContextLoader
});
response = await handleInbox(deferredMalformedTemporalLdSignedRequest, {
recipient: null,
context: deferredMalformedTemporalLdSignedContext,
inboxContextFactory(_activity) {
return createInboxContext({
...deferredMalformedTemporalLdSignedContext,
clone: void 0
});
},
...inboxOptions
});
assertEquals([response.status, await response.text()], [400, "Invalid activity."]);
const malformedLdSignedRequest = new Request("https://example.com/", {
method: "POST",
body: JSON.stringify({
...ldSignedOnlyBody,
"@context": ["not a url", "https://www.w3.org/ns/activitystreams"]
})
});
const malformedLdSignedContext = createRequestContext({
federation,
request: malformedLdSignedRequest,
url: new URL(malformedLdSignedRequest.url),
data: void 0,
documentLoader: mockDocumentLoader,
contextLoader: mockDocumentLoader
});
response = await handleInbox(malformedLdSignedRequest, {
recipient: null,
context: malformedLdSignedContext,
inboxContextFactory(_activity) {
return createInboxContext({
...malformedLdSignedContext,
clone: void 0
});
},
...inboxOptions
});
assertEquals([response.status, await response.text()], [400, "Invalid JSON-LD."]);
const dualSignedInvalidCreatorRequest = await signRequest(new Request("https://example.com/", {
method: "POST",
body: JSON.stringify({
...httpSignedLdBody,
signature: {
...httpSignedLdBody.signature,
creator: "not a url"
}
})
}), rsaPrivateKey3, rsaPublicKey3.id);
const dualSignedInvalidCreatorContext = createRequestContext({
federation,
request: dualSignedInvalidCreatorRequest,
url: new URL(dualSignedInvalidCreatorRequest.url),
data: void 0,
documentLoader: mockDocumentLoader,
contextLoader: flakyContextLoader
});
response = await handleInbox(dualSignedInvalidCreatorRequest, {
recipient: null,
context: dualSignedInvalidCreatorContext,
inboxContextFactory(_activity) {
return createInboxContext({
...dualSignedInvalidCreatorContext,
clone: void 0
});
},
...inboxOptions
});
assertEquals(onNotFoundCalled, null);
assertEquals([response.status, await response.text()], [202, ""]);
const invalidUrlHttpSignedRequest = await signRequest(new Request("https://example.com/", {
method: "POST",
body: JSON.stringify({
"@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
id: "https://example.com/activities/http-signed-invalid-context",
type: "Create",
actor: "https://example.com/person2",
ext: "preserve-me",
object: {
id: "https://example.com/notes/http-signed-invalid-context",
type: "Note",
attributedTo: "https://example.com/person2",
content: "Hello, world!"
}
})
}), rsaPrivateKey3, rsaPublicKey3.id);
const invalidUrlHttpSignedContext = createRequestContext({
federation,
request: invalidUrlHttpSignedRequest,
url: new URL(invalidUrlHttpSignedRequest.url),
data: void 0,
documentLoader: mockDocumentLoader,
contextLoader: async (resource) => {
const url = new URL(resource).href;
if (url === remoteContextUrl) {
const error = /* @__PURE__ */ new Error(`Transient remote context failure: ${url}`);
error.name = "jsonld.InvalidUrl";
error.details = {
code: "loading remote context failed",
url
};
throw error;
}
return await mockDocumentLoader(url);
}
});
await assertRejects(() => handleInbox(invalidUrlHttpSignedRequest, {
recipient: null,
context: invalidUrlHttpSignedContext,
inboxContextFactory(_activity) {
return createInboxContext({
...invalidUrlHttpSignedContext,
clone: void 0
});
},
...inboxOptions
}), Error);
const opaqueContextIdHttpSignedRequest = await signRequest(new Request("https://example.com/", {
method: "POST",
body: JSON.stringify({
"@context": ["app-context", "https://www.w3.org/ns/activitystreams"],
id: "https://example.com/activities/http-signed-opaque-context",
type: "Create",
actor: "https://example.com/person2",
object: {
id: "https://example.com/notes/http-signed-opaque-context",
type: "Note",
attributedTo: "https://example.com/person2",
content: "Hello, world!"
}
})
}), rsaPrivateKey3, rsaPublicKey3.id);
const opaqueContextIdHttpSignedContext = createRequestContext({
federation,
request: opaqueContextIdHttpSignedRequest,
url: new URL(opaqueContextIdHttpSignedRequest.url),
data: void 0,
documentLoader: mockDocumentLoader,
contextLoader: async (resource) => {
if (resource === "app-context") {
const error = /* @__PURE__ */ new Error(`Opaque context backend is unavailable: ${resource}`);
error.name = "jsonld.InvalidUrl";
error.details = {
code: "loading remote context failed",
url: resource
};
throw error;
}
return await