@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 @@ const { Temporal } = require("@js-temporal/polyfill");
|
|
|
2
2
|
const { URLPattern } = require("urlpattern-polyfill");
|
|
3
3
|
require("./chunk-DDcVe30Y.cjs");
|
|
4
4
|
const require_transformers = require("./transformers-NeAONrAq.cjs");
|
|
5
|
-
const require_http = require("./http-
|
|
6
|
-
const require_proof = require("./proof-
|
|
5
|
+
const require_http = require("./http-BmjzD8cM.cjs");
|
|
6
|
+
const require_proof = require("./proof-Bvs3L21X.cjs");
|
|
7
7
|
const require_types = require("./types-KC4QAoxe.cjs");
|
|
8
|
-
const require_kv_cache = require("./kv-cache-
|
|
8
|
+
const require_kv_cache = require("./kv-cache-GmvjgIY4.cjs");
|
|
9
9
|
let _logtape_logtape = require("@logtape/logtape");
|
|
10
10
|
let _fedify_vocab = require("@fedify/vocab");
|
|
11
11
|
let _opentelemetry_api = require("@opentelemetry/api");
|
|
@@ -17,15 +17,14 @@ let _fedify_vocab_runtime = require("@fedify/vocab-runtime");
|
|
|
17
17
|
let _opentelemetry_semantic_conventions = require("@opentelemetry/semantic-conventions");
|
|
18
18
|
let _fedify_webfinger = require("@fedify/webfinger");
|
|
19
19
|
let node_url = require("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 === _fedify_vocab.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 = (0, _logtape_logtape.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, (0, _fedify_vocab.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: _opentelemetry_api.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: _opentelemetry_api.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
|
+
_opentelemetry_api.propagation.inject(_opentelemetry_api.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: _opentelemetry_api.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 ?? _opentelemetry_api.trace.getTracerProvider();
|
|
138
|
+
return await tracerProvider.getTracer(require_http.name, require_http.version).startActiveSpan("activitypub.dispatch_inbox_listener", { kind: _opentelemetry_api.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: _opentelemetry_api.SpanStatusCode.UNSET,
|
|
147
|
+
message: `Unsupported activity type: ${(0, _fedify_vocab.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, (0, _fedify_vocab.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: _opentelemetry_api.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().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;
|
|
@@ -210,7 +331,7 @@ var FederationBuilderImpl = class {
|
|
|
210
331
|
this.collectionTypeIds = {};
|
|
211
332
|
}
|
|
212
333
|
async build(options) {
|
|
213
|
-
const { FederationImpl } = await Promise.resolve().then(() => require("./middleware-
|
|
334
|
+
const { FederationImpl } = await Promise.resolve().then(() => require("./middleware-BXCjmWN2.cjs"));
|
|
214
335
|
const f = new FederationImpl(options);
|
|
215
336
|
const trailingSlashInsensitiveValue = f.router.trailingSlashInsensitive;
|
|
216
337
|
f.router = this.router.clone();
|
|
@@ -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="${(0, byte_encodings_hex.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 = (0, _logtape_logtape.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, (0, _fedify_vocab.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: _opentelemetry_api.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: _opentelemetry_api.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
|
-
_opentelemetry_api.propagation.inject(_opentelemetry_api.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: _opentelemetry_api.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 ?? _opentelemetry_api.trace.getTracerProvider();
|
|
857
|
-
return await tracerProvider.getTracer(require_http.name, require_http.version).startActiveSpan("activitypub.dispatch_inbox_listener", { kind: _opentelemetry_api.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: _opentelemetry_api.SpanStatusCode.UNSET,
|
|
866
|
-
message: `Unsupported activity type: ${(0, _fedify_vocab.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, (0, _fedify_vocab.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: _opentelemetry_api.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 ?? _opentelemetry_api.trace.getTracerProvider();
|
|
1140
1088
|
const tracer = tracerProvider.getTracer(require_http.name, require_http.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 _fedify_vocab.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: _opentelemetry_api.SpanKind.SERVER,
|
|
1203
1151
|
attributes: {
|
|
1204
1152
|
"activitypub.collection.id": uri.href,
|
|
@@ -1238,249 +1186,53 @@ async function handleCollection(request, { name: name$1, identifier, uriGetter,
|
|
|
1238
1186
|
}
|
|
1239
1187
|
const partOf = new URL(context.url);
|
|
1240
1188
|
partOf.searchParams.delete("cursor");
|
|
1241
|
-
collection = new _fedify_vocab.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 _fedify_vocab.Object || item instanceof _fedify_vocab.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
|
-
(0, _logtape_logtape.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 = (0, _logtape_logtape.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 _fedify_vocab.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 _fedify_vocab.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, (0, _fedify_vocab.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: (0, _fedify_vocab.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: (0, _fedify_vocab.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: (0, _fedify_vocab.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: (0, _fedify_vocab.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: (0, _fedify_vocab.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" }
|
|
1189
|
+
collection = new _fedify_vocab.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 _fedify_vocab.Object || item instanceof _fedify_vocab.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
|
+
(0, _logtape_logtape.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 = _opentelemetry_api.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;
|
|
@@ -3522,37 +3274,16 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
3522
3274
|
onNotFound
|
|
3523
3275
|
});
|
|
3524
3276
|
}
|
|
3525
|
-
case "outbox":
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
identifier: route.values.identifier,
|
|
3536
|
-
context,
|
|
3537
|
-
outboxContextFactory: context.toOutboxContext.bind(context),
|
|
3538
|
-
actorDispatcher: this.actorCallbacks?.dispatcher,
|
|
3539
|
-
authorizePredicate: this.outboxAuthorizePredicate ?? this.outboxCallbacks?.authorizePredicate,
|
|
3540
|
-
outboxListeners: this.outboxListeners,
|
|
3541
|
-
outboxErrorHandler: this.outboxListenerErrorHandler,
|
|
3542
|
-
onUnauthorized,
|
|
3543
|
-
onNotFound
|
|
3544
|
-
});
|
|
3545
|
-
}
|
|
3546
|
-
return await handleCollection(request, {
|
|
3547
|
-
name: "outbox",
|
|
3548
|
-
identifier: route.values.identifier,
|
|
3549
|
-
uriGetter: context.getOutboxUri.bind(context),
|
|
3550
|
-
context,
|
|
3551
|
-
collectionCallbacks: this.outboxCallbacks,
|
|
3552
|
-
tracerProvider: this.tracerProvider,
|
|
3553
|
-
onUnauthorized,
|
|
3554
|
-
onNotFound
|
|
3555
|
-
});
|
|
3277
|
+
case "outbox": return await handleCollection(request, {
|
|
3278
|
+
name: "outbox",
|
|
3279
|
+
identifier: route.values.identifier,
|
|
3280
|
+
uriGetter: context.getOutboxUri.bind(context),
|
|
3281
|
+
context,
|
|
3282
|
+
collectionCallbacks: this.outboxCallbacks,
|
|
3283
|
+
tracerProvider: this.tracerProvider,
|
|
3284
|
+
onUnauthorized,
|
|
3285
|
+
onNotFound
|
|
3286
|
+
});
|
|
3556
3287
|
case "inbox":
|
|
3557
3288
|
if (request.method !== "POST") return await handleCollection(request, {
|
|
3558
3289
|
name: "inbox",
|
|
@@ -3722,16 +3453,6 @@ var ContextImpl = class ContextImpl {
|
|
|
3722
3453
|
invokedFromActorKeyPairsDispatcher: this.invokedFromActorKeyPairsDispatcher
|
|
3723
3454
|
});
|
|
3724
3455
|
}
|
|
3725
|
-
toOutboxContext(identifier, activity, activityId, activityType) {
|
|
3726
|
-
return new OutboxContextImpl(identifier, activity, activityId, activityType, {
|
|
3727
|
-
url: this.url,
|
|
3728
|
-
federation: this.federation,
|
|
3729
|
-
data: this.data,
|
|
3730
|
-
documentLoader: this.documentLoader,
|
|
3731
|
-
contextLoader: this.contextLoader,
|
|
3732
|
-
invokedFromActorKeyPairsDispatcher: this.invokedFromActorKeyPairsDispatcher
|
|
3733
|
-
});
|
|
3734
|
-
}
|
|
3735
3456
|
get hostname() {
|
|
3736
3457
|
return this.url.hostname;
|
|
3737
3458
|
}
|
|
@@ -4021,9 +3742,9 @@ var ContextImpl = class ContextImpl {
|
|
|
4021
3742
|
attributes: {
|
|
4022
3743
|
"activitypub.activity.type": (0, _fedify_vocab.getTypeId)(activity).href,
|
|
4023
3744
|
"activitypub.activity.to": activity.toIds.map((to) => to.href),
|
|
4024
|
-
"activitypub.activity.cc": activity.
|
|
3745
|
+
"activitypub.activity.cc": activity.toIds.map((cc) => cc.href),
|
|
4025
3746
|
"activitypub.activity.bto": activity.btoIds.map((bto) => bto.href),
|
|
4026
|
-
"activitypub.activity.bcc": activity.
|
|
3747
|
+
"activitypub.activity.bcc": activity.toIds.map((bcc) => bcc.href)
|
|
4027
3748
|
}
|
|
4028
3749
|
}, async (span) => {
|
|
4029
3750
|
try {
|
|
@@ -4118,13 +3839,6 @@ var ContextImpl = class ContextImpl {
|
|
|
4118
3839
|
preferSharedInbox: options.preferSharedInbox,
|
|
4119
3840
|
excludeBaseUris: options.excludeBaseUris
|
|
4120
3841
|
});
|
|
4121
|
-
if (globalThis.Object.keys(inboxes).length < 1) {
|
|
4122
|
-
logger.debug("No inboxes found for activity {activityId}.", {
|
|
4123
|
-
activityId: activity.id?.href,
|
|
4124
|
-
activity
|
|
4125
|
-
});
|
|
4126
|
-
return false;
|
|
4127
|
-
}
|
|
4128
3842
|
logger.debug("Sending activity {activityId} to inboxes:\n{inboxes}", {
|
|
4129
3843
|
inboxes: globalThis.Object.keys(inboxes),
|
|
4130
3844
|
activityId: activity.id?.href,
|
|
@@ -4132,7 +3846,7 @@ var ContextImpl = class ContextImpl {
|
|
|
4132
3846
|
});
|
|
4133
3847
|
if (this.federation.fanoutQueue == null || options.immediate || options.fanout === "skip" || (options.fanout ?? "auto") === "auto" && globalThis.Object.keys(inboxes).length < FANOUT_THRESHOLD) {
|
|
4134
3848
|
await this.federation.sendActivity(keys, inboxes, activity, opts);
|
|
4135
|
-
return
|
|
3849
|
+
return;
|
|
4136
3850
|
}
|
|
4137
3851
|
const keyJwkPairs = await Promise.all(keys.map(async ({ keyId, privateKey }) => ({
|
|
4138
3852
|
keyId: keyId.href,
|
|
@@ -4161,7 +3875,6 @@ var ContextImpl = class ContextImpl {
|
|
|
4161
3875
|
};
|
|
4162
3876
|
if (!this.federation.manuallyStartQueue) this.federation._startQueueInternal(this.data);
|
|
4163
3877
|
await this.federation.fanoutQueue.enqueue(message, { orderingKey: options.orderingKey });
|
|
4164
|
-
return true;
|
|
4165
3878
|
}
|
|
4166
3879
|
async *getFollowers(identifier) {
|
|
4167
3880
|
if (this.federation.followersCallbacks == null) throw new Error("No followers collection dispatcher registered.");
|
|
@@ -4396,170 +4109,6 @@ var RequestContextImpl = class RequestContextImpl extends ContextImpl {
|
|
|
4396
4109
|
}
|
|
4397
4110
|
}
|
|
4398
4111
|
};
|
|
4399
|
-
function forwardActivity(ctx, loggerCategory, forwarder, recipients, options) {
|
|
4400
|
-
return ctx.tracerProvider.getTracer(require_http.name, require_http.version).startActiveSpan(ctx.federation.outboxQueue == null || options?.immediate ? `activitypub.${loggerCategory}` : "activitypub.fanout", {
|
|
4401
|
-
kind: ctx.federation.outboxQueue == null || options?.immediate ? _opentelemetry_api.SpanKind.CLIENT : _opentelemetry_api.SpanKind.PRODUCER,
|
|
4402
|
-
attributes: { "activitypub.activity.type": ctx.activityType }
|
|
4403
|
-
}, async (span) => {
|
|
4404
|
-
try {
|
|
4405
|
-
if (ctx.activityId != null) span.setAttribute("activitypub.activity.id", ctx.activityId);
|
|
4406
|
-
return await forwardActivityInternal(ctx, loggerCategory, forwarder, recipients, options);
|
|
4407
|
-
} catch (e) {
|
|
4408
|
-
span.setStatus({
|
|
4409
|
-
code: _opentelemetry_api.SpanStatusCode.ERROR,
|
|
4410
|
-
message: String(e)
|
|
4411
|
-
});
|
|
4412
|
-
throw e;
|
|
4413
|
-
} finally {
|
|
4414
|
-
span.end();
|
|
4415
|
-
}
|
|
4416
|
-
});
|
|
4417
|
-
}
|
|
4418
|
-
async function forwardActivityInternal(ctx, loggerCategory, forwarder, recipients, options) {
|
|
4419
|
-
const logger = (0, _logtape_logtape.getLogger)([
|
|
4420
|
-
"fedify",
|
|
4421
|
-
"federation",
|
|
4422
|
-
loggerCategory
|
|
4423
|
-
]);
|
|
4424
|
-
let keys;
|
|
4425
|
-
let identifier = null;
|
|
4426
|
-
if ("identifier" in forwarder || "username" in forwarder) {
|
|
4427
|
-
if ("identifier" in forwarder) identifier = forwarder.identifier;
|
|
4428
|
-
else {
|
|
4429
|
-
const username = forwarder.username;
|
|
4430
|
-
if (ctx.federation.actorCallbacks?.handleMapper == null) identifier = username;
|
|
4431
|
-
else {
|
|
4432
|
-
const mapped = await ctx.federation.actorCallbacks.handleMapper(ctx, username);
|
|
4433
|
-
if (mapped == null) throw new Error(`No actor found for the given username ${JSON.stringify(username)}.`);
|
|
4434
|
-
identifier = mapped;
|
|
4435
|
-
}
|
|
4436
|
-
}
|
|
4437
|
-
const actorKeyPairs = await ctx.getActorKeyPairs(identifier);
|
|
4438
|
-
if (actorKeyPairs.length < 1) throw new Error(`No key pair found for actor ${JSON.stringify(identifier)}.`);
|
|
4439
|
-
keys = actorKeyPairs.map((kp) => ({
|
|
4440
|
-
keyId: kp.keyId,
|
|
4441
|
-
privateKey: kp.privateKey
|
|
4442
|
-
}));
|
|
4443
|
-
} else if (Array.isArray(forwarder)) {
|
|
4444
|
-
if (forwarder.length < 1) throw new Error("The forwarder's key pairs are empty.");
|
|
4445
|
-
keys = forwarder;
|
|
4446
|
-
} else keys = [forwarder];
|
|
4447
|
-
if (!require_proof.hasSignatureLike(ctx.activity)) {
|
|
4448
|
-
if (!require_proof.hasProofLike(ctx.activity)) {
|
|
4449
|
-
if (options?.skipIfUnsigned) return false;
|
|
4450
|
-
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.", {
|
|
4451
|
-
activityId: ctx.activityId,
|
|
4452
|
-
activityType: ctx.activityType,
|
|
4453
|
-
identifier: identifier ?? void 0
|
|
4454
|
-
});
|
|
4455
|
-
}
|
|
4456
|
-
}
|
|
4457
|
-
if (recipients === "followers") {
|
|
4458
|
-
if (identifier == null) throw new Error("If recipients is \"followers\", forwarder must be an actor identifier or username.");
|
|
4459
|
-
const followers = [];
|
|
4460
|
-
for await (const recipient of ctx.getFollowers(identifier)) followers.push(recipient);
|
|
4461
|
-
recipients = followers;
|
|
4462
|
-
}
|
|
4463
|
-
const inboxes = extractInboxes({
|
|
4464
|
-
recipients: Array.isArray(recipients) ? recipients : [recipients],
|
|
4465
|
-
preferSharedInbox: options?.preferSharedInbox,
|
|
4466
|
-
excludeBaseUris: options?.excludeBaseUris
|
|
4467
|
-
});
|
|
4468
|
-
if (globalThis.Object.keys(inboxes).length < 1) {
|
|
4469
|
-
logger.debug("No inboxes found for activity {activityId}.", {
|
|
4470
|
-
activityId: ctx.activityId,
|
|
4471
|
-
activityType: ctx.activityType,
|
|
4472
|
-
identifier: identifier ?? void 0
|
|
4473
|
-
});
|
|
4474
|
-
return false;
|
|
4475
|
-
}
|
|
4476
|
-
logger.debug("Forwarding activity {activityId} to inboxes:\n{inboxes}", {
|
|
4477
|
-
inboxes: globalThis.Object.keys(inboxes),
|
|
4478
|
-
activityId: ctx.activityId,
|
|
4479
|
-
activity: ctx.activity
|
|
4480
|
-
});
|
|
4481
|
-
if (options?.immediate || ctx.federation.outboxQueue == null) {
|
|
4482
|
-
if (options?.immediate) logger.debug("Forwarding activity immediately without queue since immediate option is set.");
|
|
4483
|
-
else logger.debug("Forwarding activity immediately without queue since queue is not set.");
|
|
4484
|
-
const promises = [];
|
|
4485
|
-
for (const inbox in inboxes) promises.push(sendActivity({
|
|
4486
|
-
keys,
|
|
4487
|
-
activity: ctx.activity,
|
|
4488
|
-
activityId: ctx.activityId,
|
|
4489
|
-
activityType: ctx.activityType,
|
|
4490
|
-
inbox: new URL(inbox),
|
|
4491
|
-
sharedInbox: inboxes[inbox].sharedInbox,
|
|
4492
|
-
tracerProvider: ctx.tracerProvider,
|
|
4493
|
-
specDeterminer: new KvSpecDeterminer(ctx.federation.kv, ctx.federation.kvPrefixes.httpMessageSignaturesSpec, ctx.federation.firstKnock)
|
|
4494
|
-
}));
|
|
4495
|
-
await Promise.all(promises);
|
|
4496
|
-
return true;
|
|
4497
|
-
}
|
|
4498
|
-
logger.debug("Enqueuing activity {activityId} to forward later.", {
|
|
4499
|
-
activityId: ctx.activityId,
|
|
4500
|
-
activity: ctx.activity
|
|
4501
|
-
});
|
|
4502
|
-
if (!ctx.federation.manuallyStartQueue) ctx.federation._startQueueInternal(ctx.data);
|
|
4503
|
-
const keyJwkPairs = [];
|
|
4504
|
-
for (const { keyId, privateKey } of keys) {
|
|
4505
|
-
const privateKeyJwk = await require_http.exportJwk(privateKey);
|
|
4506
|
-
keyJwkPairs.push({
|
|
4507
|
-
keyId: keyId.href,
|
|
4508
|
-
privateKey: privateKeyJwk
|
|
4509
|
-
});
|
|
4510
|
-
}
|
|
4511
|
-
const carrier = {};
|
|
4512
|
-
_opentelemetry_api.propagation.inject(_opentelemetry_api.context.active(), carrier);
|
|
4513
|
-
const orderingKey = options?.orderingKey;
|
|
4514
|
-
const started = (/* @__PURE__ */ new Date()).toISOString();
|
|
4515
|
-
const messages = [];
|
|
4516
|
-
for (const inbox in inboxes) {
|
|
4517
|
-
const inboxUrl = new URL(inbox);
|
|
4518
|
-
const message = {
|
|
4519
|
-
type: "outbox",
|
|
4520
|
-
id: crypto.randomUUID(),
|
|
4521
|
-
baseUrl: ctx.origin,
|
|
4522
|
-
keys: keyJwkPairs,
|
|
4523
|
-
activity: ctx.activity,
|
|
4524
|
-
activityId: ctx.activityId,
|
|
4525
|
-
activityType: ctx.activityType,
|
|
4526
|
-
inbox,
|
|
4527
|
-
sharedInbox: inboxes[inbox].sharedInbox,
|
|
4528
|
-
actorIds: [...inboxes[inbox].actorIds],
|
|
4529
|
-
started,
|
|
4530
|
-
attempt: 0,
|
|
4531
|
-
headers: {},
|
|
4532
|
-
orderingKey: orderingKey == null ? void 0 : `${orderingKey}\n${inboxUrl.origin}`,
|
|
4533
|
-
traceContext: carrier
|
|
4534
|
-
};
|
|
4535
|
-
messages.push({
|
|
4536
|
-
message,
|
|
4537
|
-
orderingKey: message.orderingKey
|
|
4538
|
-
});
|
|
4539
|
-
}
|
|
4540
|
-
const { outboxQueue } = ctx.federation;
|
|
4541
|
-
if (outboxQueue.enqueueMany == null || orderingKey != null) {
|
|
4542
|
-
const promises = messages.map((m) => outboxQueue.enqueue(m.message, { orderingKey: m.orderingKey }));
|
|
4543
|
-
const errors = (await Promise.allSettled(promises)).filter((r) => r.status === "rejected").map((r) => r.reason);
|
|
4544
|
-
if (errors.length > 0) {
|
|
4545
|
-
logger.error("Failed to enqueue activity {activityId} to forward later:\n{errors}", {
|
|
4546
|
-
activityId: ctx.activityId,
|
|
4547
|
-
errors
|
|
4548
|
-
});
|
|
4549
|
-
if (errors.length > 1) throw new AggregateError(errors, `Failed to enqueue activity ${ctx.activityId} to forward later.`);
|
|
4550
|
-
throw errors[0];
|
|
4551
|
-
}
|
|
4552
|
-
} else try {
|
|
4553
|
-
await outboxQueue.enqueueMany(messages.map((m) => m.message));
|
|
4554
|
-
} catch (error) {
|
|
4555
|
-
logger.error("Failed to enqueue activity {activityId} to forward later:\n{error}", {
|
|
4556
|
-
activityId: ctx.activityId,
|
|
4557
|
-
error
|
|
4558
|
-
});
|
|
4559
|
-
throw error;
|
|
4560
|
-
}
|
|
4561
|
-
return true;
|
|
4562
|
-
}
|
|
4563
4112
|
var InboxContextImpl = class InboxContextImpl extends ContextImpl {
|
|
4564
4113
|
recipient;
|
|
4565
4114
|
activity;
|
|
@@ -4583,40 +4132,13 @@ var InboxContextImpl = class InboxContextImpl extends ContextImpl {
|
|
|
4583
4132
|
});
|
|
4584
4133
|
}
|
|
4585
4134
|
forwardActivity(forwarder, recipients, options) {
|
|
4586
|
-
return
|
|
4587
|
-
|
|
4588
|
-
}
|
|
4589
|
-
var OutboxContextImpl = class OutboxContextImpl extends ContextImpl {
|
|
4590
|
-
#deliveryState;
|
|
4591
|
-
identifier;
|
|
4592
|
-
activity;
|
|
4593
|
-
activityId;
|
|
4594
|
-
activityType;
|
|
4595
|
-
constructor(identifier, activity, activityId, activityType, options, deliveryState = { delivered: false }) {
|
|
4596
|
-
super(options);
|
|
4597
|
-
this.#deliveryState = deliveryState;
|
|
4598
|
-
this.identifier = identifier;
|
|
4599
|
-
this.activity = activity;
|
|
4600
|
-
this.activityId = activityId;
|
|
4601
|
-
this.activityType = activityType;
|
|
4602
|
-
}
|
|
4603
|
-
hasDeliveredActivity() {
|
|
4604
|
-
return this.#deliveryState.delivered;
|
|
4605
|
-
}
|
|
4606
|
-
sendActivity(sender, recipients, activity, options = {}) {
|
|
4607
|
-
return this.tracerProvider.getTracer(require_http.name, require_http.version).startActiveSpan(this.federation.outboxQueue == null || options.immediate ? "activitypub.outbox" : "activitypub.fanout", {
|
|
4608
|
-
kind: this.federation.outboxQueue == null || options.immediate ? _opentelemetry_api.SpanKind.CLIENT : _opentelemetry_api.SpanKind.PRODUCER,
|
|
4609
|
-
attributes: {
|
|
4610
|
-
"activitypub.activity.type": (0, _fedify_vocab.getTypeId)(activity).href,
|
|
4611
|
-
"activitypub.activity.to": activity.toIds.map((to) => to.href),
|
|
4612
|
-
"activitypub.activity.cc": activity.ccIds.map((cc) => cc.href),
|
|
4613
|
-
"activitypub.activity.bto": activity.btoIds.map((bto) => bto.href),
|
|
4614
|
-
"activitypub.activity.bcc": activity.bccIds.map((bcc) => bcc.href)
|
|
4615
|
-
}
|
|
4135
|
+
return this.tracerProvider.getTracer(require_http.name, require_http.version).startActiveSpan("activitypub.outbox", {
|
|
4136
|
+
kind: this.federation.outboxQueue == null || options?.immediate ? _opentelemetry_api.SpanKind.CLIENT : _opentelemetry_api.SpanKind.PRODUCER,
|
|
4137
|
+
attributes: { "activitypub.activity.type": this.activityType }
|
|
4616
4138
|
}, async (span) => {
|
|
4617
4139
|
try {
|
|
4618
|
-
if (
|
|
4619
|
-
|
|
4140
|
+
if (this.activityId != null) span.setAttribute("activitypub.activity.id", this.activityId);
|
|
4141
|
+
await this.forwardActivityInternal(forwarder, recipients, options);
|
|
4620
4142
|
} catch (e) {
|
|
4621
4143
|
span.setStatus({
|
|
4622
4144
|
code: _opentelemetry_api.SpanStatusCode.ERROR,
|
|
@@ -4628,20 +4150,151 @@ var OutboxContextImpl = class OutboxContextImpl extends ContextImpl {
|
|
|
4628
4150
|
}
|
|
4629
4151
|
});
|
|
4630
4152
|
}
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4153
|
+
async forwardActivityInternal(forwarder, recipients, options) {
|
|
4154
|
+
const logger = (0, _logtape_logtape.getLogger)([
|
|
4155
|
+
"fedify",
|
|
4156
|
+
"federation",
|
|
4157
|
+
"inbox"
|
|
4158
|
+
]);
|
|
4159
|
+
let keys;
|
|
4160
|
+
let identifier = null;
|
|
4161
|
+
if ("identifier" in forwarder || "username" in forwarder) {
|
|
4162
|
+
if ("identifier" in forwarder) identifier = forwarder.identifier;
|
|
4163
|
+
else {
|
|
4164
|
+
const username = forwarder.username;
|
|
4165
|
+
if (this.federation.actorCallbacks?.handleMapper == null) identifier = username;
|
|
4166
|
+
else {
|
|
4167
|
+
const mapped = await this.federation.actorCallbacks.handleMapper(this, username);
|
|
4168
|
+
if (mapped == null) throw new Error(`No actor found for the given username ${JSON.stringify(username)}.`);
|
|
4169
|
+
identifier = mapped;
|
|
4170
|
+
}
|
|
4171
|
+
}
|
|
4172
|
+
const actorKeyPairs = await this.getActorKeyPairs(identifier);
|
|
4173
|
+
if (actorKeyPairs.length < 1) throw new Error(`No key pair found for actor ${JSON.stringify(identifier)}.`);
|
|
4174
|
+
keys = actorKeyPairs.map((kp) => ({
|
|
4175
|
+
keyId: kp.keyId,
|
|
4176
|
+
privateKey: kp.privateKey
|
|
4177
|
+
}));
|
|
4178
|
+
} else if (Array.isArray(forwarder)) {
|
|
4179
|
+
if (forwarder.length < 1) throw new Error("The forwarder's key pairs are empty.");
|
|
4180
|
+
keys = forwarder;
|
|
4181
|
+
} else keys = [forwarder];
|
|
4182
|
+
if (!require_proof.hasSignature(this.activity)) {
|
|
4183
|
+
let hasProof;
|
|
4184
|
+
try {
|
|
4185
|
+
hasProof = await (await _fedify_vocab.Activity.fromJsonLd(this.activity, this)).getProof() != null;
|
|
4186
|
+
} catch {
|
|
4187
|
+
hasProof = false;
|
|
4188
|
+
}
|
|
4189
|
+
if (!hasProof) {
|
|
4190
|
+
if (options?.skipIfUnsigned) return;
|
|
4191
|
+
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.");
|
|
4192
|
+
}
|
|
4193
|
+
}
|
|
4194
|
+
if (recipients === "followers") {
|
|
4195
|
+
if (identifier == null) throw new Error("If recipients is \"followers\", forwarder must be an actor identifier or username.");
|
|
4196
|
+
const followers = [];
|
|
4197
|
+
for await (const recipient of this.getFollowers(identifier)) followers.push(recipient);
|
|
4198
|
+
recipients = followers;
|
|
4199
|
+
}
|
|
4200
|
+
const inboxes = extractInboxes({
|
|
4201
|
+
recipients: Array.isArray(recipients) ? recipients : [recipients],
|
|
4202
|
+
preferSharedInbox: options?.preferSharedInbox,
|
|
4203
|
+
excludeBaseUris: options?.excludeBaseUris
|
|
4634
4204
|
});
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
4205
|
+
logger.debug("Forwarding activity {activityId} to inboxes:\n{inboxes}", {
|
|
4206
|
+
inboxes: globalThis.Object.keys(inboxes),
|
|
4207
|
+
activityId: this.activityId,
|
|
4208
|
+
activity: this.activity
|
|
4209
|
+
});
|
|
4210
|
+
if (options?.immediate || this.federation.outboxQueue == null) {
|
|
4211
|
+
if (options?.immediate) logger.debug("Forwarding activity immediately without queue since immediate option is set.");
|
|
4212
|
+
else logger.debug("Forwarding activity immediately without queue since queue is not set.");
|
|
4213
|
+
const promises = [];
|
|
4214
|
+
for (const inbox in inboxes) promises.push(sendActivity({
|
|
4215
|
+
keys,
|
|
4216
|
+
activity: this.activity,
|
|
4217
|
+
activityId: this.activityId,
|
|
4218
|
+
activityType: this.activityType,
|
|
4219
|
+
inbox: new URL(inbox),
|
|
4220
|
+
sharedInbox: inboxes[inbox].sharedInbox,
|
|
4221
|
+
tracerProvider: this.tracerProvider,
|
|
4222
|
+
specDeterminer: new KvSpecDeterminer(this.federation.kv, this.federation.kvPrefixes.httpMessageSignaturesSpec, this.federation.firstKnock)
|
|
4223
|
+
}));
|
|
4224
|
+
await Promise.all(promises);
|
|
4225
|
+
return;
|
|
4226
|
+
}
|
|
4227
|
+
logger.debug("Enqueuing activity {activityId} to forward later.", {
|
|
4228
|
+
activityId: this.activityId,
|
|
4229
|
+
activity: this.activity
|
|
4230
|
+
});
|
|
4231
|
+
const keyJwkPairs = [];
|
|
4232
|
+
for (const { keyId, privateKey } of keys) {
|
|
4233
|
+
const privateKeyJwk = await require_http.exportJwk(privateKey);
|
|
4234
|
+
keyJwkPairs.push({
|
|
4235
|
+
keyId: keyId.href,
|
|
4236
|
+
privateKey: privateKeyJwk
|
|
4237
|
+
});
|
|
4238
|
+
}
|
|
4239
|
+
const carrier = {};
|
|
4240
|
+
_opentelemetry_api.propagation.inject(_opentelemetry_api.context.active(), carrier);
|
|
4241
|
+
const orderingKey = options?.orderingKey;
|
|
4242
|
+
const messages = [];
|
|
4243
|
+
for (const inbox in inboxes) {
|
|
4244
|
+
const inboxUrl = new URL(inbox);
|
|
4245
|
+
const message = {
|
|
4246
|
+
type: "outbox",
|
|
4247
|
+
id: crypto.randomUUID(),
|
|
4248
|
+
baseUrl: this.origin,
|
|
4249
|
+
keys: keyJwkPairs,
|
|
4250
|
+
activity: this.activity,
|
|
4251
|
+
activityId: this.activityId,
|
|
4252
|
+
activityType: this.activityType,
|
|
4253
|
+
inbox,
|
|
4254
|
+
sharedInbox: inboxes[inbox].sharedInbox,
|
|
4255
|
+
started: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4256
|
+
attempt: 0,
|
|
4257
|
+
headers: {},
|
|
4258
|
+
orderingKey: orderingKey == null ? void 0 : `${orderingKey}\n${inboxUrl.origin}`,
|
|
4259
|
+
traceContext: carrier
|
|
4260
|
+
};
|
|
4261
|
+
messages.push({
|
|
4262
|
+
message,
|
|
4263
|
+
orderingKey: message.orderingKey
|
|
4264
|
+
});
|
|
4265
|
+
}
|
|
4266
|
+
const { outboxQueue } = this.federation;
|
|
4267
|
+
if (outboxQueue.enqueueMany == null) {
|
|
4268
|
+
const promises = messages.map((m) => outboxQueue.enqueue(m.message, { orderingKey: m.orderingKey }));
|
|
4269
|
+
const errors = (await Promise.allSettled(promises)).filter((r) => r.status === "rejected").map((r) => r.reason);
|
|
4270
|
+
if (errors.length > 0) {
|
|
4271
|
+
logger.error("Failed to enqueue activity {activityId} to forward later:\n{errors}", {
|
|
4272
|
+
activityId: this.activityId,
|
|
4273
|
+
errors
|
|
4274
|
+
});
|
|
4275
|
+
if (errors.length > 1) throw new AggregateError(errors, `Failed to enqueue activity ${this.activityId} to forward later.`);
|
|
4276
|
+
throw errors[0];
|
|
4277
|
+
}
|
|
4278
|
+
} else if (orderingKey != null) {
|
|
4279
|
+
const promises = messages.map((m) => outboxQueue.enqueue(m.message, { orderingKey: m.orderingKey }));
|
|
4280
|
+
const errors = (await Promise.allSettled(promises)).filter((r) => r.status === "rejected").map((r) => r.reason);
|
|
4281
|
+
if (errors.length > 0) {
|
|
4282
|
+
logger.error("Failed to enqueue activity {activityId} to forward later:\n{errors}", {
|
|
4283
|
+
activityId: this.activityId,
|
|
4284
|
+
errors
|
|
4285
|
+
});
|
|
4286
|
+
if (errors.length > 1) throw new AggregateError(errors, `Failed to enqueue activity ${this.activityId} to forward later.`);
|
|
4287
|
+
throw errors[0];
|
|
4288
|
+
}
|
|
4289
|
+
} else try {
|
|
4290
|
+
await outboxQueue.enqueueMany(messages.map((m) => m.message));
|
|
4291
|
+
} catch (error) {
|
|
4292
|
+
logger.error("Failed to enqueue activity {activityId} to forward later:\n{error}", {
|
|
4293
|
+
activityId: this.activityId,
|
|
4294
|
+
error
|
|
4295
|
+
});
|
|
4296
|
+
throw error;
|
|
4297
|
+
}
|
|
4645
4298
|
}
|
|
4646
4299
|
};
|
|
4647
4300
|
var KvSpecDeterminer = class {
|
|
@@ -4718,12 +4371,6 @@ Object.defineProperty(exports, "KvSpecDeterminer", {
|
|
|
4718
4371
|
return KvSpecDeterminer;
|
|
4719
4372
|
}
|
|
4720
4373
|
});
|
|
4721
|
-
Object.defineProperty(exports, "OutboxContextImpl", {
|
|
4722
|
-
enumerable: true,
|
|
4723
|
-
get: function() {
|
|
4724
|
-
return OutboxContextImpl;
|
|
4725
|
-
}
|
|
4726
|
-
});
|
|
4727
4374
|
Object.defineProperty(exports, "Router", {
|
|
4728
4375
|
enumerable: true,
|
|
4729
4376
|
get: function() {
|