@fedify/fedify 2.4.0-dev.2035 → 2.4.0-dev.2054

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-CxB0RNUS.mjs → builder-C86Z3BKs.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-DAEfpSL-.mjs → compound-proof-BcZqTCwy.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-ByRFTNQR.mjs → deno-Bz6KTcon.mjs} +1 -1
  10. package/dist/{docloader-CQ8Edzd4.mjs → docloader-DS54zLD1.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-Bxy5QoWo.cjs → federation-su1OEWsM.cjs} +324 -26
  31. package/dist/{http-BMAvWfcv.js → http-C3WGK1dL.js} +1 -1
  32. package/dist/{http--RCp5CRI.cjs → http-LmUsMjK_.cjs} +1 -1
  33. package/dist/{http-BZl8ShOJ.mjs → http-Ty805y3A.mjs} +3 -3
  34. package/dist/{key-BeFBBxpa.mjs → key-BMc-2eW0.mjs} +2 -2
  35. package/dist/{kv-cache-CnAYugy5.js → kv-cache-BcHCmrC8.js} +1 -1
  36. package/dist/{kv-cache-D_2ZatKb.mjs → kv-cache-BhPPtHg7.mjs} +1 -1
  37. package/dist/{kv-cache-yChGryXN.cjs → kv-cache-CuaZnk6r.cjs} +1 -1
  38. package/dist/{ld-BepC--ab.mjs → ld-OpXWt-80.mjs} +3 -3
  39. package/dist/{metrics-eZ0bEvxw.mjs → metrics-CTAe8lYz.mjs} +1 -1
  40. package/dist/{middleware-8c3mI8D0.mjs → middleware-BtwW3G41.mjs} +326 -28
  41. package/dist/{middleware-DDFdZjkU.mjs → middleware-C-JLcqy7.mjs} +1 -1
  42. package/dist/{middleware-CeK467Oy.js → middleware-_CMav1o7.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-DS-Bz_zW.js → owner-B-TLr-1I.js} +16 -3
  51. package/dist/{owner-DwF4aZoy.cjs → owner-Bcm4pLXw.cjs} +16 -3
  52. package/dist/{owner-DZOj3ivi.mjs → owner-RxB_eQpU.mjs} +2 -2
  53. package/dist/{proof-DyPuryze.mjs → proof-CVf2XbEa.mjs} +18 -5
  54. package/dist/{send-jgopmjWM.mjs → send-DF9KzhJH.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-DjFCwpqJ.mjs → temporal-B6U3fEUW.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 +6 -6
@@ -1,19 +1,19 @@
1
1
  const { URLPattern } = require("urlpattern-polyfill");
2
2
  const require_rolldown_runtime = require("./rolldown-runtime-Dmm3t92W.cjs");
3
3
  const require_transformers = require("./transformers-BalCrSsr.cjs");
4
- const require_http = require("./http--RCp5CRI.cjs");
4
+ const require_http = require("./http-LmUsMjK_.cjs");
5
5
  const require_types = require("./types-Be2zbyeG.cjs");
6
- const require_owner = require("./owner-DwF4aZoy.cjs");
7
- const require_kv_cache = require("./kv-cache-yChGryXN.cjs");
6
+ const require_owner = require("./owner-Bcm4pLXw.cjs");
7
+ const require_kv_cache = require("./kv-cache-CuaZnk6r.cjs");
8
8
  let _logtape_logtape = require("@logtape/logtape");
9
9
  let _fedify_uri_template = require("@fedify/uri-template");
10
10
  let _fedify_vocab = require("@fedify/vocab");
11
11
  let _opentelemetry_api = require("@opentelemetry/api");
12
12
  let byte_encodings_hex = require("byte-encodings/hex");
13
13
  let _fedify_vocab_runtime = require("@fedify/vocab-runtime");
14
- let es_toolkit = require("es-toolkit");
15
14
  let _fedify_vocab_runtime_jsonld = require("@fedify/vocab-runtime/jsonld");
16
15
  _fedify_vocab_runtime_jsonld = require_rolldown_runtime.__toESM(_fedify_vocab_runtime_jsonld, 1);
16
+ let es_toolkit = require("es-toolkit");
17
17
  let _opentelemetry_semantic_conventions = require("@opentelemetry/semantic-conventions");
18
18
  let _fedify_webfinger = require("@fedify/webfinger");
19
19
  let _opentelemetry_sdk_metrics = require("@opentelemetry/sdk-metrics");
@@ -203,38 +203,39 @@ var FederationBuilderImpl = class {
203
203
  "federation",
204
204
  "actor"
205
205
  ]);
206
+ const portable = actor.id?.protocol === "ap:" || actor.id?.protocol === "ap+ef61:";
206
207
  if (actor.id == null) logger.warn("Actor dispatcher returned an actor without an id property. Set the property with Context.getActorUri(identifier).");
207
- else if (actor.id.href != context.getActorUri(identifier).href) logger.warn("Actor dispatcher returned an actor with an id property that does not match the actor URI. Set the property with Context.getActorUri(identifier).");
208
+ else if (!portable && actor.id.href != context.getActorUri(identifier).href) logger.warn("Actor dispatcher returned an actor with an id property that does not match the actor URI. Set the property with Context.getActorUri(identifier).");
208
209
  if (actor instanceof _fedify_vocab.Tombstone) return actor;
209
210
  if (this.followingCallbacks != null && this.followingCallbacks.dispatcher != null) {
210
211
  if (actor.followingId == null) logger.warn("You configured a following collection dispatcher, but the actor does not have a following property. Set the property with Context.getFollowingUri(identifier).");
211
- else if (actor.followingId.href != context.getFollowingUri(identifier).href) logger.warn("You configured a following collection dispatcher, but the actor's following property does not match the following collection URI. Set the property with Context.getFollowingUri(identifier).");
212
+ else if (!portable && actor.followingId.href != context.getFollowingUri(identifier).href) logger.warn("You configured a following collection dispatcher, but the actor's following property does not match the following collection URI. Set the property with Context.getFollowingUri(identifier).");
212
213
  }
213
214
  if (this.followersCallbacks != null && this.followersCallbacks.dispatcher != null) {
214
215
  if (actor.followersId == null) logger.warn("You configured a followers collection dispatcher, but the actor does not have a followers property. Set the property with Context.getFollowersUri(identifier).");
215
- else if (actor.followersId.href != context.getFollowersUri(identifier).href) logger.warn("You configured a followers collection dispatcher, but the actor's followers property does not match the followers collection URI. Set the property with Context.getFollowersUri(identifier).");
216
+ else if (!portable && actor.followersId.href != context.getFollowersUri(identifier).href) logger.warn("You configured a followers collection dispatcher, but the actor's followers property does not match the followers collection URI. Set the property with Context.getFollowersUri(identifier).");
216
217
  }
217
218
  if (this.outboxCallbacks != null && this.outboxCallbacks.dispatcher != null) {
218
219
  if (actor?.outboxId == null) logger.warn("You configured an outbox collection dispatcher, but the actor does not have an outbox property. Set the property with Context.getOutboxUri(identifier).");
219
- else if (actor.outboxId.href != context.getOutboxUri(identifier).href) logger.warn("You configured an outbox collection dispatcher, but the actor's outbox property does not match the outbox collection URI. Set the property with Context.getOutboxUri(identifier).");
220
+ else if (!portable && actor.outboxId.href != context.getOutboxUri(identifier).href) logger.warn("You configured an outbox collection dispatcher, but the actor's outbox property does not match the outbox collection URI. Set the property with Context.getOutboxUri(identifier).");
220
221
  }
221
222
  if (this.likedCallbacks != null && this.likedCallbacks.dispatcher != null) {
222
223
  if (actor?.likedId == null) logger.warn("You configured a liked collection dispatcher, but the actor does not have a liked property. Set the property with Context.getLikedUri(identifier).");
223
- else if (actor.likedId.href != context.getLikedUri(identifier).href) logger.warn("You configured a liked collection dispatcher, but the actor's liked property does not match the liked collection URI. Set the property with Context.getLikedUri(identifier).");
224
+ else if (!portable && actor.likedId.href != context.getLikedUri(identifier).href) logger.warn("You configured a liked collection dispatcher, but the actor's liked property does not match the liked collection URI. Set the property with Context.getLikedUri(identifier).");
224
225
  }
225
226
  if (this.featuredCallbacks != null && this.featuredCallbacks.dispatcher != null) {
226
227
  if (actor?.featuredId == null) logger.warn("You configured a featured collection dispatcher, but the actor does not have a featured property. Set the property with Context.getFeaturedUri(identifier).");
227
- else if (actor.featuredId.href != context.getFeaturedUri(identifier).href) logger.warn("You configured a featured collection dispatcher, but the actor's featured property does not match the featured collection URI. Set the property with Context.getFeaturedUri(identifier).");
228
+ else if (!portable && actor.featuredId.href != context.getFeaturedUri(identifier).href) logger.warn("You configured a featured collection dispatcher, but the actor's featured property does not match the featured collection URI. Set the property with Context.getFeaturedUri(identifier).");
228
229
  }
229
230
  if (this.featuredTagsCallbacks != null && this.featuredTagsCallbacks.dispatcher != null) {
230
231
  if (actor?.featuredTagsId == null) logger.warn("You configured a featured tags collection dispatcher, but the actor does not have a featuredTags property. Set the property with Context.getFeaturedTagsUri(identifier).");
231
- else if (actor.featuredTagsId.href != context.getFeaturedTagsUri(identifier).href) logger.warn("You configured a featured tags collection dispatcher, but the actor's featuredTags property does not match the featured tags collection URI. Set the property with Context.getFeaturedTagsUri(identifier).");
232
+ else if (!portable && actor.featuredTagsId.href != context.getFeaturedTagsUri(identifier).href) logger.warn("You configured a featured tags collection dispatcher, but the actor's featuredTags property does not match the featured tags collection URI. Set the property with Context.getFeaturedTagsUri(identifier).");
232
233
  }
233
234
  if (this.router.has("inbox")) {
234
235
  if (actor.inboxId == null) logger.warn("You configured inbox listeners, but the actor does not have an inbox property. Set the property with Context.getInboxUri(identifier).");
235
- else if (actor.inboxId.href != context.getInboxUri(identifier).href) logger.warn("You configured inbox listeners, but the actor's inbox property does not match the inbox URI. Set the property with Context.getInboxUri(identifier).");
236
+ else if (!portable && actor.inboxId.href != context.getInboxUri(identifier).href) logger.warn("You configured inbox listeners, but the actor's inbox property does not match the inbox URI. Set the property with Context.getInboxUri(identifier).");
236
237
  if (actor.endpoints == null || actor.endpoints.sharedInbox == null) logger.warn("You configured inbox listeners, but the actor does not have a endpoints.sharedInbox property. Set the property with Context.getInboxUri().");
237
- else if (actor.endpoints.sharedInbox.href != context.getInboxUri().href) logger.warn("You configured inbox listeners, but the actor's endpoints.sharedInbox property does not match the shared inbox URI. Set the property with Context.getInboxUri().");
238
+ else if (!portable && actor.endpoints.sharedInbox.href != context.getInboxUri().href) logger.warn("You configured inbox listeners, but the actor's endpoints.sharedInbox property does not match the shared inbox URI. Set the property with Context.getInboxUri().");
238
239
  }
239
240
  if (callbacks.keyPairsDispatcher != null) {
240
241
  if (actor.publicKeyId == null) logger.warn("You configured a key pairs dispatcher, but the actor does not have a publicKey property. Set the property with Context.getActorKeyPairs(identifier).");
@@ -2191,6 +2192,68 @@ function acceptsJsonLd(request) {
2191
2192
  return types.includes("application/activity+json") || types.includes("application/ld+json") || types.includes("application/json");
2192
2193
  }
2193
2194
  //#endregion
2195
+ //#region src/federation/portable.ts
2196
+ /**
2197
+ * The media type that FEP-ef61 gateways must use for portable objects.
2198
+ */
2199
+ const PORTABLE_OBJECT_CONTENT_TYPE = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"";
2200
+ const GATEWAY_OBJECT_PATH_PATTERN = /^\/\.well-known\/apgateway\/did(?::|%3A)/i;
2201
+ const BARE_DID_PATTERN = /^did:[a-z0-9]+:[^/?#]+$/i;
2202
+ /**
2203
+ * Recognizes an FEP-ef61 gateway request for a portable object, e.g.,
2204
+ * `GET /.well-known/apgateway/did:key:z6Mk.../notes/123`.
2205
+ *
2206
+ * The query is not part of the portable ID, so it is ignored here. It is not
2207
+ * removed from the request itself, which HTTP Signatures may cover.
2208
+ *
2209
+ * @param url The request URL.
2210
+ * @returns The parsed request, or `null` if the URL is not a gateway request
2211
+ * for a portable object, e.g., the gateway discovery endpoint or
2212
+ * a hashlink media URL.
2213
+ */
2214
+ function parsePortableGatewayRequest(url) {
2215
+ if (url.protocol !== "http:" && url.protocol !== "https:") return null;
2216
+ if (!GATEWAY_OBJECT_PATH_PATTERN.test(url.pathname)) return null;
2217
+ let id;
2218
+ let canonicalId;
2219
+ try {
2220
+ id = (0, _fedify_vocab_runtime.fromCompatibleEf61Id)(url.origin + url.pathname);
2221
+ if (id == null) return null;
2222
+ canonicalId = (0, _fedify_vocab_runtime.canonicalizePortableUri)((0, _fedify_vocab_runtime.formatIri)(id));
2223
+ } catch (error) {
2224
+ if (error instanceof TypeError) return {
2225
+ type: "malformed",
2226
+ error
2227
+ };
2228
+ throw error;
2229
+ }
2230
+ const authority = (0, _fedify_vocab_runtime.getFe34Origin)(id);
2231
+ const href = id.href;
2232
+ return {
2233
+ type: "object",
2234
+ portableRequest: Object.freeze({
2235
+ authority,
2236
+ get id() {
2237
+ return new URL(href);
2238
+ }
2239
+ }),
2240
+ canonicalId,
2241
+ path: id.pathname
2242
+ };
2243
+ }
2244
+ /**
2245
+ * Builds a portable ID from a DID authority and an object path.
2246
+ * @param authority The bare DID, e.g., `did:key:z6Mk...`.
2247
+ * @param path The object path, e.g., `/notes/123`.
2248
+ * @returns The portable ID.
2249
+ * @throws {TypeError} If the authority is not a bare DID.
2250
+ */
2251
+ function buildPortableUri(authority, path) {
2252
+ 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.");
2253
+ (0, _fedify_vocab_runtime.getFe34Origin)(authority);
2254
+ return (0, _fedify_vocab_runtime.parseIri)(`ap+ef61://${authority}${path}`);
2255
+ }
2256
+ //#endregion
2194
2257
  //#region src/federation/temporal.ts
2195
2258
  const Temporal$7 = require_http.Temporal;
2196
2259
  function isPlainObject$1(value) {
@@ -2377,6 +2440,102 @@ async function handleObject(request, { values, context, objectDispatcher, author
2377
2440
  Vary: "Accept"
2378
2441
  } });
2379
2442
  }
2443
+ /**
2444
+ * Handles an FEP-ef61 gateway request for a portable object through an object
2445
+ * dispatcher. The object is served only if its ID canonically matches the
2446
+ * requested portable ID and it satisfies the FEP-ef61 proof policy.
2447
+ * @template TContextData The context data to pass to the context.
2448
+ * @param request The HTTP request.
2449
+ * @param parameters The parameters for handling the portable object.
2450
+ * @returns A promise that resolves to an HTTP response.
2451
+ */
2452
+ async function handlePortableObject(request, { values, context, objectDispatcher, authorizePredicate, canonicalId, onNotFound, onUnauthorized }) {
2453
+ const logger = (0, _logtape_logtape.getLogger)([
2454
+ "fedify",
2455
+ "federation",
2456
+ "object"
2457
+ ]);
2458
+ if (objectDispatcher == null) return await onNotFound(request);
2459
+ const object = await objectDispatcher(context, values);
2460
+ if (object == null) return await onNotFound(request);
2461
+ if (!isRequestedPortableObject(object, canonicalId)) {
2462
+ logger.debug("The object {objectId} does not match the requested portable object {portableId}.", {
2463
+ objectId: object.id?.href,
2464
+ portableId: canonicalId
2465
+ });
2466
+ return await onNotFound(request);
2467
+ }
2468
+ if (authorizePredicate != null) {
2469
+ if (!await authorizePredicate(context, values)) return await onUnauthorized(request);
2470
+ }
2471
+ let jsonLd;
2472
+ let servedId;
2473
+ let result;
2474
+ try {
2475
+ jsonLd = await object.toJsonLd(context);
2476
+ servedId = await getJsonLdRootId(jsonLd, context.contextLoader);
2477
+ result = await require_owner.verifyPortableObjectProof(jsonLd, {
2478
+ contextLoader: context.contextLoader,
2479
+ documentLoader: context.documentLoader,
2480
+ tracerProvider: context.tracerProvider,
2481
+ meterProvider: context.meterProvider
2482
+ });
2483
+ } catch (error) {
2484
+ logger.error("Failed to verify the Object Integrity Proofs of the portable object {portableId}:\n{error}", {
2485
+ portableId: canonicalId,
2486
+ error
2487
+ });
2488
+ return portableObjectInternalServerError(request);
2489
+ }
2490
+ if (servedId == null || !isRequestedPortableId(servedId, canonicalId)) {
2491
+ logger.debug("The serialized object does not match the requested portable object {portableId}.", { portableId: canonicalId });
2492
+ return await onNotFound(request);
2493
+ }
2494
+ if (!result.verified && result.reason.type !== "unsecuredCollection" && result.reason.type !== "unsupportedObjectType") {
2495
+ 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.", {
2496
+ portableId: canonicalId,
2497
+ reason: result.reason.type
2498
+ });
2499
+ return portableObjectInternalServerError(request);
2500
+ }
2501
+ return new Response(request.method === "HEAD" ? null : JSON.stringify(jsonLd), { headers: {
2502
+ "Content-Type": PORTABLE_OBJECT_CONTENT_TYPE,
2503
+ Vary: "Accept"
2504
+ } });
2505
+ }
2506
+ function isRequestedPortableObject(object, canonicalId) {
2507
+ if (object.id == null) return false;
2508
+ if (object.id.protocol !== "ap:" && object.id.protocol !== "ap+ef61:") return false;
2509
+ try {
2510
+ return (0, _fedify_vocab_runtime.canonicalizePortableUri)((0, _fedify_vocab_runtime.formatIri)(object.id)) === canonicalId;
2511
+ } catch (error) {
2512
+ if (error instanceof TypeError) return false;
2513
+ throw error;
2514
+ }
2515
+ }
2516
+ async function getJsonLdRootId(jsonLd, contextLoader) {
2517
+ const expanded = await _fedify_vocab_runtime_jsonld.default.expand(jsonLd, {
2518
+ documentLoader: require_owner.getNormalizationContextLoader(contextLoader),
2519
+ keepFreeFloatingNodes: true
2520
+ });
2521
+ if (expanded.length !== 1) return null;
2522
+ const id = expanded[0]["@id"];
2523
+ return typeof id === "string" ? id : null;
2524
+ }
2525
+ function isRequestedPortableId(id, canonicalId) {
2526
+ try {
2527
+ return (0, _fedify_vocab_runtime.canonicalizePortableUri)(id) === canonicalId;
2528
+ } catch (error) {
2529
+ if (error instanceof TypeError) return false;
2530
+ throw error;
2531
+ }
2532
+ }
2533
+ function portableObjectInternalServerError(request) {
2534
+ return new Response(request.method === "HEAD" ? null : "Internal server error.", {
2535
+ status: 500,
2536
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
2537
+ });
2538
+ }
2380
2539
  const BUILT_IN_COLLECTION_METRIC_KINDS = /* @__PURE__ */ new Set([
2381
2540
  "inbox",
2382
2541
  "outbox",
@@ -2414,13 +2573,13 @@ function recordCollectionMetrics(meterProvider, base, result, options = {}) {
2414
2573
  * @param parameters The parameters for handling the collection.
2415
2574
  * @returns A promise that resolves to an HTTP response.
2416
2575
  */
2417
- async function handleCollection(request, { name: name$1, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, tracerProvider, meterProvider, onUnauthorized, onNotFound }) {
2418
- const spanName = name$1.trim().replace(/\s+/g, "_");
2576
+ async function handleCollection(request, { name: name$2, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, tracerProvider, meterProvider, onUnauthorized, onNotFound }) {
2577
+ const spanName = name$2.trim().replace(/\s+/g, "_");
2419
2578
  tracerProvider = tracerProvider ?? _opentelemetry_api.trace.getTracerProvider();
2420
2579
  const tracer = tracerProvider.getTracer(require_http.name, require_http.version);
2421
2580
  const cursor = new URL(request.url).searchParams.get("cursor");
2422
2581
  const metricBase = {
2423
- kind: getCollectionMetricKind(name$1),
2582
+ kind: getCollectionMetricKind(name$2),
2424
2583
  page: cursor != null,
2425
2584
  dispatcher: "built_in"
2426
2585
  };
@@ -2461,7 +2620,7 @@ async function handleCollection(request, { name: name$1, identifier, uriGetter,
2461
2620
  span.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
2462
2621
  return await onNotFound(request);
2463
2622
  }
2464
- const items = filterCollectionItems(page.items, name$1, filterPredicate);
2623
+ const items = filterCollectionItems(page.items, name$2, filterPredicate);
2465
2624
  itemCount = items.length;
2466
2625
  span.setAttribute("fedify.collection.items", itemCount);
2467
2626
  return items;
@@ -2501,7 +2660,7 @@ async function handleCollection(request, { name: name$1, identifier, uriGetter,
2501
2660
  } else {
2502
2661
  const uri = new URL(baseUri);
2503
2662
  uri.searchParams.set("cursor", cursor);
2504
- const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$1}`, {
2663
+ const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$2}`, {
2505
2664
  kind: _opentelemetry_api.SpanKind.SERVER,
2506
2665
  attributes: {
2507
2666
  "activitypub.collection.id": uri.href,
@@ -2517,7 +2676,7 @@ async function handleCollection(request, { name: name$1, identifier, uriGetter,
2517
2676
  span.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
2518
2677
  return await onNotFound(request);
2519
2678
  }
2520
- const items = filterCollectionItems(page.items, name$1, filterPredicate);
2679
+ const items = filterCollectionItems(page.items, name$2, filterPredicate);
2521
2680
  itemCount = items.length;
2522
2681
  span.setAttribute("fedify.collection.items", itemCount);
2523
2682
  return {
@@ -3610,8 +3769,8 @@ var CustomCollectionHandler = class {
3610
3769
  * @param CollectionPage The CollectionPage constructor.
3611
3770
  * @param filterPredicate Optional filter predicate for items.
3612
3771
  */
3613
- constructor(name$2, values, context, callbacks, tracerProvider = _opentelemetry_api.trace.getTracerProvider(), meterProvider, Collection, CollectionPage, filterPredicate) {
3614
- this.name = name$2;
3772
+ constructor(name$1, values, context, callbacks, tracerProvider = _opentelemetry_api.trace.getTracerProvider(), meterProvider, Collection, CollectionPage, filterPredicate) {
3773
+ this.name = name$1;
3615
3774
  this.values = values;
3616
3775
  this.context = context;
3617
3776
  this.callbacks = callbacks;
@@ -5187,6 +5346,34 @@ function getWebFingerSubjectAndAliases({ resourceUrl, actorUri, preferredUsernam
5187
5346
  };
5188
5347
  }
5189
5348
  /**
5349
+ * Computes the subject and aliases for an FEP-ef61 portable actor, whose
5350
+ * WebFinger address takes its domain from the first gateway of the actor
5351
+ * rather than from the actor ID. The canonical `ap:` ID is not emitted,
5352
+ * since it is not a valid URI for software that does not support FEP-ef61;
5353
+ * the compatible identifier stands for it.
5354
+ */
5355
+ function getPortableWebFingerSubjectAndAliases({ resourceUrl, compatibleId, preferredUsername, acctUsername, gatewayHost }) {
5356
+ const aliases = [];
5357
+ const add = (alias) => {
5358
+ if (alias !== subject && !aliases.includes(alias)) aliases.push(alias);
5359
+ };
5360
+ let subject;
5361
+ if (resourceUrl.protocol === "acct:") {
5362
+ const username = preferredUsername ?? acctUsername;
5363
+ subject = username == null ? resourceUrl.href : `acct:${username}@${gatewayHost}`;
5364
+ add(compatibleId.href);
5365
+ add(resourceUrl.href);
5366
+ } else {
5367
+ subject = preferredUsername == null ? compatibleId.href : `acct:${preferredUsername}@${gatewayHost}`;
5368
+ add(compatibleId.href);
5369
+ if (resourceUrl.protocol === "http:" || resourceUrl.protocol === "https:") add(resourceUrl.href);
5370
+ }
5371
+ return {
5372
+ subject,
5373
+ aliases
5374
+ };
5375
+ }
5376
+ /**
5190
5377
  * Handles a WebFinger request. You would not typically call this function
5191
5378
  * directly, but instead use {@link Federation.fetch} method.
5192
5379
  * @param request The WebFinger request to handle.
@@ -5271,7 +5458,7 @@ async function handleWebFingerInternal(request, { context, host, actorDispatcher
5271
5458
  span?.setAttribute("webfinger.resource", resource);
5272
5459
  let resourceUrl;
5273
5460
  try {
5274
- resourceUrl = new URL(resource);
5461
+ resourceUrl = (0, _fedify_vocab_runtime.parseIri)(resource);
5275
5462
  } catch (e) {
5276
5463
  if (e instanceof TypeError) return new Response("Invalid resource URL.", { status: 400 });
5277
5464
  throw e;
@@ -5320,7 +5507,29 @@ async function handleWebFingerInternal(request, { context, host, actorDispatcher
5320
5507
  status: 410,
5321
5508
  headers: { "Access-Control-Allow-Origin": "*" }
5322
5509
  });
5323
- const actorUri = context.getActorUri(identifier);
5510
+ let actorUri = context.getActorUri(identifier);
5511
+ let portable;
5512
+ if (actor.id != null && (actor.id.protocol === "ap:" || actor.id.protocol === "ap+ef61:")) {
5513
+ const gateway = actor.gateway;
5514
+ let compatibleId;
5515
+ if (gateway != null && (0, _fedify_vocab_runtime.isGatewayUrl)(gateway)) try {
5516
+ compatibleId = (0, _fedify_vocab_runtime.toCompatibleEf61Id)(actor.id, gateway);
5517
+ } catch (error) {
5518
+ if (!(error instanceof TypeError)) throw error;
5519
+ }
5520
+ if (gateway == null || compatibleId == null) {
5521
+ logger$1.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.", {
5522
+ actorId: (0, _fedify_vocab_runtime.formatIri)(actor.id),
5523
+ identifier
5524
+ });
5525
+ return await onNotFound(request);
5526
+ }
5527
+ actorUri = compatibleId;
5528
+ portable = {
5529
+ compatibleId,
5530
+ gatewayHost: gateway.host
5531
+ };
5532
+ }
5324
5533
  const links = [{
5325
5534
  rel: "self",
5326
5535
  href: actorUri.href,
@@ -5347,13 +5556,19 @@ async function handleWebFingerInternal(request, { context, host, actorDispatcher
5347
5556
  const customLinks = await webFingerLinksDispatcher(context, resourceUrl);
5348
5557
  if (customLinks != null) for (const link of customLinks) links.push(link);
5349
5558
  }
5350
- const { subject, aliases } = getWebFingerSubjectAndAliases({
5559
+ const { subject, aliases } = portable == null ? getWebFingerSubjectAndAliases({
5351
5560
  resourceUrl,
5352
5561
  actorUri,
5353
5562
  preferredUsername: actor.preferredUsername,
5354
5563
  acctUsername,
5355
5564
  host,
5356
5565
  contextHost: context.url.host
5566
+ }) : getPortableWebFingerSubjectAndAliases({
5567
+ resourceUrl,
5568
+ compatibleId: portable.compatibleId,
5569
+ preferredUsername: actor.preferredUsername,
5570
+ acctUsername,
5571
+ gatewayHost: portable.gatewayHost
5357
5572
  });
5358
5573
  return new Response(JSON.stringify({
5359
5574
  subject,
@@ -6581,7 +6796,8 @@ var FederationImpl = class extends FederationBuilderImpl {
6581
6796
  ...ctxOptions,
6582
6797
  request,
6583
6798
  invokedFromActorDispatcher: opts.invokedFromActorDispatcher,
6584
- invokedFromObjectDispatcher: opts.invokedFromObjectDispatcher
6799
+ invokedFromObjectDispatcher: opts.invokedFromObjectDispatcher,
6800
+ portableRequest: opts.portableRequest
6585
6801
  });
6586
6802
  }
6587
6803
  #getLoaderOptions(origin) {
@@ -6832,6 +7048,24 @@ var FederationImpl = class extends FederationBuilderImpl {
6832
7048
  const url = new URL(request.url);
6833
7049
  const route = this.router.route(url.pathname);
6834
7050
  if (route == null) {
7051
+ const portable = request.method === "GET" || request.method === "HEAD" ? parsePortableGatewayRequest(url) : null;
7052
+ if (portable != null) {
7053
+ const response = await this.#fetchPortableObject(request, portable, {
7054
+ onNotFound,
7055
+ onNotAcceptable,
7056
+ onUnauthorized,
7057
+ contextData,
7058
+ span,
7059
+ metricState
7060
+ });
7061
+ if (request.method !== "HEAD" || response.body == null) return response;
7062
+ await response.body.cancel();
7063
+ return new Response(null, {
7064
+ status: response.status,
7065
+ statusText: response.statusText,
7066
+ headers: response.headers
7067
+ });
7068
+ }
6835
7069
  metricState.endpoint = "not_found";
6836
7070
  return await onNotFound(request);
6837
7071
  }
@@ -7107,7 +7341,56 @@ var FederationImpl = class extends FederationBuilderImpl {
7107
7341
  }
7108
7342
  }
7109
7343
  }
7344
+ async #fetchPortableObject(request, portable, { onNotFound, onNotAcceptable, onUnauthorized, contextData, span, metricState }) {
7345
+ if (portable.type === "malformed") {
7346
+ metricState.endpoint = "not_found";
7347
+ (0, _logtape_logtape.getLogger)([
7348
+ "fedify",
7349
+ "federation",
7350
+ "object"
7351
+ ]).debug("Malformed FEP-ef61 gateway request {url}: {error}", {
7352
+ url: request.url,
7353
+ error: portable.error
7354
+ });
7355
+ return new Response(request.method === "HEAD" ? null : "Malformed portable object ID.", {
7356
+ status: 400,
7357
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
7358
+ });
7359
+ }
7360
+ const route = this.router.route(portable.path);
7361
+ if (route == null || !route.name.startsWith("object:")) {
7362
+ metricState.endpoint = "not_found";
7363
+ return await onNotFound(request);
7364
+ }
7365
+ metricState.routeTemplate = PORTABLE_GATEWAY_ROUTE_PREFIX + route.template;
7366
+ metricState.endpoint = "object";
7367
+ span.updateName(`${request.method} ${metricState.routeTemplate}`);
7368
+ if (!acceptsJsonLd(request)) {
7369
+ metricState.endpoint = "not_acceptable";
7370
+ return await onNotAcceptable(request);
7371
+ }
7372
+ const typeId = route.name.replace(/^object:/, "");
7373
+ const callbacks = this.objectCallbacks[typeId];
7374
+ const cls = this.objectTypeIds[typeId];
7375
+ const context = this.#createContext(request, contextData, {
7376
+ invokedFromObjectDispatcher: {
7377
+ cls,
7378
+ values: route.values
7379
+ },
7380
+ portableRequest: portable.portableRequest
7381
+ });
7382
+ return await handlePortableObject(request, {
7383
+ values: route.values,
7384
+ context,
7385
+ objectDispatcher: callbacks?.dispatcher,
7386
+ authorizePredicate: callbacks?.authorizePredicate,
7387
+ canonicalId: portable.canonicalId,
7388
+ onUnauthorized,
7389
+ onNotFound
7390
+ });
7391
+ }
7110
7392
  };
7393
+ const PORTABLE_GATEWAY_ROUTE_PREFIX = "/.well-known/apgateway/{did}";
7111
7394
  function getEndpointCategory(routeName) {
7112
7395
  if (routeName.startsWith("object:")) return "object";
7113
7396
  if (routeName.startsWith("collection:") || routeName.startsWith("orderedCollection:")) return "collection";
@@ -7242,12 +7525,20 @@ var ContextImpl = class ContextImpl {
7242
7525
  return new URL(path, this.canonicalOrigin);
7243
7526
  }
7244
7527
  getObjectUri(cls, values) {
7528
+ return new URL(this.#getObjectPath(cls, values), this.canonicalOrigin);
7529
+ }
7530
+ getPortableObjectUri(cls, values, authority) {
7531
+ const path = this.#getObjectPath(cls, values);
7532
+ if (authority == null) throw new TypeError("The authority of a portable ID is required outside an FEP-ef61 gateway request.");
7533
+ return buildPortableUri(authority, path);
7534
+ }
7535
+ #getObjectPath(cls, values) {
7245
7536
  const callbacks = this.federation.objectCallbacks[cls.typeId.href];
7246
7537
  if (callbacks == null) throw new _fedify_uri_template.RouterError("No object dispatcher registered.");
7247
7538
  for (const param of callbacks.parameters) if (!(param in values)) throw new TypeError(`Missing parameter: ${param}`);
7248
7539
  const path = this.federation.router.build(`object:${cls.typeId.href}`, values);
7249
7540
  if (path == null) throw new _fedify_uri_template.RouterError("No object dispatcher registered.");
7250
- return new URL(path, this.canonicalOrigin);
7541
+ return path;
7251
7542
  }
7252
7543
  getOutboxUri(identifier) {
7253
7544
  const path = this.federation.router.build("outbox", { identifier });
@@ -7476,6 +7767,7 @@ var ContextImpl = class ContextImpl {
7476
7767
  userAgent: options.userAgent ?? this.federation.userAgent,
7477
7768
  tracerProvider: options.tracerProvider ?? this.tracerProvider,
7478
7769
  meterProvider: options.meterProvider ?? this.meterProvider,
7770
+ verifyPortableObject: options.verifyPortableObject ?? require_owner.verifyPortableObjectProof,
7479
7771
  allowPrivateAddress: this.federation.allowPrivateAddress
7480
7772
  });
7481
7773
  }
@@ -7822,12 +8114,14 @@ var RequestContextImpl = class RequestContextImpl extends ContextImpl {
7822
8114
  #invokedFromObjectDispatcher;
7823
8115
  request;
7824
8116
  url = void 0;
8117
+ portableRequest;
7825
8118
  constructor(options) {
7826
8119
  super(options);
7827
8120
  this.#invokedFromActorDispatcher = options.invokedFromActorDispatcher;
7828
8121
  this.#invokedFromObjectDispatcher = options.invokedFromObjectDispatcher;
7829
8122
  this.request = options.request;
7830
8123
  this.url = options.url;
8124
+ this.portableRequest = options.portableRequest;
7831
8125
  }
7832
8126
  clone(data) {
7833
8127
  return new RequestContextImpl({
@@ -7839,9 +8133,13 @@ var RequestContextImpl = class RequestContextImpl extends ContextImpl {
7839
8133
  invokedFromActorKeyPairsDispatcher: this.invokedFromActorKeyPairsDispatcher,
7840
8134
  invokedFromActorDispatcher: this.#invokedFromActorDispatcher,
7841
8135
  invokedFromObjectDispatcher: this.#invokedFromObjectDispatcher,
8136
+ portableRequest: this.portableRequest,
7842
8137
  request: this.request
7843
8138
  });
7844
8139
  }
8140
+ getPortableObjectUri(cls, values, authority) {
8141
+ return super.getPortableObjectUri(cls, values, authority ?? this.portableRequest?.authority);
8142
+ }
7845
8143
  async getActor(identifier, options) {
7846
8144
  if (this.federation.actorCallbacks == null || this.federation.actorCallbacks.dispatcher == null) throw new Error("No actor dispatcher registered.");
7847
8145
  if (this.#invokedFromActorDispatcher != null) (0, _logtape_logtape.getLogger)([
@@ -10,7 +10,7 @@ import { decodeBase64, encodeBase64 } from "byte-encodings/base64";
10
10
  import { ATTR_HTTP_REQUEST_HEADER, ATTR_HTTP_REQUEST_METHOD, ATTR_URL_FULL } from "@opentelemetry/semantic-conventions";
11
11
  //#region deno.json
12
12
  var name = "@fedify/fedify";
13
- var version = "2.4.0-dev.2035+d1f8f8e2";
13
+ var version = "2.4.0-dev.2054+1756e190";
14
14
  //#endregion
15
15
  //#region src/sig/accept.ts
16
16
  /**
@@ -9,7 +9,7 @@ let byte_encodings_base64 = require("byte-encodings/base64");
9
9
  let _opentelemetry_semantic_conventions = require("@opentelemetry/semantic-conventions");
10
10
  //#region deno.json
11
11
  var name = "@fedify/fedify";
12
- var version = "2.4.0-dev.2035+d1f8f8e2";
12
+ var version = "2.4.0-dev.2054+1756e190";
13
13
  //#endregion
14
14
  //#region ../../node_modules/.pnpm/temporal-polyfill@1.0.1/node_modules/temporal-polyfill/chunks/root.js
15
15
  const NativeTemporal = globalThis.Temporal;
@@ -1,10 +1,10 @@
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-ByRFTNQR.mjs";
5
- import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-eZ0bEvxw.mjs";
4
+ import { n as version, t as name } from "./deno-Bz6KTcon.mjs";
5
+ import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-CTAe8lYz.mjs";
6
6
  import { i as validateAcceptSignature, n as fulfillAcceptSignature, r as parseAcceptSignature } from "./accept-CPVqki78.mjs";
7
- import { i as fetchKeyDetailed, s as validateCryptoKey } from "./key-BeFBBxpa.mjs";
7
+ import { i as fetchKeyDetailed, s as validateCryptoKey } from "./key-BMc-2eW0.mjs";
8
8
  import { getLogger } from "@logtape/logtape";
9
9
  import { CryptographicKey } from "@fedify/vocab";
10
10
  import { SpanStatusCode, trace } from "@opentelemetry/api";
@@ -1,8 +1,8 @@
1
1
  import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-ByRFTNQR.mjs";
5
- import { _ as recordKeyLookup, n as getDurationMs, t as classifyFetchError } from "./metrics-eZ0bEvxw.mjs";
4
+ import { n as version, t as name } from "./deno-Bz6KTcon.mjs";
5
+ import { _ as recordKeyLookup, n as getDurationMs, t as classifyFetchError } from "./metrics-CTAe8lYz.mjs";
6
6
  import { getLogger } from "@logtape/logtape";
7
7
  import { CryptographicKey, Multikey, Object as Object$1, isActor } from "@fedify/vocab";
8
8
  import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
@@ -1,6 +1,6 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
- import { T as recordDocumentCache, f as validateCryptoKey, t as doubleKnock } from "./http-BMAvWfcv.js";
3
+ import { T as recordDocumentCache, f as validateCryptoKey, t as doubleKnock } from "./http-C3WGK1dL.js";
4
4
  import { getLogger } from "@logtape/logtape";
5
5
  import { UrlError, createActivityPubRequest, getRemoteDocument, logRequest, preloadedContexts, validatePublicUrl } from "@fedify/vocab-runtime";
6
6
  import { curry } from "es-toolkit";
@@ -1,7 +1,7 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { p as recordDocumentCache } from "./metrics-eZ0bEvxw.mjs";
4
+ import { p as recordDocumentCache } from "./metrics-CTAe8lYz.mjs";
5
5
  import { getLogger } from "@logtape/logtape";
6
6
  import { preloadedContexts } from "@fedify/vocab-runtime";
7
7
  //#region src/utils/kv-cache.ts
@@ -1,5 +1,5 @@
1
1
  const { URLPattern } = require("urlpattern-polyfill");
2
- const require_http = require("./http--RCp5CRI.cjs");
2
+ const require_http = require("./http-LmUsMjK_.cjs");
3
3
  let _logtape_logtape = require("@logtape/logtape");
4
4
  let _fedify_vocab_runtime = require("@fedify/vocab-runtime");
5
5
  let es_toolkit = require("es-toolkit");
@@ -1,9 +1,9 @@
1
1
  import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-ByRFTNQR.mjs";
5
- import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-eZ0bEvxw.mjs";
6
- import { r as fetchKey, s as validateCryptoKey } from "./key-BeFBBxpa.mjs";
4
+ import { n as version, t as name } from "./deno-Bz6KTcon.mjs";
5
+ import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-CTAe8lYz.mjs";
6
+ import { r as fetchKey, s as validateCryptoKey } from "./key-BMc-2eW0.mjs";
7
7
  import { getLogger } from "@logtape/logtape";
8
8
  import { Activity, CryptographicKey, Object as Object$1, getTypeId } from "@fedify/vocab";
9
9
  import { SpanStatusCode, trace } from "@opentelemetry/api";
@@ -1,7 +1,7 @@
1
1
  import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-ByRFTNQR.mjs";
4
+ import { n as version, t as name } from "./deno-Bz6KTcon.mjs";
5
5
  import { metrics } from "@opentelemetry/api";
6
6
  import { FetchError } from "@fedify/vocab-runtime";
7
7
  //#region src/federation/metrics.ts