@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.
@@ -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 this.#listenOutboxMessage(ctxData, message);
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 this.#listenInboxMessage(ctxData, message);
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
- const listener = this.inboxListeners?.dispatch(activity);
279
- if (listener == null) {
280
- logger.error("Unsupported activity type:\n{activity}", {
281
- activityId: activity.id?.href,
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 delay = this.inboxRetryPolicy({
305
- elapsedTime: dntShim.Temporal.Instant.from(message.started).until(dntShim.Temporal.Now.instant()),
306
- attempts: message.attempt,
307
- });
308
- if (delay != null) {
309
- logger.error("Failed to process the incoming activity {activityId} (attempt " +
310
- "#{attempt}); retry...:\n{error}", {
311
- error,
312
- attempt: message.attempt,
313
- activityId: activity.id?.href,
314
- activity: message.activity,
315
- recipient: message.identifier,
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
- this.inboxQueue?.enqueue({
318
- ...message,
319
- attempt: message.attempt + 1,
320
- }, {
321
- delay: dntShim.Temporal.Duration.compare(delay, { seconds: 0 }) < 0
322
- ? dntShim.Temporal.Duration.from({ seconds: 0 })
323
- : delay,
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
- else {
327
- logger.error("Failed to process the incoming activity {activityId} after " +
328
- "{trial} attempts; giving up:\n{error}", {
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
- return;
336
- }
337
- if (cacheKey != null) {
338
- await this.kv.set(cacheKey, true, {
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
- activity = activity.clone({
978
- id: new URL(`urn:uuid:${dntShim.crypto.randomUUID()}`),
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
- async sendActivity(sender, recipients, activity, options = {}) {
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
- return await this.federation.sendActivity(keys, expandedRecipients, activity, opts);
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
- constructor(recipient, activity, options) {
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 forwardActivity(forwarder, recipients, options) {
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
  }
@@ -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
- const inbox = preferSharedInbox
13
- ? recipient.endpoints?.sharedInbox ?? recipient.inboxId
14
- : recipient.inboxId;
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 == inbox.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 async function sendActivity({ activity, activityId, keys, inbox, headers, tracerProvider, }) {
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");