@fedify/fedify 1.3.0-dev.571 → 1.3.0-dev.576
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/CHANGES.md +4 -0
- package/esm/deno.js +1 -1
- package/esm/federation/handler.js +155 -59
- package/esm/federation/inbox.js +5 -2
- package/esm/federation/middleware.js +243 -92
- package/esm/federation/send.js +42 -7
- package/esm/vocab/vocab.js +173 -173
- package/package.json +1 -1
- package/types/federation/handler.d.ts +2 -2
- package/types/federation/handler.d.ts.map +1 -1
- package/types/federation/inbox.d.ts +4 -0
- package/types/federation/inbox.d.ts.map +1 -1
- package/types/federation/middleware.d.ts +14 -4
- package/types/federation/middleware.d.ts.map +1 -1
- package/types/federation/mod.d.ts +1 -1
- package/types/federation/mod.d.ts.map +1 -1
- package/types/federation/queue.d.ts +4 -0
- package/types/federation/queue.d.ts.map +1 -1
- package/types/federation/send.d.ts +16 -3
- package/types/federation/send.d.ts.map +1 -1
- package/types/sig/mod.d.ts +1 -1
- package/types/sig/mod.d.ts.map +1 -1
@@ -1,6 +1,6 @@
|
|
1
1
|
import * as dntShim from "../_dnt.shims.js";
|
2
2
|
import { getLogger, withContext } from "@logtape/logtape";
|
3
|
-
import { SpanKind, SpanStatusCode, trace, } from "@opentelemetry/api";
|
3
|
+
import { context, propagation, SpanKind, SpanStatusCode, trace, } from "@opentelemetry/api";
|
4
4
|
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";
|
5
5
|
import metadata from "../deno.js";
|
6
6
|
import { handleNodeInfo, handleNodeInfoJrd } from "../nodeinfo/handler.js";
|
@@ -157,16 +157,60 @@ export class FederationImpl {
|
|
157
157
|
await Promise.all(promises);
|
158
158
|
}
|
159
159
|
#listenQueue(ctxData, message) {
|
160
|
+
const tracer = this.#getTracer();
|
161
|
+
const extractedContext = propagation.extract(context.active(), message.traceContext);
|
160
162
|
return withContext({ messageId: message.id }, async () => {
|
161
163
|
if (message.type === "outbox") {
|
162
|
-
await
|
164
|
+
await tracer.startActiveSpan("activitypub.outbox", {
|
165
|
+
kind: SpanKind.CONSUMER,
|
166
|
+
attributes: {
|
167
|
+
"activitypub.activity.type": message.activityType,
|
168
|
+
"activitypub.activity.retries": message.attempt,
|
169
|
+
},
|
170
|
+
}, extractedContext, async (span) => {
|
171
|
+
if (message.activityId != null) {
|
172
|
+
span.setAttribute("activitypub.activity.id", message.activityId);
|
173
|
+
}
|
174
|
+
try {
|
175
|
+
await this.#listenOutboxMessage(ctxData, message, span);
|
176
|
+
}
|
177
|
+
catch (e) {
|
178
|
+
span.setStatus({
|
179
|
+
code: SpanStatusCode.ERROR,
|
180
|
+
message: String(e),
|
181
|
+
});
|
182
|
+
throw e;
|
183
|
+
}
|
184
|
+
finally {
|
185
|
+
span.end();
|
186
|
+
}
|
187
|
+
});
|
163
188
|
}
|
164
189
|
else if (message.type === "inbox") {
|
165
|
-
await
|
190
|
+
await tracer.startActiveSpan("activitypub.inbox", {
|
191
|
+
kind: SpanKind.CONSUMER,
|
192
|
+
attributes: {
|
193
|
+
"activitypub.shared_inbox": message.identifier == null,
|
194
|
+
},
|
195
|
+
}, extractedContext, async (span) => {
|
196
|
+
try {
|
197
|
+
await this.#listenInboxMessage(ctxData, message, span);
|
198
|
+
}
|
199
|
+
catch (e) {
|
200
|
+
span.setStatus({
|
201
|
+
code: SpanStatusCode.ERROR,
|
202
|
+
message: String(e),
|
203
|
+
});
|
204
|
+
throw e;
|
205
|
+
}
|
206
|
+
finally {
|
207
|
+
span.end();
|
208
|
+
}
|
209
|
+
});
|
166
210
|
}
|
167
211
|
});
|
168
212
|
}
|
169
|
-
async #listenOutboxMessage(_, message) {
|
213
|
+
async #listenOutboxMessage(_, message, span) {
|
170
214
|
const logger = getLogger(["fedify", "federation", "outbox"]);
|
171
215
|
const logData = {
|
172
216
|
keyIds: message.keys.map((pair) => pair.keyId),
|
@@ -194,12 +238,15 @@ export class FederationImpl {
|
|
194
238
|
keys,
|
195
239
|
activity: message.activity,
|
196
240
|
activityId: message.activityId,
|
241
|
+
activityType: message.activityType,
|
197
242
|
inbox: new URL(message.inbox),
|
243
|
+
sharedInbox: message.sharedInbox,
|
198
244
|
headers: new Headers(message.headers),
|
199
245
|
tracerProvider: this.tracerProvider,
|
200
246
|
});
|
201
247
|
}
|
202
248
|
catch (error) {
|
249
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: String(error) });
|
203
250
|
const activity = await Activity.fromJsonLd(message.activity, {
|
204
251
|
contextLoader: this.contextLoader,
|
205
252
|
documentLoader: rsaKeyPair == null
|
@@ -220,7 +267,7 @@ export class FederationImpl {
|
|
220
267
|
if (delay != null) {
|
221
268
|
logger.error("Failed to send activity {activityId} to {inbox} (attempt " +
|
222
269
|
"#{attempt}); retry...:\n{error}", { ...logData, error });
|
223
|
-
this.outboxQueue?.enqueue({
|
270
|
+
await this.outboxQueue?.enqueue({
|
224
271
|
...message,
|
225
272
|
attempt: message.attempt + 1,
|
226
273
|
}, {
|
@@ -237,7 +284,7 @@ export class FederationImpl {
|
|
237
284
|
}
|
238
285
|
logger.info("Successfully sent activity {activityId} to {inbox}.", { ...logData });
|
239
286
|
}
|
240
|
-
async #listenInboxMessage(ctxData, message) {
|
287
|
+
async #listenInboxMessage(ctxData, message, span) {
|
241
288
|
const logger = getLogger(["fedify", "federation", "inbox"]);
|
242
289
|
const baseUrl = new URL(message.baseUrl);
|
243
290
|
let context = this.#createContext(baseUrl, ctxData);
|
@@ -260,6 +307,10 @@ export class FederationImpl {
|
|
260
307
|
}
|
261
308
|
}
|
262
309
|
const activity = await Activity.fromJsonLd(message.activity, context);
|
310
|
+
span.setAttribute("activitypub.activity.type", getTypeId(activity).href);
|
311
|
+
if (activity.id != null) {
|
312
|
+
span.setAttribute("activitypub.activity.id", activity.id.href);
|
313
|
+
}
|
263
314
|
const cacheKey = activity.id == null ? null : [
|
264
315
|
...this.kvPrefixes.activityIdempotence,
|
265
316
|
activity.id.href,
|
@@ -275,74 +326,89 @@ export class FederationImpl {
|
|
275
326
|
return;
|
276
327
|
}
|
277
328
|
}
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
activity: message.activity,
|
283
|
-
recipient: message.identifier,
|
284
|
-
trial: message.attempt,
|
285
|
-
});
|
286
|
-
return;
|
287
|
-
}
|
288
|
-
try {
|
289
|
-
await listener(context.toInboxContext(message.identifier, message.activity), activity);
|
290
|
-
}
|
291
|
-
catch (error) {
|
292
|
-
try {
|
293
|
-
await this.inboxErrorHandler?.(context, error);
|
294
|
-
}
|
295
|
-
catch (error) {
|
296
|
-
logger.error("An unexpected error occurred in inbox error handler:\n{error}", {
|
297
|
-
error,
|
298
|
-
trial: message.attempt,
|
329
|
+
await this.#getTracer().startActiveSpan("activitypub.dispatch_inbox_listener", { kind: SpanKind.INTERNAL }, async (span) => {
|
330
|
+
const dispatched = this.inboxListeners?.dispatchWithClass(activity);
|
331
|
+
if (dispatched == null) {
|
332
|
+
logger.error("Unsupported activity type:\n{activity}", {
|
299
333
|
activityId: activity.id?.href,
|
300
334
|
activity: message.activity,
|
301
335
|
recipient: message.identifier,
|
336
|
+
trial: message.attempt,
|
302
337
|
});
|
338
|
+
span.setStatus({
|
339
|
+
code: SpanStatusCode.ERROR,
|
340
|
+
message: `Unsupported activity type: ${getTypeId(activity).href}`,
|
341
|
+
});
|
342
|
+
span.end();
|
343
|
+
return;
|
303
344
|
}
|
304
|
-
const
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
error
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
345
|
+
const { class: cls, listener } = dispatched;
|
346
|
+
span.updateName(`activitypub.dispatch_inbox_listener ${cls.name}`);
|
347
|
+
try {
|
348
|
+
await listener(context.toInboxContext(message.identifier, message.activity, activity.id?.href, getTypeId(activity).href), activity);
|
349
|
+
}
|
350
|
+
catch (error) {
|
351
|
+
try {
|
352
|
+
await this.inboxErrorHandler?.(context, error);
|
353
|
+
}
|
354
|
+
catch (error) {
|
355
|
+
logger.error("An unexpected error occurred in inbox error handler:\n{error}", {
|
356
|
+
error,
|
357
|
+
trial: message.attempt,
|
358
|
+
activityId: activity.id?.href,
|
359
|
+
activity: message.activity,
|
360
|
+
recipient: message.identifier,
|
361
|
+
});
|
362
|
+
}
|
363
|
+
const delay = this.inboxRetryPolicy({
|
364
|
+
elapsedTime: dntShim.Temporal.Instant.from(message.started).until(dntShim.Temporal.Now.instant()),
|
365
|
+
attempts: message.attempt,
|
316
366
|
});
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
:
|
367
|
+
if (delay != null) {
|
368
|
+
logger.error("Failed to process the incoming activity {activityId} (attempt " +
|
369
|
+
"#{attempt}); retry...:\n{error}", {
|
370
|
+
error,
|
371
|
+
attempt: message.attempt,
|
372
|
+
activityId: activity.id?.href,
|
373
|
+
activity: message.activity,
|
374
|
+
recipient: message.identifier,
|
375
|
+
});
|
376
|
+
await this.inboxQueue?.enqueue({
|
377
|
+
...message,
|
378
|
+
attempt: message.attempt + 1,
|
379
|
+
}, {
|
380
|
+
delay: dntShim.Temporal.Duration.compare(delay, { seconds: 0 }) < 0
|
381
|
+
? dntShim.Temporal.Duration.from({ seconds: 0 })
|
382
|
+
: delay,
|
383
|
+
});
|
384
|
+
}
|
385
|
+
else {
|
386
|
+
logger.error("Failed to process the incoming activity {activityId} after " +
|
387
|
+
"{trial} attempts; giving up:\n{error}", {
|
388
|
+
error,
|
389
|
+
activityId: activity.id?.href,
|
390
|
+
activity: message.activity,
|
391
|
+
recipient: message.identifier,
|
392
|
+
});
|
393
|
+
}
|
394
|
+
span.setStatus({
|
395
|
+
code: SpanStatusCode.ERROR,
|
396
|
+
message: String(error),
|
324
397
|
});
|
398
|
+
span.end();
|
399
|
+
return;
|
325
400
|
}
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
error,
|
330
|
-
activityId: activity.id?.href,
|
331
|
-
activity: message.activity,
|
332
|
-
recipient: message.identifier,
|
401
|
+
if (cacheKey != null) {
|
402
|
+
await this.kv.set(cacheKey, true, {
|
403
|
+
ttl: dntShim.Temporal.Duration.from({ days: 1 }),
|
333
404
|
});
|
334
405
|
}
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
ttl: dntShim.Temporal.Duration.from({ days: 1 }),
|
406
|
+
logger.info("Activity {activityId} has been processed.", {
|
407
|
+
activityId: activity.id?.href,
|
408
|
+
activity: message.activity,
|
409
|
+
recipient: message.identifier,
|
340
410
|
});
|
341
|
-
|
342
|
-
logger.info("Activity {activityId} has been processed.", {
|
343
|
-
activityId: activity.id?.href,
|
344
|
-
activity: message.activity,
|
345
|
-
recipient: message.identifier,
|
411
|
+
span.end();
|
346
412
|
});
|
347
413
|
}
|
348
414
|
startQueue(contextData, options = {}) {
|
@@ -960,7 +1026,7 @@ export class FederationImpl {
|
|
960
1026
|
};
|
961
1027
|
return setters;
|
962
1028
|
}
|
963
|
-
async sendActivity(keys, recipients, activity, options) {
|
1029
|
+
async sendActivity(keys, recipients, activity, options, span) {
|
964
1030
|
const logger = getLogger(["fedify", "federation", "outbox"]);
|
965
1031
|
const { preferSharedInbox, immediate, excludeBaseUris, collectionSync, contextData, } = options;
|
966
1032
|
if (keys.length < 1) {
|
@@ -974,9 +1040,9 @@ export class FederationImpl {
|
|
974
1040
|
throw new TypeError("The activity to send must have at least one actor property.");
|
975
1041
|
}
|
976
1042
|
if (activity.id == null) {
|
977
|
-
|
978
|
-
|
979
|
-
|
1043
|
+
const id = new URL(`urn:uuid:${dntShim.crypto.randomUUID()}`);
|
1044
|
+
activity = activity.clone({ id });
|
1045
|
+
span?.setAttribute("activitypub.activity.id", id.href);
|
980
1046
|
}
|
981
1047
|
const inboxes = extractInboxes({
|
982
1048
|
recipients: Array.isArray(recipients) ? recipients : [recipients],
|
@@ -1061,9 +1127,11 @@ export class FederationImpl {
|
|
1061
1127
|
keys,
|
1062
1128
|
activity: jsonLd,
|
1063
1129
|
activityId: activity.id?.href,
|
1130
|
+
activityType: getTypeId(activity).href,
|
1064
1131
|
inbox: new URL(inbox),
|
1132
|
+
sharedInbox: inboxes[inbox].sharedInbox,
|
1065
1133
|
headers: collectionSync == null ? undefined : new Headers({
|
1066
|
-
"Collection-Synchronization": await buildCollectionSynchronizationHeader(collectionSync, inboxes[inbox]),
|
1134
|
+
"Collection-Synchronization": await buildCollectionSynchronizationHeader(collectionSync, inboxes[inbox].actorIds),
|
1067
1135
|
}),
|
1068
1136
|
tracerProvider: this.tracerProvider,
|
1069
1137
|
}));
|
@@ -1079,6 +1147,9 @@ export class FederationImpl {
|
|
1079
1147
|
}
|
1080
1148
|
if (!this.manuallyStartQueue)
|
1081
1149
|
this.#startQueue(contextData);
|
1150
|
+
const carrier = {};
|
1151
|
+
propagation.inject(context.active(), carrier);
|
1152
|
+
const promises = [];
|
1082
1153
|
for (const inbox in inboxes) {
|
1083
1154
|
const message = {
|
1084
1155
|
type: "outbox",
|
@@ -1086,14 +1157,28 @@ export class FederationImpl {
|
|
1086
1157
|
keys: keyJwkPairs,
|
1087
1158
|
activity: jsonLd,
|
1088
1159
|
activityId: activity.id?.href,
|
1160
|
+
activityType: getTypeId(activity).href,
|
1089
1161
|
inbox,
|
1162
|
+
sharedInbox: inboxes[inbox].sharedInbox,
|
1090
1163
|
started: new Date().toISOString(),
|
1091
1164
|
attempt: 0,
|
1092
1165
|
headers: collectionSync == null ? {} : {
|
1093
|
-
"Collection-Synchronization": await buildCollectionSynchronizationHeader(collectionSync, inboxes[inbox]),
|
1166
|
+
"Collection-Synchronization": await buildCollectionSynchronizationHeader(collectionSync, inboxes[inbox].actorIds),
|
1094
1167
|
},
|
1168
|
+
traceContext: carrier,
|
1095
1169
|
};
|
1096
|
-
this.outboxQueue.enqueue(message);
|
1170
|
+
promises.push(this.outboxQueue.enqueue(message));
|
1171
|
+
}
|
1172
|
+
const results = await Promise.allSettled(promises);
|
1173
|
+
const errors = results
|
1174
|
+
.filter((r) => r.status === "rejected")
|
1175
|
+
.map((r) => r.reason);
|
1176
|
+
if (errors.length > 0) {
|
1177
|
+
logger.error("Failed to enqueue activity {activityId} to send later: {errors}", { activityId: activity.id.href, errors });
|
1178
|
+
if (errors.length > 1) {
|
1179
|
+
throw new AggregateError(errors, `Failed to enqueue activity ${activityId} to send later.`);
|
1180
|
+
}
|
1181
|
+
throw errors[0];
|
1097
1182
|
}
|
1098
1183
|
}
|
1099
1184
|
fetch(request, options) {
|
@@ -1368,8 +1453,8 @@ export class ContextImpl {
|
|
1368
1453
|
this.invokedFromActorKeyPairsDispatcher =
|
1369
1454
|
invokedFromActorKeyPairsDispatcher;
|
1370
1455
|
}
|
1371
|
-
toInboxContext(recipient, activity) {
|
1372
|
-
return new InboxContextImpl(recipient, activity, {
|
1456
|
+
toInboxContext(recipient, activity, activityId, activityType) {
|
1457
|
+
return new InboxContextImpl(recipient, activity, activityId, activityType, {
|
1373
1458
|
url: this.url,
|
1374
1459
|
federation: this.federation,
|
1375
1460
|
data: this.data,
|
@@ -1738,7 +1823,36 @@ export class ContextImpl {
|
|
1738
1823
|
contextLoader: options.contextLoader ?? this.contextLoader,
|
1739
1824
|
});
|
1740
1825
|
}
|
1741
|
-
|
1826
|
+
sendActivity(sender, recipients, activity, options = {}) {
|
1827
|
+
const tracer = this.tracerProvider.getTracer(metadata.name, metadata.version);
|
1828
|
+
return tracer.startActiveSpan("activitypub.outbox", {
|
1829
|
+
kind: this.federation.outboxQueue == null || options.immediate
|
1830
|
+
? SpanKind.CLIENT
|
1831
|
+
: SpanKind.PRODUCER,
|
1832
|
+
attributes: {
|
1833
|
+
"activitypub.activity.type": getTypeId(activity).href,
|
1834
|
+
"activitypub.activity.to": activity.toIds.map((to) => to.href),
|
1835
|
+
"activitypub.activity.cc": activity.toIds.map((cc) => cc.href),
|
1836
|
+
"activitypub.activity.bto": activity.btoIds.map((bto) => bto.href),
|
1837
|
+
"activitypub.activity.bcc": activity.toIds.map((bcc) => bcc.href),
|
1838
|
+
},
|
1839
|
+
}, async (span) => {
|
1840
|
+
try {
|
1841
|
+
if (activity.id != null) {
|
1842
|
+
span.setAttribute("activitypub.activity.id", activity.id.href);
|
1843
|
+
}
|
1844
|
+
await this.sendActivityInternal(sender, recipients, activity, options, span);
|
1845
|
+
}
|
1846
|
+
catch (e) {
|
1847
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: String(e) });
|
1848
|
+
throw e;
|
1849
|
+
}
|
1850
|
+
finally {
|
1851
|
+
span.end();
|
1852
|
+
}
|
1853
|
+
});
|
1854
|
+
}
|
1855
|
+
async sendActivityInternal(sender, recipients, activity, options = {}, span) {
|
1742
1856
|
let keys;
|
1743
1857
|
let identifier = null;
|
1744
1858
|
if ("identifier" in sender || "username" in sender || "handle" in sender) {
|
@@ -1766,6 +1880,7 @@ export class ContextImpl {
|
|
1766
1880
|
identifier = mapped;
|
1767
1881
|
}
|
1768
1882
|
}
|
1883
|
+
span.setAttribute("fedify.actor.identifier", identifier);
|
1769
1884
|
keys = await this.getKeyPairsFromIdentifier(identifier);
|
1770
1885
|
if (keys.length < 1) {
|
1771
1886
|
throw new Error(`No key pair found for actor ${JSON.stringify(identifier)}.`);
|
@@ -1805,7 +1920,8 @@ export class ContextImpl {
|
|
1805
1920
|
else {
|
1806
1921
|
expandedRecipients = [recipients];
|
1807
1922
|
}
|
1808
|
-
|
1923
|
+
span.setAttribute("activitypub.inboxes", expandedRecipients.length);
|
1924
|
+
return await this.federation.sendActivity(keys, expandedRecipients, activity, opts, span);
|
1809
1925
|
}
|
1810
1926
|
async *getFollowers(identifier) {
|
1811
1927
|
if (this.federation.followersCallbacks == null) {
|
@@ -1912,12 +2028,39 @@ class RequestContextImpl extends ContextImpl {
|
|
1912
2028
|
export class InboxContextImpl extends ContextImpl {
|
1913
2029
|
recipient;
|
1914
2030
|
activity;
|
1915
|
-
|
2031
|
+
activityId;
|
2032
|
+
activityType;
|
2033
|
+
constructor(recipient, activity, activityId, activityType, options) {
|
1916
2034
|
super(options);
|
1917
2035
|
this.recipient = recipient;
|
1918
2036
|
this.activity = activity;
|
2037
|
+
this.activityId = activityId;
|
2038
|
+
this.activityType = activityType;
|
2039
|
+
}
|
2040
|
+
forwardActivity(forwarder, recipients, options) {
|
2041
|
+
const tracer = this.tracerProvider.getTracer(metadata.name, metadata.version);
|
2042
|
+
return tracer.startActiveSpan("activitypub.outbox", {
|
2043
|
+
kind: this.federation.outboxQueue == null || options?.immediate
|
2044
|
+
? SpanKind.CLIENT
|
2045
|
+
: SpanKind.PRODUCER,
|
2046
|
+
attributes: { "activitypub.activity.type": this.activityType },
|
2047
|
+
}, async (span) => {
|
2048
|
+
try {
|
2049
|
+
if (this.activityId != null) {
|
2050
|
+
span.setAttribute("activitypub.activity.id", this.activityId);
|
2051
|
+
}
|
2052
|
+
await this.forwardActivityInternal(forwarder, recipients, options);
|
2053
|
+
}
|
2054
|
+
catch (e) {
|
2055
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: String(e) });
|
2056
|
+
throw e;
|
2057
|
+
}
|
2058
|
+
finally {
|
2059
|
+
span.end();
|
2060
|
+
}
|
2061
|
+
});
|
1919
2062
|
}
|
1920
|
-
async
|
2063
|
+
async forwardActivityInternal(forwarder, recipients, options) {
|
1921
2064
|
const logger = getLogger(["fedify", "federation", "inbox"]);
|
1922
2065
|
let keys;
|
1923
2066
|
let identifier = null;
|
@@ -1961,12 +2104,10 @@ export class InboxContextImpl extends ContextImpl {
|
|
1961
2104
|
else {
|
1962
2105
|
keys = [forwarder];
|
1963
2106
|
}
|
1964
|
-
let activityId = undefined;
|
1965
2107
|
if (!hasSignature(this.activity)) {
|
1966
2108
|
let hasProof;
|
1967
2109
|
try {
|
1968
2110
|
const activity = await Activity.fromJsonLd(this.activity, this);
|
1969
|
-
activityId = activity.id?.href;
|
1970
2111
|
hasProof = await activity.getProof() != null;
|
1971
2112
|
}
|
1972
2113
|
catch {
|
@@ -1980,15 +2121,6 @@ export class InboxContextImpl extends ContextImpl {
|
|
1980
2121
|
"them due to the lack of a signature/proof.");
|
1981
2122
|
}
|
1982
2123
|
}
|
1983
|
-
if (activityId == null && typeof this.activity === "object" &&
|
1984
|
-
this.activity != null) {
|
1985
|
-
activityId =
|
1986
|
-
"@id" in this.activity && typeof this.activity["@id"] === "string"
|
1987
|
-
? this.activity["@id"]
|
1988
|
-
: "id" in this.activity && typeof this.activity.id === "string"
|
1989
|
-
? this.activity.id
|
1990
|
-
: undefined;
|
1991
|
-
}
|
1992
2124
|
if (recipients === "followers") {
|
1993
2125
|
if (identifier == null) {
|
1994
2126
|
throw new Error('If recipients is "followers", ' +
|
@@ -2007,7 +2139,7 @@ export class InboxContextImpl extends ContextImpl {
|
|
2007
2139
|
});
|
2008
2140
|
logger.debug("Forwarding activity {activityId} to inboxes:\n{inboxes}", {
|
2009
2141
|
inboxes: globalThis.Object.keys(inboxes),
|
2010
|
-
activityId,
|
2142
|
+
activityId: this.activityId,
|
2011
2143
|
activity: this.activity,
|
2012
2144
|
});
|
2013
2145
|
if (options?.immediate || this.federation.outboxQueue == null) {
|
@@ -2024,33 +2156,52 @@ export class InboxContextImpl extends ContextImpl {
|
|
2024
2156
|
promises.push(sendActivity({
|
2025
2157
|
keys,
|
2026
2158
|
activity: this.activity,
|
2027
|
-
activityId: activityId,
|
2159
|
+
activityId: this.activityId,
|
2160
|
+
activityType: this.activityType,
|
2028
2161
|
inbox: new URL(inbox),
|
2162
|
+
sharedInbox: inboxes[inbox].sharedInbox,
|
2029
2163
|
tracerProvider: this.tracerProvider,
|
2030
2164
|
}));
|
2031
2165
|
}
|
2032
2166
|
await Promise.all(promises);
|
2033
2167
|
return;
|
2034
2168
|
}
|
2035
|
-
logger.debug("Enqueuing activity {activityId} to forward later.", { activityId, activity: this.activity });
|
2169
|
+
logger.debug("Enqueuing activity {activityId} to forward later.", { activityId: this.activityId, activity: this.activity });
|
2036
2170
|
const keyJwkPairs = [];
|
2037
2171
|
for (const { keyId, privateKey } of keys) {
|
2038
2172
|
const privateKeyJwk = await exportJwk(privateKey);
|
2039
2173
|
keyJwkPairs.push({ keyId: keyId.href, privateKey: privateKeyJwk });
|
2040
2174
|
}
|
2175
|
+
const carrier = {};
|
2176
|
+
propagation.inject(context.active(), carrier);
|
2177
|
+
const promises = [];
|
2041
2178
|
for (const inbox in inboxes) {
|
2042
2179
|
const message = {
|
2043
2180
|
type: "outbox",
|
2044
2181
|
id: dntShim.crypto.randomUUID(),
|
2045
2182
|
keys: keyJwkPairs,
|
2046
2183
|
activity: this.activity,
|
2047
|
-
activityId,
|
2184
|
+
activityId: this.activityId,
|
2185
|
+
activityType: this.activityType,
|
2048
2186
|
inbox,
|
2187
|
+
sharedInbox: inboxes[inbox].sharedInbox,
|
2049
2188
|
started: new Date().toISOString(),
|
2050
2189
|
attempt: 0,
|
2051
2190
|
headers: {},
|
2191
|
+
traceContext: carrier,
|
2052
2192
|
};
|
2053
|
-
this.federation.outboxQueue.enqueue(message);
|
2193
|
+
promises.push(this.federation.outboxQueue.enqueue(message));
|
2194
|
+
}
|
2195
|
+
const results = await Promise.allSettled(promises);
|
2196
|
+
const errors = results
|
2197
|
+
.filter((r) => r.status === "rejected")
|
2198
|
+
.map((r) => r.reason);
|
2199
|
+
if (errors.length > 0) {
|
2200
|
+
logger.error("Failed to enqueue activity {activityId} to forward later:\n{errors}", { activityId: this.activityId, errors });
|
2201
|
+
if (errors.length > 1) {
|
2202
|
+
throw new AggregateError(errors, `Failed to enqueue activity ${this.activityId} to forward later.`);
|
2203
|
+
}
|
2204
|
+
throw errors[0];
|
2054
2205
|
}
|
2055
2206
|
}
|
2056
2207
|
}
|
package/esm/federation/send.js
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
import { getLogger } from "@logtape/logtape";
|
2
|
+
import { SpanKind, SpanStatusCode, trace, } from "@opentelemetry/api";
|
3
|
+
import metadata from "../deno.js";
|
2
4
|
import { signRequest } from "../sig/http.js";
|
3
5
|
/**
|
4
6
|
* Extracts the inbox URLs from recipients.
|
@@ -9,16 +11,22 @@ import { signRequest } from "../sig/http.js";
|
|
9
11
|
export function extractInboxes({ recipients, preferSharedInbox, excludeBaseUris }) {
|
10
12
|
const inboxes = {};
|
11
13
|
for (const recipient of recipients) {
|
12
|
-
|
13
|
-
|
14
|
-
|
14
|
+
let inbox;
|
15
|
+
let sharedInbox = false;
|
16
|
+
if (preferSharedInbox && recipient.endpoints?.sharedInbox != null) {
|
17
|
+
inbox = recipient.endpoints.sharedInbox;
|
18
|
+
sharedInbox = true;
|
19
|
+
}
|
20
|
+
else {
|
21
|
+
inbox = recipient.inboxId;
|
22
|
+
}
|
15
23
|
if (inbox != null && recipient.id != null) {
|
16
24
|
if (excludeBaseUris != null &&
|
17
|
-
excludeBaseUris.some((u) => u.origin
|
25
|
+
excludeBaseUris.some((u) => u.origin === inbox?.origin)) {
|
18
26
|
continue;
|
19
27
|
}
|
20
|
-
inboxes[inbox.href] ??= new Set();
|
21
|
-
inboxes[inbox.href].add(recipient.id.href);
|
28
|
+
inboxes[inbox.href] ??= { actorIds: new Set(), sharedInbox };
|
29
|
+
inboxes[inbox.href].actorIds.add(recipient.id.href);
|
22
30
|
}
|
23
31
|
}
|
24
32
|
return inboxes;
|
@@ -30,7 +38,34 @@ export function extractInboxes({ recipients, preferSharedInbox, excludeBaseUris
|
|
30
38
|
* See also {@link SendActivityParameters}.
|
31
39
|
* @throws {Error} If the activity fails to send.
|
32
40
|
*/
|
33
|
-
export
|
41
|
+
export function sendActivity(options) {
|
42
|
+
const tracerProvider = options.tracerProvider ?? trace.getTracerProvider();
|
43
|
+
const tracer = tracerProvider.getTracer(metadata.name, metadata.version);
|
44
|
+
return tracer.startActiveSpan("activitypub.send_activity", {
|
45
|
+
kind: SpanKind.CLIENT,
|
46
|
+
attributes: {
|
47
|
+
"activitypub.shared_inbox": options.sharedInbox ?? false,
|
48
|
+
},
|
49
|
+
}, async (span) => {
|
50
|
+
if (options.activityId != null) {
|
51
|
+
span.setAttribute("activitypub.activity.id", options.activityId);
|
52
|
+
}
|
53
|
+
if (options.activityType != null) {
|
54
|
+
span.setAttribute("activitypub.activity.type", options.activityType);
|
55
|
+
}
|
56
|
+
try {
|
57
|
+
await sendActivityInternal({ ...options, tracerProvider });
|
58
|
+
}
|
59
|
+
catch (e) {
|
60
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: String(e) });
|
61
|
+
throw e;
|
62
|
+
}
|
63
|
+
finally {
|
64
|
+
span.end();
|
65
|
+
}
|
66
|
+
});
|
67
|
+
}
|
68
|
+
async function sendActivityInternal({ activity, activityId, keys, inbox, headers, tracerProvider, }) {
|
34
69
|
const logger = getLogger(["fedify", "federation", "outbox"]);
|
35
70
|
headers = new Headers(headers);
|
36
71
|
headers.set("Content-Type", "application/activity+json");
|