@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.
- package/dist/activity-listener-Ck3JZ_hR.mjs +40 -0
- package/dist/{builder-7PVCiLiR.mjs → builder-CssIxEgK.mjs} +57 -7
- package/dist/compat/mod.d.cts +1 -1
- package/dist/compat/mod.d.ts +1 -1
- package/dist/compat/transformers.test.mjs +1 -1
- package/dist/{context-78ecvxf5.d.ts → context-BGrYMSTk.d.ts} +143 -1
- package/dist/{context-DYDPdoCb.d.cts → context-CMUd4wy0.d.cts} +143 -1
- package/dist/{context-Juj6bdHC.mjs → context-Dk_tacqz.mjs} +17 -2
- package/dist/{deno-vxcWcxQS.mjs → deno-DFC3hdDk.mjs} +1 -1
- package/dist/{docloader-D7q0-Xef.mjs → docloader-DJSGzW4N.mjs} +2 -2
- package/dist/federation/builder.test.mjs +25 -1
- package/dist/federation/handler.test.mjs +369 -8
- package/dist/federation/idempotency.test.mjs +2 -2
- package/dist/federation/inbox.test.mjs +3 -3
- package/dist/federation/middleware.test.mjs +510 -8
- package/dist/federation/mod.cjs +1 -1
- package/dist/federation/mod.d.cts +3 -3
- package/dist/federation/mod.d.ts +3 -3
- package/dist/federation/mod.js +1 -1
- package/dist/federation/send.test.mjs +3 -3
- package/dist/federation/webfinger.test.mjs +2 -2
- package/dist/{http-RZPxDWq5.mjs → http-BrF-JQov.mjs} +2 -2
- package/dist/{http-JxF7bG0o.cjs → http-DDRsBRY5.cjs} +1 -1
- package/dist/{http-D-MhhYUF.js → http-nVAbZPuI.js} +1 -1
- package/dist/{key-CGx_dDkX.mjs → key-2MxqHz-F.mjs} +1 -1
- package/dist/{kv-cache-D84Mk0fZ.js → kv-cache-5w7DZnmJ.js} +1 -1
- package/dist/{kv-cache-C2gdVgvb.cjs → kv-cache-CPeV5q6I.cjs} +1 -1
- package/dist/{ld-wup-liFO.mjs → ld-bY6topKr.mjs} +26 -3
- package/dist/{middleware-BjVx-_bv.mjs → middleware-BCJUFXYb.mjs} +612 -180
- package/dist/{middleware-Bn75dPug.cjs → middleware-BPHO6DE3.cjs} +676 -323
- package/dist/{middleware-RF-sUfTr.js → middleware-C10kVjEo.js} +670 -322
- package/dist/{middleware-wdfeWjRJ.mjs → middleware-DI82-dr3.mjs} +1 -1
- package/dist/{middleware-CXOVT4Ph.cjs → middleware-JAlnEFGy.cjs} +1 -1
- package/dist/{mod-CEohtXhV.d.cts → mod-BcJHeuv1.d.cts} +1 -1
- package/dist/{mod-CokIUYDr.d.ts → mod-CJXfyw7v.d.ts} +1 -1
- package/dist/{mod-DvxszxXC.d.ts → mod-CR8soWa9.d.ts} +18 -1
- package/dist/{mod-DoJBjjnO.d.cts → mod-Cr3f-ACa.d.cts} +18 -1
- package/dist/mod.cjs +6 -4
- package/dist/mod.d.cts +5 -5
- package/dist/mod.d.ts +5 -5
- package/dist/mod.js +5 -5
- package/dist/nodeinfo/handler.test.mjs +2 -2
- package/dist/{owner-q2mUMM9a.mjs → owner-D4-A8f_n.mjs} +2 -2
- package/dist/{proof--CpZsF_p.mjs → proof-45_MjnD1.mjs} +32 -3
- package/dist/{proof-_Zyfqyce.cjs → proof-COdach6j.cjs} +61 -3
- package/dist/{proof-CirP9OSd.js → proof-CnfkRjXL.js} +54 -2
- package/dist/{send-CVJfx7bF.mjs → send-BeNvLSaC.mjs} +2 -2
- package/dist/sig/http.test.mjs +2 -2
- package/dist/sig/key.test.mjs +1 -1
- package/dist/sig/ld.test.mjs +44 -2
- package/dist/sig/mod.cjs +4 -2
- package/dist/sig/mod.d.cts +2 -2
- package/dist/sig/mod.d.ts +2 -2
- package/dist/sig/mod.js +3 -3
- package/dist/sig/owner.test.mjs +1 -1
- package/dist/sig/proof.test.mjs +46 -2
- package/dist/testing/mod.d.mts +149 -1
- package/dist/testing/mod.mjs +2 -2
- package/dist/utils/docloader.test.mjs +2 -2
- package/dist/utils/mod.cjs +1 -1
- package/dist/utils/mod.js +1 -1
- package/package.json +5 -5
- 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-
|
|
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-
|
|
8
|
-
import { l as verifyRequest, o as parseRfc9421SignatureInput, u as verifyRequestDetailed } from "./http-
|
|
9
|
-
import { t as getAuthenticatedDocumentLoader } from "./docloader-
|
|
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
|
|
12
|
-
import { n as getKeyOwner, t as doesActorOwnKey } from "./owner-
|
|
13
|
-
import { n as
|
|
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 {
|
|
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-
|
|
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$
|
|
229
|
-
const spanName = name$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
811
|
-
this.name = name$
|
|
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":
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
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.
|
|
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.
|
|
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
|
|
3029
|
-
|
|
3030
|
-
|
|
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 (
|
|
3034
|
-
await this.
|
|
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
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
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
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
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 {
|
|
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 };
|