Loading federation/handler.ts +20 −0 Original line number Diff line number Diff line Loading @@ -186,6 +186,8 @@ export async function handleOutbox<TContextData>( export interface InboxHandlerParameters<TContextData> { handle: string; context: RequestContext<TContextData>; kv: Deno.Kv; kvPrefix: Deno.KvKey; actorDispatcher?: ActorDispatcher<TContextData>; inboxListeners: Map< new (...args: unknown[]) => Activity, Loading @@ -201,6 +203,8 @@ export async function handleInbox<TContextData>( { handle, context, kv, kvPrefix, actorDispatcher, inboxListeners, inboxErrorHandler, Loading Loading @@ -250,6 +254,19 @@ export async function handleInbox<TContextData>( headers: { "Content-Type": "text/plain; charset=utf-8" }, }); } const cacheKey = activity.id == null ? null : [...kvPrefix, activity.id.href]; if (cacheKey != null) { const cached = await kv.get(cacheKey); if (cached != null && cached.value === true) { return new Response( `Activity <${activity.id}> has already been processed.`, { status: 202, headers: { "Content-Type": "text/plain; charset=utf-8" }, }, ); } } if (activity.actorId == null) { const response = new Response("Missing actor.", { status: 400, Loading Loading @@ -281,6 +298,9 @@ export async function handleInbox<TContextData>( const listener = inboxListeners.get(cls)!; const promise = listener(context, activity); if (promise instanceof Promise) await promise; if (cacheKey != null) { await kv.set(cacheKey, true, { expireIn: 1000 * 60 * 60 * 24 }); } return new Response("", { status: 202, headers: { "Content-Type": "text/plain; charset=utf-8" }, Loading federation/middleware.ts +31 −1 Original line number Diff line number Diff line Loading @@ -26,10 +26,27 @@ import { extractInboxes, sendActivity } from "./send.ts"; */ export interface FederationParameters { kv: Deno.Kv; kvPrefixes?: Partial<FederationKvPrefixes>; documentLoader?: DocumentLoader; treatHttps?: boolean; } /** * Prefixes for namespacing keys in the Deno KV store. */ export interface FederationKvPrefixes { /** * The key prefix used for storing whether activities have already been * processed or not. */ activityIdempotence: Deno.KvKey; /** * The key prefix used for storing remote JSON-LD documents. */ remoteDocument: Deno.KvKey; } /** * An object that registers federation-related business logic and dispatches * requests to the appropriate handlers. Loading @@ -39,6 +56,7 @@ export interface FederationParameters { */ export class Federation<TContextData> { #kv: Deno.Kv; #kvPrefixes: FederationKvPrefixes; #router: Router; #actorCallbacks?: ActorCallbacks<TContextData>; #outboxCallbacks?: OutboxCallbacks<TContextData>; Loading @@ -54,14 +72,24 @@ export class Federation<TContextData> { * Create a new {@link Federation} instance. * @param parameters Parameters for initializing the instance. */ constructor({ kv, documentLoader, treatHttps }: FederationParameters) { constructor( { kv, kvPrefixes, documentLoader, treatHttps }: FederationParameters, ) { this.#kv = kv; this.#kvPrefixes = { ...({ activityIdempotence: ["_fedify", "activityIdempotence"], remoteDocument: ["_fedify", "remoteDocument"], } satisfies FederationKvPrefixes), ...(kvPrefixes ?? {}), }; this.#router = new Router(); this.#router.add("/.well-known/webfinger", "webfinger"); this.#inboxListeners = new Map(); this.#documentLoader = documentLoader ?? kvCache({ loader: fetchDocumentLoader, kv: kv, prefix: this.#kvPrefixes.remoteDocument, }); this.#treatHttps = treatHttps ?? false; Loading Loading @@ -400,6 +428,8 @@ export class Federation<TContextData> { return await handleInbox(request, { handle: route.values.handle, context, kv: this.#kv, kvPrefix: this.#kvPrefixes.activityIdempotence, documentLoader: this.#documentLoader, actorDispatcher: this.#actorCallbacks?.dispatcher, inboxListeners: this.#inboxListeners, Loading Loading
federation/handler.ts +20 −0 Original line number Diff line number Diff line Loading @@ -186,6 +186,8 @@ export async function handleOutbox<TContextData>( export interface InboxHandlerParameters<TContextData> { handle: string; context: RequestContext<TContextData>; kv: Deno.Kv; kvPrefix: Deno.KvKey; actorDispatcher?: ActorDispatcher<TContextData>; inboxListeners: Map< new (...args: unknown[]) => Activity, Loading @@ -201,6 +203,8 @@ export async function handleInbox<TContextData>( { handle, context, kv, kvPrefix, actorDispatcher, inboxListeners, inboxErrorHandler, Loading Loading @@ -250,6 +254,19 @@ export async function handleInbox<TContextData>( headers: { "Content-Type": "text/plain; charset=utf-8" }, }); } const cacheKey = activity.id == null ? null : [...kvPrefix, activity.id.href]; if (cacheKey != null) { const cached = await kv.get(cacheKey); if (cached != null && cached.value === true) { return new Response( `Activity <${activity.id}> has already been processed.`, { status: 202, headers: { "Content-Type": "text/plain; charset=utf-8" }, }, ); } } if (activity.actorId == null) { const response = new Response("Missing actor.", { status: 400, Loading Loading @@ -281,6 +298,9 @@ export async function handleInbox<TContextData>( const listener = inboxListeners.get(cls)!; const promise = listener(context, activity); if (promise instanceof Promise) await promise; if (cacheKey != null) { await kv.set(cacheKey, true, { expireIn: 1000 * 60 * 60 * 24 }); } return new Response("", { status: 202, headers: { "Content-Type": "text/plain; charset=utf-8" }, Loading
federation/middleware.ts +31 −1 Original line number Diff line number Diff line Loading @@ -26,10 +26,27 @@ import { extractInboxes, sendActivity } from "./send.ts"; */ export interface FederationParameters { kv: Deno.Kv; kvPrefixes?: Partial<FederationKvPrefixes>; documentLoader?: DocumentLoader; treatHttps?: boolean; } /** * Prefixes for namespacing keys in the Deno KV store. */ export interface FederationKvPrefixes { /** * The key prefix used for storing whether activities have already been * processed or not. */ activityIdempotence: Deno.KvKey; /** * The key prefix used for storing remote JSON-LD documents. */ remoteDocument: Deno.KvKey; } /** * An object that registers federation-related business logic and dispatches * requests to the appropriate handlers. Loading @@ -39,6 +56,7 @@ export interface FederationParameters { */ export class Federation<TContextData> { #kv: Deno.Kv; #kvPrefixes: FederationKvPrefixes; #router: Router; #actorCallbacks?: ActorCallbacks<TContextData>; #outboxCallbacks?: OutboxCallbacks<TContextData>; Loading @@ -54,14 +72,24 @@ export class Federation<TContextData> { * Create a new {@link Federation} instance. * @param parameters Parameters for initializing the instance. */ constructor({ kv, documentLoader, treatHttps }: FederationParameters) { constructor( { kv, kvPrefixes, documentLoader, treatHttps }: FederationParameters, ) { this.#kv = kv; this.#kvPrefixes = { ...({ activityIdempotence: ["_fedify", "activityIdempotence"], remoteDocument: ["_fedify", "remoteDocument"], } satisfies FederationKvPrefixes), ...(kvPrefixes ?? {}), }; this.#router = new Router(); this.#router.add("/.well-known/webfinger", "webfinger"); this.#inboxListeners = new Map(); this.#documentLoader = documentLoader ?? kvCache({ loader: fetchDocumentLoader, kv: kv, prefix: this.#kvPrefixes.remoteDocument, }); this.#treatHttps = treatHttps ?? false; Loading Loading @@ -400,6 +428,8 @@ export class Federation<TContextData> { return await handleInbox(request, { handle: route.values.handle, context, kv: this.#kv, kvPrefix: this.#kvPrefixes.activityIdempotence, documentLoader: this.#documentLoader, actorDispatcher: this.#actorCallbacks?.dispatcher, inboxListeners: this.#inboxListeners, Loading