@fedify/fedify 2.0.36-dev.2551 → 2.0.36-dev.2637

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 (61) hide show
  1. package/dist/{builder-Dndt590h.mjs → builder-BC3eRIbe.mjs} +3 -3
  2. package/dist/compat/mod.d.cts +1 -1
  3. package/dist/compat/mod.d.ts +1 -1
  4. package/dist/compat/transformers.test.mjs +1 -1
  5. package/dist/{context-lxjaUTAI.d.ts → context-C00pWP8x.d.ts} +6 -0
  6. package/dist/{context-CnoWxuoB.d.cts → context-ao-qb3eP.d.cts} +6 -0
  7. package/dist/{deno-D_rTMbAD.mjs → deno-Dv6PIwH_.mjs} +1 -1
  8. package/dist/{docloader-DpTrZ5sA.mjs → docloader-BgHM-JLc.mjs} +2 -2
  9. package/dist/federation/builder.test.mjs +1 -1
  10. package/dist/federation/handler.test.mjs +8 -8
  11. package/dist/federation/idempotency.test.mjs +2 -2
  12. package/dist/federation/inbox.test.mjs +1 -1
  13. package/dist/federation/middleware.test.mjs +696 -8
  14. package/dist/federation/mod.cjs +1 -1
  15. package/dist/federation/mod.d.cts +2 -2
  16. package/dist/federation/mod.d.ts +2 -2
  17. package/dist/federation/mod.js +1 -1
  18. package/dist/federation/send.test.mjs +3 -3
  19. package/dist/federation/temporal.test.mjs +1 -1
  20. package/dist/federation/webfinger.test.mjs +1 -1
  21. package/dist/{http-DGD9d8-A.mjs → http-BWoAqky4.mjs} +2 -2
  22. package/dist/{http-BS1CmlZ6.js → http-CrEB4I8A.js} +1 -1
  23. package/dist/{http-DbDnj-Q7.cjs → http-D69BMfAn.cjs} +1 -1
  24. package/dist/{inbox-BwpBTbhH.mjs → inbox-0Dbv0QVc.mjs} +1 -1
  25. package/dist/{key-1Gf85RN1.mjs → key-ChKDZfvS.mjs} +1 -1
  26. package/dist/{kv-cache-BdA7HpfF.cjs → kv-cache-B0JZ30EF.cjs} +1 -1
  27. package/dist/{kv-cache-BARiicPE.js → kv-cache-CDvE8__9.js} +1 -1
  28. package/dist/{ld-BuuOR_Ms.mjs → ld-DbSNiv8Q.mjs} +99 -10
  29. package/dist/{middleware-C5Nypx4Z.mjs → middleware-BA9cM-vz.mjs} +1 -1
  30. package/dist/{middleware-DDM0j-X6.mjs → middleware-C849kmHB.mjs} +160 -29
  31. package/dist/{middleware-CQRCnFB4.js → middleware-CEufJR5k.js} +152 -21
  32. package/dist/{middleware-DThg01d8.cjs → middleware-C_fDk_dx.cjs} +153 -22
  33. package/dist/{middleware-D9TWYyT_.cjs → middleware-DCtCNota.cjs} +1 -1
  34. package/dist/{mod-w1oyHseQ.d.cts → mod-6uJWdCc0.d.cts} +1 -1
  35. package/dist/{mod-DVPS4yam.d.cts → mod-BA6vqC2S.d.cts} +19 -7
  36. package/dist/{mod-BS-jg3Wx.d.ts → mod-CL9Ep0uo.d.ts} +19 -7
  37. package/dist/{mod-I-34OMN3.d.ts → mod-DtRh015i.d.ts} +1 -1
  38. package/dist/mod.cjs +4 -4
  39. package/dist/mod.d.cts +3 -3
  40. package/dist/mod.d.ts +3 -3
  41. package/dist/mod.js +4 -4
  42. package/dist/nodeinfo/handler.test.mjs +1 -1
  43. package/dist/{owner-BI33UeIv.mjs → owner-BJeJfxnT.mjs} +2 -2
  44. package/dist/{proof-GM73LJjx.mjs → proof-CaMBdk_x.mjs} +36 -12
  45. package/dist/{proof-vVwkNRv4.cjs → proof-DNgQwz9E.cjs} +131 -18
  46. package/dist/{proof-BBHUW8ev.js → proof-DwooE8kV.js} +132 -19
  47. package/dist/{send-nImUK91D.mjs → send-CYrAOC1K.mjs} +2 -2
  48. package/dist/sig/http.test.mjs +2 -2
  49. package/dist/sig/key.test.mjs +2 -2
  50. package/dist/sig/ld.test.mjs +198 -4
  51. package/dist/sig/mod.cjs +2 -2
  52. package/dist/sig/mod.d.cts +1 -1
  53. package/dist/sig/mod.d.ts +1 -1
  54. package/dist/sig/mod.js +2 -2
  55. package/dist/sig/owner.test.mjs +1 -1
  56. package/dist/sig/proof.test.mjs +71 -2
  57. package/dist/{temporal-CfNFB7M_.mjs → temporal-BJoGKOXy.mjs} +1 -1
  58. package/dist/utils/docloader.test.mjs +2 -2
  59. package/dist/utils/mod.cjs +1 -1
  60. package/dist/utils/mod.js +1 -1
  61. package/package.json +5 -5
@@ -2,10 +2,10 @@ import { Temporal } from "@js-temporal/polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
3
  import { t as __exportAll } from "./chunk-CRNNMoPX.js";
4
4
  import { r as getDefaultActivityTransformers } from "./transformers-BGMIq1cs.js";
5
- import { c as importJwk, d as name, f as version, i as exportJwk, l as validateCryptoKey, r as verifyRequest, t as doubleKnockScoped } from "./http-BS1CmlZ6.js";
5
+ import { c as importJwk, d as name, f as version, i as exportJwk, l as validateCryptoKey, r as verifyRequest, t as doubleKnockScoped } from "./http-CrEB4I8A.js";
6
6
  import { a as MAX_BODY_SIZE, i as BodyTooLargeError, n as getNodeInfo, o as readBoundedText, t as nodeInfoToJson } from "./types-B1_pRFE_.js";
7
- import { _ as signJsonLd, a as doesActorOwnKey, c as assertSafeJsonLd, f as detachSignature, g as isInvalidUrlTypeError, h as isClearlyMalformedContextReference, m as hasSignature, n as signObject, o as getKeyOwner, p as getNormalizationContextLoader, r as verifyObject, s as InvalidContextReferenceError, u as compactJsonLd, v as verifyCompactJsonLd, x as wrapContextLoaderForJsonLd } from "./proof-BBHUW8ev.js";
8
- import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-BARiicPE.js";
7
+ import { _ as signJsonLd, a as doesActorOwnKey, c as assertSafeJsonLd, f as detachSignature, g as isInvalidUrlTypeError, h as isClearlyMalformedContextReference, m as hasSignature, n as signObject, o as getKeyOwner, p as getNormalizationContextLoader, r as verifyObject, s as InvalidContextReferenceError, u as compactJsonLd, v as verifyCompactJsonLd, x as wrapContextLoaderForJsonLd } from "./proof-DwooE8kV.js";
8
+ import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-CDvE8__9.js";
9
9
  import { getLogger, withContext } from "@logtape/logtape";
10
10
  import { Activity, Collection, CollectionPage, CryptographicKey, Link, Multikey, Object as Object$1, OrderedCollection, OrderedCollectionPage, getTypeId, lookupObject, traverseCollection } from "@fedify/vocab";
11
11
  import { SpanKind, SpanStatusCode, context, propagation, trace } from "@opentelemetry/api";
@@ -2869,6 +2869,23 @@ var FederationImpl = class extends FederationBuilderImpl {
2869
2869
  _getTracer() {
2870
2870
  return this.tracerProvider.getTracer(name, version);
2871
2871
  }
2872
+ /**
2873
+ * Starts listening on a queue, converting a synchronous throw into
2874
+ * a rejected promise.
2875
+ *
2876
+ * `MessageQueue.listen()` is documented to never reject, but some
2877
+ * implementations (e.g., `WorkersMessageQueue`) throw synchronously. Without
2878
+ * this normalization, one such throw aborts {@link _startQueueInternal} while
2879
+ * it is still collecting listener promises, so listeners started earlier are
2880
+ * never awaited and later queues are never started.
2881
+ */
2882
+ #listenQueue(queue, ctxData, signal) {
2883
+ try {
2884
+ return queue.listen((msg) => this.processQueuedTask(ctxData, msg), { signal });
2885
+ } catch (error) {
2886
+ return Promise.reject(error);
2887
+ }
2888
+ }
2872
2889
  async _startQueueInternal(ctxData, signal, queue) {
2873
2890
  if (this.inboxQueue == null && this.outboxQueue == null) return;
2874
2891
  const logger = getLogger([
@@ -2880,20 +2897,38 @@ var FederationImpl = class extends FederationBuilderImpl {
2880
2897
  if (this.inboxQueue != null && (queue == null || queue === "inbox") && !this.inboxQueueStarted) {
2881
2898
  logger.debug("Starting an inbox task worker.");
2882
2899
  this.inboxQueueStarted = true;
2883
- promises.push(this.inboxQueue.listen((msg) => this.processQueuedTask(ctxData, msg), { signal }));
2900
+ promises.push(this.#listenQueue(this.inboxQueue, ctxData, signal));
2884
2901
  }
2885
2902
  if (this.outboxQueue != null && this.outboxQueue !== this.inboxQueue && (queue == null || queue === "outbox") && !this.outboxQueueStarted) {
2886
2903
  logger.debug("Starting an outbox task worker.");
2887
2904
  this.outboxQueueStarted = true;
2888
- promises.push(this.outboxQueue.listen((msg) => this.processQueuedTask(ctxData, msg), { signal }));
2905
+ promises.push(this.#listenQueue(this.outboxQueue, ctxData, signal));
2889
2906
  }
2890
2907
  if (this.fanoutQueue != null && this.fanoutQueue !== this.inboxQueue && this.fanoutQueue !== this.outboxQueue && (queue == null || queue === "fanout") && !this.fanoutQueueStarted) {
2891
2908
  logger.debug("Starting a fanout task worker.");
2892
2909
  this.fanoutQueueStarted = true;
2893
- promises.push(this.fanoutQueue.listen((msg) => this.processQueuedTask(ctxData, msg), { signal }));
2910
+ promises.push(this.#listenQueue(this.fanoutQueue, ctxData, signal));
2894
2911
  }
2895
2912
  await Promise.all(promises);
2896
2913
  }
2914
+ /**
2915
+ * Starts the queue workers automatically, reporting a failure instead of
2916
+ * leaving the returned promise unobserved.
2917
+ *
2918
+ * Automatic startup is best-effort: call sites cannot await it, so a failing
2919
+ * listener (e.g., `WorkersMessageQueue`, whose `listen()` throws) must not
2920
+ * become an unhandled rejection. {@link startQueue} keeps rejecting for
2921
+ * callers that start workers deliberately.
2922
+ */
2923
+ _startQueueAutomatically(ctxData) {
2924
+ this._startQueueInternal(ctxData).catch((error) => {
2925
+ getLogger([
2926
+ "fedify",
2927
+ "federation",
2928
+ "queue"
2929
+ ]).warn("Failed to start queue workers automatically: {error}", { error });
2930
+ });
2931
+ }
2897
2932
  processQueuedTask(contextData, message) {
2898
2933
  const tracer = this._getTracer();
2899
2934
  const extractedContext = propagation.extract(context.active(), message.traceContext);
@@ -3002,7 +3037,8 @@ var FederationImpl = class extends FederationBuilderImpl {
3002
3037
  await this.sendActivity(keys, message.inboxes, activity, {
3003
3038
  collectionSync: message.collectionSync,
3004
3039
  orderingKey: message.orderingKey,
3005
- context
3040
+ context,
3041
+ activityJsonLd: hasTopLevelProof(message.activity) ? message.activity : void 0
3006
3042
  });
3007
3043
  }
3008
3044
  async #listenOutboxMessage(_, message, span) {
@@ -3409,7 +3445,7 @@ var FederationImpl = class extends FederationBuilderImpl {
3409
3445
  proofCreated = true;
3410
3446
  }
3411
3447
  }
3412
- let jsonLd = await activity.toJsonLd({
3448
+ let jsonLd = options.activityJsonLd != null ? detachSignature(options.activityJsonLd) : await activity.toJsonLd({
3413
3449
  format: "compact",
3414
3450
  contextLoader
3415
3451
  });
@@ -3476,7 +3512,7 @@ var FederationImpl = class extends FederationBuilderImpl {
3476
3512
  privateKey: privateKeyJwk
3477
3513
  });
3478
3514
  }
3479
- if (!this.manuallyStartQueue) this._startQueueInternal(ctx.data);
3515
+ if (!this.manuallyStartQueue) this._startQueueAutomatically(ctx.data);
3480
3516
  const carrier = {};
3481
3517
  propagation.inject(context.active(), carrier);
3482
3518
  const messages = [];
@@ -3688,7 +3724,7 @@ var FederationImpl = class extends FederationBuilderImpl {
3688
3724
  const identity = await this.sharedInboxKeyDispatcher(context);
3689
3725
  if (identity != null) context = this.#createContext(request, contextData, { documentLoader: "identifier" in identity || "username" in identity ? await context.getDocumentLoader(identity) : context.getDocumentLoader(identity) });
3690
3726
  }
3691
- if (!this.manuallyStartQueue) this._startQueueInternal(contextData);
3727
+ if (!this.manuallyStartQueue) this._startQueueAutomatically(contextData);
3692
3728
  const inboxContextFactory = context.toInboxContext.bind(context);
3693
3729
  inboxContextFactory[rawInboxContextFactorySymbol] = context.toInboxContext.bind(context);
3694
3730
  return await handleInbox(request, {
@@ -4126,7 +4162,17 @@ var ContextImpl = class ContextImpl {
4126
4162
  allowPrivateAddress: this.federation.allowPrivateAddress
4127
4163
  });
4128
4164
  }
4129
- sendActivity(sender, recipients, activity, options = {}) {
4165
+ async sendActivity(sender, recipients, activity, options = {}) {
4166
+ let activityJsonLd = void 0;
4167
+ let hasProof = activity.proofIds.length > 0;
4168
+ if (!hasProof) for await (const _ of activity.getProofs({ contextLoader: this.contextLoader })) {
4169
+ hasProof = true;
4170
+ break;
4171
+ }
4172
+ if (hasProof) {
4173
+ activityJsonLd = await activity.toJsonLd({ contextLoader: this.contextLoader });
4174
+ if (!hasTopLevelProof(activityJsonLd)) activityJsonLd = void 0;
4175
+ }
4130
4176
  return this.tracerProvider.getTracer(name, version).startActiveSpan(this.federation.outboxQueue == null || options.immediate ? "activitypub.outbox" : "activitypub.fanout", {
4131
4177
  kind: this.federation.outboxQueue == null || options.immediate ? SpanKind.CLIENT : SpanKind.PRODUCER,
4132
4178
  attributes: {
@@ -4139,7 +4185,7 @@ var ContextImpl = class ContextImpl {
4139
4185
  }, async (span) => {
4140
4186
  try {
4141
4187
  if (activity.id != null) span.setAttribute("activitypub.activity.id", activity.id.href);
4142
- await this.sendActivityInternal(sender, recipients, activity, options, span);
4188
+ await this.sendActivityInternal(sender, recipients, activity, options, span, activityJsonLd);
4143
4189
  } catch (e) {
4144
4190
  span.setStatus({
4145
4191
  code: SpanStatusCode.ERROR,
@@ -4151,7 +4197,7 @@ var ContextImpl = class ContextImpl {
4151
4197
  }
4152
4198
  });
4153
4199
  }
4154
- async sendActivityInternal(sender, recipients, activity, options, span) {
4200
+ async sendActivityInternal(sender, recipients, activity, options, span, activityJsonLd) {
4155
4201
  const logger = getLogger([
4156
4202
  "fedify",
4157
4203
  "federation",
@@ -4198,14 +4244,23 @@ var ContextImpl = class ContextImpl {
4198
4244
  if (!(error instanceof RouterError)) throw error;
4199
4245
  }
4200
4246
  } else expandedRecipients = [recipients];
4201
- const opts = {
4202
- context: this,
4203
- orderingKey: options.orderingKey,
4204
- collectionSync,
4205
- immediate: options.immediate
4206
- };
4207
4247
  span.setAttribute("activitypub.inboxes", expandedRecipients.length);
4208
4248
  for (const activityTransformer of this.federation.activityTransformers) activity = activityTransformer(activity, this);
4249
+ let contextMatches = false;
4250
+ if (activityJsonLd != null) {
4251
+ const capturedJsonLd = activityJsonLd;
4252
+ const transformedJsonLd = await activity.toJsonLd({
4253
+ format: "compact",
4254
+ contextLoader: this.contextLoader
4255
+ });
4256
+ const contentChanged = getContentFields(transformedJsonLd) !== getContentFields(capturedJsonLd);
4257
+ contextMatches = getContextFields(transformedJsonLd) === getContextFields(capturedJsonLd);
4258
+ const proofsChanged = getProofFields(transformedJsonLd) !== getProofFields(capturedJsonLd);
4259
+ if (contentChanged || !contextMatches && proofsChanged) {
4260
+ activity = activity.clone({ proofs: await getUncapturedProofs(activity, capturedJsonLd, this.contextLoader) });
4261
+ activityJsonLd = void 0;
4262
+ } else if (proofsChanged) activityJsonLd = void 0;
4263
+ }
4209
4264
  span?.setAttribute("activitypub.activity.id", activity?.id?.href ?? "");
4210
4265
  if (activity.actorId == null) {
4211
4266
  logger.error("Activity {activityId} to send does not have an actor.", {
@@ -4216,14 +4271,27 @@ var ContextImpl = class ContextImpl {
4216
4271
  }
4217
4272
  if (actorKeyPairs != null) {
4218
4273
  const contextLoader = this.contextLoader;
4274
+ if (actorKeyPairs.some((kp) => kp.privateKey.algorithm.name === "Ed25519" && kp.multikey.id != null)) {
4275
+ if (activityJsonLd != null && !contextMatches) activity = activity.clone({ proofs: [] });
4276
+ }
4277
+ let proofAdded = false;
4219
4278
  for (const kp of actorKeyPairs) {
4220
4279
  if (kp.privateKey.algorithm.name !== "Ed25519" || kp.multikey.id == null) continue;
4221
4280
  activity = await signObject(activity, kp.privateKey, kp.multikey.id, {
4222
4281
  contextLoader,
4223
4282
  tracerProvider: this.tracerProvider
4224
4283
  });
4284
+ proofAdded = true;
4225
4285
  }
4286
+ if (proofAdded) activityJsonLd = void 0;
4226
4287
  }
4288
+ const opts = {
4289
+ context: this,
4290
+ orderingKey: options.orderingKey,
4291
+ collectionSync,
4292
+ immediate: options.immediate,
4293
+ activityJsonLd
4294
+ };
4227
4295
  const inboxes = extractInboxes({
4228
4296
  recipients: expandedRecipients,
4229
4297
  preferSharedInbox: options.preferSharedInbox,
@@ -4253,7 +4321,7 @@ var ContextImpl = class ContextImpl {
4253
4321
  actorIds: [...actorIds],
4254
4322
  sharedInbox
4255
4323
  }])),
4256
- activity: await activity.toJsonLd({
4324
+ activity: activityJsonLd != null ? detachSignature(activityJsonLd) : await activity.toJsonLd({
4257
4325
  format: "compact",
4258
4326
  contextLoader: this.contextLoader
4259
4327
  }),
@@ -4263,7 +4331,7 @@ var ContextImpl = class ContextImpl {
4263
4331
  orderingKey: options.orderingKey,
4264
4332
  traceContext: carrier
4265
4333
  };
4266
- if (!this.federation.manuallyStartQueue) this.federation._startQueueInternal(this.data);
4334
+ if (!this.federation.manuallyStartQueue) this.federation._startQueueAutomatically(this.data);
4267
4335
  await this.federation.fanoutQueue.enqueue(message, { orderingKey: options.orderingKey });
4268
4336
  }
4269
4337
  async *getFollowers(identifier) {
@@ -4739,6 +4807,69 @@ var KvSpecDeterminer = class {
4739
4807
  function notFound(_request) {
4740
4808
  return new Response("Not Found", { status: 404 });
4741
4809
  }
4810
+ function hasTopLevelProof(value) {
4811
+ return typeof value === "object" && value != null && !Array.isArray(value) && globalThis.Object.prototype.hasOwnProperty.call(value, "proof");
4812
+ }
4813
+ function getContentFields(value) {
4814
+ if (typeof value !== "object" || value == null || Array.isArray(value)) return JSON.stringify(sortJsonKeys(value));
4815
+ const record = { ...value };
4816
+ delete record["@context"];
4817
+ delete record.proof;
4818
+ delete record.signature;
4819
+ return JSON.stringify(sortJsonKeys(record));
4820
+ }
4821
+ function getContextFields(value) {
4822
+ if (typeof value !== "object" || value == null || Array.isArray(value)) return JSON.stringify(sortJsonKeys(value));
4823
+ const context = value["@context"];
4824
+ return JSON.stringify(sortJsonKeys(context ?? null));
4825
+ }
4826
+ function getProofEntries(value) {
4827
+ if (typeof value !== "object" || value == null || Array.isArray(value)) return [];
4828
+ const proof = value.proof;
4829
+ if (proof == null) return [];
4830
+ return Array.isArray(proof) ? proof : [proof];
4831
+ }
4832
+ function normalizeProofEntry(proof) {
4833
+ const stripped = stripContexts(proof);
4834
+ if (typeof stripped !== "object" || stripped == null || Array.isArray(stripped)) return stripped;
4835
+ const record = { ...stripped };
4836
+ if (typeof record.created === "string") try {
4837
+ record.created = Temporal.Instant.from(record.created).toString();
4838
+ } catch {}
4839
+ return record;
4840
+ }
4841
+ function getProofEntryFields(proof) {
4842
+ return JSON.stringify(sortJsonKeys(normalizeProofEntry(proof)));
4843
+ }
4844
+ function getProofFields(value) {
4845
+ return JSON.stringify(getProofEntries(value).map(getProofEntryFields).sort());
4846
+ }
4847
+ async function getUncapturedProofs(activity, capturedJsonLd, contextLoader) {
4848
+ const capturedProofs = new Set(getProofEntries(capturedJsonLd).map(getProofEntryFields));
4849
+ const retainedProofs = [];
4850
+ for await (const proof of activity.getProofs({ contextLoader })) {
4851
+ const proofJsonLd = await proof.toJsonLd({
4852
+ format: "compact",
4853
+ contextLoader
4854
+ });
4855
+ if (!capturedProofs.has(getProofEntryFields(proofJsonLd))) retainedProofs.push(proof);
4856
+ }
4857
+ return retainedProofs;
4858
+ }
4859
+ function stripContexts(value) {
4860
+ if (Array.isArray(value)) return value.map(stripContexts);
4861
+ if (typeof value !== "object" || value == null) return value;
4862
+ const record = { ...value };
4863
+ delete record["@context"];
4864
+ delete record.signature;
4865
+ for (const key of globalThis.Object.keys(record)) record[key] = stripContexts(record[key]);
4866
+ return record;
4867
+ }
4868
+ function sortJsonKeys(value) {
4869
+ if (Array.isArray(value)) return value.map(sortJsonKeys);
4870
+ if (typeof value !== "object" || value == null) return value;
4871
+ return globalThis.Object.fromEntries(globalThis.Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, item]) => [key, sortJsonKeys(item)]));
4872
+ }
4742
4873
  function notAcceptable(_request) {
4743
4874
  return new Response("Not Acceptable", {
4744
4875
  status: 406,
@@ -2,10 +2,10 @@ const { Temporal } = require("@js-temporal/polyfill");
2
2
  const { URLPattern } = require("urlpattern-polyfill");
3
3
  const require_chunk = require("./chunk-DDcVe30Y.cjs");
4
4
  const require_transformers = require("./transformers-NeAONrAq.cjs");
5
- const require_http = require("./http-DbDnj-Q7.cjs");
5
+ const require_http = require("./http-D69BMfAn.cjs");
6
6
  const require_types = require("./types-DOvyymM4.cjs");
7
- const require_proof = require("./proof-vVwkNRv4.cjs");
8
- const require_kv_cache = require("./kv-cache-BdA7HpfF.cjs");
7
+ const require_proof = require("./proof-DNgQwz9E.cjs");
8
+ const require_kv_cache = require("./kv-cache-B0JZ30EF.cjs");
9
9
  let _logtape_logtape = require("@logtape/logtape");
10
10
  let _fedify_vocab = require("@fedify/vocab");
11
11
  let _opentelemetry_api = require("@opentelemetry/api");
@@ -351,7 +351,7 @@ var FederationBuilderImpl = class {
351
351
  this.collectionTypeIds = {};
352
352
  }
353
353
  async build(options) {
354
- const { FederationImpl } = await Promise.resolve().then(() => require("./middleware-D9TWYyT_.cjs"));
354
+ const { FederationImpl } = await Promise.resolve().then(() => require("./middleware-DCtCNota.cjs"));
355
355
  const f = new FederationImpl(options);
356
356
  const trailingSlashInsensitiveValue = f.router.trailingSlashInsensitive;
357
357
  f.router = this.router.clone();
@@ -2863,6 +2863,23 @@ var FederationImpl = class extends FederationBuilderImpl {
2863
2863
  _getTracer() {
2864
2864
  return this.tracerProvider.getTracer(require_http.name, require_http.version);
2865
2865
  }
2866
+ /**
2867
+ * Starts listening on a queue, converting a synchronous throw into
2868
+ * a rejected promise.
2869
+ *
2870
+ * `MessageQueue.listen()` is documented to never reject, but some
2871
+ * implementations (e.g., `WorkersMessageQueue`) throw synchronously. Without
2872
+ * this normalization, one such throw aborts {@link _startQueueInternal} while
2873
+ * it is still collecting listener promises, so listeners started earlier are
2874
+ * never awaited and later queues are never started.
2875
+ */
2876
+ #listenQueue(queue, ctxData, signal) {
2877
+ try {
2878
+ return queue.listen((msg) => this.processQueuedTask(ctxData, msg), { signal });
2879
+ } catch (error) {
2880
+ return Promise.reject(error);
2881
+ }
2882
+ }
2866
2883
  async _startQueueInternal(ctxData, signal, queue) {
2867
2884
  if (this.inboxQueue == null && this.outboxQueue == null) return;
2868
2885
  const logger = (0, _logtape_logtape.getLogger)([
@@ -2874,20 +2891,38 @@ var FederationImpl = class extends FederationBuilderImpl {
2874
2891
  if (this.inboxQueue != null && (queue == null || queue === "inbox") && !this.inboxQueueStarted) {
2875
2892
  logger.debug("Starting an inbox task worker.");
2876
2893
  this.inboxQueueStarted = true;
2877
- promises.push(this.inboxQueue.listen((msg) => this.processQueuedTask(ctxData, msg), { signal }));
2894
+ promises.push(this.#listenQueue(this.inboxQueue, ctxData, signal));
2878
2895
  }
2879
2896
  if (this.outboxQueue != null && this.outboxQueue !== this.inboxQueue && (queue == null || queue === "outbox") && !this.outboxQueueStarted) {
2880
2897
  logger.debug("Starting an outbox task worker.");
2881
2898
  this.outboxQueueStarted = true;
2882
- promises.push(this.outboxQueue.listen((msg) => this.processQueuedTask(ctxData, msg), { signal }));
2899
+ promises.push(this.#listenQueue(this.outboxQueue, ctxData, signal));
2883
2900
  }
2884
2901
  if (this.fanoutQueue != null && this.fanoutQueue !== this.inboxQueue && this.fanoutQueue !== this.outboxQueue && (queue == null || queue === "fanout") && !this.fanoutQueueStarted) {
2885
2902
  logger.debug("Starting a fanout task worker.");
2886
2903
  this.fanoutQueueStarted = true;
2887
- promises.push(this.fanoutQueue.listen((msg) => this.processQueuedTask(ctxData, msg), { signal }));
2904
+ promises.push(this.#listenQueue(this.fanoutQueue, ctxData, signal));
2888
2905
  }
2889
2906
  await Promise.all(promises);
2890
2907
  }
2908
+ /**
2909
+ * Starts the queue workers automatically, reporting a failure instead of
2910
+ * leaving the returned promise unobserved.
2911
+ *
2912
+ * Automatic startup is best-effort: call sites cannot await it, so a failing
2913
+ * listener (e.g., `WorkersMessageQueue`, whose `listen()` throws) must not
2914
+ * become an unhandled rejection. {@link startQueue} keeps rejecting for
2915
+ * callers that start workers deliberately.
2916
+ */
2917
+ _startQueueAutomatically(ctxData) {
2918
+ this._startQueueInternal(ctxData).catch((error) => {
2919
+ (0, _logtape_logtape.getLogger)([
2920
+ "fedify",
2921
+ "federation",
2922
+ "queue"
2923
+ ]).warn("Failed to start queue workers automatically: {error}", { error });
2924
+ });
2925
+ }
2891
2926
  processQueuedTask(contextData, message) {
2892
2927
  const tracer = this._getTracer();
2893
2928
  const extractedContext = _opentelemetry_api.propagation.extract(_opentelemetry_api.context.active(), message.traceContext);
@@ -2996,7 +3031,8 @@ var FederationImpl = class extends FederationBuilderImpl {
2996
3031
  await this.sendActivity(keys, message.inboxes, activity, {
2997
3032
  collectionSync: message.collectionSync,
2998
3033
  orderingKey: message.orderingKey,
2999
- context
3034
+ context,
3035
+ activityJsonLd: hasTopLevelProof(message.activity) ? message.activity : void 0
3000
3036
  });
3001
3037
  }
3002
3038
  async #listenOutboxMessage(_, message, span) {
@@ -3403,7 +3439,7 @@ var FederationImpl = class extends FederationBuilderImpl {
3403
3439
  proofCreated = true;
3404
3440
  }
3405
3441
  }
3406
- let jsonLd = await activity.toJsonLd({
3442
+ let jsonLd = options.activityJsonLd != null ? require_proof.detachSignature(options.activityJsonLd) : await activity.toJsonLd({
3407
3443
  format: "compact",
3408
3444
  contextLoader
3409
3445
  });
@@ -3470,7 +3506,7 @@ var FederationImpl = class extends FederationBuilderImpl {
3470
3506
  privateKey: privateKeyJwk
3471
3507
  });
3472
3508
  }
3473
- if (!this.manuallyStartQueue) this._startQueueInternal(ctx.data);
3509
+ if (!this.manuallyStartQueue) this._startQueueAutomatically(ctx.data);
3474
3510
  const carrier = {};
3475
3511
  _opentelemetry_api.propagation.inject(_opentelemetry_api.context.active(), carrier);
3476
3512
  const messages = [];
@@ -3682,7 +3718,7 @@ var FederationImpl = class extends FederationBuilderImpl {
3682
3718
  const identity = await this.sharedInboxKeyDispatcher(context);
3683
3719
  if (identity != null) context = this.#createContext(request, contextData, { documentLoader: "identifier" in identity || "username" in identity ? await context.getDocumentLoader(identity) : context.getDocumentLoader(identity) });
3684
3720
  }
3685
- if (!this.manuallyStartQueue) this._startQueueInternal(contextData);
3721
+ if (!this.manuallyStartQueue) this._startQueueAutomatically(contextData);
3686
3722
  const inboxContextFactory = context.toInboxContext.bind(context);
3687
3723
  inboxContextFactory[rawInboxContextFactorySymbol] = context.toInboxContext.bind(context);
3688
3724
  return await handleInbox(request, {
@@ -4120,7 +4156,17 @@ var ContextImpl = class ContextImpl {
4120
4156
  allowPrivateAddress: this.federation.allowPrivateAddress
4121
4157
  });
4122
4158
  }
4123
- sendActivity(sender, recipients, activity, options = {}) {
4159
+ async sendActivity(sender, recipients, activity, options = {}) {
4160
+ let activityJsonLd = void 0;
4161
+ let hasProof = activity.proofIds.length > 0;
4162
+ if (!hasProof) for await (const _ of activity.getProofs({ contextLoader: this.contextLoader })) {
4163
+ hasProof = true;
4164
+ break;
4165
+ }
4166
+ if (hasProof) {
4167
+ activityJsonLd = await activity.toJsonLd({ contextLoader: this.contextLoader });
4168
+ if (!hasTopLevelProof(activityJsonLd)) activityJsonLd = void 0;
4169
+ }
4124
4170
  return this.tracerProvider.getTracer(require_http.name, require_http.version).startActiveSpan(this.federation.outboxQueue == null || options.immediate ? "activitypub.outbox" : "activitypub.fanout", {
4125
4171
  kind: this.federation.outboxQueue == null || options.immediate ? _opentelemetry_api.SpanKind.CLIENT : _opentelemetry_api.SpanKind.PRODUCER,
4126
4172
  attributes: {
@@ -4133,7 +4179,7 @@ var ContextImpl = class ContextImpl {
4133
4179
  }, async (span) => {
4134
4180
  try {
4135
4181
  if (activity.id != null) span.setAttribute("activitypub.activity.id", activity.id.href);
4136
- await this.sendActivityInternal(sender, recipients, activity, options, span);
4182
+ await this.sendActivityInternal(sender, recipients, activity, options, span, activityJsonLd);
4137
4183
  } catch (e) {
4138
4184
  span.setStatus({
4139
4185
  code: _opentelemetry_api.SpanStatusCode.ERROR,
@@ -4145,7 +4191,7 @@ var ContextImpl = class ContextImpl {
4145
4191
  }
4146
4192
  });
4147
4193
  }
4148
- async sendActivityInternal(sender, recipients, activity, options, span) {
4194
+ async sendActivityInternal(sender, recipients, activity, options, span, activityJsonLd) {
4149
4195
  const logger = (0, _logtape_logtape.getLogger)([
4150
4196
  "fedify",
4151
4197
  "federation",
@@ -4192,14 +4238,23 @@ var ContextImpl = class ContextImpl {
4192
4238
  if (!(error instanceof RouterError)) throw error;
4193
4239
  }
4194
4240
  } else expandedRecipients = [recipients];
4195
- const opts = {
4196
- context: this,
4197
- orderingKey: options.orderingKey,
4198
- collectionSync,
4199
- immediate: options.immediate
4200
- };
4201
4241
  span.setAttribute("activitypub.inboxes", expandedRecipients.length);
4202
4242
  for (const activityTransformer of this.federation.activityTransformers) activity = activityTransformer(activity, this);
4243
+ let contextMatches = false;
4244
+ if (activityJsonLd != null) {
4245
+ const capturedJsonLd = activityJsonLd;
4246
+ const transformedJsonLd = await activity.toJsonLd({
4247
+ format: "compact",
4248
+ contextLoader: this.contextLoader
4249
+ });
4250
+ const contentChanged = getContentFields(transformedJsonLd) !== getContentFields(capturedJsonLd);
4251
+ contextMatches = getContextFields(transformedJsonLd) === getContextFields(capturedJsonLd);
4252
+ const proofsChanged = getProofFields(transformedJsonLd) !== getProofFields(capturedJsonLd);
4253
+ if (contentChanged || !contextMatches && proofsChanged) {
4254
+ activity = activity.clone({ proofs: await getUncapturedProofs(activity, capturedJsonLd, this.contextLoader) });
4255
+ activityJsonLd = void 0;
4256
+ } else if (proofsChanged) activityJsonLd = void 0;
4257
+ }
4203
4258
  span?.setAttribute("activitypub.activity.id", activity?.id?.href ?? "");
4204
4259
  if (activity.actorId == null) {
4205
4260
  logger.error("Activity {activityId} to send does not have an actor.", {
@@ -4210,14 +4265,27 @@ var ContextImpl = class ContextImpl {
4210
4265
  }
4211
4266
  if (actorKeyPairs != null) {
4212
4267
  const contextLoader = this.contextLoader;
4268
+ if (actorKeyPairs.some((kp) => kp.privateKey.algorithm.name === "Ed25519" && kp.multikey.id != null)) {
4269
+ if (activityJsonLd != null && !contextMatches) activity = activity.clone({ proofs: [] });
4270
+ }
4271
+ let proofAdded = false;
4213
4272
  for (const kp of actorKeyPairs) {
4214
4273
  if (kp.privateKey.algorithm.name !== "Ed25519" || kp.multikey.id == null) continue;
4215
4274
  activity = await require_proof.signObject(activity, kp.privateKey, kp.multikey.id, {
4216
4275
  contextLoader,
4217
4276
  tracerProvider: this.tracerProvider
4218
4277
  });
4278
+ proofAdded = true;
4219
4279
  }
4280
+ if (proofAdded) activityJsonLd = void 0;
4220
4281
  }
4282
+ const opts = {
4283
+ context: this,
4284
+ orderingKey: options.orderingKey,
4285
+ collectionSync,
4286
+ immediate: options.immediate,
4287
+ activityJsonLd
4288
+ };
4221
4289
  const inboxes = extractInboxes({
4222
4290
  recipients: expandedRecipients,
4223
4291
  preferSharedInbox: options.preferSharedInbox,
@@ -4247,7 +4315,7 @@ var ContextImpl = class ContextImpl {
4247
4315
  actorIds: [...actorIds],
4248
4316
  sharedInbox
4249
4317
  }])),
4250
- activity: await activity.toJsonLd({
4318
+ activity: activityJsonLd != null ? require_proof.detachSignature(activityJsonLd) : await activity.toJsonLd({
4251
4319
  format: "compact",
4252
4320
  contextLoader: this.contextLoader
4253
4321
  }),
@@ -4257,7 +4325,7 @@ var ContextImpl = class ContextImpl {
4257
4325
  orderingKey: options.orderingKey,
4258
4326
  traceContext: carrier
4259
4327
  };
4260
- if (!this.federation.manuallyStartQueue) this.federation._startQueueInternal(this.data);
4328
+ if (!this.federation.manuallyStartQueue) this.federation._startQueueAutomatically(this.data);
4261
4329
  await this.federation.fanoutQueue.enqueue(message, { orderingKey: options.orderingKey });
4262
4330
  }
4263
4331
  async *getFollowers(identifier) {
@@ -4733,6 +4801,69 @@ var KvSpecDeterminer = class {
4733
4801
  function notFound(_request) {
4734
4802
  return new Response("Not Found", { status: 404 });
4735
4803
  }
4804
+ function hasTopLevelProof(value) {
4805
+ return typeof value === "object" && value != null && !Array.isArray(value) && globalThis.Object.prototype.hasOwnProperty.call(value, "proof");
4806
+ }
4807
+ function getContentFields(value) {
4808
+ if (typeof value !== "object" || value == null || Array.isArray(value)) return JSON.stringify(sortJsonKeys(value));
4809
+ const record = { ...value };
4810
+ delete record["@context"];
4811
+ delete record.proof;
4812
+ delete record.signature;
4813
+ return JSON.stringify(sortJsonKeys(record));
4814
+ }
4815
+ function getContextFields(value) {
4816
+ if (typeof value !== "object" || value == null || Array.isArray(value)) return JSON.stringify(sortJsonKeys(value));
4817
+ const context = value["@context"];
4818
+ return JSON.stringify(sortJsonKeys(context ?? null));
4819
+ }
4820
+ function getProofEntries(value) {
4821
+ if (typeof value !== "object" || value == null || Array.isArray(value)) return [];
4822
+ const proof = value.proof;
4823
+ if (proof == null) return [];
4824
+ return Array.isArray(proof) ? proof : [proof];
4825
+ }
4826
+ function normalizeProofEntry(proof) {
4827
+ const stripped = stripContexts(proof);
4828
+ if (typeof stripped !== "object" || stripped == null || Array.isArray(stripped)) return stripped;
4829
+ const record = { ...stripped };
4830
+ if (typeof record.created === "string") try {
4831
+ record.created = Temporal.Instant.from(record.created).toString();
4832
+ } catch {}
4833
+ return record;
4834
+ }
4835
+ function getProofEntryFields(proof) {
4836
+ return JSON.stringify(sortJsonKeys(normalizeProofEntry(proof)));
4837
+ }
4838
+ function getProofFields(value) {
4839
+ return JSON.stringify(getProofEntries(value).map(getProofEntryFields).sort());
4840
+ }
4841
+ async function getUncapturedProofs(activity, capturedJsonLd, contextLoader) {
4842
+ const capturedProofs = new Set(getProofEntries(capturedJsonLd).map(getProofEntryFields));
4843
+ const retainedProofs = [];
4844
+ for await (const proof of activity.getProofs({ contextLoader })) {
4845
+ const proofJsonLd = await proof.toJsonLd({
4846
+ format: "compact",
4847
+ contextLoader
4848
+ });
4849
+ if (!capturedProofs.has(getProofEntryFields(proofJsonLd))) retainedProofs.push(proof);
4850
+ }
4851
+ return retainedProofs;
4852
+ }
4853
+ function stripContexts(value) {
4854
+ if (Array.isArray(value)) return value.map(stripContexts);
4855
+ if (typeof value !== "object" || value == null) return value;
4856
+ const record = { ...value };
4857
+ delete record["@context"];
4858
+ delete record.signature;
4859
+ for (const key of globalThis.Object.keys(record)) record[key] = stripContexts(record[key]);
4860
+ return record;
4861
+ }
4862
+ function sortJsonKeys(value) {
4863
+ if (Array.isArray(value)) return value.map(sortJsonKeys);
4864
+ if (typeof value !== "object" || value == null) return value;
4865
+ return globalThis.Object.fromEntries(globalThis.Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, item]) => [key, sortJsonKeys(item)]));
4866
+ }
4736
4867
  function notAcceptable(_request) {
4737
4868
  return new Response("Not Acceptable", {
4738
4869
  status: 406,
@@ -1,4 +1,4 @@
1
1
  const { Temporal } = require("@js-temporal/polyfill");
2
2
  const { URLPattern } = require("urlpattern-polyfill");
3
- const require_middleware = require("./middleware-DThg01d8.cjs");
3
+ const require_middleware = require("./middleware-C_fDk_dx.cjs");
4
4
  exports.FederationImpl = require_middleware.FederationImpl;
@@ -1,5 +1,5 @@
1
1
  /// <reference lib="esnext.temporal" />
2
- import { X as ActorDispatcher, Y as ActorAliasMapper, Z as ActorHandleMapper, mt as WebFingerLinksDispatcher, s as RequestContext } from "./context-CnoWxuoB.cjs";
2
+ import { X as ActorDispatcher, Y as ActorAliasMapper, Z as ActorHandleMapper, mt as WebFingerLinksDispatcher, s as RequestContext } from "./context-ao-qb3eP.cjs";
3
3
  import { Span, Tracer } from "@opentelemetry/api";
4
4
 
5
5
  //#region src/federation/webfinger.d.ts