UNPKG

@fedify/fedify

Version:

An ActivityPub server framework

171 lines (167 loc) • 5.07 kB
import { Temporal } from "@js-temporal/polyfill"; import { URLPattern } from "urlpattern-polyfill"; globalThis.addEventListener = () => {}; import { deno_default } from "./docloader-C8mmLN-Y.js"; import { Activity } from "./vocab-dDpPQ0fF.js"; import { getTypeId } from "./type-D2s5lmbZ.js"; import { getLogger } from "@logtape/logtape"; import { SpanKind, SpanStatusCode, context, propagation, trace } from "@opentelemetry/api"; //#region federation/inbox.ts var InboxListenerSet = class InboxListenerSet { #listeners; constructor() { this.#listeners = /* @__PURE__ */ new Map(); } clone() { const clone = new InboxListenerSet(); clone.#listeners = new Map(this.#listeners); return clone; } add(type, listener) { if (this.#listeners.has(type)) throw new TypeError("Listener already set for this type."); this.#listeners.set(type, listener); } dispatchWithClass(activity) { let cls = activity.constructor; const inboxListeners = this.#listeners; if (inboxListeners == null) return null; while (true) { if (inboxListeners.has(cls)) break; if (cls === Activity) return null; cls = globalThis.Object.getPrototypeOf(cls); } const listener = inboxListeners.get(cls); return { class: cls, listener }; } dispatch(activity) { return this.dispatchWithClass(activity)?.listener ?? null; } }; async function routeActivity({ context: ctx, json, activity, recipient, inboxListeners, inboxContextFactory, inboxErrorHandler, kv, kvPrefixes, queue, span, tracerProvider }) { const logger = getLogger([ "fedify", "federation", "inbox" ]); const cacheKey = activity.id == null ? null : [ ...kvPrefixes.activityIdempotence, ctx.origin, activity.id.href ]; if (cacheKey != null) { const cached = await kv.get(cacheKey); if (cached === true) { logger.debug("Activity {activityId} has already been processed.", { activityId: activity.id?.href, activity: json, recipient }); span.setStatus({ code: SpanStatusCode.UNSET, message: `Activity ${activity.id?.href} has already been processed.` }); return "alreadyProcessed"; } } if (activity.actorId == null) { logger.error("Missing actor.", { activity: json }); span.setStatus({ code: SpanStatusCode.ERROR, message: "Missing actor." }); return "missingActor"; } span.setAttribute("activitypub.actor.id", activity.actorId.href); if (queue != null) { const carrier = {}; propagation.inject(context.active(), carrier); try { await queue.enqueue({ type: "inbox", id: crypto.randomUUID(), baseUrl: ctx.origin, activity: json, identifier: recipient, attempt: 0, started: (/* @__PURE__ */ new Date()).toISOString(), traceContext: carrier }); } catch (error) { logger.error("Failed to enqueue the incoming activity {activityId}:\n{error}", { error, activityId: activity.id?.href, activity: json, recipient }); span.setStatus({ code: SpanStatusCode.ERROR, message: `Failed to enqueue the incoming activity ${activity.id?.href}.` }); throw error; } logger.info("Activity {activityId} is enqueued.", { activityId: activity.id?.href, activity: json, recipient }); return "enqueued"; } tracerProvider = tracerProvider ?? trace.getTracerProvider(); const tracer = tracerProvider.getTracer(deno_default.name, deno_default.version); return await tracer.startActiveSpan("activitypub.dispatch_inbox_listener", { kind: SpanKind.INTERNAL }, async (span$1) => { const dispatched = inboxListeners?.dispatchWithClass(activity); if (dispatched == null) { logger.error("Unsupported activity type:\n{activity}", { activity: json, recipient }); span$1.setStatus({ code: SpanStatusCode.UNSET, message: `Unsupported activity type: ${getTypeId(activity).href}` }); span$1.end(); return "unsupportedActivity"; } const { class: cls, listener } = dispatched; span$1.updateName(`activitypub.dispatch_inbox_listener ${cls.name}`); try { await listener(inboxContextFactory(recipient, json, activity?.id?.href, getTypeId(activity).href), activity); } catch (error) { try { await inboxErrorHandler?.(ctx, error); } catch (error$1) { logger.error("An unexpected error occurred in inbox error handler:\n{error}", { error: error$1, activityId: activity.id?.href, activity: json, recipient }); } logger.error("Failed to process the incoming activity {activityId}:\n{error}", { error, activityId: activity.id?.href, activity: json, recipient }); span$1.setStatus({ code: SpanStatusCode.ERROR, message: String(error) }); span$1.end(); return "error"; } if (cacheKey != null) await kv.set(cacheKey, true, { ttl: Temporal.Duration.from({ days: 1 }) }); logger.info("Activity {activityId} has been processed.", { activityId: activity.id?.href, activity: json, recipient }); span$1.end(); return "success"; }); } //#endregion export { InboxListenerSet, routeActivity };