@fedify/fedify 2.3.0-dev.1137 → 2.3.0-dev.1150

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/dist/{builder-BCkBXxky.mjs → builder-Bjm1Jq9n.mjs} +2 -2
  2. package/dist/compat/mod.d.cts +1 -1
  3. package/dist/compat/mod.d.ts +1 -1
  4. package/dist/compat/transformers.test.mjs +1 -1
  5. package/dist/{context-DI2gRbyN.d.cts → context-CRXCkTM6.d.cts} +48 -6
  6. package/dist/{context-DCtsSHDv.d.ts → context-MgCh7YGu.d.ts} +48 -6
  7. package/dist/{deno-B_9yJW3w.mjs → deno-CKFE6Uya.mjs} +1 -1
  8. package/dist/{docloader-BT89tyFr.mjs → docloader-B-ZE1cZf.mjs} +2 -2
  9. package/dist/federation/builder.test.mjs +1 -1
  10. package/dist/federation/handler.test.mjs +1363 -44
  11. package/dist/federation/idempotency.test.mjs +2 -2
  12. package/dist/federation/metrics.test.mjs +60 -1
  13. package/dist/federation/middleware.test.mjs +1667 -163
  14. package/dist/federation/mod.cjs +1 -1
  15. package/dist/federation/mod.d.cts +2 -2
  16. package/dist/federation/mod.d.ts +2 -2
  17. package/dist/federation/mod.js +1 -1
  18. package/dist/federation/retry.test.mjs +1 -1
  19. package/dist/federation/send.test.mjs +8 -8
  20. package/dist/federation/temporal.test.d.mts +2 -0
  21. package/dist/federation/temporal.test.mjs +71 -0
  22. package/dist/federation/webfinger.test.mjs +147 -2
  23. package/dist/{getMachineId-bsd-etIyxDet.mjs → getMachineId-bsd-BY01PL1n.mjs} +1 -1
  24. package/dist/{getMachineId-darwin-D23zTf4g.mjs → getMachineId-darwin-Dr1gkBkp.mjs} +1 -1
  25. package/dist/{getMachineId-win-Dpap6v5i.mjs → getMachineId-win-QEYwcJiy.mjs} +1 -1
  26. package/dist/{http-CWoeyogl.cjs → http-DQYEA7AZ.cjs} +53 -1
  27. package/dist/{http-CToqG5ap.js → http-WbS1gKzr.js} +48 -2
  28. package/dist/{http-Cyx5SNuu.mjs → http-vHCgbhTg.mjs} +3 -3
  29. package/dist/{key-CkkMJBjF.mjs → key-N0zP_oJA.mjs} +2 -2
  30. package/dist/{kv-cache-CuCn2xvM.js → kv-cache-DM2O-Yjy.js} +1 -1
  31. package/dist/{kv-cache-DuEwFYcN.cjs → kv-cache-Dsg_bi4N.cjs} +1 -1
  32. package/dist/{kv-cache-VHFP42vY.mjs → kv-cache-GXXZEemD.mjs} +1 -1
  33. package/dist/{ld-k8yqD2a-.mjs → ld-BwKhquPx.mjs} +302 -6
  34. package/dist/{metrics-iRBg8jTk.mjs → metrics-7Vy9FvEw.mjs} +48 -2
  35. package/dist/{middleware-D7FrhN9q.js → middleware-BscgvU-m.js} +496 -115
  36. package/dist/{middleware-BWLUrbS9.cjs → middleware-D_iXrYHJ.cjs} +497 -115
  37. package/dist/{middleware-CztxpARM.mjs → middleware-Db1_qAFG.mjs} +1 -1
  38. package/dist/{middleware-DQEgdr83.mjs → middleware-ZuUcO0t1.mjs} +416 -124
  39. package/dist/{mod-C504qevA.d.cts → mod-C7HOzGqH.d.cts} +11 -2
  40. package/dist/{mod-wYfuXeDE.d.ts → mod-CpQHB3Ys.d.ts} +11 -2
  41. package/dist/mod.cjs +4 -4
  42. package/dist/mod.d.cts +2 -2
  43. package/dist/mod.d.ts +2 -2
  44. package/dist/mod.js +4 -4
  45. package/dist/nodeinfo/handler.test.mjs +1 -1
  46. package/dist/{owner-nmXdvXpc.mjs → owner-FD0H_vpj.mjs} +2 -2
  47. package/dist/{proof-CcsIJLTn.cjs → proof-CYK8T8IS.cjs} +353 -3
  48. package/dist/{proof-NRmtrTDu.js → proof-I3EokKN-.js} +300 -4
  49. package/dist/{proof-DpwO1T4S.mjs → proof-V_lafPmA.mjs} +3 -3
  50. package/dist/{send-DvX2tYyZ.mjs → send-Cc2_10tF.mjs} +3 -3
  51. package/dist/sig/http.test.mjs +2 -2
  52. package/dist/sig/key.test.mjs +1 -1
  53. package/dist/sig/ld.test.mjs +558 -2
  54. package/dist/sig/mod.cjs +2 -2
  55. package/dist/sig/mod.js +2 -2
  56. package/dist/sig/owner.test.mjs +1 -1
  57. package/dist/sig/proof.test.mjs +1 -1
  58. package/dist/temporal-BkmBfs__.mjs +95 -0
  59. package/dist/testing/mod.d.mts +48 -6
  60. package/dist/utils/docloader.test.mjs +2 -2
  61. package/dist/utils/kv-cache.test.mjs +1 -1
  62. package/dist/utils/mod.cjs +1 -1
  63. package/dist/utils/mod.js +1 -1
  64. package/package.json +6 -6
  65. /package/dist/{execAsync-DCBrgFiV.mjs → execAsync-Dxb7rNf3.mjs} +0 -0
  66. /package/dist/{getMachineId-linux-ObI47Hql.mjs → getMachineId-linux-Bbhofx-s.mjs} +0 -0
  67. /package/dist/{getMachineId-unsupported-Ddu-PFeh.mjs → getMachineId-unsupported-dIOte2Ct.mjs} +0 -0
  68. /package/dist/{retry-v_sGLH1d.mjs → retry-_VvV0h9f.mjs} +0 -0
@@ -2,10 +2,10 @@ const { Temporal } = require("@js-temporal/polyfill");
2
2
  const { URLPattern } = require("urlpattern-polyfill");
3
3
  const require_chunk = require("./chunk-DDcVe30Y.cjs");
4
4
  const require_transformers = require("./transformers-NeAONrAq.cjs");
5
- const require_http = require("./http-CWoeyogl.cjs");
6
- const require_proof = require("./proof-CcsIJLTn.cjs");
5
+ const require_http = require("./http-DQYEA7AZ.cjs");
6
+ const require_proof = require("./proof-CYK8T8IS.cjs");
7
7
  const require_types = require("./types-KC4QAoxe.cjs");
8
- const require_kv_cache = require("./kv-cache-DuEwFYcN.cjs");
8
+ const require_kv_cache = require("./kv-cache-Dsg_bi4N.cjs");
9
9
  let _logtape_logtape = require("@logtape/logtape");
10
10
  let _fedify_uri_template = require("@fedify/uri-template");
11
11
  let _fedify_vocab = require("@fedify/vocab");
@@ -14,6 +14,8 @@ let byte_encodings_hex = require("byte-encodings/hex");
14
14
  let es_toolkit = require("es-toolkit");
15
15
  let _fedify_vocab_runtime = require("@fedify/vocab-runtime");
16
16
  let _opentelemetry_semantic_conventions = require("@opentelemetry/semantic-conventions");
17
+ let _fedify_vocab_runtime_jsonld = require("@fedify/vocab-runtime/jsonld");
18
+ _fedify_vocab_runtime_jsonld = require_chunk.__toESM(_fedify_vocab_runtime_jsonld, 1);
17
19
  let _fedify_webfinger = require("@fedify/webfinger");
18
20
  let node_url = require("node:url");
19
21
  //#region src/federation/activity-listener.ts
@@ -691,7 +693,7 @@ async function buildCollectionSynchronizationHeader(collectionId, actorIds) {
691
693
  }
692
694
  //#endregion
693
695
  //#region src/federation/inbox.ts
694
- async function routeActivity({ context: ctx, json, activity, recipient, inboxListeners, inboxContextFactory, inboxErrorHandler, kv, kvPrefixes, queue, span, meterProvider, tracerProvider, idempotencyStrategy }) {
696
+ async function routeActivity({ context: ctx, json, originalJson, normalizedActivity, ldSignatureVerified, activity, recipient, inboxListeners, inboxContextFactory, listenerInboxContextFactory, inboxErrorHandler, kv, kvPrefixes, queue, span, meterProvider, tracerProvider, idempotencyStrategy }) {
695
697
  const logger = (0, _logtape_logtape.getLogger)([
696
698
  "fedify",
697
699
  "federation",
@@ -750,7 +752,9 @@ async function routeActivity({ context: ctx, json, activity, recipient, inboxLis
750
752
  type: "inbox",
751
753
  id: crypto.randomUUID(),
752
754
  baseUrl: ctx.origin,
753
- activity: json,
755
+ activity: originalJson ?? json,
756
+ ...normalizedActivity == null ? {} : { normalizedActivity },
757
+ ...ldSignatureVerified == null ? {} : { ldSignatureVerified },
754
758
  identifier: recipient,
755
759
  attempt: 0,
756
760
  started: (/* @__PURE__ */ new Date()).toISOString(),
@@ -804,7 +808,8 @@ async function routeActivity({ context: ctx, json, activity, recipient, inboxLis
804
808
  const activityType = (0, _fedify_vocab.getTypeId)(activity).href;
805
809
  const started = performance.now();
806
810
  try {
807
- await listener(inboxContextFactory(recipient, json, activity.id?.href, activityType), activity);
811
+ const contextFactory = listenerInboxContextFactory ?? inboxContextFactory;
812
+ await listener(contextFactory(recipient, contextFactory === inboxContextFactory ? json : originalJson ?? json, activity.id?.href, activityType), activity);
808
813
  } finally {
809
814
  require_http.getFederationMetrics(meterProvider).recordInboxProcessingDuration(activityType, require_http.getDurationMs(started));
810
815
  }
@@ -1029,7 +1034,123 @@ function acceptsJsonLd(request) {
1029
1034
  return types.includes("application/activity+json") || types.includes("application/ld+json") || types.includes("application/json");
1030
1035
  }
1031
1036
  //#endregion
1037
+ //#region src/federation/temporal.ts
1038
+ function isPlainObject(value) {
1039
+ return typeof value === "object" && value != null && !Array.isArray(value);
1040
+ }
1041
+ function normalizeDateTimeLiteral(value) {
1042
+ return value.substring(19).match(/[Z+-]/) ? value : value + "Z";
1043
+ }
1044
+ function isMalformedDateTimeLiteral(value) {
1045
+ if (typeof value !== "string") return false;
1046
+ try {
1047
+ Temporal.Instant.from(normalizeDateTimeLiteral(value));
1048
+ return false;
1049
+ } catch {
1050
+ return true;
1051
+ }
1052
+ }
1053
+ function isMalformedDurationLiteral(value) {
1054
+ if (typeof value !== "string") return false;
1055
+ try {
1056
+ Temporal.Duration.from(value);
1057
+ return false;
1058
+ } catch {
1059
+ return true;
1060
+ }
1061
+ }
1062
+ const TEMPORAL_DATE_TIME_IRIS = new Set([
1063
+ "https://www.w3.org/ns/activitystreams#deleted",
1064
+ "https://www.w3.org/ns/activitystreams#endTime",
1065
+ "https://www.w3.org/ns/activitystreams#published",
1066
+ "https://www.w3.org/ns/activitystreams#startTime",
1067
+ "https://www.w3.org/ns/activitystreams#updated",
1068
+ "http://purl.org/dc/terms/created",
1069
+ "https://w3id.org/security#created"
1070
+ ]);
1071
+ const TEMPORAL_DURATION_IRIS = new Set(["https://www.w3.org/ns/activitystreams#duration"]);
1072
+ const QUESTION_CLOSED_IRI = "https://www.w3.org/ns/activitystreams#closed";
1073
+ const XSD_DATE_TIME_IRI = "http://www.w3.org/2001/XMLSchema#dateTime";
1074
+ function hasMalformedExpandedDateTimeLiteral(value) {
1075
+ if (Array.isArray(value)) return value.some(hasMalformedExpandedDateTimeLiteral);
1076
+ return isPlainObject(value) && "@value" in value && isMalformedDateTimeLiteral(value["@value"]);
1077
+ }
1078
+ function hasMalformedExpandedQuestionClosedLiteral(value) {
1079
+ if (Array.isArray(value)) return value.some(hasMalformedExpandedQuestionClosedLiteral);
1080
+ if (!isPlainObject(value) || !("@value" in value)) return false;
1081
+ const literal = value["@value"];
1082
+ if (typeof literal === "boolean") return false;
1083
+ if (typeof literal !== "string") return false;
1084
+ if (value["@type"] !== XSD_DATE_TIME_IRI) return false;
1085
+ if (new Date(literal).toString() === "Invalid Date") return false;
1086
+ return isMalformedDateTimeLiteral(literal);
1087
+ }
1088
+ function hasMalformedExpandedDurationLiteral(value) {
1089
+ if (Array.isArray(value)) return value.some(hasMalformedExpandedDurationLiteral);
1090
+ return isPlainObject(value) && "@value" in value && isMalformedDurationLiteral(value["@value"]);
1091
+ }
1092
+ function hasMalformedKnownTemporalLiteralInternal(value, visited) {
1093
+ if (Array.isArray(value)) return value.some((item) => hasMalformedKnownTemporalLiteralInternal(item, visited));
1094
+ if (!isPlainObject(value)) return false;
1095
+ if (visited.has(value)) return false;
1096
+ visited.add(value);
1097
+ if ("@value" in value) return false;
1098
+ for (const [key, child] of Object.entries(value)) {
1099
+ if (TEMPORAL_DATE_TIME_IRIS.has(key)) {
1100
+ if (hasMalformedExpandedDateTimeLiteral(child)) return true;
1101
+ continue;
1102
+ }
1103
+ if (key === QUESTION_CLOSED_IRI) {
1104
+ if (hasMalformedExpandedQuestionClosedLiteral(child)) return true;
1105
+ continue;
1106
+ }
1107
+ if (TEMPORAL_DURATION_IRIS.has(key)) {
1108
+ if (hasMalformedExpandedDurationLiteral(child)) return true;
1109
+ continue;
1110
+ }
1111
+ if (hasMalformedKnownTemporalLiteralInternal(child, visited)) return true;
1112
+ }
1113
+ return false;
1114
+ }
1115
+ async function hasMalformedKnownTemporalLiteral(value, contextLoader) {
1116
+ try {
1117
+ return hasMalformedKnownTemporalLiteralInternal(await _fedify_vocab_runtime_jsonld.default.expand(value, {
1118
+ documentLoader: require_proof.getNormalizationContextLoader(contextLoader),
1119
+ keepFreeFloatingNodes: true
1120
+ }), /* @__PURE__ */ new Set());
1121
+ } catch {
1122
+ return false;
1123
+ }
1124
+ }
1125
+ //#endregion
1032
1126
  //#region src/federation/handler.ts
1127
+ const rawInboxContextFactorySymbol = Symbol("fedify.rawInboxContextFactory");
1128
+ function isRemoteContextLoadingFailure$1(error) {
1129
+ return error instanceof Error && typeof error.details === "object" && error.details != null && error.details.code === "loading remote context failed";
1130
+ }
1131
+ function isPermanentRemoteContextError$1(error) {
1132
+ if (!(error instanceof Error) || error.name !== "jsonld.InvalidUrl") return false;
1133
+ const details = error.details;
1134
+ if (details?.code === "invalid remote context") return true;
1135
+ return isRemoteContextLoadingFailure$1(error) && typeof details?.url === "string" && !URL.canParse(details.url) && require_proof.isClearlyMalformedContextReference(details.url);
1136
+ }
1137
+ function isInvalidJsonLdError(error) {
1138
+ if (!(error instanceof Error)) return false;
1139
+ const name = error.name;
1140
+ return name === "UnsafeJsonLdError" || error instanceof require_proof.InvalidContextReferenceError || isPermanentRemoteContextError$1(error) || name === "jsonld.SyntaxError" && !isRemoteContextLoadingFailure$1(error);
1141
+ }
1142
+ function isValidationTypeError(error) {
1143
+ return error instanceof TypeError && (/^(Invalid JSON-LD:|Invalid type:|Unexpected type:)/.test(error.message) || require_proof.isInvalidUrlTypeError(error));
1144
+ }
1145
+ function isPermanentActivityParseError(error) {
1146
+ return isInvalidJsonLdError(error) || isValidationTypeError(error);
1147
+ }
1148
+ function hasHttpSignatureHeaders(request) {
1149
+ return request.headers.has("Signature") || request.headers.has("Signature-Input");
1150
+ }
1151
+ function hasObjectIntegrityProof(json) {
1152
+ return typeof json === "object" && json != null && "proof" in json;
1153
+ }
1033
1154
  /**
1034
1155
  * Handles an actor request.
1035
1156
  * @template TContextData The context data to pass to the context.
@@ -1101,8 +1222,8 @@ async function handleObject(request, { values, context, objectDispatcher, author
1101
1222
  * @param parameters The parameters for handling the collection.
1102
1223
  * @returns A promise that resolves to an HTTP response.
1103
1224
  */
1104
- async function handleCollection(request, { name: name$2, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, tracerProvider, onUnauthorized, onNotFound }) {
1105
- const spanName = name$2.trim().replace(/\s+/g, "_");
1225
+ async function handleCollection(request, { name: name$1, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, tracerProvider, onUnauthorized, onNotFound }) {
1226
+ const spanName = name$1.trim().replace(/\s+/g, "_");
1106
1227
  tracerProvider = tracerProvider ?? _opentelemetry_api.trace.getTracerProvider();
1107
1228
  const tracer = tracerProvider.getTracer(require_http.name, require_http.version);
1108
1229
  const cursor = new URL(request.url).searchParams.get("cursor");
@@ -1144,7 +1265,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
1144
1265
  collection = new _fedify_vocab.OrderedCollection({
1145
1266
  id: baseUri,
1146
1267
  totalItems: totalItems == null ? null : Number(totalItems),
1147
- items: filterCollectionItems(itemsOrResponse, name$2, filterPredicate)
1268
+ items: filterCollectionItems(itemsOrResponse, name$1, filterPredicate)
1148
1269
  });
1149
1270
  } else {
1150
1271
  const lastCursor = await collectionCallbacks.lastCursor?.(context, identifier);
@@ -1165,7 +1286,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
1165
1286
  } else {
1166
1287
  const uri = new URL(baseUri);
1167
1288
  uri.searchParams.set("cursor", cursor);
1168
- const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$2}`, {
1289
+ const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$1}`, {
1169
1290
  kind: _opentelemetry_api.SpanKind.SERVER,
1170
1291
  attributes: {
1171
1292
  "activitypub.collection.id": uri.href,
@@ -1209,7 +1330,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
1209
1330
  id: uri,
1210
1331
  prev,
1211
1332
  next,
1212
- items: filterCollectionItems(items, name$2, filterPredicate),
1333
+ items: filterCollectionItems(items, name$1, filterPredicate),
1213
1334
  partOf
1214
1335
  });
1215
1336
  }
@@ -1556,29 +1677,105 @@ async function handleInboxInternal(request, parameters, span) {
1556
1677
  });
1557
1678
  }
1558
1679
  const keyCache = new KvKeyCache(kv, kvPrefixes.publicKey, ctx);
1559
- let ldSigVerified;
1560
- try {
1561
- ldSigVerified = await require_proof.verifyJsonLd(json, {
1562
- contextLoader: ctx.contextLoader,
1563
- documentLoader: ctx.documentLoader,
1564
- keyCache,
1565
- meterProvider,
1566
- tracerProvider
1680
+ const jsonWithoutSig = require_proof.detachSignature(json);
1681
+ const hasLdSignature = require_proof.hasSignature(json);
1682
+ const canAttemptAlternateAuthAfterLdSignatureFailure = skipSignatureVerification || hasHttpSignatureHeaders(request) || hasObjectIntegrityProof(jsonWithoutSig);
1683
+ let deferredLdSignatureError = void 0;
1684
+ const respondInvalidActivity = async (error) => {
1685
+ logger.error("Failed to parse activity:\n{error}", {
1686
+ recipient,
1687
+ activity: json,
1688
+ error
1567
1689
  });
1568
- } catch (error) {
1569
- if (error instanceof Error && error.name === "jsonld.SyntaxError") {
1570
- logger.error("Failed to parse JSON-LD:\n{error}", {
1690
+ try {
1691
+ await inboxErrorHandler?.(ctx, error);
1692
+ } catch (error) {
1693
+ logger.error("An unexpected error occurred in inbox error handler:\n{error}", {
1694
+ error,
1695
+ activity: json,
1696
+ recipient
1697
+ });
1698
+ }
1699
+ span.setStatus({
1700
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
1701
+ message: `Failed to parse activity:\n${error}`
1702
+ });
1703
+ return new Response("Invalid activity.", {
1704
+ status: 400,
1705
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
1706
+ });
1707
+ };
1708
+ let compactedJson = json;
1709
+ let compactedJsonWithoutSig = jsonWithoutSig;
1710
+ let ldSigVerified = false;
1711
+ if (hasLdSignature) {
1712
+ try {
1713
+ compactedJson = await require_proof.compactJsonLd(json, ctx.contextLoader);
1714
+ } catch (error) {
1715
+ if (isInvalidJsonLdError(error)) {
1716
+ logger.error("Failed to parse JSON-LD:\n{error}", {
1717
+ recipient,
1718
+ error
1719
+ });
1720
+ return new Response("Invalid JSON-LD.", {
1721
+ status: 400,
1722
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
1723
+ });
1724
+ }
1725
+ if (!canAttemptAlternateAuthAfterLdSignatureFailure) throw error;
1726
+ if (!skipSignatureVerification) deferredLdSignatureError = error;
1727
+ logger.debug("Failed to normalize JSON-LD for Linked Data Signatures; deferring to another authentication path only if it verifies:\n{error}", {
1571
1728
  recipient,
1572
1729
  error
1573
1730
  });
1574
- return new Response("Invalid JSON-LD.", {
1575
- status: 400,
1576
- headers: { "Content-Type": "text/plain; charset=utf-8" }
1577
- });
1578
1731
  }
1579
- ldSigVerified = false;
1732
+ if (compactedJson !== json) {
1733
+ compactedJsonWithoutSig = require_proof.detachSignature(compactedJson);
1734
+ try {
1735
+ ldSigVerified = await require_proof.verifyCompactJsonLd(compactedJson, {
1736
+ contextLoader: ctx.contextLoader,
1737
+ documentLoader: ctx.documentLoader,
1738
+ keyCache,
1739
+ meterProvider,
1740
+ tracerProvider
1741
+ });
1742
+ } catch (error) {
1743
+ if (error instanceof RangeError && await hasMalformedKnownTemporalLiteral(compactedJsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(error);
1744
+ if (isInvalidJsonLdError(error)) {
1745
+ logger.error("Failed to parse JSON-LD:\n{error}", {
1746
+ recipient,
1747
+ error
1748
+ });
1749
+ return new Response("Invalid JSON-LD.", {
1750
+ status: 400,
1751
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
1752
+ });
1753
+ }
1754
+ if (!canAttemptAlternateAuthAfterLdSignatureFailure) throw error;
1755
+ if (!skipSignatureVerification) try {
1756
+ await _fedify_vocab.Object.fromJsonLd(compactedJson, {
1757
+ contextLoader: require_proof.getNormalizationContextLoader(ctx.contextLoader),
1758
+ documentLoader: ctx.documentLoader,
1759
+ tracerProvider
1760
+ });
1761
+ } catch (parseError) {
1762
+ if (parseError instanceof RangeError && await hasMalformedKnownTemporalLiteral(compactedJsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(parseError);
1763
+ if (isInvalidJsonLdError(parseError)) {
1764
+ logger.error("Failed to parse JSON-LD:\n{error}", {
1765
+ recipient,
1766
+ error: parseError
1767
+ });
1768
+ return new Response("Invalid JSON-LD.", {
1769
+ status: 400,
1770
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
1771
+ });
1772
+ }
1773
+ deferredLdSignatureError = parseError;
1774
+ }
1775
+ ldSigVerified = false;
1776
+ }
1777
+ }
1580
1778
  }
1581
- const jsonWithoutSig = require_proof.detachSignature(json);
1582
1779
  let activity = null;
1583
1780
  let activityVerified = false;
1584
1781
  if (ldSigVerified) {
@@ -1586,7 +1783,16 @@ async function handleInboxInternal(request, parameters, span) {
1586
1783
  recipient,
1587
1784
  json
1588
1785
  });
1589
- activity = await _fedify_vocab.Activity.fromJsonLd(jsonWithoutSig, ctx);
1786
+ try {
1787
+ activity = await _fedify_vocab.Activity.fromJsonLd(compactedJsonWithoutSig, {
1788
+ ...ctx,
1789
+ contextLoader: require_proof.getNormalizationContextLoader(ctx.contextLoader)
1790
+ });
1791
+ } catch (error) {
1792
+ if (error instanceof RangeError && await hasMalformedKnownTemporalLiteral(compactedJsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(error);
1793
+ if (!isPermanentActivityParseError(error)) throw error;
1794
+ return await respondInvalidActivity(error);
1795
+ }
1590
1796
  activityVerified = true;
1591
1797
  } else {
1592
1798
  logger.debug("Linked Data Signatures are not verified.", {
@@ -1595,13 +1801,22 @@ async function handleInboxInternal(request, parameters, span) {
1595
1801
  });
1596
1802
  try {
1597
1803
  activity = await require_proof.verifyObject(_fedify_vocab.Activity, jsonWithoutSig, {
1598
- contextLoader: ctx.contextLoader,
1804
+ contextLoader: require_proof.wrapContextLoaderForJsonLd(ctx.contextLoader),
1599
1805
  documentLoader: ctx.documentLoader,
1600
1806
  keyCache,
1601
1807
  meterProvider,
1602
1808
  tracerProvider
1603
1809
  });
1604
1810
  } catch (error) {
1811
+ if (error instanceof RangeError && await hasMalformedKnownTemporalLiteral(jsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(error);
1812
+ if (deferredLdSignatureError != null) {
1813
+ logger.debug("Object Integrity Proof fallback did not supersede a deferred Linked Data Signature failure:\n{error}", {
1814
+ recipient,
1815
+ error
1816
+ });
1817
+ activity = null;
1818
+ }
1819
+ if (!isPermanentActivityParseError(error)) throw error;
1605
1820
  logger.error("Failed to parse activity:\n{error}", {
1606
1821
  recipient,
1607
1822
  activity: json,
@@ -1650,6 +1865,7 @@ async function handleInboxInternal(request, parameters, span) {
1650
1865
  tracerProvider
1651
1866
  });
1652
1867
  if (verification.verified === false) {
1868
+ if (deferredLdSignatureError != null) throw deferredLdSignatureError;
1653
1869
  const reason = verification.reason;
1654
1870
  const remoteHost = "keyId" in reason && reason.keyId != null ? require_http.getRemoteHost(reason.keyId) : void 0;
1655
1871
  require_http.getFederationMetrics(parameters.meterProvider).recordSignatureVerificationFailure(reason.type, remoteHost);
@@ -1727,7 +1943,15 @@ async function handleInboxInternal(request, parameters, span) {
1727
1943
  }
1728
1944
  httpSigKey = verification.key;
1729
1945
  }
1730
- activity = await _fedify_vocab.Activity.fromJsonLd(jsonWithoutSig, ctx);
1946
+ try {
1947
+ activity = await _fedify_vocab.Activity.fromJsonLd(jsonWithoutSig, {
1948
+ ...ctx,
1949
+ contextLoader: require_proof.wrapContextLoaderForJsonLd(ctx.contextLoader)
1950
+ });
1951
+ } catch (error) {
1952
+ if (!isPermanentActivityParseError(error)) throw error;
1953
+ return await respondInvalidActivity(error);
1954
+ }
1731
1955
  }
1732
1956
  if (activity.id != null) span.setAttribute("activitypub.activity.id", activity.id.href);
1733
1957
  span.setAttribute("activitypub.activity.type", (0, _fedify_vocab.getTypeId)(activity).href);
@@ -1739,6 +1963,7 @@ async function handleInboxInternal(request, parameters, span) {
1739
1963
  "http_signatures.key_id": httpSigKey?.id?.href ?? ""
1740
1964
  });
1741
1965
  if (httpSigKey != null && !await require_proof.doesActorOwnKey(activity, httpSigKey, ctx)) {
1966
+ if (deferredLdSignatureError != null) throw deferredLdSignatureError;
1742
1967
  require_http.getFederationMetrics(parameters.meterProvider).recordSignatureVerificationFailure("actorKeyMismatch", httpSigKey.id == null ? void 0 : require_http.getRemoteHost(httpSigKey.id));
1743
1968
  logger.error("The signer ({keyId}) and the actor ({actorId}) do not match.", {
1744
1969
  activity: json,
@@ -1765,10 +1990,14 @@ async function handleInboxInternal(request, parameters, span) {
1765
1990
  const routeResult = await routeActivity({
1766
1991
  context: ctx,
1767
1992
  json,
1993
+ originalJson: json,
1994
+ normalizedActivity: hasLdSignature && compactedJson !== json ? compactedJson : void 0,
1995
+ ldSignatureVerified: hasLdSignature ? ldSigVerified : void 0,
1768
1996
  activity,
1769
1997
  recipient,
1770
1998
  inboxListeners,
1771
1999
  inboxContextFactory,
2000
+ listenerInboxContextFactory: ldSigVerified ? inboxContextFactory[rawInboxContextFactorySymbol] : void 0,
1772
2001
  inboxErrorHandler,
1773
2002
  kv,
1774
2003
  kvPrefixes,
@@ -1895,8 +2124,8 @@ var CustomCollectionHandler = class {
1895
2124
  * @param CollectionPage The CollectionPage constructor.
1896
2125
  * @param filterPredicate Optional filter predicate for items.
1897
2126
  */
1898
- constructor(name$1, values, context, callbacks, tracerProvider = _opentelemetry_api.trace.getTracerProvider(), Collection, CollectionPage, filterPredicate) {
1899
- this.name = name$1;
2127
+ constructor(name$2, values, context, callbacks, tracerProvider = _opentelemetry_api.trace.getTracerProvider(), Collection, CollectionPage, filterPredicate) {
2128
+ this.name = name$2;
1900
2129
  this.values = values;
1901
2130
  this.context = context;
1902
2131
  this.callbacks = callbacks;
@@ -2601,22 +2830,72 @@ const logger = (0, _logtape_logtape.getLogger)([
2601
2830
  * @returns The response to the request.
2602
2831
  */
2603
2832
  async function handleWebFinger(request, options) {
2604
- if (options.tracer == null) return await handleWebFingerInternal(request, options);
2605
- return await options.tracer.startActiveSpan("webfinger.handle", { kind: _opentelemetry_api.SpanKind.SERVER }, async (span) => {
2606
- try {
2607
- const response = await handleWebFingerInternal(request, options);
2608
- span.setStatus({ code: response.ok ? _opentelemetry_api.SpanStatusCode.UNSET : _opentelemetry_api.SpanStatusCode.ERROR });
2609
- return response;
2610
- } catch (error) {
2611
- span.setStatus({
2612
- code: _opentelemetry_api.SpanStatusCode.ERROR,
2613
- message: String(error)
2614
- });
2615
- throw error;
2616
- } finally {
2617
- span.end();
2833
+ const meterProvider = options.meterProvider;
2834
+ const start = meterProvider == null ? 0 : performance.now();
2835
+ const scheme = computeResourceScheme(options.context.url.searchParams.get("resource"));
2836
+ let notFoundResponse;
2837
+ const wrappedOptions = {
2838
+ ...options,
2839
+ async onNotFound(req) {
2840
+ const r = await options.onNotFound(req);
2841
+ notFoundResponse = r;
2842
+ return r;
2618
2843
  }
2619
- });
2844
+ };
2845
+ let response;
2846
+ try {
2847
+ if (options.tracer == null) response = await handleWebFingerInternal(request, wrappedOptions);
2848
+ else response = await options.tracer.startActiveSpan("webfinger.handle", { kind: _opentelemetry_api.SpanKind.SERVER }, async (span) => {
2849
+ try {
2850
+ const inner = await handleWebFingerInternal(request, wrappedOptions);
2851
+ span.setStatus({ code: inner.ok ? _opentelemetry_api.SpanStatusCode.UNSET : _opentelemetry_api.SpanStatusCode.ERROR });
2852
+ return inner;
2853
+ } catch (error) {
2854
+ span.setStatus({
2855
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
2856
+ message: String(error)
2857
+ });
2858
+ throw error;
2859
+ } finally {
2860
+ span.end();
2861
+ }
2862
+ });
2863
+ return response;
2864
+ } finally {
2865
+ if (meterProvider != null) require_http.recordWebFingerHandle(meterProvider, {
2866
+ durationMs: Math.max(0, performance.now() - start),
2867
+ result: classifyWebFingerHandleResult(response, notFoundResponse),
2868
+ scheme,
2869
+ statusCode: response?.status
2870
+ });
2871
+ }
2872
+ }
2873
+ const WEBFINGER_HANDLE_SCHEME_WHITELIST = new Set([
2874
+ "acct",
2875
+ "http",
2876
+ "https",
2877
+ "mailto"
2878
+ ]);
2879
+ function isAllowedResourceScheme(scheme) {
2880
+ return WEBFINGER_HANDLE_SCHEME_WHITELIST.has(scheme);
2881
+ }
2882
+ function computeResourceScheme(resource) {
2883
+ if (resource == null) return void 0;
2884
+ const colon = resource.indexOf(":");
2885
+ if (colon <= 0) return void 0;
2886
+ const candidate = resource.substring(0, colon).toLowerCase();
2887
+ return isAllowedResourceScheme(candidate) ? candidate : "other";
2888
+ }
2889
+ function classifyWebFingerHandleResult(response, notFoundResponse) {
2890
+ if (response == null) return "error";
2891
+ if (notFoundResponse != null && response === notFoundResponse) return "not_found";
2892
+ switch (response.status) {
2893
+ case 200: return "resolved";
2894
+ case 400: return "invalid";
2895
+ case 404: return "not_found";
2896
+ case 410: return "tombstoned";
2897
+ default: return "error";
2898
+ }
2620
2899
  }
2621
2900
  async function handleWebFingerInternal(request, { context, host, actorDispatcher, actorHandleMapper, actorAliasMapper, onNotFound, span, webFingerLinksDispatcher }) {
2622
2901
  if (actorDispatcher == null) {
@@ -2732,6 +3011,18 @@ var middleware_exports = /* @__PURE__ */ require_chunk.__exportAll({
2732
3011
  OutboxContextImpl: () => OutboxContextImpl,
2733
3012
  createFederation: () => createFederation
2734
3013
  });
3014
+ function isRemoteContextLoadingFailure(error) {
3015
+ return error instanceof Error && typeof error.details === "object" && error.details != null && error.details.code === "loading remote context failed";
3016
+ }
3017
+ function isPermanentRemoteContextError(error) {
3018
+ if (!(error instanceof Error) || error.name !== "jsonld.InvalidUrl") return false;
3019
+ const details = error.details;
3020
+ if (details?.code === "invalid remote context") return true;
3021
+ return isRemoteContextLoadingFailure(error) && typeof details?.url === "string" && !URL.canParse(details.url) && require_proof.isClearlyMalformedContextReference(details.url);
3022
+ }
3023
+ function isPermanentInboxParseError(error) {
3024
+ return error instanceof Error && (error.name === "UnsafeJsonLdError" || error instanceof require_proof.InvalidContextReferenceError || isPermanentRemoteContextError(error) || error.name === "jsonld.SyntaxError" && !isRemoteContextLoadingFailure(error)) || error instanceof TypeError && (/^(Invalid JSON-LD:|Invalid type:|Unexpected type:)/.test(error.message) || require_proof.isInvalidUrlTypeError(error));
3025
+ }
2735
3026
  /**
2736
3027
  * Create a new {@link Federation} instance.
2737
3028
  * @param parameters Parameters for initializing the instance.
@@ -3234,78 +3525,37 @@ var FederationImpl = class extends FederationBuilderImpl {
3234
3525
  const identity = await this.sharedInboxKeyDispatcher(context);
3235
3526
  if (identity != null) context = this.#createContext(baseUrl, ctxData, { documentLoader: "identifier" in identity || "username" in identity ? await context.getDocumentLoader(identity) : context.getDocumentLoader(identity) });
3236
3527
  }
3237
- const activity = await _fedify_vocab.Activity.fromJsonLd(message.activity, context);
3238
- const activityType = (0, _fedify_vocab.getTypeId)(activity).href;
3239
- span.setAttribute("activitypub.activity.type", activityType);
3240
- onActivityType?.(activityType);
3241
- if (activity.id != null) span.setAttribute("activitypub.activity.id", activity.id.href);
3242
- const cacheKey = activity.id == null ? null : [
3243
- ...this.kvPrefixes.activityIdempotence,
3244
- context.origin,
3245
- activity.id.href
3246
- ];
3247
- if (cacheKey != null) {
3248
- if (await this.kv.get(cacheKey) === true) {
3249
- logger.debug("Activity {activityId} has already been processed.", {
3250
- activityId: activity.id?.href,
3251
- activity: message.activity,
3252
- recipient: message.identifier
3253
- });
3254
- require_http.recordInboxActivity(this.meterProvider, "rejected", activityType);
3255
- return;
3256
- }
3257
- }
3258
- await this._getTracer().startActiveSpan("activitypub.dispatch_inbox_listener", { kind: _opentelemetry_api.SpanKind.INTERNAL }, async (span) => {
3259
- const dispatched = this.inboxListeners?.dispatchWithClass(activity);
3260
- if (dispatched == null) {
3261
- logger.error("Unsupported activity type:\n{activity}", {
3262
- activityId: activity.id?.href,
3263
- activity: message.activity,
3264
- recipient: message.identifier,
3265
- trial: message.attempt
3266
- });
3267
- span.setStatus({
3268
- code: _opentelemetry_api.SpanStatusCode.ERROR,
3269
- message: `Unsupported activity type: ${activityType}`
3270
- });
3271
- require_http.recordInboxActivity(this.meterProvider, "rejected", activityType);
3272
- span.end();
3273
- return;
3274
- }
3275
- const { class: cls, listener } = dispatched;
3276
- span.updateName(`activitypub.dispatch_inbox_listener ${cls.name}`);
3277
- try {
3278
- const started = performance.now();
3279
- try {
3280
- await listener(context.toInboxContext(message.identifier, message.activity, activity.id?.href, activityType), activity);
3281
- } finally {
3282
- require_http.getFederationMetrics(this.meterProvider).recordInboxProcessingDuration(activityType, require_http.getDurationMs(started));
3283
- }
3284
- require_http.recordInboxActivity(this.meterProvider, "processed", activityType);
3285
- } catch (error) {
3528
+ await this._getTracer().startActiveSpan("activitypub.dispatch_inbox_listener", { kind: _opentelemetry_api.SpanKind.INTERNAL }, async (listenerSpan) => {
3529
+ let activity = null;
3530
+ let cacheKey = null;
3531
+ let activityType;
3532
+ const reportInboxError = async (error) => {
3286
3533
  try {
3287
3534
  await this.inboxErrorHandler?.(context, error);
3288
3535
  } catch (error) {
3289
3536
  logger.error("An unexpected error occurred in inbox error handler:\n{error}", {
3290
3537
  error,
3291
3538
  trial: message.attempt,
3292
- activityId: activity.id?.href,
3539
+ activityId: activity?.id?.href,
3293
3540
  activity: message.activity,
3294
3541
  recipient: message.identifier
3295
3542
  });
3296
3543
  }
3544
+ };
3545
+ const handleRetriableFailure = async (error) => {
3546
+ await reportInboxError(error);
3297
3547
  if (this.inboxQueue?.nativeRetrial) {
3298
3548
  logger.error("Failed to process the incoming activity {activityId}; backend will handle retry:\n{error}", {
3299
3549
  error,
3300
- activityId: activity.id?.href,
3550
+ activityId: activity?.id?.href,
3301
3551
  activity: message.activity,
3302
3552
  recipient: message.identifier
3303
3553
  });
3304
- span.setStatus({
3554
+ listenerSpan.setStatus({
3305
3555
  code: _opentelemetry_api.SpanStatusCode.ERROR,
3306
3556
  message: String(error)
3307
3557
  });
3308
- span.end();
3558
+ listenerSpan.end();
3309
3559
  throw error;
3310
3560
  }
3311
3561
  const delay = this.inboxRetryPolicy({
@@ -3316,20 +3566,27 @@ var FederationImpl = class extends FederationBuilderImpl {
3316
3566
  logger.error("Failed to process the incoming activity {activityId} (attempt #{attempt}); retry...:\n{error}", {
3317
3567
  error,
3318
3568
  attempt: message.attempt,
3319
- activityId: activity.id?.href,
3569
+ activityId: activity?.id?.href,
3320
3570
  activity: message.activity,
3321
3571
  recipient: message.identifier
3322
3572
  });
3573
+ if (this.inboxQueue == null) {
3574
+ listenerSpan.setStatus({
3575
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
3576
+ message: String(error)
3577
+ });
3578
+ listenerSpan.end();
3579
+ throw error;
3580
+ }
3323
3581
  const retryMessage = {
3324
3582
  ...message,
3325
3583
  attempt: message.attempt + 1
3326
3584
  };
3327
- const { inboxQueue } = this;
3328
- if (inboxQueue != null) {
3329
- await inboxQueue.enqueue(retryMessage, { delay: Temporal.Duration.compare(delay, { seconds: 0 }) < 0 ? Temporal.Duration.from({ seconds: 0 }) : delay });
3585
+ await this.inboxQueue.enqueue(retryMessage, { delay: Temporal.Duration.compare(delay, { seconds: 0 }) < 0 ? Temporal.Duration.from({ seconds: 0 }) : delay });
3586
+ if (activityType != null) {
3330
3587
  require_http.getFederationMetrics(this.meterProvider).recordQueueTaskEnqueued({
3331
3588
  role: "inbox",
3332
- queue: inboxQueue,
3589
+ queue: this.inboxQueue,
3333
3590
  activityType
3334
3591
  }, retryMessage.attempt);
3335
3592
  require_http.recordInboxActivity(this.meterProvider, "retried", activityType);
@@ -3337,26 +3594,137 @@ var FederationImpl = class extends FederationBuilderImpl {
3337
3594
  } else {
3338
3595
  logger.error("Failed to process the incoming activity {activityId} after {trial} attempts; giving up:\n{error}", {
3339
3596
  error,
3340
- activityId: activity.id?.href,
3597
+ activityId: activity?.id?.href,
3341
3598
  activity: message.activity,
3342
3599
  recipient: message.identifier
3343
3600
  });
3344
- require_http.recordInboxActivity(this.meterProvider, "abandoned", activityType);
3601
+ if (activityType != null) require_http.recordInboxActivity(this.meterProvider, "abandoned", activityType);
3345
3602
  }
3346
- span.setStatus({
3603
+ listenerSpan.setStatus({
3347
3604
  code: _opentelemetry_api.SpanStatusCode.ERROR,
3348
3605
  message: String(error)
3349
3606
  });
3350
- span.end();
3607
+ listenerSpan.end();
3608
+ };
3609
+ let dispatched;
3610
+ let parseInput = void 0;
3611
+ let parseContextLoader = context.contextLoader;
3612
+ try {
3613
+ const hasSignatureField = require_proof.hasSignature(message.activity);
3614
+ const shouldParseFromNormalizedSignedPayload = message.ldSignatureVerified === true || message.normalizedActivity != null || message.ldSignatureVerified == null && hasSignatureField;
3615
+ const parseContext = hasSignatureField ? {
3616
+ ...context,
3617
+ contextLoader: require_proof.getNormalizationContextLoader(context.contextLoader)
3618
+ } : {
3619
+ ...context,
3620
+ contextLoader: require_proof.wrapContextLoaderForJsonLd(context.contextLoader)
3621
+ };
3622
+ parseContextLoader = parseContext.contextLoader;
3623
+ let normalizedActivity;
3624
+ if (shouldParseFromNormalizedSignedPayload) {
3625
+ normalizedActivity = message.normalizedActivity ?? await require_proof.compactJsonLd(message.activity, context.contextLoader);
3626
+ require_proof.assertSafeJsonLd(normalizedActivity);
3627
+ }
3628
+ parseInput = shouldParseFromNormalizedSignedPayload ? require_proof.detachSignature(normalizedActivity) : hasSignatureField ? require_proof.detachSignature(message.activity) : message.activity;
3629
+ activity = await _fedify_vocab.Activity.fromJsonLd(parseInput, parseContext);
3630
+ activityType = (0, _fedify_vocab.getTypeId)(activity).href;
3631
+ span.setAttribute("activitypub.activity.type", activityType);
3632
+ listenerSpan.setAttribute("activitypub.activity.type", activityType);
3633
+ onActivityType?.(activityType);
3634
+ if (activity.id != null) {
3635
+ span.setAttribute("activitypub.activity.id", activity.id.href);
3636
+ listenerSpan.setAttribute("activitypub.activity.id", activity.id.href);
3637
+ }
3638
+ cacheKey = activity.id == null ? null : [
3639
+ ...this.kvPrefixes.activityIdempotence,
3640
+ context.origin,
3641
+ activity.id.href
3642
+ ];
3643
+ if (cacheKey != null) {
3644
+ if (await this.kv.get(cacheKey) === true) {
3645
+ logger.debug("Activity {activityId} has already been processed.", {
3646
+ activityId: activity.id?.href,
3647
+ activity: message.activity,
3648
+ recipient: message.identifier
3649
+ });
3650
+ require_http.recordInboxActivity(this.meterProvider, "rejected", activityType);
3651
+ listenerSpan.end();
3652
+ return;
3653
+ }
3654
+ }
3655
+ dispatched = this.inboxListeners?.dispatchWithClass(activity);
3656
+ } catch (error) {
3657
+ if (activity == null && error instanceof RangeError && await hasMalformedKnownTemporalLiteral(parseInput, parseContextLoader)) {
3658
+ await reportInboxError(error);
3659
+ logger.error("Failed to parse the queued incoming activity {activityId}:\n{error}", {
3660
+ error,
3661
+ trial: message.attempt,
3662
+ activityId: null,
3663
+ activity: message.activity,
3664
+ recipient: message.identifier
3665
+ });
3666
+ listenerSpan.setStatus({
3667
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
3668
+ message: String(error)
3669
+ });
3670
+ listenerSpan.end();
3671
+ return;
3672
+ }
3673
+ if (isPermanentInboxParseError(error)) {
3674
+ await reportInboxError(error);
3675
+ logger.error("Failed to parse the queued incoming activity {activityId}:\n{error}", {
3676
+ error,
3677
+ trial: message.attempt,
3678
+ activityId: activity?.id?.href,
3679
+ activity: message.activity,
3680
+ recipient: message.identifier
3681
+ });
3682
+ listenerSpan.setStatus({
3683
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
3684
+ message: String(error)
3685
+ });
3686
+ listenerSpan.end();
3687
+ return;
3688
+ }
3689
+ await handleRetriableFailure(error);
3690
+ return;
3691
+ }
3692
+ if (dispatched == null) {
3693
+ logger.error("Unsupported activity type:\n{activity}", {
3694
+ activityId: activity.id?.href,
3695
+ activity: message.activity,
3696
+ recipient: message.identifier,
3697
+ trial: message.attempt
3698
+ });
3699
+ listenerSpan.setStatus({
3700
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
3701
+ message: `Unsupported activity type: ${activityType}`
3702
+ });
3703
+ require_http.recordInboxActivity(this.meterProvider, "rejected", activityType);
3704
+ listenerSpan.end();
3705
+ return;
3706
+ }
3707
+ const { class: cls, listener } = dispatched;
3708
+ listenerSpan.updateName(`activitypub.dispatch_inbox_listener ${cls.name}`);
3709
+ try {
3710
+ const started = performance.now();
3711
+ try {
3712
+ await listener(context.toInboxContext(message.identifier, message.activity, activity.id?.href, activityType), activity);
3713
+ } finally {
3714
+ require_http.getFederationMetrics(this.meterProvider).recordInboxProcessingDuration(activityType, require_http.getDurationMs(started));
3715
+ }
3716
+ require_http.recordInboxActivity(this.meterProvider, "processed", activityType);
3717
+ } catch (error) {
3718
+ await handleRetriableFailure(error);
3351
3719
  return;
3352
3720
  }
3353
3721
  if (cacheKey != null) await this.kv.set(cacheKey, true, { ttl: Temporal.Duration.from({ days: 1 }) });
3354
3722
  logger.info("Activity {activityId} has been processed.", {
3355
- activityId: activity.id?.href,
3723
+ activityId: activity?.id?.href,
3356
3724
  activity: message.activity,
3357
3725
  recipient: message.identifier
3358
3726
  });
3359
- span.end();
3727
+ listenerSpan.end();
3360
3728
  });
3361
3729
  }
3362
3730
  startQueue(contextData, options = {}) {
@@ -3650,7 +4018,8 @@ var FederationImpl = class extends FederationBuilderImpl {
3650
4018
  actorAliasMapper: this.actorCallbacks?.aliasMapper,
3651
4019
  webFingerLinksDispatcher: this.webFingerLinksDispatcher,
3652
4020
  onNotFound,
3653
- tracer
4021
+ tracer,
4022
+ meterProvider: this._meterProvider
3654
4023
  });
3655
4024
  case "nodeInfoJrd": return await handleNodeInfoJrd(request, context);
3656
4025
  case "nodeInfo": return await handleNodeInfo(request, {
@@ -3736,16 +4105,18 @@ var FederationImpl = class extends FederationBuilderImpl {
3736
4105
  onNotFound
3737
4106
  });
3738
4107
  context = this.#createContext(request, contextData, { documentLoader: await context.getDocumentLoader({ identifier: route.values.identifier }) });
3739
- case "sharedInbox":
4108
+ case "sharedInbox": {
3740
4109
  if (routeName !== "inbox" && this.sharedInboxKeyDispatcher != null) {
3741
4110
  const identity = await this.sharedInboxKeyDispatcher(context);
3742
4111
  if (identity != null) context = this.#createContext(request, contextData, { documentLoader: "identifier" in identity || "username" in identity ? await context.getDocumentLoader(identity) : context.getDocumentLoader(identity) });
3743
4112
  }
3744
4113
  if (!this.manuallyStartQueue) this._startQueueInternal(contextData);
4114
+ const inboxContextFactory = context.toInboxContext.bind(context);
4115
+ inboxContextFactory[rawInboxContextFactorySymbol] = context.toInboxContext.bind(context);
3745
4116
  return await handleInbox(request, {
3746
4117
  recipient: route.values.identifier ?? null,
3747
4118
  context,
3748
- inboxContextFactory: context.toInboxContext.bind(context),
4119
+ inboxContextFactory,
3749
4120
  kv: this.kv,
3750
4121
  kvPrefixes: this.kvPrefixes,
3751
4122
  queue: this.inboxQueue,
@@ -3761,6 +4132,7 @@ var FederationImpl = class extends FederationBuilderImpl {
3761
4132
  tracerProvider: this.tracerProvider,
3762
4133
  idempotencyStrategy: this.idempotencyStrategy
3763
4134
  });
4135
+ }
3764
4136
  case "following": return await handleCollection(request, {
3765
4137
  name: "following",
3766
4138
  identifier: route.values.identifier,
@@ -4209,6 +4581,7 @@ var ContextImpl = class ContextImpl {
4209
4581
  ...options,
4210
4582
  userAgent: options.userAgent ?? this.federation.userAgent,
4211
4583
  tracerProvider: options.tracerProvider ?? this.tracerProvider,
4584
+ meterProvider: options.meterProvider ?? this.federation._meterProvider,
4212
4585
  allowPrivateAddress: this.federation.allowPrivateAddress
4213
4586
  });
4214
4587
  }
@@ -4493,6 +4866,7 @@ var ContextImpl = class ContextImpl {
4493
4866
  const routeResult = await routeActivity({
4494
4867
  context: this,
4495
4868
  json,
4869
+ ldSignatureVerified: false,
4496
4870
  activity,
4497
4871
  recipient,
4498
4872
  inboxListeners: this.federation.inboxListeners,
@@ -4781,6 +5155,14 @@ async function forwardActivityInternal(ctx, loggerCategory, forwarder, recipient
4781
5155
  }
4782
5156
  var InboxContextImpl = class InboxContextImpl extends ContextImpl {
4783
5157
  recipient;
5158
+ /**
5159
+ * The original received activity payload.
5160
+ *
5161
+ * Fedify may normalize a Linked Data Signature payload internally for safe
5162
+ * parsing, but forwarding must keep the sender's payload unchanged so
5163
+ * third-party signatures/proofs remain intact.
5164
+ * @internal
5165
+ */
4784
5166
  activity;
4785
5167
  activityId;
4786
5168
  activityType;