@fedify/fedify 2.2.0-pr.695.16 → 2.2.0-pr.697.18

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 (63) hide show
  1. package/dist/activity-listener-Ck3JZ_hR.mjs +40 -0
  2. package/dist/{builder-7PVCiLiR.mjs → builder-CssIxEgK.mjs} +57 -7
  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-78ecvxf5.d.ts → context-BGrYMSTk.d.ts} +143 -1
  7. package/dist/{context-DYDPdoCb.d.cts → context-CMUd4wy0.d.cts} +143 -1
  8. package/dist/{context-Juj6bdHC.mjs → context-Dk_tacqz.mjs} +17 -2
  9. package/dist/{deno-vxcWcxQS.mjs → deno-DFC3hdDk.mjs} +1 -1
  10. package/dist/{docloader-D7q0-Xef.mjs → docloader-DJSGzW4N.mjs} +2 -2
  11. package/dist/federation/builder.test.mjs +25 -1
  12. package/dist/federation/handler.test.mjs +369 -8
  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 +510 -8
  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-RZPxDWq5.mjs → http-BrF-JQov.mjs} +2 -2
  23. package/dist/{http-JxF7bG0o.cjs → http-DDRsBRY5.cjs} +1 -1
  24. package/dist/{http-D-MhhYUF.js → http-nVAbZPuI.js} +1 -1
  25. package/dist/{key-CGx_dDkX.mjs → key-2MxqHz-F.mjs} +1 -1
  26. package/dist/{kv-cache-D84Mk0fZ.js → kv-cache-5w7DZnmJ.js} +1 -1
  27. package/dist/{kv-cache-C2gdVgvb.cjs → kv-cache-CPeV5q6I.cjs} +1 -1
  28. package/dist/{ld-wup-liFO.mjs → ld-bY6topKr.mjs} +26 -3
  29. package/dist/{middleware-BjVx-_bv.mjs → middleware-BCJUFXYb.mjs} +612 -180
  30. package/dist/{middleware-Bn75dPug.cjs → middleware-BPHO6DE3.cjs} +676 -323
  31. package/dist/{middleware-RF-sUfTr.js → middleware-C10kVjEo.js} +670 -322
  32. package/dist/{middleware-wdfeWjRJ.mjs → middleware-DI82-dr3.mjs} +1 -1
  33. package/dist/{middleware-CXOVT4Ph.cjs → middleware-JAlnEFGy.cjs} +1 -1
  34. package/dist/{mod-CEohtXhV.d.cts → mod-BcJHeuv1.d.cts} +1 -1
  35. package/dist/{mod-CokIUYDr.d.ts → mod-CJXfyw7v.d.ts} +1 -1
  36. package/dist/{mod-DvxszxXC.d.ts → mod-CR8soWa9.d.ts} +18 -1
  37. package/dist/{mod-DoJBjjnO.d.cts → mod-Cr3f-ACa.d.cts} +18 -1
  38. package/dist/mod.cjs +6 -4
  39. package/dist/mod.d.cts +5 -5
  40. package/dist/mod.d.ts +5 -5
  41. package/dist/mod.js +5 -5
  42. package/dist/nodeinfo/handler.test.mjs +2 -2
  43. package/dist/{owner-q2mUMM9a.mjs → owner-D4-A8f_n.mjs} +2 -2
  44. package/dist/{proof--CpZsF_p.mjs → proof-45_MjnD1.mjs} +32 -3
  45. package/dist/{proof-_Zyfqyce.cjs → proof-COdach6j.cjs} +61 -3
  46. package/dist/{proof-CirP9OSd.js → proof-CnfkRjXL.js} +54 -2
  47. package/dist/{send-CVJfx7bF.mjs → send-BeNvLSaC.mjs} +2 -2
  48. package/dist/sig/http.test.mjs +2 -2
  49. package/dist/sig/key.test.mjs +1 -1
  50. package/dist/sig/ld.test.mjs +44 -2
  51. package/dist/sig/mod.cjs +4 -2
  52. package/dist/sig/mod.d.cts +2 -2
  53. package/dist/sig/mod.d.ts +2 -2
  54. package/dist/sig/mod.js +3 -3
  55. package/dist/sig/owner.test.mjs +1 -1
  56. package/dist/sig/proof.test.mjs +46 -2
  57. package/dist/testing/mod.d.mts +149 -1
  58. package/dist/testing/mod.mjs +2 -2
  59. package/dist/utils/docloader.test.mjs +2 -2
  60. package/dist/utils/mod.cjs +1 -1
  61. package/dist/utils/mod.js +1 -1
  62. package/package.json +5 -5
  63. package/dist/inbox-CmYvcSMM.mjs +0 -179
@@ -2,24 +2,23 @@ import { Temporal } from "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { n as RouterError } from "./router-CrMLXoOr.mjs";
5
- import { n as version, t as name } from "./deno-vxcWcxQS.mjs";
5
+ import { n as version, t as name } from "./deno-DFC3hdDk.mjs";
6
6
  import { t as formatAcceptSignature } from "./accept-Dd__NiUL.mjs";
7
- import { a as importJwk, o as validateCryptoKey, t as exportJwk } from "./key-CGx_dDkX.mjs";
8
- import { l as verifyRequest, o as parseRfc9421SignatureInput, u as verifyRequestDetailed } from "./http-RZPxDWq5.mjs";
9
- import { t as getAuthenticatedDocumentLoader } from "./docloader-D7q0-Xef.mjs";
7
+ import { a as importJwk, o as validateCryptoKey, t as exportJwk } from "./key-2MxqHz-F.mjs";
8
+ import { l as verifyRequest, o as parseRfc9421SignatureInput, u as verifyRequestDetailed } from "./http-BrF-JQov.mjs";
9
+ import { t as getAuthenticatedDocumentLoader } from "./docloader-DJSGzW4N.mjs";
10
10
  import { n as kvCache } from "./kv-cache-B01V7s3h.mjs";
11
- import { a as signJsonLd, i as hasSignature, o as verifyJsonLd, r as detachSignature } from "./ld-wup-liFO.mjs";
12
- import { n as getKeyOwner, t as doesActorOwnKey } from "./owner-q2mUMM9a.mjs";
13
- import { n as signObject, r as verifyObject } from "./proof--CpZsF_p.mjs";
11
+ import { a as signJsonLd, i as hasSignatureLike, o as verifyJsonLd, r as detachSignature } from "./ld-bY6topKr.mjs";
12
+ import { n as getKeyOwner, t as doesActorOwnKey } from "./owner-D4-A8f_n.mjs";
13
+ import { i as verifyObject, n as hasProofLike, r as signObject } from "./proof-45_MjnD1.mjs";
14
14
  import { t as getNodeInfo } from "./client-DEpOVgY1.mjs";
15
15
  import { t as nodeInfoToJson } from "./types-DCP0WLdt.mjs";
16
- import { n as routeActivity } from "./inbox-CmYvcSMM.mjs";
17
- import { t as FederationBuilderImpl } from "./builder-7PVCiLiR.mjs";
16
+ import { t as FederationBuilderImpl } from "./builder-CssIxEgK.mjs";
18
17
  import { t as buildCollectionSynchronizationHeader } from "./collection-BD6-SZ6O.mjs";
19
18
  import { t as KvKeyCache } from "./keycache-CCSwkQcY.mjs";
20
19
  import { t as acceptsJsonLd } from "./negotiation-DnsfFF8I.mjs";
21
20
  import { t as createExponentialBackoffPolicy } from "./retry-B_E3V_Dx.mjs";
22
- import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "./send-CVJfx7bF.mjs";
21
+ import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "./send-BeNvLSaC.mjs";
23
22
  import { Activity, Collection, CollectionPage, CryptographicKey, Link, Multikey, Object as Object$1, OrderedCollection, OrderedCollectionPage, Tombstone, getTypeId, lookupObject, traverseCollection } from "@fedify/vocab";
24
23
  import { lookupWebFinger } from "@fedify/webfinger";
25
24
  import { SpanKind, SpanStatusCode, context, propagation, trace } from "@opentelemetry/api";
@@ -153,6 +152,144 @@ function handleNodeInfoJrd(_request, context) {
153
152
  return Promise.resolve(response);
154
153
  }
155
154
  //#endregion
155
+ //#region src/federation/inbox.ts
156
+ async function routeActivity({ context: ctx, json, activity, recipient, inboxListeners, inboxContextFactory, inboxErrorHandler, kv, kvPrefixes, queue, span, tracerProvider, idempotencyStrategy }) {
157
+ const logger = getLogger([
158
+ "fedify",
159
+ "federation",
160
+ "inbox"
161
+ ]);
162
+ let cacheKey = null;
163
+ if (activity.id != null) {
164
+ const inboxContext = inboxContextFactory(recipient, json, activity.id?.href, getTypeId(activity).href);
165
+ const strategy = idempotencyStrategy ?? "per-inbox";
166
+ let keyString;
167
+ if (typeof strategy === "function") keyString = await strategy(inboxContext, activity);
168
+ else switch (strategy) {
169
+ case "global":
170
+ keyString = activity.id.href;
171
+ break;
172
+ case "per-origin":
173
+ keyString = `${ctx.origin}\n${activity.id.href}`;
174
+ break;
175
+ case "per-inbox":
176
+ keyString = `${ctx.origin}\n${activity.id.href}\n${recipient == null ? "sharedInbox" : `inbox\n${recipient}`}`;
177
+ break;
178
+ default: keyString = `${ctx.origin}\n${activity.id.href}`;
179
+ }
180
+ if (keyString != null) cacheKey = [...kvPrefixes.activityIdempotence, keyString];
181
+ }
182
+ if (cacheKey != null) {
183
+ if (await kv.get(cacheKey) === true) {
184
+ logger.debug("Activity {activityId} has already been processed.", {
185
+ activityId: activity.id?.href,
186
+ activity: json,
187
+ recipient
188
+ });
189
+ span.setStatus({
190
+ code: SpanStatusCode.UNSET,
191
+ message: `Activity ${activity.id?.href} has already been processed.`
192
+ });
193
+ return "alreadyProcessed";
194
+ }
195
+ }
196
+ if (activity.actorId == null) {
197
+ logger.error("Missing actor.", { activity: json });
198
+ span.setStatus({
199
+ code: SpanStatusCode.ERROR,
200
+ message: "Missing actor."
201
+ });
202
+ return "missingActor";
203
+ }
204
+ span.setAttribute("activitypub.actor.id", activity.actorId.href);
205
+ if (queue != null) {
206
+ const carrier = {};
207
+ propagation.inject(context.active(), carrier);
208
+ try {
209
+ await queue.enqueue({
210
+ type: "inbox",
211
+ id: crypto.randomUUID(),
212
+ baseUrl: ctx.origin,
213
+ activity: json,
214
+ identifier: recipient,
215
+ attempt: 0,
216
+ started: (/* @__PURE__ */ new Date()).toISOString(),
217
+ traceContext: carrier
218
+ });
219
+ } catch (error) {
220
+ logger.error("Failed to enqueue the incoming activity {activityId}:\n{error}", {
221
+ error,
222
+ activityId: activity.id?.href,
223
+ activity: json,
224
+ recipient
225
+ });
226
+ span.setStatus({
227
+ code: SpanStatusCode.ERROR,
228
+ message: `Failed to enqueue the incoming activity ${activity.id?.href}.`
229
+ });
230
+ throw error;
231
+ }
232
+ logger.info("Activity {activityId} is enqueued.", {
233
+ activityId: activity.id?.href,
234
+ activity: json,
235
+ recipient
236
+ });
237
+ return "enqueued";
238
+ }
239
+ tracerProvider = tracerProvider ?? trace.getTracerProvider();
240
+ return await tracerProvider.getTracer(name, version).startActiveSpan("activitypub.dispatch_inbox_listener", { kind: SpanKind.INTERNAL }, async (span) => {
241
+ const dispatched = inboxListeners?.dispatchWithClass(activity);
242
+ if (dispatched == null) {
243
+ logger.error("Unsupported activity type:\n{activity}", {
244
+ activity: json,
245
+ recipient
246
+ });
247
+ span.setStatus({
248
+ code: SpanStatusCode.UNSET,
249
+ message: `Unsupported activity type: ${getTypeId(activity).href}`
250
+ });
251
+ span.end();
252
+ return "unsupportedActivity";
253
+ }
254
+ const { class: cls, listener } = dispatched;
255
+ span.updateName(`activitypub.dispatch_inbox_listener ${cls.name}`);
256
+ try {
257
+ await listener(inboxContextFactory(recipient, json, activity?.id?.href, getTypeId(activity).href), activity);
258
+ } catch (error) {
259
+ try {
260
+ await inboxErrorHandler?.(ctx, error);
261
+ } catch (error) {
262
+ logger.error("An unexpected error occurred in inbox error handler:\n{error}", {
263
+ error,
264
+ activityId: activity.id?.href,
265
+ activity: json,
266
+ recipient
267
+ });
268
+ }
269
+ logger.error("Failed to process the incoming activity {activityId}:\n{error}", {
270
+ error,
271
+ activityId: activity.id?.href,
272
+ activity: json,
273
+ recipient
274
+ });
275
+ span.setStatus({
276
+ code: SpanStatusCode.ERROR,
277
+ message: String(error)
278
+ });
279
+ span.end();
280
+ return "error";
281
+ }
282
+ if (cacheKey != null) await kv.set(cacheKey, true, { ttl: Temporal.Duration.from({ days: 1 }) });
283
+ logger.info("Activity {activityId} has been processed.", {
284
+ activityId: activity.id?.href,
285
+ activity: json,
286
+ recipient
287
+ });
288
+ span.end();
289
+ return "success";
290
+ });
291
+ }
292
+ //#endregion
156
293
  //#region src/federation/handler.ts
157
294
  /**
158
295
  * Handles an actor request.
@@ -225,8 +362,8 @@ async function handleObject(request, { values, context, objectDispatcher, author
225
362
  * @param parameters The parameters for handling the collection.
226
363
  * @returns A promise that resolves to an HTTP response.
227
364
  */
228
- async function handleCollection(request, { name: name$2, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, tracerProvider, onUnauthorized, onNotFound }) {
229
- const spanName = name$2.trim().replace(/\s+/g, "_");
365
+ async function handleCollection(request, { name: name$1, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, tracerProvider, onUnauthorized, onNotFound }) {
366
+ const spanName = name$1.trim().replace(/\s+/g, "_");
230
367
  tracerProvider = tracerProvider ?? trace.getTracerProvider();
231
368
  const tracer = tracerProvider.getTracer(name, version);
232
369
  const cursor = new URL(request.url).searchParams.get("cursor");
@@ -268,7 +405,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
268
405
  collection = new OrderedCollection({
269
406
  id: baseUri,
270
407
  totalItems: totalItems == null ? null : Number(totalItems),
271
- items: filterCollectionItems(itemsOrResponse, name$2, filterPredicate)
408
+ items: filterCollectionItems(itemsOrResponse, name$1, filterPredicate)
272
409
  });
273
410
  } else {
274
411
  const lastCursor = await collectionCallbacks.lastCursor?.(context, identifier);
@@ -289,7 +426,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
289
426
  } else {
290
427
  const uri = new URL(baseUri);
291
428
  uri.searchParams.set("cursor", cursor);
292
- const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$2}`, {
429
+ const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$1}`, {
293
430
  kind: SpanKind.SERVER,
294
431
  attributes: {
295
432
  "activitypub.collection.id": uri.href,
@@ -333,7 +470,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
333
470
  id: uri,
334
471
  prev,
335
472
  next,
336
- items: filterCollectionItems(items, name$2, filterPredicate),
473
+ items: filterCollectionItems(items, name$1, filterPredicate),
337
474
  partOf
338
475
  });
339
476
  }
@@ -377,6 +514,202 @@ function filterCollectionItems(items, collectionName, filterPredicate) {
377
514
  }
378
515
  return result;
379
516
  }
517
+ function summarizeJsonActivity(json) {
518
+ if (json == null || typeof json !== "object") return {};
519
+ const activity = json;
520
+ return {
521
+ activityId: typeof activity.id === "string" ? activity.id : void 0,
522
+ activityType: typeof activity.type === "string" ? activity.type : void 0
523
+ };
524
+ }
525
+ /**
526
+ * Handles an outbox POST request.
527
+ * @template TContextData The context data to pass to the context.
528
+ * @param request The HTTP request.
529
+ * @param parameters The parameters for handling the request.
530
+ * @returns A promise that resolves to an HTTP response.
531
+ * @since 2.2.0
532
+ */
533
+ async function handleOutbox(request, { identifier, context: ctx, outboxContextFactory, actorDispatcher, authorizePredicate, outboxListeners, outboxErrorHandler, onUnauthorized, onNotFound }) {
534
+ const logger = getLogger([
535
+ "fedify",
536
+ "federation",
537
+ "outbox"
538
+ ]);
539
+ if (request.bodyUsed) {
540
+ logger.error("Request body has already been read.", { identifier });
541
+ return new Response("Internal server error.", {
542
+ status: 500,
543
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
544
+ });
545
+ } else if (request.body?.locked) {
546
+ logger.error("Request body is locked.", { identifier });
547
+ return new Response("Internal server error.", {
548
+ status: 500,
549
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
550
+ });
551
+ }
552
+ if (actorDispatcher == null) {
553
+ logger.error("Actor dispatcher is not set.", { identifier });
554
+ return await onNotFound(request);
555
+ }
556
+ if (authorizePredicate != null) {
557
+ const authorizeContext = ctx.clone(ctx.data);
558
+ authorizeContext.request = request.clone();
559
+ const requestForUnauthorized = authorizeContext.request.clone();
560
+ if (!await authorizePredicate(authorizeContext, identifier)) return await onUnauthorized(requestForUnauthorized);
561
+ }
562
+ const actor = await actorDispatcher(ctx, identifier);
563
+ if (actor == null || actor instanceof Tombstone) {
564
+ logger.error("Actor {identifier} not found.", { identifier });
565
+ return await onNotFound(request);
566
+ }
567
+ const requestForParsing = request.clone();
568
+ let json;
569
+ try {
570
+ json = await requestForParsing.json();
571
+ } catch (error) {
572
+ logger.error("Failed to parse JSON:\n{error}", {
573
+ identifier,
574
+ error
575
+ });
576
+ const outboxContext = outboxContextFactory(identifier, null, void 0, "");
577
+ try {
578
+ await outboxErrorHandler?.(outboxContext, error);
579
+ } catch (error) {
580
+ logger.error("An unexpected error occurred in outbox error handler:\n{error}", {
581
+ error,
582
+ identifier
583
+ });
584
+ }
585
+ return new Response("Invalid JSON.", {
586
+ status: 400,
587
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
588
+ });
589
+ }
590
+ let activity;
591
+ try {
592
+ activity = await Activity.fromJsonLd(json, ctx);
593
+ } catch (error) {
594
+ const summary = summarizeJsonActivity(json);
595
+ logger.error("Failed to parse activity:\n{error}", {
596
+ identifier,
597
+ ...summary,
598
+ error
599
+ });
600
+ const outboxContext = outboxContextFactory(identifier, json, summary.activityId, summary.activityType ?? "");
601
+ try {
602
+ await outboxErrorHandler?.(outboxContext, error);
603
+ } catch (error) {
604
+ logger.error("An unexpected error occurred in outbox error handler:\n{error}", {
605
+ error,
606
+ identifier,
607
+ ...summary
608
+ });
609
+ }
610
+ return new Response("Invalid activity.", {
611
+ status: 400,
612
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
613
+ });
614
+ }
615
+ const outboxContext = outboxContextFactory(identifier, json, activity.id?.href, getTypeId(activity).href);
616
+ const expectedActorId = actor.id ?? ctx.getActorUri(identifier);
617
+ if (activity.actorIds.length < 1) {
618
+ const error = /* @__PURE__ */ new Error("The posted activity has no actor.");
619
+ logger.error("The posted activity has no actor for outbox {identifier}.", {
620
+ identifier,
621
+ activityId: activity.id?.href,
622
+ expectedActorId: expectedActorId.href
623
+ });
624
+ try {
625
+ await outboxErrorHandler?.(outboxContext, error);
626
+ } catch (error) {
627
+ logger.error("An unexpected error occurred in outbox error handler:\n{error}", {
628
+ error,
629
+ activityId: activity.id?.href,
630
+ activityType: getTypeId(activity).href,
631
+ identifier
632
+ });
633
+ }
634
+ return new Response(error.message, {
635
+ status: 400,
636
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
637
+ });
638
+ }
639
+ if (!activity.actorIds.every((actorId) => actorId.href === expectedActorId.href)) {
640
+ const error = /* @__PURE__ */ new Error("The activity actor does not match the outbox owner.");
641
+ logger.error("The posted activity actor does not match outbox owner {identifier}.", {
642
+ identifier,
643
+ activityId: activity.id?.href,
644
+ expectedActorId: expectedActorId.href,
645
+ actorIds: activity.actorIds.map((actorId) => actorId.href)
646
+ });
647
+ try {
648
+ await outboxErrorHandler?.(outboxContext, error);
649
+ } catch (error) {
650
+ logger.error("An unexpected error occurred in outbox error handler:\n{error}", {
651
+ error,
652
+ activityId: activity.id?.href,
653
+ activityType: getTypeId(activity).href,
654
+ identifier
655
+ });
656
+ }
657
+ return new Response(error.message, {
658
+ status: 400,
659
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
660
+ });
661
+ }
662
+ const dispatched = outboxListeners?.dispatchWithClass(activity);
663
+ if (dispatched == null) {
664
+ logger.debug("Unsupported activity type {activityType}.", {
665
+ identifier,
666
+ activityId: activity.id?.href,
667
+ activityType: getTypeId(activity).href
668
+ });
669
+ return new Response("", {
670
+ status: 202,
671
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
672
+ });
673
+ }
674
+ try {
675
+ await dispatched.listener(outboxContext, activity);
676
+ } catch (error) {
677
+ try {
678
+ await outboxErrorHandler?.(outboxContext, error);
679
+ } catch (error) {
680
+ logger.error("An unexpected error occurred in outbox error handler:\n{error}", {
681
+ error,
682
+ activityId: activity.id?.href,
683
+ activityType: getTypeId(activity).href,
684
+ identifier
685
+ });
686
+ }
687
+ logger.error("Failed to process the incoming activity {activityId}:\n{error}", {
688
+ error,
689
+ activityId: activity.id?.href,
690
+ activityType: getTypeId(activity).href,
691
+ identifier
692
+ });
693
+ return new Response("Internal server error.", {
694
+ status: 500,
695
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
696
+ });
697
+ }
698
+ 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.", {
699
+ identifier,
700
+ activityId: activity.id?.href,
701
+ activityType: getTypeId(activity).href
702
+ });
703
+ logger.info("Activity {activityId} has been processed in outbox listener.", {
704
+ activityId: activity.id?.href,
705
+ activityType: getTypeId(activity).href,
706
+ identifier
707
+ });
708
+ return new Response("", {
709
+ status: 202,
710
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
711
+ });
712
+ }
380
713
  /**
381
714
  * Handles an inbox request for ActivityPub activities.
382
715
  * @template TContextData The context data to pass to the context.
@@ -807,8 +1140,8 @@ var CustomCollectionHandler = class {
807
1140
  * @param CollectionPage The CollectionPage constructor.
808
1141
  * @param filterPredicate Optional filter predicate for items.
809
1142
  */
810
- constructor(name$1, values, context, callbacks, tracerProvider = trace.getTracerProvider(), Collection, CollectionPage, filterPredicate) {
811
- this.name = name$1;
1143
+ constructor(name$2, values, context, callbacks, tracerProvider = trace.getTracerProvider(), Collection, CollectionPage, filterPredicate) {
1144
+ this.name = name$2;
812
1145
  this.values = values;
813
1146
  this.context = context;
814
1147
  this.callbacks = callbacks;
@@ -2167,16 +2500,37 @@ var FederationImpl = class extends FederationBuilderImpl {
2167
2500
  onNotFound
2168
2501
  });
2169
2502
  }
2170
- case "outbox": return await handleCollection(request, {
2171
- name: "outbox",
2172
- identifier: route.values.identifier,
2173
- uriGetter: context.getOutboxUri.bind(context),
2174
- context,
2175
- collectionCallbacks: this.outboxCallbacks,
2176
- tracerProvider: this.tracerProvider,
2177
- onUnauthorized,
2178
- onNotFound
2179
- });
2503
+ case "outbox":
2504
+ if (request.method === "POST") {
2505
+ if (this.outboxListeners == null) return new Response("Method not allowed.", {
2506
+ status: 405,
2507
+ headers: {
2508
+ Allow: "GET, HEAD",
2509
+ "Content-Type": "text/plain; charset=utf-8"
2510
+ }
2511
+ });
2512
+ return await handleOutbox(request, {
2513
+ identifier: route.values.identifier,
2514
+ context,
2515
+ outboxContextFactory: context.toOutboxContext.bind(context),
2516
+ actorDispatcher: this.actorCallbacks?.dispatcher,
2517
+ authorizePredicate: this.outboxAuthorizePredicate ?? this.outboxCallbacks?.authorizePredicate,
2518
+ outboxListeners: this.outboxListeners,
2519
+ outboxErrorHandler: this.outboxListenerErrorHandler,
2520
+ onUnauthorized,
2521
+ onNotFound
2522
+ });
2523
+ }
2524
+ return await handleCollection(request, {
2525
+ name: "outbox",
2526
+ identifier: route.values.identifier,
2527
+ uriGetter: context.getOutboxUri.bind(context),
2528
+ context,
2529
+ collectionCallbacks: this.outboxCallbacks,
2530
+ tracerProvider: this.tracerProvider,
2531
+ onUnauthorized,
2532
+ onNotFound
2533
+ });
2180
2534
  case "inbox":
2181
2535
  if (request.method !== "POST") return await handleCollection(request, {
2182
2536
  name: "inbox",
@@ -2346,6 +2700,16 @@ var ContextImpl = class ContextImpl {
2346
2700
  invokedFromActorKeyPairsDispatcher: this.invokedFromActorKeyPairsDispatcher
2347
2701
  });
2348
2702
  }
2703
+ toOutboxContext(identifier, activity, activityId, activityType) {
2704
+ return new OutboxContextImpl(identifier, activity, activityId, activityType, {
2705
+ url: this.url,
2706
+ federation: this.federation,
2707
+ data: this.data,
2708
+ documentLoader: this.documentLoader,
2709
+ contextLoader: this.contextLoader,
2710
+ invokedFromActorKeyPairsDispatcher: this.invokedFromActorKeyPairsDispatcher
2711
+ });
2712
+ }
2349
2713
  get hostname() {
2350
2714
  return this.url.hostname;
2351
2715
  }
@@ -2635,9 +2999,9 @@ var ContextImpl = class ContextImpl {
2635
2999
  attributes: {
2636
3000
  "activitypub.activity.type": getTypeId(activity).href,
2637
3001
  "activitypub.activity.to": activity.toIds.map((to) => to.href),
2638
- "activitypub.activity.cc": activity.toIds.map((cc) => cc.href),
3002
+ "activitypub.activity.cc": activity.ccIds.map((cc) => cc.href),
2639
3003
  "activitypub.activity.bto": activity.btoIds.map((bto) => bto.href),
2640
- "activitypub.activity.bcc": activity.toIds.map((bcc) => bcc.href)
3004
+ "activitypub.activity.bcc": activity.bccIds.map((bcc) => bcc.href)
2641
3005
  }
2642
3006
  }, async (span) => {
2643
3007
  try {
@@ -2732,6 +3096,13 @@ var ContextImpl = class ContextImpl {
2732
3096
  preferSharedInbox: options.preferSharedInbox,
2733
3097
  excludeBaseUris: options.excludeBaseUris
2734
3098
  });
3099
+ if (globalThis.Object.keys(inboxes).length < 1) {
3100
+ logger.debug("No inboxes found for activity {activityId}.", {
3101
+ activityId: activity.id?.href,
3102
+ activity
3103
+ });
3104
+ return false;
3105
+ }
2735
3106
  logger.debug("Sending activity {activityId} to inboxes:\n{inboxes}", {
2736
3107
  inboxes: globalThis.Object.keys(inboxes),
2737
3108
  activityId: activity.id?.href,
@@ -2739,7 +3110,7 @@ var ContextImpl = class ContextImpl {
2739
3110
  });
2740
3111
  if (this.federation.fanoutQueue == null || options.immediate || options.fanout === "skip" || (options.fanout ?? "auto") === "auto" && globalThis.Object.keys(inboxes).length < FANOUT_THRESHOLD) {
2741
3112
  await this.federation.sendActivity(keys, inboxes, activity, opts);
2742
- return;
3113
+ return true;
2743
3114
  }
2744
3115
  const keyJwkPairs = await Promise.all(keys.map(async ({ keyId, privateKey }) => ({
2745
3116
  keyId: keyId.href,
@@ -2768,6 +3139,7 @@ var ContextImpl = class ContextImpl {
2768
3139
  };
2769
3140
  if (!this.federation.manuallyStartQueue) this.federation._startQueueInternal(this.data);
2770
3141
  await this.federation.fanoutQueue.enqueue(message, { orderingKey: options.orderingKey });
3142
+ return true;
2771
3143
  }
2772
3144
  async *getFollowers(identifier) {
2773
3145
  if (this.federation.followersCallbacks == null) throw new Error("No followers collection dispatcher registered.");
@@ -3002,6 +3374,170 @@ var RequestContextImpl = class RequestContextImpl extends ContextImpl {
3002
3374
  }
3003
3375
  }
3004
3376
  };
3377
+ function forwardActivity(ctx, loggerCategory, forwarder, recipients, options) {
3378
+ return ctx.tracerProvider.getTracer(name, version).startActiveSpan(ctx.federation.outboxQueue == null || options?.immediate ? `activitypub.${loggerCategory}` : "activitypub.fanout", {
3379
+ kind: ctx.federation.outboxQueue == null || options?.immediate ? SpanKind.CLIENT : SpanKind.PRODUCER,
3380
+ attributes: { "activitypub.activity.type": ctx.activityType }
3381
+ }, async (span) => {
3382
+ try {
3383
+ if (ctx.activityId != null) span.setAttribute("activitypub.activity.id", ctx.activityId);
3384
+ return await forwardActivityInternal(ctx, loggerCategory, forwarder, recipients, options);
3385
+ } catch (e) {
3386
+ span.setStatus({
3387
+ code: SpanStatusCode.ERROR,
3388
+ message: String(e)
3389
+ });
3390
+ throw e;
3391
+ } finally {
3392
+ span.end();
3393
+ }
3394
+ });
3395
+ }
3396
+ async function forwardActivityInternal(ctx, loggerCategory, forwarder, recipients, options) {
3397
+ const logger = getLogger([
3398
+ "fedify",
3399
+ "federation",
3400
+ loggerCategory
3401
+ ]);
3402
+ let keys;
3403
+ let identifier = null;
3404
+ if ("identifier" in forwarder || "username" in forwarder) {
3405
+ if ("identifier" in forwarder) identifier = forwarder.identifier;
3406
+ else {
3407
+ const username = forwarder.username;
3408
+ if (ctx.federation.actorCallbacks?.handleMapper == null) identifier = username;
3409
+ else {
3410
+ const mapped = await ctx.federation.actorCallbacks.handleMapper(ctx, username);
3411
+ if (mapped == null) throw new Error(`No actor found for the given username ${JSON.stringify(username)}.`);
3412
+ identifier = mapped;
3413
+ }
3414
+ }
3415
+ const actorKeyPairs = await ctx.getActorKeyPairs(identifier);
3416
+ if (actorKeyPairs.length < 1) throw new Error(`No key pair found for actor ${JSON.stringify(identifier)}.`);
3417
+ keys = actorKeyPairs.map((kp) => ({
3418
+ keyId: kp.keyId,
3419
+ privateKey: kp.privateKey
3420
+ }));
3421
+ } else if (Array.isArray(forwarder)) {
3422
+ if (forwarder.length < 1) throw new Error("The forwarder's key pairs are empty.");
3423
+ keys = forwarder;
3424
+ } else keys = [forwarder];
3425
+ if (!hasSignatureLike(ctx.activity)) {
3426
+ if (!hasProofLike(ctx.activity)) {
3427
+ if (options?.skipIfUnsigned) return false;
3428
+ 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.", {
3429
+ activityId: ctx.activityId,
3430
+ activityType: ctx.activityType,
3431
+ identifier: identifier ?? void 0
3432
+ });
3433
+ }
3434
+ }
3435
+ if (recipients === "followers") {
3436
+ if (identifier == null) throw new Error("If recipients is \"followers\", forwarder must be an actor identifier or username.");
3437
+ const followers = [];
3438
+ for await (const recipient of ctx.getFollowers(identifier)) followers.push(recipient);
3439
+ recipients = followers;
3440
+ }
3441
+ const inboxes = extractInboxes({
3442
+ recipients: Array.isArray(recipients) ? recipients : [recipients],
3443
+ preferSharedInbox: options?.preferSharedInbox,
3444
+ excludeBaseUris: options?.excludeBaseUris
3445
+ });
3446
+ if (globalThis.Object.keys(inboxes).length < 1) {
3447
+ logger.debug("No inboxes found for activity {activityId}.", {
3448
+ activityId: ctx.activityId,
3449
+ activityType: ctx.activityType,
3450
+ identifier: identifier ?? void 0
3451
+ });
3452
+ return false;
3453
+ }
3454
+ logger.debug("Forwarding activity {activityId} to inboxes:\n{inboxes}", {
3455
+ inboxes: globalThis.Object.keys(inboxes),
3456
+ activityId: ctx.activityId,
3457
+ activity: ctx.activity
3458
+ });
3459
+ if (options?.immediate || ctx.federation.outboxQueue == null) {
3460
+ if (options?.immediate) logger.debug("Forwarding activity immediately without queue since immediate option is set.");
3461
+ else logger.debug("Forwarding activity immediately without queue since queue is not set.");
3462
+ const promises = [];
3463
+ for (const inbox in inboxes) promises.push(sendActivity({
3464
+ keys,
3465
+ activity: ctx.activity,
3466
+ activityId: ctx.activityId,
3467
+ activityType: ctx.activityType,
3468
+ inbox: new URL(inbox),
3469
+ sharedInbox: inboxes[inbox].sharedInbox,
3470
+ tracerProvider: ctx.tracerProvider,
3471
+ specDeterminer: new KvSpecDeterminer(ctx.federation.kv, ctx.federation.kvPrefixes.httpMessageSignaturesSpec, ctx.federation.firstKnock)
3472
+ }));
3473
+ await Promise.all(promises);
3474
+ return true;
3475
+ }
3476
+ logger.debug("Enqueuing activity {activityId} to forward later.", {
3477
+ activityId: ctx.activityId,
3478
+ activity: ctx.activity
3479
+ });
3480
+ if (!ctx.federation.manuallyStartQueue) ctx.federation._startQueueInternal(ctx.data);
3481
+ const keyJwkPairs = [];
3482
+ for (const { keyId, privateKey } of keys) {
3483
+ const privateKeyJwk = await exportJwk(privateKey);
3484
+ keyJwkPairs.push({
3485
+ keyId: keyId.href,
3486
+ privateKey: privateKeyJwk
3487
+ });
3488
+ }
3489
+ const carrier = {};
3490
+ propagation.inject(context.active(), carrier);
3491
+ const orderingKey = options?.orderingKey;
3492
+ const started = (/* @__PURE__ */ new Date()).toISOString();
3493
+ const messages = [];
3494
+ for (const inbox in inboxes) {
3495
+ const inboxUrl = new URL(inbox);
3496
+ const message = {
3497
+ type: "outbox",
3498
+ id: crypto.randomUUID(),
3499
+ baseUrl: ctx.origin,
3500
+ keys: keyJwkPairs,
3501
+ activity: ctx.activity,
3502
+ activityId: ctx.activityId,
3503
+ activityType: ctx.activityType,
3504
+ inbox,
3505
+ sharedInbox: inboxes[inbox].sharedInbox,
3506
+ actorIds: [...inboxes[inbox].actorIds],
3507
+ started,
3508
+ attempt: 0,
3509
+ headers: {},
3510
+ orderingKey: orderingKey == null ? void 0 : `${orderingKey}\n${inboxUrl.origin}`,
3511
+ traceContext: carrier
3512
+ };
3513
+ messages.push({
3514
+ message,
3515
+ orderingKey: message.orderingKey
3516
+ });
3517
+ }
3518
+ const { outboxQueue } = ctx.federation;
3519
+ if (outboxQueue.enqueueMany == null || orderingKey != null) {
3520
+ const promises = messages.map((m) => outboxQueue.enqueue(m.message, { orderingKey: m.orderingKey }));
3521
+ const errors = (await Promise.allSettled(promises)).filter((r) => r.status === "rejected").map((r) => r.reason);
3522
+ if (errors.length > 0) {
3523
+ logger.error("Failed to enqueue activity {activityId} to forward later:\n{errors}", {
3524
+ activityId: ctx.activityId,
3525
+ errors
3526
+ });
3527
+ if (errors.length > 1) throw new AggregateError(errors, `Failed to enqueue activity ${ctx.activityId} to forward later.`);
3528
+ throw errors[0];
3529
+ }
3530
+ } else try {
3531
+ await outboxQueue.enqueueMany(messages.map((m) => m.message));
3532
+ } catch (error) {
3533
+ logger.error("Failed to enqueue activity {activityId} to forward later:\n{error}", {
3534
+ activityId: ctx.activityId,
3535
+ error
3536
+ });
3537
+ throw error;
3538
+ }
3539
+ return true;
3540
+ }
3005
3541
  var InboxContextImpl = class InboxContextImpl extends ContextImpl {
3006
3542
  recipient;
3007
3543
  activity;
@@ -3025,13 +3561,40 @@ var InboxContextImpl = class InboxContextImpl extends ContextImpl {
3025
3561
  });
3026
3562
  }
3027
3563
  forwardActivity(forwarder, recipients, options) {
3028
- return this.tracerProvider.getTracer(name, version).startActiveSpan("activitypub.outbox", {
3029
- kind: this.federation.outboxQueue == null || options?.immediate ? SpanKind.CLIENT : SpanKind.PRODUCER,
3030
- attributes: { "activitypub.activity.type": this.activityType }
3564
+ return forwardActivity(this, "inbox", forwarder, recipients, options).then(() => void 0);
3565
+ }
3566
+ };
3567
+ var OutboxContextImpl = class OutboxContextImpl extends ContextImpl {
3568
+ #deliveryState;
3569
+ identifier;
3570
+ activity;
3571
+ activityId;
3572
+ activityType;
3573
+ constructor(identifier, activity, activityId, activityType, options, deliveryState = { delivered: false }) {
3574
+ super(options);
3575
+ this.#deliveryState = deliveryState;
3576
+ this.identifier = identifier;
3577
+ this.activity = activity;
3578
+ this.activityId = activityId;
3579
+ this.activityType = activityType;
3580
+ }
3581
+ hasDeliveredActivity() {
3582
+ return this.#deliveryState.delivered;
3583
+ }
3584
+ sendActivity(sender, recipients, activity, options = {}) {
3585
+ return this.tracerProvider.getTracer(name, version).startActiveSpan(this.federation.outboxQueue == null || options.immediate ? "activitypub.outbox" : "activitypub.fanout", {
3586
+ kind: this.federation.outboxQueue == null || options.immediate ? SpanKind.CLIENT : SpanKind.PRODUCER,
3587
+ attributes: {
3588
+ "activitypub.activity.type": getTypeId(activity).href,
3589
+ "activitypub.activity.to": activity.toIds.map((to) => to.href),
3590
+ "activitypub.activity.cc": activity.ccIds.map((cc) => cc.href),
3591
+ "activitypub.activity.bto": activity.btoIds.map((bto) => bto.href),
3592
+ "activitypub.activity.bcc": activity.bccIds.map((bcc) => bcc.href)
3593
+ }
3031
3594
  }, async (span) => {
3032
3595
  try {
3033
- if (this.activityId != null) span.setAttribute("activitypub.activity.id", this.activityId);
3034
- await this.forwardActivityInternal(forwarder, recipients, options);
3596
+ if (activity.id != null) span.setAttribute("activitypub.activity.id", activity.id.href);
3597
+ if (await this.sendActivityInternal(sender, recipients, activity, options, span)) this.#deliveryState.delivered = true;
3035
3598
  } catch (e) {
3036
3599
  span.setStatus({
3037
3600
  code: SpanStatusCode.ERROR,
@@ -3043,151 +3606,20 @@ var InboxContextImpl = class InboxContextImpl extends ContextImpl {
3043
3606
  }
3044
3607
  });
3045
3608
  }
3046
- async forwardActivityInternal(forwarder, recipients, options) {
3047
- const logger = getLogger([
3048
- "fedify",
3049
- "federation",
3050
- "inbox"
3051
- ]);
3052
- let keys;
3053
- let identifier = null;
3054
- if ("identifier" in forwarder || "username" in forwarder) {
3055
- if ("identifier" in forwarder) identifier = forwarder.identifier;
3056
- else {
3057
- const username = forwarder.username;
3058
- if (this.federation.actorCallbacks?.handleMapper == null) identifier = username;
3059
- else {
3060
- const mapped = await this.federation.actorCallbacks.handleMapper(this, username);
3061
- if (mapped == null) throw new Error(`No actor found for the given username ${JSON.stringify(username)}.`);
3062
- identifier = mapped;
3063
- }
3064
- }
3065
- const actorKeyPairs = await this.getActorKeyPairs(identifier);
3066
- if (actorKeyPairs.length < 1) throw new Error(`No key pair found for actor ${JSON.stringify(identifier)}.`);
3067
- keys = actorKeyPairs.map((kp) => ({
3068
- keyId: kp.keyId,
3069
- privateKey: kp.privateKey
3070
- }));
3071
- } else if (Array.isArray(forwarder)) {
3072
- if (forwarder.length < 1) throw new Error("The forwarder's key pairs are empty.");
3073
- keys = forwarder;
3074
- } else keys = [forwarder];
3075
- if (!hasSignature(this.activity)) {
3076
- let hasProof;
3077
- try {
3078
- hasProof = await (await Activity.fromJsonLd(this.activity, this)).getProof() != null;
3079
- } catch {
3080
- hasProof = false;
3081
- }
3082
- if (!hasProof) {
3083
- if (options?.skipIfUnsigned) return;
3084
- 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.");
3085
- }
3086
- }
3087
- if (recipients === "followers") {
3088
- if (identifier == null) throw new Error("If recipients is \"followers\", forwarder must be an actor identifier or username.");
3089
- const followers = [];
3090
- for await (const recipient of this.getFollowers(identifier)) followers.push(recipient);
3091
- recipients = followers;
3092
- }
3093
- const inboxes = extractInboxes({
3094
- recipients: Array.isArray(recipients) ? recipients : [recipients],
3095
- preferSharedInbox: options?.preferSharedInbox,
3096
- excludeBaseUris: options?.excludeBaseUris
3097
- });
3098
- logger.debug("Forwarding activity {activityId} to inboxes:\n{inboxes}", {
3099
- inboxes: globalThis.Object.keys(inboxes),
3100
- activityId: this.activityId,
3101
- activity: this.activity
3102
- });
3103
- if (options?.immediate || this.federation.outboxQueue == null) {
3104
- if (options?.immediate) logger.debug("Forwarding activity immediately without queue since immediate option is set.");
3105
- else logger.debug("Forwarding activity immediately without queue since queue is not set.");
3106
- const promises = [];
3107
- for (const inbox in inboxes) promises.push(sendActivity({
3108
- keys,
3109
- activity: this.activity,
3110
- activityId: this.activityId,
3111
- activityType: this.activityType,
3112
- inbox: new URL(inbox),
3113
- sharedInbox: inboxes[inbox].sharedInbox,
3114
- tracerProvider: this.tracerProvider,
3115
- specDeterminer: new KvSpecDeterminer(this.federation.kv, this.federation.kvPrefixes.httpMessageSignaturesSpec, this.federation.firstKnock)
3116
- }));
3117
- await Promise.all(promises);
3118
- return;
3119
- }
3120
- logger.debug("Enqueuing activity {activityId} to forward later.", {
3121
- activityId: this.activityId,
3122
- activity: this.activity
3609
+ forwardActivity(forwarder, recipients, options) {
3610
+ return forwardActivity(this, "outbox", forwarder, recipients, options).then((delivered) => {
3611
+ if (delivered) this.#deliveryState.delivered = true;
3123
3612
  });
3124
- const keyJwkPairs = [];
3125
- for (const { keyId, privateKey } of keys) {
3126
- const privateKeyJwk = await exportJwk(privateKey);
3127
- keyJwkPairs.push({
3128
- keyId: keyId.href,
3129
- privateKey: privateKeyJwk
3130
- });
3131
- }
3132
- const carrier = {};
3133
- propagation.inject(context.active(), carrier);
3134
- const orderingKey = options?.orderingKey;
3135
- const messages = [];
3136
- for (const inbox in inboxes) {
3137
- const inboxUrl = new URL(inbox);
3138
- const message = {
3139
- type: "outbox",
3140
- id: crypto.randomUUID(),
3141
- baseUrl: this.origin,
3142
- keys: keyJwkPairs,
3143
- activity: this.activity,
3144
- activityId: this.activityId,
3145
- activityType: this.activityType,
3146
- inbox,
3147
- sharedInbox: inboxes[inbox].sharedInbox,
3148
- started: (/* @__PURE__ */ new Date()).toISOString(),
3149
- attempt: 0,
3150
- headers: {},
3151
- orderingKey: orderingKey == null ? void 0 : `${orderingKey}\n${inboxUrl.origin}`,
3152
- traceContext: carrier
3153
- };
3154
- messages.push({
3155
- message,
3156
- orderingKey: message.orderingKey
3157
- });
3158
- }
3159
- const { outboxQueue } = this.federation;
3160
- if (outboxQueue.enqueueMany == null) {
3161
- const promises = messages.map((m) => outboxQueue.enqueue(m.message, { orderingKey: m.orderingKey }));
3162
- const errors = (await Promise.allSettled(promises)).filter((r) => r.status === "rejected").map((r) => r.reason);
3163
- if (errors.length > 0) {
3164
- logger.error("Failed to enqueue activity {activityId} to forward later:\n{errors}", {
3165
- activityId: this.activityId,
3166
- errors
3167
- });
3168
- if (errors.length > 1) throw new AggregateError(errors, `Failed to enqueue activity ${this.activityId} to forward later.`);
3169
- throw errors[0];
3170
- }
3171
- } else if (orderingKey != null) {
3172
- const promises = messages.map((m) => outboxQueue.enqueue(m.message, { orderingKey: m.orderingKey }));
3173
- const errors = (await Promise.allSettled(promises)).filter((r) => r.status === "rejected").map((r) => r.reason);
3174
- if (errors.length > 0) {
3175
- logger.error("Failed to enqueue activity {activityId} to forward later:\n{errors}", {
3176
- activityId: this.activityId,
3177
- errors
3178
- });
3179
- if (errors.length > 1) throw new AggregateError(errors, `Failed to enqueue activity ${this.activityId} to forward later.`);
3180
- throw errors[0];
3181
- }
3182
- } else try {
3183
- await outboxQueue.enqueueMany(messages.map((m) => m.message));
3184
- } catch (error) {
3185
- logger.error("Failed to enqueue activity {activityId} to forward later:\n{error}", {
3186
- activityId: this.activityId,
3187
- error
3188
- });
3189
- throw error;
3190
- }
3613
+ }
3614
+ clone(data) {
3615
+ return new OutboxContextImpl(this.identifier, this.activity, this.activityId, this.activityType, {
3616
+ url: this.url,
3617
+ federation: this.federation,
3618
+ data,
3619
+ documentLoader: this.documentLoader,
3620
+ contextLoader: this.contextLoader,
3621
+ invokedFromActorKeyPairsDispatcher: this.invokedFromActorKeyPairsDispatcher
3622
+ }, this.#deliveryState);
3191
3623
  }
3192
3624
  };
3193
3625
  var KvSpecDeterminer = class {
@@ -3240,4 +3672,4 @@ function getRequestId(request) {
3240
3672
  return `req_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`;
3241
3673
  }
3242
3674
  //#endregion
3243
- export { autoIdAssigner as _, createFederation as a, handleCollection as c, handleObject as d, respondWithObject as f, actorDehydrator as g, handleNodeInfoJrd as h, KvSpecDeterminer as i, handleCustomCollection as l, handleNodeInfo as m, FederationImpl as n, handleWebFinger as o, respondWithObjectIfAcceptable as p, InboxContextImpl as r, handleActor as s, ContextImpl as t, handleInbox as u };
3675
+ export { handleNodeInfoJrd as _, OutboxContextImpl as a, handleActor as c, handleInbox as d, handleObject as f, handleNodeInfo as g, respondWithObjectIfAcceptable as h, KvSpecDeterminer as i, handleCollection as l, respondWithObject as m, FederationImpl as n, createFederation as o, handleOutbox as p, InboxContextImpl as r, handleWebFinger as s, ContextImpl as t, handleCustomCollection as u, actorDehydrator as v, autoIdAssigner as y };