@fedify/fedify 2.3.0-dev.1145 → 2.3.0-dev.1154

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-ShiR1K6b.mjs → builder-BwSH45lU.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-h0TWFuEz.mjs → deno-Aas8ryCk.mjs} +1 -1
  8. package/dist/{docloader-BdDN0Aqx.mjs → docloader-_1lh7Dn1.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 +1 -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 +1 -1
  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-B2hxA7dO.js → http-CP1Qje2Q.js} +1 -1
  27. package/dist/{http-QzW9IWfs.mjs → http-DUBr4pJL.mjs} +3 -3
  28. package/dist/{http-7kAB7PVx.cjs → http-bAPHYmg8.cjs} +1 -1
  29. package/dist/{key-Dh2OK1XQ.mjs → key-DVh4I9kS.mjs} +2 -2
  30. package/dist/{kv-cache-DCPp-MT0.cjs → kv-cache-2zYOM6Q7.cjs} +1 -1
  31. package/dist/{kv-cache-EZRIPZXD.mjs → kv-cache-6CA6Rx92.mjs} +1 -1
  32. package/dist/{kv-cache-b22dNkjt.js → kv-cache-azKKIdQE.js} +1 -1
  33. package/dist/{ld-eZbar1rr.mjs → ld-D9435Gn1.mjs} +302 -6
  34. package/dist/{metrics-E0hAHtLZ.mjs → metrics-K7CyLZhK.mjs} +1 -1
  35. package/dist/{middleware-mToCR2tG.mjs → middleware-Bn8SYmaa.mjs} +1 -1
  36. package/dist/{middleware-BUl1BH4x.cjs → middleware-Cu8Lw81i.cjs} +429 -99
  37. package/dist/{middleware-CyJDCmNg.mjs → middleware-DZjXGmiF.mjs} +348 -108
  38. package/dist/{middleware-BrGIM_Ra.js → middleware-tbfw9GfY.js} +428 -99
  39. package/dist/{mod-CI9fduEi.d.cts → mod-C7HOzGqH.d.cts} +1 -1
  40. package/dist/{mod-CkRiJHGA.d.ts → mod-CpQHB3Ys.d.ts} +1 -1
  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-ByO_Fw6U.mjs → owner-Bt9Zdipr.mjs} +2 -2
  47. package/dist/{proof-jVqClF49.cjs → proof-3YeQ4Z5A.cjs} +353 -3
  48. package/dist/{proof-CSo0S8OK.mjs → proof-CfevUec1.mjs} +3 -3
  49. package/dist/{proof-BkRyFchv.js → proof-CqwCBFaT.js} +300 -4
  50. package/dist/{send-jzrTV1FU.mjs → send-CuS5FYIt.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-BacpfwuJ.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-7kAB7PVx.cjs");
6
- const require_proof = require("./proof-jVqClF49.cjs");
5
+ const require_http = require("./http-bAPHYmg8.cjs");
6
+ const require_proof = require("./proof-3YeQ4Z5A.cjs");
7
7
  const require_types = require("./types-KC4QAoxe.cjs");
8
- const require_kv_cache = require("./kv-cache-DCPp-MT0.cjs");
8
+ const require_kv_cache = require("./kv-cache-2zYOM6Q7.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;
@@ -2782,6 +3011,18 @@ var middleware_exports = /* @__PURE__ */ require_chunk.__exportAll({
2782
3011
  OutboxContextImpl: () => OutboxContextImpl,
2783
3012
  createFederation: () => createFederation
2784
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
+ }
2785
3026
  /**
2786
3027
  * Create a new {@link Federation} instance.
2787
3028
  * @param parameters Parameters for initializing the instance.
@@ -3284,78 +3525,37 @@ var FederationImpl = class extends FederationBuilderImpl {
3284
3525
  const identity = await this.sharedInboxKeyDispatcher(context);
3285
3526
  if (identity != null) context = this.#createContext(baseUrl, ctxData, { documentLoader: "identifier" in identity || "username" in identity ? await context.getDocumentLoader(identity) : context.getDocumentLoader(identity) });
3286
3527
  }
3287
- const activity = await _fedify_vocab.Activity.fromJsonLd(message.activity, context);
3288
- const activityType = (0, _fedify_vocab.getTypeId)(activity).href;
3289
- span.setAttribute("activitypub.activity.type", activityType);
3290
- onActivityType?.(activityType);
3291
- if (activity.id != null) span.setAttribute("activitypub.activity.id", activity.id.href);
3292
- const cacheKey = activity.id == null ? null : [
3293
- ...this.kvPrefixes.activityIdempotence,
3294
- context.origin,
3295
- activity.id.href
3296
- ];
3297
- if (cacheKey != null) {
3298
- if (await this.kv.get(cacheKey) === true) {
3299
- logger.debug("Activity {activityId} has already been processed.", {
3300
- activityId: activity.id?.href,
3301
- activity: message.activity,
3302
- recipient: message.identifier
3303
- });
3304
- require_http.recordInboxActivity(this.meterProvider, "rejected", activityType);
3305
- return;
3306
- }
3307
- }
3308
- await this._getTracer().startActiveSpan("activitypub.dispatch_inbox_listener", { kind: _opentelemetry_api.SpanKind.INTERNAL }, async (span) => {
3309
- const dispatched = this.inboxListeners?.dispatchWithClass(activity);
3310
- if (dispatched == null) {
3311
- logger.error("Unsupported activity type:\n{activity}", {
3312
- activityId: activity.id?.href,
3313
- activity: message.activity,
3314
- recipient: message.identifier,
3315
- trial: message.attempt
3316
- });
3317
- span.setStatus({
3318
- code: _opentelemetry_api.SpanStatusCode.ERROR,
3319
- message: `Unsupported activity type: ${activityType}`
3320
- });
3321
- require_http.recordInboxActivity(this.meterProvider, "rejected", activityType);
3322
- span.end();
3323
- return;
3324
- }
3325
- const { class: cls, listener } = dispatched;
3326
- span.updateName(`activitypub.dispatch_inbox_listener ${cls.name}`);
3327
- try {
3328
- const started = performance.now();
3329
- try {
3330
- await listener(context.toInboxContext(message.identifier, message.activity, activity.id?.href, activityType), activity);
3331
- } finally {
3332
- require_http.getFederationMetrics(this.meterProvider).recordInboxProcessingDuration(activityType, require_http.getDurationMs(started));
3333
- }
3334
- require_http.recordInboxActivity(this.meterProvider, "processed", activityType);
3335
- } 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) => {
3336
3533
  try {
3337
3534
  await this.inboxErrorHandler?.(context, error);
3338
3535
  } catch (error) {
3339
3536
  logger.error("An unexpected error occurred in inbox error handler:\n{error}", {
3340
3537
  error,
3341
3538
  trial: message.attempt,
3342
- activityId: activity.id?.href,
3539
+ activityId: activity?.id?.href,
3343
3540
  activity: message.activity,
3344
3541
  recipient: message.identifier
3345
3542
  });
3346
3543
  }
3544
+ };
3545
+ const handleRetriableFailure = async (error) => {
3546
+ await reportInboxError(error);
3347
3547
  if (this.inboxQueue?.nativeRetrial) {
3348
3548
  logger.error("Failed to process the incoming activity {activityId}; backend will handle retry:\n{error}", {
3349
3549
  error,
3350
- activityId: activity.id?.href,
3550
+ activityId: activity?.id?.href,
3351
3551
  activity: message.activity,
3352
3552
  recipient: message.identifier
3353
3553
  });
3354
- span.setStatus({
3554
+ listenerSpan.setStatus({
3355
3555
  code: _opentelemetry_api.SpanStatusCode.ERROR,
3356
3556
  message: String(error)
3357
3557
  });
3358
- span.end();
3558
+ listenerSpan.end();
3359
3559
  throw error;
3360
3560
  }
3361
3561
  const delay = this.inboxRetryPolicy({
@@ -3366,20 +3566,27 @@ var FederationImpl = class extends FederationBuilderImpl {
3366
3566
  logger.error("Failed to process the incoming activity {activityId} (attempt #{attempt}); retry...:\n{error}", {
3367
3567
  error,
3368
3568
  attempt: message.attempt,
3369
- activityId: activity.id?.href,
3569
+ activityId: activity?.id?.href,
3370
3570
  activity: message.activity,
3371
3571
  recipient: message.identifier
3372
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
+ }
3373
3581
  const retryMessage = {
3374
3582
  ...message,
3375
3583
  attempt: message.attempt + 1
3376
3584
  };
3377
- const { inboxQueue } = this;
3378
- if (inboxQueue != null) {
3379
- 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) {
3380
3587
  require_http.getFederationMetrics(this.meterProvider).recordQueueTaskEnqueued({
3381
3588
  role: "inbox",
3382
- queue: inboxQueue,
3589
+ queue: this.inboxQueue,
3383
3590
  activityType
3384
3591
  }, retryMessage.attempt);
3385
3592
  require_http.recordInboxActivity(this.meterProvider, "retried", activityType);
@@ -3387,26 +3594,137 @@ var FederationImpl = class extends FederationBuilderImpl {
3387
3594
  } else {
3388
3595
  logger.error("Failed to process the incoming activity {activityId} after {trial} attempts; giving up:\n{error}", {
3389
3596
  error,
3390
- activityId: activity.id?.href,
3597
+ activityId: activity?.id?.href,
3391
3598
  activity: message.activity,
3392
3599
  recipient: message.identifier
3393
3600
  });
3394
- require_http.recordInboxActivity(this.meterProvider, "abandoned", activityType);
3601
+ if (activityType != null) require_http.recordInboxActivity(this.meterProvider, "abandoned", activityType);
3395
3602
  }
3396
- span.setStatus({
3603
+ listenerSpan.setStatus({
3397
3604
  code: _opentelemetry_api.SpanStatusCode.ERROR,
3398
3605
  message: String(error)
3399
3606
  });
3400
- 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);
3401
3719
  return;
3402
3720
  }
3403
3721
  if (cacheKey != null) await this.kv.set(cacheKey, true, { ttl: Temporal.Duration.from({ days: 1 }) });
3404
3722
  logger.info("Activity {activityId} has been processed.", {
3405
- activityId: activity.id?.href,
3723
+ activityId: activity?.id?.href,
3406
3724
  activity: message.activity,
3407
3725
  recipient: message.identifier
3408
3726
  });
3409
- span.end();
3727
+ listenerSpan.end();
3410
3728
  });
3411
3729
  }
3412
3730
  startQueue(contextData, options = {}) {
@@ -3787,16 +4105,18 @@ var FederationImpl = class extends FederationBuilderImpl {
3787
4105
  onNotFound
3788
4106
  });
3789
4107
  context = this.#createContext(request, contextData, { documentLoader: await context.getDocumentLoader({ identifier: route.values.identifier }) });
3790
- case "sharedInbox":
4108
+ case "sharedInbox": {
3791
4109
  if (routeName !== "inbox" && this.sharedInboxKeyDispatcher != null) {
3792
4110
  const identity = await this.sharedInboxKeyDispatcher(context);
3793
4111
  if (identity != null) context = this.#createContext(request, contextData, { documentLoader: "identifier" in identity || "username" in identity ? await context.getDocumentLoader(identity) : context.getDocumentLoader(identity) });
3794
4112
  }
3795
4113
  if (!this.manuallyStartQueue) this._startQueueInternal(contextData);
4114
+ const inboxContextFactory = context.toInboxContext.bind(context);
4115
+ inboxContextFactory[rawInboxContextFactorySymbol] = context.toInboxContext.bind(context);
3796
4116
  return await handleInbox(request, {
3797
4117
  recipient: route.values.identifier ?? null,
3798
4118
  context,
3799
- inboxContextFactory: context.toInboxContext.bind(context),
4119
+ inboxContextFactory,
3800
4120
  kv: this.kv,
3801
4121
  kvPrefixes: this.kvPrefixes,
3802
4122
  queue: this.inboxQueue,
@@ -3812,6 +4132,7 @@ var FederationImpl = class extends FederationBuilderImpl {
3812
4132
  tracerProvider: this.tracerProvider,
3813
4133
  idempotencyStrategy: this.idempotencyStrategy
3814
4134
  });
4135
+ }
3815
4136
  case "following": return await handleCollection(request, {
3816
4137
  name: "following",
3817
4138
  identifier: route.values.identifier,
@@ -4545,6 +4866,7 @@ var ContextImpl = class ContextImpl {
4545
4866
  const routeResult = await routeActivity({
4546
4867
  context: this,
4547
4868
  json,
4869
+ ldSignatureVerified: false,
4548
4870
  activity,
4549
4871
  recipient,
4550
4872
  inboxListeners: this.federation.inboxListeners,
@@ -4833,6 +5155,14 @@ async function forwardActivityInternal(ctx, loggerCategory, forwarder, recipient
4833
5155
  }
4834
5156
  var InboxContextImpl = class InboxContextImpl extends ContextImpl {
4835
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
+ */
4836
5166
  activity;
4837
5167
  activityId;
4838
5168
  activityType;