@fedify/fedify 2.3.0-dev.1110 → 2.3.0-dev.1119

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.
Files changed (97) hide show
  1. package/dist/{assert_rejects-B-qJtC9Z.mjs → assert_rejects-DQP-q39h.mjs} +27 -2
  2. package/dist/{builder-B-Y6fwSu.mjs → builder-Ond_h57y.mjs} +3 -3
  3. package/dist/compat/mod.d.cts +1 -1
  4. package/dist/compat/mod.d.ts +1 -1
  5. package/dist/compat/outgoing-jsonld.test.mjs +1 -1
  6. package/dist/compat/public-audience.test.mjs +1 -1
  7. package/dist/compat/transformers.test.mjs +2 -2
  8. package/dist/{context-C0C_sRha.d.cts → context-Ch-ZLyTQ.d.cts} +1 -1
  9. package/dist/{context-Dqgt8saU.d.ts → context-cSUMk2da.d.ts} +1 -1
  10. package/dist/{deno-hqC7tKJn.mjs → deno-DVsHS7rA.mjs} +1 -1
  11. package/dist/{docloader-BOEuuXkX.mjs → docloader-WsWfKaE5.mjs} +2 -2
  12. package/dist/federation/builder.test.mjs +3 -3
  13. package/dist/federation/collection.test.mjs +2 -2
  14. package/dist/federation/handler.test.mjs +8 -7
  15. package/dist/federation/idempotency.test.mjs +5 -5
  16. package/dist/federation/inbox.test.mjs +1 -1
  17. package/dist/federation/keycache.test.mjs +1 -1
  18. package/dist/federation/kv.test.mjs +2 -2
  19. package/dist/federation/metrics.test.d.mts +2 -0
  20. package/dist/federation/metrics.test.mjs +107 -0
  21. package/dist/federation/middleware.test.mjs +390 -10
  22. package/dist/federation/mod.cjs +1 -1
  23. package/dist/federation/mod.d.cts +2 -2
  24. package/dist/federation/mod.d.ts +2 -2
  25. package/dist/federation/mod.js +1 -1
  26. package/dist/federation/mq.test.mjs +2 -2
  27. package/dist/federation/negotiation.test.mjs +2 -2
  28. package/dist/federation/router.test.mjs +2 -2
  29. package/dist/federation/send.test.mjs +11 -11
  30. package/dist/federation/webfinger.test.mjs +3 -3
  31. package/dist/{getMachineId-bsd-etIyxDet.mjs → getMachineId-bsd-BY01PL1n.mjs} +1 -1
  32. package/dist/{getMachineId-darwin-D23zTf4g.mjs → getMachineId-darwin-Dr1gkBkp.mjs} +1 -1
  33. package/dist/{getMachineId-win-Dpap6v5i.mjs → getMachineId-win-QEYwcJiy.mjs} +1 -1
  34. package/dist/{http-O8MYWwk8.js → http-CouJSFVK.js} +461 -37
  35. package/dist/{http-DV0il3vk.cjs → http-CubOB9wq.cjs} +513 -35
  36. package/dist/{http-BDZeS5om.d.ts → http-D6LP89UO.d.ts} +7 -1
  37. package/dist/{http-C87EWkO0.d.cts → http-D6aw3j2U.d.cts} +7 -1
  38. package/dist/{http-BLopFpvC.mjs → http-DUV8ysti.mjs} +86 -37
  39. package/dist/{key-DW1EVmtP.mjs → key-BoWaYRHm.mjs} +1 -1
  40. package/dist/{kv-cache-C3NWWiTg.js → kv-cache-DBNpsneh.js} +1 -1
  41. package/dist/{kv-cache-Dya-TWMe.cjs → kv-cache-Dz31ATUT.cjs} +1 -1
  42. package/dist/{ld-BNkk2Yal.mjs → ld-B5K1mSuG.mjs} +60 -9
  43. package/dist/{send-hokVCPu6.mjs → metrics-C4attqv0.mjs} +124 -224
  44. package/dist/{middleware-D6FbOjuK.mjs → middleware-BDKFRjue.mjs} +1 -1
  45. package/dist/{middleware-DUWeXjZR.cjs → middleware-CmsDtIHI.cjs} +75 -309
  46. package/dist/{middleware-CjzI3aYo.js → middleware-Dtjz-hSk.js} +46 -280
  47. package/dist/{middleware-DA2WTBr4.mjs → middleware-t0jC8I99.mjs} +59 -34
  48. package/dist/{mod-DXY9JF28.d.cts → mod-B-Lin9Sy.d.ts} +25 -2
  49. package/dist/{mod-DHO9lk3D.d.ts → mod-BDhgfjP7.d.cts} +25 -2
  50. package/dist/{mod-B0rWmfW5.d.cts → mod-BR_BB0bh.d.cts} +1 -1
  51. package/dist/{mod-Dx3-hqyo.d.ts → mod-C6E8rkcz.d.ts} +1 -1
  52. package/dist/{mod-BhU_H1I_.d.ts → mod-DLrRb0dx.d.ts} +1 -1
  53. package/dist/{mod-CLPnQPsv.d.cts → mod-P9tE2WmM.d.cts} +1 -1
  54. package/dist/mod.cjs +4 -4
  55. package/dist/mod.d.cts +5 -5
  56. package/dist/mod.d.ts +5 -5
  57. package/dist/mod.js +4 -4
  58. package/dist/nodeinfo/client.test.mjs +2 -2
  59. package/dist/nodeinfo/handler.test.mjs +3 -3
  60. package/dist/nodeinfo/types.test.mjs +2 -2
  61. package/dist/otel/exporter.test.mjs +2 -2
  62. package/dist/{outgoing-jsonld-BgFLCJQ_.mjs → outgoing-jsonld-BNL8AC14.mjs} +1 -1
  63. package/dist/{owner-jvJAtR5O.mjs → owner-hDxI0ufu.mjs} +2 -2
  64. package/dist/{proof-BD92WeqV.cjs → proof-BUWfVr6Q.cjs} +78 -11
  65. package/dist/{proof-mfmHH9j0.mjs → proof-DhVuz4bc.mjs} +25 -7
  66. package/dist/{proof-5kT7OUPV.js → proof-n60t8o9P.js} +78 -11
  67. package/dist/send-BPhyR5Oo.mjs +225 -0
  68. package/dist/sig/accept.test.mjs +1 -1
  69. package/dist/sig/http.test.mjs +212 -6
  70. package/dist/sig/key.test.mjs +4 -4
  71. package/dist/sig/ld.test.mjs +138 -5
  72. package/dist/sig/mod.cjs +2 -2
  73. package/dist/sig/mod.d.cts +2 -2
  74. package/dist/sig/mod.d.ts +2 -2
  75. package/dist/sig/mod.js +2 -2
  76. package/dist/sig/owner.test.mjs +4 -4
  77. package/dist/sig/proof.test.mjs +167 -6
  78. package/dist/{std__assert-CRDpx_HF.mjs → std__assert-BTEgfoJo.mjs} +2 -27
  79. package/dist/utils/docloader.test.mjs +5 -5
  80. package/dist/utils/kv-cache.test.mjs +1 -1
  81. package/dist/utils/mod.cjs +1 -1
  82. package/dist/utils/mod.d.cts +1 -1
  83. package/dist/utils/mod.d.ts +1 -1
  84. package/dist/utils/mod.js +1 -1
  85. package/package.json +5 -5
  86. /package/dist/{accept-CceiKpCy.mjs → accept-CgDcxvjV.mjs} +0 -0
  87. /package/dist/{activity-listener-tztVvlNb.mjs → activity-listener-BeTGV3wc.mjs} +0 -0
  88. /package/dist/{client-B_A6mfn3.mjs → client-Bneh_DYR.mjs} +0 -0
  89. /package/dist/{collection-CA3V5zyK.mjs → collection-Cc3DVAhE.mjs} +0 -0
  90. /package/dist/{execAsync-DCBrgFiV.mjs → execAsync-Dxb7rNf3.mjs} +0 -0
  91. /package/dist/{getMachineId-linux-ObI47Hql.mjs → getMachineId-linux-Bbhofx-s.mjs} +0 -0
  92. /package/dist/{getMachineId-unsupported-Ddu-PFeh.mjs → getMachineId-unsupported-dIOte2Ct.mjs} +0 -0
  93. /package/dist/{keys-C3kae-6B.mjs → keys-CSYsOMFG.mjs} +0 -0
  94. /package/dist/{kv-x2IvBUyq.mjs → kv-QHE0oeM3.mjs} +0 -0
  95. /package/dist/{kv-cache-CiiNwT6W.mjs → kv-cache-DihufyAQ.mjs} +0 -0
  96. /package/dist/{public-audience-N3pyOx2p.mjs → public-audience-c9zmYKgA.mjs} +0 -0
  97. /package/dist/{types-BFowWFTT.mjs → types-D09GN0uZ.mjs} +0 -0
@@ -11,7 +11,7 @@ let _opentelemetry_semantic_conventions = require("@opentelemetry/semantic-conve
11
11
  let byte_encodings_base64 = require("byte-encodings/base64");
12
12
  //#region deno.json
13
13
  var name = "@fedify/fedify";
14
- var version = "2.3.0-dev.1110+3ba84466";
14
+ var version = "2.3.0-dev.1119+6cc02662";
15
15
  //#endregion
16
16
  //#region src/sig/accept.ts
17
17
  /**
@@ -152,6 +152,382 @@ function fulfillAcceptSignature(entry, localKeyId, localAlg) {
152
152
  };
153
153
  }
154
154
  //#endregion
155
+ //#region src/federation/metrics.ts
156
+ var FederationMetrics = class {
157
+ deliverySent;
158
+ deliveryPermanentFailure;
159
+ signatureVerificationFailure;
160
+ signatureVerificationDuration;
161
+ signatureKeyFetchDuration;
162
+ deliveryDuration;
163
+ inboxProcessingDuration;
164
+ httpServerRequestCount;
165
+ httpServerRequestDuration;
166
+ queueTaskEnqueued;
167
+ queueTaskStarted;
168
+ queueTaskCompleted;
169
+ queueTaskFailed;
170
+ queueTaskDuration;
171
+ queueTaskInFlight;
172
+ fanoutRecipients;
173
+ inboxActivity;
174
+ outboxActivity;
175
+ constructor(meterProvider) {
176
+ const meter = meterProvider.getMeter(name, version);
177
+ this.deliverySent = meter.createCounter("activitypub.delivery.sent", {
178
+ description: "ActivityPub delivery attempts.",
179
+ unit: "{attempt}"
180
+ });
181
+ this.deliveryPermanentFailure = meter.createCounter("activitypub.delivery.permanent_failure", {
182
+ description: "ActivityPub deliveries abandoned as permanent failures.",
183
+ unit: "{failure}"
184
+ });
185
+ this.signatureVerificationFailure = meter.createCounter("activitypub.signature.verification_failure", {
186
+ description: "ActivityPub signature verification failures.",
187
+ unit: "{failure}"
188
+ });
189
+ this.signatureVerificationDuration = meter.createHistogram("activitypub.signature.verification.duration", {
190
+ description: "Duration of ActivityPub signature verification, including local key lookup and remote key fetches.",
191
+ unit: "ms"
192
+ });
193
+ this.signatureKeyFetchDuration = meter.createHistogram("activitypub.signature.key_fetch.duration", {
194
+ description: "Duration of public key lookup performed during ActivityPub signature verification.",
195
+ unit: "ms"
196
+ });
197
+ this.deliveryDuration = meter.createHistogram("activitypub.delivery.duration", {
198
+ description: "Duration of ActivityPub delivery attempts.",
199
+ unit: "ms"
200
+ });
201
+ this.inboxProcessingDuration = meter.createHistogram("activitypub.inbox.processing_duration", {
202
+ description: "Duration of ActivityPub inbox listener processing.",
203
+ unit: "ms"
204
+ });
205
+ this.httpServerRequestCount = meter.createCounter("fedify.http.server.request.count", {
206
+ description: "HTTP requests handled by Federation.fetch().",
207
+ unit: "{request}"
208
+ });
209
+ this.httpServerRequestDuration = meter.createHistogram("fedify.http.server.request.duration", {
210
+ description: "Duration of HTTP requests handled by Federation.fetch().",
211
+ unit: "ms",
212
+ advice: { explicitBucketBoundaries: [
213
+ 5,
214
+ 10,
215
+ 25,
216
+ 50,
217
+ 75,
218
+ 100,
219
+ 250,
220
+ 500,
221
+ 750,
222
+ 1e3,
223
+ 2500,
224
+ 5e3,
225
+ 7500,
226
+ 1e4
227
+ ] }
228
+ });
229
+ this.queueTaskEnqueued = meter.createCounter("fedify.queue.task.enqueued", {
230
+ description: "Tasks Fedify enqueued for inbox, outbox, or fanout work.",
231
+ unit: "{task}"
232
+ });
233
+ this.queueTaskStarted = meter.createCounter("fedify.queue.task.started", {
234
+ description: "Tasks Fedify began processing as a queue worker.",
235
+ unit: "{task}"
236
+ });
237
+ this.queueTaskCompleted = meter.createCounter("fedify.queue.task.completed", {
238
+ description: "Queue tasks Fedify finished processing without throwing.",
239
+ unit: "{task}"
240
+ });
241
+ this.queueTaskFailed = meter.createCounter("fedify.queue.task.failed", {
242
+ description: "Queue tasks Fedify abandoned because processing threw.",
243
+ unit: "{task}"
244
+ });
245
+ this.queueTaskDuration = meter.createHistogram("fedify.queue.task.duration", {
246
+ description: "Duration of queue task processing in Fedify workers.",
247
+ unit: "ms",
248
+ advice: { explicitBucketBoundaries: [
249
+ 5,
250
+ 10,
251
+ 25,
252
+ 50,
253
+ 75,
254
+ 100,
255
+ 250,
256
+ 500,
257
+ 750,
258
+ 1e3,
259
+ 2500,
260
+ 5e3,
261
+ 7500,
262
+ 1e4
263
+ ] }
264
+ });
265
+ this.queueTaskInFlight = meter.createUpDownCounter("fedify.queue.task.in_flight", {
266
+ description: "Queue tasks currently being processed in this Fedify process.",
267
+ unit: "{task}"
268
+ });
269
+ this.fanoutRecipients = meter.createHistogram("activitypub.fanout.recipients", {
270
+ description: "Number of recipient inboxes produced by an ActivityPub fanout task.",
271
+ unit: "{recipient}"
272
+ });
273
+ this.inboxActivity = meter.createCounter("activitypub.inbox.activity", {
274
+ description: "ActivityPub activities observed at the inbox lifecycle level: queued, processed, retried, rejected, or abandoned.",
275
+ unit: "{activity}"
276
+ });
277
+ this.outboxActivity = meter.createCounter("activitypub.outbox.activity", {
278
+ description: "ActivityPub activities observed at the outbox lifecycle level: queued, retried, or abandoned. Per-recipient delivery counters live on `activitypub.delivery.*`.",
279
+ unit: "{activity}"
280
+ });
281
+ }
282
+ recordDelivery(inbox, durationMs, success, activityType) {
283
+ const deliveryAttributes = {
284
+ "activitypub.remote.host": getRemoteHost(inbox),
285
+ "activitypub.delivery.success": success
286
+ };
287
+ if (activityType != null) deliveryAttributes["activitypub.activity.type"] = activityType;
288
+ this.deliverySent.add(1, deliveryAttributes);
289
+ this.deliveryDuration.record(durationMs, deliveryAttributes);
290
+ }
291
+ recordPermanentFailure(inbox, statusCode) {
292
+ this.deliveryPermanentFailure.add(1, {
293
+ "activitypub.remote.host": getRemoteHost(inbox),
294
+ "http.response.status_code": statusCode
295
+ });
296
+ }
297
+ recordSignatureVerificationFailure(reason, remoteHost) {
298
+ const attributes = { "activitypub.verification.failure_reason": reason };
299
+ if (remoteHost != null) attributes["activitypub.remote.host"] = remoteHost;
300
+ this.signatureVerificationFailure.add(1, attributes);
301
+ }
302
+ recordSignatureVerificationDuration(durationMs, kind, result, extra = {}) {
303
+ const attributes = {
304
+ "activitypub.signature.kind": kind,
305
+ "activitypub.signature.result": result
306
+ };
307
+ if (extra.algorithm != null) attributes["http_signatures.algorithm"] = extra.algorithm;
308
+ if (extra.failureReason != null) attributes["http_signatures.failure_reason"] = extra.failureReason;
309
+ if (extra.ldType != null) attributes["ld_signatures.type"] = extra.ldType;
310
+ if (extra.cryptosuite != null) attributes["object_integrity_proofs.cryptosuite"] = extra.cryptosuite;
311
+ this.signatureVerificationDuration.record(durationMs, attributes);
312
+ }
313
+ recordSignatureKeyFetchDuration(durationMs, kind, result) {
314
+ this.signatureKeyFetchDuration.record(durationMs, {
315
+ "activitypub.signature.kind": kind,
316
+ "activitypub.signature.key_fetch.result": result
317
+ });
318
+ }
319
+ recordInboxProcessingDuration(activityType, durationMs) {
320
+ this.inboxProcessingDuration.record(durationMs, { "activitypub.activity.type": activityType });
321
+ }
322
+ recordHttpServerRequest(method, endpoint, durationMs, options = {}) {
323
+ const attributes = {
324
+ "http.request.method": normalizeHttpMethod(method),
325
+ "fedify.endpoint": endpoint
326
+ };
327
+ if (options.statusCode != null) attributes["http.response.status_code"] = options.statusCode;
328
+ if (options.routeTemplate != null) attributes["fedify.route.template"] = options.routeTemplate;
329
+ this.httpServerRequestCount.add(1, attributes);
330
+ this.httpServerRequestDuration.record(durationMs, attributes);
331
+ }
332
+ recordQueueTaskEnqueued(common, attempt) {
333
+ const attributes = buildQueueTaskAttributes(common);
334
+ attributes["fedify.queue.task.attempt"] = attempt;
335
+ this.queueTaskEnqueued.add(1, attributes);
336
+ }
337
+ recordQueueTaskStarted(common) {
338
+ this.queueTaskStarted.add(1, buildQueueTaskAttributes(common));
339
+ }
340
+ incrementQueueTaskInFlight(common) {
341
+ this.queueTaskInFlight.add(1, buildQueueTaskInFlightAttributes(common));
342
+ }
343
+ decrementQueueTaskInFlight(common) {
344
+ this.queueTaskInFlight.add(-1, buildQueueTaskInFlightAttributes(common));
345
+ }
346
+ recordQueueTaskOutcome(common, result, durationMs) {
347
+ const attributes = buildQueueTaskAttributes(common);
348
+ attributes["fedify.queue.task.result"] = result;
349
+ if (result === "completed") this.queueTaskCompleted.add(1, attributes);
350
+ else if (result === "failed") this.queueTaskFailed.add(1, attributes);
351
+ this.queueTaskDuration.record(durationMs, attributes);
352
+ }
353
+ recordFanoutRecipients(recipientCount, activityType) {
354
+ const attributes = {};
355
+ if (activityType != null) attributes["activitypub.activity.type"] = activityType;
356
+ this.fanoutRecipients.record(recipientCount, attributes);
357
+ }
358
+ recordInboxActivity(result, activityType) {
359
+ this.inboxActivity.add(1, buildActivityLifecycleAttributes(result, activityType));
360
+ }
361
+ recordOutboxActivity(result, activityType) {
362
+ this.outboxActivity.add(1, buildActivityLifecycleAttributes(result, activityType));
363
+ }
364
+ };
365
+ function buildActivityLifecycleAttributes(result, activityType) {
366
+ const attributes = { "activitypub.processing.result": result };
367
+ if (activityType != null) attributes["activitypub.activity.type"] = activityType;
368
+ return attributes;
369
+ }
370
+ function buildQueueTaskAttributes(common) {
371
+ const attributes = { "fedify.queue.role": common.role };
372
+ const backend = getQueueBackend(common.queue);
373
+ if (backend != null) attributes["fedify.queue.backend"] = backend;
374
+ const nativeRetrial = common.queue?.nativeRetrial;
375
+ if (typeof nativeRetrial === "boolean") attributes["fedify.queue.native_retrial"] = nativeRetrial;
376
+ if (common.activityType != null) attributes["activitypub.activity.type"] = common.activityType;
377
+ return attributes;
378
+ }
379
+ function buildQueueTaskInFlightAttributes(common) {
380
+ return buildQueueTaskAttributes({
381
+ role: common.role,
382
+ queue: common.queue
383
+ });
384
+ }
385
+ /**
386
+ * Returns the constructor name of the given message queue, when it is a
387
+ * meaningful identifier. Used as a best-effort `fedify.queue.backend`
388
+ * attribute on queue task metrics; returns `undefined` for plain object
389
+ * literals (whose constructor is `Object`) so the attribute does not appear
390
+ * with a non-informative value.
391
+ * @since 2.3.0
392
+ */
393
+ function getQueueBackend(queue) {
394
+ const name = queue?.constructor?.name;
395
+ if (name == null || name === "" || name === "Object") return void 0;
396
+ return name;
397
+ }
398
+ /**
399
+ * Records `fedify.queue.task.enqueued` for an outgoing outbox enqueue and,
400
+ * for the initial attempt, also records
401
+ * `activitypub.outbox.activity{queued}`.
402
+ *
403
+ * Both `Context.sendActivity()` and `OutboxContext.forwardActivity()` enqueue
404
+ * outbox messages with the same metric attributes (role, queue, activity
405
+ * type, attempt), so they share this helper rather than each defining a local
406
+ * closure. Retry enqueues (attempt > 0) intentionally do not record a
407
+ * second `activitypub.outbox.activity{queued}`; retries are reported as
408
+ * `result=retried` from the retry-scheduling site, which has the failure
409
+ * context.
410
+ * @since 2.3.0
411
+ */
412
+ function recordOutboxEnqueue(meterProvider, outboxQueue, message) {
413
+ const metrics = getFederationMetrics(meterProvider);
414
+ metrics.recordQueueTaskEnqueued({
415
+ role: "outbox",
416
+ queue: outboxQueue,
417
+ activityType: message.activityType
418
+ }, message.attempt);
419
+ if (message.attempt === 0) metrics.recordOutboxActivity("queued", message.activityType);
420
+ }
421
+ /**
422
+ * Records `activitypub.fanout.recipients` with the number of recipient
423
+ * inboxes a single fanout produced. The histogram is unitless count
424
+ * (one measurement per fanout enqueue). Recipient URLs are deliberately
425
+ * not recorded; only the activity type, when known.
426
+ * @since 2.3.0
427
+ */
428
+ function recordFanoutRecipients(meterProvider, recipientCount, activityType) {
429
+ getFederationMetrics(meterProvider).recordFanoutRecipients(recipientCount, activityType);
430
+ }
431
+ /**
432
+ * Records one `activitypub.inbox.activity` measurement. The
433
+ * `activitypub.processing.result` attribute is always present;
434
+ * `activitypub.activity.type` is recorded only when Fedify already knows
435
+ * the activity type.
436
+ * @since 2.3.0
437
+ */
438
+ function recordInboxActivity(meterProvider, result, activityType) {
439
+ getFederationMetrics(meterProvider).recordInboxActivity(result, activityType);
440
+ }
441
+ /**
442
+ * Records one `activitypub.outbox.activity` measurement. The
443
+ * `activitypub.processing.result` attribute is always present;
444
+ * `activitypub.activity.type` is recorded only when Fedify already knows
445
+ * the activity type (it is always known for outbox lifecycle events).
446
+ * @since 2.3.0
447
+ */
448
+ function recordOutboxActivity(meterProvider, result, activityType) {
449
+ getFederationMetrics(meterProvider).recordOutboxActivity(result, activityType);
450
+ }
451
+ /**
452
+ * Times an awaited public key fetch and records exactly one
453
+ * `activitypub.signature.key_fetch.duration` measurement, classifying the
454
+ * outcome as `hit`, `fetched`, or `error` based on the `cached` flag and
455
+ * whether the returned key is non-null. Errors thrown by the fetch are
456
+ * reported as `error` and rethrown, so verifier behavior is unchanged.
457
+ *
458
+ * Shared by the three signature verifiers (HTTP, Linked Data, Object
459
+ * Integrity Proofs); the only per-call variation is the
460
+ * `activitypub.signature.kind` attribute value.
461
+ * @since 2.3.0
462
+ */
463
+ async function measureSignatureKeyFetch(meterProvider, kind, fetch) {
464
+ const start = performance.now();
465
+ try {
466
+ const result = await fetch();
467
+ getFederationMetrics(meterProvider).recordSignatureKeyFetchDuration(getDurationMs(start), kind, result.key != null ? result.cached ? "hit" : "fetched" : "error");
468
+ return result;
469
+ } catch (error) {
470
+ getFederationMetrics(meterProvider).recordSignatureKeyFetchDuration(getDurationMs(start), kind, "error");
471
+ throw error;
472
+ }
473
+ }
474
+ /**
475
+ * Whether the given thrown value is an `AbortError`.
476
+ *
477
+ * `processQueuedTask` distinguishes aborted tasks (recorded as
478
+ * `fedify.queue.task.result=aborted`) from other failures so that backend
479
+ * shutdown signals do not inflate the `fedify.queue.task.failed` counter.
480
+ * @since 2.3.0
481
+ */
482
+ function isAbortError$1(error) {
483
+ if (error == null || typeof error !== "object") return false;
484
+ const name = error.name;
485
+ return typeof name === "string" && name === "AbortError";
486
+ }
487
+ const KNOWN_HTTP_METHODS = new Set([
488
+ "CONNECT",
489
+ "DELETE",
490
+ "GET",
491
+ "HEAD",
492
+ "OPTIONS",
493
+ "PATCH",
494
+ "POST",
495
+ "PUT",
496
+ "QUERY",
497
+ "TRACE"
498
+ ]);
499
+ function normalizeHttpMethod(method) {
500
+ const upper = method.toUpperCase();
501
+ return KNOWN_HTTP_METHODS.has(upper) ? upper : "_OTHER";
502
+ }
503
+ const federationMetrics = /* @__PURE__ */ new WeakMap();
504
+ /**
505
+ * Gets the cached Fedify metric instruments for a meter provider.
506
+ * @since 2.3.0
507
+ */
508
+ function getFederationMetrics(meterProvider = _opentelemetry_api.metrics.getMeterProvider()) {
509
+ let instruments = federationMetrics.get(meterProvider);
510
+ if (instruments == null) {
511
+ instruments = new FederationMetrics(meterProvider);
512
+ federationMetrics.set(meterProvider, instruments);
513
+ }
514
+ return instruments;
515
+ }
516
+ /**
517
+ * Gets the bounded remote host attribute value for a URL.
518
+ * @since 2.3.0
519
+ */
520
+ function getRemoteHost(url) {
521
+ return url.hostname;
522
+ }
523
+ /**
524
+ * Gets an elapsed duration in milliseconds from a `performance.now()` value.
525
+ * @since 2.3.0
526
+ */
527
+ function getDurationMs(start) {
528
+ return Math.max(0, performance.now() - start);
529
+ }
530
+ //#endregion
155
531
  //#region src/sig/key.ts
156
532
  /**
157
533
  * Checks if the given key is valid and supported. No-op if the key is valid,
@@ -800,6 +1176,27 @@ function parseKeyId(value) {
800
1176
  function getKeyFetchErrorName(error) {
801
1177
  return error.name || error.constructor.name || "Error";
802
1178
  }
1179
+ /**
1180
+ * Known draft-cavage `algorithm` parameter values, used to keep the
1181
+ * `http_signatures.algorithm` metric attribute on a bounded set. The header
1182
+ * field is attacker-controlled and not used to select the verification
1183
+ * algorithm, so unknown values are dropped from the metric to prevent
1184
+ * cardinality blow-up.
1185
+ */
1186
+ const DRAFT_KNOWN_ALGORITHMS = new Set([
1187
+ "ecdsa-sha256",
1188
+ "ecdsa-sha384",
1189
+ "ecdsa-sha512",
1190
+ "ed25519",
1191
+ "hs2019",
1192
+ "rsa-sha1",
1193
+ "rsa-sha256",
1194
+ "rsa-sha512"
1195
+ ]);
1196
+ function classifyHttpVerifyResult(result) {
1197
+ if (result.verified) return "verified";
1198
+ return result.reason.type === "noSignature" ? "missing" : "rejected";
1199
+ }
803
1200
  function recordVerificationResult(span, result) {
804
1201
  span.setAttribute("http_signatures.verified", result.verified);
805
1202
  if (result.verified === true) return;
@@ -843,27 +1240,37 @@ async function verifyRequestDetailed(request, options = {}) {
843
1240
  span.setAttribute(_opentelemetry_semantic_conventions.ATTR_URL_FULL, request.url);
844
1241
  for (const [name, value] of request.headers) span.setAttribute((0, _opentelemetry_semantic_conventions.ATTR_HTTP_REQUEST_HEADER)(name), value);
845
1242
  }
1243
+ const start = performance.now();
1244
+ const metricsContext = {};
1245
+ let result;
1246
+ let threw = false;
846
1247
  try {
847
1248
  let spec = options.spec;
848
1249
  if (spec == null) spec = request.headers.has("Signature-Input") ? "rfc9421" : "draft-cavage-http-signatures-12";
849
- let result;
850
- if (spec === "rfc9421") result = await verifyRequestRfc9421(request, span, options);
851
- else result = await verifyRequestDraft(request, span, options);
1250
+ if (spec === "rfc9421") result = await verifyRequestRfc9421(request, span, metricsContext, options);
1251
+ else result = await verifyRequestDraft(request, span, metricsContext, options);
852
1252
  recordVerificationResult(span, result);
853
1253
  if (!result.verified) span.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
854
1254
  return result;
855
1255
  } catch (error) {
1256
+ threw = true;
856
1257
  span.setStatus({
857
1258
  code: _opentelemetry_api.SpanStatusCode.ERROR,
858
1259
  message: String(error)
859
1260
  });
860
1261
  throw error;
861
1262
  } finally {
1263
+ const classified = threw ? "error" : classifyHttpVerifyResult(result);
1264
+ const failureReason = result != null && !result.verified && result.reason.type !== "noSignature" ? result.reason.type : void 0;
1265
+ getFederationMetrics(options.meterProvider).recordSignatureVerificationDuration(getDurationMs(start), "http", classified, {
1266
+ algorithm: metricsContext.algorithm,
1267
+ failureReason
1268
+ });
862
1269
  span.end();
863
1270
  }
864
1271
  });
865
1272
  }
866
- async function verifyRequestDraft(request, span, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, tracerProvider } = {}) {
1273
+ async function verifyRequestDraft(request, span, metricsContext, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, meterProvider, tracerProvider } = {}) {
867
1274
  const logger = (0, _logtape_logtape.getLogger)([
868
1275
  "fedify",
869
1276
  "sig",
@@ -1011,13 +1418,17 @@ async function verifyRequestDraft(request, span, { documentLoader, contextLoader
1011
1418
  const keyIdUrl = parseKeyId(keyId);
1012
1419
  if (keyIdUrl == null) return invalidSignatureResult(null);
1013
1420
  span?.setAttribute("http_signatures.key_id", keyId);
1014
- if ("algorithm" in sigValues) span?.setAttribute("http_signatures.algorithm", sigValues.algorithm);
1015
- const { key, cached, fetchError } = await fetchKeyDetailed(keyIdUrl, _fedify_vocab.CryptographicKey, {
1421
+ if ("algorithm" in sigValues) {
1422
+ span?.setAttribute("http_signatures.algorithm", sigValues.algorithm);
1423
+ const normalizedAlgorithm = sigValues.algorithm.toLowerCase();
1424
+ if (DRAFT_KNOWN_ALGORITHMS.has(normalizedAlgorithm)) metricsContext.algorithm = normalizedAlgorithm;
1425
+ }
1426
+ const { key, cached, fetchError } = await measureSignatureKeyFetch(meterProvider, "http", () => fetchKeyDetailed(keyIdUrl, _fedify_vocab.CryptographicKey, {
1016
1427
  documentLoader,
1017
1428
  contextLoader,
1018
1429
  keyCache,
1019
1430
  tracerProvider
1020
- });
1431
+ }));
1021
1432
  if (fetchError != null) return keyFetchErrorResult(keyIdUrl, fetchError);
1022
1433
  if (key == null) return invalidSignatureResult(keyIdUrl);
1023
1434
  const headerNames = headers.split(/\s+/g);
@@ -1039,7 +1450,7 @@ async function verifyRequestDraft(request, span, { documentLoader, contextLoader
1039
1450
  signature,
1040
1451
  message
1041
1452
  });
1042
- return await verifyRequestDetailed(originalRequest, {
1453
+ return await verifyRequestDraft(originalRequest, span, metricsContext, {
1043
1454
  documentLoader,
1044
1455
  contextLoader,
1045
1456
  timeWindow,
@@ -1047,7 +1458,9 @@ async function verifyRequestDraft(request, span, { documentLoader, contextLoader
1047
1458
  keyCache: {
1048
1459
  get: () => Promise.resolve(void 0),
1049
1460
  set: async (keyId, key) => await keyCache?.set(keyId, key)
1050
- }
1461
+ },
1462
+ meterProvider,
1463
+ tracerProvider
1051
1464
  });
1052
1465
  }
1053
1466
  logger.debug("Failed to verify with the fetched key {keyId}; signature {signature} is invalid. Check if the key is correct or if the signed message is correct. The message to sign is:\n{message}", {
@@ -1123,7 +1536,7 @@ async function verifyRfc9421ContentDigest(digestHeader, body) {
1123
1536
  }
1124
1537
  return false;
1125
1538
  }
1126
- async function verifyRequestRfc9421(request, span, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, tracerProvider } = {}) {
1539
+ async function verifyRequestRfc9421(request, span, metricsContext, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, meterProvider, tracerProvider } = {}) {
1127
1540
  const logger = (0, _logtape_logtape.getLogger)([
1128
1541
  "fedify",
1129
1542
  "sig",
@@ -1157,9 +1570,14 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
1157
1570
  return invalidSignatureResult(null);
1158
1571
  }
1159
1572
  let failure = noSignatureResult();
1573
+ let failureAlgorithm;
1574
+ const setFailure = (result, algorithm) => {
1575
+ failure = result;
1576
+ failureAlgorithm = algorithm;
1577
+ };
1160
1578
  for (const sigName of signatureNames) {
1161
1579
  if (!signatures[sigName]) {
1162
- failure = invalidSignatureResult(parseKeyId(signatureInputs[sigName]?.keyId));
1580
+ setFailure(invalidSignatureResult(parseKeyId(signatureInputs[sigName]?.keyId)));
1163
1581
  continue;
1164
1582
  }
1165
1583
  const sigInput = signatureInputs[sigName];
@@ -1170,7 +1588,7 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
1170
1588
  signatureName: sigName,
1171
1589
  signatureInput: signatureInputHeader
1172
1590
  });
1173
- failure = invalidSignatureResult(null);
1591
+ setFailure(invalidSignatureResult(null));
1174
1592
  continue;
1175
1593
  }
1176
1594
  if (!sigInput.created) {
@@ -1178,7 +1596,7 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
1178
1596
  signatureName: sigName,
1179
1597
  signatureInput: signatureInputHeader
1180
1598
  });
1181
- failure = invalidSignatureResult(keyId);
1599
+ setFailure(invalidSignatureResult(keyId));
1182
1600
  continue;
1183
1601
  }
1184
1602
  const signatureCreated = Temporal.Instant.fromEpochMilliseconds(sigInput.created * 1e3);
@@ -1190,14 +1608,14 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
1190
1608
  created: signatureCreated.toString(),
1191
1609
  now: now.toString()
1192
1610
  });
1193
- failure = invalidSignatureResult(keyId);
1611
+ setFailure(invalidSignatureResult(keyId));
1194
1612
  continue;
1195
1613
  } else if (Temporal.Instant.compare(signatureCreated, now.subtract(tw)) < 0) {
1196
1614
  logger.debug("Failed to verify; signature created time is too far in the past.", {
1197
1615
  created: signatureCreated.toString(),
1198
1616
  now: now.toString()
1199
1617
  });
1200
- failure = invalidSignatureResult(keyId);
1618
+ setFailure(invalidSignatureResult(keyId));
1201
1619
  continue;
1202
1620
  }
1203
1621
  }
@@ -1205,34 +1623,34 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
1205
1623
  const contentDigestHeader = request.headers.get("Content-Digest");
1206
1624
  if (!contentDigestHeader) {
1207
1625
  logger.debug("Failed to verify; Content-Digest header required but not found.", { components: sigInput.components });
1208
- failure = invalidSignatureResult(keyId);
1626
+ setFailure(invalidSignatureResult(keyId));
1209
1627
  continue;
1210
1628
  }
1211
1629
  if (!await verifyRfc9421ContentDigest(contentDigestHeader, await request.arrayBuffer())) {
1212
1630
  logger.debug("Failed to verify; Content-Digest verification failed.", { contentDigest: contentDigestHeader });
1213
- failure = invalidSignatureResult(keyId);
1631
+ setFailure(invalidSignatureResult(keyId));
1214
1632
  continue;
1215
1633
  }
1216
1634
  }
1217
1635
  span?.setAttribute("http_signatures.key_id", sigInput.keyId);
1218
1636
  span?.setAttribute("http_signatures.created", sigInput.created.toString());
1219
1637
  if (keyId == null) {
1220
- failure = invalidSignatureResult(null);
1638
+ setFailure(invalidSignatureResult(null));
1221
1639
  continue;
1222
1640
  }
1223
- const { key, cached, fetchError } = await fetchKeyDetailed(keyId, _fedify_vocab.CryptographicKey, {
1641
+ const { key, cached, fetchError } = await measureSignatureKeyFetch(meterProvider, "http", () => fetchKeyDetailed(keyId, _fedify_vocab.CryptographicKey, {
1224
1642
  documentLoader,
1225
1643
  contextLoader,
1226
1644
  keyCache,
1227
1645
  tracerProvider
1228
- });
1646
+ }));
1229
1647
  if (fetchError != null) {
1230
- failure = keyFetchErrorResult(keyId, fetchError);
1648
+ setFailure(keyFetchErrorResult(keyId, fetchError));
1231
1649
  continue;
1232
1650
  }
1233
1651
  if (!key) {
1234
1652
  logger.debug("Failed to fetch key: {keyId}", { keyId: sigInput.keyId });
1235
- failure = invalidSignatureResult(keyId);
1653
+ setFailure(invalidSignatureResult(keyId));
1236
1654
  continue;
1237
1655
  }
1238
1656
  let alg = sigInput.alg?.toLowerCase();
@@ -1244,12 +1662,13 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
1244
1662
  }
1245
1663
  if (alg) span?.setAttribute("http_signatures.algorithm", alg);
1246
1664
  const algorithm = alg && rfc9421AlgorithmMap[alg];
1665
+ const candidateAlgorithm = algorithm ? alg : void 0;
1247
1666
  if (!algorithm) {
1248
1667
  logger.debug("Failed to verify; unsupported algorithm: {algorithm}", {
1249
1668
  algorithm: sigInput.alg,
1250
1669
  supported: Object.keys(rfc9421AlgorithmMap)
1251
1670
  });
1252
- failure = invalidSignatureResult(keyId);
1671
+ setFailure(invalidSignatureResult(keyId));
1253
1672
  continue;
1254
1673
  }
1255
1674
  let signatureBase;
@@ -1260,20 +1679,22 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
1260
1679
  error,
1261
1680
  signatureInput: sigInput
1262
1681
  });
1263
- failure = invalidSignatureResult(keyId);
1682
+ setFailure(invalidSignatureResult(keyId), candidateAlgorithm);
1264
1683
  continue;
1265
1684
  }
1266
1685
  const signatureBaseBytes = new TextEncoder().encode(signatureBase);
1267
1686
  span?.setAttribute("http_signatures.signature", (0, byte_encodings_hex.encodeHex)(sigBytes));
1268
1687
  try {
1269
- if (await crypto.subtle.verify(algorithm, key.publicKey, sigBytes.slice(), signatureBaseBytes)) return {
1270
- verified: true,
1271
- key,
1272
- signatureLabel: sigName
1273
- };
1274
- else if (cached) {
1688
+ if (await crypto.subtle.verify(algorithm, key.publicKey, sigBytes.slice(), signatureBaseBytes)) {
1689
+ metricsContext.algorithm = candidateAlgorithm;
1690
+ return {
1691
+ verified: true,
1692
+ key,
1693
+ signatureLabel: sigName
1694
+ };
1695
+ } else if (cached) {
1275
1696
  logger.debug("Failed to verify with cached key {keyId}; retrying with fresh key...", { keyId: sigInput.keyId });
1276
- return await verifyRequestDetailed(originalRequest, {
1697
+ return await verifyRequestRfc9421(originalRequest, span, metricsContext, {
1277
1698
  documentLoader,
1278
1699
  contextLoader,
1279
1700
  timeWindow,
@@ -1282,14 +1703,16 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
1282
1703
  get: () => Promise.resolve(void 0),
1283
1704
  set: async (keyId, key) => await keyCache?.set(keyId, key)
1284
1705
  },
1285
- spec: "rfc9421"
1706
+ spec: "rfc9421",
1707
+ meterProvider,
1708
+ tracerProvider
1286
1709
  });
1287
1710
  } else {
1288
1711
  logger.debug("Failed to verify signature with fetched key {keyId}; signature invalid.", {
1289
1712
  keyId: sigInput.keyId,
1290
1713
  signatureBase
1291
1714
  });
1292
- failure = invalidSignatureResult(keyId);
1715
+ setFailure(invalidSignatureResult(keyId), candidateAlgorithm);
1293
1716
  }
1294
1717
  } catch (error) {
1295
1718
  logger.debug("Error during signature verification: {error}", {
@@ -1297,9 +1720,10 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
1297
1720
  keyId: sigInput.keyId,
1298
1721
  algorithm: sigInput.alg
1299
1722
  });
1300
- failure = invalidSignatureResult(keyId);
1723
+ setFailure(invalidSignatureResult(keyId), candidateAlgorithm);
1301
1724
  }
1302
1725
  }
1726
+ metricsContext.algorithm = failureAlgorithm;
1303
1727
  return failure;
1304
1728
  }
1305
1729
  /**
@@ -1563,12 +1987,42 @@ Object.defineProperty(exports, "generateCryptoKeyPair", {
1563
1987
  return generateCryptoKeyPair;
1564
1988
  }
1565
1989
  });
1990
+ Object.defineProperty(exports, "getDurationMs", {
1991
+ enumerable: true,
1992
+ get: function() {
1993
+ return getDurationMs;
1994
+ }
1995
+ });
1996
+ Object.defineProperty(exports, "getFederationMetrics", {
1997
+ enumerable: true,
1998
+ get: function() {
1999
+ return getFederationMetrics;
2000
+ }
2001
+ });
2002
+ Object.defineProperty(exports, "getRemoteHost", {
2003
+ enumerable: true,
2004
+ get: function() {
2005
+ return getRemoteHost;
2006
+ }
2007
+ });
1566
2008
  Object.defineProperty(exports, "importJwk", {
1567
2009
  enumerable: true,
1568
2010
  get: function() {
1569
2011
  return importJwk;
1570
2012
  }
1571
2013
  });
2014
+ Object.defineProperty(exports, "isAbortError", {
2015
+ enumerable: true,
2016
+ get: function() {
2017
+ return isAbortError$1;
2018
+ }
2019
+ });
2020
+ Object.defineProperty(exports, "measureSignatureKeyFetch", {
2021
+ enumerable: true,
2022
+ get: function() {
2023
+ return measureSignatureKeyFetch;
2024
+ }
2025
+ });
1572
2026
  Object.defineProperty(exports, "name", {
1573
2027
  enumerable: true,
1574
2028
  get: function() {
@@ -1587,6 +2041,30 @@ Object.defineProperty(exports, "parseRfc9421SignatureInput", {
1587
2041
  return parseRfc9421SignatureInput;
1588
2042
  }
1589
2043
  });
2044
+ Object.defineProperty(exports, "recordFanoutRecipients", {
2045
+ enumerable: true,
2046
+ get: function() {
2047
+ return recordFanoutRecipients;
2048
+ }
2049
+ });
2050
+ Object.defineProperty(exports, "recordInboxActivity", {
2051
+ enumerable: true,
2052
+ get: function() {
2053
+ return recordInboxActivity;
2054
+ }
2055
+ });
2056
+ Object.defineProperty(exports, "recordOutboxActivity", {
2057
+ enumerable: true,
2058
+ get: function() {
2059
+ return recordOutboxActivity;
2060
+ }
2061
+ });
2062
+ Object.defineProperty(exports, "recordOutboxEnqueue", {
2063
+ enumerable: true,
2064
+ get: function() {
2065
+ return recordOutboxEnqueue;
2066
+ }
2067
+ });
1590
2068
  Object.defineProperty(exports, "signRequest", {
1591
2069
  enumerable: true,
1592
2070
  get: function() {