@fedify/fedify 1.10.9 → 1.10.11

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 (121) hide show
  1. package/dist/{actor-DSmY7of0.cjs → actor--xTQfY8h.cjs} +187 -187
  2. package/dist/{actor-CMBXPdjk.js → actor-B7XRaa54.js} +187 -187
  3. package/dist/{actor-z4bjwAPy.mjs → actor-CTpwJ1pI.mjs} +1 -1
  4. package/dist/{authdocloader-C7iVUcxr.mjs → authdocloader-DJ-h-WMy.mjs} +3 -3
  5. package/dist/{authdocloader-YXazGK4I.cjs → authdocloader-DMkkNwRc.cjs} +3 -3
  6. package/dist/{authdocloader-B4QKchy5.js → authdocloader-_h7VKnUP.js} +3 -3
  7. package/dist/{builder-C3PFAtTV.mjs → builder-Dx_LGmTI.mjs} +4 -4
  8. package/dist/{client-C-xdpu6M.mjs → client-DDy3-85E.mjs} +1 -1
  9. package/dist/compat/mod.cjs +1 -1
  10. package/dist/compat/mod.d.cts +1 -1
  11. package/dist/compat/mod.d.ts +1 -1
  12. package/dist/compat/transformers.test.mjs +3 -3
  13. package/dist/{context-BzSr5FCW.d.cts → context-CC4UrGc5.d.cts} +48 -6
  14. package/dist/{context-CyacVopT.d.ts → context-xfiK4xM5.d.ts} +48 -6
  15. package/dist/{docloader-DXUkyjIx.js → docloader-Ca6-eCr_.js} +121 -13
  16. package/dist/{docloader-9cG6ESUw.cjs → docloader-CvMstYam.cjs} +121 -13
  17. package/dist/{esm-Bfj-SwEx.mjs → esm-CzaH6D4W.mjs} +1 -1
  18. package/dist/federation/builder.test.mjs +3 -3
  19. package/dist/federation/collection.test.mjs +1 -1
  20. package/dist/federation/handler.test.mjs +1362 -46
  21. package/dist/federation/idempotency.test.mjs +5 -5
  22. package/dist/federation/inbox.test.mjs +3 -3
  23. package/dist/federation/keycache.test.mjs +3 -3
  24. package/dist/federation/kv.test.mjs +1 -1
  25. package/dist/federation/middleware.test.mjs +1586 -82
  26. package/dist/federation/mod.cjs +2 -2
  27. package/dist/federation/mod.d.cts +1 -1
  28. package/dist/federation/mod.d.ts +1 -1
  29. package/dist/federation/mod.js +1 -1
  30. package/dist/federation/mq.test.mjs +3 -3
  31. package/dist/federation/retry.test.mjs +2 -2
  32. package/dist/federation/router.test.mjs +2 -2
  33. package/dist/federation/send.test.mjs +8 -8
  34. package/dist/federation/temporal.test.d.mts +2 -0
  35. package/dist/federation/temporal.test.mjs +71 -0
  36. package/dist/{http-LjaXmp2m.cjs → http-B3o3SOXX.cjs} +3 -3
  37. package/dist/{http-CuloN7xQ.js → http-Bku28NB8.js} +3 -3
  38. package/dist/{http-D5CDiVKf.mjs → http-CiqAQGRx.mjs} +2 -2
  39. package/dist/{inbox-D0Mzd940.mjs → inbox-D3RCgSiJ.mjs} +7 -4
  40. package/dist/{key-B22mXs2u.cjs → key-BbdUjmdf.cjs} +2 -2
  41. package/dist/{key-B5QN42gR.mjs → key-C15RK-kf.mjs} +1 -1
  42. package/dist/{key-D1PkkNK-.js → key-CJU1QxP2.js} +2 -2
  43. package/dist/{key-CXkrXfz6.cjs → key-ClgfrNJf.cjs} +1 -1
  44. package/dist/{key-Dnc1ekhA.mjs → key-LJoRzwG2.mjs} +2 -2
  45. package/dist/{keycache-DwDRFAp7.mjs → keycache-BCuiTm4y.mjs} +1 -1
  46. package/dist/{keys-C4Ms2Cy-.mjs → keys-wZip1i2J.mjs} +1 -1
  47. package/dist/ld-BO_vEkla.mjs +548 -0
  48. package/dist/{lookup-D0d32cZH.cjs → lookup-Cs7LHObN.cjs} +1 -1
  49. package/dist/{lookup-B8rOR_1a.js → lookup-DRzvxZ-l.js} +1 -1
  50. package/dist/{lookup-Br7I7sqm.mjs → lookup-FvsTNqW8.mjs} +1 -1
  51. package/dist/{middleware-BlsPkr2X.mjs → middleware-Cdb7LDeG.mjs} +1 -1
  52. package/dist/{middleware-BbgpZrsX.mjs → middleware-CzjJsU2k.mjs} +320 -87
  53. package/dist/{middleware-0yq2uipZ.cjs → middleware-Dn5HfxXi.cjs} +411 -85
  54. package/dist/{middleware-CYNQWJ-D.js → middleware-DoM0y4tG.js} +408 -83
  55. package/dist/{middleware-BRXIidLb.cjs → middleware-SfuiAsBu.cjs} +1 -1
  56. package/dist/mod.cjs +10 -10
  57. package/dist/mod.d.cts +1 -1
  58. package/dist/mod.d.ts +1 -1
  59. package/dist/mod.js +9 -9
  60. package/dist/nodeinfo/client.test.mjs +3 -3
  61. package/dist/nodeinfo/handler.test.mjs +2 -2
  62. package/dist/nodeinfo/mod.cjs +1 -1
  63. package/dist/nodeinfo/mod.js +1 -1
  64. package/dist/nodeinfo/semver.test.mjs +1 -1
  65. package/dist/nodeinfo/types.test.mjs +1 -1
  66. package/dist/otel/exporter.test.mjs +1 -1
  67. package/dist/otel/mod.cjs +1 -1
  68. package/dist/{owner-D32qC-wF.mjs → owner-BRgVUFTA.mjs} +3 -3
  69. package/dist/{proof-u5ivh_eA.js → proof-BL5RXXp2.js} +300 -6
  70. package/dist/{proof-DE2Z0_uv.cjs → proof-BPEkbKl1.cjs} +347 -5
  71. package/dist/{proof-B67QOiuS.mjs → proof-De_2gCxi.mjs} +2 -2
  72. package/dist/runtime/authdocloader.test.mjs +6 -6
  73. package/dist/runtime/docloader.test.mjs +4 -4
  74. package/dist/runtime/key.test.mjs +3 -3
  75. package/dist/runtime/langstr.test.mjs +2 -2
  76. package/dist/runtime/link.test.mjs +2 -2
  77. package/dist/runtime/mod.cjs +3 -3
  78. package/dist/runtime/mod.js +3 -3
  79. package/dist/runtime/multibase/multibase.test.mjs +2 -2
  80. package/dist/runtime/url.test.mjs +53 -2
  81. package/dist/{send-7RGxOhrY.mjs → send-C7irF8CJ.mjs} +2 -2
  82. package/dist/sig/http.test.mjs +6 -6
  83. package/dist/sig/key.test.mjs +4 -4
  84. package/dist/sig/ld.test.mjs +561 -5
  85. package/dist/sig/mod.cjs +3 -3
  86. package/dist/sig/mod.js +3 -3
  87. package/dist/sig/owner.test.mjs +6 -6
  88. package/dist/sig/proof.test.mjs +4 -4
  89. package/dist/temporal-CtvhWksK.mjs +95 -0
  90. package/dist/testing/docloader.test.mjs +1 -1
  91. package/dist/testing/mod.d.mts +48 -6
  92. package/dist/testing/mod.mjs +1 -1
  93. package/dist/{testing-zWgaXBJW.mjs → testing-DskvUSfh.mjs} +2 -2
  94. package/dist/{transformers-BdbEtUZU.cjs → transformers-BbJsnOaL.cjs} +1 -1
  95. package/dist/{types-6_pWuZfK.js → types-B-GUdtNR.js} +1 -1
  96. package/dist/{types-ec4G8nGs.cjs → types-BWNGBD9D.cjs} +1 -1
  97. package/dist/vocab/actor.test.mjs +4 -4
  98. package/dist/vocab/cjs.test.mjs +1 -1
  99. package/dist/vocab/lookup.test.mjs +5 -5
  100. package/dist/vocab/mod.cjs +3 -3
  101. package/dist/vocab/mod.js +3 -3
  102. package/dist/vocab/type.test.mjs +2 -2
  103. package/dist/vocab/vocab.test.mjs +3 -3
  104. package/dist/{vocab-C60RNInZ.mjs → vocab-C0yDmtpE.mjs} +306 -198
  105. package/dist/webfinger/handler.test.mjs +3 -3
  106. package/dist/webfinger/lookup.test.mjs +3 -3
  107. package/dist/webfinger/mod.cjs +1 -1
  108. package/dist/webfinger/mod.js +1 -1
  109. package/dist/x/cfworkers.cjs +1 -1
  110. package/dist/x/cfworkers.test.mjs +1 -1
  111. package/dist/x/hono.cjs +1 -1
  112. package/dist/x/hono.d.cts +1 -1
  113. package/dist/x/hono.d.ts +1 -1
  114. package/dist/x/sveltekit.cjs +1 -1
  115. package/dist/x/sveltekit.d.cts +1 -1
  116. package/dist/x/sveltekit.d.ts +1 -1
  117. package/package.json +1 -1
  118. package/dist/ld-BJ8BZLC8.mjs +0 -254
  119. /package/dist/{denokv-NJPMXyK0.mjs → denokv-B0ZRIDsH.mjs} +0 -0
  120. /package/dist/{otel-0v4Zy01l.mjs → otel-D00WjYJ1.mjs} +0 -0
  121. /package/dist/{retry-Ddbq3AcK.mjs → retry-B_E3V_Dx.mjs} +0 -0
@@ -1,23 +1,24 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { J as getDocumentLoader, U as getTypeId, W as lookupWebFinger, Z as kvCache, a as CollectionPage, b as OrderedCollection, g as Multikey, h as Link, i as Collection, ot as name, s as CryptographicKey, st as version, t as Activity, v as Object$1, x as OrderedCollectionPage } from "./vocab-C60RNInZ.mjs";
5
- import { i as RouterError, n as traverseCollection, t as lookupObject } from "./lookup-Br7I7sqm.mjs";
6
- import { t as getNodeInfo } from "./client-C-xdpu6M.mjs";
4
+ import { J as getDocumentLoader, U as getTypeId, W as lookupWebFinger, Z as kvCache, a as CollectionPage, b as OrderedCollection, g as Multikey, h as Link, i as Collection, ot as name, s as CryptographicKey, st as version, t as Activity, v as Object$1, x as OrderedCollectionPage } from "./vocab-C0yDmtpE.mjs";
5
+ import { i as RouterError, n as traverseCollection, t as lookupObject } from "./lookup-FvsTNqW8.mjs";
6
+ import { t as getNodeInfo } from "./client-DDy3-85E.mjs";
7
7
  import { t as nodeInfoToJson } from "./types-CAnkAQGM.mjs";
8
- import "./actor-z4bjwAPy.mjs";
9
- import { a as validateCryptoKey, i as importJwk, t as exportJwk } from "./key-Dnc1ekhA.mjs";
10
- import { l as verifyRequest } from "./http-D5CDiVKf.mjs";
11
- import { t as getAuthenticatedDocumentLoader } from "./authdocloader-C7iVUcxr.mjs";
12
- import { a as signJsonLd, i as hasSignature, o as verifyJsonLd, r as detachSignature } from "./ld-BJ8BZLC8.mjs";
13
- import { n as getKeyOwner, t as doesActorOwnKey } from "./owner-D32qC-wF.mjs";
14
- import { n as signObject, r as verifyObject } from "./proof-B67QOiuS.mjs";
15
- import { n as routeActivity } from "./inbox-D0Mzd940.mjs";
16
- import { t as FederationBuilderImpl } from "./builder-C3PFAtTV.mjs";
8
+ import "./actor-CTpwJ1pI.mjs";
9
+ import { a as validateCryptoKey, i as importJwk, t as exportJwk } from "./key-LJoRzwG2.mjs";
10
+ import { l as verifyRequest } from "./http-CiqAQGRx.mjs";
11
+ import { t as getAuthenticatedDocumentLoader } from "./authdocloader-DJ-h-WMy.mjs";
12
+ import { a as compactJsonLd, c as getNormalizationContextLoader, d as isInvalidUrlTypeError, f as signJsonLd, g as wrapContextLoaderForJsonLd, l as hasSignature, p as verifyCompactJsonLd, r as assertSafeJsonLd, s as detachSignature, t as InvalidContextReferenceError, u as isClearlyMalformedContextReference } from "./ld-BO_vEkla.mjs";
13
+ import { n as getKeyOwner, t as doesActorOwnKey } from "./owner-BRgVUFTA.mjs";
14
+ import { n as signObject, r as verifyObject } from "./proof-De_2gCxi.mjs";
15
+ import { n as routeActivity } from "./inbox-D3RCgSiJ.mjs";
16
+ import { t as FederationBuilderImpl } from "./builder-Dx_LGmTI.mjs";
17
17
  import { t as buildCollectionSynchronizationHeader } from "./collection-BD6-SZ6O.mjs";
18
- import { t as KvKeyCache } from "./keycache-DwDRFAp7.mjs";
19
- import { t as createExponentialBackoffPolicy } from "./retry-Ddbq3AcK.mjs";
20
- import { n as sendActivity, t as extractInboxes } from "./send-7RGxOhrY.mjs";
18
+ import { t as KvKeyCache } from "./keycache-BCuiTm4y.mjs";
19
+ import { t as hasMalformedKnownTemporalLiteral } from "./temporal-CtvhWksK.mjs";
20
+ import { t as createExponentialBackoffPolicy } from "./retry-B_E3V_Dx.mjs";
21
+ import { n as sendActivity, t as extractInboxes } from "./send-C7irF8CJ.mjs";
21
22
  import { getLogger, withContext } from "@logtape/logtape";
22
23
  import { SpanKind, SpanStatusCode, context, propagation, trace } from "@opentelemetry/api";
23
24
  import { ATTR_HTTP_REQUEST_HEADER, ATTR_HTTP_REQUEST_METHOD, ATTR_HTTP_RESPONSE_HEADER, ATTR_HTTP_RESPONSE_STATUS_CODE, ATTR_URL_FULL } from "@opentelemetry/semantic-conventions";
@@ -337,6 +338,33 @@ function preferredMediaTypes(accept) {
337
338
  }
338
339
  //#endregion
339
340
  //#region src/federation/handler.ts
341
+ const rawInboxContextFactorySymbol = Symbol("fedify.rawInboxContextFactory");
342
+ function isRemoteContextLoadingFailure$1(error) {
343
+ return error instanceof Error && typeof error.details === "object" && error.details != null && error.details.code === "loading remote context failed";
344
+ }
345
+ function isPermanentRemoteContextError$1(error) {
346
+ if (!(error instanceof Error) || error.name !== "jsonld.InvalidUrl") return false;
347
+ const details = error.details;
348
+ if (details?.code === "invalid remote context") return true;
349
+ return isRemoteContextLoadingFailure$1(error) && typeof details?.url === "string" && !URL.canParse(details.url) && isClearlyMalformedContextReference(details.url);
350
+ }
351
+ function isInvalidJsonLdError(error) {
352
+ if (!(error instanceof Error)) return false;
353
+ const name = error.name;
354
+ return name === "UnsafeJsonLdError" || error instanceof InvalidContextReferenceError || isPermanentRemoteContextError$1(error) || name === "jsonld.SyntaxError" && !isRemoteContextLoadingFailure$1(error);
355
+ }
356
+ function isValidationTypeError(error) {
357
+ return error instanceof TypeError && (/^(Invalid JSON-LD:|Invalid type:|Unexpected type:)/.test(error.message) || isInvalidUrlTypeError(error));
358
+ }
359
+ function isPermanentActivityParseError(error) {
360
+ return isInvalidJsonLdError(error) || isValidationTypeError(error);
361
+ }
362
+ function hasHttpSignatureHeaders(request) {
363
+ return request.headers.has("Signature") || request.headers.has("Signature-Input");
364
+ }
365
+ function hasObjectIntegrityProof(json) {
366
+ return typeof json === "object" && json != null && "proof" in json;
367
+ }
340
368
  function acceptsJsonLd(request) {
341
369
  const accept = request.headers.get("Accept");
342
370
  const types = accept ? preferredMediaTypes(accept) : ["*/*"];
@@ -443,8 +471,8 @@ async function handleObject(request, { values, context, objectDispatcher, author
443
471
  * @param parameters The parameters for handling the collection.
444
472
  * @returns A promise that resolves to an HTTP response.
445
473
  */
446
- async function handleCollection(request, { name: name$2, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, tracerProvider, onUnauthorized, onNotFound, onNotAcceptable }) {
447
- const spanName = name$2.trim().replace(/\s+/g, "_");
474
+ async function handleCollection(request, { name: name$1, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, tracerProvider, onUnauthorized, onNotFound, onNotAcceptable }) {
475
+ const spanName = name$1.trim().replace(/\s+/g, "_");
448
476
  tracerProvider = tracerProvider ?? trace.getTracerProvider();
449
477
  const tracer = tracerProvider.getTracer(name, version);
450
478
  const cursor = new URL(request.url).searchParams.get("cursor");
@@ -486,7 +514,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
486
514
  collection = new OrderedCollection({
487
515
  id: baseUri,
488
516
  totalItems: totalItems == null ? null : Number(totalItems),
489
- items: filterCollectionItems(itemsOrResponse, name$2, filterPredicate)
517
+ items: filterCollectionItems(itemsOrResponse, name$1, filterPredicate)
490
518
  });
491
519
  } else {
492
520
  const lastCursor = await collectionCallbacks.lastCursor?.(context, identifier);
@@ -507,7 +535,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
507
535
  } else {
508
536
  const uri = new URL(baseUri);
509
537
  uri.searchParams.set("cursor", cursor);
510
- const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$2}`, {
538
+ const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$1}`, {
511
539
  kind: SpanKind.SERVER,
512
540
  attributes: {
513
541
  "activitypub.collection.id": uri.href,
@@ -551,7 +579,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
551
579
  id: uri,
552
580
  prev,
553
581
  next,
554
- items: filterCollectionItems(items, name$2, filterPredicate),
582
+ items: filterCollectionItems(items, name$1, filterPredicate),
555
583
  partOf
556
584
  });
557
585
  }
@@ -720,35 +748,120 @@ async function handleInboxInternal(request, parameters, span) {
720
748
  });
721
749
  }
722
750
  const keyCache = new KvKeyCache(kv, kvPrefixes.publicKey, ctx);
723
- let ldSigVerified;
724
- try {
725
- ldSigVerified = await verifyJsonLd(json, {
726
- contextLoader: ctx.contextLoader,
727
- documentLoader: ctx.documentLoader,
728
- keyCache,
729
- tracerProvider
751
+ const jsonWithoutSig = detachSignature(json);
752
+ const hasLdSignature = hasSignature(json);
753
+ const canAttemptAlternateAuthAfterLdSignatureFailure = skipSignatureVerification || hasHttpSignatureHeaders(request) || hasObjectIntegrityProof(jsonWithoutSig);
754
+ let deferredLdSignatureError = void 0;
755
+ const respondInvalidActivity = async (error) => {
756
+ logger.error("Failed to parse activity:\n{error}", {
757
+ recipient,
758
+ activity: json,
759
+ error
730
760
  });
731
- } catch (error) {
732
- if (error instanceof Error && error.name === "jsonld.SyntaxError") {
733
- logger.error("Failed to parse JSON-LD:\n{error}", {
761
+ try {
762
+ await inboxErrorHandler?.(ctx, error);
763
+ } catch (error) {
764
+ logger.error("An unexpected error occurred in inbox error handler:\n{error}", {
765
+ error,
766
+ activity: json,
767
+ recipient
768
+ });
769
+ }
770
+ span.setStatus({
771
+ code: SpanStatusCode.ERROR,
772
+ message: `Failed to parse activity:\n${error}`
773
+ });
774
+ return new Response("Invalid activity.", {
775
+ status: 400,
776
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
777
+ });
778
+ };
779
+ let compactedJson = json;
780
+ let compactedJsonWithoutSig = jsonWithoutSig;
781
+ let ldSigVerified = false;
782
+ if (hasLdSignature) {
783
+ try {
784
+ compactedJson = await compactJsonLd(json, ctx.contextLoader);
785
+ } catch (error) {
786
+ if (isInvalidJsonLdError(error)) {
787
+ logger.error("Failed to parse JSON-LD:\n{error}", {
788
+ recipient,
789
+ error
790
+ });
791
+ return new Response("Invalid JSON-LD.", {
792
+ status: 400,
793
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
794
+ });
795
+ }
796
+ if (!canAttemptAlternateAuthAfterLdSignatureFailure) throw error;
797
+ if (!skipSignatureVerification) deferredLdSignatureError = error;
798
+ logger.debug("Failed to normalize JSON-LD for Linked Data Signatures; deferring to another authentication path only if it verifies:\n{error}", {
734
799
  recipient,
735
800
  error
736
801
  });
737
- return new Response("Invalid JSON-LD.", {
738
- status: 400,
739
- headers: { "Content-Type": "text/plain; charset=utf-8" }
740
- });
741
802
  }
742
- ldSigVerified = false;
803
+ if (compactedJson !== json) {
804
+ compactedJsonWithoutSig = detachSignature(compactedJson);
805
+ try {
806
+ ldSigVerified = await verifyCompactJsonLd(compactedJson, {
807
+ contextLoader: ctx.contextLoader,
808
+ documentLoader: ctx.documentLoader,
809
+ keyCache,
810
+ tracerProvider
811
+ });
812
+ } catch (error) {
813
+ if (error instanceof RangeError && await hasMalformedKnownTemporalLiteral(compactedJsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(error);
814
+ if (isInvalidJsonLdError(error)) {
815
+ logger.error("Failed to parse JSON-LD:\n{error}", {
816
+ recipient,
817
+ error
818
+ });
819
+ return new Response("Invalid JSON-LD.", {
820
+ status: 400,
821
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
822
+ });
823
+ }
824
+ if (!canAttemptAlternateAuthAfterLdSignatureFailure) throw error;
825
+ if (!skipSignatureVerification) try {
826
+ await Object$1.fromJsonLd(compactedJson, {
827
+ contextLoader: getNormalizationContextLoader(ctx.contextLoader),
828
+ documentLoader: ctx.documentLoader,
829
+ tracerProvider
830
+ });
831
+ } catch (parseError) {
832
+ if (parseError instanceof RangeError && await hasMalformedKnownTemporalLiteral(compactedJsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(parseError);
833
+ if (isInvalidJsonLdError(parseError)) {
834
+ logger.error("Failed to parse JSON-LD:\n{error}", {
835
+ recipient,
836
+ error: parseError
837
+ });
838
+ return new Response("Invalid JSON-LD.", {
839
+ status: 400,
840
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
841
+ });
842
+ }
843
+ deferredLdSignatureError = parseError;
844
+ }
845
+ ldSigVerified = false;
846
+ }
847
+ }
743
848
  }
744
- const jsonWithoutSig = detachSignature(json);
745
849
  let activity = null;
746
850
  if (ldSigVerified) {
747
851
  logger.debug("Linked Data Signatures are verified.", {
748
852
  recipient,
749
853
  json
750
854
  });
751
- activity = await Activity.fromJsonLd(jsonWithoutSig, ctx);
855
+ try {
856
+ activity = await Activity.fromJsonLd(compactedJsonWithoutSig, {
857
+ ...ctx,
858
+ contextLoader: getNormalizationContextLoader(ctx.contextLoader)
859
+ });
860
+ } catch (error) {
861
+ if (error instanceof RangeError && await hasMalformedKnownTemporalLiteral(compactedJsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(error);
862
+ if (!isPermanentActivityParseError(error)) throw error;
863
+ return await respondInvalidActivity(error);
864
+ }
752
865
  } else {
753
866
  logger.debug("Linked Data Signatures are not verified.", {
754
867
  recipient,
@@ -756,12 +869,21 @@ async function handleInboxInternal(request, parameters, span) {
756
869
  });
757
870
  try {
758
871
  activity = await verifyObject(Activity, jsonWithoutSig, {
759
- contextLoader: ctx.contextLoader,
872
+ contextLoader: wrapContextLoaderForJsonLd(ctx.contextLoader),
760
873
  documentLoader: ctx.documentLoader,
761
874
  keyCache,
762
875
  tracerProvider
763
876
  });
764
877
  } catch (error) {
878
+ if (error instanceof RangeError && await hasMalformedKnownTemporalLiteral(jsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(error);
879
+ if (deferredLdSignatureError != null) {
880
+ logger.debug("Object Integrity Proof fallback did not supersede a deferred Linked Data Signature failure:\n{error}", {
881
+ recipient,
882
+ error
883
+ });
884
+ activity = null;
885
+ }
886
+ if (!isPermanentActivityParseError(error)) throw error;
765
887
  logger.error("Failed to parse activity:\n{error}", {
766
888
  recipient,
767
889
  activity: json,
@@ -805,6 +927,7 @@ async function handleInboxInternal(request, parameters, span) {
805
927
  tracerProvider
806
928
  });
807
929
  if (key == null) {
930
+ if (deferredLdSignatureError != null) throw deferredLdSignatureError;
808
931
  logger.error("Failed to verify the request's HTTP Signatures.", { recipient });
809
932
  span.setStatus({
810
933
  code: SpanStatusCode.ERROR,
@@ -817,7 +940,15 @@ async function handleInboxInternal(request, parameters, span) {
817
940
  } else logger.debug("HTTP Signatures are verified.", { recipient });
818
941
  httpSigKey = key;
819
942
  }
820
- activity = await Activity.fromJsonLd(jsonWithoutSig, ctx);
943
+ try {
944
+ activity = await Activity.fromJsonLd(jsonWithoutSig, {
945
+ ...ctx,
946
+ contextLoader: wrapContextLoaderForJsonLd(ctx.contextLoader)
947
+ });
948
+ } catch (error) {
949
+ if (!isPermanentActivityParseError(error)) throw error;
950
+ return await respondInvalidActivity(error);
951
+ }
821
952
  }
822
953
  if (activity.id != null) span.setAttribute("activitypub.activity.id", activity.id.href);
823
954
  span.setAttribute("activitypub.activity.type", getTypeId(activity).href);
@@ -829,6 +960,7 @@ async function handleInboxInternal(request, parameters, span) {
829
960
  "http_signatures.key_id": httpSigKey?.id?.href ?? ""
830
961
  });
831
962
  if (httpSigKey != null && !await doesActorOwnKey(activity, httpSigKey, ctx)) {
963
+ if (deferredLdSignatureError != null) throw deferredLdSignatureError;
832
964
  logger.error("The signer ({keyId}) and the actor ({actorId}) do not match.", {
833
965
  activity: json,
834
966
  recipient,
@@ -847,10 +979,14 @@ async function handleInboxInternal(request, parameters, span) {
847
979
  const routeResult = await routeActivity({
848
980
  context: ctx,
849
981
  json,
982
+ originalJson: json,
983
+ normalizedActivity: hasLdSignature && compactedJson !== json ? compactedJson : void 0,
984
+ ldSignatureVerified: hasLdSignature ? ldSigVerified : void 0,
850
985
  activity,
851
986
  recipient,
852
987
  inboxListeners,
853
988
  inboxContextFactory,
989
+ listenerInboxContextFactory: ldSigVerified ? inboxContextFactory[rawInboxContextFactorySymbol] : void 0,
854
990
  inboxErrorHandler,
855
991
  kv,
856
992
  kvPrefixes,
@@ -970,8 +1106,8 @@ var CustomCollectionHandler = class {
970
1106
  * @param CollectionPage The CollectionPage constructor.
971
1107
  * @param filterPredicate Optional filter predicate for items.
972
1108
  */
973
- constructor(name$1, values, context, callbacks, tracerProvider = trace.getTracerProvider(), Collection, CollectionPage, filterPredicate) {
974
- this.name = name$1;
1109
+ constructor(name$2, values, context, callbacks, tracerProvider = trace.getTracerProvider(), Collection, CollectionPage, filterPredicate) {
1110
+ this.name = name$2;
975
1111
  this.values = values;
976
1112
  this.context = context;
977
1113
  this.callbacks = callbacks;
@@ -1350,6 +1486,18 @@ async function respondWithObjectIfAcceptable(object, request, options) {
1350
1486
  }
1351
1487
  //#endregion
1352
1488
  //#region src/federation/middleware.ts
1489
+ function isRemoteContextLoadingFailure(error) {
1490
+ return error instanceof Error && typeof error.details === "object" && error.details != null && error.details.code === "loading remote context failed";
1491
+ }
1492
+ function isPermanentRemoteContextError(error) {
1493
+ if (!(error instanceof Error) || error.name !== "jsonld.InvalidUrl") return false;
1494
+ const details = error.details;
1495
+ if (details?.code === "invalid remote context") return true;
1496
+ return isRemoteContextLoadingFailure(error) && typeof details?.url === "string" && !URL.canParse(details.url) && isClearlyMalformedContextReference(details.url);
1497
+ }
1498
+ function isPermanentInboxParseError(error) {
1499
+ return error instanceof Error && (error.name === "UnsafeJsonLdError" || error instanceof InvalidContextReferenceError || isPermanentRemoteContextError(error) || error.name === "jsonld.SyntaxError" && !isRemoteContextLoadingFailure(error)) || error instanceof TypeError && (/^(Invalid JSON-LD:|Invalid type:|Unexpected type:)/.test(error.message) || isInvalidUrlTypeError(error));
1500
+ }
1353
1501
  /**
1354
1502
  * Create a new {@link Federation} instance.
1355
1503
  * @param parameters Parameters for initializing the instance.
@@ -1681,7 +1829,7 @@ var FederationImpl = class extends FederationBuilderImpl {
1681
1829
  }
1682
1830
  logger.info("Successfully sent activity {activityId} to {inbox}.", { ...logData });
1683
1831
  }
1684
- async #listenInboxMessage(ctxData, message, span) {
1832
+ async #listenInboxMessage(ctxData, message, _span) {
1685
1833
  const logger = getLogger([
1686
1834
  "fedify",
1687
1835
  "federation",
@@ -1694,60 +1842,28 @@ var FederationImpl = class extends FederationBuilderImpl {
1694
1842
  const identity = await this.sharedInboxKeyDispatcher(context);
1695
1843
  if (identity != null) context = this.#createContext(baseUrl, ctxData, { documentLoader: "identifier" in identity || "username" in identity || "handle" in identity ? await context.getDocumentLoader(identity) : context.getDocumentLoader(identity) });
1696
1844
  }
1697
- const activity = await Activity.fromJsonLd(message.activity, context);
1698
- span.setAttribute("activitypub.activity.type", getTypeId(activity).href);
1699
- if (activity.id != null) span.setAttribute("activitypub.activity.id", activity.id.href);
1700
- const cacheKey = activity.id == null ? null : [
1701
- ...this.kvPrefixes.activityIdempotence,
1702
- context.origin,
1703
- activity.id.href
1704
- ];
1705
- if (cacheKey != null) {
1706
- if (await this.kv.get(cacheKey) === true) {
1707
- logger.debug("Activity {activityId} has already been processed.", {
1708
- activityId: activity.id?.href,
1709
- activity: message.activity,
1710
- recipient: message.identifier
1711
- });
1712
- return;
1713
- }
1714
- }
1715
1845
  await this._getTracer().startActiveSpan("activitypub.dispatch_inbox_listener", { kind: SpanKind.INTERNAL }, async (span) => {
1716
- const dispatched = this.inboxListeners?.dispatchWithClass(activity);
1717
- if (dispatched == null) {
1718
- logger.error("Unsupported activity type:\n{activity}", {
1719
- activityId: activity.id?.href,
1720
- activity: message.activity,
1721
- recipient: message.identifier,
1722
- trial: message.attempt
1723
- });
1724
- span.setStatus({
1725
- code: SpanStatusCode.ERROR,
1726
- message: `Unsupported activity type: ${getTypeId(activity).href}`
1727
- });
1728
- span.end();
1729
- return;
1730
- }
1731
- const { class: cls, listener } = dispatched;
1732
- span.updateName(`activitypub.dispatch_inbox_listener ${cls.name}`);
1733
- try {
1734
- await listener(context.toInboxContext(message.identifier, message.activity, activity.id?.href, getTypeId(activity).href), activity);
1735
- } catch (error) {
1846
+ let activity = null;
1847
+ let cacheKey = null;
1848
+ const reportInboxError = async (error) => {
1736
1849
  try {
1737
1850
  await this.inboxErrorHandler?.(context, error);
1738
1851
  } catch (error) {
1739
1852
  logger.error("An unexpected error occurred in inbox error handler:\n{error}", {
1740
1853
  error,
1741
1854
  trial: message.attempt,
1742
- activityId: activity.id?.href,
1855
+ activityId: activity?.id?.href,
1743
1856
  activity: message.activity,
1744
1857
  recipient: message.identifier
1745
1858
  });
1746
1859
  }
1860
+ };
1861
+ const handleRetriableFailure = async (error) => {
1862
+ await reportInboxError(error);
1747
1863
  if (this.inboxQueue?.nativeRetrial) {
1748
1864
  logger.error("Failed to process the incoming activity {activityId}; backend will handle retry:\n{error}", {
1749
1865
  error,
1750
- activityId: activity.id?.href,
1866
+ activityId: activity?.id?.href,
1751
1867
  activity: message.activity,
1752
1868
  recipient: message.identifier
1753
1869
  });
@@ -1766,17 +1882,25 @@ var FederationImpl = class extends FederationBuilderImpl {
1766
1882
  logger.error("Failed to process the incoming activity {activityId} (attempt #{attempt}); retry...:\n{error}", {
1767
1883
  error,
1768
1884
  attempt: message.attempt,
1769
- activityId: activity.id?.href,
1885
+ activityId: activity?.id?.href,
1770
1886
  activity: message.activity,
1771
1887
  recipient: message.identifier
1772
1888
  });
1889
+ if (this.inboxQueue == null) {
1890
+ span.setStatus({
1891
+ code: SpanStatusCode.ERROR,
1892
+ message: String(error)
1893
+ });
1894
+ span.end();
1895
+ throw error;
1896
+ }
1773
1897
  await this.inboxQueue?.enqueue({
1774
1898
  ...message,
1775
1899
  attempt: message.attempt + 1
1776
1900
  }, { delay: Temporal.Duration.compare(delay, { seconds: 0 }) < 0 ? Temporal.Duration.from({ seconds: 0 }) : delay });
1777
1901
  } else logger.error("Failed to process the incoming activity {activityId} after {trial} attempts; giving up:\n{error}", {
1778
1902
  error,
1779
- activityId: activity.id?.href,
1903
+ activityId: activity?.id?.href,
1780
1904
  activity: message.activity,
1781
1905
  recipient: message.identifier
1782
1906
  });
@@ -1785,11 +1909,108 @@ var FederationImpl = class extends FederationBuilderImpl {
1785
1909
  message: String(error)
1786
1910
  });
1787
1911
  span.end();
1912
+ };
1913
+ let dispatched;
1914
+ let parseInput = void 0;
1915
+ let parseContextLoader = context.contextLoader;
1916
+ try {
1917
+ const hasSignatureField = hasSignature(message.activity);
1918
+ const shouldParseFromNormalizedSignedPayload = message.ldSignatureVerified === true || message.normalizedActivity != null || message.ldSignatureVerified == null && hasSignatureField;
1919
+ const parseContext = hasSignatureField ? {
1920
+ ...context,
1921
+ contextLoader: getNormalizationContextLoader(context.contextLoader)
1922
+ } : {
1923
+ ...context,
1924
+ contextLoader: wrapContextLoaderForJsonLd(context.contextLoader)
1925
+ };
1926
+ parseContextLoader = parseContext.contextLoader;
1927
+ let normalizedActivity;
1928
+ if (shouldParseFromNormalizedSignedPayload) {
1929
+ normalizedActivity = message.normalizedActivity ?? await compactJsonLd(message.activity, context.contextLoader);
1930
+ assertSafeJsonLd(normalizedActivity);
1931
+ }
1932
+ parseInput = shouldParseFromNormalizedSignedPayload ? detachSignature(normalizedActivity) : hasSignatureField ? detachSignature(message.activity) : message.activity;
1933
+ activity = await Activity.fromJsonLd(parseInput, parseContext);
1934
+ span.setAttribute("activitypub.activity.type", getTypeId(activity).href);
1935
+ if (activity.id != null) span.setAttribute("activitypub.activity.id", activity.id.href);
1936
+ cacheKey = activity.id == null ? null : [
1937
+ ...this.kvPrefixes.activityIdempotence,
1938
+ context.origin,
1939
+ activity.id.href
1940
+ ];
1941
+ if (cacheKey != null) {
1942
+ if (await this.kv.get(cacheKey) === true) {
1943
+ logger.debug("Activity {activityId} has already been processed.", {
1944
+ activityId: activity.id?.href,
1945
+ activity: message.activity,
1946
+ recipient: message.identifier
1947
+ });
1948
+ span.end();
1949
+ return;
1950
+ }
1951
+ }
1952
+ dispatched = this.inboxListeners?.dispatchWithClass(activity);
1953
+ } catch (error) {
1954
+ if (activity == null && error instanceof RangeError && await hasMalformedKnownTemporalLiteral(parseInput, parseContextLoader)) {
1955
+ await reportInboxError(error);
1956
+ logger.error("Failed to parse the queued incoming activity {activityId}:\n{error}", {
1957
+ error,
1958
+ trial: message.attempt,
1959
+ activityId: null,
1960
+ activity: message.activity,
1961
+ recipient: message.identifier
1962
+ });
1963
+ span.setStatus({
1964
+ code: SpanStatusCode.ERROR,
1965
+ message: String(error)
1966
+ });
1967
+ span.end();
1968
+ return;
1969
+ }
1970
+ if (isPermanentInboxParseError(error)) {
1971
+ await reportInboxError(error);
1972
+ logger.error("Failed to parse the queued incoming activity {activityId}:\n{error}", {
1973
+ error,
1974
+ trial: message.attempt,
1975
+ activityId: activity?.id?.href,
1976
+ activity: message.activity,
1977
+ recipient: message.identifier
1978
+ });
1979
+ span.setStatus({
1980
+ code: SpanStatusCode.ERROR,
1981
+ message: String(error)
1982
+ });
1983
+ span.end();
1984
+ return;
1985
+ }
1986
+ await handleRetriableFailure(error);
1987
+ return;
1988
+ }
1989
+ if (dispatched == null) {
1990
+ logger.error("Unsupported activity type:\n{activity}", {
1991
+ activityId: activity.id?.href,
1992
+ activity: message.activity,
1993
+ recipient: message.identifier,
1994
+ trial: message.attempt
1995
+ });
1996
+ span.setStatus({
1997
+ code: SpanStatusCode.ERROR,
1998
+ message: `Unsupported activity type: ${getTypeId(activity).href}`
1999
+ });
2000
+ span.end();
2001
+ return;
2002
+ }
2003
+ const { class: cls, listener } = dispatched;
2004
+ span.updateName(`activitypub.dispatch_inbox_listener ${cls.name}`);
2005
+ try {
2006
+ await listener(context.toInboxContext(message.identifier, message.activity, activity.id?.href, getTypeId(activity).href), activity);
2007
+ } catch (error) {
2008
+ await handleRetriableFailure(error);
1788
2009
  return;
1789
2010
  }
1790
2011
  if (cacheKey != null) await this.kv.set(cacheKey, true, { ttl: Temporal.Duration.from({ days: 1 }) });
1791
2012
  logger.info("Activity {activityId} has been processed.", {
1792
- activityId: activity.id?.href,
2013
+ activityId: activity?.id?.href,
1793
2014
  activity: message.activity,
1794
2015
  recipient: message.identifier
1795
2016
  });
@@ -2111,16 +2332,18 @@ var FederationImpl = class extends FederationBuilderImpl {
2111
2332
  onNotAcceptable
2112
2333
  });
2113
2334
  context = this.#createContext(request, contextData, { documentLoader: await context.getDocumentLoader({ identifier: route.values.identifier ?? route.values.handle }) });
2114
- case "sharedInbox":
2335
+ case "sharedInbox": {
2115
2336
  if (routeName !== "inbox" && this.sharedInboxKeyDispatcher != null) {
2116
2337
  const identity = await this.sharedInboxKeyDispatcher(context);
2117
2338
  if (identity != null) context = this.#createContext(request, contextData, { documentLoader: "identifier" in identity || "username" in identity || "handle" in identity ? await context.getDocumentLoader(identity) : context.getDocumentLoader(identity) });
2118
2339
  }
2119
2340
  if (!this.manuallyStartQueue) this._startQueueInternal(contextData);
2341
+ const inboxContextFactory = context.toInboxContext.bind(context);
2342
+ inboxContextFactory[rawInboxContextFactorySymbol] = context.toInboxContext.bind(context);
2120
2343
  return await handleInbox(request, {
2121
2344
  recipient: route.values.identifier ?? route.values.handle ?? null,
2122
2345
  context,
2123
- inboxContextFactory: context.toInboxContext.bind(context),
2346
+ inboxContextFactory,
2124
2347
  kv: this.kv,
2125
2348
  kvPrefixes: this.kvPrefixes,
2126
2349
  queue: this.inboxQueue,
@@ -2133,6 +2356,7 @@ var FederationImpl = class extends FederationBuilderImpl {
2133
2356
  tracerProvider: this.tracerProvider,
2134
2357
  idempotencyStrategy: this.idempotencyStrategy
2135
2358
  });
2359
+ }
2136
2360
  case "following": return await handleCollection(request, {
2137
2361
  name: "following",
2138
2362
  identifier: route.values.identifier ?? route.values.handle,
@@ -2883,6 +3107,7 @@ var ContextImpl = class ContextImpl {
2883
3107
  const routeResult = await routeActivity({
2884
3108
  context: this,
2885
3109
  json,
3110
+ ldSignatureVerified: false,
2886
3111
  activity,
2887
3112
  recipient,
2888
3113
  inboxListeners: this.federation.inboxListeners,
@@ -2981,6 +3206,14 @@ var RequestContextImpl = class RequestContextImpl extends ContextImpl {
2981
3206
  };
2982
3207
  var InboxContextImpl = class InboxContextImpl extends ContextImpl {
2983
3208
  recipient;
3209
+ /**
3210
+ * The original received activity payload.
3211
+ *
3212
+ * Fedify may normalize a Linked Data Signature payload internally for safe
3213
+ * parsing, but forwarding must keep the sender's payload unchanged so
3214
+ * third-party signatures/proofs remain intact.
3215
+ * @internal
3216
+ */
2984
3217
  activity;
2985
3218
  activityId;
2986
3219
  activityType;