@fedify/fedify 1.3.0-dev.570 → 1.3.0-dev.575
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 +63 -43
- package/esm/federation/middleware.js +143 -34
- 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/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 +3 -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
package/CHANGES.md
CHANGED
@@ -59,6 +59,9 @@ To be released.
|
|
59
59
|
|
60
60
|
- Added `getTypeId()` function.
|
61
61
|
|
62
|
+
- `Context.sendActivity()` and `InboxContext.forwardActivity()` methods now
|
63
|
+
reject when they fail to enqueue the task. [[#192]]
|
64
|
+
|
62
65
|
- Fedify now supports OpenTelemetry for tracing. [[#170]]
|
63
66
|
|
64
67
|
- Added `Context.tracerProvider` property.
|
@@ -103,6 +106,7 @@ To be released.
|
|
103
106
|
[#173]: https://github.com/dahlia/fedify/issues/173
|
104
107
|
[#183]: https://github.com/dahlia/fedify/pull/183
|
105
108
|
[#186]: https://github.com/dahlia/fedify/pull/186
|
109
|
+
[#192]: https://github.com/dahlia/fedify/issues/192
|
106
110
|
|
107
111
|
|
108
112
|
Version 1.2.8
|
package/esm/deno.js
CHANGED
@@ -71,28 +71,12 @@ export async function handleObject(request, { values, context, objectDispatcher,
|
|
71
71
|
},
|
72
72
|
});
|
73
73
|
}
|
74
|
-
export function handleCollection(request,
|
75
|
-
const
|
76
|
-
|
74
|
+
export async function handleCollection(request, { name, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, tracerProvider, onUnauthorized, onNotFound, onNotAcceptable, }) {
|
75
|
+
const spanName = name.trim().replace(/\s+/g, "_");
|
76
|
+
tracerProvider = tracerProvider ?? trace.getTracerProvider();
|
77
77
|
const tracer = tracerProvider.getTracer(metadata.name, metadata.version);
|
78
78
|
const url = new URL(request.url);
|
79
79
|
const cursor = url.searchParams.get("cursor");
|
80
|
-
return tracer.startActiveSpan(cursor == null
|
81
|
-
? `activitypub.dispatch_collection ${name}`
|
82
|
-
: `activitypub.dispatch_collection_page ${name}`, { kind: SpanKind.SERVER }, async (span) => {
|
83
|
-
try {
|
84
|
-
return await handleCollectionInternal(request, cursor, params, span);
|
85
|
-
}
|
86
|
-
catch (e) {
|
87
|
-
span.setStatus({ code: SpanStatusCode.ERROR, message: String(e) });
|
88
|
-
throw e;
|
89
|
-
}
|
90
|
-
finally {
|
91
|
-
span.end();
|
92
|
-
}
|
93
|
-
});
|
94
|
-
}
|
95
|
-
async function handleCollectionInternal(request, cursor, { name, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, onUnauthorized, onNotFound, onNotAcceptable, }, span) {
|
96
80
|
if (collectionCallbacks == null)
|
97
81
|
return await onNotFound(request);
|
98
82
|
let collection;
|
@@ -100,21 +84,41 @@ async function handleCollectionInternal(request, cursor, { name, identifier, uri
|
|
100
84
|
if (cursor == null) {
|
101
85
|
const firstCursor = await collectionCallbacks.firstCursor?.(context, identifier);
|
102
86
|
const totalItems = await collectionCallbacks.counter?.(context, identifier);
|
103
|
-
if (totalItems != null) {
|
104
|
-
span.setAttribute("activitypub.collection.total_items", Number(totalItems));
|
105
|
-
}
|
106
87
|
if (firstCursor == null) {
|
107
|
-
const
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
88
|
+
const itemsOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection ${spanName}`, {
|
89
|
+
kind: SpanKind.SERVER,
|
90
|
+
attributes: {
|
91
|
+
"activitypub.collection.id": baseUri.href,
|
92
|
+
"activitypub.collection.type": OrderedCollection.typeId.href,
|
93
|
+
},
|
94
|
+
}, async (span) => {
|
95
|
+
if (totalItems != null) {
|
96
|
+
span.setAttribute("activitypub.collection.total_items", Number(totalItems));
|
97
|
+
}
|
98
|
+
try {
|
99
|
+
const page = await collectionCallbacks.dispatcher(context, identifier, null, filter);
|
100
|
+
if (page == null) {
|
101
|
+
span.setStatus({ code: SpanStatusCode.ERROR });
|
102
|
+
return await onNotFound(request);
|
103
|
+
}
|
104
|
+
const { items } = page;
|
105
|
+
span.setAttribute("fedify.collection.items", items.length);
|
106
|
+
return items;
|
107
|
+
}
|
108
|
+
catch (e) {
|
109
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: String(e) });
|
110
|
+
throw e;
|
111
|
+
}
|
112
|
+
finally {
|
113
|
+
span.end();
|
114
|
+
}
|
115
|
+
});
|
116
|
+
if (itemsOrResponse instanceof Response)
|
117
|
+
return itemsOrResponse;
|
114
118
|
collection = new OrderedCollection({
|
115
119
|
id: baseUri,
|
116
120
|
totalItems: totalItems == null ? null : Number(totalItems),
|
117
|
-
items: filterCollectionItems(
|
121
|
+
items: filterCollectionItems(itemsOrResponse, name, filterPredicate),
|
118
122
|
});
|
119
123
|
}
|
120
124
|
else {
|
@@ -135,16 +139,36 @@ async function handleCollectionInternal(request, cursor, { name, identifier, uri
|
|
135
139
|
}
|
136
140
|
}
|
137
141
|
else {
|
138
|
-
span.setAttribute("fedify.collection.cursor", cursor);
|
139
142
|
const uri = new URL(baseUri);
|
140
143
|
uri.searchParams.set("cursor", cursor);
|
141
|
-
const
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
144
|
+
const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name}`, {
|
145
|
+
kind: SpanKind.SERVER,
|
146
|
+
attributes: {
|
147
|
+
"activitypub.collection.id": uri.href,
|
148
|
+
"activitypub.collection.type": OrderedCollectionPage.typeId.href,
|
149
|
+
"fedify.collection.cursor": cursor,
|
150
|
+
},
|
151
|
+
}, async (span) => {
|
152
|
+
try {
|
153
|
+
const page = await collectionCallbacks.dispatcher(context, identifier, cursor, filter);
|
154
|
+
if (page == null) {
|
155
|
+
span.setStatus({ code: SpanStatusCode.ERROR });
|
156
|
+
return await onNotFound(request);
|
157
|
+
}
|
158
|
+
span.setAttribute("fedify.collection.items", page.items.length);
|
159
|
+
return page;
|
160
|
+
}
|
161
|
+
catch (e) {
|
162
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: String(e) });
|
163
|
+
throw e;
|
164
|
+
}
|
165
|
+
finally {
|
166
|
+
span.end();
|
167
|
+
}
|
168
|
+
});
|
169
|
+
if (pageOrResponse instanceof Response)
|
170
|
+
return pageOrResponse;
|
171
|
+
const { items, prevCursor, nextCursor } = pageOrResponse;
|
148
172
|
let prev = null;
|
149
173
|
if (prevCursor != null) {
|
150
174
|
prev = new URL(context.url);
|
@@ -174,10 +198,6 @@ async function handleCollectionInternal(request, cursor, { name, identifier, uri
|
|
174
198
|
return await onUnauthorized(request);
|
175
199
|
}
|
176
200
|
}
|
177
|
-
if (collection.id != null) {
|
178
|
-
span.setAttribute("activitypub.collection.id", collection.id.href);
|
179
|
-
}
|
180
|
-
span.setAttribute("activitypub.collection.type", getTypeId(collection).href);
|
181
201
|
const jsonLd = await collection.toJsonLd(context);
|
182
202
|
return new Response(JSON.stringify(jsonLd), {
|
183
203
|
headers: {
|
@@ -425,7 +445,7 @@ export async function handleInbox(request, { recipient, context, inboxContextFac
|
|
425
445
|
});
|
426
446
|
}
|
427
447
|
try {
|
428
|
-
await listener(inboxContextFactory(recipient, json), activity);
|
448
|
+
await listener(inboxContextFactory(recipient, json, activity.id?.href, getTypeId(activity).href), activity);
|
429
449
|
}
|
430
450
|
catch (error) {
|
431
451
|
try {
|
@@ -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,9 +157,31 @@ export class FederationImpl {
|
|
157
157
|
await Promise.all(promises);
|
158
158
|
}
|
159
159
|
#listenQueue(ctxData, message) {
|
160
|
+
const tracer = this.#getTracer();
|
160
161
|
return withContext({ messageId: message.id }, async () => {
|
161
162
|
if (message.type === "outbox") {
|
162
|
-
|
163
|
+
const extractedContext = propagation.extract(context.active(), message.traceContext);
|
164
|
+
await tracer.startActiveSpan("activitypub.outbox", {
|
165
|
+
kind: SpanKind.CONSUMER,
|
166
|
+
attributes: { "activitypub.activity.type": message.activityType },
|
167
|
+
}, extractedContext, async (span) => {
|
168
|
+
if (message.activityId != null) {
|
169
|
+
span.setAttribute("activitypub.activity.id", message.activityId);
|
170
|
+
}
|
171
|
+
try {
|
172
|
+
await this.#listenOutboxMessage(ctxData, message);
|
173
|
+
}
|
174
|
+
catch (e) {
|
175
|
+
span.setStatus({
|
176
|
+
code: SpanStatusCode.ERROR,
|
177
|
+
message: String(e),
|
178
|
+
});
|
179
|
+
throw e;
|
180
|
+
}
|
181
|
+
finally {
|
182
|
+
span.end();
|
183
|
+
}
|
184
|
+
});
|
163
185
|
}
|
164
186
|
else if (message.type === "inbox") {
|
165
187
|
await this.#listenInboxMessage(ctxData, message);
|
@@ -194,7 +216,9 @@ export class FederationImpl {
|
|
194
216
|
keys,
|
195
217
|
activity: message.activity,
|
196
218
|
activityId: message.activityId,
|
219
|
+
activityType: message.activityType,
|
197
220
|
inbox: new URL(message.inbox),
|
221
|
+
sharedInbox: message.sharedInbox,
|
198
222
|
headers: new Headers(message.headers),
|
199
223
|
tracerProvider: this.tracerProvider,
|
200
224
|
});
|
@@ -220,7 +244,7 @@ export class FederationImpl {
|
|
220
244
|
if (delay != null) {
|
221
245
|
logger.error("Failed to send activity {activityId} to {inbox} (attempt " +
|
222
246
|
"#{attempt}); retry...:\n{error}", { ...logData, error });
|
223
|
-
this.outboxQueue?.enqueue({
|
247
|
+
await this.outboxQueue?.enqueue({
|
224
248
|
...message,
|
225
249
|
attempt: message.attempt + 1,
|
226
250
|
}, {
|
@@ -286,7 +310,7 @@ export class FederationImpl {
|
|
286
310
|
return;
|
287
311
|
}
|
288
312
|
try {
|
289
|
-
await listener(context.toInboxContext(message.identifier, message.activity), activity);
|
313
|
+
await listener(context.toInboxContext(message.identifier, message.activity, activity.id?.href, getTypeId(activity).href), activity);
|
290
314
|
}
|
291
315
|
catch (error) {
|
292
316
|
try {
|
@@ -314,7 +338,7 @@ export class FederationImpl {
|
|
314
338
|
activity: message.activity,
|
315
339
|
recipient: message.identifier,
|
316
340
|
});
|
317
|
-
this.inboxQueue?.enqueue({
|
341
|
+
await this.inboxQueue?.enqueue({
|
318
342
|
...message,
|
319
343
|
attempt: message.attempt + 1,
|
320
344
|
}, {
|
@@ -960,7 +984,7 @@ export class FederationImpl {
|
|
960
984
|
};
|
961
985
|
return setters;
|
962
986
|
}
|
963
|
-
async sendActivity(keys, recipients, activity, options) {
|
987
|
+
async sendActivity(keys, recipients, activity, options, span) {
|
964
988
|
const logger = getLogger(["fedify", "federation", "outbox"]);
|
965
989
|
const { preferSharedInbox, immediate, excludeBaseUris, collectionSync, contextData, } = options;
|
966
990
|
if (keys.length < 1) {
|
@@ -974,9 +998,9 @@ export class FederationImpl {
|
|
974
998
|
throw new TypeError("The activity to send must have at least one actor property.");
|
975
999
|
}
|
976
1000
|
if (activity.id == null) {
|
977
|
-
|
978
|
-
|
979
|
-
|
1001
|
+
const id = new URL(`urn:uuid:${dntShim.crypto.randomUUID()}`);
|
1002
|
+
activity = activity.clone({ id });
|
1003
|
+
span?.setAttribute("activitypub.activity.id", id.href);
|
980
1004
|
}
|
981
1005
|
const inboxes = extractInboxes({
|
982
1006
|
recipients: Array.isArray(recipients) ? recipients : [recipients],
|
@@ -1061,9 +1085,11 @@ export class FederationImpl {
|
|
1061
1085
|
keys,
|
1062
1086
|
activity: jsonLd,
|
1063
1087
|
activityId: activity.id?.href,
|
1088
|
+
activityType: getTypeId(activity).href,
|
1064
1089
|
inbox: new URL(inbox),
|
1090
|
+
sharedInbox: inboxes[inbox].sharedInbox,
|
1065
1091
|
headers: collectionSync == null ? undefined : new Headers({
|
1066
|
-
"Collection-Synchronization": await buildCollectionSynchronizationHeader(collectionSync, inboxes[inbox]),
|
1092
|
+
"Collection-Synchronization": await buildCollectionSynchronizationHeader(collectionSync, inboxes[inbox].actorIds),
|
1067
1093
|
}),
|
1068
1094
|
tracerProvider: this.tracerProvider,
|
1069
1095
|
}));
|
@@ -1079,6 +1105,9 @@ export class FederationImpl {
|
|
1079
1105
|
}
|
1080
1106
|
if (!this.manuallyStartQueue)
|
1081
1107
|
this.#startQueue(contextData);
|
1108
|
+
const carrier = {};
|
1109
|
+
propagation.inject(context.active(), carrier);
|
1110
|
+
const promises = [];
|
1082
1111
|
for (const inbox in inboxes) {
|
1083
1112
|
const message = {
|
1084
1113
|
type: "outbox",
|
@@ -1086,14 +1115,28 @@ export class FederationImpl {
|
|
1086
1115
|
keys: keyJwkPairs,
|
1087
1116
|
activity: jsonLd,
|
1088
1117
|
activityId: activity.id?.href,
|
1118
|
+
activityType: getTypeId(activity).href,
|
1089
1119
|
inbox,
|
1120
|
+
sharedInbox: inboxes[inbox].sharedInbox,
|
1090
1121
|
started: new Date().toISOString(),
|
1091
1122
|
attempt: 0,
|
1092
1123
|
headers: collectionSync == null ? {} : {
|
1093
|
-
"Collection-Synchronization": await buildCollectionSynchronizationHeader(collectionSync, inboxes[inbox]),
|
1124
|
+
"Collection-Synchronization": await buildCollectionSynchronizationHeader(collectionSync, inboxes[inbox].actorIds),
|
1094
1125
|
},
|
1126
|
+
traceContext: carrier,
|
1095
1127
|
};
|
1096
|
-
this.outboxQueue.enqueue(message);
|
1128
|
+
promises.push(this.outboxQueue.enqueue(message));
|
1129
|
+
}
|
1130
|
+
const results = await Promise.allSettled(promises);
|
1131
|
+
const errors = results
|
1132
|
+
.filter((r) => r.status === "rejected")
|
1133
|
+
.map((r) => r.reason);
|
1134
|
+
if (errors.length > 0) {
|
1135
|
+
logger.error("Failed to enqueue activity {activityId} to send later: {errors}", { activityId: activity.id.href, errors });
|
1136
|
+
if (errors.length > 1) {
|
1137
|
+
throw new AggregateError(errors, `Failed to enqueue activity ${activityId} to send later.`);
|
1138
|
+
}
|
1139
|
+
throw errors[0];
|
1097
1140
|
}
|
1098
1141
|
}
|
1099
1142
|
fetch(request, options) {
|
@@ -1368,8 +1411,8 @@ export class ContextImpl {
|
|
1368
1411
|
this.invokedFromActorKeyPairsDispatcher =
|
1369
1412
|
invokedFromActorKeyPairsDispatcher;
|
1370
1413
|
}
|
1371
|
-
toInboxContext(recipient, activity) {
|
1372
|
-
return new InboxContextImpl(recipient, activity, {
|
1414
|
+
toInboxContext(recipient, activity, activityId, activityType) {
|
1415
|
+
return new InboxContextImpl(recipient, activity, activityId, activityType, {
|
1373
1416
|
url: this.url,
|
1374
1417
|
federation: this.federation,
|
1375
1418
|
data: this.data,
|
@@ -1738,7 +1781,36 @@ export class ContextImpl {
|
|
1738
1781
|
contextLoader: options.contextLoader ?? this.contextLoader,
|
1739
1782
|
});
|
1740
1783
|
}
|
1741
|
-
|
1784
|
+
sendActivity(sender, recipients, activity, options = {}) {
|
1785
|
+
const tracer = this.tracerProvider.getTracer(metadata.name, metadata.version);
|
1786
|
+
return tracer.startActiveSpan("activitypub.outbox", {
|
1787
|
+
kind: this.federation.outboxQueue == null || options.immediate
|
1788
|
+
? SpanKind.CLIENT
|
1789
|
+
: SpanKind.PRODUCER,
|
1790
|
+
attributes: {
|
1791
|
+
"activitypub.activity.type": getTypeId(activity).href,
|
1792
|
+
"activitypub.activity.to": activity.toIds.map((to) => to.href),
|
1793
|
+
"activitypub.activity.cc": activity.toIds.map((cc) => cc.href),
|
1794
|
+
"activitypub.activity.bto": activity.btoIds.map((bto) => bto.href),
|
1795
|
+
"activitypub.activity.bcc": activity.toIds.map((bcc) => bcc.href),
|
1796
|
+
},
|
1797
|
+
}, async (span) => {
|
1798
|
+
try {
|
1799
|
+
if (activity.id != null) {
|
1800
|
+
span.setAttribute("activitypub.activity.id", activity.id.href);
|
1801
|
+
}
|
1802
|
+
await this.sendActivityInternal(sender, recipients, activity, options, span);
|
1803
|
+
}
|
1804
|
+
catch (e) {
|
1805
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: String(e) });
|
1806
|
+
throw e;
|
1807
|
+
}
|
1808
|
+
finally {
|
1809
|
+
span.end();
|
1810
|
+
}
|
1811
|
+
});
|
1812
|
+
}
|
1813
|
+
async sendActivityInternal(sender, recipients, activity, options = {}, span) {
|
1742
1814
|
let keys;
|
1743
1815
|
let identifier = null;
|
1744
1816
|
if ("identifier" in sender || "username" in sender || "handle" in sender) {
|
@@ -1766,6 +1838,7 @@ export class ContextImpl {
|
|
1766
1838
|
identifier = mapped;
|
1767
1839
|
}
|
1768
1840
|
}
|
1841
|
+
span.setAttribute("fedify.actor.identifier", identifier);
|
1769
1842
|
keys = await this.getKeyPairsFromIdentifier(identifier);
|
1770
1843
|
if (keys.length < 1) {
|
1771
1844
|
throw new Error(`No key pair found for actor ${JSON.stringify(identifier)}.`);
|
@@ -1805,7 +1878,8 @@ export class ContextImpl {
|
|
1805
1878
|
else {
|
1806
1879
|
expandedRecipients = [recipients];
|
1807
1880
|
}
|
1808
|
-
|
1881
|
+
span.setAttribute("activitypub.inboxes", expandedRecipients.length);
|
1882
|
+
return await this.federation.sendActivity(keys, expandedRecipients, activity, opts, span);
|
1809
1883
|
}
|
1810
1884
|
async *getFollowers(identifier) {
|
1811
1885
|
if (this.federation.followersCallbacks == null) {
|
@@ -1912,12 +1986,39 @@ class RequestContextImpl extends ContextImpl {
|
|
1912
1986
|
export class InboxContextImpl extends ContextImpl {
|
1913
1987
|
recipient;
|
1914
1988
|
activity;
|
1915
|
-
|
1989
|
+
activityId;
|
1990
|
+
activityType;
|
1991
|
+
constructor(recipient, activity, activityId, activityType, options) {
|
1916
1992
|
super(options);
|
1917
1993
|
this.recipient = recipient;
|
1918
1994
|
this.activity = activity;
|
1995
|
+
this.activityId = activityId;
|
1996
|
+
this.activityType = activityType;
|
1919
1997
|
}
|
1920
|
-
|
1998
|
+
forwardActivity(forwarder, recipients, options) {
|
1999
|
+
const tracer = this.tracerProvider.getTracer(metadata.name, metadata.version);
|
2000
|
+
return tracer.startActiveSpan("activitypub.outbox", {
|
2001
|
+
kind: this.federation.outboxQueue == null || options?.immediate
|
2002
|
+
? SpanKind.CLIENT
|
2003
|
+
: SpanKind.PRODUCER,
|
2004
|
+
attributes: { "activitypub.activity.type": this.activityType },
|
2005
|
+
}, async (span) => {
|
2006
|
+
try {
|
2007
|
+
if (this.activityId != null) {
|
2008
|
+
span.setAttribute("activitypub.activity.id", this.activityId);
|
2009
|
+
}
|
2010
|
+
await this.forwardActivityInternal(forwarder, recipients, options);
|
2011
|
+
}
|
2012
|
+
catch (e) {
|
2013
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: String(e) });
|
2014
|
+
throw e;
|
2015
|
+
}
|
2016
|
+
finally {
|
2017
|
+
span.end();
|
2018
|
+
}
|
2019
|
+
});
|
2020
|
+
}
|
2021
|
+
async forwardActivityInternal(forwarder, recipients, options) {
|
1921
2022
|
const logger = getLogger(["fedify", "federation", "inbox"]);
|
1922
2023
|
let keys;
|
1923
2024
|
let identifier = null;
|
@@ -1961,12 +2062,10 @@ export class InboxContextImpl extends ContextImpl {
|
|
1961
2062
|
else {
|
1962
2063
|
keys = [forwarder];
|
1963
2064
|
}
|
1964
|
-
let activityId = undefined;
|
1965
2065
|
if (!hasSignature(this.activity)) {
|
1966
2066
|
let hasProof;
|
1967
2067
|
try {
|
1968
2068
|
const activity = await Activity.fromJsonLd(this.activity, this);
|
1969
|
-
activityId = activity.id?.href;
|
1970
2069
|
hasProof = await activity.getProof() != null;
|
1971
2070
|
}
|
1972
2071
|
catch {
|
@@ -1980,15 +2079,6 @@ export class InboxContextImpl extends ContextImpl {
|
|
1980
2079
|
"them due to the lack of a signature/proof.");
|
1981
2080
|
}
|
1982
2081
|
}
|
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
2082
|
if (recipients === "followers") {
|
1993
2083
|
if (identifier == null) {
|
1994
2084
|
throw new Error('If recipients is "followers", ' +
|
@@ -2007,7 +2097,7 @@ export class InboxContextImpl extends ContextImpl {
|
|
2007
2097
|
});
|
2008
2098
|
logger.debug("Forwarding activity {activityId} to inboxes:\n{inboxes}", {
|
2009
2099
|
inboxes: globalThis.Object.keys(inboxes),
|
2010
|
-
activityId,
|
2100
|
+
activityId: this.activityId,
|
2011
2101
|
activity: this.activity,
|
2012
2102
|
});
|
2013
2103
|
if (options?.immediate || this.federation.outboxQueue == null) {
|
@@ -2024,33 +2114,52 @@ export class InboxContextImpl extends ContextImpl {
|
|
2024
2114
|
promises.push(sendActivity({
|
2025
2115
|
keys,
|
2026
2116
|
activity: this.activity,
|
2027
|
-
activityId: activityId,
|
2117
|
+
activityId: this.activityId,
|
2118
|
+
activityType: this.activityType,
|
2028
2119
|
inbox: new URL(inbox),
|
2120
|
+
sharedInbox: inboxes[inbox].sharedInbox,
|
2029
2121
|
tracerProvider: this.tracerProvider,
|
2030
2122
|
}));
|
2031
2123
|
}
|
2032
2124
|
await Promise.all(promises);
|
2033
2125
|
return;
|
2034
2126
|
}
|
2035
|
-
logger.debug("Enqueuing activity {activityId} to forward later.", { activityId, activity: this.activity });
|
2127
|
+
logger.debug("Enqueuing activity {activityId} to forward later.", { activityId: this.activityId, activity: this.activity });
|
2036
2128
|
const keyJwkPairs = [];
|
2037
2129
|
for (const { keyId, privateKey } of keys) {
|
2038
2130
|
const privateKeyJwk = await exportJwk(privateKey);
|
2039
2131
|
keyJwkPairs.push({ keyId: keyId.href, privateKey: privateKeyJwk });
|
2040
2132
|
}
|
2133
|
+
const carrier = {};
|
2134
|
+
propagation.inject(context.active(), carrier);
|
2135
|
+
const promises = [];
|
2041
2136
|
for (const inbox in inboxes) {
|
2042
2137
|
const message = {
|
2043
2138
|
type: "outbox",
|
2044
2139
|
id: dntShim.crypto.randomUUID(),
|
2045
2140
|
keys: keyJwkPairs,
|
2046
2141
|
activity: this.activity,
|
2047
|
-
activityId,
|
2142
|
+
activityId: this.activityId,
|
2143
|
+
activityType: this.activityType,
|
2048
2144
|
inbox,
|
2145
|
+
sharedInbox: inboxes[inbox].sharedInbox,
|
2049
2146
|
started: new Date().toISOString(),
|
2050
2147
|
attempt: 0,
|
2051
2148
|
headers: {},
|
2149
|
+
traceContext: carrier,
|
2052
2150
|
};
|
2053
|
-
this.federation.outboxQueue.enqueue(message);
|
2151
|
+
promises.push(this.federation.outboxQueue.enqueue(message));
|
2152
|
+
}
|
2153
|
+
const results = await Promise.allSettled(promises);
|
2154
|
+
const errors = results
|
2155
|
+
.filter((r) => r.status === "rejected")
|
2156
|
+
.map((r) => r.reason);
|
2157
|
+
if (errors.length > 0) {
|
2158
|
+
logger.error("Failed to enqueue activity {activityId} to forward later:\n{errors}", { activityId: this.activityId, errors });
|
2159
|
+
if (errors.length > 1) {
|
2160
|
+
throw new AggregateError(errors, `Failed to enqueue activity ${this.activityId} to forward later.`);
|
2161
|
+
}
|
2162
|
+
throw errors[0];
|
2054
2163
|
}
|
2055
2164
|
}
|
2056
2165
|
}
|
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");
|