@fedify/fedify 2.2.0-dev.869 → 2.2.0-pr.695.23

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 (64) hide show
  1. package/README.md +0 -2
  2. package/dist/{builder-D8YzbzDN.mjs → builder-_3USSkAi.mjs} +7 -57
  3. package/dist/compat/mod.d.cts +1 -1
  4. package/dist/compat/mod.d.ts +1 -1
  5. package/dist/compat/transformers.test.mjs +1 -1
  6. package/dist/{context-BGrYMSTk.d.ts → context-78ecvxf5.d.ts} +1 -143
  7. package/dist/{context-CMUd4wy0.d.cts → context-DYDPdoCb.d.cts} +1 -143
  8. package/dist/{context-Dk_tacqz.mjs → context-Juj6bdHC.mjs} +2 -17
  9. package/dist/{deno-DXdMYkAF.mjs → deno-CALl2W-v.mjs} +1 -1
  10. package/dist/{docloader-D6GGGkLu.mjs → docloader-Cd_GcKDJ.mjs} +2 -2
  11. package/dist/federation/builder.test.mjs +1 -25
  12. package/dist/federation/handler.test.mjs +8 -369
  13. package/dist/federation/idempotency.test.mjs +2 -2
  14. package/dist/federation/inbox.test.mjs +3 -3
  15. package/dist/federation/middleware.test.mjs +8 -510
  16. package/dist/federation/mod.cjs +1 -1
  17. package/dist/federation/mod.d.cts +3 -3
  18. package/dist/federation/mod.d.ts +3 -3
  19. package/dist/federation/mod.js +1 -1
  20. package/dist/federation/send.test.mjs +3 -3
  21. package/dist/federation/webfinger.test.mjs +2 -2
  22. package/dist/{http-Bkl65Xah.cjs → http-BmjzD8cM.cjs} +1 -1
  23. package/dist/{http-kJLVVuQ4.mjs → http-BoYB66uz.mjs} +2 -2
  24. package/dist/{http-Bwhs9THj.js → http-Co58ywXN.js} +1 -1
  25. package/dist/inbox-BRn2Zxr4.mjs +179 -0
  26. package/dist/{key-DY9YAHVK.mjs → key-CrCG-yLH.mjs} +1 -1
  27. package/dist/{kv-cache-DTEfriBO.js → kv-cache-DWlJLiMn.js} +1 -1
  28. package/dist/{kv-cache-PqsOT6Ky.cjs → kv-cache-GmvjgIY4.cjs} +1 -1
  29. package/dist/{ld-Bittq8I7.mjs → ld-Cj_0JVzk.mjs} +3 -26
  30. package/dist/{middleware-BE03PkEx.mjs → middleware-B-hCoIdY.mjs} +180 -612
  31. package/dist/{middleware-DVMQdDWr.cjs → middleware-BXCjmWN2.cjs} +1 -1
  32. package/dist/{middleware-VSA_KWpd.js → middleware-CLVQBjm2.js} +368 -716
  33. package/dist/{middleware-ChIzhod7.mjs → middleware-Djvz1scF.mjs} +1 -1
  34. package/dist/{middleware-DMOqJ2rJ.cjs → middleware-SVMhMPsP.cjs} +365 -718
  35. package/dist/{mod-BcJHeuv1.d.cts → mod-CEohtXhV.d.cts} +1 -1
  36. package/dist/{mod-CJXfyw7v.d.ts → mod-CokIUYDr.d.ts} +1 -1
  37. package/dist/{mod-Cr3f-ACa.d.cts → mod-DoJBjjnO.d.cts} +1 -18
  38. package/dist/{mod-CR8soWa9.d.ts → mod-DvxszxXC.d.ts} +1 -18
  39. package/dist/mod.cjs +4 -6
  40. package/dist/mod.d.cts +5 -5
  41. package/dist/mod.d.ts +5 -5
  42. package/dist/mod.js +5 -5
  43. package/dist/nodeinfo/handler.test.mjs +2 -2
  44. package/dist/{owner-BZgNaUac.mjs → owner-CQ0ITJYn.mjs} +2 -2
  45. package/dist/{proof-DX47G5Fd.js → proof-B2qPm5I4.js} +2 -54
  46. package/dist/{proof-B5TlVvOq.cjs → proof-Bvs3L21X.cjs} +3 -61
  47. package/dist/{proof-41DPsEcO.mjs → proof-OOosrRgx.mjs} +3 -32
  48. package/dist/{send-m-XUlhVD.mjs → send-CP34W1Zh.mjs} +2 -2
  49. package/dist/sig/http.test.mjs +2 -2
  50. package/dist/sig/key.test.mjs +1 -1
  51. package/dist/sig/ld.test.mjs +2 -44
  52. package/dist/sig/mod.cjs +2 -4
  53. package/dist/sig/mod.d.cts +2 -2
  54. package/dist/sig/mod.d.ts +2 -2
  55. package/dist/sig/mod.js +3 -3
  56. package/dist/sig/owner.test.mjs +1 -1
  57. package/dist/sig/proof.test.mjs +2 -46
  58. package/dist/testing/mod.d.mts +1 -149
  59. package/dist/testing/mod.mjs +2 -2
  60. package/dist/utils/docloader.test.mjs +2 -2
  61. package/dist/utils/mod.cjs +1 -1
  62. package/dist/utils/mod.js +1 -1
  63. package/package.json +5 -5
  64. package/dist/activity-listener-Ck3JZ_hR.mjs +0 -40
@@ -2,10 +2,10 @@ import { Temporal } from "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  import { t as __exportAll } from "./chunk-nlSIicah.js";
4
4
  import { r as getDefaultActivityTransformers } from "./transformers-ve6e2xcg.js";
5
- import { _ as version, a as verifyRequestDetailed, d as validateCryptoKey, f as formatAcceptSignature, g as name, i as verifyRequest, n as parseRfc9421SignatureInput, o as exportJwk, t as doubleKnock, u as importJwk } from "./http-Bwhs9THj.js";
6
- import { d as hasSignatureLike, f as signJsonLd, i as verifyObject, n as hasProofLike, o as doesActorOwnKey, p as verifyJsonLd, r as signObject, s as getKeyOwner, u as detachSignature } from "./proof-DX47G5Fd.js";
5
+ import { _ as version, a as verifyRequestDetailed, d as validateCryptoKey, f as formatAcceptSignature, g as name, i as verifyRequest, n as parseRfc9421SignatureInput, o as exportJwk, t as doubleKnock, u as importJwk } from "./http-Co58ywXN.js";
6
+ import { a as doesActorOwnKey, d as signJsonLd, f as verifyJsonLd, l as detachSignature, n as signObject, o as getKeyOwner, r as verifyObject, u as hasSignature } from "./proof-B2qPm5I4.js";
7
7
  import { n as getNodeInfo, t as nodeInfoToJson } from "./types-hvL8ElAs.js";
8
- import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-DTEfriBO.js";
8
+ import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-DWlJLiMn.js";
9
9
  import { getLogger, withContext } from "@logtape/logtape";
10
10
  import { Activity, Collection, CollectionPage, CryptographicKey, Link, Multikey, Object as Object$1, OrderedCollection, OrderedCollectionPage, Tombstone, getTypeId, lookupObject, traverseCollection } from "@fedify/vocab";
11
11
  import { SpanKind, SpanStatusCode, context, propagation, trace } from "@opentelemetry/api";
@@ -17,15 +17,14 @@ import { FetchError, getDocumentLoader } from "@fedify/vocab-runtime";
17
17
  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";
18
18
  import { lookupWebFinger } from "@fedify/webfinger";
19
19
  import { domainToASCII } from "node:url";
20
- //#region src/federation/activity-listener.ts
21
- var ActivityListenerSet = class {
20
+ //#region src/federation/inbox.ts
21
+ var InboxListenerSet = class InboxListenerSet {
22
22
  #listeners;
23
23
  constructor() {
24
24
  this.#listeners = /* @__PURE__ */ new Map();
25
25
  }
26
26
  clone() {
27
- const Clone = this.constructor;
28
- const clone = new Clone();
27
+ const clone = new InboxListenerSet();
29
28
  clone.#listeners = new Map(this.#listeners);
30
29
  return clone;
31
30
  }
@@ -35,13 +34,14 @@ var ActivityListenerSet = class {
35
34
  }
36
35
  dispatchWithClass(activity) {
37
36
  let cls = activity.constructor;
38
- while (cls != null) {
39
- if (this.#listeners.has(cls)) break;
37
+ const inboxListeners = this.#listeners;
38
+ if (inboxListeners == null) return null;
39
+ while (true) {
40
+ if (inboxListeners.has(cls)) break;
40
41
  if (cls === Activity) return null;
41
42
  cls = globalThis.Object.getPrototypeOf(cls);
42
43
  }
43
- if (cls == null) return null;
44
- const listener = this.#listeners.get(cls);
44
+ const listener = inboxListeners.get(cls);
45
45
  return {
46
46
  class: cls,
47
47
  listener
@@ -51,6 +51,142 @@ var ActivityListenerSet = class {
51
51
  return this.dispatchWithClass(activity)?.listener ?? null;
52
52
  }
53
53
  };
54
+ async function routeActivity({ context: ctx, json, activity, recipient, inboxListeners, inboxContextFactory, inboxErrorHandler, kv, kvPrefixes, queue, span, tracerProvider, idempotencyStrategy }) {
55
+ const logger = getLogger([
56
+ "fedify",
57
+ "federation",
58
+ "inbox"
59
+ ]);
60
+ let cacheKey = null;
61
+ if (activity.id != null) {
62
+ const inboxContext = inboxContextFactory(recipient, json, activity.id?.href, getTypeId(activity).href);
63
+ const strategy = idempotencyStrategy ?? "per-inbox";
64
+ let keyString;
65
+ if (typeof strategy === "function") keyString = await strategy(inboxContext, activity);
66
+ else switch (strategy) {
67
+ case "global":
68
+ keyString = activity.id.href;
69
+ break;
70
+ case "per-origin":
71
+ keyString = `${ctx.origin}\n${activity.id.href}`;
72
+ break;
73
+ case "per-inbox":
74
+ keyString = `${ctx.origin}\n${activity.id.href}\n${recipient == null ? "sharedInbox" : `inbox\n${recipient}`}`;
75
+ break;
76
+ default: keyString = `${ctx.origin}\n${activity.id.href}`;
77
+ }
78
+ if (keyString != null) cacheKey = [...kvPrefixes.activityIdempotence, keyString];
79
+ }
80
+ if (cacheKey != null) {
81
+ if (await kv.get(cacheKey) === true) {
82
+ logger.debug("Activity {activityId} has already been processed.", {
83
+ activityId: activity.id?.href,
84
+ activity: json,
85
+ recipient
86
+ });
87
+ span.setStatus({
88
+ code: SpanStatusCode.UNSET,
89
+ message: `Activity ${activity.id?.href} has already been processed.`
90
+ });
91
+ return "alreadyProcessed";
92
+ }
93
+ }
94
+ if (activity.actorId == null) {
95
+ logger.error("Missing actor.", { activity: json });
96
+ span.setStatus({
97
+ code: SpanStatusCode.ERROR,
98
+ message: "Missing actor."
99
+ });
100
+ return "missingActor";
101
+ }
102
+ span.setAttribute("activitypub.actor.id", activity.actorId.href);
103
+ if (queue != null) {
104
+ const carrier = {};
105
+ propagation.inject(context.active(), carrier);
106
+ try {
107
+ await queue.enqueue({
108
+ type: "inbox",
109
+ id: crypto.randomUUID(),
110
+ baseUrl: ctx.origin,
111
+ activity: json,
112
+ identifier: recipient,
113
+ attempt: 0,
114
+ started: (/* @__PURE__ */ new Date()).toISOString(),
115
+ traceContext: carrier
116
+ });
117
+ } catch (error) {
118
+ logger.error("Failed to enqueue the incoming activity {activityId}:\n{error}", {
119
+ error,
120
+ activityId: activity.id?.href,
121
+ activity: json,
122
+ recipient
123
+ });
124
+ span.setStatus({
125
+ code: SpanStatusCode.ERROR,
126
+ message: `Failed to enqueue the incoming activity ${activity.id?.href}.`
127
+ });
128
+ throw error;
129
+ }
130
+ logger.info("Activity {activityId} is enqueued.", {
131
+ activityId: activity.id?.href,
132
+ activity: json,
133
+ recipient
134
+ });
135
+ return "enqueued";
136
+ }
137
+ tracerProvider = tracerProvider ?? trace.getTracerProvider();
138
+ return await tracerProvider.getTracer(name, version).startActiveSpan("activitypub.dispatch_inbox_listener", { kind: SpanKind.INTERNAL }, async (span) => {
139
+ const dispatched = inboxListeners?.dispatchWithClass(activity);
140
+ if (dispatched == null) {
141
+ logger.error("Unsupported activity type:\n{activity}", {
142
+ activity: json,
143
+ recipient
144
+ });
145
+ span.setStatus({
146
+ code: SpanStatusCode.UNSET,
147
+ message: `Unsupported activity type: ${getTypeId(activity).href}`
148
+ });
149
+ span.end();
150
+ return "unsupportedActivity";
151
+ }
152
+ const { class: cls, listener } = dispatched;
153
+ span.updateName(`activitypub.dispatch_inbox_listener ${cls.name}`);
154
+ try {
155
+ await listener(inboxContextFactory(recipient, json, activity?.id?.href, getTypeId(activity).href), activity);
156
+ } catch (error) {
157
+ try {
158
+ await inboxErrorHandler?.(ctx, error);
159
+ } catch (error) {
160
+ logger.error("An unexpected error occurred in inbox error handler:\n{error}", {
161
+ error,
162
+ activityId: activity.id?.href,
163
+ activity: json,
164
+ recipient
165
+ });
166
+ }
167
+ logger.error("Failed to process the incoming activity {activityId}:\n{error}", {
168
+ error,
169
+ activityId: activity.id?.href,
170
+ activity: json,
171
+ recipient
172
+ });
173
+ span.setStatus({
174
+ code: SpanStatusCode.ERROR,
175
+ message: String(error)
176
+ });
177
+ span.end();
178
+ return "error";
179
+ }
180
+ if (cacheKey != null) await kv.set(cacheKey, true, { ttl: Temporal.Duration.from({ days: 1 }) });
181
+ logger.info("Activity {activityId} has been processed.", {
182
+ activityId: activity.id?.href,
183
+ activity: json,
184
+ recipient
185
+ });
186
+ span.end();
187
+ return "success";
188
+ });
189
+ }
54
190
  //#endregion
55
191
  //#region src/federation/router.ts
56
192
  function cloneInnerRouter(router) {
@@ -160,17 +296,6 @@ var RouterError = class extends Error {
160
296
  };
161
297
  //#endregion
162
298
  //#region src/federation/builder.ts
163
- function validateSingleIdentifierVariablePath(path, errorMessage) {
164
- const operatorMatches = globalThis.Array.from(path.matchAll(/{([+#./;?&]?)([A-Za-z_][A-Za-z0-9_]*)}/g));
165
- if (operatorMatches.length !== 1 || operatorMatches[0]?.[2] !== "identifier") throw new RouterError(errorMessage);
166
- if (operatorMatches.some((match) => [
167
- "?",
168
- "&",
169
- "#"
170
- ].includes(match[1]) && match[2] === "identifier")) throw new RouterError(errorMessage);
171
- const variables = new Router$1().add(path, "outbox");
172
- if (variables.size !== 1 || !variables.has("identifier")) throw new RouterError(errorMessage);
173
- }
174
299
  var FederationBuilderImpl = class {
175
300
  router;
176
301
  actorCallbacks;
@@ -179,7 +304,6 @@ var FederationBuilderImpl = class {
179
304
  objectCallbacks;
180
305
  objectTypeIds;
181
306
  inboxPath;
182
- outboxPath;
183
307
  inboxCallbacks;
184
308
  outboxCallbacks;
185
309
  followingCallbacks;
@@ -188,10 +312,7 @@ var FederationBuilderImpl = class {
188
312
  featuredCallbacks;
189
313
  featuredTagsCallbacks;
190
314
  inboxListeners;
191
- outboxListeners;
192
315
  inboxErrorHandler;
193
- outboxListenerErrorHandler;
194
- outboxAuthorizePredicate;
195
316
  sharedInboxKeyDispatcher;
196
317
  unverifiedActivityHandler;
197
318
  outboxPermanentFailureHandler;
@@ -222,7 +343,6 @@ var FederationBuilderImpl = class {
222
343
  f.objectCallbacks = { ...this.objectCallbacks };
223
344
  f.objectTypeIds = { ...this.objectTypeIds };
224
345
  f.inboxPath = this.inboxPath;
225
- f.outboxPath = this.outboxPath;
226
346
  f.inboxCallbacks = this.inboxCallbacks == null ? void 0 : { ...this.inboxCallbacks };
227
347
  f.outboxCallbacks = this.outboxCallbacks == null ? void 0 : { ...this.outboxCallbacks };
228
348
  f.followingCallbacks = this.followingCallbacks == null ? void 0 : { ...this.followingCallbacks };
@@ -231,10 +351,7 @@ var FederationBuilderImpl = class {
231
351
  f.featuredCallbacks = this.featuredCallbacks == null ? void 0 : { ...this.featuredCallbacks };
232
352
  f.featuredTagsCallbacks = this.featuredTagsCallbacks == null ? void 0 : { ...this.featuredTagsCallbacks };
233
353
  f.inboxListeners = this.inboxListeners?.clone();
234
- f.outboxListeners = this.outboxListeners?.clone();
235
354
  f.inboxErrorHandler = this.inboxErrorHandler;
236
- f.outboxListenerErrorHandler = this.outboxListenerErrorHandler;
237
- f.outboxAuthorizePredicate = this.outboxAuthorizePredicate;
238
355
  f.sharedInboxKeyDispatcher = this.sharedInboxKeyDispatcher;
239
356
  f.unverifiedActivityHandler = this.unverifiedActivityHandler;
240
357
  f.outboxPermanentFailureHandler = this.outboxPermanentFailureHandler;
@@ -434,14 +551,9 @@ var FederationBuilderImpl = class {
434
551
  return setters;
435
552
  }
436
553
  setOutboxDispatcher(path, dispatcher) {
437
- if (this.outboxCallbacks != null) throw new RouterError("Outbox dispatcher already set.");
438
- if (this.router.has("outbox")) {
439
- if (this.outboxPath !== path) throw new RouterError("Outbox dispatcher path must match outbox listener path.");
440
- } else {
441
- validateSingleIdentifierVariablePath(path, "Path for outbox dispatcher must have one variable: {identifier}");
442
- this.router.add(path, "outbox");
443
- this.outboxPath = path;
444
- }
554
+ if (this.router.has("outbox")) throw new RouterError("Outbox dispatcher already set.");
555
+ const variables = this.router.add(path, "outbox");
556
+ if (variables.size !== 1 || !variables.has("identifier")) throw new RouterError("Path for outbox dispatcher must have one variable: {identifier}");
445
557
  const callbacks = { dispatcher };
446
558
  this.outboxCallbacks = callbacks;
447
559
  const setters = {
@@ -464,32 +576,6 @@ var FederationBuilderImpl = class {
464
576
  };
465
577
  return setters;
466
578
  }
467
- setOutboxListeners(outboxPath) {
468
- if (this.outboxListeners != null) throw new RouterError("Outbox listeners already set.");
469
- if (this.router.has("outbox")) {
470
- if (this.outboxPath !== outboxPath) throw new RouterError("Outbox listener path must match outbox dispatcher path.");
471
- } else {
472
- validateSingleIdentifierVariablePath(outboxPath, "Path for outbox must have one variable: {identifier}");
473
- this.router.add(outboxPath, "outbox");
474
- this.outboxPath = outboxPath;
475
- }
476
- const listeners = this.outboxListeners = new ActivityListenerSet();
477
- const setters = {
478
- on(type, listener) {
479
- listeners.add(type, listener);
480
- return setters;
481
- },
482
- onError: (handler) => {
483
- this.outboxListenerErrorHandler = handler;
484
- return setters;
485
- },
486
- authorize: (predicate) => {
487
- this.outboxAuthorizePredicate = predicate;
488
- return setters;
489
- }
490
- };
491
- return setters;
492
- }
493
579
  setFollowingDispatcher(path, dispatcher) {
494
580
  if (this.router.has("following")) throw new RouterError("Following collection dispatcher already set.");
495
581
  const variables = this.router.add(path, "following");
@@ -632,7 +718,7 @@ var FederationBuilderImpl = class {
632
718
  if (sharedInboxPath != null) {
633
719
  if (this.router.add(sharedInboxPath, "sharedInbox").size !== 0) throw new RouterError("Path for shared inbox must have no variables.");
634
720
  }
635
- const listeners = this.inboxListeners = new ActivityListenerSet();
721
+ const listeners = this.inboxListeners = new InboxListenerSet();
636
722
  const setters = {
637
723
  on(type, listener) {
638
724
  listeners.add(type, listener);
@@ -769,144 +855,6 @@ async function buildCollectionSynchronizationHeader(collectionId, actorIds) {
769
855
  return `collectionId="${collectionId}", url="${url}", digest="${encodeHex(await digest(actorIds))}"`;
770
856
  }
771
857
  //#endregion
772
- //#region src/federation/inbox.ts
773
- async function routeActivity({ context: ctx, json, activity, recipient, inboxListeners, inboxContextFactory, inboxErrorHandler, kv, kvPrefixes, queue, span, tracerProvider, idempotencyStrategy }) {
774
- const logger = getLogger([
775
- "fedify",
776
- "federation",
777
- "inbox"
778
- ]);
779
- let cacheKey = null;
780
- if (activity.id != null) {
781
- const inboxContext = inboxContextFactory(recipient, json, activity.id?.href, getTypeId(activity).href);
782
- const strategy = idempotencyStrategy ?? "per-inbox";
783
- let keyString;
784
- if (typeof strategy === "function") keyString = await strategy(inboxContext, activity);
785
- else switch (strategy) {
786
- case "global":
787
- keyString = activity.id.href;
788
- break;
789
- case "per-origin":
790
- keyString = `${ctx.origin}\n${activity.id.href}`;
791
- break;
792
- case "per-inbox":
793
- keyString = `${ctx.origin}\n${activity.id.href}\n${recipient == null ? "sharedInbox" : `inbox\n${recipient}`}`;
794
- break;
795
- default: keyString = `${ctx.origin}\n${activity.id.href}`;
796
- }
797
- if (keyString != null) cacheKey = [...kvPrefixes.activityIdempotence, keyString];
798
- }
799
- if (cacheKey != null) {
800
- if (await kv.get(cacheKey) === true) {
801
- logger.debug("Activity {activityId} has already been processed.", {
802
- activityId: activity.id?.href,
803
- activity: json,
804
- recipient
805
- });
806
- span.setStatus({
807
- code: SpanStatusCode.UNSET,
808
- message: `Activity ${activity.id?.href} has already been processed.`
809
- });
810
- return "alreadyProcessed";
811
- }
812
- }
813
- if (activity.actorId == null) {
814
- logger.error("Missing actor.", { activity: json });
815
- span.setStatus({
816
- code: SpanStatusCode.ERROR,
817
- message: "Missing actor."
818
- });
819
- return "missingActor";
820
- }
821
- span.setAttribute("activitypub.actor.id", activity.actorId.href);
822
- if (queue != null) {
823
- const carrier = {};
824
- propagation.inject(context.active(), carrier);
825
- try {
826
- await queue.enqueue({
827
- type: "inbox",
828
- id: crypto.randomUUID(),
829
- baseUrl: ctx.origin,
830
- activity: json,
831
- identifier: recipient,
832
- attempt: 0,
833
- started: (/* @__PURE__ */ new Date()).toISOString(),
834
- traceContext: carrier
835
- });
836
- } catch (error) {
837
- logger.error("Failed to enqueue the incoming activity {activityId}:\n{error}", {
838
- error,
839
- activityId: activity.id?.href,
840
- activity: json,
841
- recipient
842
- });
843
- span.setStatus({
844
- code: SpanStatusCode.ERROR,
845
- message: `Failed to enqueue the incoming activity ${activity.id?.href}.`
846
- });
847
- throw error;
848
- }
849
- logger.info("Activity {activityId} is enqueued.", {
850
- activityId: activity.id?.href,
851
- activity: json,
852
- recipient
853
- });
854
- return "enqueued";
855
- }
856
- tracerProvider = tracerProvider ?? trace.getTracerProvider();
857
- return await tracerProvider.getTracer(name, version).startActiveSpan("activitypub.dispatch_inbox_listener", { kind: SpanKind.INTERNAL }, async (span) => {
858
- const dispatched = inboxListeners?.dispatchWithClass(activity);
859
- if (dispatched == null) {
860
- logger.error("Unsupported activity type:\n{activity}", {
861
- activity: json,
862
- recipient
863
- });
864
- span.setStatus({
865
- code: SpanStatusCode.UNSET,
866
- message: `Unsupported activity type: ${getTypeId(activity).href}`
867
- });
868
- span.end();
869
- return "unsupportedActivity";
870
- }
871
- const { class: cls, listener } = dispatched;
872
- span.updateName(`activitypub.dispatch_inbox_listener ${cls.name}`);
873
- try {
874
- await listener(inboxContextFactory(recipient, json, activity?.id?.href, getTypeId(activity).href), activity);
875
- } catch (error) {
876
- try {
877
- await inboxErrorHandler?.(ctx, error);
878
- } catch (error) {
879
- logger.error("An unexpected error occurred in inbox error handler:\n{error}", {
880
- error,
881
- activityId: activity.id?.href,
882
- activity: json,
883
- recipient
884
- });
885
- }
886
- logger.error("Failed to process the incoming activity {activityId}:\n{error}", {
887
- error,
888
- activityId: activity.id?.href,
889
- activity: json,
890
- recipient
891
- });
892
- span.setStatus({
893
- code: SpanStatusCode.ERROR,
894
- message: String(error)
895
- });
896
- span.end();
897
- return "error";
898
- }
899
- if (cacheKey != null) await kv.set(cacheKey, true, { ttl: Temporal.Duration.from({ days: 1 }) });
900
- logger.info("Activity {activityId} has been processed.", {
901
- activityId: activity.id?.href,
902
- activity: json,
903
- recipient
904
- });
905
- span.end();
906
- return "success";
907
- });
908
- }
909
- //#endregion
910
858
  //#region src/federation/keycache.ts
911
859
  var KvKeyCache = class {
912
860
  kv;
@@ -1134,8 +1082,8 @@ async function handleObject(request, { values, context, objectDispatcher, author
1134
1082
  * @param parameters The parameters for handling the collection.
1135
1083
  * @returns A promise that resolves to an HTTP response.
1136
1084
  */
1137
- async function handleCollection(request, { name: name$1, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, tracerProvider, onUnauthorized, onNotFound }) {
1138
- const spanName = name$1.trim().replace(/\s+/g, "_");
1085
+ async function handleCollection(request, { name: name$2, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, tracerProvider, onUnauthorized, onNotFound }) {
1086
+ const spanName = name$2.trim().replace(/\s+/g, "_");
1139
1087
  tracerProvider = tracerProvider ?? trace.getTracerProvider();
1140
1088
  const tracer = tracerProvider.getTracer(name, version);
1141
1089
  const cursor = new URL(request.url).searchParams.get("cursor");
@@ -1177,7 +1125,7 @@ async function handleCollection(request, { name: name$1, identifier, uriGetter,
1177
1125
  collection = new OrderedCollection({
1178
1126
  id: baseUri,
1179
1127
  totalItems: totalItems == null ? null : Number(totalItems),
1180
- items: filterCollectionItems(itemsOrResponse, name$1, filterPredicate)
1128
+ items: filterCollectionItems(itemsOrResponse, name$2, filterPredicate)
1181
1129
  });
1182
1130
  } else {
1183
1131
  const lastCursor = await collectionCallbacks.lastCursor?.(context, identifier);
@@ -1198,7 +1146,7 @@ async function handleCollection(request, { name: name$1, identifier, uriGetter,
1198
1146
  } else {
1199
1147
  const uri = new URL(baseUri);
1200
1148
  uri.searchParams.set("cursor", cursor);
1201
- const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$1}`, {
1149
+ const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$2}`, {
1202
1150
  kind: SpanKind.SERVER,
1203
1151
  attributes: {
1204
1152
  "activitypub.collection.id": uri.href,
@@ -1234,253 +1182,57 @@ async function handleCollection(request, { name: name$1, identifier, uriGetter,
1234
1182
  let next = null;
1235
1183
  if (nextCursor != null) {
1236
1184
  next = new URL(context.url);
1237
- next.searchParams.set("cursor", nextCursor);
1238
- }
1239
- const partOf = new URL(context.url);
1240
- partOf.searchParams.delete("cursor");
1241
- collection = new OrderedCollectionPage({
1242
- id: uri,
1243
- prev,
1244
- next,
1245
- items: filterCollectionItems(items, name$1, filterPredicate),
1246
- partOf
1247
- });
1248
- }
1249
- if (collectionCallbacks.authorizePredicate != null) {
1250
- if (!await collectionCallbacks.authorizePredicate(context, identifier)) return await onUnauthorized(request);
1251
- }
1252
- const jsonLd = await collection.toJsonLd(context);
1253
- return new Response(JSON.stringify(jsonLd), { headers: {
1254
- "Content-Type": "application/activity+json",
1255
- Vary: "Accept"
1256
- } });
1257
- }
1258
- /**
1259
- * Filters collection items based on the provided predicate.
1260
- * @template TItem The type of items to filter.
1261
- * @param items The items to filter.
1262
- * @param collectionName The name of the collection for logging purposes.
1263
- * @param filterPredicate Optional predicate function to filter items.
1264
- * @returns The filtered items as Objects, Links, or URLs.
1265
- */
1266
- function filterCollectionItems(items, collectionName, filterPredicate) {
1267
- const result = [];
1268
- let logged = false;
1269
- for (const item of items) {
1270
- let mappedItem;
1271
- if (item instanceof Object$1 || item instanceof Link || item instanceof URL) mappedItem = item;
1272
- else if (item.id == null) continue;
1273
- else mappedItem = item.id;
1274
- if (filterPredicate != null && !filterPredicate(item)) {
1275
- if (!logged) {
1276
- getLogger([
1277
- "fedify",
1278
- "federation",
1279
- "collection"
1280
- ]).warn(`The ${collectionName} collection apparently does not implement filtering. This may result in a large response payload. Please consider implementing filtering for the collection. See also: https://fedify.dev/manual/collections#filtering-by-server`);
1281
- logged = true;
1282
- }
1283
- continue;
1284
- }
1285
- result.push(mappedItem);
1286
- }
1287
- return result;
1288
- }
1289
- function summarizeJsonActivity(json) {
1290
- if (json == null || typeof json !== "object") return {};
1291
- const activity = json;
1292
- return {
1293
- activityId: typeof activity.id === "string" ? activity.id : void 0,
1294
- activityType: typeof activity.type === "string" ? activity.type : void 0
1295
- };
1296
- }
1297
- /**
1298
- * Handles an outbox POST request.
1299
- * @template TContextData The context data to pass to the context.
1300
- * @param request The HTTP request.
1301
- * @param parameters The parameters for handling the request.
1302
- * @returns A promise that resolves to an HTTP response.
1303
- * @since 2.2.0
1304
- */
1305
- async function handleOutbox(request, { identifier, context: ctx, outboxContextFactory, actorDispatcher, authorizePredicate, outboxListeners, outboxErrorHandler, onUnauthorized, onNotFound }) {
1306
- const logger = getLogger([
1307
- "fedify",
1308
- "federation",
1309
- "outbox"
1310
- ]);
1311
- if (request.bodyUsed) {
1312
- logger.error("Request body has already been read.", { identifier });
1313
- return new Response("Internal server error.", {
1314
- status: 500,
1315
- headers: { "Content-Type": "text/plain; charset=utf-8" }
1316
- });
1317
- } else if (request.body?.locked) {
1318
- logger.error("Request body is locked.", { identifier });
1319
- return new Response("Internal server error.", {
1320
- status: 500,
1321
- headers: { "Content-Type": "text/plain; charset=utf-8" }
1322
- });
1323
- }
1324
- if (actorDispatcher == null) {
1325
- logger.error("Actor dispatcher is not set.", { identifier });
1326
- return await onNotFound(request);
1327
- }
1328
- if (authorizePredicate != null) {
1329
- const authorizeContext = ctx.clone(ctx.data);
1330
- authorizeContext.request = request.clone();
1331
- const requestForUnauthorized = authorizeContext.request.clone();
1332
- if (!await authorizePredicate(authorizeContext, identifier)) return await onUnauthorized(requestForUnauthorized);
1333
- }
1334
- const actor = await actorDispatcher(ctx, identifier);
1335
- if (actor == null || actor instanceof Tombstone) {
1336
- logger.error("Actor {identifier} not found.", { identifier });
1337
- return await onNotFound(request);
1338
- }
1339
- const requestForParsing = request.clone();
1340
- let json;
1341
- try {
1342
- json = await requestForParsing.json();
1343
- } catch (error) {
1344
- logger.error("Failed to parse JSON:\n{error}", {
1345
- identifier,
1346
- error
1347
- });
1348
- const outboxContext = outboxContextFactory(identifier, null, void 0, "");
1349
- try {
1350
- await outboxErrorHandler?.(outboxContext, error);
1351
- } catch (error) {
1352
- logger.error("An unexpected error occurred in outbox error handler:\n{error}", {
1353
- error,
1354
- identifier
1355
- });
1356
- }
1357
- return new Response("Invalid JSON.", {
1358
- status: 400,
1359
- headers: { "Content-Type": "text/plain; charset=utf-8" }
1360
- });
1361
- }
1362
- let activity;
1363
- try {
1364
- activity = await Activity.fromJsonLd(json, ctx);
1365
- } catch (error) {
1366
- const summary = summarizeJsonActivity(json);
1367
- logger.error("Failed to parse activity:\n{error}", {
1368
- identifier,
1369
- ...summary,
1370
- error
1371
- });
1372
- const outboxContext = outboxContextFactory(identifier, json, summary.activityId, summary.activityType ?? "");
1373
- try {
1374
- await outboxErrorHandler?.(outboxContext, error);
1375
- } catch (error) {
1376
- logger.error("An unexpected error occurred in outbox error handler:\n{error}", {
1377
- error,
1378
- identifier,
1379
- ...summary
1380
- });
1381
- }
1382
- return new Response("Invalid activity.", {
1383
- status: 400,
1384
- headers: { "Content-Type": "text/plain; charset=utf-8" }
1385
- });
1386
- }
1387
- const outboxContext = outboxContextFactory(identifier, json, activity.id?.href, getTypeId(activity).href);
1388
- const expectedActorId = actor.id ?? ctx.getActorUri(identifier);
1389
- if (activity.actorIds.length < 1) {
1390
- const error = /* @__PURE__ */ new Error("The posted activity has no actor.");
1391
- logger.error("The posted activity has no actor for outbox {identifier}.", {
1392
- identifier,
1393
- activityId: activity.id?.href,
1394
- expectedActorId: expectedActorId.href
1395
- });
1396
- try {
1397
- await outboxErrorHandler?.(outboxContext, error);
1398
- } catch (error) {
1399
- logger.error("An unexpected error occurred in outbox error handler:\n{error}", {
1400
- error,
1401
- activityId: activity.id?.href,
1402
- activityType: getTypeId(activity).href,
1403
- identifier
1404
- });
1405
- }
1406
- return new Response(error.message, {
1407
- status: 400,
1408
- headers: { "Content-Type": "text/plain; charset=utf-8" }
1409
- });
1410
- }
1411
- if (!activity.actorIds.every((actorId) => actorId.href === expectedActorId.href)) {
1412
- const error = /* @__PURE__ */ new Error("The activity actor does not match the outbox owner.");
1413
- logger.error("The posted activity actor does not match outbox owner {identifier}.", {
1414
- identifier,
1415
- activityId: activity.id?.href,
1416
- expectedActorId: expectedActorId.href,
1417
- actorIds: activity.actorIds.map((actorId) => actorId.href)
1418
- });
1419
- try {
1420
- await outboxErrorHandler?.(outboxContext, error);
1421
- } catch (error) {
1422
- logger.error("An unexpected error occurred in outbox error handler:\n{error}", {
1423
- error,
1424
- activityId: activity.id?.href,
1425
- activityType: getTypeId(activity).href,
1426
- identifier
1427
- });
1428
- }
1429
- return new Response(error.message, {
1430
- status: 400,
1431
- headers: { "Content-Type": "text/plain; charset=utf-8" }
1432
- });
1433
- }
1434
- const dispatched = outboxListeners?.dispatchWithClass(activity);
1435
- if (dispatched == null) {
1436
- logger.debug("Unsupported activity type {activityType}.", {
1437
- identifier,
1438
- activityId: activity.id?.href,
1439
- activityType: getTypeId(activity).href
1440
- });
1441
- return new Response("", {
1442
- status: 202,
1443
- headers: { "Content-Type": "text/plain; charset=utf-8" }
1444
- });
1445
- }
1446
- try {
1447
- await dispatched.listener(outboxContext, activity);
1448
- } catch (error) {
1449
- try {
1450
- await outboxErrorHandler?.(outboxContext, error);
1451
- } catch (error) {
1452
- logger.error("An unexpected error occurred in outbox error handler:\n{error}", {
1453
- error,
1454
- activityId: activity.id?.href,
1455
- activityType: getTypeId(activity).href,
1456
- identifier
1457
- });
1458
- }
1459
- logger.error("Failed to process the incoming activity {activityId}:\n{error}", {
1460
- error,
1461
- activityId: activity.id?.href,
1462
- activityType: getTypeId(activity).href,
1463
- identifier
1464
- });
1465
- return new Response("Internal server error.", {
1466
- status: 500,
1467
- headers: { "Content-Type": "text/plain; charset=utf-8" }
1185
+ next.searchParams.set("cursor", nextCursor);
1186
+ }
1187
+ const partOf = new URL(context.url);
1188
+ partOf.searchParams.delete("cursor");
1189
+ collection = new OrderedCollectionPage({
1190
+ id: uri,
1191
+ prev,
1192
+ next,
1193
+ items: filterCollectionItems(items, name$2, filterPredicate),
1194
+ partOf
1468
1195
  });
1469
1196
  }
1470
- if (!outboxContext.hasDeliveredActivity()) logger.warn("Outbox listener for {identifier} returned without delivering the posted activity; ctx.sendActivity() or ctx.forwardActivity() may have been skipped or resulted in no delivery.", {
1471
- identifier,
1472
- activityId: activity.id?.href,
1473
- activityType: getTypeId(activity).href
1474
- });
1475
- logger.info("Activity {activityId} has been processed in outbox listener.", {
1476
- activityId: activity.id?.href,
1477
- activityType: getTypeId(activity).href,
1478
- identifier
1479
- });
1480
- return new Response("", {
1481
- status: 202,
1482
- headers: { "Content-Type": "text/plain; charset=utf-8" }
1483
- });
1197
+ if (collectionCallbacks.authorizePredicate != null) {
1198
+ if (!await collectionCallbacks.authorizePredicate(context, identifier)) return await onUnauthorized(request);
1199
+ }
1200
+ const jsonLd = await collection.toJsonLd(context);
1201
+ return new Response(JSON.stringify(jsonLd), { headers: {
1202
+ "Content-Type": "application/activity+json",
1203
+ Vary: "Accept"
1204
+ } });
1205
+ }
1206
+ /**
1207
+ * Filters collection items based on the provided predicate.
1208
+ * @template TItem The type of items to filter.
1209
+ * @param items The items to filter.
1210
+ * @param collectionName The name of the collection for logging purposes.
1211
+ * @param filterPredicate Optional predicate function to filter items.
1212
+ * @returns The filtered items as Objects, Links, or URLs.
1213
+ */
1214
+ function filterCollectionItems(items, collectionName, filterPredicate) {
1215
+ const result = [];
1216
+ let logged = false;
1217
+ for (const item of items) {
1218
+ let mappedItem;
1219
+ if (item instanceof Object$1 || item instanceof Link || item instanceof URL) mappedItem = item;
1220
+ else if (item.id == null) continue;
1221
+ else mappedItem = item.id;
1222
+ if (filterPredicate != null && !filterPredicate(item)) {
1223
+ if (!logged) {
1224
+ getLogger([
1225
+ "fedify",
1226
+ "federation",
1227
+ "collection"
1228
+ ]).warn(`The ${collectionName} collection apparently does not implement filtering. This may result in a large response payload. Please consider implementing filtering for the collection. See also: https://fedify.dev/manual/collections#filtering-by-server`);
1229
+ logged = true;
1230
+ }
1231
+ continue;
1232
+ }
1233
+ result.push(mappedItem);
1234
+ }
1235
+ return result;
1484
1236
  }
1485
1237
  /**
1486
1238
  * Handles an inbox request for ActivityPub activities.
@@ -1912,8 +1664,8 @@ var CustomCollectionHandler = class {
1912
1664
  * @param CollectionPage The CollectionPage constructor.
1913
1665
  * @param filterPredicate Optional filter predicate for items.
1914
1666
  */
1915
- constructor(name$2, values, context, callbacks, tracerProvider = trace.getTracerProvider(), Collection, CollectionPage, filterPredicate) {
1916
- this.name = name$2;
1667
+ constructor(name$1, values, context, callbacks, tracerProvider = trace.getTracerProvider(), Collection, CollectionPage, filterPredicate) {
1668
+ this.name = name$1;
1917
1669
  this.values = values;
1918
1670
  this.context = context;
1919
1671
  this.callbacks = callbacks;
@@ -2715,7 +2467,6 @@ var middleware_exports = /* @__PURE__ */ __exportAll({
2715
2467
  FederationImpl: () => FederationImpl,
2716
2468
  InboxContextImpl: () => InboxContextImpl,
2717
2469
  KvSpecDeterminer: () => KvSpecDeterminer,
2718
- OutboxContextImpl: () => OutboxContextImpl,
2719
2470
  createFederation: () => createFederation
2720
2471
  });
2721
2472
  /**
@@ -3530,37 +3281,16 @@ var FederationImpl = class extends FederationBuilderImpl {
3530
3281
  onNotFound
3531
3282
  });
3532
3283
  }
3533
- case "outbox":
3534
- if (request.method === "POST") {
3535
- if (this.outboxListeners == null) return new Response("Method not allowed.", {
3536
- status: 405,
3537
- headers: {
3538
- Allow: "GET, HEAD",
3539
- "Content-Type": "text/plain; charset=utf-8"
3540
- }
3541
- });
3542
- return await handleOutbox(request, {
3543
- identifier: route.values.identifier,
3544
- context,
3545
- outboxContextFactory: context.toOutboxContext.bind(context),
3546
- actorDispatcher: this.actorCallbacks?.dispatcher,
3547
- authorizePredicate: this.outboxAuthorizePredicate ?? this.outboxCallbacks?.authorizePredicate,
3548
- outboxListeners: this.outboxListeners,
3549
- outboxErrorHandler: this.outboxListenerErrorHandler,
3550
- onUnauthorized,
3551
- onNotFound
3552
- });
3553
- }
3554
- return await handleCollection(request, {
3555
- name: "outbox",
3556
- identifier: route.values.identifier,
3557
- uriGetter: context.getOutboxUri.bind(context),
3558
- context,
3559
- collectionCallbacks: this.outboxCallbacks,
3560
- tracerProvider: this.tracerProvider,
3561
- onUnauthorized,
3562
- onNotFound
3563
- });
3284
+ case "outbox": return await handleCollection(request, {
3285
+ name: "outbox",
3286
+ identifier: route.values.identifier,
3287
+ uriGetter: context.getOutboxUri.bind(context),
3288
+ context,
3289
+ collectionCallbacks: this.outboxCallbacks,
3290
+ tracerProvider: this.tracerProvider,
3291
+ onUnauthorized,
3292
+ onNotFound
3293
+ });
3564
3294
  case "inbox":
3565
3295
  if (request.method !== "POST") return await handleCollection(request, {
3566
3296
  name: "inbox",
@@ -3730,16 +3460,6 @@ var ContextImpl = class ContextImpl {
3730
3460
  invokedFromActorKeyPairsDispatcher: this.invokedFromActorKeyPairsDispatcher
3731
3461
  });
3732
3462
  }
3733
- toOutboxContext(identifier, activity, activityId, activityType) {
3734
- return new OutboxContextImpl(identifier, activity, activityId, activityType, {
3735
- url: this.url,
3736
- federation: this.federation,
3737
- data: this.data,
3738
- documentLoader: this.documentLoader,
3739
- contextLoader: this.contextLoader,
3740
- invokedFromActorKeyPairsDispatcher: this.invokedFromActorKeyPairsDispatcher
3741
- });
3742
- }
3743
3463
  get hostname() {
3744
3464
  return this.url.hostname;
3745
3465
  }
@@ -4029,9 +3749,9 @@ var ContextImpl = class ContextImpl {
4029
3749
  attributes: {
4030
3750
  "activitypub.activity.type": getTypeId(activity).href,
4031
3751
  "activitypub.activity.to": activity.toIds.map((to) => to.href),
4032
- "activitypub.activity.cc": activity.ccIds.map((cc) => cc.href),
3752
+ "activitypub.activity.cc": activity.toIds.map((cc) => cc.href),
4033
3753
  "activitypub.activity.bto": activity.btoIds.map((bto) => bto.href),
4034
- "activitypub.activity.bcc": activity.bccIds.map((bcc) => bcc.href)
3754
+ "activitypub.activity.bcc": activity.toIds.map((bcc) => bcc.href)
4035
3755
  }
4036
3756
  }, async (span) => {
4037
3757
  try {
@@ -4126,13 +3846,6 @@ var ContextImpl = class ContextImpl {
4126
3846
  preferSharedInbox: options.preferSharedInbox,
4127
3847
  excludeBaseUris: options.excludeBaseUris
4128
3848
  });
4129
- if (globalThis.Object.keys(inboxes).length < 1) {
4130
- logger.debug("No inboxes found for activity {activityId}.", {
4131
- activityId: activity.id?.href,
4132
- activity
4133
- });
4134
- return false;
4135
- }
4136
3849
  logger.debug("Sending activity {activityId} to inboxes:\n{inboxes}", {
4137
3850
  inboxes: globalThis.Object.keys(inboxes),
4138
3851
  activityId: activity.id?.href,
@@ -4140,7 +3853,7 @@ var ContextImpl = class ContextImpl {
4140
3853
  });
4141
3854
  if (this.federation.fanoutQueue == null || options.immediate || options.fanout === "skip" || (options.fanout ?? "auto") === "auto" && globalThis.Object.keys(inboxes).length < FANOUT_THRESHOLD) {
4142
3855
  await this.federation.sendActivity(keys, inboxes, activity, opts);
4143
- return true;
3856
+ return;
4144
3857
  }
4145
3858
  const keyJwkPairs = await Promise.all(keys.map(async ({ keyId, privateKey }) => ({
4146
3859
  keyId: keyId.href,
@@ -4169,7 +3882,6 @@ var ContextImpl = class ContextImpl {
4169
3882
  };
4170
3883
  if (!this.federation.manuallyStartQueue) this.federation._startQueueInternal(this.data);
4171
3884
  await this.federation.fanoutQueue.enqueue(message, { orderingKey: options.orderingKey });
4172
- return true;
4173
3885
  }
4174
3886
  async *getFollowers(identifier) {
4175
3887
  if (this.federation.followersCallbacks == null) throw new Error("No followers collection dispatcher registered.");
@@ -4404,170 +4116,6 @@ var RequestContextImpl = class RequestContextImpl extends ContextImpl {
4404
4116
  }
4405
4117
  }
4406
4118
  };
4407
- function forwardActivity(ctx, loggerCategory, forwarder, recipients, options) {
4408
- return ctx.tracerProvider.getTracer(name, version).startActiveSpan(ctx.federation.outboxQueue == null || options?.immediate ? `activitypub.${loggerCategory}` : "activitypub.fanout", {
4409
- kind: ctx.federation.outboxQueue == null || options?.immediate ? SpanKind.CLIENT : SpanKind.PRODUCER,
4410
- attributes: { "activitypub.activity.type": ctx.activityType }
4411
- }, async (span) => {
4412
- try {
4413
- if (ctx.activityId != null) span.setAttribute("activitypub.activity.id", ctx.activityId);
4414
- return await forwardActivityInternal(ctx, loggerCategory, forwarder, recipients, options);
4415
- } catch (e) {
4416
- span.setStatus({
4417
- code: SpanStatusCode.ERROR,
4418
- message: String(e)
4419
- });
4420
- throw e;
4421
- } finally {
4422
- span.end();
4423
- }
4424
- });
4425
- }
4426
- async function forwardActivityInternal(ctx, loggerCategory, forwarder, recipients, options) {
4427
- const logger = getLogger([
4428
- "fedify",
4429
- "federation",
4430
- loggerCategory
4431
- ]);
4432
- let keys;
4433
- let identifier = null;
4434
- if ("identifier" in forwarder || "username" in forwarder) {
4435
- if ("identifier" in forwarder) identifier = forwarder.identifier;
4436
- else {
4437
- const username = forwarder.username;
4438
- if (ctx.federation.actorCallbacks?.handleMapper == null) identifier = username;
4439
- else {
4440
- const mapped = await ctx.federation.actorCallbacks.handleMapper(ctx, username);
4441
- if (mapped == null) throw new Error(`No actor found for the given username ${JSON.stringify(username)}.`);
4442
- identifier = mapped;
4443
- }
4444
- }
4445
- const actorKeyPairs = await ctx.getActorKeyPairs(identifier);
4446
- if (actorKeyPairs.length < 1) throw new Error(`No key pair found for actor ${JSON.stringify(identifier)}.`);
4447
- keys = actorKeyPairs.map((kp) => ({
4448
- keyId: kp.keyId,
4449
- privateKey: kp.privateKey
4450
- }));
4451
- } else if (Array.isArray(forwarder)) {
4452
- if (forwarder.length < 1) throw new Error("The forwarder's key pairs are empty.");
4453
- keys = forwarder;
4454
- } else keys = [forwarder];
4455
- if (!hasSignatureLike(ctx.activity)) {
4456
- if (!hasProofLike(ctx.activity)) {
4457
- if (options?.skipIfUnsigned) return false;
4458
- logger.warn("The activity {activityId} is not signed; even if it is forwarded to other servers as is, it may not be accepted by them due to the lack of a signature/proof.", {
4459
- activityId: ctx.activityId,
4460
- activityType: ctx.activityType,
4461
- identifier: identifier ?? void 0
4462
- });
4463
- }
4464
- }
4465
- if (recipients === "followers") {
4466
- if (identifier == null) throw new Error("If recipients is \"followers\", forwarder must be an actor identifier or username.");
4467
- const followers = [];
4468
- for await (const recipient of ctx.getFollowers(identifier)) followers.push(recipient);
4469
- recipients = followers;
4470
- }
4471
- const inboxes = extractInboxes({
4472
- recipients: Array.isArray(recipients) ? recipients : [recipients],
4473
- preferSharedInbox: options?.preferSharedInbox,
4474
- excludeBaseUris: options?.excludeBaseUris
4475
- });
4476
- if (globalThis.Object.keys(inboxes).length < 1) {
4477
- logger.debug("No inboxes found for activity {activityId}.", {
4478
- activityId: ctx.activityId,
4479
- activityType: ctx.activityType,
4480
- identifier: identifier ?? void 0
4481
- });
4482
- return false;
4483
- }
4484
- logger.debug("Forwarding activity {activityId} to inboxes:\n{inboxes}", {
4485
- inboxes: globalThis.Object.keys(inboxes),
4486
- activityId: ctx.activityId,
4487
- activity: ctx.activity
4488
- });
4489
- if (options?.immediate || ctx.federation.outboxQueue == null) {
4490
- if (options?.immediate) logger.debug("Forwarding activity immediately without queue since immediate option is set.");
4491
- else logger.debug("Forwarding activity immediately without queue since queue is not set.");
4492
- const promises = [];
4493
- for (const inbox in inboxes) promises.push(sendActivity({
4494
- keys,
4495
- activity: ctx.activity,
4496
- activityId: ctx.activityId,
4497
- activityType: ctx.activityType,
4498
- inbox: new URL(inbox),
4499
- sharedInbox: inboxes[inbox].sharedInbox,
4500
- tracerProvider: ctx.tracerProvider,
4501
- specDeterminer: new KvSpecDeterminer(ctx.federation.kv, ctx.federation.kvPrefixes.httpMessageSignaturesSpec, ctx.federation.firstKnock)
4502
- }));
4503
- await Promise.all(promises);
4504
- return true;
4505
- }
4506
- logger.debug("Enqueuing activity {activityId} to forward later.", {
4507
- activityId: ctx.activityId,
4508
- activity: ctx.activity
4509
- });
4510
- if (!ctx.federation.manuallyStartQueue) ctx.federation._startQueueInternal(ctx.data);
4511
- const keyJwkPairs = [];
4512
- for (const { keyId, privateKey } of keys) {
4513
- const privateKeyJwk = await exportJwk(privateKey);
4514
- keyJwkPairs.push({
4515
- keyId: keyId.href,
4516
- privateKey: privateKeyJwk
4517
- });
4518
- }
4519
- const carrier = {};
4520
- propagation.inject(context.active(), carrier);
4521
- const orderingKey = options?.orderingKey;
4522
- const started = (/* @__PURE__ */ new Date()).toISOString();
4523
- const messages = [];
4524
- for (const inbox in inboxes) {
4525
- const inboxUrl = new URL(inbox);
4526
- const message = {
4527
- type: "outbox",
4528
- id: crypto.randomUUID(),
4529
- baseUrl: ctx.origin,
4530
- keys: keyJwkPairs,
4531
- activity: ctx.activity,
4532
- activityId: ctx.activityId,
4533
- activityType: ctx.activityType,
4534
- inbox,
4535
- sharedInbox: inboxes[inbox].sharedInbox,
4536
- actorIds: [...inboxes[inbox].actorIds],
4537
- started,
4538
- attempt: 0,
4539
- headers: {},
4540
- orderingKey: orderingKey == null ? void 0 : `${orderingKey}\n${inboxUrl.origin}`,
4541
- traceContext: carrier
4542
- };
4543
- messages.push({
4544
- message,
4545
- orderingKey: message.orderingKey
4546
- });
4547
- }
4548
- const { outboxQueue } = ctx.federation;
4549
- if (outboxQueue.enqueueMany == null || orderingKey != null) {
4550
- const promises = messages.map((m) => outboxQueue.enqueue(m.message, { orderingKey: m.orderingKey }));
4551
- const errors = (await Promise.allSettled(promises)).filter((r) => r.status === "rejected").map((r) => r.reason);
4552
- if (errors.length > 0) {
4553
- logger.error("Failed to enqueue activity {activityId} to forward later:\n{errors}", {
4554
- activityId: ctx.activityId,
4555
- errors
4556
- });
4557
- if (errors.length > 1) throw new AggregateError(errors, `Failed to enqueue activity ${ctx.activityId} to forward later.`);
4558
- throw errors[0];
4559
- }
4560
- } else try {
4561
- await outboxQueue.enqueueMany(messages.map((m) => m.message));
4562
- } catch (error) {
4563
- logger.error("Failed to enqueue activity {activityId} to forward later:\n{error}", {
4564
- activityId: ctx.activityId,
4565
- error
4566
- });
4567
- throw error;
4568
- }
4569
- return true;
4570
- }
4571
4119
  var InboxContextImpl = class InboxContextImpl extends ContextImpl {
4572
4120
  recipient;
4573
4121
  activity;
@@ -4591,40 +4139,13 @@ var InboxContextImpl = class InboxContextImpl extends ContextImpl {
4591
4139
  });
4592
4140
  }
4593
4141
  forwardActivity(forwarder, recipients, options) {
4594
- return forwardActivity(this, "inbox", forwarder, recipients, options).then(() => void 0);
4595
- }
4596
- };
4597
- var OutboxContextImpl = class OutboxContextImpl extends ContextImpl {
4598
- #deliveryState;
4599
- identifier;
4600
- activity;
4601
- activityId;
4602
- activityType;
4603
- constructor(identifier, activity, activityId, activityType, options, deliveryState = { delivered: false }) {
4604
- super(options);
4605
- this.#deliveryState = deliveryState;
4606
- this.identifier = identifier;
4607
- this.activity = activity;
4608
- this.activityId = activityId;
4609
- this.activityType = activityType;
4610
- }
4611
- hasDeliveredActivity() {
4612
- return this.#deliveryState.delivered;
4613
- }
4614
- sendActivity(sender, recipients, activity, options = {}) {
4615
- return this.tracerProvider.getTracer(name, version).startActiveSpan(this.federation.outboxQueue == null || options.immediate ? "activitypub.outbox" : "activitypub.fanout", {
4616
- kind: this.federation.outboxQueue == null || options.immediate ? SpanKind.CLIENT : SpanKind.PRODUCER,
4617
- attributes: {
4618
- "activitypub.activity.type": getTypeId(activity).href,
4619
- "activitypub.activity.to": activity.toIds.map((to) => to.href),
4620
- "activitypub.activity.cc": activity.ccIds.map((cc) => cc.href),
4621
- "activitypub.activity.bto": activity.btoIds.map((bto) => bto.href),
4622
- "activitypub.activity.bcc": activity.bccIds.map((bcc) => bcc.href)
4623
- }
4142
+ return this.tracerProvider.getTracer(name, version).startActiveSpan("activitypub.outbox", {
4143
+ kind: this.federation.outboxQueue == null || options?.immediate ? SpanKind.CLIENT : SpanKind.PRODUCER,
4144
+ attributes: { "activitypub.activity.type": this.activityType }
4624
4145
  }, async (span) => {
4625
4146
  try {
4626
- if (activity.id != null) span.setAttribute("activitypub.activity.id", activity.id.href);
4627
- if (await this.sendActivityInternal(sender, recipients, activity, options, span)) this.#deliveryState.delivered = true;
4147
+ if (this.activityId != null) span.setAttribute("activitypub.activity.id", this.activityId);
4148
+ await this.forwardActivityInternal(forwarder, recipients, options);
4628
4149
  } catch (e) {
4629
4150
  span.setStatus({
4630
4151
  code: SpanStatusCode.ERROR,
@@ -4636,20 +4157,151 @@ var OutboxContextImpl = class OutboxContextImpl extends ContextImpl {
4636
4157
  }
4637
4158
  });
4638
4159
  }
4639
- forwardActivity(forwarder, recipients, options) {
4640
- return forwardActivity(this, "outbox", forwarder, recipients, options).then((delivered) => {
4641
- if (delivered) this.#deliveryState.delivered = true;
4160
+ async forwardActivityInternal(forwarder, recipients, options) {
4161
+ const logger = getLogger([
4162
+ "fedify",
4163
+ "federation",
4164
+ "inbox"
4165
+ ]);
4166
+ let keys;
4167
+ let identifier = null;
4168
+ if ("identifier" in forwarder || "username" in forwarder) {
4169
+ if ("identifier" in forwarder) identifier = forwarder.identifier;
4170
+ else {
4171
+ const username = forwarder.username;
4172
+ if (this.federation.actorCallbacks?.handleMapper == null) identifier = username;
4173
+ else {
4174
+ const mapped = await this.federation.actorCallbacks.handleMapper(this, username);
4175
+ if (mapped == null) throw new Error(`No actor found for the given username ${JSON.stringify(username)}.`);
4176
+ identifier = mapped;
4177
+ }
4178
+ }
4179
+ const actorKeyPairs = await this.getActorKeyPairs(identifier);
4180
+ if (actorKeyPairs.length < 1) throw new Error(`No key pair found for actor ${JSON.stringify(identifier)}.`);
4181
+ keys = actorKeyPairs.map((kp) => ({
4182
+ keyId: kp.keyId,
4183
+ privateKey: kp.privateKey
4184
+ }));
4185
+ } else if (Array.isArray(forwarder)) {
4186
+ if (forwarder.length < 1) throw new Error("The forwarder's key pairs are empty.");
4187
+ keys = forwarder;
4188
+ } else keys = [forwarder];
4189
+ if (!hasSignature(this.activity)) {
4190
+ let hasProof;
4191
+ try {
4192
+ hasProof = await (await Activity.fromJsonLd(this.activity, this)).getProof() != null;
4193
+ } catch {
4194
+ hasProof = false;
4195
+ }
4196
+ if (!hasProof) {
4197
+ if (options?.skipIfUnsigned) return;
4198
+ logger.warn("The received activity {activityId} is not signed; even if it is forwarded to other servers as is, it may not be accepted by them due to the lack of a signature/proof.");
4199
+ }
4200
+ }
4201
+ if (recipients === "followers") {
4202
+ if (identifier == null) throw new Error("If recipients is \"followers\", forwarder must be an actor identifier or username.");
4203
+ const followers = [];
4204
+ for await (const recipient of this.getFollowers(identifier)) followers.push(recipient);
4205
+ recipients = followers;
4206
+ }
4207
+ const inboxes = extractInboxes({
4208
+ recipients: Array.isArray(recipients) ? recipients : [recipients],
4209
+ preferSharedInbox: options?.preferSharedInbox,
4210
+ excludeBaseUris: options?.excludeBaseUris
4642
4211
  });
4643
- }
4644
- clone(data) {
4645
- return new OutboxContextImpl(this.identifier, this.activity, this.activityId, this.activityType, {
4646
- url: this.url,
4647
- federation: this.federation,
4648
- data,
4649
- documentLoader: this.documentLoader,
4650
- contextLoader: this.contextLoader,
4651
- invokedFromActorKeyPairsDispatcher: this.invokedFromActorKeyPairsDispatcher
4652
- }, this.#deliveryState);
4212
+ logger.debug("Forwarding activity {activityId} to inboxes:\n{inboxes}", {
4213
+ inboxes: globalThis.Object.keys(inboxes),
4214
+ activityId: this.activityId,
4215
+ activity: this.activity
4216
+ });
4217
+ if (options?.immediate || this.federation.outboxQueue == null) {
4218
+ if (options?.immediate) logger.debug("Forwarding activity immediately without queue since immediate option is set.");
4219
+ else logger.debug("Forwarding activity immediately without queue since queue is not set.");
4220
+ const promises = [];
4221
+ for (const inbox in inboxes) promises.push(sendActivity({
4222
+ keys,
4223
+ activity: this.activity,
4224
+ activityId: this.activityId,
4225
+ activityType: this.activityType,
4226
+ inbox: new URL(inbox),
4227
+ sharedInbox: inboxes[inbox].sharedInbox,
4228
+ tracerProvider: this.tracerProvider,
4229
+ specDeterminer: new KvSpecDeterminer(this.federation.kv, this.federation.kvPrefixes.httpMessageSignaturesSpec, this.federation.firstKnock)
4230
+ }));
4231
+ await Promise.all(promises);
4232
+ return;
4233
+ }
4234
+ logger.debug("Enqueuing activity {activityId} to forward later.", {
4235
+ activityId: this.activityId,
4236
+ activity: this.activity
4237
+ });
4238
+ const keyJwkPairs = [];
4239
+ for (const { keyId, privateKey } of keys) {
4240
+ const privateKeyJwk = await exportJwk(privateKey);
4241
+ keyJwkPairs.push({
4242
+ keyId: keyId.href,
4243
+ privateKey: privateKeyJwk
4244
+ });
4245
+ }
4246
+ const carrier = {};
4247
+ propagation.inject(context.active(), carrier);
4248
+ const orderingKey = options?.orderingKey;
4249
+ const messages = [];
4250
+ for (const inbox in inboxes) {
4251
+ const inboxUrl = new URL(inbox);
4252
+ const message = {
4253
+ type: "outbox",
4254
+ id: crypto.randomUUID(),
4255
+ baseUrl: this.origin,
4256
+ keys: keyJwkPairs,
4257
+ activity: this.activity,
4258
+ activityId: this.activityId,
4259
+ activityType: this.activityType,
4260
+ inbox,
4261
+ sharedInbox: inboxes[inbox].sharedInbox,
4262
+ started: (/* @__PURE__ */ new Date()).toISOString(),
4263
+ attempt: 0,
4264
+ headers: {},
4265
+ orderingKey: orderingKey == null ? void 0 : `${orderingKey}\n${inboxUrl.origin}`,
4266
+ traceContext: carrier
4267
+ };
4268
+ messages.push({
4269
+ message,
4270
+ orderingKey: message.orderingKey
4271
+ });
4272
+ }
4273
+ const { outboxQueue } = this.federation;
4274
+ if (outboxQueue.enqueueMany == null) {
4275
+ const promises = messages.map((m) => outboxQueue.enqueue(m.message, { orderingKey: m.orderingKey }));
4276
+ const errors = (await Promise.allSettled(promises)).filter((r) => r.status === "rejected").map((r) => r.reason);
4277
+ if (errors.length > 0) {
4278
+ logger.error("Failed to enqueue activity {activityId} to forward later:\n{errors}", {
4279
+ activityId: this.activityId,
4280
+ errors
4281
+ });
4282
+ if (errors.length > 1) throw new AggregateError(errors, `Failed to enqueue activity ${this.activityId} to forward later.`);
4283
+ throw errors[0];
4284
+ }
4285
+ } else if (orderingKey != null) {
4286
+ const promises = messages.map((m) => outboxQueue.enqueue(m.message, { orderingKey: m.orderingKey }));
4287
+ const errors = (await Promise.allSettled(promises)).filter((r) => r.status === "rejected").map((r) => r.reason);
4288
+ if (errors.length > 0) {
4289
+ logger.error("Failed to enqueue activity {activityId} to forward later:\n{errors}", {
4290
+ activityId: this.activityId,
4291
+ errors
4292
+ });
4293
+ if (errors.length > 1) throw new AggregateError(errors, `Failed to enqueue activity ${this.activityId} to forward later.`);
4294
+ throw errors[0];
4295
+ }
4296
+ } else try {
4297
+ await outboxQueue.enqueueMany(messages.map((m) => m.message));
4298
+ } catch (error) {
4299
+ logger.error("Failed to enqueue activity {activityId} to forward later:\n{error}", {
4300
+ activityId: this.activityId,
4301
+ error
4302
+ });
4303
+ throw error;
4304
+ }
4653
4305
  }
4654
4306
  };
4655
4307
  var KvSpecDeterminer = class {