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