@fedify/fedify 2.4.0-dev.2030 → 2.4.0-dev.2047

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 (75) hide show
  1. package/dist/{builder-BYJ5otJa.mjs → builder-Cue1kVHU.mjs} +12 -11
  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/{compound-proof-BIvUoJxm.mjs → compound-proof-YT4tajyj.mjs} +1 -1
  6. package/dist/{context-uyRUWoZQ.d.ts → context-BJPHhBmD.d.ts} +100 -2
  7. package/dist/{context-BeDnDuF3.d.cts → context-Bh_mJfbD.d.cts} +100 -2
  8. package/dist/{context-CbS35fLr.mjs → context-DG5QeQ8W.mjs} +3 -1
  9. package/dist/{deno-Vj_9wePq.mjs → deno-Dcj6O4LJ.mjs} +1 -1
  10. package/dist/{docloader-qUudHZVH.mjs → docloader-BRlNjHlQ.mjs} +2 -2
  11. package/dist/federation/builder.test.mjs +1 -1
  12. package/dist/federation/compound-fanout.test.mjs +3 -3
  13. package/dist/federation/handler-compound-proof.test.mjs +4 -4
  14. package/dist/federation/handler.test.mjs +6 -6
  15. package/dist/federation/idempotency.test.mjs +2 -2
  16. package/dist/federation/metrics.test.mjs +1 -1
  17. package/dist/federation/middleware.test.mjs +8 -8
  18. package/dist/federation/mod.cjs +1 -1
  19. package/dist/federation/mod.d.cts +3 -3
  20. package/dist/federation/mod.d.ts +3 -3
  21. package/dist/federation/mod.js +1 -1
  22. package/dist/federation/outgoing-proof.test.mjs +5 -5
  23. package/dist/federation/portable.test.d.mts +2 -0
  24. package/dist/federation/portable.test.mjs +430 -0
  25. package/dist/federation/send.test.mjs +3 -3
  26. package/dist/federation/tasks/enqueue.test.mjs +2 -2
  27. package/dist/federation/tasks/tasks.test.mjs +3 -3
  28. package/dist/federation/temporal.test.mjs +1 -1
  29. package/dist/federation/webfinger.test.mjs +115 -2
  30. package/dist/{federation-CTnaljmy.cjs → federation-Bdu06LEl.cjs} +324 -26
  31. package/dist/{http-By8tnmEr.js → http-BmdqHyms.js} +1 -1
  32. package/dist/{http-B5F1JMh0.cjs → http-CGjvl34X.cjs} +1 -1
  33. package/dist/{http-D4WPcJUe.mjs → http-CK6kjLnO.mjs} +3 -3
  34. package/dist/{key-B_Zc5Jbd.mjs → key-CzCAq6D9.mjs} +2 -2
  35. package/dist/{kv-cache-8K70pjtX.cjs → kv-cache-CvEicbrf.cjs} +1 -1
  36. package/dist/{kv-cache-BXdT7pJQ.mjs → kv-cache-YATuzCy-.mjs} +1 -1
  37. package/dist/{kv-cache-BswMKQjC.js → kv-cache-fi_-XZco.js} +1 -1
  38. package/dist/{ld-B16KUSIQ.mjs → ld-CNWm8mJc.mjs} +3 -3
  39. package/dist/{metrics-DpQqUAmh.mjs → metrics-CDoBPWjf.mjs} +1 -1
  40. package/dist/{middleware-BL5HFFVU.mjs → middleware-CF6uz8wa.mjs} +1 -1
  41. package/dist/{middleware-C7_hqDnl.mjs → middleware-CtFNjIUD.mjs} +326 -28
  42. package/dist/{middleware-Cxto6Bq2.js → middleware-Drzx-OID.js} +325 -27
  43. package/dist/{mod-D882_Xsg.d.ts → mod-BofpDIcU.d.ts} +1 -1
  44. package/dist/{mod-x9W0d0Pk.d.cts → mod-DU8aVzQJ.d.cts} +1 -1
  45. package/dist/mod.cjs +4 -4
  46. package/dist/mod.d.cts +4 -4
  47. package/dist/mod.d.ts +4 -4
  48. package/dist/mod.js +4 -4
  49. package/dist/nodeinfo/handler.test.mjs +2 -2
  50. package/dist/{owner-BIOIhZoM.mjs → owner-B3PgGiJ1.mjs} +2 -2
  51. package/dist/{owner-D-E0bT2O.js → owner-CMnmv7S9.js} +16 -3
  52. package/dist/{owner-WUYke16i.cjs → owner-Hurn_ocn.cjs} +16 -3
  53. package/dist/{proof-BVxJu5vv.mjs → proof-fKycdssC.mjs} +18 -5
  54. package/dist/{send-Cr5FJ7iu.mjs → send-BqBILxYE.mjs} +3 -3
  55. package/dist/sig/compound-proof-discovery.test.mjs +1 -1
  56. package/dist/sig/compound-proof-producer.test.mjs +1 -1
  57. package/dist/sig/compound-proof-verification.test.mjs +2 -2
  58. package/dist/sig/compound-proof.test.mjs +2 -2
  59. package/dist/sig/http.test.mjs +2 -2
  60. package/dist/sig/key.test.mjs +1 -1
  61. package/dist/sig/ld.test.mjs +2 -2
  62. package/dist/sig/mod.cjs +2 -2
  63. package/dist/sig/mod.js +2 -2
  64. package/dist/sig/owner.test.mjs +1 -1
  65. package/dist/sig/proof.test.mjs +208 -1
  66. package/dist/{temporal-CCf0lZV3.mjs → temporal-SUiLJ0Co.mjs} +1 -1
  67. package/dist/testing/kv.test.mjs +1 -1
  68. package/dist/testing/mod.d.mts +99 -1
  69. package/dist/testing/mod.mjs +2 -2
  70. package/dist/{testing-hbky2gMN.mjs → testing-By1YYsIK.mjs} +1 -1
  71. package/dist/utils/docloader.test.mjs +2 -2
  72. package/dist/utils/kv-cache.test.mjs +1 -1
  73. package/dist/utils/mod.cjs +1 -1
  74. package/dist/utils/mod.js +1 -1
  75. package/package.json +7 -7
@@ -1,27 +1,27 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-Vj_9wePq.mjs";
5
- import { a as instrumentDocumentLoader, b as recordWebFingerHandle, c as recordCircuitBreakerStateChange, d as recordCollectionRequest, f as recordCollectionTotalItems, g as recordInboxActivity, h as recordFanoutRecipients, i as getRemoteHost, l as recordCollectionDispatchDuration, n as getDurationMs, o as isAbortError, r as getFederationMetrics, u as recordCollectionPageItems, v as recordOutboxActivity, x as registerQueueDepthGauge, y as recordOutboxEnqueue } from "./metrics-DpQqUAmh.mjs";
4
+ import { n as version, t as name } from "./deno-Dcj6O4LJ.mjs";
5
+ import { a as instrumentDocumentLoader, b as recordWebFingerHandle, c as recordCircuitBreakerStateChange, d as recordCollectionRequest, f as recordCollectionTotalItems, g as recordInboxActivity, h as recordFanoutRecipients, i as getRemoteHost, l as recordCollectionDispatchDuration, n as getDurationMs, o as isAbortError, r as getFederationMetrics, u as recordCollectionPageItems, v as recordOutboxActivity, x as registerQueueDepthGauge, y as recordOutboxEnqueue } from "./metrics-CDoBPWjf.mjs";
6
6
  import { t as formatAcceptSignature } from "./accept-CPVqki78.mjs";
7
- import { o as importJwk, s as validateCryptoKey, t as exportJwk } from "./key-B_Zc5Jbd.mjs";
8
- import { l as verifyRequest, o as parseRfc9421SignatureInput, u as verifyRequestDetailed } from "./http-D4WPcJUe.mjs";
9
- import { t as getAuthenticatedDocumentLoader } from "./docloader-qUudHZVH.mjs";
10
- import { n as kvCache } from "./kv-cache-BXdT7pJQ.mjs";
7
+ import { o as importJwk, s as validateCryptoKey, t as exportJwk } from "./key-CzCAq6D9.mjs";
8
+ import { l as verifyRequest, o as parseRfc9421SignatureInput, u as verifyRequestDetailed } from "./http-CK6kjLnO.mjs";
9
+ import { t as getAuthenticatedDocumentLoader } from "./docloader-BRlNjHlQ.mjs";
10
+ import { n as kvCache } from "./kv-cache-YATuzCy-.mjs";
11
11
  import { r as normalizeOutgoingActivityJsonLd } from "./outgoing-jsonld-4IJqPYos.mjs";
12
12
  import { d as readBoundedText, l as BodyTooLargeError, t as getNodeInfo, u as MAX_BODY_SIZE } from "./client-DpnpgjyX.mjs";
13
13
  import { t as nodeInfoToJson } from "./types-DE_LV1JB.mjs";
14
- import { _ as wrapContextLoaderForJsonLd, a as compactJsonLd, c as getNormalizationContextLoader, d as isClearlyMalformedContextReference, f as isInvalidUrlTypeError, l as hasSignature, m as verifyCompactJsonLd, p as signJsonLd, r as assertSafeJsonLd, s as detachSignature, t as InvalidContextReferenceError, u as hasSignatureLike } from "./ld-B16KUSIQ.mjs";
15
- import { n as getKeyOwner, t as doesActorOwnKey } from "./owner-BIOIhZoM.mjs";
16
- import { a as verifyObject, n as hasProofLike, r as signObject } from "./proof-BVxJu5vv.mjs";
17
- import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "./send-Cr5FJ7iu.mjs";
18
- import { n as FederationBuilderImpl, t as ACTOR_ALIAS_PREFIX } from "./builder-BYJ5otJa.mjs";
14
+ import { _ as wrapContextLoaderForJsonLd, a as compactJsonLd, c as getNormalizationContextLoader, d as isClearlyMalformedContextReference, f as isInvalidUrlTypeError, l as hasSignature, m as verifyCompactJsonLd, p as signJsonLd, r as assertSafeJsonLd, s as detachSignature, t as InvalidContextReferenceError, u as hasSignatureLike } from "./ld-CNWm8mJc.mjs";
15
+ import { n as getKeyOwner, t as doesActorOwnKey } from "./owner-B3PgGiJ1.mjs";
16
+ import { a as verifyObject, n as hasProofLike, o as verifyPortableObjectProof, r as signObject } from "./proof-fKycdssC.mjs";
17
+ import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "./send-BqBILxYE.mjs";
18
+ import { n as FederationBuilderImpl, t as ACTOR_ALIAS_PREFIX } from "./builder-Cue1kVHU.mjs";
19
19
  import { t as CircuitBreaker } from "./circuit-breaker-C5NqoLBJ.mjs";
20
20
  import { t as buildCollectionSynchronizationHeader } from "./collection-CnnP1UAv.mjs";
21
- import { a as verifyCompoundPortableObjectProofs, i as inspectCompoundPortableObjectApplicability, r as findUnsupportedCompoundProofShape, t as containsCompoundPortableObject } from "./compound-proof-BIvUoJxm.mjs";
21
+ import { a as verifyCompoundPortableObjectProofs, i as inspectCompoundPortableObjectApplicability, r as findUnsupportedCompoundProofShape, t as containsCompoundPortableObject } from "./compound-proof-YT4tajyj.mjs";
22
22
  import { t as KvKeyCache } from "./keycache-BUDHB5rY.mjs";
23
23
  import { t as acceptsJsonLd } from "./negotiation-CCocWT_N.mjs";
24
- import { t as hasMalformedKnownTemporalLiteral } from "./temporal-CCf0lZV3.mjs";
24
+ import { t as hasMalformedKnownTemporalLiteral } from "./temporal-SUiLJ0Co.mjs";
25
25
  import { t as createExponentialBackoffPolicy } from "./retry-zX5xw5e0.mjs";
26
26
  import { t as TaskCodec } from "./codec-C6rTrpIp.mjs";
27
27
  import { getLogger, withContext } from "@logtape/logtape";
@@ -30,8 +30,9 @@ import { Activity, Collection, CollectionPage, CryptographicKey, Link, Multikey,
30
30
  import { lookupWebFinger } from "@fedify/webfinger";
31
31
  import { SpanKind, SpanStatusCode, context, metrics, propagation, trace } from "@opentelemetry/api";
32
32
  import { uniq } from "es-toolkit";
33
- import { FetchError, getDocumentLoader, getFe34Origin, haveSameFe34Origin } from "@fedify/vocab-runtime";
33
+ import { FetchError, canonicalizePortableUri, formatIri, fromCompatibleEf61Id, getDocumentLoader, getFe34Origin, haveSameFe34Origin, isGatewayUrl, parseIri, toCompatibleEf61Id } from "@fedify/vocab-runtime";
34
34
  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";
35
+ import jsonld from "@fedify/vocab-runtime/jsonld";
35
36
  import { DataPointType, MeterProvider as MeterProvider$1, MetricReader } from "@opentelemetry/sdk-metrics";
36
37
  import { domainToASCII } from "node:url";
37
38
  //#region src/compat/transformers.ts
@@ -512,6 +513,68 @@ async function routeActivity({ context: ctx, json, originalJson, normalizedActiv
512
513
  });
513
514
  }
514
515
  //#endregion
516
+ //#region src/federation/portable.ts
517
+ /**
518
+ * The media type that FEP-ef61 gateways must use for portable objects.
519
+ */
520
+ const PORTABLE_OBJECT_CONTENT_TYPE = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"";
521
+ const GATEWAY_OBJECT_PATH_PATTERN = /^\/\.well-known\/apgateway\/did(?::|%3A)/i;
522
+ const BARE_DID_PATTERN = /^did:[a-z0-9]+:[^/?#]+$/i;
523
+ /**
524
+ * Recognizes an FEP-ef61 gateway request for a portable object, e.g.,
525
+ * `GET /.well-known/apgateway/did:key:z6Mk.../notes/123`.
526
+ *
527
+ * The query is not part of the portable ID, so it is ignored here. It is not
528
+ * removed from the request itself, which HTTP Signatures may cover.
529
+ *
530
+ * @param url The request URL.
531
+ * @returns The parsed request, or `null` if the URL is not a gateway request
532
+ * for a portable object, e.g., the gateway discovery endpoint or
533
+ * a hashlink media URL.
534
+ */
535
+ function parsePortableGatewayRequest(url) {
536
+ if (url.protocol !== "http:" && url.protocol !== "https:") return null;
537
+ if (!GATEWAY_OBJECT_PATH_PATTERN.test(url.pathname)) return null;
538
+ let id;
539
+ let canonicalId;
540
+ try {
541
+ id = fromCompatibleEf61Id(url.origin + url.pathname);
542
+ if (id == null) return null;
543
+ canonicalId = canonicalizePortableUri(formatIri(id));
544
+ } catch (error) {
545
+ if (error instanceof TypeError) return {
546
+ type: "malformed",
547
+ error
548
+ };
549
+ throw error;
550
+ }
551
+ const authority = getFe34Origin(id);
552
+ const href = id.href;
553
+ return {
554
+ type: "object",
555
+ portableRequest: Object.freeze({
556
+ authority,
557
+ get id() {
558
+ return new URL(href);
559
+ }
560
+ }),
561
+ canonicalId,
562
+ path: id.pathname
563
+ };
564
+ }
565
+ /**
566
+ * Builds a portable ID from a DID authority and an object path.
567
+ * @param authority The bare DID, e.g., `did:key:z6Mk...`.
568
+ * @param path The object path, e.g., `/notes/123`.
569
+ * @returns The portable ID.
570
+ * @throws {TypeError} If the authority is not a bare DID.
571
+ */
572
+ function buildPortableUri(authority, path) {
573
+ if (typeof authority !== "string" || !BARE_DID_PATTERN.test(authority)) throw new TypeError("The authority of a portable ID must be a DID without a path, query, or fragment.");
574
+ getFe34Origin(authority);
575
+ return parseIri(`ap+ef61://${authority}${path}`);
576
+ }
577
+ //#endregion
515
578
  //#region src/federation/handler.ts
516
579
  const rawInboxContextFactorySymbol = Symbol("fedify.rawInboxContextFactory");
517
580
  const INBOX_COMPOUND_PROOF_LIMITS = {
@@ -607,6 +670,102 @@ async function handleObject(request, { values, context, objectDispatcher, author
607
670
  Vary: "Accept"
608
671
  } });
609
672
  }
673
+ /**
674
+ * Handles an FEP-ef61 gateway request for a portable object through an object
675
+ * dispatcher. The object is served only if its ID canonically matches the
676
+ * requested portable ID and it satisfies the FEP-ef61 proof policy.
677
+ * @template TContextData The context data to pass to the context.
678
+ * @param request The HTTP request.
679
+ * @param parameters The parameters for handling the portable object.
680
+ * @returns A promise that resolves to an HTTP response.
681
+ */
682
+ async function handlePortableObject(request, { values, context, objectDispatcher, authorizePredicate, canonicalId, onNotFound, onUnauthorized }) {
683
+ const logger = getLogger([
684
+ "fedify",
685
+ "federation",
686
+ "object"
687
+ ]);
688
+ if (objectDispatcher == null) return await onNotFound(request);
689
+ const object = await objectDispatcher(context, values);
690
+ if (object == null) return await onNotFound(request);
691
+ if (!isRequestedPortableObject(object, canonicalId)) {
692
+ logger.debug("The object {objectId} does not match the requested portable object {portableId}.", {
693
+ objectId: object.id?.href,
694
+ portableId: canonicalId
695
+ });
696
+ return await onNotFound(request);
697
+ }
698
+ if (authorizePredicate != null) {
699
+ if (!await authorizePredicate(context, values)) return await onUnauthorized(request);
700
+ }
701
+ let jsonLd;
702
+ let servedId;
703
+ let result;
704
+ try {
705
+ jsonLd = await object.toJsonLd(context);
706
+ servedId = await getJsonLdRootId(jsonLd, context.contextLoader);
707
+ result = await verifyPortableObjectProof(jsonLd, {
708
+ contextLoader: context.contextLoader,
709
+ documentLoader: context.documentLoader,
710
+ tracerProvider: context.tracerProvider,
711
+ meterProvider: context.meterProvider
712
+ });
713
+ } catch (error) {
714
+ logger.error("Failed to verify the Object Integrity Proofs of the portable object {portableId}:\n{error}", {
715
+ portableId: canonicalId,
716
+ error
717
+ });
718
+ return portableObjectInternalServerError(request);
719
+ }
720
+ if (servedId == null || !isRequestedPortableId(servedId, canonicalId)) {
721
+ logger.debug("The serialized object does not match the requested portable object {portableId}.", { portableId: canonicalId });
722
+ return await onNotFound(request);
723
+ }
724
+ if (!result.verified && result.reason.type !== "unsecuredCollection" && result.reason.type !== "unsupportedObjectType") {
725
+ logger.error("Refusing to serve the portable object {portableId}, as it does not satisfy the FEP-ef61 proof policy: {reason}. Portable actors, activities, and objects need an Object Integrity Proof made with a key of the DID in their ID.", {
726
+ portableId: canonicalId,
727
+ reason: result.reason.type
728
+ });
729
+ return portableObjectInternalServerError(request);
730
+ }
731
+ return new Response(request.method === "HEAD" ? null : JSON.stringify(jsonLd), { headers: {
732
+ "Content-Type": PORTABLE_OBJECT_CONTENT_TYPE,
733
+ Vary: "Accept"
734
+ } });
735
+ }
736
+ function isRequestedPortableObject(object, canonicalId) {
737
+ if (object.id == null) return false;
738
+ if (object.id.protocol !== "ap:" && object.id.protocol !== "ap+ef61:") return false;
739
+ try {
740
+ return canonicalizePortableUri(formatIri(object.id)) === canonicalId;
741
+ } catch (error) {
742
+ if (error instanceof TypeError) return false;
743
+ throw error;
744
+ }
745
+ }
746
+ async function getJsonLdRootId(jsonLd, contextLoader) {
747
+ const expanded = await jsonld.expand(jsonLd, {
748
+ documentLoader: getNormalizationContextLoader(contextLoader),
749
+ keepFreeFloatingNodes: true
750
+ });
751
+ if (expanded.length !== 1) return null;
752
+ const id = expanded[0]["@id"];
753
+ return typeof id === "string" ? id : null;
754
+ }
755
+ function isRequestedPortableId(id, canonicalId) {
756
+ try {
757
+ return canonicalizePortableUri(id) === canonicalId;
758
+ } catch (error) {
759
+ if (error instanceof TypeError) return false;
760
+ throw error;
761
+ }
762
+ }
763
+ function portableObjectInternalServerError(request) {
764
+ return new Response(request.method === "HEAD" ? null : "Internal server error.", {
765
+ status: 500,
766
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
767
+ });
768
+ }
610
769
  const BUILT_IN_COLLECTION_METRIC_KINDS = /* @__PURE__ */ new Set([
611
770
  "inbox",
612
771
  "outbox",
@@ -644,13 +803,13 @@ function recordCollectionMetrics(meterProvider, base, result, options = {}) {
644
803
  * @param parameters The parameters for handling the collection.
645
804
  * @returns A promise that resolves to an HTTP response.
646
805
  */
647
- async function handleCollection(request, { name: name$1, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, tracerProvider, meterProvider, onUnauthorized, onNotFound }) {
648
- const spanName = name$1.trim().replace(/\s+/g, "_");
806
+ async function handleCollection(request, { name: name$2, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, tracerProvider, meterProvider, onUnauthorized, onNotFound }) {
807
+ const spanName = name$2.trim().replace(/\s+/g, "_");
649
808
  tracerProvider = tracerProvider ?? trace.getTracerProvider();
650
809
  const tracer = tracerProvider.getTracer(name, version);
651
810
  const cursor = new URL(request.url).searchParams.get("cursor");
652
811
  const metricBase = {
653
- kind: getCollectionMetricKind(name$1),
812
+ kind: getCollectionMetricKind(name$2),
654
813
  page: cursor != null,
655
814
  dispatcher: "built_in"
656
815
  };
@@ -691,7 +850,7 @@ async function handleCollection(request, { name: name$1, identifier, uriGetter,
691
850
  span.setStatus({ code: SpanStatusCode.ERROR });
692
851
  return await onNotFound(request);
693
852
  }
694
- const items = filterCollectionItems(page.items, name$1, filterPredicate);
853
+ const items = filterCollectionItems(page.items, name$2, filterPredicate);
695
854
  itemCount = items.length;
696
855
  span.setAttribute("fedify.collection.items", itemCount);
697
856
  return items;
@@ -731,7 +890,7 @@ async function handleCollection(request, { name: name$1, identifier, uriGetter,
731
890
  } else {
732
891
  const uri = new URL(baseUri);
733
892
  uri.searchParams.set("cursor", cursor);
734
- const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$1}`, {
893
+ const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$2}`, {
735
894
  kind: SpanKind.SERVER,
736
895
  attributes: {
737
896
  "activitypub.collection.id": uri.href,
@@ -747,7 +906,7 @@ async function handleCollection(request, { name: name$1, identifier, uriGetter,
747
906
  span.setStatus({ code: SpanStatusCode.ERROR });
748
907
  return await onNotFound(request);
749
908
  }
750
- const items = filterCollectionItems(page.items, name$1, filterPredicate);
909
+ const items = filterCollectionItems(page.items, name$2, filterPredicate);
751
910
  itemCount = items.length;
752
911
  span.setAttribute("fedify.collection.items", itemCount);
753
912
  return {
@@ -1840,8 +1999,8 @@ var CustomCollectionHandler = class {
1840
1999
  * @param CollectionPage The CollectionPage constructor.
1841
2000
  * @param filterPredicate Optional filter predicate for items.
1842
2001
  */
1843
- constructor(name$2, values, context, callbacks, tracerProvider = trace.getTracerProvider(), meterProvider, Collection, CollectionPage, filterPredicate) {
1844
- this.name = name$2;
2002
+ constructor(name$1, values, context, callbacks, tracerProvider = trace.getTracerProvider(), meterProvider, Collection, CollectionPage, filterPredicate) {
2003
+ this.name = name$1;
1845
2004
  this.values = values;
1846
2005
  this.context = context;
1847
2006
  this.callbacks = callbacks;
@@ -2640,6 +2799,34 @@ function getWebFingerSubjectAndAliases({ resourceUrl, actorUri, preferredUsernam
2640
2799
  };
2641
2800
  }
2642
2801
  /**
2802
+ * Computes the subject and aliases for an FEP-ef61 portable actor, whose
2803
+ * WebFinger address takes its domain from the first gateway of the actor
2804
+ * rather than from the actor ID. The canonical `ap:` ID is not emitted,
2805
+ * since it is not a valid URI for software that does not support FEP-ef61;
2806
+ * the compatible identifier stands for it.
2807
+ */
2808
+ function getPortableWebFingerSubjectAndAliases({ resourceUrl, compatibleId, preferredUsername, acctUsername, gatewayHost }) {
2809
+ const aliases = [];
2810
+ const add = (alias) => {
2811
+ if (alias !== subject && !aliases.includes(alias)) aliases.push(alias);
2812
+ };
2813
+ let subject;
2814
+ if (resourceUrl.protocol === "acct:") {
2815
+ const username = preferredUsername ?? acctUsername;
2816
+ subject = username == null ? resourceUrl.href : `acct:${username}@${gatewayHost}`;
2817
+ add(compatibleId.href);
2818
+ add(resourceUrl.href);
2819
+ } else {
2820
+ subject = preferredUsername == null ? compatibleId.href : `acct:${preferredUsername}@${gatewayHost}`;
2821
+ add(compatibleId.href);
2822
+ if (resourceUrl.protocol === "http:" || resourceUrl.protocol === "https:") add(resourceUrl.href);
2823
+ }
2824
+ return {
2825
+ subject,
2826
+ aliases
2827
+ };
2828
+ }
2829
+ /**
2643
2830
  * Handles a WebFinger request. You would not typically call this function
2644
2831
  * directly, but instead use {@link Federation.fetch} method.
2645
2832
  * @param request The WebFinger request to handle.
@@ -2724,7 +2911,7 @@ async function handleWebFingerInternal(request, { context, host, actorDispatcher
2724
2911
  span?.setAttribute("webfinger.resource", resource);
2725
2912
  let resourceUrl;
2726
2913
  try {
2727
- resourceUrl = new URL(resource);
2914
+ resourceUrl = parseIri(resource);
2728
2915
  } catch (e) {
2729
2916
  if (e instanceof TypeError) return new Response("Invalid resource URL.", { status: 400 });
2730
2917
  throw e;
@@ -2773,7 +2960,29 @@ async function handleWebFingerInternal(request, { context, host, actorDispatcher
2773
2960
  status: 410,
2774
2961
  headers: { "Access-Control-Allow-Origin": "*" }
2775
2962
  });
2776
- const actorUri = context.getActorUri(identifier);
2963
+ let actorUri = context.getActorUri(identifier);
2964
+ let portable;
2965
+ if (actor.id != null && (actor.id.protocol === "ap:" || actor.id.protocol === "ap+ef61:")) {
2966
+ const gateway = actor.gateway;
2967
+ let compatibleId;
2968
+ if (gateway != null && isGatewayUrl(gateway)) try {
2969
+ compatibleId = toCompatibleEf61Id(actor.id, gateway);
2970
+ } catch (error) {
2971
+ if (!(error instanceof TypeError)) throw error;
2972
+ }
2973
+ if (gateway == null || compatibleId == null) {
2974
+ logger.error("The portable actor {actorId} (identifier: {identifier}) needs an HTTP(S) origin as the first item of its gateways property to be discovered through WebFinger.", {
2975
+ actorId: formatIri(actor.id),
2976
+ identifier
2977
+ });
2978
+ return await onNotFound(request);
2979
+ }
2980
+ actorUri = compatibleId;
2981
+ portable = {
2982
+ compatibleId,
2983
+ gatewayHost: gateway.host
2984
+ };
2985
+ }
2777
2986
  const links = [{
2778
2987
  rel: "self",
2779
2988
  href: actorUri.href,
@@ -2800,13 +3009,19 @@ async function handleWebFingerInternal(request, { context, host, actorDispatcher
2800
3009
  const customLinks = await webFingerLinksDispatcher(context, resourceUrl);
2801
3010
  if (customLinks != null) for (const link of customLinks) links.push(link);
2802
3011
  }
2803
- const { subject, aliases } = getWebFingerSubjectAndAliases({
3012
+ const { subject, aliases } = portable == null ? getWebFingerSubjectAndAliases({
2804
3013
  resourceUrl,
2805
3014
  actorUri,
2806
3015
  preferredUsername: actor.preferredUsername,
2807
3016
  acctUsername,
2808
3017
  host,
2809
3018
  contextHost: context.url.host
3019
+ }) : getPortableWebFingerSubjectAndAliases({
3020
+ resourceUrl,
3021
+ compatibleId: portable.compatibleId,
3022
+ preferredUsername: actor.preferredUsername,
3023
+ acctUsername,
3024
+ gatewayHost: portable.gatewayHost
2810
3025
  });
2811
3026
  return new Response(JSON.stringify({
2812
3027
  subject,
@@ -4025,7 +4240,8 @@ var FederationImpl = class extends FederationBuilderImpl {
4025
4240
  ...ctxOptions,
4026
4241
  request,
4027
4242
  invokedFromActorDispatcher: opts.invokedFromActorDispatcher,
4028
- invokedFromObjectDispatcher: opts.invokedFromObjectDispatcher
4243
+ invokedFromObjectDispatcher: opts.invokedFromObjectDispatcher,
4244
+ portableRequest: opts.portableRequest
4029
4245
  });
4030
4246
  }
4031
4247
  #getLoaderOptions(origin) {
@@ -4276,6 +4492,24 @@ var FederationImpl = class extends FederationBuilderImpl {
4276
4492
  const url = new URL(request.url);
4277
4493
  const route = this.router.route(url.pathname);
4278
4494
  if (route == null) {
4495
+ const portable = request.method === "GET" || request.method === "HEAD" ? parsePortableGatewayRequest(url) : null;
4496
+ if (portable != null) {
4497
+ const response = await this.#fetchPortableObject(request, portable, {
4498
+ onNotFound,
4499
+ onNotAcceptable,
4500
+ onUnauthorized,
4501
+ contextData,
4502
+ span,
4503
+ metricState
4504
+ });
4505
+ if (request.method !== "HEAD" || response.body == null) return response;
4506
+ await response.body.cancel();
4507
+ return new Response(null, {
4508
+ status: response.status,
4509
+ statusText: response.statusText,
4510
+ headers: response.headers
4511
+ });
4512
+ }
4279
4513
  metricState.endpoint = "not_found";
4280
4514
  return await onNotFound(request);
4281
4515
  }
@@ -4551,7 +4785,56 @@ var FederationImpl = class extends FederationBuilderImpl {
4551
4785
  }
4552
4786
  }
4553
4787
  }
4788
+ async #fetchPortableObject(request, portable, { onNotFound, onNotAcceptable, onUnauthorized, contextData, span, metricState }) {
4789
+ if (portable.type === "malformed") {
4790
+ metricState.endpoint = "not_found";
4791
+ getLogger([
4792
+ "fedify",
4793
+ "federation",
4794
+ "object"
4795
+ ]).debug("Malformed FEP-ef61 gateway request {url}: {error}", {
4796
+ url: request.url,
4797
+ error: portable.error
4798
+ });
4799
+ return new Response(request.method === "HEAD" ? null : "Malformed portable object ID.", {
4800
+ status: 400,
4801
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
4802
+ });
4803
+ }
4804
+ const route = this.router.route(portable.path);
4805
+ if (route == null || !route.name.startsWith("object:")) {
4806
+ metricState.endpoint = "not_found";
4807
+ return await onNotFound(request);
4808
+ }
4809
+ metricState.routeTemplate = PORTABLE_GATEWAY_ROUTE_PREFIX + route.template;
4810
+ metricState.endpoint = "object";
4811
+ span.updateName(`${request.method} ${metricState.routeTemplate}`);
4812
+ if (!acceptsJsonLd(request)) {
4813
+ metricState.endpoint = "not_acceptable";
4814
+ return await onNotAcceptable(request);
4815
+ }
4816
+ const typeId = route.name.replace(/^object:/, "");
4817
+ const callbacks = this.objectCallbacks[typeId];
4818
+ const cls = this.objectTypeIds[typeId];
4819
+ const context = this.#createContext(request, contextData, {
4820
+ invokedFromObjectDispatcher: {
4821
+ cls,
4822
+ values: route.values
4823
+ },
4824
+ portableRequest: portable.portableRequest
4825
+ });
4826
+ return await handlePortableObject(request, {
4827
+ values: route.values,
4828
+ context,
4829
+ objectDispatcher: callbacks?.dispatcher,
4830
+ authorizePredicate: callbacks?.authorizePredicate,
4831
+ canonicalId: portable.canonicalId,
4832
+ onUnauthorized,
4833
+ onNotFound
4834
+ });
4835
+ }
4554
4836
  };
4837
+ const PORTABLE_GATEWAY_ROUTE_PREFIX = "/.well-known/apgateway/{did}";
4555
4838
  function getEndpointCategory(routeName) {
4556
4839
  if (routeName.startsWith("object:")) return "object";
4557
4840
  if (routeName.startsWith("collection:") || routeName.startsWith("orderedCollection:")) return "collection";
@@ -4686,12 +4969,20 @@ var ContextImpl = class ContextImpl {
4686
4969
  return new URL(path, this.canonicalOrigin);
4687
4970
  }
4688
4971
  getObjectUri(cls, values) {
4972
+ return new URL(this.#getObjectPath(cls, values), this.canonicalOrigin);
4973
+ }
4974
+ getPortableObjectUri(cls, values, authority) {
4975
+ const path = this.#getObjectPath(cls, values);
4976
+ if (authority == null) throw new TypeError("The authority of a portable ID is required outside an FEP-ef61 gateway request.");
4977
+ return buildPortableUri(authority, path);
4978
+ }
4979
+ #getObjectPath(cls, values) {
4689
4980
  const callbacks = this.federation.objectCallbacks[cls.typeId.href];
4690
4981
  if (callbacks == null) throw new RouterError("No object dispatcher registered.");
4691
4982
  for (const param of callbacks.parameters) if (!(param in values)) throw new TypeError(`Missing parameter: ${param}`);
4692
4983
  const path = this.federation.router.build(`object:${cls.typeId.href}`, values);
4693
4984
  if (path == null) throw new RouterError("No object dispatcher registered.");
4694
- return new URL(path, this.canonicalOrigin);
4985
+ return path;
4695
4986
  }
4696
4987
  getOutboxUri(identifier) {
4697
4988
  const path = this.federation.router.build("outbox", { identifier });
@@ -4920,6 +5211,7 @@ var ContextImpl = class ContextImpl {
4920
5211
  userAgent: options.userAgent ?? this.federation.userAgent,
4921
5212
  tracerProvider: options.tracerProvider ?? this.tracerProvider,
4922
5213
  meterProvider: options.meterProvider ?? this.meterProvider,
5214
+ verifyPortableObject: options.verifyPortableObject ?? verifyPortableObjectProof,
4923
5215
  allowPrivateAddress: this.federation.allowPrivateAddress
4924
5216
  });
4925
5217
  }
@@ -5266,12 +5558,14 @@ var RequestContextImpl = class RequestContextImpl extends ContextImpl {
5266
5558
  #invokedFromObjectDispatcher;
5267
5559
  request;
5268
5560
  url = void 0;
5561
+ portableRequest;
5269
5562
  constructor(options) {
5270
5563
  super(options);
5271
5564
  this.#invokedFromActorDispatcher = options.invokedFromActorDispatcher;
5272
5565
  this.#invokedFromObjectDispatcher = options.invokedFromObjectDispatcher;
5273
5566
  this.request = options.request;
5274
5567
  this.url = options.url;
5568
+ this.portableRequest = options.portableRequest;
5275
5569
  }
5276
5570
  clone(data) {
5277
5571
  return new RequestContextImpl({
@@ -5283,9 +5577,13 @@ var RequestContextImpl = class RequestContextImpl extends ContextImpl {
5283
5577
  invokedFromActorKeyPairsDispatcher: this.invokedFromActorKeyPairsDispatcher,
5284
5578
  invokedFromActorDispatcher: this.#invokedFromActorDispatcher,
5285
5579
  invokedFromObjectDispatcher: this.#invokedFromObjectDispatcher,
5580
+ portableRequest: this.portableRequest,
5286
5581
  request: this.request
5287
5582
  });
5288
5583
  }
5584
+ getPortableObjectUri(cls, values, authority) {
5585
+ return super.getPortableObjectUri(cls, values, authority ?? this.portableRequest?.authority);
5586
+ }
5289
5587
  async getActor(identifier, options) {
5290
5588
  if (this.federation.actorCallbacks == null || this.federation.actorCallbacks.dispatcher == null) throw new Error("No actor dispatcher registered.");
5291
5589
  if (this.#invokedFromActorDispatcher != null) getLogger([
@@ -5676,4 +5974,4 @@ function getRequestId(request) {
5676
5974
  return `req_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`;
5677
5975
  }
5678
5976
  //#endregion
5679
- export { handleNodeInfo as _, OutboxContextImpl as a, autoIdAssigner as b, handleActor as c, handleInbox as d, handleObject as f, handleBenchmarkTrigger as g, respondWithObjectIfAcceptable as h, KvSpecDeterminer as i, handleCollection as l, respondWithObject as m, FederationImpl as n, createFederation as o, handleOutbox as p, InboxContextImpl as r, handleWebFinger as s, ContextImpl as t, handleCustomCollection as u, handleNodeInfoJrd as v, actorDehydrator as y };
5977
+ export { handleBenchmarkTrigger as _, OutboxContextImpl as a, actorDehydrator as b, handleActor as c, handleInbox as d, handleObject as f, PORTABLE_OBJECT_CONTENT_TYPE as g, respondWithObjectIfAcceptable as h, KvSpecDeterminer as i, handleCollection as l, respondWithObject as m, FederationImpl as n, createFederation as o, handleOutbox as p, InboxContextImpl as r, handleWebFinger as s, ContextImpl as t, handleCustomCollection as u, handleNodeInfo as v, autoIdAssigner as x, handleNodeInfoJrd as y };